[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 的二进制文件了。大致流程如下:
在 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.h
和impala-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
网友评论