* kcas #1
-(by [[K]], 2025.04.30)

** (0) kcasとはなんですか?
-Kが考えた新しいアセンブラの構文です。構想はだいぶ前から持っていたのですが、きちんとした内容にまとめたのは2025年からです。

** (1) 基本構文-1
-ここでは話を分かりやすくするために、x86向けのアセンブラに限定して書きます。

 void casMain()
 {
     casMain_init;
     ORG(0x100);
     MOV8RI(AH, 0x02); // AH=02: 1文字出力.
     MOV8RI(DL, 0x20);
 LB(lp);
     INC16R(DX);
     CMP8RI(DL, 0x7f);
     JNE(lp);
 }
-ORG命令は説明不要として、次のMOV8RIを説明すると、これは MOV 命令の8bit版の「レジスタ,即値」形式です。
-同様に、INC16RはINC命令の16bit版の「レジスタ」形式ですし、CMP8RIはCMP命令の8bit版の「レジスタ,即値」形式になります。
-JNEはJNE命令になります。LB命令はラベル宣言命令です。
-ちなみにこのプログラムはINT21Hをあえて書いてないので何も出力しません。

-このアセンブラは、C言語の文法を利用して構成されています。セミコロンで切ってあるので、1行にいくつでも命令を並べることができますし、コメントも入れられます。#defineも使えますし、enumも使えます。
-C言語のルールなので、大文字小文字は区別されます。kcasでは[[K]]の趣味により、命令もレジスタ名もすべて大文字で書きます。

** (2) メリット-1
-このkcasにはどんなメリットがあるでしょうか。それは、以下のようなファイルをインクルードしてコンパイルして実行すれば、NASM形式でのプログラムに簡単に変換できることです。

 // kcas to nasm
 #include <stdio.h>
 #define casMain_init  ;
 void casMain();
 int main() { casMain(); return 0; }
 #define MOV8RI(r, i)  MOV8RI_(#r, i)
 void MOV8RI_(const char *r, int i) { printf("    MOV   %s,%d\n", r, i); }
 #define INC16R(r)  INC16R_(#r)
 void INC16R_(const char *r) { printf("    INC   %s\n", r); }
 // 以下略

** (3) メリット-2
-もう一つのメリットとして、以下のようなファイルをインクルードしてコンパイルすれば、標準的なC言語が使える環境なら、どこにでも移植できるということです(実行環境はx86に限定されません)。

 // kcas to C
 #include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
 void casMain();
 int main() { casMain(); return 0; }
 uint16_t ax, cx, dx, bx, sp, bp, si, di;
 char CF, ZF, SF, OF;
 #define casMain_init   int32_t i32
 #define MOV8RI(r, i)   MOV8RI_ ## r(i)
 #define MOV16RI(r, i)  MOV16RI_ ## r(i)
 #define INC16R(r)      INC16R_ ## r()
 #define CMP8RI(r, i)   CMP8RI_ ## r(i)
 #define LB(l)          l:
 #define al     (ax & 0xff)
 #define dl     (dx & 0xff)
 #define ah     (ax >> 8)
 #define dh     (dx >> 8)
 #define MOV8RI_AL(i)   ax = (ax & 0xff00) | ((i) & 0xff)
 #define MOV8RI_AH(i)   ax = (ax & 0x00ff) | ((i) & 0xff) << 8
 #define MOV8RI_DL(i)   dx = (dx & 0xff00) | ((i) & 0xff)
 #define MOV16RI_AX(i)  ax = (i)
 #define JNE(lb)        if (ZF == 0) goto lb
 #define INC16R_DX()    dx++; ZF = (dx == 0); SF = dx >> 15; OF = ((dx & 0x7fff) == 0)
 #define CMP8RI_DL(i)   i32 = dl - (i); CF = (i32 < 0); ZF = (i32 == 0); SF = (i32 >> 15) & 1; /* todo OF */
 #define ORG(i)         /* ORG */
-ということで、kcasで一度書いておけば、エミュレータを使わずに高速に実行できますし(アセンブラなのに移植性が高い?笑)、NASMでもMASMでもGNU-asにも変換できるわけです(原理的には)。

** (4) 基本構文-2
-アセンブラにはCALL/RET命令があります。これを実現するために、書き方を拡張します。

 void casMain()
 {
     casMain_init;
     ORG(0x100);
     int vip = 0, vcs = 0;
 vjmp:
     // このプログラムではコードセグメントは1つしかないので、vcsによる分岐命令は省略している.
     switch(vip) {
     case 0:
         MOV8RI(AH, 0x02); // AH=02: 1文字出力.
         MOV8RI(DL, 0x20);
 LB(lp);
         INT(0x21, 1); case 1: // int-no, vip.
         INC16R(DX);
         CMP8RI(DL, 0x7f);
         JNE(lp);
         MOV16RI(AX, 0x4c00);
         INT(0x21, 2); case 2: ;
     }
 }
-この書き方のポイントは、INT命令のようなスタックにIP値を積む命令の場合、本当のIP値を積むのではなくvirtualなIPを積みます(C言語に変換して実行する場合)。同様にCSを積む場合は本当のCS値を積むのではなくvirtualなCSを積みます(同じくC言語に変換して実行する場合)。
-もちろんアセンブラや機械語に変換する場合は、こんなvip値は全く不要なので無視されます。
-C言語に変換する場合、CALL/RETはこうなります。以下はnear-call, near-retの例です。
 #define CALL16(lb, vi)     PUSH16I(vi); goto lb
 #define RET16()            vip = pop16(); goto vjmp
-関数ポインタのようにラベルの値を取得する必要がある時も、case命令を書いてvip値を定めます。

** (5) コード例
-再帰でフィボナッチ数を計算するやつを作ってみました。CALL/RETがちゃんと動くのかテストしたかったのです。

 void casMain()
 {
     casMain_init;
     ORG(0x100);
     int vip = 0, vcs = 0;
 vjmp:
     // このプログラムではコードセグメントは1つしかないので、vcsによる分岐命令は省略している.
     switch(vip) {
     case 0:
         MOV16RI(CX, 20);
         CALL16(fib, 1); case 1:
         printf("AX=%d\n", ax); // ズル.
         return; // ズル.
 
     LB(fib); // CXに引数を入れてCALL16すると、AXに結果を返す. AX以外のレジスタは破壊しない.
         MOV16RR(AX, CX);
         CMP16RI(CX, 2);
         JB(skip);
         PUSH16R(DX);
         DEC16R(CX);
         CALL16(fib, 2); case 2:
         MOV16RR(DX, AX);
         DEC16R(CX);
         CALL16(fib, 3); case 3:
         ADD16RR(AX,DX);
         INC16R(CX);
         INC16R(CX);
         POP16R(DX);
     LB(skip);
         RET16();
     }
 }
-C言語化して動かしたら、うまくいきましたー。

-もうCALL16マクロの中にcaseの記述も入れちゃえばいいかもしれない。そうすれば見た目はすっきりする。
-もうCALL16マクロの中にcaseの記述も入れちゃえばいいかもしれないです。そうすれば見た目はすっきりします。

-しかしそれにしても、C言語ってすごく柔軟だなーって思います。他の言語でこんなことができるかどうか自信ないです。

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS