LLVM算术指令(如add/sub/mul/sdiv)必须显式指定类型,整数用i32等,浮点用float/double,指针需先ptrtoint转换;IRBuilder要求操作数为Value*,常量须用getInt32等包装,且严格遵循SSA规则。

add/sub/mul/sdiv 这些指令怎么选类型
LLVM 的算术指令(如 add、sub、mul、sdiv)必须显式指定操作数和结果的类型,不能靠上下文推断。比如整数除法要用 sdiv(有符号)或 udiv(无符号),不能写成 div——后者根本不存在。
常见错误是传入 i32 类型的操作数却用 fadd,或者对指针用 add 而没先用 ptrtoint 转换。IRBuilder 会直接报错:Assertion failed: isValidType(Type) && "Invalid type for binary operator!"。
-
add i32 %a, %b:两个i32相加,结果也是i32 -
fadd float %x, %y:浮点加法,类型必须是float或double,不能混用 -
srem i64 %m, %n:求余必须匹配整数宽度,i32和i64不能直接运算
IRBuilder.CreateAdd 和 builder.CreateMul 怎么用才不崩
用 IRBuilder 生成指令时,所有操作数必须是 Value* 类型,不能是原始 C++ int 或 float。新手常犯的错是直接传 3 而不是 builder.getInt32(3),导致编译器崩溃或生成非法 IR。
另外,CreateAdd 等函数默认不带溢出检查;如果需要检测(比如实现 Python 的任意精度语义),得手动插入 overflow-checking 模式,或者改用 CreateNSWAdd/CreateNUWAdd 等带语义标记的变体。
- 参数来自函数参数?用
function->getArg(0),别用arg_begin()后裸解引用 - 常量必须包装:用
builder.getInt32(42),不是ConstantInt::get(...)手动构造(除非你真要控制 APInt 细节) - 临时变量名不重要,但 SSA 要求每个
%tmp只赋值一次;重复调用CreateAdd会生成不同名字的寄存器,不是覆盖
为什么生成的 IR 里有 %1 = add i32 %a, %b,而不是直接写 a + b
LLVM IR 是静态单赋值(SSA)形式,所有中间结果都必须命名(如 %1),且每个名字只能定义一次。这不是语法糖,而是优化器工作的基础——没有它,Value* 就无法唯一标识一个计算结果,后续的常量传播、死代码消除都会失效。
你看到的 %a 和 %b 是函数参数的 SSA 名,不是变量名;它们在 IR 中实际对应的是 function->getArg(0) 返回的 Argument* 对象。如果你在多个基本块里用同一个参数,IRBuilder 会自动处理 φ 节点,但前提是:你得用 builder.SetInsertPoint() 正确切换插入点。
- 忘记调用
SetInsertPoint就直接CreateAdd?会 crash,报Cannot insert instruction into basic block that is not in a function - 想让 IR 更可读?可以用
builder.CreateAdd(..., "sum"),生成%sum = add i32 %a, %b,但名字只影响打印,不影响语义 - 不要试图“复用”
%1:IRBuilder 每次调用都会生成新寄存器,硬编码名字会导致 verifier 失败
表达式嵌套时,操作数顺序和括号怎么体现
LLVM IR 没有括号概念,嵌套靠指令依赖链体现。比如 (a + b) * c 必须先生成 add 指令,再把它的返回值 Value* 当作 mul 的第一个操作数。顺序错了,IR 就语义错误——哪怕文本上看着像。
典型陷阱是把 builder.CreateMul(a, builder.CreateAdd(b, c)) 写成 builder.CreateMul(builder.CreateAdd(a, b), c),表面看只是括号挪了位置,但实际生成的指令流完全不同,而且 verifier 不会报错,只有运行时行为异常。
- AST 到 IR 的映射必须严格按后序遍历:子表达式先生成指令,父表达式再用其返回值
- 二元操作数顺序影响有符号性:例如
sdiv i32 %a, %b中,%a是被除数,%b是除数;反了就不是原意 - 浮点指令(
fadd等)不支持nuw/nsw标记,也不做整数溢出检查,这点和整数指令本质不同
复杂点在于:IRBuilder 不验证语义合理性,只保证语法合法。比如 builder.CreateAdd(ptr, ptr) 在类型检查阶段就过不去,但 builder.CreateAdd(ptr, int) 可能通过,只是后续 codegen 阶段会失败。真正容易被忽略的,是操作数来源的生命周期管理——比如临时 ConstantInt 对象被提前析构,导致 Value* 悬空。

















