2016-08-25 15 views
9

Tôi đang cố gọi một phương thức từ LLVM IR trở lại mã C++. Tôi đang làm việc trong Visual C++ 64-bit, hoặc như LLVM mô tả nó:Cách thích hợp để gọi một hàm Win32/64 từ LLVM là gì?

Machine CPU:  skylake 
Machine info:  x86_64-pc-windows-msvc 

Đối với kiểu số nguyên và kiểu con trỏ, mã của tôi hoạt động tốt. Tuy nhiên, số điểm nổi dường như được xử lý một chút lạ.

Về cơ bản các cuộc gọi như sau:

struct SomeStruct 
{ 
    static void Breakpoint(return; } // used to set a breakpoint 
    static void Set(uint8_t* ptr, double foo) { return foo * 2; } 
}; 

và LLVM IR trông như thế này:

define i32 @main(i32, i8**) { 
varinit: 
    // omitted here: initialize %ptr from i8**. 
    %5 = load i8*, i8** %instance0 

    // call to some method. This works - I use it to set a breakpoint 
    call void @"Helper::Breakpoint"(i8* %5) 

    // this call fails: 
    call void @"Helper::Set"(i8* %5, double 0xC19EC46965A6494D) 
    ret i32 0 
} 

declare double @"SomeStruct::Callback"(i8*, double) 

I figured rằng vấn đề có lẽ là trong cách các công ước gọi làm việc. Vì vậy, tôi đã cố gắng thực hiện một số điều chỉnh để sửa cho rằng:

// during initialization of the function 
auto function = llvm::Function::Create(functionType, llvm::Function::ExternalLinkage, name, module); 
function->setCallingConv(llvm::CallingConv::X86_64_Win64); 
... 

// during calling of the function 
call->setCallingConv(llvm::CallingConv::X86_64_Win64); 

Đáng tiếc là không có vấn đề gì tôi cố gắng, tôi kết thúc với lỗi 'không hợp lệ hướng dẫn', mà điều này báo cáo sử dụng là một vấn đề với các công ước gọi: Clang producing executable with illegal instruction. Tôi đã thử điều này với X86-64_Win64, Stdcall, Fastcall và không có thông số quy ước gọi điện thoại - tất cả đều có cùng kết quả.

Tôi đã đọc trên https://msdn.microsoft.com/en-us/library/ms235286.aspx để tìm hiểu xem điều gì đang xảy ra. Sau đó, tôi nhìn vào sản lượng lắp ráp mà là nghĩa vụ phải được tạo ra bởi LLVM (sử dụng cuộc gọi targetMachine-> addPassesToEmitFile API) và nhận thấy:

movq (%rdx), %rsi 
    movq %rsi, %rcx 
    callq "Helper2<double>::Breakpoint" 
    vmovsd [email protected](%rip), %xmm1 
    movq %rsi, %rcx 
    callq "Helper2<double>::Set" 
    xorl %eax, %eax 
    addq $32, %rsp 
    popq %rsi 

Theo MSDN, lập luận 2 phải ở trong% xmm1 để cũng có vẻ đúng . Tuy nhiên, khi kiểm tra xem mọi thứ có hoạt động trong trình gỡ lỗi hay không, Visual Studio báo cáo rất nhiều dấu hỏi (ví dụ: 'hướng dẫn bất hợp pháp').

Mọi phản hồi đều được đánh giá cao.


Mã tháo:

00000144F2480007 48 B8 B6 48 B8 C8 FA 7F 00 00 mov   rax,7FFAC8B848B6h 
00000144F2480011 48 89 D1    mov   rcx,rdx 
00000144F2480014 48 89 54 24 20  mov   qword ptr [rsp+20h],rdx 
00000144F2480019 FF D0    call  rax 
00000144F248001B 48 B8 C0 48 B8 C8 FA 7F 00 00 mov   rax,7FFAC8B848C0h 
00000144F2480025 48 B9 00 00 47 F2 44 01 00 00 mov   rcx,144F2470000h 
00000144F248002F ??     ?? ?? 
00000144F2480030 ??     ?? ?? 
00000144F2480031 FF 08    dec   dword ptr [rax] 
00000144F2480033 10 09    adc   byte ptr [rcx],cl 
00000144F2480035 48 8B 4C 24 20  mov   rcx,qword ptr [rsp+20h] 
00000144F248003A FF D0    call  rax 
00000144F248003C 31 C0    xor   eax,eax 
00000144F248003E 48 83 C4 28   add   rsp,28h 
00000144F2480042 C3     ret 

