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.
Nó phải là% xmm0. –
@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
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. –