はじめに
前回はMind8のランタイムの中間コードディスパッチャ(の一部)をCからC#に書き換えてみるという野望の現状認識を「三段目」とかってに判定して、構想段階のクラス図なんかをまとめてみました。(この番付の判定はてきとうです)
その後、お題の中間コードをディスパッチャが処理するステップ数をより一層進行させようと、基本的にはC版オリジナルのディスパッチャが処理するC定義単語の関数配列のC#代替実装の範囲を地道に広げていくという工程に入っております。
この個々の配列関数の定義内容がほんとうに妥当かということがステップ数の進行状況に影響を与えるようになってきており、ざっと仮実装しているようなところでオリジナルの動作と非互換な場合はその後に処理するMCodeへのジャンプ先が微妙に異なってしまうというような問題も起きています。(この非互換は次のステップですぐに起きるというわけではなく、間に他のスタック操作を挟んだのちに発現するので特定の難度がやや高いという状況です。)
前提条件
Windows11 Pro 22H2
VSCode(Visual Studo Code) 1.86.1
Microsoft Visual C++ 2008 Express Edition
Mind Version 8.0.08 for Windows
C# 12
dotnet-sdk-8.0.204-win-x64
VSCodeの拡張機能
C/C++ for Visual Studio Code 1.18.5 Microsoft
C/C++ Extension Pack 1.3.0 Microsoft
.NET Install Tool 2.0.2 Microsoft
Base language support for C# 2.18.16 Microsoft
C/C++のデバッガは直接使わないのですが、Cで実装されているMind8kernelの動作をデバッグ実行で探るために使用します。こちらの記事の環境となります。
お題のMind8の中間コードファイル
こちらの記事をご参照ください。
また、前回記事に実際のMCode領域内のMCodeをC版オリジナルがディスパッチしたリストを確認しています。お題の「Hello by mind8」のコンソール出力までは649(同一コード含む)のmcodeを処理します。
とりあえずどんな状況か
前回記事は37ステップ目で未実装関数の出現でとまっていましたが、現状は下記のとおりです。
SqNo mcode mcode(dec)
1 8019 32793 //2024/04/23
2 002F 47 //2024/04/25
3 0036 54
4 0080 128 //2024/04/27
5 0036 54
6 0070 112
7 803A 32826
8 002F 47
9 813E 33086
10 0012 18
11 0036 54
12 0071 113
13 8046 32838
14 8045 32837
15 0012 18
16 0036 54
17 0074 116
18 0020 32
19 0020 32
20 80BA 32954
21 0036 54
22 0012 18
23 016E 366
24 0020 32
25 80A6 32934
26 0097 151
27 0065 101
28 0020 32
29 80D7 32983
30 0261 609
31 8053 32851
32 8052 32850
33 001A 26
34 006B 107 //2024/04/29
35 0055 85
36 008F 143
37 00ED 237 //2024/05/03
38 0055 85
39 0012 18
40 00A2 162
41 00BF 191
42 0095 149
43 01D7 471
44 006B 107
45 0055 85
46 00ED 237
47 0055 85
48 0055 85
49 0103 259
50 00BF 191
51 0055 85
52 0055 85
53 0107 263
54 0022 34 //2024/05/06
実際にはもう少しループしていますが、55ステップ以降のmcodeはC版オリジナルとは異なる値を読み込んでおり、64ステップまでループした後、未実装コードで例外により停止しています。
お題のC#ソースコードのクラス構成
クラス構成に変更はないのですが、C#デリゲートによる関数配列の数が増えてきましたので、ソースコード内を縦スクロールするのがちょっとつらくなってきましたので、オリジナルとにた感じで分割しました。
クラスの説明と対応ソースファイル構成
クラス名 | 概要 | 対応ソースファイル |
---|---|---|
Mind8Dispatcher | Windowsコンソールアプリのホストクラス | Mind8Dispatch.cs |
Dispatcher | ディスパッチャ本体クラス | Dispatcher.cs |
C#関数配列(配列初期化) | CsFunctions.cs | |
同実際の定義部分 | CsFunctionsCompword.cs | |
同実際の定義部分 | CsFunctions0ker.cs | |
同実際の定義部分 | CsFunctions1ker.cs | |
同実際の定義部分 | CsFunctions2ker2.cs | |
同実際の定義部分 | CsFunctions3ker.cs | |
同実際の定義部分 | CsFunctionsBunki.cs | |
McodeInfoStruct | Mcode情報構造体 | Dispatcher.cs |
LongJmp | 広域ジャンプ用のアプリケーション例外 | Dispatcher.cs |
MCodePointer | Mcode領域アクセスクラス | MCodePointer.cs |
LocTablePointer | Locテーブルアクセスクラス | LocTablePointer.cs |
AccessPointer | データ領域アクセスクラス | AccessPointer.cs |
StackPointer | スタッククラス | StackPointer.cs |
C関数配列の数は膨大で、オリジナルの実装数に対して数的にまともに実装してあるのはCsFunctionsCompword.csくらいで、内容的にはまだ妥当性不明状態のものが多数あります。その他の配列関数ファイルはお題の中間コード処理で出現したものから優先的に実装していますが、仮実装にとどもっているものを含みます。
お題のC#ソースコード
StackPointerクラス
スタックアクセスクラスです。前回記事でインデックスのPOP/PUSHの際の増減方向を変更してMind互換に変更しましたが、デバッガの配列の展開順が昇順なので末尾のインデックスから使用して逆順に成長させるとデバッグが若干しずらいため、配列のインデックス変数とは別にポインタ変数を導入してこちらはMind互換(最大値が初期値でPUSHすると減っていくPOPすると減っていく(最大値に近づく))として、Byte配列のインデックスは0から使用するに換算を挟みました。
namespace Mind8Kernel
{
/// <summary>スタックポインタ</summary>
public class StackPointer{
private readonly uint stackSize=4;
private uint stackMaxSize;
private uint stackIndex;
private uint stackPointer;
public byte[] dataArray =[];
public void SetupStackArray(uint size){
stackMaxSize=size;
dataArray =new byte[stackMaxSize];
stackPointer=stackMaxSize/stackSize;//stackPointerは1024が初期値
stackIndex=0;//stackIndexは0が初期値
}
public void ResetStackPointer(uint point){
stackPointer=point;
stackIndex=(stackMaxSize/stackSize-point)*stackSize;//byte配列インデックス=アドレスポインタ変数の値*4
}
public void MoveStackPointer(int point){
stackPointer=(uint)(stackPointer+point);
stackIndex=(uint)(stackIndex + point*stackSize);//byte配列インデックス=アドレスポインタ変数の値*4
}
public uint GetCurrentStackPointer(){
return stackPointer;
}
public byte GetUb(){
//アドレス指定で直接参照している元コードのためポインタインデックスの自動減少はなし
byte[] int32Byte = new byte[stackSize];
for (ulong i = 0; i < stackSize; i++) int32Byte[i] = dataArray[stackIndex + i];
return (byte)BitConverter.ToUInt32(int32Byte, 0);
}
public uint GetUl(){
//アドレス指定で直接参照している元コードのためポインタインデックスの自動減少はなし
byte[] int32Byte = new byte[stackSize];
for (ulong i = 0; i < stackSize; i++) int32Byte[i] = dataArray[stackIndex + i];
return BitConverter.ToUInt32(int32Byte, 0);
}
public uint GetUlOffset(int offset){/* for unsigned long access */
//アドレス指定で直接セットしている元コードのためポインタインデックスの自動増加はなし
byte[] int32Byte = new byte[stackSize];
uint index=(uint)(stackIndex + offset*stackSize);
for (ulong i = 0; i < stackSize; i++) int32Byte[i] = dataArray[index + i];
return BitConverter.ToUInt32(int32Byte, 0);
}
public byte PopUb(){ /* for unsigned byte access */
if(stackIndex - stackSize < 0)throw new Exception("stack under flow");
byte[] int32Byte = new byte[stackSize];
for (ulong i = 0; i < stackSize; i++) int32Byte[i] = dataArray[stackIndex + i];
stackPointer++;//POPはポインタ増加
stackIndex-=stackSize;//POPはインデックス減少
return (byte)BitConverter.ToUInt32(int32Byte, 0);
}
public ushort PopUs(){ /* for unsigned byte access */
if(stackIndex - stackSize < 0)throw new Exception("stack under flow");
byte[] int32Byte = new byte[stackSize];
for (ulong i = 0; i < stackSize; i++) int32Byte[i] = dataArray[stackIndex + i];
stackPointer++;//POPはポインタ増加
stackIndex-=stackSize;//POPはインデックス減少
return BitConverter.ToUInt16(int32Byte, 0);
}
public uint PopUl(){/* for unsigned long(int32) access */
if(stackIndex - stackSize < 0)throw new Exception("stack under flow");
byte[] int32Byte = new byte[stackSize];
for (ulong i = 0; i < stackSize; i++) int32Byte[i] = dataArray[stackIndex + i];
stackPointer++;//POPはポインタ増加
stackIndex-=stackSize;//POPはインデックス減少
return BitConverter.ToUInt32(int32Byte, 0);
}
public void PushUb(byte b){ /* for unsigned byte access */
if(stackIndex + stackSize > stackMaxSize-1)throw new Exception("stack over flow");
stackPointer--;//POPはポインタ減少
stackIndex +=stackSize;//PUSHはインデックス増加
byte[] int32Byte = new byte[stackSize];
int32Byte[3] =b;
for (ulong i = 0; i < stackSize; i++) dataArray[stackIndex + i]=int32Byte[i];
}
public void PushUs(ushort us){ /* for unsigned byte access */
if(stackIndex + stackSize > stackMaxSize-1)throw new Exception("stack over flow");
stackPointer--;//POPはポインタ減少
stackIndex +=stackSize;//PUSHはインデックス増加
byte[] int32Byte =BitConverter.GetBytes(us);
for (ulong i = 0; i < stackSize; i++) dataArray[stackIndex + i]=int32Byte[i];
}
public void PushUl(uint ul){/* for unsigned long(int32) access */
if(stackIndex + stackSize > stackMaxSize-1)throw new Exception("stack over flow");
stackPointer--;//POPはポインタ減少
stackIndex +=stackSize;//PUSHはインデックス増加
byte[] int32Byte =BitConverter.GetBytes(ul);
for (ulong i = 0; i < stackSize; i++) dataArray[stackIndex + i]=int32Byte[i];
}
public void PushSl(int sl){/* for signed long(int32) access */
if(stackIndex + stackSize > stackMaxSize-1)throw new Exception("stack over flow");
stackPointer--;//POPはポインタ減少
stackIndex +=stackSize;//PUSHはインデックス増加
byte[] int32Byte =BitConverter.GetBytes(sl);
for (ulong i = 0; i < stackSize; i++) dataArray[stackIndex + i]=int32Byte[i];
}
public void SetUl(uint ul){/* for unsigned long access */
//アドレス指定で直接セットしている元コードのためポインタインデックスの自動増加はなし
byte[] int32Byte =BitConverter.GetBytes(ul);
for (ulong i = 0; i < stackSize; i++) dataArray[stackIndex + i]=int32Byte[i];
}
public void SetUl2var(uint ul,uint ul2){/* for unsigned long access (for 1set of 2variable) */
//アドレス指定で直接セットしている元コードのためポインタインデックスの自動増加はなし
byte[] int32Byte =BitConverter.GetBytes(ul);
for (ulong i = 0; i < stackSize; i++) dataArray[stackIndex + i]=int32Byte[i];
int32Byte =BitConverter.GetBytes(ul2);
for (ulong i = 0; i < stackSize; i++) dataArray[stackIndex + i + 4]=int32Byte[i];
}
public void SetUlOffset(uint ul,int offset){/* for unsigned long access */
//アドレス指定で直接セットしている元コードのためポインタインデックスの自動増加はなし
byte[] int32Byte =BitConverter.GetBytes(ul);
uint index=(uint)(stackIndex - offset*stackSize);//+は減方向
for (ulong i = 0; i < stackSize; i++) dataArray[index + i]=int32Byte[i];
}
}
}
Dispatcherクラス(C#関数配列)
C#の関数配列
前回記事で未実装のため該当mcodeの処理でとまっていた関数配列は下記のような感じで実装しています。オリジナルのCマクロの定義内容を実行文の前後に引用して全体をコメントアウトしているので読み取りづらくなってしますが、オリジナルのマウロの動きを忠実にC#で再現することがキモとも言えますので、いったんざっと書いた後、ほんとにあっているかどうかを再考する上でも、このコメント部に立ち返るようにしておりまして、わたしの中ではかなり重要です。
実際に突入するのは下のzzNotif()の方です。デバッグ用に中間変数を介しています。
namespace Mind8Kernel
{
public partial class Dispatcher
{
/*--------------------- ならば~つぎに -------------------------*/
private void zzif(){ /* ;WORD $$IF */
/* Mコード=0x00EC */
// #define DROP_L DstackPointer++
// #define DROP_F DROP_L
// #define POP_L (*DstackPointer++)
// #define POP_L_OF_Q (DROP_F,POP_L)
// if ( POP_L_OF_Q )
// {
// SKIP_MCODE_1WORD;
//#define SKIP_MCODE_1WORD McodePointer.w++
// }
// else
// {
// SKIP_MCODE( READ_MCODE_SHORT );
//#define SKIP_MCODE(bytes) McodePointer.b+=(bytes)
//#define READ_MCODE_SHORT (*McodePointer.s)
// }
dtp.PopUl();//≒DROP Dataスタックから取り出して捨てる
if(dtp.PopUl()>=1){
mp.MoveMcodeIndex(1);
}else{
mp.MoveMcodeIndexByByte(mp.ReadMcodeShort());
}
}
private void zzNotif(){ /* ;WORD $$-IF */
/* Mコード=0x00ED */
// if ( POP_L_OF_Q )
// {
// SKIP_MCODE( READ_MCODE_SHORT );
//#define SKIP_MCODE(bytes) McodePointer.b+=(bytes)
//#define READ_MCODE_SHORT (*McodePointer.s)
// }
// else
// {
// SKIP_MCODE_1WORD;
// }
dtp.PopUl();//≒DROP Dataスタックから取り出して捨てる
if(dtp.PopUl()>=1){
//mp.MoveMcodeIndexByByte(mp.ReadMcodeShort());
short mc=mp.ReadMcodeShort();
mp.MoveMcodeIndexByByte(mc);
}else{
mp.MoveMcodeIndex(1);
}
}
}
}
これを実装した当初、分岐の結果がC版オリジナルと逆になってしまって、後続のmcodeがあさってになるという現象に遭遇したため、Mind開発者の@killyさんが注意喚起されていたPOP_L_OF_Q すなわち(DROP_F,POP_L)の実行順が逆なのではとも考えたのですが、実際のところは現状でもこの対応実装であっているようで、原因は少し手前のデータスタックへの値の積み具合の不具合でした。
つづく
生成されたhello.mcoを解釈して、C#側で「Hello by mind8」が出力されるようにするまでの長い道のりですが、とりあえず構想段階のソースコードの確度をざくっと引き上げて「三段目」とかかなという感じでしたが、最近は道の険しさにひるんでいます。(この番付の判定はてきとうです)まだまだ課題山積ですが、次回以降でもこのソースコードを肉付けして実際に動く範囲をステップバイステップで広げてまいります。