* HLX-001 の補足ページ#1 -(by [[K]], 2021.08.18) ** (6) HLX-001の共通中間コード -共通中間コードは、HL-9aの内部コードを真似して作りました。 -基本的にintの配列で、命令長は5の倍数になるようになっています。 --先頭が0の命令は拡張命令で、この形式のみが命令長を10以上にできます。それ以外の命令は命令長が5で固定です。 |[0]|[1]|[2]|[3]|[4]|| |AEs_Op0(0)|n|AEs_Op0Nop(0)|||何もしない命令です。nは命令長で、(n+1)*5が総命令長になります。| |AEs_Op0(0)|n|AEs_Op0DefLb(1)|lb||lbはラベル番号です。命令長nは1以上を指定することもできますが、普通は0にします。| |AEs_Op0(0)|n|AEs_Op0Jmp(2)|lb||無条件分岐命令です。これも通常はn=0です。| |AEs_Op0(0)|n|AEs_Op0Jcc(3)|lb||条件分岐命令です。条件は先行するCMP命令で指示します。これも通常はn=0です。| |AEs_Op0(0)|n|AEs_Op0Ent(4)|||関数宣言開始の命令です。| |AEs_Op0(0)|n|AEs_Op0Tet(5)|||関数を抜ける命令です。| |AEs_Op0(0)|n|AEs_Op0SysFn(6)|retVar|funcPtr|システム関数呼び出し(普通の関数)。詳細後述。| |AEs_Op0(0)|n|AEs_Op0SysFnP(7)|retVar|funcPtr|システム関数呼び出し(純関数)。詳細後述。| ||||||| |AEs_OpVoid(2)|var|0|0|0|指定した変数の値をこの先参照しない(=値が壊れてもよい)という情報を表します。| |AEs_OpSetCc(3)|var|0|0|0|先行するCMP命令の結果に応じてvarに0か1を代入します。| |AEs_OpCpy(4)|v0|v1|0|0|v0 = v1;| |AEs_OpNeg(5)|v0|v1|0|0|v0 = - v1;| |AEs_OpArySet(6)|0|v0|v1|v2|v0[v1] = v2;| |AEs_OpAryGet(7)|v0|v1|v2|0|v0 = v1[v2];| |AEs_OpCmpEq(8)|0|v0|v1|0|CMP(v0, v1);| |AEs_OpCmpNe(9)|0|v0|v1|0|CMP(v0, v1);| |AEs_OpCmpLt(10)|0|v0|v1|0|CMP(v0, v1);| |AEs_OpCmpGe(11)|0|v0|v1|0|CMP(v0, v1);| |AEs_OpCmpLe(12)|0|v0|v1|0|CMP(v0, v1);| |AEs_OpCmpGt(13)|0|v0|v1|0|CMP(v0, v1);| |AEs_OpCmpRnz(14)|0|0|0|0|直前の関数呼び出しの戻り値が0かどうか比較します。| |AEs_OpAdd(16)|v0|v1|v2|0|v0 = v1 + v2;| |AEs_OpSub(17)|v0|v1|v2|0|v0 = v1 - v2;| |AEs_OpMul(18)|v0|v1|v2|0|v0 = v1 * v2;| |AEs_OpDiv(19)|v0|v1|v2|0|v0 = v1 / v2;| |AEs_OpMod(20)|v0|v1|v2|0|v0 = v1 % v2;| |AEs_OpAnd(21)|v0|v1|v2|0|v0 = v1 & v2;| |AEs_OpShr(22)|v0|v1|v2|0|v0 = v1 >> v2;| |AEs_OpM64s(23)|v0|v1|v2|v3|v0 = ((AInt64) v1 * (AInt64) v2) >> v3;| -空欄は基本的にはゼロにします。 -Op0以外は、[1]に演算結果をしまう変数番号が入ります。[2], [3], [4]に演算に使用する変数番号を書きます。 --このフォーマットに統一することで最適化ルーチンを単純化できています。 -AEs_Op0SysFn, AEs_Op0SysFnPは以下のような形式になります。 |[0]|AEs_Op0(0)|基本命令コード。| |[1]|n|命令長。結果的に1以上を指定することになります。| |[2]|AEs_Op0SysFn(6), AEs_Op0SysFn(7)|補助命令コード。| |[3]|retVar|関数の戻り値を格納する変数を指定します。0なら戻り値は捨てられます。| |[4]|funcPtr|呼び出したい関数の関数ポインタです。| |[5]|m|引数の数です。引数がなければ0でも構いません。| |[6]|arg0|引数の変数番号です。| |[7]|arg1|| |[8]|arg2|| |[9]|arg3|| ** (7) HLX-001のx864の内部コード -x864では、x86/x64の機械語を出力する前に、もっと扱いやすい形式のデータを出力しています。 --x64ではレジスタ番号の上位1bitがREXの中に持っていかれたり、modr/mで[RSP]を指定するためにはsibバイトが必要だとか、[RBP]は指定できなくて[RBP+0]にしなきゃいけないとか、とにかく細かい例外事項が多くて面倒なのです。だからそういうのが一切ないシンプルなコード体系を作り、まずはそれを出力して最適化して、最後にx86/x64の機械語を出力するようにしています。 |00;|NOP(何も出力しません)| |01;|RET| |02:0_reg;|PUSH reg| |02:1_reg;|POP reg| |03:0_2:ptr;|CALL ptr| |03:1_1:lb;|JMP lb| |04_reg_rmi;|MOV reg,rmi (regへのload)| |05_reg_rmi;|MOV rmi,reg (regからのstore)| |06:0_ijk;|特殊な命令 HL-14aでいうところの%iLjk相当| |06:1_ijk;|6:0は必要に応じてEAX/RAXへのロードを行うが、6:1はそのロードを行わない| |07_i;|特殊な命令 HL-14aでいうところの%iS相当| |08:0_reg_rmi;|ADD reg,rmi| |08:1_reg_rmi;|OR reg,rmi| |08:2_reg_rmi;|ADC reg,rmi| |08:3_reg_rmi;|SBB reg,rmi| |08:4_reg_rmi;|AND reg,rmi| |08:5_reg_rmi;|SUB reg,rmi| |08:6_reg_rmi;|XOR reg,rmi| |08:7_reg_rmi;|CMP reg,rmi| |09_reg_rmi;|IMUL reg,rmi (reg *= rmi;)| |0a:0_rmi;|IMUL rmi (RDX:RAX = RAX * rmi;)| |0a:1_rmi;|IDIV rmi| |0a:5_rmi;|IMUL rmi (RDX:RAX = RAX * rmi;)| |0a:7_rmi;|IDIV rmi| |0b;|CQO, CDQ| |0c:4_reg;|SETE AL; MOVZX reg,AL| |0c:5_reg;|SETNE AL; MOVZX reg,AL| |0c:c_reg;|SETL AL; MOVZX reg,AL| |0c:d_reg;|SETGE AL; MOVZX reg,AL| |0c:e_reg;|SETLE AL; MOVZX reg,AL| |0c:f_reg;|SETG AL; MOVZX reg,AL| |0d:4_1:lb;|JE lb| |0d:5_1:lb;|JNE lb| |0d:c_1:lb;|JL lb| |0d:d_1:lb;|JGE lb| |0d:e_1:lb;|JLE lb| |0d:f_1:lb;|JG lb| |0e:7_reg;|SAR reg,CL| |0f:7_reg_0:i;|SAR reg,i| |10:0_lb;|lb: (ラベル宣言)| |11_reg;|TEST reg,reg| |12:3_reg;|NEG reg| |13:d_reg_rmi;|SHRD rmi,reg,CL (rmiがシフトされ、rmiに結果が入る。regが押し込まれる)| |14:c_reg_rmi_0:i|SHRD rmi,reg,i| |15_rmi;|rmi = void;| |16_reg_rmi;|LEA reg,rmi| -rmiは、レジスタ、メモリ、定数のいずれかを表すことができます。