【问题标题】:LLVM 8 and later ORC JIT problems with math libraryLLVM 8 和更高版本的 ORC JIT 问题与数学库
【发布时间】:2019-09-20 04:45:15
【问题描述】:

从 LLVM 8(包括当前的 LLVM 主干,又名 LLVM 9)开始,我在将 ORC JIT ExecutionEngine 与包含对标准数学库的调用的函数一起使用时遇到了问题。

JIT 编译器能够找到函数的符号,但在函数调用数学库的情况下无法获取它的地址。

我附上了一个简单的漏洞利用来说明问题。程序 test.cc 读入一个 IR 文件,该文件包含 LLVM 的中间表示中的一个函数: 该函数接受一个参数,一个浮点数,并在

的情况下返回
  • "func_works.ll" 参数本身,如果是
  • “func_cos_fails.ll”参数的余弦值。

我没有在运行时实现两个文件之间的选择,所以切换到另一种情况时需要重新构建程序。

该程序使用 LLVM 附带的标准 KaleidoscopeJIT.h(除了我必须公开 Datalayout)。

如果您使用“func_works.ll”构建程序并运行它,程序会成功:

symbol found!
address found!

如果您使用“func_cos_fails.ll”构建程序并运行它,程序将失败并显示:

symbol found!
Failure value returned from cantFail wrapped call
UNREACHABLE executed at install/llvm-8.0-x86-debug/include/llvm/Support/Error.h:732!

LLVM 8 版本和当前的 LLVM 主干会发生这种情况。

有人知道发生了什么吗?

此测试在配置了 LLVM 的 x86 Linux Ubuntu 系统上运行

cmake -G "Unix Makefiles" \
      -DBUILD_SHARED_LIBS="ON" \
      -DLLVM_ENABLE_RTTI="ON" \
      -DLLVM_ENABLE_ZLIB="OFF" \
      -DLLVM_ENABLE_TERMINFO="OFF" \
      -DCMAKE_BUILD_TYPE="Debug" \
      -DCMAKE_INSTALL_PREFIX=$CMAKE_INSTALL_PREFIX \
      -DLLVM_TARGETS_TO_BUILD="X86" \
      $SRC

test.cc:

#include "KaleidoscopeJIT.h"

#include "llvm/Analysis/Passes.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/ExecutionEngine/ObjectCache.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/Verifier.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/Scalar.h"
#include "llvm/Support/InitLLVM.h"

#include <iostream>

using namespace llvm;
using namespace llvm::orc;




int main(int argc, char **argv) {
  InitLLVM X(argc, argv);
  EnableDebugBuffering = true;
  LLVMContext Context;

  InitializeNativeTarget();
  InitializeNativeTargetAsmPrinter();
  InitializeNativeTargetAsmParser();

  cl::ParseCommandLineOptions(argc, argv, "Kaleidoscope example program\n");
  SMDiagnostic Err;

  std::unique_ptr<llvm::Module> M = parseIRFile( "func_cos_fails.ll" , Err, Context, false);
  //std::unique_ptr<llvm::Module> M = parseIRFile( "func_works.ll" , Err, Context, false);
  if (!M) {
    Err.print("IR parsing failed: ", errs());
    return 0;
  }

  std::unique_ptr<KaleidoscopeJIT> TheJIT;

  TheJIT = llvm::make_unique<KaleidoscopeJIT>();

  auto H = TheJIT->addModule(std::move(M));

  std::string MangledName;
  llvm::raw_string_ostream MangledNameStream(MangledName);
  llvm::Mangler::getNameWithPrefix(MangledNameStream, "func_ir" , TheJIT->getDL() );

  if (auto Sym = TheJIT->findSymbol(MangledNameStream.str()))
    {
      std::cout << "symbol found!\n";
      void* fptr = (void *)cantFail(Sym.getAddress());

      std::cout << "address found!\n";
    }
  else
    {
      std::cout << "symbol not found!\n";
    }

  return 0;
}

func_cos_fails.ll:

source_filename = "module"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

declare float @cosf(float)

define float @func_ir(float %arg0) {
entrypoint:
  %0 = call float @cosf(float %arg0)
  ret float %0
}

func_works.ll:

source_filename = "module"
target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"


define float @func_ir(float %arg0) {
entrypoint:
  ret float %arg0
}

万花筒JIT.h:

#ifndef LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H
#define LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H

#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/iterator_range.h"
#include "llvm/ExecutionEngine/ExecutionEngine.h"
#include "llvm/ExecutionEngine/JITSymbol.h"
#include "llvm/ExecutionEngine/Orc/CompileUtils.h"
#include "llvm/ExecutionEngine/Orc/IRCompileLayer.h"
#include "llvm/ExecutionEngine/Orc/LambdaResolver.h"
#include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/RTDyldMemoryManager.h"
#include "llvm/ExecutionEngine/SectionMemoryManager.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Mangler.h"
#include "llvm/Support/DynamicLibrary.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetMachine.h"
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <vector>

namespace llvm {
namespace orc {

class KaleidoscopeJIT {
public:
  using ObjLayerT = LegacyRTDyldObjectLinkingLayer;
  using CompileLayerT = LegacyIRCompileLayer<ObjLayerT, SimpleCompiler>;

  KaleidoscopeJIT()
      : Resolver(createLegacyLookupResolver(
            ES,
            [this](const std::string &Name) {
              return ObjectLayer.findSymbol(Name, true);
            },
            [](Error Err) { cantFail(std::move(Err), "lookupFlags failed"); })),
        TM(EngineBuilder().selectTarget()), DL(TM->createDataLayout()),
        ObjectLayer(ES,
                    [this](VModuleKey) {
                      return ObjLayerT::Resources{
                          std::make_shared<SectionMemoryManager>(), Resolver};
                    }),
        CompileLayer(ObjectLayer, SimpleCompiler(*TM)) {
    llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr);
  }

