acl4の開発ログ #07

  • (by K, 2026.03.04)
  • acl4開発のもくじ → a4_i01

2026.03.04(水) #0

  • 今日からインタプリタの基礎部分を作ります。

2026.03.04(水) #1

  • このプログラムを実行すると、私の好きなマンデルブロー集合の画像が出ます(出るはずです)。
  • だからこれを動かしたいです。
#define x   R00
#define y   R01
#define c   R02
#define sx  R03
#define sy  R04
#define cx  R05
#define cy  R06
#define zx  R07
#define zy  R08
#define xx  R09
#define yy  R0a
#define n   R0b
#define sn  R0c
#define t0  c
#define lp0 L0000
#define lp1 L0001
#define lp2 L0002
#define lp3 L0003
#define lp4 L0004
#define sk0 L0005
#define sk1 L0006

  Lod_RI(x, 512); Lod_RI(y, 384); Dbg_OpenWin_RR(x, y);
  Lod_RI(y, 0); Lbl_T(lp0); // for (y = 0; y < 384; y++) {
    Lod_RI(x, 0); Lbl_T(lp1); // for (x = 0; x < 512; x++) {
      Lod_RI(sn, 0);
      Lod_RI(sx, 0); Lbl_T(lp2); // for (sx = 0; sx < 4; sx++) {
        Lod_RR(cx, x); Mul_RI(cx, 4); Add_RR(cx, sx); Mul_RI(cx, 56); Add_RI(cx, 4673536);
        Lod_RI(sy, 0); Lbl_T(lp3); // for (sy = 0; sy < 4; sy++) {
          Lod_RR(cy, y); Mul_RI(cy, 4); Add_RR(cy, sy); Mul_RI(cy, -56); Add_RI(cy, -124928);
          Lod_RR(zx, cx); Lod_RR(zy, cy);
          Lod_RI(n, 1); Lbl_T(lp4); // for (n = 1; n < 447; n++) {
            Mul64Shr_RRRI(xx, zx, zx, 24);
            Mul64Shr_RRRI(yy, zy, zy, 24);
            Lod_RR(t0, xx); Add_RR(t0, yy); Jgt_RIT(t0, 0x4000000, sk0);
            Mul64Shr_RRRI(zy, zy, zx, 23);
            Lod_RR(zx, xx); Add_RR(zx, cx); Sub_RR(zx, yy);
            Add_RR(zy, cy);
            Add_RI(n, 1); Jlt_RIT(n, 447, lp4);
Lbl_T(sk0);
          Add_RR(sn, n);
          Add_RI(sy, 1); Jlt_RIT(sy, 4, lp3);
        Add_RI(sx, 1); Jlt_RIT(sx, 4, lp2);
      Shr_RI(sn, 4);
      Lod_RR(c, sn); Mul_RI(c, 256);
      Jlt_RIT(sn, 256, sk1); // if (sn >= 256) {
        Lod_RI(c, 0);
        Jge_RIT(sn, 447, sk1); // if (sn < 447) {
          Lod_RR(c, sn); Add_RI(c, 0xfe01);
Lbl_T(sk1);
      Mul_RI(c, 256);
      Dbg_SetPix_RRR(x, y, c);
      Add_RI(x, 1); Jlt_RIT(x, 512, lp1);
    Add_RI(y, 1); Jlt_RIT(y, 384, lp0);
  Dbg_Ret_I(0);
  • ちなみにこれは以下の定義を使えばC言語として動きます(この方法でデバッグしました)。
