8

LLVM インフラストラクチャの使い方を学ぼうとしています。MinGW インストールに Windows 用の LLVM バイナリをインストールしました。

私は、いわゆる万華鏡言語に関する LLVM サイトにあるチュートリアルに従っています。このページの最後に記載されているコードとまったく同じソース ファイルがあります。

また、重要な場合は、次のフラグを使用して構築しています ( llvm-configWindows シェルには非常に快適な置換構文がないため、事前に取得します)。

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 によって生成されたコードである可能性があると私には信じさせられますが、この結論は大雑把に解釈してください。

ご覧のとおり、逆アセンブラーは、それと次の同様の行にもコメントを付けて、レジスターの不正使用であると述べています。正直に言うと、この特定の拡張レジスタ ペアがこの命令に対して違法である理由はわかりませんが、違法である場合、そもそもなぜ存在し、コンパイラに正当なコードを生成させるにはどうすればよいでしょうか?

4

1 に答える 1

5

どうやら LLVM は VEX プレフィックス付きの AVX 命令を生成していますが、プロセッサはその命令セットをサポートしていません (逆アセンブラもサポートしていません)。

JIT バイトを AVX 対応でデコードすると、次の有効なコードが得られます。

   0:   55                      push   ebp
   1:   89 e5                   mov    ebp,esp
   3:   81 e4 f8 ff ff ff       and    esp,0xfffffff8
   9:   83 ec 08                sub    esp,0x8
   c:   c5 fb 10 45 08          vmovsd xmm0,QWORD PTR [ebp+0x8]
  11:   c5 fb 58 05 60 00 d7    vaddsd xmm0,xmm0,QWORD PTR ds:0xd70060
  18:   00
  19:   c5 fb 11 04 24          vmovsd QWORD PTR [esp],xmm0
  1e:   dd 04 24                fld    QWORD PTR [esp]
  21:   89 ec                   mov    esp,ebp
  23:   5d                      pop    ebp
  24:   c3                      ret

LLVM がネイティブ アーキテクチャを誤検出している場合、または単にオーバーライドしたい場合EngineBuilderは、サンプル コードで使用されている を次のように変更できます。

TheExecutionEngine = EngineBuilder(TheModule).setErrorStr(&ErrStr).setMCPU("i386").create();

アーキテクチャを設定したり、属性を提供したりすることもできます。

于 2013-05-07T17:47:46.370 に答える