在 LLVM-C API 中确定和设置主机目标三元组和指令扩展

Posted

技术标签:

【中文标题】在 LLVM-C API 中确定和设置主机目标三元组和指令扩展【英文标题】:Determining and setting host target triple and instruction extensions in LLVM-C API 【发布时间】:2016-08-03 08:17:11 【问题描述】:

以下冗长的 C 程序生成一个简单的 LLVM 模块,其中包含一个仅调用 llvm.x86.sse41.round.ps 的函数。它发出 bitcode 文件,然后运行 ​​LLVM 生成的代码。我的问题是如何找出主机的目标三元组和指令扩展,如 SSE 或 AVX,以及如何将此信息添加到 LLVM 模块,或者如何将其告知 LLVM 执行引擎。这是我的工作:

$ cat ctest/avx-instruction-selection.c
#include <llvm-c/Core.h>
#include <llvm-c/Target.h>
#include <llvm-c/ExecutionEngine.h>
#include <llvm-c/BitWriter.h>
#include <llvm-c/Transforms/Scalar.h>

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#if 1
const int vectorSize = 4;
const char* roundName = "llvm.x86.sse41.round.ps";
#else
const int vectorSize = 8;
const char* roundName = "llvm.x86.avx.round.ps.256";
#endif

int main ()

  LLVMModuleRef module;
  LLVMExecutionEngineRef execEngine;
  LLVMTargetDataRef targetData;
  LLVMTypeRef floatType, vectorType, ptrType, voidType, funcType, roundType, int32Type;
  LLVMValueRef func, roundFunc;
  LLVMValueRef param, loaded, const1, callRound;
  LLVMBuilderRef builder;
  LLVMBasicBlockRef block;
  const int false = 0;

  LLVMInitializeX86TargetInfo();
  LLVMInitializeX86Target();
  LLVMInitializeX86TargetMC();
  module = LLVMModuleCreateWithName("_module");
  LLVMSetTarget(module, "x86_64-unknown-linux-gnu");
  floatType = LLVMFloatType();
  vectorType = LLVMVectorType(floatType, vectorSize);
  ptrType = LLVMPointerType(vectorType, 0);
  voidType = LLVMVoidType();
  LLVMTypeRef roundParams[] =  ptrType ;
  roundType = LLVMFunctionType(voidType, roundParams, 1, false);
  func = LLVMAddFunction(module, "round", roundType);
  LLVMSetLinkage(func, LLVMExternalLinkage);
  builder = LLVMCreateBuilder();
  block = LLVMAppendBasicBlock(func, "_L1");
  LLVMPositionBuilderAtEnd(builder, block);
  param = LLVMGetParam(func, 0);
  loaded = LLVMBuildLoad(builder, param, "");
  int32Type = LLVMIntType(32);
  LLVMTypeRef funcParams[] =  vectorType, int32Type  ;
  funcType = LLVMFunctionType(vectorType, funcParams, 2, false);
  roundFunc = LLVMAddFunction(module, roundName, funcType);
  LLVMSetLinkage(roundFunc, LLVMExternalLinkage);
  const1 = LLVMConstInt(int32Type, 1, false);
  LLVMValueRef callParams [] =  loaded, const1  ;
  callRound = LLVMBuildCall(builder, roundFunc, callParams, 2, "");
  LLVMSetInstructionCallConv(callRound, 0);
  LLVMAddInstrAttribute(callRound, 0, 0);
  LLVMBuildStore(builder, callRound, param);
  LLVMBuildRetVoid(builder);
  LLVMWriteBitcodeToFile(module, "round-avx.bc");
  char *errorMsg;
  LLVMCreateExecutionEngineForModule(&execEngine, module, &errorMsg);
  targetData = LLVMGetExecutionEngineTargetData(execEngine);
  size_t vectorSize0 = LLVMStoreSizeOfType(targetData, vectorType);
  size_t vectorAlign = LLVMABIAlignmentOfType(targetData, vectorType);
  float vector[vectorSize];
  printf("%lx, size %lx, align %lx\n", (size_t)vector, vectorSize0, vectorAlign);
  LLVMGenericValueRef genericVector = LLVMCreateGenericValueOfPointer(vector);
  LLVMGenericValueRef runParams[] =  genericVector  ;
  LLVMRunFunction(execEngine, func, 1, runParams);
  return 0;


$ gcc -Wall -o ctest/avx-instruction-selection ctest/avx-instruction-selection.c `/usr/lib/llvm-3.4/bin/llvm-config --cflags --ldflags` -lLLVM-3.4

$ ctest/avx-instruction-selection
7fff590431c0, size 10, align 10

$ ls round-avx.bc
round-avx.bc

$ llvm-dis -o - round-avx.bc
; ModuleID = 'round-avx.bc'
target triple = "x86_64-unknown-linux-gnu"

define void @round(<4 x float>*) 
_L1:
  %1 = load <4 x float>* %0
  %2 = call <4 x float> @llvm.x86.sse41.round.ps(<4 x float> %1, i32 1)
  store <4 x float> %2, <4 x float>* %0
  ret void


; Function Attrs: nounwind readnone
declare <4 x float> @llvm.x86.sse41.round.ps(<4 x float>, i32) #0

attributes #0 =  nounwind readnone 

$ gcc -Wall -o ctest/avx-instruction-selection ctest/avx-instruction-selection.c `/usr/lib/llvm-3.5/bin/llvm-config --cflags --ldflags` -lLLVM-3.5

$ ctest/avx-instruction-selection
7ffed6170350, size 10, align 10
LLVM ERROR: Cannot select: intrinsic %llvm.x86.sse41.round.ps

