llvm函数调用ir生成核心是irbuilder::createcall,需先声明/定义函数、确保参数类型匹配和调用约定一致,否则触发verifier错误;外部函数(如printf)须用function::create声明签名,参数须严格对齐并显式转换,递归调用需将函数指针提前加入模块。

LLVM 为函数调用生成 IR 的核心是 IRBuilder::CreateCall,但直接调用它之前必须确保函数声明/定义已存在、参数类型匹配、调用约定一致——否则会触发 verifier 错误或运行时崩溃。
函数必须先声明或定义,不能“裸调用”
LLVM 不允许在未注册函数符号的情况下生成 call 指令。即使目标函数在外部(如 libc 的 printf),也得先用 Function::Create 声明其签名:
-
FunctionType::get(RetTy, ArgTys, isVarArg)构建函数类型,注意isVarArg对printf类函数必须为true - 用
Function::Create(..., GlobalValue::ExternalLinkage, "printf", module)创建声明;若函数在当前模块中定义,则需先建好Function并填充基本块 - 漏掉这步会导致
Call site does not have matching function signature或 verifier 失败
CreateCall 的参数顺序和值类型必须严格对齐
调用 builder.CreateCall(Fn, Args, Name) 时,Args 是 ArrayRef<value></value>,每个 Value* 必须与函数参数类型一一兼容:
- 整数参数不能传
float,哪怕数值相同;需显式用builder.CreateSIToFP或builder.CreateFPToSI转换 - 指针参数(如字符串字面量)要先用
builder.CreateGlobalStringPtr转成i8*,不能直接传 C 字符串常量 - 若函数原型是
i32 (i32, i32),但你传了 3 个参数,LLVM 会静默截断或崩溃,取决于构建模式(Debug 版通常 abort)
调用外部函数时,CallingConv::C 是默认且关键的约定
多数 C 标准库函数(printf、malloc)依赖 C 调用约定,LLVM 默认使用 CallingConv::C,但如果你手动设置了其他约定(如 X86_64_SysV),链接期可能失败或行为异常:
- 无需显式设置,
Function::Create声明时默认就是CallingConv::C - 若调用 Windows API,需确认是否应设为
CallingConv::X86_StdCall;不匹配会导致栈失衡 - 可通过
Fn->setCallingConv(CallingConv::C)显式加固,尤其在多目标平台交叉生成时
递归调用必须确保函数定义已插入模块,且 codegen() 能访问到自身
像 factorial 这类递归函数,在 AST codegen 阶段生成调用时,Function* 可能尚未完成 body 填充,但 CreateCall 仍可接受未完成的 Function* ——前提是该 Function* 已被加入 TheModule:
- 典型流程:先
Function::Create(..., module)创建空函数 → 插入NamedValues["factorial"] = Fn→ 再进入Fn->begin()->getFirstInsertionPt()填充 body - 递归 call 发生在 body 填充过程中,此时
Fn是有效指针,但Fn->empty() == true;LLVM 允许这种“前向引用” - 如果忘了把
Fn加入TheModule,CreateCall会返回空指针,且后续verifyModule报Call to undefined function
最易被忽略的一点:CallInst 本身不携带返回值绑定逻辑,所有返回值都靠 Value* 指针承接并存入 NamedValues 或参与后续运算;漏接返回值不会报错,但会导致后续 use-def 链断裂,优化器可能直接删掉整个 call。











