EssenR2_jit00
の編集
https://khfdpl.osask.jp:443/wiki/?EssenR2_jit00
[
トップ
] [
編集
|
差分
|
バックアップ
|
添付
|
リロード
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
-- 雛形とするページ --
2016_10
2016_11
2016_12
BracketName
Essen
Essen0
Essen1
Essen2
Essen3
Essen4
EssenMemo0001
EssenMemo0002
EssenMemo0003
EssenMemo0004
EssenMemo0005
EssenMemo0006
EssenMemo0007
EssenMemo0008
EssenMemo0009
EssenMemo0010
EssenMemo0011
EssenR2
EssenR2_ess03f
EssenR2_ess03h
EssenR2_ess03i
EssenR2_ideas
EssenR2_jit00
EssenR2_jit01
FormattingRules
FrontPage
Help
IP
InterWiki
InterWikiName
InterWikiSandBox
K
KHPC
KHPC_v000doc01
KHPC_v001doc01
KHPC_v002doc01
KHPC_v003doc01
MenuBar
OSC
OSC20181027
OSC20190222
OSC20191123
OSC20230401
OSC20230528
OSC20231021
OSC20240310
OSC20241026
PHP
PukiWiki
PukiWiki/1.4
PukiWiki/1.4/Manual
PukiWiki/1.4/Manual/Plugin
PukiWiki/1.4/Manual/Plugin/A-D
PukiWiki/1.4/Manual/Plugin/E-G
PukiWiki/1.4/Manual/Plugin/H-K
PukiWiki/1.4/Manual/Plugin/L-N
PukiWiki/1.4/Manual/Plugin/O-R
PukiWiki/1.4/Manual/Plugin/S-U
PukiWiki/1.4/Manual/Plugin/V-Z
RecentDeleted
SandBox
SltVA
VariableArray
WikiEngines
WikiName
YukiWiki
advcal20161205
advcal20161206
advcal20161209
advcal20161210
advcal20161215
eoml0001
eoml0002
essen_ex01_0001
impressions
kcl_malloc
khfdpl_result1
members
memo0001
memo0002
note0001
note0002
note0003
note0004
note0005
note0006
oldworks
oldworks00
oldworks06
oldworks12
oldworks13
osaskjp_index
persistent_C
populars
pr20161105
pr20161105b
scsc
seccamp2017
spam_test
uxf
uxf_01
uxf_02
uxp
* Essen Rev2 JIT00 -(by [[K]], 2017.07.31) ** JIT00で提供する機能 -「即値が使えない命令」のような例外をなくす -ラベルを使えるようにする -CPUに依存するレイヤをここに集約する(JIT01以降はCPUに依存しない) -実際のCPUのレジスタ数がいくつであるかなどの制約を受けずに済むようにする ** 基本仕様 -c# : 即値 -l# : ローカル変数(#=0~) -g# : グローバル変数 -L# : 分岐先指定などに使うラベル -p# : ポインタレジスタ(p0~p63) -i# : s32以上レジスタ(i0~i63) -f# : f64以上レジスタ(f0~f63) -ローカル変数、グローバル変数には、どんな型の値でも代入できる。なぜならEssenは値が型を持つ言語だから。 -ポインタレジスタはポインタしか代入できない。 -s32以上レジスタは整数しか代入できない。 -f64以上レジスタは浮動小数点しか代入できない。 -p#, i#, f#はグローバル変数で、これをうまく使いこなせるかどうかはJIT01レイヤ次第(使えばきっと高速化には寄与する)。 -基本構文 命令 パラメータ1 パラメータ2 ... -人間にとって書きやすい構文にしようとしていない。どうせこれはJIT01から渡されてくる中間言語でしかない。 --だからコメントやマクロなどはない。 -パラメータはスペース区切り(コンマはいらない) -命令の命名規則 --最初の一文字はnかs。nはノーマル。sはスピード優先。 --二文字目はパラメータの数。 --末尾には型を表す接尾子が入る場合がある。 -例1: n0sysEnt // JITコードの先頭に入れるべきコード、レジスタを初期化する s2cp_s32 g0 c0 // g0に定数0を代入 n1label L0 // ここをL0とする s2add_s32 g0 g0 c1 // g0 += 1 s4cjmp_s32 g0 c1000000000 1 L0 // 条件ジャンプ命令 if (g0 != 1000000000) goto L0 n0sysRet // JITコードから帰る場合に入れるべきコード -[Q]人間が書くわけじゃない中間言語なのに、どうしてバイナリにしなかったの?テキストなんて生成するのも解釈するのも手間じゃないか。 --[A]その意見には全面的に賛成します。まあひとまずはデバッグを楽にするためにテキストでやっています、くらいの感じで。・・・将来的にはバイナリ化するかもしれません。 ** 命令一覧 -システム命令 --n0sysEnt --n0sysRet --n0reg ---n0reg "ppif" みたいに使う。「可能であればp00,p01,i00,f00の順に実レジスタに割り当ててほしい」という意思表示。 -2項演算命令 --s2cp_s32 --s2cp_f64 --s2cnv_f64_s32 --s2cnv_s32_f64 --s2not_s32 // xor -1 で代用できるので、消すかもしれない. --s2neg_s32 // mul -1 で代用できるので、消すかもしれない. -3項演算命令 --s3or_s32 --s3xor_s32 --s3and_s32 --s3add_s32 --s3sub_s32 --s3mul_s32 --s3shl_s32 --s3shr_s32 (符号付き右シフト) --s3div_s32 --s3mod_s32 --s3add_f64 --s3sub_f64 --s3mul_f64 --s3div_f64 -条件分岐命令など --n1align --n1label --n1jmp --n1call_ext --s4cjmp_s32 (ポインタを使ったjmpは想定しない) --s4cset_s32_s32 --s4cjmp_f64 (ポインタを使ったjmpは想定しない) --s4cset_s32_f64 -ポインタ演算 --未設計 ** x86(32bit)版の仕様 -こうでなければいけないということではないが、とりあえず最初のバージョンではこうやって構成した、という例として: -変数は16バイト --型(32bit)、補助属性(32bit)、値(最大64bit) --値が64bitに収まらない場合や可変長の場合は、ポインタを入れておく。 -EAX: テンポラリデータレジスタ -EBP: ローカル変数のベース -EBX, ESI, EDI, ECX, EDX: pかiを割り当てる //-EAX: テンポラリデータレジスタ //-EDI: グローバル変数のベース //-EBP: ローカル変数のベース //-ESI: ワークエリアのベース //-EBX: p0 //-ECX: テンポラリポインタ(p1) //-EDX: テンポラリ //--p2~p31はワークエリア内にある //--i0~i31はワークエリア内にある //--f0~f5はFPUの中にあり、f6~f31はワークエリア内にある -例1は以下のような機械語になる(2017.07.28時点) 60 PUSHAD n0sysEnt BF E8 24 48 00 EDI=.... B8 00 00 00 00 EAX=0 s2cp_s32 g0 i0 89 87 08 00 00 00 [EDI+8]=EAX n1label L0 8B 87 08 00 00 00 EAX=[EDI+8] s2add_s32 g0 g0 i1 40 EAX++ 89 87 08 00 00 00 [EDI+8]=EAX 3D 00 CA 9A 3B CMP(EAX,0x3b9aca00) s4cjmp_s32 g0 i1000000000 1 L0 // EAX=[EDI+8]を自動で省略している. 0F 85 E8 FF FF FF JNE L0 61 POPAD n0sysRet C3 RET -[Q]もっと短い機械語を生成しなくていいのか? --[A]もちろんやりたいが、今は他を先に作るべきだと思っているので、今はまだそこには着手しない。 -[Q]・・・で、肝心の性能は? --[A]Core-i7で実験した範囲では、gccで-O3したのと同じくらいの性能が出る。これは使える!! ** もうちょっと細かい解説(1) -基本的には以下のルールでコンパイルされている。 s2cp_s32 a b → MOV(EAX, b); MOV(a, EAX); s2add_s32 a b c → MOV(EAX, b); ADD(EAX, c); MOV(a, EAX); // subやandなどもほぼ同様. s4cjmp_s32 a b c d → MOV(EAX, a); CMP(EAX, b); jcc(d); -見ての通りEAXしか使ってない。きわめてシンプルだ。 -ただしこれだけだと速度が出ない、なぜなら、 1: MOV(EAX, [EDI+8]); 2: INC(EAX); 3: MOV([EDI+8], EAX); 4: MOV(EAX, [EDI+8]); 5: CMP(EAX, 0x3b9aca00); 6: JNE(L0); -のときに、3行目の書き込みが終わらないと4行目の読み込みが実行できず、そこでCPUが待たされてしまうから。 -ということで、JITコンパイラは不要な4行目を削除している(ストアした直後に同じレジスタに同じアドレスでロード命令を生成しそうになったら生成を中止しているだけ)。この程度の最適化だけで、多くのケースでgcc -O3並みの速度を実現できる。 -EAXばかり使っていてもCPUはおそらくちゃんと依存関係を理解してレジスタリネーミングをして並列実行していると思われる。 ** もうちょっと細かい解説(2) -レジスタ変数: zx:f0, zy:f1, xx:f2, yy:f3, tmp:f4, tx:f4, ty:f5 1: s3mul_f64 f2 f0 f0 xx = zx * zx 2: s3mul_f64 f3 f1 f1 yy = zy * zy 3: s3add_f64 f4 f3 f2 tmp = yy + xx 4: s4cjmp_f64 f4 c4.0 5 L5 if (tmp > 4) goto L5 5: s3sub_f64 f4 f2 f3 tx = xx - yy 6: s3mul_f64 f5 f0 f1 ty = zx * zy * 2.0 7: s3add_f64 f5 f5 f5 8: s3add_f64 f0 f4 g8 zx = tx + cx 9: s3add_f64 f1 f5 g9 zy = ty + cy -このコードから以下のバイナリを生成する(でもここでは機械語部分は省略してアセンブラ表記のみで)。 01: FLD ST(0) // f0 02: FMUL ST(1) // f0 03: FSTP ST(3) // f2 04: FLD ST(1) // f1 05: FMUL ST(2) // f1 06: FSTP ST(4) // f3 07: FLD ST(3) // f3 08: FADD ST(3) // f2 09: FSTP ST(5) // f4 10: FLD ST(4) // f4 11: FCOMP [...] // 4.0 12: FNSTSW AX 13: SAHF 14: JA L5 (以下略) -ここでEssenRev2のJIT00程度でも、以下の最適化が可能だと思われる(まだやってないけどいつかやる予定)。 --06行目をFSTに変更し、07行目を省略する --09行目をFSTに変更し、10行目を省略する -このようにレジスタ内で演算するようにすると、CPUは依存関係に注意しつつ並列実行してくれるようだ。
タイムスタンプを変更しない
* Essen Rev2 JIT00 -(by [[K]], 2017.07.31) ** JIT00で提供する機能 -「即値が使えない命令」のような例外をなくす -ラベルを使えるようにする -CPUに依存するレイヤをここに集約する(JIT01以降はCPUに依存しない) -実際のCPUのレジスタ数がいくつであるかなどの制約を受けずに済むようにする ** 基本仕様 -c# : 即値 -l# : ローカル変数(#=0~) -g# : グローバル変数 -L# : 分岐先指定などに使うラベル -p# : ポインタレジスタ(p0~p63) -i# : s32以上レジスタ(i0~i63) -f# : f64以上レジスタ(f0~f63) -ローカル変数、グローバル変数には、どんな型の値でも代入できる。なぜならEssenは値が型を持つ言語だから。 -ポインタレジスタはポインタしか代入できない。 -s32以上レジスタは整数しか代入できない。 -f64以上レジスタは浮動小数点しか代入できない。 -p#, i#, f#はグローバル変数で、これをうまく使いこなせるかどうかはJIT01レイヤ次第(使えばきっと高速化には寄与する)。 -基本構文 命令 パラメータ1 パラメータ2 ... -人間にとって書きやすい構文にしようとしていない。どうせこれはJIT01から渡されてくる中間言語でしかない。 --だからコメントやマクロなどはない。 -パラメータはスペース区切り(コンマはいらない) -命令の命名規則 --最初の一文字はnかs。nはノーマル。sはスピード優先。 --二文字目はパラメータの数。 --末尾には型を表す接尾子が入る場合がある。 -例1: n0sysEnt // JITコードの先頭に入れるべきコード、レジスタを初期化する s2cp_s32 g0 c0 // g0に定数0を代入 n1label L0 // ここをL0とする s2add_s32 g0 g0 c1 // g0 += 1 s4cjmp_s32 g0 c1000000000 1 L0 // 条件ジャンプ命令 if (g0 != 1000000000) goto L0 n0sysRet // JITコードから帰る場合に入れるべきコード -[Q]人間が書くわけじゃない中間言語なのに、どうしてバイナリにしなかったの?テキストなんて生成するのも解釈するのも手間じゃないか。 --[A]その意見には全面的に賛成します。まあひとまずはデバッグを楽にするためにテキストでやっています、くらいの感じで。・・・将来的にはバイナリ化するかもしれません。 ** 命令一覧 -システム命令 --n0sysEnt --n0sysRet --n0reg ---n0reg "ppif" みたいに使う。「可能であればp00,p01,i00,f00の順に実レジスタに割り当ててほしい」という意思表示。 -2項演算命令 --s2cp_s32 --s2cp_f64 --s2cnv_f64_s32 --s2cnv_s32_f64 --s2not_s32 // xor -1 で代用できるので、消すかもしれない. --s2neg_s32 // mul -1 で代用できるので、消すかもしれない. -3項演算命令 --s3or_s32 --s3xor_s32 --s3and_s32 --s3add_s32 --s3sub_s32 --s3mul_s32 --s3shl_s32 --s3shr_s32 (符号付き右シフト) --s3div_s32 --s3mod_s32 --s3add_f64 --s3sub_f64 --s3mul_f64 --s3div_f64 -条件分岐命令など --n1align --n1label --n1jmp --n1call_ext --s4cjmp_s32 (ポインタを使ったjmpは想定しない) --s4cset_s32_s32 --s4cjmp_f64 (ポインタを使ったjmpは想定しない) --s4cset_s32_f64 -ポインタ演算 --未設計 ** x86(32bit)版の仕様 -こうでなければいけないということではないが、とりあえず最初のバージョンではこうやって構成した、という例として: -変数は16バイト --型(32bit)、補助属性(32bit)、値(最大64bit) --値が64bitに収まらない場合や可変長の場合は、ポインタを入れておく。 -EAX: テンポラリデータレジスタ -EBP: ローカル変数のベース -EBX, ESI, EDI, ECX, EDX: pかiを割り当てる //-EAX: テンポラリデータレジスタ //-EDI: グローバル変数のベース //-EBP: ローカル変数のベース //-ESI: ワークエリアのベース //-EBX: p0 //-ECX: テンポラリポインタ(p1) //-EDX: テンポラリ //--p2~p31はワークエリア内にある //--i0~i31はワークエリア内にある //--f0~f5はFPUの中にあり、f6~f31はワークエリア内にある -例1は以下のような機械語になる(2017.07.28時点) 60 PUSHAD n0sysEnt BF E8 24 48 00 EDI=.... B8 00 00 00 00 EAX=0 s2cp_s32 g0 i0 89 87 08 00 00 00 [EDI+8]=EAX n1label L0 8B 87 08 00 00 00 EAX=[EDI+8] s2add_s32 g0 g0 i1 40 EAX++ 89 87 08 00 00 00 [EDI+8]=EAX 3D 00 CA 9A 3B CMP(EAX,0x3b9aca00) s4cjmp_s32 g0 i1000000000 1 L0 // EAX=[EDI+8]を自動で省略している. 0F 85 E8 FF FF FF JNE L0 61 POPAD n0sysRet C3 RET -[Q]もっと短い機械語を生成しなくていいのか? --[A]もちろんやりたいが、今は他を先に作るべきだと思っているので、今はまだそこには着手しない。 -[Q]・・・で、肝心の性能は? --[A]Core-i7で実験した範囲では、gccで-O3したのと同じくらいの性能が出る。これは使える!! ** もうちょっと細かい解説(1) -基本的には以下のルールでコンパイルされている。 s2cp_s32 a b → MOV(EAX, b); MOV(a, EAX); s2add_s32 a b c → MOV(EAX, b); ADD(EAX, c); MOV(a, EAX); // subやandなどもほぼ同様. s4cjmp_s32 a b c d → MOV(EAX, a); CMP(EAX, b); jcc(d); -見ての通りEAXしか使ってない。きわめてシンプルだ。 -ただしこれだけだと速度が出ない、なぜなら、 1: MOV(EAX, [EDI+8]); 2: INC(EAX); 3: MOV([EDI+8], EAX); 4: MOV(EAX, [EDI+8]); 5: CMP(EAX, 0x3b9aca00); 6: JNE(L0); -のときに、3行目の書き込みが終わらないと4行目の読み込みが実行できず、そこでCPUが待たされてしまうから。 -ということで、JITコンパイラは不要な4行目を削除している(ストアした直後に同じレジスタに同じアドレスでロード命令を生成しそうになったら生成を中止しているだけ)。この程度の最適化だけで、多くのケースでgcc -O3並みの速度を実現できる。 -EAXばかり使っていてもCPUはおそらくちゃんと依存関係を理解してレジスタリネーミングをして並列実行していると思われる。 ** もうちょっと細かい解説(2) -レジスタ変数: zx:f0, zy:f1, xx:f2, yy:f3, tmp:f4, tx:f4, ty:f5 1: s3mul_f64 f2 f0 f0 xx = zx * zx 2: s3mul_f64 f3 f1 f1 yy = zy * zy 3: s3add_f64 f4 f3 f2 tmp = yy + xx 4: s4cjmp_f64 f4 c4.0 5 L5 if (tmp > 4) goto L5 5: s3sub_f64 f4 f2 f3 tx = xx - yy 6: s3mul_f64 f5 f0 f1 ty = zx * zy * 2.0 7: s3add_f64 f5 f5 f5 8: s3add_f64 f0 f4 g8 zx = tx + cx 9: s3add_f64 f1 f5 g9 zy = ty + cy -このコードから以下のバイナリを生成する(でもここでは機械語部分は省略してアセンブラ表記のみで)。 01: FLD ST(0) // f0 02: FMUL ST(1) // f0 03: FSTP ST(3) // f2 04: FLD ST(1) // f1 05: FMUL ST(2) // f1 06: FSTP ST(4) // f3 07: FLD ST(3) // f3 08: FADD ST(3) // f2 09: FSTP ST(5) // f4 10: FLD ST(4) // f4 11: FCOMP [...] // 4.0 12: FNSTSW AX 13: SAHF 14: JA L5 (以下略) -ここでEssenRev2のJIT00程度でも、以下の最適化が可能だと思われる(まだやってないけどいつかやる予定)。 --06行目をFSTに変更し、07行目を省略する --09行目をFSTに変更し、10行目を省略する -このようにレジスタ内で演算するようにすると、CPUは依存関係に注意しつつ並列実行してくれるようだ。
テキスト整形のルールを表示する