宮商定時制計算部

平成29年度秋季 基本情報技術者試験(FE)
午後 問12 アセンブラ

こちらは顧問が書いています。
アセンブラは、C言語の滑り止めとして8月中旬くらいから個人的に勉強を始めました(計算部の活動ではアセンブラは取り上げていません)。
正直、試験用に作られたCASL2では実用的なプログラムを作ることはできませんので、モチベーションが上がらず学習がなかなか進みませんでしたが、2か月弱くらいでFEの過去問がどうにか解ける程度には慣れることができました。
試験本番では、「問9・C言語と問12・アセンブラは、よりページ数の少ない方を取る」と決めていました(同じページ数なら、学習期間が5か月ほど長いC言語にするつもりでした)。
結局、問9は7ページもあるのに、問12は4ページしかなかったので、迷うことなく問12を取りました。
家に帰ってからC言語も解きましたが、分量はともかく、難易度は同じくらいだと感じましたので、そちらを選択しても大丈夫だったかも、と思いました(ただし、C言語の方が解くのに時間がかかりました)。
アセンブラを勉強して得になったのは、以下の点です。
  1. 基数変換や論理演算に自然と慣れることができたこと。
  2. ハードウェアの機械語命令問題に抵抗感がなくなったこと。
  3. C言語やC++におけるポインタ理解の助けとなったこと。
してみると、CASL2自体は確かに実用的ではないけれども、その学習はけっこう他分野の学習の足しになっていた、という印象です(さすが「教育用」言語)。
そういうわけで、今回の計算部の課題のうち、問9・C言語の解説は部員A君に任せることにして、ここでは問12・アセンブラの方を解説してみようと思います。