  TargetMachine &getTargetMachine() { return *TM; }

  VModuleKey addModule(std::unique_ptr<Module> M) {
    auto K = ES.allocateVModule();
    cantFail(CompileLayer.addModule(K, std::move(M)));
    ModuleKeys.push_back(K);
    return K;
  }

  void removeModule(VModuleKey K) {
    ModuleKeys.erase(find(ModuleKeys, K));
    cantFail(CompileLayer.removeModule(K));
  }

  JITSymbol findSymbol(const std::string Name) {
    return findMangledSymbol(mangle(Name));
  }

  const DataLayout& getDL() const {
    return DL;
  }

private:
  std::string mangle(const std::string &Name) {
    std::string MangledName;
    {
      raw_string_ostream MangledNameStream(MangledName);
      Mangler::getNameWithPrefix(MangledNameStream, Name, DL);
    }
    return MangledName;
  }

  JITSymbol findMangledSymbol(const std::string &Name) {
#ifdef _WIN32
    // The symbol lookup of ObjectLinkingLayer uses the SymbolRef::SF_Exported
    // flag to decide whether a symbol will be visible or not, when we call
    // IRCompileLayer::findSymbolIn with ExportedSymbolsOnly set to true.
    //
    // But for Windows COFF objects, this flag is currently never set.
    // For a potential solution see: https://reviews.llvm.org/rL258665
    // For now, we allow non-exported symbols on Windows as a workaround.
    const bool ExportedSymbolsOnly = false;
#else
    const bool ExportedSymbolsOnly = true;
#endif

    // Search modules in reverse order: from last added to first added.
    // This is the opposite of the usual search order for dlsym, but makes more
    // sense in a REPL where we want to bind to the newest available definition.
    for (auto H : make_range(ModuleKeys.rbegin(), ModuleKeys.rend()))
      if (auto Sym = CompileLayer.findSymbolIn(H, Name, ExportedSymbolsOnly))
        return Sym;

    // If we can't find the symbol in the JIT, try looking in the host process.
    if (auto SymAddr = RTDyldMemoryManager::getSymbolAddressInProcess(Name))
      return JITSymbol(SymAddr, JITSymbolFlags::Exported);

#ifdef _WIN32
    // For Windows retry without "_" at beginning, as RTDyldMemoryManager uses
    // GetProcAddress and standard libraries like msvcrt.dll use names
    // with and without "_" (for example "_itoa" but "sin").
    if (Name.length() > 2 && Name[0] == '_')
      if (auto SymAddr =
              RTDyldMemoryManager::getSymbolAddressInProcess(Name.substr(1)))
        return JITSymbol(SymAddr, JITSymbolFlags::Exported);
#endif

    return nullptr;
  }

  ExecutionSession ES;
  std::shared_ptr<SymbolResolver> Resolver;
  std::unique_ptr<TargetMachine> TM;
  const DataLayout DL;
  ObjLayerT ObjectLayer;
  CompileLayerT CompileLayer;
  std::vector<VModuleKey> ModuleKeys;
};

} // end namespace orc
} // end namespace llvm

#endif // LLVM_EXECUTIONENGINE_ORC_KALEIDOSCOPEJIT_H

为方便起见,我提供了一个 Makefile:

LLVM_CONFIG = ${LLVM_INSTALL_PATH}

LLVM_CXXFLAGS = $(shell $(LLVM_CONFIG) --cxxflags)
LLVM_LDFLAGS  = $(shell $(LLVM_CONFIG) --ldflags)
LLVM_LIBS     = $(shell $(LLVM_CONFIG) --libs)

all: test

test.o: test.cc KaleidoscopeJIT.h
    g++ -c -o $@ $< $(LLVM_CXXFLAGS)

test: test.o
    g++ -o $@ $< $(LLVM_LDFLAGS) $(LLVM_LIBS)

clean:
    rm -f *.o
    rm -f test

【问题讨论】:

  • 我没有看到你在加载主机进程的符号,你可以在创建 JIT 之后尝试调用llvm::sys::DynamicLibrary::LoadLibraryPermanently(nullptr) 吗?
  • @Banex 刚刚尝试过,但没有帮助。该程序生成相同的输出。
  • 我明白了,您可以尝试在数学库中以及在您的 Makefile 中进行链接吗?将-lm 添加到链接器标志。
  • @Banex 刚试过。同样的问题。

标签: llvm jit llvm-ir


【解决方案1】:

我相信可以在这里找到解决方案(对于 llvm 7 和 8): https://stackoverflow.com/a/56862433/2310373

即替换:

[this](const std::string &Name) {
    return ObjectLayer.findSymbol(Name, true);
},

类似的东西

[this](const std::string &Name) {
    auto FoundSymbol = ObjectLayer.findSymbol(Name, true);
    if (!FoundSymbol) {
        if (auto SymAddr = RTDyldMemoryManager::getSymbolAddressInProcess(Name))
            return JITSymbol(SymAddr, JITSymbolFlags::Exported);
    }
    return FoundSymbol;
},

【讨论】:

    猜你喜欢
    • 2011-03-31
    • 2022-01-04
    • 1970-01-01
    • 2018-01-02
    • 1970-01-01
    • 1970-01-01
    • 2019-06-21
    • 1970-01-01
    • 2011-02-17
    相关资源
    最近更新 更多