美文网首页
Impala中 LLVM 的交叉编译、调用过程

Impala中 LLVM 的交叉编译、调用过程

作者: GOGOYAO | 来源:发表于2019-11-19 17:14 被阅读0次

    [TOC]

    本文主要介绍通过gen_ir_descriptions.py引入的 LLVM IR 函数(交叉编译)。Impala 还可以通过加载文件(hdfs 或本地文件)引入 IR 函数,本文暂不介绍。

    1. 简介

    Impala 使用的 LLVM JIT,首先通过 Clang 将源码编译成了 LLVM IR 文件,然后通过脚本将 IR 文件装成可加载的二进制文件,BE 进程在运行过程中,通过 LLVM 的加载接口,把二进制文件加载进来使用。

    2. 编译流程

    待编译的文件通过codegen/impala-ir.cpp指定

    // impala_ir.cpp
    #ifdef IR_COMPILE
    
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wheader-hygiene"
    
    #include "codegen/codegen-anyval-ir.cc"
    #include "exec/grouping-aggregator-ir.cc"
    #include "exec/hash-table-ir.cc"
    
    ......
    #pragma clang diagnostic pop
    
    // Unused function to make sure printf declaration is included in IR module. Used by
    // LlvmCodegen::CodegenDebugTrace().
    void printf_dummy_fn() {
      printf("dummy");
    }
    
    #else
    #error "This file should only be used for cross compiling to IR."
    #endif
    

    impala-ir.cpp文件主要的作用就是把需要产生 LLVM IR 的文件包含进来。
    确定了哪些文件需要产生 LLVM IR 之后,就开始生成 IR 的二进制文件了。大致流程如下:
    {c源文件 => bc 文件 => 优化后的bc 文件 => 可加载二进制 c 文件}

    在 Impala 中,有两个版本的 LLVM 代码生成:SSE和非 SSE。根据环境自动适配,二者的编译方式除了"-msse4.2"编译选项的区别,其他全部一致。后文将以 SSE 版本为例进行解说。

    2.1. cpp 文件 => bc 文件

    这个阶段生成最初始的bc文件,使用的是 CLang 的编译工具。命令可见codegen/CMakeFiles.txt

    set(IR_INPUT_FILES impala-ir.cc)
    set(IR_SSE_TMP_OUTPUT_FILE "${LLVM_IR_OUTPUT_DIRECTORY}/impala-sse-tmp.bc")
    
    ${LLVM_CLANG_EXECUTABLE} ${CLANG_IR_CXX_FLAGS} "-msse4.2" ${CLANG_INCLUDE_FLAGS} ${IR_INPUT_FILES} -o ${IR_SSE_TMP_OUTPUT_FILE}
    

    生成的结果是impala-sse-tmp.bc文件。

    2.2. bc 文件 => 优化后的 bc 文件

    使用LLVM 优化工具,对原始的 bc 文件进行优化。命令可见codegen/CMakeFiles.txt

    set(IR_SSE_TMP_OUTPUT_FILE "${LLVM_IR_OUTPUT_DIRECTORY}/impala-sse-tmp.bc")
    set(IR_SSE_OUTPUT_FILE "${LLVM_IR_OUTPUT_DIRECTORY}/impala-sse.bc")
    
    ${LLVM_OPT_EXECUTABLE} ${LLVM_OPT_IR_FLAGS} < ${IR_SSE_TMP_OUTPUT_FILE} > ${IR_SSE_OUTPUT_FILE}
    

    生成的结果就是impala-sse.bc。

    2.3. 优化后的 bc 文件 => 可加载的二进制c文件

    这一步使用的是Impala 自定义的一个脚本file2array.sh,将优化后的 bc 文件转换为可加载的二进制c 文件。命令可见codegen/CMakeFiles.txt。

    set(IR_SSE_C_FILE $ENV{IMPALA_HOME}/be/generated-sources/impala-ir/impala-sse-ir.cc)
    set(IR_SSE_TMP_C_FILE ${IR_SSE_C_FILE}.tmp)
    
    $ENV{IMPALA_HOME}/bin/file2array.sh -n -v impala_sse_llvm_ir ${IR_SSE_OUTPUT_FILE}
    COMMAND mv ${IR_SSE_TMP_C_FILE} ${IR_SSE_C_FILE}
    

    生成的结果是impala-sse-ir.cc。这个文件内部就是用一个数组存放二进制的值。

    const unsigned char impala_sse_llvm_ir[] = {
      0x42, 0x43, 0xc0, 0xde, 0x21, 0x0c, 0x00, 0x00, 0xba, 0x30, 0x02, 0x00,
      0x0b, 0x82, 0x20, 0x00, 0x02, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
      0x07, 0x81, 0x23, 0x91, 0x41, 0xc8, 0x04, 0x49, 0x06, 0x10, 0x32, 0x39,
      0x92, 0x01, 0x84, 0x0c, 0x25, 0x05, 0x08, 0x19, 0x1e, 0x04, 0x8b, 0x62,
      0x80, 0x30, 0x45, 0x02, 0x42, 0x92, 0x0b, 0x42, 0x84, 0x11, 0x32, 0x14,
    ......
    };
    

    be 进程就是通过读取impala_sse_llvm_ir数组,把 LLVM IR加载到进程中。
    file2array.sh 脚本其实就是使用xxd -i < impala-sse-ir.cc命令把bc 文件内容转成 c 语言的二进制形式。

    3. 加载和调用

    LlvmCodeGen 类通过CreateImpalaCodegen接口实例化 codegen 对象。CreateImpalaCodegen最终会调用CreateFromMemory,在CreateFromMemory中就是将上文中生成的impala_sse_llvm_ir数组通过 LLVM 接口加载进来。

    完成加载后,就可以通过GetFunction获取指定的 IR 函数了。

    4. 函数列表和描述

    所有的函数名及描述,定义在impala-ir-names.himpala-ir-functions.h,这两个文件是有对应关系的,都是通过gen_ir_descriptions.py生成。

    impala-ir-names.h定义了数组FN_MAPPINGS,存储函数名和枚举值的映射关系,如下:

    // impala-ir-names.h 片段
    static struct {
      std::string fn_name;
      IRFunction::Type fn;
    } FN_MAPPINGS[] = {
      { "process_row_batch_with_grouping", IRFunction::AGG_NODE_PROCESS_ROW_BATCH_WITH_GROUPING },
      { "process_row_batch_no_grouping", IRFunction::AGG_NODE_PROCESS_ROW_BATCH_NO_GROUPING },
      { "12HashJoinNode19process_build_batch", IRFunction::HASH_JOIN_PROCESS_BUILD_BATCH },
      { "12HashJoinNode19process_probe_batch", IRFunction::HASH_JOIN_PROCESS_PROBE_BATCH },
      ......
    };
    

    impala-ir-functions.h定义了所有函数的枚举值,如下:

    class IRFunction {
     public:
      enum Type {
        FN_START = 0,
        AGG_NODE_PROCESS_ROW_BATCH_WITH_GROUPING = 0,
        AGG_NODE_PROCESS_ROW_BATCH_NO_GROUPING = 1,
        HASH_JOIN_PROCESS_BUILD_BATCH = 2,
        HASH_JOIN_PROCESS_PROBE_BATCH = 3,
        ......
      }
    };
    

    4.1. 获取函数时使用

    通过GetFunction获取函数的时候,因为有了FN_MAPPINGS存储的映射关系,可以通过传入枚举值或者字符串符号查找函数。

    4.2. 初始化时使用

    InitializeLlvm方法中会使用FN_MAPPINGS,对加载的 llvm 函数进行校验。

      // Validate the module by verifying that functions for all IRFunction::Type
      // can be found.
      for (int i = IRFunction::FN_START; i < IRFunction::FN_END; ++i) {
        DCHECK_EQ(FN_MAPPINGS[i].fn, i);
        const string& fn_name = FN_MAPPINGS[i].fn_name;
        if (init_codegen->module_->getFunction(fn_name) == nullptr) {
          return Status(Substitute("Failed to find function $0", fn_name));
        }
      }
    

    Tips: 学习 codegen 的接口,可以多看下对应的 ut

    相关文章

      网友评论

          本文标题:Impala中 LLVM 的交叉编译、调用过程

          本文链接:https://www.haomeiwen.com/subject/cwxgictx.html