Một số thông tin về bộ nhớ là mất tích. Giao diện bộ nhớ:

0x00000144F248001B 48 b8 c0 48 b8 c8 fa 7f 00 00 48 b9 00 00 47 f2 44 01 00 00 62 f1 ff 08 10 09 48 8b 4c 24 20 ff d0 31 c0 48 83 c4 28 c3 00 00 00 00 00 ...

Dấu hỏi bị thiếu ở đây là: '62 f1 '.


Một số mã là hữu ích để xem làm thế nào tôi có được JIT biên dịch vv Tôi sợ đó là một chút dài, nhưng giúp để có được những ý tưởng ... và tôi không có đầu mối làm thế nào để tạo ra một nhỏ đoạn mã.

// Note: FunctionBinderBase basically holds an llvm::Function* object 
    // which is bound using the above code and a name. 
    llvm::ExecutionEngine* Module::Compile(std::unordered_map<std::string, FunctionBinderBase*>& externalFunctions) 
    { 
     //   DebugFlag = true; 

#if (LLVMDEBUG >= 1) 
     this->module->dump(); 
#endif 

     // -- Initialize LLVM compiler: -- 
     std::string error; 

     // Helper function, gets the current machine triplet. 
     llvm::Triple triple(MachineContextInfo::Triplet()); 
     const llvm::Target *target = llvm::TargetRegistry::lookupTarget("x86-64", triple, error); 
     if (!target) 
     { 
      throw error.c_str(); 
     } 

     llvm::TargetOptions Options; 
     // Options.PrintMachineCode = true; 
     // Options.EnableFastISel = true; 

     std::unique_ptr<llvm::TargetMachine> targetMachine(
      target->createTargetMachine(MachineContextInfo::Triplet(), MachineContextInfo::CPU(), "", Options, llvm::Reloc::Default, llvm::CodeModel::Default, llvm::CodeGenOpt::Aggressive)); 

     if (!targetMachine.get()) 
     { 
      throw "Could not allocate target machine!"; 
     } 

     // Create the target machine; set the module data layout to the correct values. 
     auto DL = targetMachine->createDataLayout(); 
     module->setDataLayout(DL); 
     module->setTargetTriple(MachineContextInfo::Triplet()); 

     // Pass manager builder: 
     llvm::PassManagerBuilder pmbuilder; 
     pmbuilder.OptLevel = 3; 
     pmbuilder.BBVectorize = false; 
     pmbuilder.SLPVectorize = true; 
     pmbuilder.LoopVectorize = true; 
     pmbuilder.Inliner = llvm::createFunctionInliningPass(3, 2); 
     llvm::TargetLibraryInfoImpl *TLI = new llvm::TargetLibraryInfoImpl(triple); 
     pmbuilder.LibraryInfo = TLI; 

     // Generate pass managers: 

     // 1. Function pass manager: 
     llvm::legacy::FunctionPassManager FPM(module.get()); 
     pmbuilder.populateFunctionPassManager(FPM); 

     // 2. Module pass manager: 
     llvm::legacy::PassManager PM; 
     PM.add(llvm::createTargetTransformInfoWrapperPass(targetMachine->getTargetIRAnalysis())); 
     pmbuilder.populateModulePassManager(PM); 

     // 3. Execute passes: 
     // - Per-function passes: 
     FPM.doInitialization(); 
     for (llvm::Module::iterator I = module->begin(), E = module->end(); I != E; ++I) 
     { 
      if (!I->isDeclaration()) 
      { 
       FPM.run(*I); 
      } 
     } 
     FPM.doFinalization(); 

     // - Per-module passes: 
     PM.run(*module); 

     // Fix function pointers; the PM.run will ruin them, this fixes that. 
     for (auto it : externalFunctions) 
     { 
      auto name = it.first; 
      auto fcn = module->getFunction(name); 
      it.second->function = fcn; 
     } 

#if (LLVMDEBUG >= 2) 
     // -- ASSEMBLER dump code 
     // 3. Code generation pass manager: 

     llvm::legacy::PassManager CGP; 
     CGP.add(llvm::createTargetTransformInfoWrapperPass(targetMachine->getTargetIRAnalysis())); 
     pmbuilder.populateModulePassManager(CGP); 

     std::string result; 
     llvm::raw_string_ostream str(result); 
     llvm::buffer_ostream os(str); 

     targetMachine->addPassesToEmitFile(CGP, os, llvm::TargetMachine::CodeGenFileType::CGFT_AssemblyFile); 

     CGP.run(*module); 

     str.flush(); 

     auto stringref = os.str(); 
     std::string assembly(stringref.begin(), stringref.end()); 

     std::cout << "ASM code: " << std::endl << "---------------------" << std::endl << assembly << std::endl << "---------------------" << std::endl; 
     // -- end of ASSEMBLER dump code. 

     for (auto it : externalFunctions) 
     { 
      auto name = it.first; 
      auto fcn = module->getFunction(name); 
      it.second->function = fcn; 
     } 

#endif 

#if (LLVMDEBUG >= 2) 
     module->dump(); 
#endif 

     // All done, *RUN*. 

     llvm::EngineBuilder engineBuilder(std::move(module)); 
     engineBuilder.setEngineKind(llvm::EngineKind::JIT); 
     engineBuilder.setMCPU(MachineContextInfo::CPU()); 
     engineBuilder.setMArch("x86-64"); 
     engineBuilder.setUseOrcMCJITReplacement(false); 
     engineBuilder.setOptLevel(llvm::CodeGenOpt::None); 

     llvm::ExecutionEngine* engine = engineBuilder.create(); 

     // Define external functions 
     for (auto it : externalFunctions) 
     { 
      auto fcn = it.second; 
      if (fcn->function) 
      { 
       engine->addGlobalMapping(fcn->function, const_cast<void*>(fcn->FunctionPointer())); // Yuck... LLVM only takes non-const pointers 
      } 
     } 

     // Finalize 
     engine->finalizeObject(); 

     return engine; 
    } 

