LLVM インフラストラクチャの使い方を学ぼうとしています。MinGW インストールに Windows 用の LLVM バイナリをインストールしました。
私は、いわゆる万華鏡言語に関する LLVM サイトにあるチュートリアルに従っています。このページの最後に記載されているコードとまったく同じソース ファイルがあります。
また、重要な場合は、次のフラグを使用して構築しています ( llvm-config
Windows シェルには非常に快適な置換構文がないため、事前に取得します)。
clang++ -g -O3 kaleido.cpp -o kaleido.exe -IC:/MinGW/include -DNDEBUG -D__NO_CTYPE_INLINE -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -LC:/MinGW/lib -lLLVMCore -lLLVMSupport -lpthread -lLLVMX86Disassembler -lLLVMX86AsmParser -lLLVMX86CodeGen -lLLVMSelectionDAG -lLLVMAsmPrinter -lLLVMMCParser -lLLVMX86Desc -lLLVMX86Info -lLLVMX86AsmPrinter -lLLVMX86Utils -lLLVMJIT -lLLVMRuntimeDyld -lLLVMExecutionEngine -lLLVMCodeGen -lLLVMScalarOpts -lLLVMInstCombine -lLLVMTransformUtils -lLLVMipa -lLLVMAnalysis -lLLVMTarget -lLLVMMC -lLLVMObject -lLLVMCore -lLLVMSupport -lm -limagehlp -lpsapi
リンクされたコードに実装されたその提案された言語を使用して、いくつかのトップレベルの式をテストしています。まず、リテラルを持つもの:
ready> 5 + 3;
ready> Read top-level expression:
define double @0() {
entry:
ret double 8.000000e+00
}
Evaluated to 8.000000
...期待どおりに動作します。次に、一定の結果を持つ関数定義:
ready> def f(x) 12;
ready> Read function definition:
define double @f(double %x) {
entry:
ret double 1.200000e+01
}
...再び、期待どおりに動作します。任意の入力に対してこれを呼び出すと、一定の結果が得られます。
ready> f(5);
ready> Read top-level expression:
define double @1() {
entry:
%calltmp = call double @f(double 5.000000e+00)
ret double %calltmp
}
Evaluated to 12.000000
...驚くことではありません。次に、パラメーターで何かを行う関数定義:
ready> def g(x) x + 1;
ready> Read function definition:
define double @g(double %x) {
entry:
%addtmp = fadd double 1.000000e+00, %x
ret double %addtmp
}
・・・無事、バイトコードが生成されました。今、それを呼び出します:
ready> g(5);
ready> Read top-level expression:
define double @2() {
entry:
%calltmp = call double @g(double 5.000000e+00)
ret double %calltmp
}
0x00D400A4 (0x0000000A 0x00000000 0x0028FF28 0x00D40087) <unknown module>
0x00C7A5E0 (0x01078A28 0x010CF040 0x0028FEF0 0x40280000)
0x004023F1 (0x00000001 0x01072FD0 0x01071B10 0xFFFFFFFF)
0x004010B9 (0x00000001 0x00000000 0x00000000 0x00000000)
0x00401284 (0x7EFDE000 0x0028FFD4 0x77E59F42 0x7EFDE000)
0x75693677 (0x7EFDE000 0x7B3361A2 0x00000000 0x00000000), BaseThreadInitThunk() + 0x12 bytes(s)
0x77E59F42 (0x0040126C 0x7EFDE000 0x00000000 0x00000000), RtlInitializeExceptionChain() + 0x63 bytes(s)
0x77E59F15 (0x0040126C 0x7EFDE000 0x00000000 0x78746341), RtlInitializeExceptionChain() + 0x36 bytes(s)
...クラッシュします。
いくつかの初歩的なデバッグを通じて、関連するコードの断片、つまりトップレベルの式 (g(x)
引数 5 を指定した への呼び出し) と呼び出された関数のコードは両方とも JIT であると信じるようになりました。正常にコンパイルされました。これは、クラッシュの前に関数ポインターを取得しているためだと思います(実行エンジンは、関数のコンパイルに成功した後にのみ関数ポインターを返すと想定しています)。より正確に言うと、クラッシュは関数ポインターが実行された時点で正確に発生します。これは、ソース ファイルの次の行を意味します ( 内HandleTopLevelExpression()
)。
fprintf(stderr, "Evaluated to %f\n", FP());
ほとんどの場合、行自体は無害です。これは、他の関数に対して正常に実行されるためです。犯人は、上記の例の最後の例で指摘された関数内のどこかにある可能性がFP
ありますが、そのコードは実行時に生成されるため、私のcpp
ファイルにはありません。
その特定のシナリオでクラッシュする理由についてのアイデアはありますか?
更新 #1: gdb を介してプロセスを実行すると、クラッシュ ポイントでこれが示されます。
プログラムがシグナル SIGILL を受信しました。不正な命令です。
そして、何も教えてくれないトレース:
0x00ee0044 in ?? ()
更新#2:これをさらに明らかにするために、クラッシュ周辺のアセンブリを次に示します。
00D70068 55 PUSH EBP
00D70069 89E5 MOV EBP,ESP
00D7006B 81E4 F8FFFFFF AND ESP,FFFFFFF8
00D70071 83EC 08 SUB ESP,8
00D70074 C5FB LDS EDI,EBX ; Here! ; Illegal use of register
00D70076 1045 08 ADC BYTE PTR SS:[EBP+8],AL
00D70079 C5FB LDS EDI,EBX ; Illegal use of register
00D7007B 58 POP EAX
00D7007C 05 6000D700 ADD EAX,0D70060
00D70081 C5FB LDS EDI,EBX ; Illegal use of register
00D70083 110424 ADC DWORD PTR SS:[ESP],EAX
00D70086 DD0424 FLD QWORD PTR SS:[ESP]
00D70089 89EC MOV ESP,EBP
00D7008B 5D POP EBP
00D7008C C3 RETN
でクラッシュが発生しています。00D70074
命令はLDS EDI,EBX
です。これは、 が指すアドレスよりもいくつかアドレスが高くなりますFP
(これはすべて JIT によって生成されたコードである可能性があると私には信じさせられますが、この結論は大雑把に解釈してください。
ご覧のとおり、逆アセンブラーは、それと次の同様の行にもコメントを付けて、レジスターの不正使用であると述べています。正直に言うと、この特定の拡張レジスタ ペアがこの命令に対して違法である理由はわかりませんが、違法である場合、そもそもなぜ存在し、コンパイラに正当なコードを生成させるにはどうすればよいでしょうか?