a25_kharcs1
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
* kharcs #1
-(by [[K]], 2025.06.03)
** (0) これはなに?
-(kharcsページは、kharc開発でわかったことを整理して説明...
-今回説明したいこと:
--[1]独自の仮想マシンの命令セットを考える
--[2]そいつのアセンブラを設計する
--[3]サンプルコードを書いてみる
--[4]エミュレータを作ってサンプルコードを実行してみる
** (1)
-開発のきっかけはいろいろありましたが、まあそれは本題では...
-簡単なCPUを設計してみました。レジスタマシンです。
-レジスタ:
--整数レジスタ(32bit): R0~R7
--アドレスレジスタ(32bit): A0~A7
--浮動小数点レジスタ(64bit): F0~F7 (しかし今回の実装...
-一般的なCPUではレジスタは「汎用レジスタ」とされ、整数用...
--(ちなみに今回の実装の範囲では、この整数レジスタとアド...
--(ああ、だから多くのCPUで汎用レジスタとして統合されてい...
-レジスタの別名:
--SP: A6の別名。スタックポインタ用。
--RP: A7の別名。return-pc。リターン用のプログラムカウンタ。
-演算系の命令(四則演算やビット演算など)はレジスタ間、レ...
-メモリ上のスタックに対する命令はなしにしました。何かをPU...
-CALLも戻り先をスタックには積まずに、リターンアドレスをRP...
-こうすることで、メモリアクセスを伴う命令はload/store命令...
-関数呼び出し規約(とりあえず):
--SP以外のレジスタは保存しなくてよい。
--関数の引数はすべてスタック渡し。レジスタ渡しはしない。
--スタック上の引数はintもポインタもdoubleもすべて8バイト...
--各関数のローカル変数については、好きなアラインでよい。
** (2)
-kharcのアセンブラは、C言語っぽい構文を採用してみました(...
-ラベルの名前解決が面倒だったので、ラベルはすべて番号にし...
-このアセンブラはバックエンドとして動けばいいので、頑張ら...
|MovRI(r,imm);|R0~R7に即値定数immを代入、Rx=imm;|
|MovAI(a,imm);|A0~A7に即値定数immを代入、Ax=imm;|
|LodRMd(r,base,disp);|Rxの値をメモリから読み込みます(loa...
|StoRMd(r,base,disp);|Rxの値をメモリに書き込みます(store...
|LodAMd(a,base,disp);|Ax=[Ax+disp];|
|StoAMd(a,base,disp);|[Ax+disp]=Ax;|
|AddRI(r,imm);|Rxに即値定数immを加算、Rx+=imm;|
|AddAI(a,imm);|Ax+=imm;|
|AddRR(r0,r1);|r0+=r1;|
|SubRI(r,imm);|Rx-=imm;|
|SubAI(a,imm);|Ax-=imm;|
|SubRR(r0,r1);|r0-=r1;|
|CmpJleRII(r,imm,lab);|Rxを即値定数immと比較して結果に応...
|CmpJccRII(r,imm,lab);|他にもJe/Jne/Jl/Jge/Jgがあるが、書...
|LbI(lab);|ラベル宣言、JmpやCalの飛び先指定に使用|
|JmpI(lab);|無条件分岐、goto lab;|
|JmpA(a);|無条件分岐、goto Ax;|
|CalII(lab,tmp);|サブルーチンのCALL命令、tmpは内部処理用...
|Ret();|サブルーチンからのリターン、return;|
-補足説明:
--CalII(lab,tmp); は実際はマクロになっていて、 MovAI(RP,t...
--Ret(); もマクロで、 JmpA(RP); になっています。
--load/storeでのメモリアドレッシングでは、ベースレジスタA...
** (3)
-サンプルコードとして、再帰でフィボナッチ数を求めるプログ...
-まず、C言語で書くとこんな感じです(prog1)。
int fib(int i)
{
if (i <= 1) return i;
return fib(i - 2) + fib(i - 1);
}
int main()
{
printf("fib(46)=%d\n", fib(46));
return 0;
}
-これをkharcアセンブラで書くとこうなります(prog2)。
LbI(1); // fib.
SubAI(SP,16);
StoAMd(RP,SP,8); // RPを保存.
LodRMd(R0,SP,16); // R0=引数.
CmpJleRII(R0,1,5);
LbI(2);
SubRI(R0,2); // fib(i-2)
StoRMd(R0,SP,0);
CalII(1,3);
StoRMd(R0,SP,12); // 結果を保存.
LodRMd(R0,SP,16); // fib(i-1)
SubRI(R0,1);
StoRMd(R0,SP,0);
CalII(1,4);
LodRMd(R1,SP,12); // 結果の加算.
AddRR(R0,R1);
LbI(5);
LodAMd(RP,SP,8); // RPの復元.
AddAI(SP,16);
Ret();
LbI(6); // main
SubAI(SP,8);
MovRI(R0,46); // fib(46)
StoRMd(R0,SP,0);
CalII(1,7);
// この時点のR0に答えが入っている.
** (4)
-ちょっとずるい方法でエミュレータを作りました(prog3)。
#include <stdio.h>
#include <stdlib.h>
#define kharcMain_bgn() int R0,R1,RP; vJmp: switch (...
#define kharcMain_end() }
#define MovRI(r,i) r=i
#define MovAI(a,i) a=i
#define LodRMd(r,b,d) r=*(int*)&m[b+d]
#define StoRMd(r,b,d) *(int*)&m[b+d]=r
#define LodAMd(a,b,d) a=*(int*)&m[b+d]
#define StoAMd(a,b,d) *(int*)&m[b+d]=a
#define AddRI(r,i) r+=i
#define AddAI(a,i) a+=i
#define AddRR(r0,r1) r0+=r1
#define SubRI(r,i) r-=i
#define SubAI(a,i) a-=i
#define CmpJleRII(r,i,l) if(r<=i)goto lb##l
#define LbI(l) lb##l: case l:
#define CalII(l,t) MovAI(RP,t); goto lb##l; LbI...
#define Ret() PC=RP; goto vJmp
int kharcMain(int PC, int SP, char *m)
{
kharcMain_bgn();
(上記のコード:prog2)
kharcMain_end();
return R0;
}
int main()
{
char *mem = malloc(1024);
int R0 = kharcMain(6, 1024, mem);
printf("R0=%d\n", R0);
return 0;
}
** (5)
-上記のプログラム(prog3)をCコンパイラでコンパイルして実...
-またprog1と実行時間を比較したところ、1.5倍ほどの違いしか...
--これは実装がこんなに平易だったことも考えれば、劇的に良...
--普通にエミュレータを書いたら10倍くらいは遅くなるのが普...
--それより速くするにはJITコンパイラ型のエミュレータを作る...
-[結論] この方法で、CPUのアーキテクチャを考えて、エミュレ...
-[次の課題] kharcアセンブラを出力するCコンパイラを作る。→...
** (6) 補足
-まあ結局、17行の#defineだけでアセンブラとCPUアーキテクチ...
-実行速度を上げるために、レジスタやメモリイメージ配列m[]...
-CmpとJcc命令を一体化させることで、ゼロフラグとかキャリー...
-[Q]今はCのマクロで簡単に書ける内容の処理しかしてないけど...
-[A]マクロに関数呼び出しを書くことはできますから、拡張性...
終了行:
* kharcs #1
-(by [[K]], 2025.06.03)
** (0) これはなに?
-(kharcsページは、kharc開発でわかったことを整理して説明...
-今回説明したいこと:
--[1]独自の仮想マシンの命令セットを考える
--[2]そいつのアセンブラを設計する
--[3]サンプルコードを書いてみる
--[4]エミュレータを作ってサンプルコードを実行してみる
** (1)
-開発のきっかけはいろいろありましたが、まあそれは本題では...
-簡単なCPUを設計してみました。レジスタマシンです。
-レジスタ:
--整数レジスタ(32bit): R0~R7
--アドレスレジスタ(32bit): A0~A7
--浮動小数点レジスタ(64bit): F0~F7 (しかし今回の実装...
-一般的なCPUではレジスタは「汎用レジスタ」とされ、整数用...
--(ちなみに今回の実装の範囲では、この整数レジスタとアド...
--(ああ、だから多くのCPUで汎用レジスタとして統合されてい...
-レジスタの別名:
--SP: A6の別名。スタックポインタ用。
--RP: A7の別名。return-pc。リターン用のプログラムカウンタ。
-演算系の命令(四則演算やビット演算など)はレジスタ間、レ...
-メモリ上のスタックに対する命令はなしにしました。何かをPU...
-CALLも戻り先をスタックには積まずに、リターンアドレスをRP...
-こうすることで、メモリアクセスを伴う命令はload/store命令...
-関数呼び出し規約(とりあえず):
--SP以外のレジスタは保存しなくてよい。
--関数の引数はすべてスタック渡し。レジスタ渡しはしない。
--スタック上の引数はintもポインタもdoubleもすべて8バイト...
--各関数のローカル変数については、好きなアラインでよい。
** (2)
-kharcのアセンブラは、C言語っぽい構文を採用してみました(...
-ラベルの名前解決が面倒だったので、ラベルはすべて番号にし...
-このアセンブラはバックエンドとして動けばいいので、頑張ら...
|MovRI(r,imm);|R0~R7に即値定数immを代入、Rx=imm;|
|MovAI(a,imm);|A0~A7に即値定数immを代入、Ax=imm;|
|LodRMd(r,base,disp);|Rxの値をメモリから読み込みます(loa...
|StoRMd(r,base,disp);|Rxの値をメモリに書き込みます(store...
|LodAMd(a,base,disp);|Ax=[Ax+disp];|
|StoAMd(a,base,disp);|[Ax+disp]=Ax;|
|AddRI(r,imm);|Rxに即値定数immを加算、Rx+=imm;|
|AddAI(a,imm);|Ax+=imm;|
|AddRR(r0,r1);|r0+=r1;|
|SubRI(r,imm);|Rx-=imm;|
|SubAI(a,imm);|Ax-=imm;|
|SubRR(r0,r1);|r0-=r1;|
|CmpJleRII(r,imm,lab);|Rxを即値定数immと比較して結果に応...
|CmpJccRII(r,imm,lab);|他にもJe/Jne/Jl/Jge/Jgがあるが、書...
|LbI(lab);|ラベル宣言、JmpやCalの飛び先指定に使用|
|JmpI(lab);|無条件分岐、goto lab;|
|JmpA(a);|無条件分岐、goto Ax;|
|CalII(lab,tmp);|サブルーチンのCALL命令、tmpは内部処理用...
|Ret();|サブルーチンからのリターン、return;|
-補足説明:
--CalII(lab,tmp); は実際はマクロになっていて、 MovAI(RP,t...
--Ret(); もマクロで、 JmpA(RP); になっています。
--load/storeでのメモリアドレッシングでは、ベースレジスタA...
** (3)
-サンプルコードとして、再帰でフィボナッチ数を求めるプログ...
-まず、C言語で書くとこんな感じです(prog1)。
int fib(int i)
{
if (i <= 1) return i;
return fib(i - 2) + fib(i - 1);
}
int main()
{
printf("fib(46)=%d\n", fib(46));
return 0;
}
-これをkharcアセンブラで書くとこうなります(prog2)。
LbI(1); // fib.
SubAI(SP,16);
StoAMd(RP,SP,8); // RPを保存.
LodRMd(R0,SP,16); // R0=引数.
CmpJleRII(R0,1,5);
LbI(2);
SubRI(R0,2); // fib(i-2)
StoRMd(R0,SP,0);
CalII(1,3);
StoRMd(R0,SP,12); // 結果を保存.
LodRMd(R0,SP,16); // fib(i-1)
SubRI(R0,1);
StoRMd(R0,SP,0);
CalII(1,4);
LodRMd(R1,SP,12); // 結果の加算.
AddRR(R0,R1);
LbI(5);
LodAMd(RP,SP,8); // RPの復元.
AddAI(SP,16);
Ret();
LbI(6); // main
SubAI(SP,8);
MovRI(R0,46); // fib(46)
StoRMd(R0,SP,0);
CalII(1,7);
// この時点のR0に答えが入っている.
** (4)
-ちょっとずるい方法でエミュレータを作りました(prog3)。
#include <stdio.h>
#include <stdlib.h>
#define kharcMain_bgn() int R0,R1,RP; vJmp: switch (...
#define kharcMain_end() }
#define MovRI(r,i) r=i
#define MovAI(a,i) a=i
#define LodRMd(r,b,d) r=*(int*)&m[b+d]
#define StoRMd(r,b,d) *(int*)&m[b+d]=r
#define LodAMd(a,b,d) a=*(int*)&m[b+d]
#define StoAMd(a,b,d) *(int*)&m[b+d]=a
#define AddRI(r,i) r+=i
#define AddAI(a,i) a+=i
#define AddRR(r0,r1) r0+=r1
#define SubRI(r,i) r-=i
#define SubAI(a,i) a-=i
#define CmpJleRII(r,i,l) if(r<=i)goto lb##l
#define LbI(l) lb##l: case l:
#define CalII(l,t) MovAI(RP,t); goto lb##l; LbI...
#define Ret() PC=RP; goto vJmp
int kharcMain(int PC, int SP, char *m)
{
kharcMain_bgn();
(上記のコード:prog2)
kharcMain_end();
return R0;
}
int main()
{
char *mem = malloc(1024);
int R0 = kharcMain(6, 1024, mem);
printf("R0=%d\n", R0);
return 0;
}
** (5)
-上記のプログラム(prog3)をCコンパイラでコンパイルして実...
-またprog1と実行時間を比較したところ、1.5倍ほどの違いしか...
--これは実装がこんなに平易だったことも考えれば、劇的に良...
--普通にエミュレータを書いたら10倍くらいは遅くなるのが普...
--それより速くするにはJITコンパイラ型のエミュレータを作る...
-[結論] この方法で、CPUのアーキテクチャを考えて、エミュレ...
-[次の課題] kharcアセンブラを出力するCコンパイラを作る。→...
** (6) 補足
-まあ結局、17行の#defineだけでアセンブラとCPUアーキテクチ...
-実行速度を上げるために、レジスタやメモリイメージ配列m[]...
-CmpとJcc命令を一体化させることで、ゼロフラグとかキャリー...
-[Q]今はCのマクロで簡単に書ける内容の処理しかしてないけど...
-[A]マクロに関数呼び出しを書くことはできますから、拡張性...
ページ名: