前回楽ではなさそうと書きましたが、基本機能は動くようになりましたのでIM6100対応について数回に分けて書いていきたいと思います。
第1回目は複合命令の処理です。
前回3種類のアイデアを書きましたが最終的にはオリジナルのアセンブラ書式に近い3番目を採用しました。
ASの一般的な処理を知らないとわかりにくいと思いますので、簡単に説明しておきます。
まずSwitchTo_xxxx()関数(これはCPU疑似命令で切り替わったときに呼ばれます)で必要な命令を登録します。特殊な疑似命令が必要ならそれもここで登録します。
AddMR("AND", 00000);
AddMR("TAD", 01000);ここでAddMR()は同じ引数を繰り返すのが面倒で用意したラッパーなので、例えば1行目は以下のように書いたのと同じです。
AddInstTable(InstTable, "AND", 00000, DecodeMR);第3引数は複数の命令で共通のデコードルーチンを使うときの識別用ですが、通常は命令コードをそのまま入れることが多いです。
第4引数はデコードルーチンです。
あとはソース1行ごとに(CPUのような共通の疑似命令の場合を除き)MakeCode_xxxx()関数が呼ばれるので以下のように処理します。
if (!LookupInstTable(InstTable, OpPart.str.p_str))
WrStrErrorPos(ErrNum_UnknownInstruction, &OpPart);この時ソースは要素ごとに分解されて、オペコードはOpPartにオペランドはArgStr[]に入って(C文字列ではないので注意)いるので、まずはOpPartによって分岐します。LookupInstTable(Table, str)はTableの中からstrを探して見つかれば対応するデコードルーチンを呼びます。見つからなければ0を返すので"Unknown Instruction"エラーを発生させます。
さてここからが本題の複合命令です。
デコードルーチンからArgStr[]について再度LookupInstTable(Table, str)を呼べば良さそうですが、再帰呼び出しになってしまうのでその対処と、複合できる命令はグループがあるのでその対応も必要です。
前者についてはグローバルなフラグを用意して済ませました。AS自体グローバル変数を多用しているので少し増えても構わないかなと。
後者についてはInstTableを複合しない命令用と各グループ命令用に分けることにしました。
区切り文字についても簡単に触れておきます。前回DivideChars = " ";と書きましたが、これだとTABを含んだ場合にエラーになってしまいます。空白にする場合の参考例がないかと探してみたところcode56k.cも空白を使っており、DivideChars = " \t";とすることでTABの問題も解消しました。
code56k.cのバグを見つけてしまいました。
これで複合命令の目途が立ったと思ったのですが...
NGな組み合わせが見つかってしまいました。
それがCLA SWPでした。まずオペコード部はCLAですが、これはグループ1~3のいずれにも存在します。順番に処理しているのでグループ1としてオペランド部にあたるSWPを処理しようとして、これはグループ3にしかないのでエラーになってしまいます。
順番を変えても今度は別の組合せがNGになってしまいます。
仕方ないのでエラーになったら別のグループとして再処理するようにしています。
結果、class="source">MakeCode_xxxx()関数の上記部分は以下のようになりました。
if (LookupInstTable(InstTable, OpPart.str.p_str)) return;
PendingError = 0;
for (i = 0; i < 3; i++)
{
BaseInst = True;
if (LookupInstTable(InstTableOP[i], OpPart.str.p_str)) Match = True;
if (CodeLen) return;
}
if (Match)
{
if (PendingError) WrError(PendingError);
return;
}
if (DecodeDefault(&OpPart)) return;最初に複合しない命令を処理したあと、各グループ毎に処理を試みます。
PendingErrorは、試行の途中でエラー報告してしまうと取り消せないので保留しておきます。なので複合命令に絡むデコードルーチンでは勝手にエラー報告してしまうChkXxx()系の便利関数は使用できません。
BaseInstは再帰呼び出しの対応です。
Matchは最初のニーモニックがテーブルにマッチしたことを示します。試行が終わったときに真なら正しいニーモニックだったが何らかの理由(組み合わせが不正・未対応命令など)でエラーが発生したことになります。
CodeLenは生成したコードのワード数(IM6100系では0か1)です。これが0でないということはコード生成に成功したので終了です。
最後のDecodeDefault(&OpPart)はニーモニックの代わりに定数(式)を書くとその値がオブジェクトに配置される機能のためです。この副作用でニーモニックを打ち間違えると>Unknown Instruction"ではなく"Symbol undefined"エラーになってしまいます。
オリジナルではニーモニックと定数の複合(ORされる)も可能なようですがこのAS拡張ではそこは対応していません。
複合命令の可能性のあるOperation Instructionのデコードルーチンも挙げておきます。
static void DecodeOP(int gp, Word Index, Boolean (*check)(Word i1, Word i2))
{
Word i;
if (!ChkValidInst(Index, False)){
PendingError = ErrNum_InstructionNotSupported;
return;
}
Index &= 07777;
if (BaseInst)
{
WAsmCode[0] = Index;
CodeLen = 1;
BaseInst = False;
for (i = 1; i <= ArgCnt; i++)
{
if (!LookupInstTable(InstTableOP[gp], ArgStr[i].str.p_str))
{
PendingError = ErrNum_UnknownInstruction;
CodeLen = 0;
return;
}
}
}
else
{ /* 2nd, 3rd instruction */
Word tmp;
if (CodeLen > 0)
{
tmp = WAsmCode[0] | Index;
if (tmp == WAsmCode[0] || tmp == Index)
{
PendingError = ErrNum_UnknownInstruction;
CodeLen = 0;
return;
}
if ((tmp & 00016) == 00016 )
{
PendingError = ErrNum_UnknownInstruction;
CodeLen = 0;
return;
}
if (check && !(*check)(WAsmCode[0], Index))
{
PendingError = ErrNum_UnknownInstruction;
CodeLen = 0;
return;
}
WAsmCode[0] = tmp;
}
}
}ChkValidInst()はパネルモードやCPU種類によって命令が有効か判定します。これは他のデコードルーチンと共用のためエラー報告するか第2引数で切り替えています。
BaseInstが真ならMakeCode_xxxx()からの呼び出し(1つ目)です。2つ目以降を自グループのテーブルから探しますが、テーブルはグループ毎に分かれているのでグループをまたいだ組合せはマッチしません。
偽なら自分自身から呼び出し(2つ目以降)なので組み合わせチェックを行ない、問題なければ合成(OR)します。
このチェックですが明らかにNGな組み合わせはチェックしていますが、データシート等を参照しても不明な点が多くそれ以外はスルーしています。
途中でPDP-8(IM6100はPDP-8互換のマイクロプロセッサ)のアセンブラのマニュアルを見てみると、ニーモニックやラベルが空白で区切られていたらORするとあるので組み合わせのチェックは全く行われていなかったようです。当時のメモリ事情を考えるとやむを得なかったのでしょう。
パッチをhttps://electrelic.com/pub/asl-IM6100/にしばらく(本家にマージされたら消す予定)置いておきますので詳しくはそちらも参照ください。
コメントを追加