#define Lbl_T(t)        t:
#define Lod_RI(r, i)    r=i
#define Lod_RR(r, s)    r=s
#define Add_RI(r, i)    r+=i
#define Add_RR(r, s)    r+=s
#define Sub_RR(r, s)    r-=s
#define Mul_RI(r, i)    r*=i
#define Shr_RI(r, i)    r>>=i
#define Jlt_RIT(r, i, t)    if(r< i)goto t
#define Jge_RIT(r, i, t)    if(r>=i)goto t
#define Jgt_RIT(r, i, t)    if(r> i)goto t
#define Mul64Shr_RRRI(r, s, t, i)   r=(intptr_t)(((int64_t)s*(int64_t)t)>>i)
#define Dbg_OpenWin_RR(x, y)        a_Win win[1]; a_Win_ini(_arg_  win, x, y, "graphic", 0)
#define Dbg_SetPix_RRR(x, y, c)     setPix0(win, x, y, c)
#define Dbg_Ret_I(i)                a_Win_flushAll0(win); return i

2026.03.04(水) #2

  • 拡張性重視だと速度が落ちて、速度重視だと拡張性が落ちるので、どうしたらいいかいろいろ試しています。

2026.03.04(水) #3

  • とりあえず、これならいいかなと思える程度のものはできました。
  • でも現状ではmandelくらいのことしかできないので、ここからメモリアクセスとかポインタ演算とかを追加していく必要があります。