Cập nhật (tiến bộ)

Rõ ràng Skylake của tôi có vấn đề với các hướng dẫn vmovsd. Khi chạy cùng một mã trên Haswell (máy chủ), thử nghiệm thành công. Tôi đã kiểm tra đầu ra lắp ráp trên cả hai - họ là chính xác như nhau.

Chỉ cần chắc chắn: XSAVE/XRESTORE không phải là vấn đề trên Win10-x64, nhưng hãy tìm hiểu mọi cách. Tôi đã kiểm tra các tính năng có mã từ https://msdn.microsoft.com/en-us/library/hskdteyh.aspx và XSAVE/XRESTORE từ https://insufficientlycomplicated.wordpress.com/2011/11/07/detecting-intel-advanced-vector-extensions-avx-in-visual-studio/. Cái sau chỉ chạy tốt. Đối với trước đây, đây là kết quả:

GenuineIntel 
Intel(R) Core(TM) i7-6700HQ CPU @ 2.60GHz 
3DNOW not supported 
3DNOWEXT not supported 
ABM not supported 
ADX supported 
AES supported 
AVX supported 
AVX2 supported 
AVX512CD not supported 
AVX512ER not supported 
AVX512F not supported 
AVX512PF not supported 
BMI1 supported 
BMI2 supported 
CLFSH supported 
CMPXCHG16B supported 
CX8 supported 
ERMS supported 
F16C supported 
FMA supported 
FSGSBASE supported 
FXSR supported 
HLE supported 
INVPCID supported 
LAHF supported 
LZCNT supported 
MMX supported 
MMXEXT not supported 
MONITOR supported 
MOVBE supported 
MSR supported 
OSXSAVE supported 
PCLMULQDQ supported 
POPCNT supported 
PREFETCHWT1 not supported 
RDRAND supported 
RDSEED supported 
RDTSCP supported 
RTM supported 
SEP supported 
SHA not supported 
SSE supported 
SSE2 supported 
SSE3 supported 
SSE4.1 supported 
SSE4.2 supported 
SSE4a not supported 
SSSE3 supported 
SYSCALL supported 
TBM not supported 
XOP not supported 
XSAVE supported 

Thật kỳ lạ, vì vậy tôi đã tìm ra: tại sao không đơn giản phát ra lệnh trực tiếp.

int main() 
{ 
    const double value = 1.2; 
    const double value2 = 1.3; 

    auto x1 = _mm_load_sd(&value); 
    auto x2 = _mm_load_sd(&value2); 

    std::string s; 
    std::getline(std::cin, s); 
} 

Mã này chạy tốt. Việc tháo gỡ:

auto x1 = _mm_load_sd(&value); 
00007FF7C4833724 C5 FB 10 45 08  vmovsd  xmm0,qword ptr [value] 

    auto x1 = _mm_load_sd(&value); 
