InterleavedAccessPass

这个 Pass(Interleaved Access Pass)的主要作用是在 LLVM 的中间表示(IR)层面识别“交错”内存访问模式(interleaved accesses),并把它们转成目标平台(例如 AArch64、ARM、X86 等)所能高效实现的向量化内置函数(intrinsics)。
所涉及的概念如下:

  1. 交错内存访问(Interleaved Access)
    • Interleaved Load一次宽向量 load 后,通过多次 shuffle(shufflevector)将“偶数位/奇数位”或多路子向量分离。
    • Interleaved Store:先将多条子向量通过 shuffle 重排成交错格式,再 store 回内存。
  2. 向量重排指令(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;  
}