2026.03.05(木) #0

  • 昨日作ったものはこんな感じです。
  • [1] Windowsでグラフィックを出すための雑なプログラム: a4_t0001.c
    // #include <windows.h> してあることが前提.
    a_class(a_Win) {
        char title[256];
        int xsz, ysz;
        uint32_t *buf;
        HWND hWin;
        HINSTANCE hInst;
        BITMAPINFO bmi;
    };
    
    a_static a_Win *a_Win_opened = NULL;
    
    a_static LRESULT CALLBACK a_Win_wndProc(HWND hw, unsigned int msg, WPARAM wp, LPARAM lp)
    {
        if (msg == WM_CLOSE) return 0; // closeボタンを無視.
        if (msg == WM_DESTROY) { PostQuitMessage(0); return 0; }
        a_Win *w = a_Win_opened;
        if (w == NULL || w->hWin != hw)
            return DefWindowProc(hw, msg, wp, lp);
        if (msg == WM_PAINT) {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hw, &ps);
            SetDIBitsToDevice(hdc, 0, 0, w->xsz, w->ysz, 0, 0, 0, w->ysz, w->buf, &w->bmi, DIB_RGB_COLORS);
            EndPaint(hw, &ps);
            return 0;
        }
        return DefWindowProc(hw, msg, wp, lp);
    }
    
    a_static int a_Win_winThread(a_Win *w)
    {
        WNDCLASSEX wc;
        RECT r;
        int i;
        MSG msg;
    
        wc.cbSize = sizeof (WNDCLASSEX);
        wc.style = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc = a_Win_wndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = w->hInst;
        wc.hIcon = (HICON) LoadImage(NULL, MAKEINTRESOURCE(IDI_APPLICATION), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
        wc.hIconSm = wc.hIcon;
        wc.hCursor = (HCURSOR) LoadImage(NULL, MAKEINTRESOURCE(IDC_ARROW), IMAGE_CURSOR, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
        wc.hbrBackground = (HBRUSH) COLOR_APPWORKSPACE;
        wc.lpszMenuName = NULL;
        wc.lpszClassName = w->title;
        if (RegisterClassEx(&wc) == 0) return 1;
        r.left = 0; r.top = 0;
        r.right = w->xsz; r.bottom = w->ysz;
        AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, FALSE);
        w->hWin = CreateWindowA(wc.lpszClassName, w->title, WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,
            CW_USEDEFAULT, CW_USEDEFAULT, r.right - r.left, r.bottom - r.top, NULL, NULL, w->hInst, NULL);
        if (w->hWin == NULL) return 1;
        ShowWindow(w->hWin, SW_SHOW);
        UpdateWindow(w->hWin);
    
        for (;;) {
            i = GetMessage(&msg, NULL, 0, 0);
            if (i == 0 || i == -1) break; // エラーもしくは終了メッセージ.
            // そのほかはとりあえずデフォルト処理で.
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
        UnregisterClass(w->title, w->hInst);
        return 0;
    }
    
    a_static void a_Win_ini(_argDef_  a_Win *w, int xsz, int ysz, const char *title, uint32_t c)
    {
        #if (a_DbgLv >= 2)
            if (xsz < 192 || ysz < 64)
                a_errExit("%s(%d): Win_ini: bad window size: xsz=%d, ysz=%d", a_fil, a_lin, xsz, ysz);
            if (title == NULL || strlen(title) == 0 || strlen(title) >= 256)
                a_errExit("%s(%d): Win_ini: bad window title", a_fil, a_lin);
        #endif
        w->buf = a_malloc(_arg_  xsz * ysz * sizeof (uint32_t));
        int i;
        for (i = 0; i < xsz * ysz; i++) { w->buf[i] = c; }
        w->xsz = xsz; w->ysz = ysz;
        strcpy(w->title, title);
    
        w->bmi.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
        w->bmi.bmiHeader.biWidth = xsz;
        w->bmi.bmiHeader.biHeight = - ysz;
        w->bmi.bmiHeader.biPlanes = 1;
        w->bmi.bmiHeader.biBitCount = 32;
        w->bmi.bmiHeader.biCompression = BI_RGB;
        a_Win_opened = w;
        CreateThread(NULL, 0, (void *) a_Win_winThread, (void *) w, 0, (void *) &i);
        Sleep(128);
    }
    
    a_static void a_Win_flushAll0(a_Win *w)
    {
        InvalidateRect(w->hWin, NULL, FALSE);
        UpdateWindow(w->hWin);
    }
  • [2] 上記の mandel くらいなら余裕で動かせるVM: tt0001a.c
    #define a_Version 1
    #include <acl4.c>
    #include <windows.h>
    #include "a4_t0001.c"
    
    void myPut(Preprocessor_Put0 *w, VecChr *lin) // t0013b.cで作ったやつと同じ.
    {
        if (Preprocessor_isDirective(lin->p, lin->p + lin->n) != 0)
            fprintf(stderr, "[err] %.*s", (int) lin->n, lin->p);
        else
            VecChr_puts(w->dst, lin->p, lin->p + lin->n);
    }
    
    int main(int argc, const char **argv)
    {
        if (argc < 2) return 1;
        Preprocessor pp[1]; Preprocessor_ini(pp);                            // プリプロセッサの初期化.
        pp->put = (void *) myPut;                                            // 出力関数の変更.
        Preprocessor_SourceFiles_addFile(pp->sfs, argv[1], strlen(argv[1])); // 入力ファイルを指定.
        Preprocessor_SourceFiles_addFile(pp->sfs, "a4vm-asm-tt0001.h", 17);  // 最初にこれをインクロードさせる.
        VecChr vc[1]; VecChr_ini(vc); pp->put0->dst = vc;                    // 出力先のオブジェクトを指定.
        Preprocessor_main(pp); Preprocessor_din(pp);                         // プリプロセッサ処理. およびメモリ開放.
    
        Token0 t0[1]; Token0_ini1(t0);
        t0->s = vc->p; t0->s1 = vc->p + vc->n;
        Preprocessor_Eval ev[1]; ev->err = 0;
        VecChr bin[1]; VecChr_ini(bin);
        for (;;) {
            intptr_t i = Preprocessor_eval(ev, t0, 0x7fff);
            if (ev->err != 0) break;
            VecChr_resizeDiff(_arg_  bin, sizeof (intptr_t)); ((intptr_t *) (bin->p + bin->n))[-1] = i;
            Token1_get(t0);
            if (t0->c != ',' && t0->c != ';') break;
        }
    
        intptr_t *bp = (intptr_t *) bin->p, bn = bin->n / sizeof (intptr_t), reg0[256], *reg, lab[256], pc;
        for (pc = 0; pc < bn; pc += 4) {
            if (bp[pc] == 0x01)
                lab[bp[pc + 1]] = pc + 4;  // Lbl_T(t)命令の次の命令を指す.
        }
        a_Win win[1]; win->buf = NULL;
        clock_t tm0 = clock(); reg = reg0 + 256 - 16;
        for (pc = 0;;) {
            intptr_t op = bp[pc], a = bp[pc + 1], b = bp[pc + 2], c = bp[pc + 3], d, e, f; pc += 4;
            switch (op) {
            case 0x01:                    continue; // Lbl_T(t)
            case 0x02: reg[a] = b;        continue; // Lod_RI(r, i)
            case 0x03: reg[a] = reg[b];   continue; // Lod_RR(r, s)
            case 0x18: reg[a] <<= b;      continue; // Shl_RI(r, i)
            case 0x19: reg[a] <<= reg[b]; continue; // Shl_RR(r, s)
            case 0x1a: reg[a] >>= b;      continue; // Shr_RI(r, i)
            case 0x1b: reg[a] >>= reg[b]; continue; // Shr_RR(r, s)
            case 0x20: reg[a] += b;       continue; // Add_RI(r, i)
            case 0x21: reg[a] += reg[b];  continue; // Add_RR(r, s)
            case 0x22: reg[a] -= b;       continue; // Sub_RI(r, i)
            case 0x23: reg[a] -= reg[b];  continue; // Sub_RR(r, s)
            case 0x24: reg[a] *= b;       continue; // Mul_RI(r, i)
            case 0x25: reg[a] *= reg[b];  continue; // Mul_RR(r, s)
            case 0x38: if (reg[b] <  c)      { pc = lab[a]; } continue; // Jlt_RI(r, i, t)
            case 0x39: if (reg[b] <  reg[c]) { pc = lab[a]; } continue; // Jlt_RR(r, s, t)
            case 0x3a: if (reg[b] >= c)      { pc = lab[a]; } continue; // Jge_RIT(r, i, t)
            case 0x3b: if (reg[b] >= reg[c]) { pc = lab[a]; } continue; // Jge_RRT(r, s, t)
            case 0x3c: if (reg[b] <= c)      { pc = lab[a]; } continue; // Jle_RIT(r, i, t)
            case 0x3d: if (reg[b] <= reg[c]) { pc = lab[a]; } continue; // Jle_RRT(r, s, t)
            case 0x3e: if (reg[b] >  c)      { pc = lab[a]; } continue; // Jgt_RIT(r, i, t)
            case 0x3f: if (reg[b] >  reg[c]) { pc = lab[a]; } continue; // Jgt_RRT(r, s, t)
    
            case 0x04: goto fin; // Dbg_Ret_I(i)
            case 0x06: d = bp[pc + 1]; pc += 4; reg[a] = (intptr_t)(((int64_t)reg[b]*(int64_t)reg[c])>>d); continue; // Mul64Shr_RRRI(r, s, t, i)
            case 0x08: a_Win_ini(_arg_  win, reg[a], reg[b], "graphic", 0); tm0 = clock(); continue; // Dbg_OpenWin_RR(x, y)
            case 0x09: win->buf[reg[b] * win->xsz + reg[a]] = reg[c]; continue; // Dbg_SetPix_RRR(x, y, c)
            case 0x36: reg[b]++; if (reg[b] <  c) { pc = lab[a]; } continue; // IncJlt_RIT(r, i, t)
    
            case 0x00: case 0x05: case 0x07: case 0x0a: case 0x0b: case 0x0c: case 0x0d: case 0x0e: case 0x0f:
            case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17: case 0x1c: case 0x1d: case 0x1e: case 0x1f:
            case 0x26: case 0x27: case 0x28: case 0x29: case 0x2a: case 0x2b: case 0x2c: case 0x2d: case 0x2e: case 0x2f:
            case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x37: continue; // すべてのcaseを書くと速くなる(コンパイラの最適化の都合).
            }
        }
    fin:
        printf("tm=%d\n", clock() - tm0);
        if (win->buf != NULL) {
            a_Win_flushAll0(win);
            for (;;) { Sleep(1000); } // Ctrl-C で終了させる.
        }
        VecChr_din4(vc, bin, 0, 0);
        a_malloc_debugList(_arg);
        return 0;
    }
  • [3] 上記のために作った設定ファイル: a4vm-asm-tt0001.h
    #define R00  0
    #define R01  1
    #define R02  2
    #define R03  3
    #define R04  4
    #define R05  5
    #define R06  6
    #define R07  7
    #define R08  8
    #define R09  9
    #define R0a  10
    #define R0b  11
    #define R0c  12
    #define R0d  13
    #define R0e  14
    #define R0f  15
    
    #define L0000    0
    #define L0001    1
    #define L0002    2
    #define L0003    3
    #define L0004    4
    #define L0005    5
    #define L0006    6
    #define L0007    7
    
    #define Lbl_T(t)         0x0001, t, 0, 0
    #define Lod_RI(r, i)     0x0002, r, i, 0
    #define Lod_RR(r, s)     0x0003, r, s, 0
    
    #define Or__RI(r, i)     0x0010, r, i, 0
    #define Or__RR(r, s)     0x0011, r, s, 0
    #define Xor_RI(r, i)     0x0012, r, i, 0
    #define Xor_RR(r, s)     0x0013, r, s, 0
    #define And_RI(r, i)     0x0014, r, i, 0
    #define And_RR(r, s)     0x0015, r, s, 0
    
    #define Shl_RI(r, i)     0x0018, r, i, 0
    #define Shl_RR(r, s)     0x0019, r, s, 0
    #define Shr_RI(r, i)     0x001a, r, i, 0
    #define Shr_RR(r, s)     0x001b, r, s, 0
    
    #define Add_RI(r, i)     0x0020, r, i, 0
    #define Add_RR(r, s)     0x0021, r, s, 0
    #define Sub_RI(r, i)     0x0022, r, i, 0
    #define Sub_RR(r, s)     0x0023, r, s, 0
    #define Mul_RI(r, i)     0x0024, r, i, 0
    #define Mul_RR(r, s)     0x0025, r, s, 0
    
    #define Div_RI(r, i)     0x0028, r, i, 0
    #define Div_RR(r, s)     0x0029, r, s, 0
    #define Mod_RI(r, i)     0x002a, r, i, 0
    #define Mod_RR(r, s)     0x002b, r, s, 0
    
    #define Jeq_RIT(r, i, t) 0x0030, t, r, i
    #define Jeq_RRT(r, s, t) 0x0031, t, r, s
    #define Jne_RIT(r, i, t) 0x0032, t, r, i
    #define Jne_RRT(r, s, t) 0x0033, t, r, s
    
    #define Jlt_RIT(r, i, t) 0x0038, t, r, i
    #define Jlt_RRT(r, s, t) 0x0039, t, r, s
    #define Jge_RIT(r, i, t) 0x003a, t, r, i
    #define Jge_RRT(r, s, t) 0x003b, t, r, s
    #define Jle_RIT(r, i, t) 0x003c, t, r, i
    #define Jle_RRT(r, s, t) 0x003d, t, r, s
    #define Jgt_RIT(r, i, t) 0x003e, t, r, i
    #define Jgt_RRT(r, s, t) 0x003f, t, r, s
    
    #define Dbg_Ret_I(i)                0x0004, i, 0, 0
    #define Mul64Shr_RRRI(r, s, t, i)   0x0006, r, s, t, 0x0, i, 0, 0
    #define Dbg_OpenWin_RR(x, y)        0x0008, x, y, 0
    #define Dbg_SetPix_RRR(x, y, c)     0x0009, x, y, c
    #define IncJlt_RIT(r, i, t)         0x0036, t, r, i
  • 実行結果:
    >tt0001a mandel_asm.txt
    • https://essen.osask.jp/files/pic20260306a.png

2026.03.05(木) #1

  • 次にやるべきことを考えました。
  • まず考えなきゃいけないのは、背伸びしすぎないことです。あんなことやこんなことができるすごいVMを作りたい衝動が毎日来るわけですが(笑)、そんなものは今はいらなくて、今は作りやすくて理解しやすいVMです。すごいのを作るのはその先の話のです。
  • とりあえず、関数を記述できるようになるべきですね。それが次の一歩になりそうです。

2026.03.05(木) #2

  • [Q] 確かCコンパイラを作りたいんですよね?こんな自作仮想マシンを作っているのは遠回りじゃないですか?
    • [A] C言語のソースコードを、自作仮想マシンのアセンブラに「コンパイル」することを考えています。
    • それができさえすれば、自作仮想マシンから実際のCPUのアセンブラや機械語に変換するのはかなり簡単です。
      #define Lod_RI(r, i)  X86_MOV_M32EbpDispI(r*4, i); // MOV DWORD [EBP+r*4],i
      #define Lod_RR(r, s)  X86_MOV_R32M32EbpDisp(EAX,s*4); X86_M32EbpDispR32(r*4,EAX);
    • だから遠回りのようでいて、実はそうではないのです。
    • このようなVMコードを経由しないでコンパイラを書こうとすると、CPUが変わるたびに作り直ししなければいけません。
    • コード生成部をきれいに切り分けておけば、複数のCPUに対応するのはほとんど手間がかからなくなります(最適化はやりにくくなる可能性はあります)。

2026.03.06(金) #0

  • a4vmの仕様:
    • レジスタマシン。整数レジスタ・ポインタレジスタは区別がない(汎用レジスタモデル)。
    • レジスタは intptr_t と同じ幅を持つ。だから環境によってレジスタの幅が変わりうる。
    • intptr_t はポインタを格納するのに十分な大きさの整数型ということになっているため、16ビット環境では16ビットになる可能性があると思われるが、とりあえず今のバージョンでは intptr_t が16ビットになる場合を想定せず、32ビット以上だと仮定する。
      • 16ビットの場合は、将来ちゃんと考えなおす。今から考えると考えることが多くなりすぎて手に負えなくなりそうだから。
    • レジスタは関数ごとにローカルな存在で、関数呼び出しによって破壊されないし、またレジスタ数も関数宣言時に好きな数に宣言できる。8や16でもいいし、100とか1000でもいい。
      • 実際のCPUではレジスタはグローバルな存在だけど、a4vmの中ではローカルとして扱う。その差異は、実CPUの命令列に置き換えるときに埋める。
      • 関数の戻り値をどう返すかは、後で考える必要がある。レジスタが完全に関数ローカルだと返せないから。
      • これらのレジスタを実際のレジスタに対応させるかどうかは、処理系依存。実際はスタック変数であってもかまわない。
      • レジスタ数に上限を設けないほうが、コンパイラを作りやすくなるだろうと考えた。
    • 浮動小数点レジスタもあとで考えるけど、たぶん関数ローカルで、個数を関数宣言時に決められるのは同じにすると思う。
  • 構想:
    • 関数の引数でレジスタ渡しを認めず、全部スタック渡しにすれば、たぶん話はシンプルになるからそれがいいかもしれない。
    • 戻り値についても、スタックの所定の場所に書いておくっていうやり方が可能ならそれでいいかもしれない。
    • これだと関数間のデータのやり取りは全部メモリ経由になってシンプルだともいえるし、返す値の個数を2個以上にする余地も生まれる。

2026.03.09(月) #0

  • C言語では、スタックに引数を積んで関数を呼び出すという「スタック渡し」が一般的です。
  • C言語がはやる前、機械語やアセンブラが主流の時は、レジスタに値を代入して呼び出す、レジスタ渡しが一般的でした。今でもBIOSの呼び出しなどにその名残りを見ることができます。
  • しかしx86-64時代になって、使えるレジスタが増えてくると、C言語も一部の引数をレジスタで渡すようになりました。
  • 結局のところ、できればレジスタ渡しが理想なんでしょう。・・・呼び出された関数のほうから見れば、スタック上に引数があってもそのままでは利用できず、レジスタに読み込んで使うしかないからです。それなら最初からレジスタに入れて呼べばいいじゃないか、とまあそういう話になるわけです。
  • ではなぜ以前はスタック渡しが主流だったのかと言えば、それはx86のレジスタが足りなかったからです。x86-64でも引数の数が既定の数を超えれば、超えた分はスタック渡しで渡されます。
  • C言語では引数の個数に上限などは規定されていないので、引数がレジスタ数よりも多くなることはあり得ることです。スタックならかなり大きくて余裕があるので、引数がかなり長くなっても余裕で対応できるというわけです。
  • 2000年ごろ、私はOSASKを作っていたのですが、関数呼び出しのたびにスタックに値を書き込んでから関数を呼び出すという方式に素朴な疑問を持ちました。レジスタならプログラムで値をロードしなければ意図した値をセットすることはできませんが、メモリならデータ領域に書いておけば、プログラム側では何もしなくても、意図した値にしておくことができるわけです。
  • スタックなんていう「初期値の設定できない場所」に引数を書け、というルールがダメだと直感で思いました。そこで「引数列はメモリ上のどこにおいてもよくて、どこに引数列があるのかをEBXレジスタで渡す」というポインタ渡し方式を採用しました。
  • この方法はスタック渡しとも相性がいいのです。なぜならスタックに積んだ後で EBX=ESP; を実行して、それから関数を呼び出せばいいからです。たった1命令のオーバーヘッドです。
  • 逆はできません。つまりスタック渡しでしか受け取れないC言語の関数は、ポインタ渡しをされても、結局引数列をスタックに積みなおすしか受け取り手段がなくて、それはかなり大きなオーバヘッドになってしまうのです。
  • 今では高速化のためにレジスタ渡しをするわけですが、レジスタ渡しとポインタ渡しはどっちがいいのでしょうか。かつての私はそれも考えました。私はポインタ渡しがレジスタ渡しよりも優れていると思います。
  • え?レジスタに入れておけば、関数側で値を読み込む手間がなくなるのに、それよりもいいことなんてあるもんか、と思うでしょう。でもそれがあるんです。そもそも呼び出し側ではメモリに書き込む操作すらしてないのです。だから関数側がメモリからレジスタに入れる処理をしても、(レジスタ渡しと比較して)追加のオーバーヘッドにはなってないのです。
  • ・・・ということが私にはすでに分かっているので、自作のCコンパイラでも関数呼び出しはポインタ渡しにしようかなあと少し思うのですが、そうやって独自拡張をやると「理解しにくい」コンパイラになってしまうので、やっぱり今回はやらないことにします。
  • 将来のバージョンで検討します。

2026.03.11(水) #0

  • 2026.03.06(金) #0 の時に考えた「戻り値もスタックに書き込むことで返す」っていうのは、結構うまいと思うようになりました。EAXやRAXなどで返す方法だと、構造体とかは返せないので、その場合だけ工夫しなければいけません。でもメモリで返す方法なら、サイズの制限とかはないので、シンプルでいいなと思いました。

2026.03.12(木) #0

  • 理想的なレジスタの使い方を考えています、なんとなく。
    • スタックポインタ: これはほしいです。
    • 引数ポインタ: 今はいらないけど、将来的には欲しいです。
    • 返値ポインタ: 今はいらないけど、将来的には欲しいです。
    • thisポインタ: これはほしいです。
  • 仮想マシンの仕様をうまく工夫したら、引数のいろんな渡し方を統合できないかなあ。

2026.03.13(金) #0

  • a4vmのメモリアクセスをどうするかずっと考えていました。a4vmでは引数を受け取るにはメモリアクセスが(とりあえず)必須なので、ここが決まらないと何も始まらないわけです。
  • それで結局、データサイズとディスプレースメントをそのまま指定する、普通のCPUと同じようなモデルを採用することにします。
  • sizeof (int)がどうだとか、構造体がどうだとかの情報をa4vmは管理せず、その上のレイヤで解決してもらおうというものです。
  • これでメモリアクセスのための命令の設計ができそうです。

2026.03.13(金) #1

  • R00だけ例外的に(実CPUのAX/EAX/RAXのような)関数においてreturn値を返すレジスタに強制的に対応させるというルールを作れば、「a4vmから普通のコンパイラで作ったC関数が呼べない」という問題を解決できそうです。
    • ここでずっと悩んでいました。
  • あとは(x86-64みたいな)一部の引数をレジスタ渡しするような仕様をどう吸収するかです。
    • まあそうでなくてもレジスタ渡しの __fastcall というやつはあるので、厳密にいえば x86 でも解決すべき問題ではあります。
    • 結局、実レジスタを指定して代入する命令も必要だということですね。

2026.03.13(金) #2

  • まだ自分が納得できていないので考えます。
  • 第一にa4vmの目的は、実CPUに依存しない中間コードを扱うことでした。こうすることでコンパイラが楽できるはずだと考えました。
  • だからまずはレジスタを抽象化して、コンパイラの都合でいくつでも使えることにしました。
  • 一方で、C言語の引数渡しの方法はかなり多様性があります-。
  • これに対応するには引数の渡し方をコンパイラ側が関知せずに、a4vm側で処理する必要があるでしょう。
  • そうすると引数の型をa4vmは把握しなければいけません。スタック渡し(=メモリ経由)になる可能性があるからです。
  • [呼び出す場合] 引数はすべて定数もしくはレジスタ(=a4vmにおける仮想レジスタ)で指定するとして、返値はレジスタで受け取るということにすれば、まあそれほど複雑にならずに済むかもしれません。
  • [呼ばれる場合] やはり引数はレジスタで受け取るということにするべきでしょうか。なんかオーバーヘッドがあるような気がしますが、まあでもしょうがないかもしれません。
  • a4vmで書かれた関数をa4vmから呼ぶ場合についても、上記のやり方でやることができますが、a4vm間呼び出しというのがあるべきです。それだと高速にできたり、高度なことができるといいです。
  • a4vm間の呼び出しについての仕様については今決める必要はなくて、まずは外部とのやり取りが可能な形式だけ設計すればよさそうです。
    • a4vm間呼び出しでは、引数も返値もメモリ経由になる。ポインタ渡しになって、高速化もされる。私の理想を好きなだけ実験して決めればいい。

2026.03.13(金) #3

  • 手元では引数の受け取りと、a4vmからprintf関数の呼び出しができましたー。
  • 次は再帰を頑張ります。

2026.03.13(金) #4

  • 再起呼び出しもできました。それぞれ動いてきちんと答えが出ました。
// tt0002c.txt: 階乗計算.
#define i       R00
#define fnc     R01
#define j       R02

  Arg2_RR(fnc, i); // fncは自分自身への関数ポインタ. 呼び出し元が与えている.
  Jle_RIT(i, 1, L0000);
  Add_RI(i, -1);
  Afn_RR_RR(j, fnc, fnc, i); // j = fnc(fnc, i);
  Add_RI(i, +1); // iを元に戻す.
  Mul_RR(i, j);
  Dbg_Ret_R(i);
Lbl_T(L0000);
  Dbg_Ret_I(1);
// tt0002d.txt: フィボナッチ数.
#define i       R00
#define fnc     R01
#define j       R02

  Arg2_RR(fnc, i); // fncは自分自身への関数ポインタ. 呼び出し元が与えている.
  Jle_RIT(i, 1, L0000);
  Add_RI(i, -2);
  Afn_RR_RR(j, fnc, fnc, i); // j = fnc(fnc, i);
  Add_RI(i, +1);
  Afn_RR_RR(i, fnc, fnc, i); // i = fnc(fnc, i);
  Add_RR(i, j);
Lbl_T(L0000);
  Dbg_Ret_R(i);
  • これだけ部品がそろえば、そろそろCコンパイラも作れそうです。
    • 直接a4vmのコードを書くのもちょっと飽きてきました(笑)。

2026.03.13(金) #5

  • ちょっとメモ:

こめんと欄


コメントお名前NameLink

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2026-03-14 (土) 11:29:23 (94d)