后端Pass简介——InterleavedAccessPass
InterleavedAccessPass
这个 Pass(Interleaved Access Pass)的主要作用是在 LLVM 的中间表示(IR)层面识别“交错”内存访问模式(interleaved accesses),并把它们转成目标平台(例如 AArch64、ARM、X86 等)所能高效实现的向量化内置函数(intrinsics)。
所涉及的概念如下:
- 交错内存访问(Interleaved Access)
- Interleaved Load:一次宽向量 load 后,通过多次 shuffle(shufflevector)将“偶数位/奇数位”或多路子向量分离。
- Interleaved Store:先将多条子向量通过 shuffle 重排成交错格式,再 store 回内存。
- 向量重排指令(ShuffleVector)与掩码(Mask)
- De-interleave Mask:例如 <0,2,4,6>、<1,3,5,7>,用于从宽向量中提取交错后的每一路。
- Re-interleave Mask:例如 <0,4,1,5,2,6,3,7>,用于将多路缩合回交错格式。
shufflevector(以下简称 “shuffle”)是 LLVM IR 中用于任意排列向量元素的原语。它的存在,解决了数据布局和向量化表达中的两大类需求:
数据布局转换:例如图 RGB 要对单通道操作时
向量运算的中间步骤:如水平求和(hadd)、前缀和、Butterfly 结构(FFT)等,需要在同一个向量内部不同 lane 之间交换数据,都依赖 shuffle 来表达。
举个例子,经过该 Pass 前:
; load 宽向量
%wide = load <8 x float>, <8 x float>* %ptr
; shuffle 拆成偶数位
%v0 = shufflevector <8 x float> %wide, <8 x float> undef,
<0, 2, 4, 6>
; shuffle 拆成奇数位
%v1 = shufflevector <8 x float> %wide, <8 x float> undef,
<1, 3, 5, 7>
; ……对 v0, v1 做运算……
; shuffle 把 v0/v1 再交错回一个 8 元素向量
%inter = shufflevector <4 x float> %v0, <4 x float> %v1,
<0,4,1,5,2,6,3,7>
; 存回内存
store <8 x float> %inter, <8 x float>* %ptr
走过 Pass 后:
; 直接一次调用 ld2 intrinsic,返回 {<4 x float>, <4 x float>}
%pair = call {<4 x float>, <4 x float>}
@llvm.aarch64.ld2.v4f32(<8 x float>* %ptr)
; 从 pair 中直接提取出偶数路/奇数路
%v0 = extractvalue {<4 x float>,<4 x float>} %pair, 0
%v1 = extractvalue {<4 x float>,<4 x float>} %pair, 1
; —— 对 v0, v1 做运算 ——
; 计算结束后,再用 st2 intrinsic 一次存回交错格式
call void @llvm.aarch64.st2.v4f32(<4 x float> %v0, <4 x float> %v1,
<8 x float>* %ptr)
可以看到1. - 原来至少要 6 条 IR 指令(加上 extract/insert),现在只剩下 intrinsic 调用。同时也不会生成中间结果,一次性得完成交错读写。
该 Pass 不可调参。
入口函数如下,基本就是判断指令类型然后去执行优化。
bool InterleavedAccessImpl::runOnFunction(Function &F) {
// Holds dead instructions that will be erased later.
SmallSetVector<Instruction *, 32> DeadInsts;
bool Changed = false;
for (auto &I : instructions(F)) {
if (auto *LI = dyn_cast<LoadInst>(&I))
Changed |= lowerInterleavedLoad(LI, DeadInsts);
if (auto *SI = dyn_cast<StoreInst>(&I))
Changed |= lowerInterleavedStore(SI, DeadInsts);
if (auto *II = dyn_cast<IntrinsicInst>(&I)) {
// At present, we only have intrinsics to represent (de)interleaving
// with a factor of 2. if (II->getIntrinsicID() == Intrinsic::vector_deinterleave2)
Changed |= lowerDeinterleaveIntrinsic(II, DeadInsts);
else if (II->getIntrinsicID() == Intrinsic::vector_interleave2)
Changed |= lowerInterleaveIntrinsic(II, DeadInsts);
}
}
for (auto *I : DeadInsts)
I->eraseFromParent();
return Changed;
}
评论