LLVM IR用<N x T>原生向量类型和向量重载指令(如fadd <4 x float>)直接表示SIMD运算,所有操作在SSA下完成;向量类型为一等类型,与数组/结构体无关,需显式指定宽度,且不自动推导;核心算术指令天然支持向量操作,但掩码语义需通过Intrinsic(如@llvm.x86.avx512.add.ps.512)实现,IR层无原生条件执行向量指令。

LLVM IR 用原生向量类型 + 向量指令直接表示 SIMD 运算,不依赖函数调用或特殊语法,所有操作都在 SSA 框架下完成。
向量类型在 IR 中的声明方式
LLVM IR 把 <N x T> 视为一级数据类型(不是聚合类型),其中 N 是元素数量,T 是基础类型(如 float、i32)。这和 C/C++ 的数组或结构体完全不同:
-
<4 x float>表示 128 位宽的向量(对应 SSE 的__m128) -
<8 x float>对应 AVX 的__m256,<16 x float>对应 AVX-512 的__m512 -
<16 x i32>或<4 x double>等写法也合法,只要目标后端支持对应宽度和类型组合 - 类型必须显式指定宽度——IR 不会自动“推导”向量化,
float和<4 x float>是完全不同的类型,不能混用
核心向量指令:add/sub/mul/fadd/fmul 等都有向量重载
LLVM IR 的算术指令天然支持向量操作,只要操作数是同构向量类型,就生成向量指令:
-
%a_vec = load <4 x float>, ptr %ptr_a—— 一次性加载 4 个 float -
%sum = fadd <4 x float> %a_vec, %b_vec—— 对应_mm_add_ps -
%prod = fmul <4 x float> %a_vec, %c_vec—— 对应_mm_mul_ps -
%mask = fcmp ogt <4 x float> %a_vec, %threshold—— 产生<4 x i1>掩码
注意:fadd 和 add 指令本身不区分标量/向量,区别只在操作数类型;IR 层不关心底层是用 SSE 还是 AVX 执行,那是后端指令选择阶段的事。
向量控制流与掩码操作的关键限制
LLVM IR 本身没有“条件执行向量指令”的原生语义,所有分支逻辑仍靠 phi 和基本块跳转实现。真正支持掩码的是后端扩展(如 AVX-512):
- IR 层无法直接写出
_mm512_mask_add_ps这类带掩码参数的指令——它属于 Intrinsic,不是 IR 指令 - 要启用掩码语义,必须通过
@llvm.x86.avx512.add.ps.512这类 Intrinsic 调用,且需配合llvm.masked.load/llvm.masked.store等内存操作 - 循环向量化器(LoopVectorizePass)在生成 IR 时,会根据是否启用
-mavx512f等 flag,决定是否插入select+phi组合来模拟掩码行为,但这不是硬件级掩码,性能开销更大
调试时怎么看 IR 是否真用了向量
别只扫一眼有没有 <4 x float> 就以为向量化成功了。容易被忽略的点:
- 检查
load/store指令是否带align属性:未对齐(align 1)可能让后端降级回标量路径 - 看是否有大量
shufflevector指令:说明向量数据布局被打散,可能是跨步访问或类型转换导致,实际性能可能不如预期 - 用
opt -print-after-all -loop-vectorize观察 Pass 输出,确认 LoopVectorizePass 是否真正触发并输出了向量 IR 块 - 最终生成的汇编里如果还是
addss而非addps,大概率是 IR 层虽有向量类型,但后端因寄存器压力或依赖关系放弃了向量化

