前半は、連続する2語から成るビット列αから、別のビット列βと一致する部分を検索するプログラム BSRH(BIT SEARCHの略?)についてでした。
図1
プログラム BSRH についての問題は設問2までですので、ソースコードを読む前に、そこまでざっと目を通すのが得策です。
というのは、もし「これこれの値で実行した場合どうなるか」というような問題が出題されていた場合、最初からその値でトレースしながらコードを読む方が、時間と労力の節約になるからです。
見ると、設問1は穴埋め問題、設問2はコードの一部を別の命令に置き換える問題になっています。
プログラム BSRH については「指定された値でトレースせよ」という問題がありませんので、何か適当な値を設定してトレースすることにしましょう。
ここでは次のような値を設定します。
ビット列α = 0101 0000 0000 0000 1111 1111 1111 1111
……1語目(アドレス#8000)= 0101 0000 0000 0000
……2語目(アドレス#8001)= 1111 1111 1111 1111
ビット列β = 101(長さ n = 3)
見て分かるとおり、このβは、αの2番目の位置で一致します(ビット番号は0始まりとする仕様なので、一致の位置 p = 1)。
説明によれば、「αの先頭(ビット位置0)からβと照合」する、つまり左から照合していくプログラムですから、この値の設定だとわりと始まってすぐに一致してしまいますが、これはトレースの労力を節約するためです。
図2
それならαの最初のビット位置(p = 0)で一致するように値を設定してしまえばさらに短時間で終わるのですが、そうしなかったのは、ループ時の動作を見たいからです。
一致部分以外のビットは、分かりやすいように、1語目は「0」で、2語目は「1」で埋めることにしました。
では、コードを読んでいきましょう。
この段階で、次の値がすでに設定されています。
3 LD GR0,=-1
GR0に「-1」を設定しています。
これは、説明(2)に「一致する部分ビット列がない場合は -1 を、GR0に設定」する、とあるので、一致するビット列がない場合の値を初期値として設定しているのです。
【レジスタの値】GR0 = -1(10)
【レジスタの値】の(10)は、10進数を表します。
4 LD GR6,=#FFFF ; マスク作成
GR6をすべて「1」にしています。
コメントに「マスク作成」とあります。
マスクとは、ビット演算において、必要な情報を取り出す場合に使うビットパターンです。
【レジスタの値】GR6 = 1111……1
……ところで、アセンブラの問題をトレースするとき、1語16ビットのビット列をどのようにメモしていけばよいでしょうか?
16個の0と1を全部書いていたら長くて時間がかかりますし、間違えやすくもなります。
そこで私は、上記【レジスタの値】のように、同じ値が数個以上並ぶ部分は「……」で書くようにしています。
「……」は、そのすぐ左の値(上の例では1)がずっと並んでいることを表しています。
ここのGR6は全部1ですから、「1……」と書いてもよいことになります。
……もっとよい方法があれば、ぜひご教授ください。
5 SRL GR6,0,GR3
GR6をGR3 = 3だけ右シフトしています。
論理シフトなので移動して空いたビットには「0」が入ります。
【レジスタの値】GR6 = 0001……1
6 XOR GR6,=#FFFF
GR6と、すべて「1」のビット列とをXOR演算しています。
XORは排他的論理和ですので、すべて「1」のビット列でXORすると、元のビット列が反転します。
【レジスタの値】GR6 = 1110……0
……GR6への連続的な処理から見て、また次の行に新しいコメントが付いていることから見て、どうやら4行目の「マスク作成」はここで完成したようです。
ということは、コメントが付いた行は、そのコメントの処理が「そこで始まる」ことを示しているだけで、その処理は複数行にわたることもある、ということになります。
7 LD GR4,0,GR1 ; αの取出し
GR4にαの1語目の値を入れています。
GR1にはαの「先頭アドレス」が入っているので、そのアドレスに「格納されている値」を取り出すためには「GR4,0,GR1」と書く必要があります。
もし「GR4,GR1」と真ん中の「0」を書かなければ、GR1に入っている値(ここではアドレス「#8000」)がそのままGR4にコピーされてしまいます。
さて、コメントは「αの取出し」となっていますね。
αは連続する2語ですので、まずGR4に1語目を取り出しています。
【レジスタの値】GR4 = 0101 0……0(←αの1語目)
8 LD GR5,1,GR1
GR5にαの2語目の値を入れています。
7行目のコメントはこの行にもかかっています。
【レジスタの値】GR5 = 1111……1(←αの2語目)
9 LD GR1,=32
GR1に定数「32」を設定しています。
何のための処理なのかはまだ分かりません。
【レジスタの値】GR1 = 32(10)
10 SUBA GR1,GR3 ; GR1 ← 32 - n
GR1に「32 - n」を設定しています。
n はビット列βの長さで、GR3に格納されています。n = 3 ですから、GR1には「29」が設定されます。
何の処理なのか、まだ分かりません。
【レジスタの値】GR1 = 29(10)
11 LD GR3,GR1 ; GR3 ← 32 - n
GR3にGR1の値、今求めた「29」をコピーしています。
【レジスタの値】GR3 = 29(10)
12 LP LD GR7,GR4
ラベル「LP」が貼られています。
「LP」は LOOP の略称に見えますから、ここがループの起点になるのかもしれません。
GR7にGR4、すなわちαの1語目をコピーしています。
【レジスタの値】GR7 = 0101 0……0(←αの1語目)
13 AND GR7,GR6
GR7 = 「αの1語目」と、GR6 = 「マスク」とをAND演算しています。
この処理によって、マスクの「1」のビットは元のまま残り、マスクの「0」のビットはすべて「0」になります。
「0101 0……0」AND 「1110……0」=「0100……0」。
【レジスタの値】GR7 = 0100……0
これで、GR7には「1語目のビット列の、最初の n( = 3)ビット分はそのままで、残りを0で埋めたビット列」が格納されたことになります。
図3
14 空欄 a
最初の空欄です。
13行目までを読んだだけでは埋められませんので、先を見てみます。
15 JZE FOUND ; 一致あり
「JZE FOUND」は「14行目の処理結果が0になったら、ラベル FOUND にジャンプせよ」という命令です。
コメントに「一致あり」とありますので、14行目はどうやら「二つのビット列が一致する場合0になる」命令が入ればよさそうです。
設問 1 空欄 a の選択肢を見ると、すべてGR7とGR2との演算になっています。
それぞれのレジスタの具体的な値はともかく、「二つのビット列が一致する場合0になる」 = 「同一のビット列どうしを演算した結果が0になる」選択肢を探してみると、オ「XORGR7,GR2」しか該当しません。
設問 1 空欄 a 答え …… オ
では、具体的な値でトレースしてみましょう。
今、GR7には「αの1語目のビット列の、最初の n( = 3)ビット分はそのままで、残りを0で埋めたビット列」が、GR2には「検索するビット列βを左詰めにし、残りを0で埋めたビット列」が格納されているのでした。
GR7 = 0100 0000 0000 0000
GR2 = 1010 0000 0000 0000
一致していないのは一目瞭然ですね。
この両者をXOR演算すると、
XORGR7,GR2 = 1110 0000 0000 0000
となって、0になりませんから、15行目でラベル FOUND には跳ばず、そのまま16行目に入ることになります。
16 SUBA GR1,=1
GR1から1を引いています。GR1には「29」が入っていましたので、ここで「28」になりました。
【レジスタの値】GR1 = 28(10)
……ところで、GR1は何をしているのでしょうか?
最初の値は(32 - n)= 29(10行目)で、14行目でビット列が一致しなかった場合、(15行目でラベル FOUND に跳ぶことなく、この行に進んで)1が引かれる、ということは、どうやらこれはループ回数を制御するカウンタ変数のようです。
ループが最も多く回る場合、すなわちαの中にβと一致するビット列がどこにもない場合、ループは全部で何回実行されるのか?
32ビットのビット列の左端からnビットのビット列を探す処理の最終回は、下の図のような感じになります。
図4
βが3ビットだった場合、30回行われるようです。
ということは、カウンタの上限は(32 - n + 1)= 30回に設定しておかなければならないのではないでしょうか?
GR1に設定されたのは(32 - n)= 29でしたから、これだと1回少なくなってしまいそうですよね。
この回数のズレはどのように処理するのか?
続きを見てみましょう。
17 JMI FIN ; 一致なし
「JMI FIN」は「16行目の処理結果がマイナスなら、ラベル FIN にジャンプせよ」という命令です。
上の疑問はこれで解決しました。
カウンタ変数29で30回繰り返すためには、1で終わるのではなく、0で終わればよいわけです。
つまり、カウンタ0(=今の設定では30回目の処理)の回はそのまま処理を続け、それが終わってカウンタ-1になった時点で処理を終わらせればよいのです。
ここにはその命令「カウンタがマイナスになったら終わり」が書かれています。
……ラベル FIN を「終わり」と断定してしまいましたが、FEの場合、ラベル FIN はほぼ終了処理の箇所に貼られていると考えて間違いありません。
18 SLL GR4,1 ; αを1ビット左シフト
コメントに「αを1ビット左シフト」とあります。
GR4にはαの1語目が入っているのでした。ここではそれを左に1ビット論理シフトしています。
何のために?
もうほぼ見当はつきますが、とりあえず続きを見てみましょう。
【レジスタの値】GR4 = 1010 0……0(←αの1語目の1ビット左論理シフト)
19 SLL GR5,1
GR5にはαの2語目が入っているのでした。
それを左に1ビット論理シフトしています。
18行目の処理の続きのようです。
【レジスタの値】GR5 = 1111 1……0(←αの2語目の1ビット左論理シフト)
20 空欄 b
二つ目の空欄です。
先を見てみます。
21 JUMP LP
「JUMP LP」は「ラベル LP にジャンプせよ」という命令です。
ここから12行目のラベル LP に跳ぶということは、以前通過した行に戻る、つまりループということになりますので、LP は、やはりループ開始位置を示すラベルだったようです。
さて、18行目と19行目は、ビット列αを1ビット左に論理シフトする命令でした。
この行の JUMP 命令から見て、18~20行目は次のループの準備処理と見てよさそうです。
1語目と2語目を左シフトして、あとは何をすれば次のループに進めるのでしょうか?
検索ビット列βはGR2に左詰めで格納されています(残りのビットは0埋め)。
これとαのビット列を左から順に比較していくためには、18、19行目で行ったように、αを1ビットずつ左シフトして、毎回検索開始位置を左端に持っていけばよさそうです。
ところが、ここで一つ問題が出てきます。
αは「連続する2語」なのであり、前の16ビットと後ろの16ビットは繫がっていないのです。
ということは、2語目の左端が1だった場合、左シフトするとその1はこぼれ落ちてなくなってしまいます(下図1回目の赤字ビット参照)。
そうなったときは、1語目の右端に1を足しておかねばなりません(下図2回目の赤字ビット参照)。
20行目の空欄 b にはその処理が入りそうです。
図5
設問 1 空欄 b の選択肢を見ると、ラベル NEXT に進むか FOUND に進むかの二通りに分かれます。
FOUND は、15行目で見たように、βと同じビット列が「見つかった場合」に進む行のラベルでしたから、除外してしまってよさそうです。
「19行目で1がこぼれ落ちた場合=オーバーフローした場合、ラベル NEXT に進む」選択肢は、エ「JOVNEXT」です。
念のためにジャンプ先も見ておきましょう。
22 NEXT OR GR4,=#0001
GR4には、1ビット左に論理シフトした、αの1語目が入っています。
#0001でOR演算するのですから、GR4の右端の0が1となります。
ということで、2語目の左シフトがオーバーフローした場合の処理としてぴったりですので、先の解答も確定としてよいでしょう。
【レジスタの値】GR4 = 1010 0……1
設問 1 空欄 b 答え …… エ
これで空欄は二つとも埋まりましたが、処理の終わりまで確認しましょう。
23 JUMP LP
2行上と同じ命令ですね。
ということは、2語目の左シフトがオーバーフローした場合、22行目の処理だけをプラスしてからループ2回目に入る、ということになります。
12 LP LD GR7,GR4 ★ここからループ2回目
ループ2回目です。
GR4にはαの1語目を1ビット左シフトしたビット列が入っています。
【レジスタの値】GR7 = 1010 0……1
13 AND GR7,GR6
GR6はマスクでした。
GR7とのAND演算で、左端からnビット分を除いてすべて0にします。
【レジスタの値】GR7 = 1010 0……0
14 XOR GR7,GR2
空欄 a です。
GR7とGR2をXOR演算しています。
両者が同じ値なら0になります。
GR7 = 1010 0000 0000 0000
GR2 = 1010 0000 0000 0000
同じですので、結果は0です。
【レジスタの値】GR7 = 0000……0
15 JZE FOUND ; 一致あり
今回は14行目の結果が0、すなわち「一致あり」ですので、ラベル FOUND にジャンプします。
24 FOUND SUBA GR3,GR1 ; pの算出
この行に入ったのは初めてです。
コメントに「pの算出」とありますので、ここでα中のβと一致したビット位置を求めるのでしょう。
GR3には、11行目で求めた(32 - n)= 29 が入っています。
これは「ループの最多回数 - 1」です。
そこからループのカウンタ変数GR1の値を引いています。
今GR1には「28」が入っているので、演算の結果は「1」になります。
【レジスタの値】GR3 = 1(10)
なぜGR3 - GR1でpが出るのでしょうか?
pとGR1とGR3の関係はどうなっているのでしょう?
ループ1回目で一致 p = 0 GR3 = 29 GR1 = 29
ループ2回目で一致 p = 1 GR3 = 29 GR1 = 28
トレースしたのはここまでですが、もしループが3回目以降も回った場合は、次のように変化していくことは明らかです。
ループ3回目で一致 p = 2 GR3 = 29 GR1 = 27
ループ4回目で一致 p = 3 GR3 = 29 GR1 = 26
ループ5回目で一致 p = 4 GR3 = 29 GR1 = 25
……
ループ30回目で一致 p = 29 GR3 = 29 GR1 = 0
GR3はループの上限回数でした。
GR1はカウンタ変数で、GR3と同じ値でスタートしてループ一回ごとに-1され、pはαの検索文字位置で、こちらは0からスタートして一回ごとに+1されます。
つまり、pは「GR1の減った値」に対応しているのです(GR1が1減ればpは1、GR1が2減ればpは2、といった具合)。
GR1が初期値からいくつ減ったのかは、初期値をずっと格納しているGR3から減算することで得られます。
従って、GR3 - GR1 = p という関係が成立するのですね。
25 LD GR0,GR3
今求めたpをGR0にコピーしています。
説明(2)に、「pを」「GR0に設定して主プログラムに戻る」とありましたので、その処理です。
GR0には、一致するビット列がなかった場合の「-1」が格納されていますが、今回は一致するビット列がありましたので、pで上書きしています。
【レジスタの値】GR0 = 1(10)
さて、プログラム1 BSRH については、まだ設問2が残っています。
プログラムの4~6行目を書き換える問題です。
4~6行目はGR6にマスクを設定する処理でした。
左端からβの長さ = nビット分だけを「1」として、残りを「0」にするのでしたね。
nは、この段階ではGR3に設定されています。
もしnが3なら、
GR6 = 1110 0000 0000 0000
となればいいわけです。
演算の結果がこの値になるのは、ウ「SRAGR6,-1,GR3」だけです。
最初にGR6に#8000 = 1000 0000 0000 0000を設定しているので、左端からnビット分だけ「1」にしたいのなら、すでに左端に一つ「1」があるので、(n - 1)ビットだけ右へ算術シフトすればよいということになります。
似たような選択肢にオがありますが、オは論理シフトなので、シフトした分が0になってしまって不適切です。
SRAGR6,-1,GR3 = 1110 0000 0000 0000 …… OK
SRLGR6,-1,GR3 = 0010 0000 0000 0000 …… NG
設問 2 答え …… ウ



設問3からは、新しいプログラム BREP についての問題です。
先の BSRH を利用してビット列αの先頭からビット列βと照合し、一致したビット列があれば、「その部分を別のビット列γで置き換える」というプログラムです(とすると、BREP は BIT REPLACE の略でしょうか?)。
一致したビット列の有無および一致部分のビット番号までは BSRH で出せますので、このプログラムは、今カギ括弧でくくった部分を処理する内容になっているはずです。
値の設定の仕方は BSRH と同じで、置き換えるビット列γがGR4に設定される点が新たに加わっています。
上と同様に、コードを読む前に設問にざっと目を通します。
すると、設問3はプログラムの穴埋め問題、設問4は……出ました、具体的な値でのトレース問題。
時間と労力の節約のために、最初から設問4の値でトレースしながらコードを読むのが賢明です。
設問4の次の設定でコードを読んでいくことにしましょう。
ビット列α = 1111 1111 1111 0011 0111 1111 1111 1111
……1語目(アドレス#8000)= 1111 1111 1111 0011
……2語目(アドレス#8001)= 0111 1111 1111 1111
ビット列β = 11011(長さ n = 5)
ビット列γ = 11110
見たところ、βはαのp = 14の位置、すなわち1語目の15番目のビット位置で一致しますので、そこをγで置き換える処理になるはずです。
図6
この設定で少しやっかいなのは、βが5文字(n = 5)で一致位置p = 14ということになると、置き換え対象部分が2語にまたがってしまうことです。
プログラムではそれをどのように解決しているのでしょうか?
では、コードを読んでいきましょう。
3 CALL BSRH
早くも BSRH を呼び出していますね。
このように、後のプログラムで前のプログラムを利用している場合は、前のプログラムの方はもうトレースせず(この試験ではそんなことをしている余裕はないでしょう)、暗算したり図を描いたりして、返ってくる値だけを慎重に出すようにします。
ここで BSRH から返ってくる値は「GR0 = 14」のはずです。
【レジスタの値】GR0 = 14(10)
なお、先の説明(3)に「副プログラム BSRH から戻るとき、汎用レジスタGR1~GR7の内容は元に戻す」とありましたので、GR1~GR4は、このプログラム開始時に設定した値に戻されます。
4 LD GR2,GR0
GR2にGR0、すなわちp = 14をコピーしています。
【レジスタの値】GR2 = 14(10)
5 JMI FIN
「4行目の処理がマイナスなら、ラベル FIN へジャンプせよ」というコマンドです。
GR0は、一致するビット列がない場合は「-1」が設定されるのでした。
つまり、αにβと一致するビット列がなかった場合は、ここでラベル FIN(おそらく終了処理)へ進みます。
6 LD GR6,=#FFFF ; マスク作成
7 SRL GR6,0,GR3
8 XOR GR6,=#FFFF
GR6にβの長さ分のマスクを設定しています。
BSRH の4~6行目と同じ処理ですね。
【レジスタの値】GR6 = 1111 10……0
9 LD GR7,GR3 ; GR7 ← n
GR7にGR3、すなわちn = 5をコピーしています。
【レジスタの値】GR7 = 5(10)
10 LD GR3,=16
GR3に16を設定しています。
【レジスタの値】GR3 = 16(10)
11 SUBA GR3,GR2 ; GR3 ← 16 - p
GR3に(16 - p) = 2を設定しています。
【レジスタの値】GR3 = 2(10)
12 JMI ONL2 ; 一致する部分ビット列が2語目だけのとき
13 JZE ONL2
「JMI ONL2」は「11行目の結果がマイナスなら、ラベル ONL2 にジャンプせよ」という命令です。
「JZE ONL2」は「11行目の結果が0なら、ラベル ONL2 にジャンプせよ」という命令です。
コメントに「一致する部分ビット列が2語目だけのとき」とあります(ということは、ONL は ONLY の略?)。
βと同じビット列がαの2語目だけに含まれるのは、pの値が16以上の場合です。
よって、(16 - p)が0以下になれば、βと同じ文字列は2語目に含まれる、ということになるわけですね。
今トレースしている値では(16 - p)が2ですから、ラベル ONL2 には跳ばずにそのまま下に進みます。
14 CPA GR3,GR7 ; (16 - p)とnの比較
コメントにあるように「(16 - p)とnの比較」です。
(16 - p)が0かマイナスの場合は、この行には入らずラベル ONL2 にジャンプしますので、ここに入ったということは、(16 - p)は正の数ということになります。
それとβの長さnを比較していますね。
15 JMI NEXT ; 2語にまたがる処理
16 JUMP ONL1 ; 一致する部分ビット列が1語目だけのとき
「JMI NEXT」は「14行目の結果がマイナスなら、ラベル NEXT にジャンプせよ」という命令です。
「JUMP ONL1」は「ラベル ONL1 にジャンプせよ」という命令です。
15行目のコメントは「2語にまたがる処理」、16行目のコメントは「一致する部分ビット列が1語目だけのとき」です。
つまり、14行目の結果がマイナスならば「一致する部分ビット列が2語にまたがる処理」となり、そうでなければ「一致する部分ビット列が1語目だけのとき」となるわけですね。
少々紛らわしくなってきましたので、図を描いて検証した方がよさそうです(正直、私は、ここの処理の意図は図を描くまで分かりませんでした)。
そもそも(16 - p)で求められる数字は何を表しているのか?
図7
こうして図にしてみると分かりやすくなります。
(16 - p)の「16」という数字は、1語目の長さと考えるよりも、「2語目の先頭番号」と考えた方がよさそうです。
そうなると、(2語目の先頭番号 - p)、すなわち(16 - p)で「1語目の終端からpまでのビット数」が得られることになります(○囲みの数字)。
それがβの長さ(= n)より大きいか、あるいは同数であれば、一致ビット列は1語目に収まります(いちばん上と二番目の図)。
また、それがβの長さ(= n)より小さければ、1語目の終端からはみ出して2語目にまたがることになるわけです(三番目の図)。
今はn = 5、p = 14ですので、「2語にまたがる処理」となります。
16行目は飛ばし、17行目のラベル NEXT に入ります。
17 NEXT LD GR5,GR4 ; γとマスクを退避
GR5にGR4をコピーしています。
GR4にはγが入っています。
「退避」というコメントから見て、「これからγとマスクに変更を加えるが、元の値は別のレジスタに保存しておく」という意図が読み取れます。
【レジスタの値】GR5 = 1111 0……0(←γのコピー)
18 LD GR7,GR6
GR7にGR6をコピーしています。
GR6にはマスクが入っています。
17行目のコメントはここにもかかっています。
【レジスタの値】GR7 = 1111 10……0(←マスクのコピー)
19 CALL S1 ; 1語目の処理
サブルーチン S1 を呼び出しています。
S1 は FIN ラベルよりも下、35行目に書いてあります。
コメントは「1語目の処理」ですから、1語目の、右端から2ビット分の処理をするのでしょう。
そちらにジャンプしましょう。
35 S1 SRL GR4,0,GR2 ; γの調整
36 SRL GR6,0,GR2 ; マスクの調整
サブルーチン S1 です。
GR4とGR6を、GR2に格納された値だけ右に論理シフトしています。
今、GR4にはγが左詰めで、GR6にはマスクが左詰めで、GR2にはp = 14が格納されているのでした。
γとマスクの長さはn = 5ですので、右に14ビットシフトすると、γとマスクの左2ビット分だけが右端に残り、右3ビット分ははみ出して消えてしまいます。
図8
このあたりまで来るとプログラムの意図がほぼ見えてきますよね。
おそらく、1語目の右端の書き換えに使うビット列を作ろうとしているのでしょう。
この下には RET がありませんので、そのまま37行目のサブルーチン S2 に進みます。
【レジスタの値】GR4 = 0……0011(←γの左2ビット分を右端に寄せたもの)
【レジスタの値】GR6 = 0……0011(←マスクの左2ビット分を右端に寄せたもの)
37 S2 LD GR2,0,GR1 ; 操作対象語の取出し
サブルーチン S2 です。
コメント「操作対象語の取出し」のとおり、GR2に、GR1に格納されたアドレスの、その領域の値(αの1語目)をコピーしています。
【レジスタの値】GR2 = 1111……0011
38 XOR GR6,=#FFFF
サブルーチン S2 の続きです。
GR6(マスク)を反転させています。
【レジスタの値】GR6 = 1……1100
39 AND GR2,GR6
さらにサブルーチン S2 の続きです。
GR2とGR6をAND演算しています。
GR2にはαの1語目が、GR6(マスク)には右端の2ビットを除きすべて1のビット列が格納されています。
この演算によって、GR2に格納されたαの1語目は、左端から14ビットまではそのままで、右端2ビットだけが強制的に0に置き換えられます。
【レジスタの値】GR2 = 1111……0000
40 空欄 d
空欄 d の方が c よりも先に来ました。
39行目までを読んだだけでは埋められませんので、先を見てみます。
41 ST GR2,0,GR1
42 RET
さらにサブルーチン S2 の続きです。
42行目に RET がありますので、この処理が終わったら S1 を呼び出した行に戻ります。
41行目で、GR2をGR1のアドレス(#8000)の領域に書き戻しています。
ということは、上の空欄 d でαの1語目の置き換え処理は終了したことになります。
d の選択肢を見ると、すべてGR2とGR4の演算になっています。
GR2には、αの1語目のうち、右端2ビット分だけが0に書き換えられたビット列が格納されています。
GR4には、γの左2ビット分を右端に寄せたビット列が格納されています。
αの1語目は、右端の2ビット分をγの左2ビット分で書き換えればよいわけです。
そのためには、GR2とGR4を「OR演算」すれば、目的の値が得られます。
よって、答えはイ「ORGR2,GR4」です。
図9
【レジスタの値】GR2 = 1111……0011(←GR1のアドレスが指し示す1語目の領域もこの値に置き換えられた)
設問 3 空欄 d 答え …… イ
20行目に戻ります。
20 LD GR4,GR5
21 LD GR6,GR7
GR4にGR5を、GR6にGR7をコピーしています。
GR5には元のγ、GR7には元のマスクが退避させられていたのでした。
ここではそれらを元のレジスタに書き戻しています。
【レジスタの値】GR4 = 1111 00……0
【レジスタの値】GR6 = 1111 10……0
22 SLL GR4,0,GR3 ; 2語目用γの調整
23 SLL GR6,0,GR3 ; 2語目用マスクの調整
GR4とGR6を、GR3の値分だけ左に論理シフトしています。
GR3には(16 - p) = 2が格納されていたのでした。
1語目の処理では右シフトでしたが、2語目の処理では左シフトになっています。
一致ビット列が2語にまたがる場合、2語目の方の書き換えるべきビット列は、1語目とは逆に左端にあるからですね。
図10
図11
【レジスタの値】GR4 = 1100……0
【レジスタの値】GR6 = 1110……0
ここで設問4の答えが出ました。
この段階でのGR6の値は「1110 0000 0000 0000」= オ「#E000」です。
設問 4 答え …… オ

24 LAD GR1,1,GR1
GR1にはαの先頭アドレスが格納されていますが、それを2番目のアドレスに書き換えています。
【レジスタの値】GR1 = #8001
25 CALL S2 ; 2語目の最終処理
サブルーチン S2 を呼び出しています。
S1 は1語目のための調整に使いましたが、それは右シフトでγとマスクを調整する処理でした。
今は2語目で、2語目は1語目とは逆に左シフトする必要があります。
よって、2語目は S1 が使えないため、S1 の代わりに22行目と23行目で左シフトによるγとマスクの調整を行い、S2 だけを呼び出しています。
37 S2 LD GR2,0,GR1 ; 操作対象語の取出し
再びサブルーチン S2 です。
GR2に、GR1に格納されたアドレスの、その領域の値をコピーしています。
今GR1には2語目のアドレスが入っているので、GR2には2語目の値がコピーされます。
【レジスタの値】GR2 = 0111……1
38 XOR GR6,=#FFFF
GR6(マスク)を反転させています。
【レジスタの値】GR6 = 0001……1
39 AND GR2,GR6
GR2とGR6のAND演算によって、GR2に格納されたαの2語目は、左端3ビットだけが強制的に0に置き換えられ、その後はそのままになります。
【レジスタの値】GR2 = 0001……1
40 OR GR2,GR4
41 ST GR2,0,GR1
42 RET
40行目は空欄でした。
GR2(2語目の左端3ビットだけが0にされたビット列)にGR4(γの後半3ビットを左端に寄せたビット列)をOR演算しています。
図12
41行目のST命令で、その値を2語目のアドレス領域(#8001)に上書きしています。
42行目のRETで元の25行目に戻りますが、26行目はラベル FIN に跳んですぐ終了しますので、これでこの処理は終了です。
【レジスタの値】GR2 = 1101……1(←GR1のアドレスが指し示す2語目の領域もこの値に置き換えられた)
さて、まだ空欄 c が埋まっていませんね。
設問4の設定だと、これまでトレースしてきたように、一致するビット列がαの2語にまたがる場合の処理になります。
空欄 c はラベル ONL2 の行にありますので、一致するビット列がαの2語目だけの場合にしか通過しません。
私はこれを初めて解いたとき、仕方ないので、p = 17を設定して、11行目からもう一度トレースしました。
pは、4行目でGR0からGR2にコピーされています。
11 SUBA GR3,GR2 ; GR3 ← 16 - p
12 JMI ONL2 ; 一致する部分ビット列が2語目だけのとき
13 JZE ONL2
GR3には10行目で「16」が格納されていました。
ここの計算で、GR3には(16 - 17)= -1が格納されます。
すると、12行目の「マイナスなら ONL2 にジャンプせよ」に該当しますので、これでめでたく29行目に入ることができます。
【レジスタの値】GR3 = -1(10)
29 ONL2 空欄 c
空欄 c です。
先を見てみます。
30 SUBA GR2,GR3 ; GR2 ← p - 16
31 LAD GR1,1,GR1 ; 操作対象を2語目にして、
32 CALL S1 ; 2語目の処理
空欄 c の選択肢は、すべてGR2の値を得るものになっています。
30行目のコメントは「GR2 ← p - 16」ですので、この行で、GR2には(17 - 16)= 1が入ることになります。
ということは、「SUBA GR2,GR3」が1になるようなGR2の値が、29行目の空欄で設定されている必要があります。
GR3には11行目で「-1」が設定されていました。
以上の条件で式を立てると、GR2 - GR3 = 1 → GR2 -(-1)= 1となり、これを解くとGR2 = 0が得られます。
GR2が0になる選択肢は、そのものずばり、イ「GR2,=0」があります(ほかの選択肢は0になりません)。
設問 3 空欄 c答え …… イ
【レジスタの値】GR2 = 1(10)
【レジスタの値】GR1 = #8001
……この空欄 c はけっこう難しいと思います。
上に書いたのは実際に私が解いたときの手順ですが、具体的な値によるトレースとコメントを頼りに解いただけなので、「なぜそうなるのか」が今ひとつぴんと来ませんでした。
以下は、これを書くためにあらためて考察したものです。
32行目でサブルーチン S1 を呼び出していますので、その前の部分は S1 に渡すための値を設定しているものと思われます。
ここでサブルーチン S1・S2 を見直してみると、
という手順であることが分かります。
要するに、S1 は「γとマスクをpの位置に右シフトする」処理、S2 は「ビット列αから1語を取り出して、pの位置からのビットをγと置き換える」処理をしているのです。
この S2 の「ビット列αから1語を取り出して」というのがポイントです。
もし2語目を取り出した場合、pは16以上のため、そのままでは使えません。
S1 の処理を見直してみてください。
もし17ビット右シフトしたら、元の値が全部右にこぼれ落ちてなくなってしまいますよね。
その場合は、置き換え対象ビット位置を「2語目の先頭からのビット番号」に変換しておく必要があります。
「2語目の先頭からのビット番号」は、30行目のコメントにあるように(p - 16)で求められます。
単純に(p - 16)で求めてくれれば分かりやすいのに、この問題では一ひねりしてあって、11行目で求めた(16 - p)を0から引くという方法で(p - 16)を求めているのです。
0 -(16 - p))を展開すれば、(0 - 16 + p)=(p - 16)になります。
これは、2語目の場合(16 - p)は必ず0か負の数になりますので、その符号をひっくり返している、と考えてもよさそうです。
そもそも(16 - p)は何なのかというと、「1語目の終端からpまでのビット数」でした(上の15・16行目参照)。
ただし、これは(16 - p)が正の数だった場合です(=一致ビット列が「1語目のみ」か「2語にまたがる」場合)。
一致ビット列が「2語目のみ」の場合、(16 - p)は0かマイナスとなりますが、その値は、今度は「2語目の先頭を基点0とした場合の、マイナス方向へのビット番号」となります。
図13
よって、一致ビット列が2語目のみの場合は、「(16 - p)の符号をひっくり返すことによって、2語目の先頭を基点0としたビット番号が得られる」ということになるわけです。
符号をひっくり返すためには、-1を乗算するか、0から減算すればよく、ここでは後者の方法を採用しています。
p = 17だとすると、(16 - p)= (16 - 17)= -1 → 0 -(-1)= 1 という具合です。
この赤字強調の「0」がGR2に入れられるかどうかが、空欄 c では試されていたわけです。
図14
……それにしても、この空欄 c を含む29・30行目のような方法は、アセンブラでは一般的なのでしょうか?
ごく普通に用いられる手法・発想だとしたら、私はまだまだ入門レベルの域を出ていないということになりそうです。
まだ27・28行目、すなわち「一致ビット列がαの1語目のみの場合の処理」が残っています。
しかし、27行目のラベル ONL1 では、サブルーチン S1 を呼んで終了するだけですので、これ以上の説明は不要と思われます。



さて、今回のアセンブラ問題の正答率は、IPAの講評によると、
だったそうですので、全体としては「やや難」くらいになるのでしょうか。
個人的には、難しい空欄問題もありましたが、総じて素直でストレートな内容だったという印象です。
「良問」と言ってよいのではないかと思います。