用于 OpenCL 本地阵列访问的 LLVM-IR GEP

Posted

技术标签:

【中文标题】用于 OpenCL 本地阵列访问的 LLVM-IR GEP【英文标题】:LLVM-IR GEP for OpenCL local array access 【发布时间】:2014-01-09 00:32:35 【问题描述】:

我通过 llvm 在我的 OpenCL 内核中创建了一个本地数组,将其称为大小为 [256 x i32] 的查找表。 后来我通过 llvm 插入代码以用值填充数组。我的问题是,当我尝试生成访问数组的代码时,我似乎无法正确隔离指向我希望的元素的指针。如果我使用名为 flattened 的晦涩局部变量,我可能会错误地索引元素:

Value *xs_ys_mul   = builder.CreateMul(shifted_x_size, y_size, "xs_ys_mul");
Value *xs_ys_z_mul = builder.CreateMul(xs_ys_mul, z, "xs_ys_z_mul");
Value *xs_y_mul    = builder.CreateMul(shifted_x_size, y, "xs_y_mul");
Value *sum_1       = builder.CreateAdd(xs_ys_z_mul, xs_y_mul, "sum_1");
Value *flattened   = builder.CreateAdd(sum_1, shifted_x, "FLATTENED");

这将是维度扁平化的本地工作组 ID。不过这无关紧要。

GEP 是这样创建的(builder 是 IRBuilder 的一个实例):

std::vector<llvm::Value *> tmp_args;
tmp_args.push_back(builder.getInt32(0));
tmp_args.push_back(flattened);
Value *table_addr = builder.CreateGEP(M.getNamedGlobal(tablename), tmp_args, tablename+"_IDX");

在这种情况下,M 是 Module 对象。 产生的table_addr是:

%i32_cllocal_TABLE_IDX = getelementptr [256 x i32] addrspace(3)* @i32_cllocal_TABLE, i32 0, i32 %FLATTENED

但是,如果我想通过使用 for 循环遍历 LLVM 中的索引来正确填充它(省略循环结构,其中“索引”是 i32 循环计数器):

std::vector<llvm::Value *> tmp_args;
tmp_args.push_back(builder.getInt32(0));
tmp_args.push_back(builder.getInt32(index));
Value *table_addr = builder.CreateGEP(M.getNamedGlobal(tablename), tmp_args, tablename+"_IDX");

在这种情况下 table_addr 的 dump() 是(当 index==0 时):

i32 addrspace(3)* getelementptr inbounds ([256 x i32] addrspace(3)* @i32_cllocal_CRC32_TABLE, i32 0, i32 0)

这意味着当我做商店时会进一步下降:

store_inst = builder.CreateStore(builder.getInt32(tablevalues[index]), table_addr);

我得到这个输出:

store volatile i32 0, i32 addrspace(3)* getelementptr inbounds ([256 x i32] addrspace(3)* @i32_cllocal_TABLE, i32 0, i32 0), align 4

这看起来不正确,但更重要的是,当“索引”> 0 时,我在断言上得到一个 SIGABRT:

Casting.h:194: typename llvm::cast_retty<To, From>::ret_type llvm::cast(const Y&) [with X = llvm::CompositeType, Y = llvm::Type*]: Assertion `isa<X>(Val) && "cast<Ty>() argument of incompatible type!"' failed.

我有点卡住了。我不明白给数组索引的显式值与在运行时计算的一些模糊值之间有什么区别。任何见解将不胜感激。

更新: 我最终做的是这个(alloc 只做一次,我只是为了视觉目的将它包含在这个代码块中,它实际上是在 for 循环之外):

std::vector<llvm::Value *> tmp_args;
tmp_args.push_back(builder.getInt32(0));
idxInst = builder.CreateAlloca(builder.getInt32Ty(), 0, "idxvalue");
//----- Inside the loop below --------------------------------------
idxStore = builder.CreateStore(builder.getInt32(index), idxInst);
indexValue = builder.CreateLoad(idxInst, "INDEX_VAL");
tmp_args.push_back(indexValue);
table_addr = builder.CreateGEP(table_ptr, tmp_args, "_IDX_PUT");
tmp_args.pop_back();
store_inst = builder.CreateStore(builder.getInt32(tableValues[index]), table_addr, "_ELEM_STORE");
store_inst->setAlignment(4);

发出此代码(对于索引 == 0 和 1):

%idxvalue = alloca i32
store i32 0, i32* %idxvalue
%INDEX_VAL = load i32* %idxvalue
%i32_cllocal_TABLE_IDX_PUT = getelementptr [256 x i32] addrspace(3)*  @i32_cllocal_TABLE, i32 0, i32 %INDEX_VAL
store volatile i32 0, i32 addrspace(3)* %i32_cllocal_TABLE_IDX_PUT, align 4
store i32 1, i32* %idxvalue
%INDEX_VAL1 = load i32* %idxvalue
%i32_cllocal_TABLE_IDX_PUT2 = getelementptr [256 x i32] addrspace(3)* @i32_cllocal_TABLE, i32 0, i32 %INDEX_VAL1
store volatile i32 1996959894, i32 addrspace(3)* %i32_cllocal_TABLE_IDX_PUT2, align 4

现在看起来是正确的,但对我来说这似乎是一种奇怪的 emi 代码方式,因为我正在存储然后立即加载,但我想这会得到优化,或者我会尝试使用那个 mem2reg。感谢@Oak 的帮助。

【问题讨论】:

【参考方案1】:

这个sn-p有问题:

std::vector<llvm::Value *> tmp_args;
tmp_args.push_back(builder.getInt32(0));
tmp_args.push_back(builder.getInt32(index));
Value *table_addr = builder.CreateGEP(M.getNamedGlobal(tablename), tmp_args, tablename+"_IDX");

IRBuilder::getInt32 创建一个 constant int。因此 GEP 将始终访问数组中的第一项,这不是您想要的。这也是 IR 显示 constant expression GEP 而不是真正的 GEP 指令的原因。您需要创建一个 Value 并将其用作第二个索引 - 就像您在第一个示例中所做的那样。

【讨论】:

我试图创建一个值并将其传递给 CreateGEP,但我在 llvm 将其识别为一个值时遇到了问题。我什至尝试获取 M.getNamedGlobal(tablename),从 ptrtoint 覆盖它,添加偏移量,然后在商店中使用它将其转换回 ptr,但没有效果。我要尝试的一件事,但似乎过分的是创建一个私有变量,将索引加载到其中,读取索引,然后使用该值索引到数组中。当我想要做的只是将值填充到本地数组中时,这似乎是一种疯狂的方式。我是新手,有什么建议吗? 那也没用。这似乎有一个基本问题,我在这里从概念上不理解某些东西。基本上,我在这里要做的就是使用编译时已知值按顺序为数组中的每个元素分配值。这样做的唯一方法是对数组进行索引,但我无法获得正确的语法。 @橡树 @redratio1 使用私有变量的想法非常好 - 您可以稍后运行 mem2reg 传递,这将优化本地,同时保持相同的语义,这就是 Clang 所做的。无论哪种情况,如果您的特定代码段不起作用,请将其编辑到您的问题中(或打开一个新问题) - 我很难想象代码。

以上是关于用于 OpenCL 本地阵列访问的 LLVM-IR GEP的主要内容,如果未能解决你的问题,请参考以下文章

在 Nvidia 下读取 OpenCL 可执行文件的共享/本地内存存储/加载库冲突硬件计数器

将 LLVM-IR 转换为类似 C 的语言

获取指向 LLVM-IR 中数组第一个元素的指针

OpenCL - 全局内存读取性能优于本地

如何在 OpenCL 中使用本地内存?

OpenCL 中的最佳本地/全局工作量