$ gcc -Wall -o ctest/avx-instruction-selection ctest/avx-instruction-selection.c `/usr/lib/llvm-3.6/bin/llvm-config --cflags --ldflags` -lLLVM-3.6

$ ctest/avx-instruction-selection
7ffeae91eb40, size 10, align 10
LLVM ERROR: Target does not support MC emission!

$ gcc -Wall -o ctest/avx-instruction-selection ctest/avx-instruction-selection.c `/usr/lib/llvm-3.7/bin/llvm-config --cflags --ldflags` -lLLVM-3.7

$ ctest/avx-instruction-selection
7fffb6464ea0, size 10, align 10
LLVM ERROR: Target does not support MC emission!

$ gcc -Wall -o ctest/avx-instruction-selection ctest/avx-instruction-selection.c `/usr/lib/llvm-3.8/bin/llvm-config --cflags --ldflags` -lLLVM-3.8

$ ctest/avx-instruction-selection
7ffd5e233000, size 10, align 10
LLVM ERROR: Target does not support MC emission!

总结:使用 LLVM-3.4 的示例有效,使用 LLVM-3.5 的内在函数 round.ps 无法找到,LLVM-3.6 和后来的一些关于 MC 排放的内容我不明白。

据我了解,LLVM-3.5 找不到 round.ps 内在函数,我猜它找不到它,因为我没有告诉它现有的 SSE 扩展。在运行llc 时,我可以添加选项-mattr=sse4.1,但如何将其告知执行引擎?

第二个问题:如何通过 LLVM-C API 了解主机的 SSE 等可用指令扩展?在 x86 上我可以调用 CPUID 指令,但是有没有一种方法可以在所有平台上统一工作并且 LLVM 可以协助检测扩展?

第三个问题:我已将目标三元组硬编码到 C 代码中。如何通过 LLVM-C API 找出主机目标三元组?

最后一个问题:这个 MC 发射错误怎么办?

【问题讨论】:

我看到llvm-config --host-target 发出了 LLVM_DEFAULT_TARGET_TRIPLE 的内容。 llvm/Config/config.h 和 llvm/Config/llvm-config.h 文件包含宏 LLVM_DEFAULT_TARGET_TRIPLE 和 LLVM_HOST_TRIPLE。它们不是严格意义上的 LLVM-C API 的一部分,而是纯 C。 如果删除 LLVMInitializeX86TargetInfo()、LLVMInitializeX86Target() 或两者,则“MC 发射”错误消失,我始终得到“无法选择:内在 %llvm.x86.sse41.round.ps”经过测试的 LLVM 版本。 在“llvm/Support/Host.h”中我找到了函数getHostCPUName。这应该回答有关主机上可用指令扩展的问题。 在“lib/ExecutionEngine/ExecutionEngineBindings.cpp”中有LLVMCreateExecutionEngineForModule的定义。我将此函数复制到我的代码中,并将.setMCPU("corei7-avx") 添加到builder。但是,运行时错误“无法选择:内在 %llvm.x86.sse41.round.ps”仍然存在。 我检查了lli 的工作原理,发现它还调用了LLVMInitializeNativeAsmPrinter();LLVMInitializeNativeAsmParser();。初始化 LLVMInitializeX86TargetInfo()LLVMInitializeX86Target() 必须保留,但三个 X86Target 初始化可以替换为单个 LLVMInitializeNativeTarget() 【参考方案1】:

尝试了很多之后,我认为答案如下:

换行

LLVMInitializeX86TargetInfo();
LLVMInitializeX86Target();
LLVMInitializeX86TargetMC();

通过

LLVMInitializeNativeTarget();
LLVMInitializeNativeAsmPrinter();
LLVMInitializeNativeAsmParser();

LLVMCreateExecutionEngineForModule 的调用替换为对自定义函数LLVMCreateExecutionEngineForModuleCPU 的调用。它是LLVMCreateExecutionEngineForModule 的原始实现加上setMCPU 的调用。

#define LLVM_VERSION (LLVM_VERSION_MAJOR * 100 + LLVM_VERSION_MINOR)

LLVMBool LLVMCreateExecutionEngineForModuleCPU
    (LLVMExecutionEngineRef *OutEE,
     LLVMModuleRef M,
     char **OutError) 
  std::string Error;
#if LLVM_VERSION < 306
  EngineBuilder builder(unwrap(M));
#else
  EngineBuilder builder(std::unique_ptr<Module>(unwrap(M)));
#endif
  builder.setEngineKind(EngineKind::Either)
         .setMCPU(sys::getHostCPUName().data())
         .setErrorStr(&Error);
  if (ExecutionEngine *EE = builder.create())
    *OutEE = wrap(EE);
    return 0;
  
  *OutError = strdup(Error.c_str());
  return 1;

我也应该补充

float vector[vectorSize] __attribute__((aligned(32)));

为了对齐 AVX 向量的数组。

根据线程crash JIT with AVX intrinsics 中的答案LLVMRunFunction 仅限于main-like 原型(显然仅在 MCJIT 中)。因此我们也应该用

替换LLVMRunFunction的东西
void (*funcPtr) (float *);
funcPtr = LLVMGetPointerToGlobal(execEngine, func);
funcPtr(vector);

【讨论】:

错误:“LLVMCreateExecutionEngineForModuleCPU”未在此范围内声明

以上是关于在 LLVM-C API 中确定和设置主机目标三元组和指令扩展的主要内容,如果未能解决你的问题,请参考以下文章

Mac OS X 和/或 iOS 中的三元光栅操作?

目标架构三元组中“pc”的含义

linux下如何设置才能ping通另一网段的主机

靶机DC-1

无法访问目标主机怎么解决

在 REACT 中进行 API 调用时动态设置目标