00007FF7C4833729 C5 F1 57 C9   vxorpd  xmm1,xmm1,xmm1 
00007FF7C483372D C5 F3 10 C0   vmovsd  xmm0,xmm1,xmm0 

Dường như nó sẽ không sử dụng thanh ghi xmm1, nhưng vẫn chứng minh rằng chính lệnh đó thực hiện thủ thuật.

+0

Nó phải là% xmm0. –

+0

@Ha. Yea, tôi đồng ý, nhưng ngay cả khi đó là trường hợp tôi sẽ không mong đợi nó sụp đổ. – atlaste

+0

Có thể sự cố của bạn không liên quan gì đến quy ước gọi điện. Hướng dẫn bất hợp pháp do clang phát ra được in dưới dạng ud2, không phải là dấu hỏi. Bạn có thể chuyển sang chế độ xem tháo gỡ trong Visual Studio và hướng dẫn từng bước để xem những gì thực sự đang diễn ra trong suốt cuộc gọi. –

Trả lời

4

tôi chỉ kiểm tra trên một Haswell của Intel gì đang xảy ra ở đây, và thấy điều này:

0000015077F20110 C5 FB 10 08   vmovsd  xmm1,qword ptr [rax] 

Rõ ràng trên Intel Haswell nó phát ra một hướng dẫn mã byte hơn trên Skylake của tôi.

@Ha. thực sự là đủ loại để chỉ cho tôi đi đúng hướng ở đây. Có, các byte ẩn thực sự chỉ ra VMOVSD, nhưng dường như nó được mã hóa là EVEX. Đó là tất cả tốt đẹp và tốt, nhưng tiền tố/mã hóa EVEX sẽ được giới thiệu trong kiến ​​trúc Skylake mới nhất như một phần của AVX512, sẽ không được hỗ trợ cho đến Skylake Purley vào năm 2017. Nói cách khác, hướng dẫn không hợp lệ.

Để kiểm tra, tôi đã đặt điểm ngắt tại X86MCCodeEmitter::EmitMemModRMByte. Tại một số thời điểm, tôi thấy một đánh giá là bool HasEVEX = [...]. Điều này xác nhận rằng codegen/emitter đang tạo ra kết quả sai.

Kết luận của tôi là do đó đây phải là một lỗi trong thông tin đích của LLVM cho CPU của Skylake. Điều đó có nghĩa là chỉ có hai việc còn lại để làm: tìm ra nơi lỗi này chính xác trong LLVM để chúng ta có thể giải quyết vấn đề này và báo cáo lỗi cho nhóm LLVM ...

Vậy nó ở đâu trong LLVM? Đó là khó khăn để nói ... x86.td.def định nghĩa các tính năng skylake như 'FeatureAVX512' mà có lẽ sẽ kích hoạt X86SSELevel để AVX512F. Điều đó lần lượt sẽ đưa ra những chỉ dẫn sai. Để khắc phục, tốt nhất bạn nên nói với LLVM rằng chúng tôi có một Intel Haswell thay thế và tất cả sẽ tốt:

// MCPU is used to call createTargetMachine 
llvm::StringRef MCPU = llvm::sys::getHostCPUName(); 
if (MCPU.str() == "skylake") 
{ 
    MCPU = llvm::StringRef("haswell"); 
} 

Kiểm tra, hoạt động.

+0

Điều này giải thích tại sao tôi không thể tìm thấy mã hóa lệnh trong tài liệu - nó quá mới. Tôi chưa thấy mã hóa EVEX, vì vậy tôi không nhận ra nó như vậy. Vâng, ít nhất tôi đã học được một cái gì đó mới ngày hôm nay. –

+0

@Ha. Tương tự ở đây. Tôi đọc các thông số kỹ thuật của Intel một năm trước, và nó đã không mô tả EVEX trở lại sau đó. Điều đó thực sự có ý nghĩa, bởi vì CPU không có thanh ghi zmm. Ngoài ra, Intel nói rằng Skylake sẽ hỗ trợ AVX512, và giải thích sau đó rằng nó chỉ trễ Skylake Purley (Xeon)/Kings Landing sẽ hỗ trợ AVX512 và Skylake AVX512F. Vì vậy, thực tế là nó sẽ không hỗ trợ bất kỳ AVX512 như ngày hôm nay. Nó khá là khó hiểu. CPUID phản ánh điều này, nhưng dường như SSELevel không hoạt động như thế (đó chỉ là mã hóa). Bối rối, khó hiểu ... Dù sao, cảm ơn tất cả sự giúp đỡ! – atlaste

Các vấn đề liên quan