HLX-001 の補足ページ#1
(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: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は、レジスタ、メモリ、定数のいずれかを表すことができます。
|