When a clang::FunctionDecl for printf is constructed programmatically (FunctionDecl::Create + TranslationUnitDecl::addDecl) — bypassing Sema::ActOnFunctionDeclarator — CIRGen emits the resulting cir.func as non-variadic, even though the underlying FunctionProtoType::isVariadic() is true at every observable point (immediately after Create, in redecls(), and at the start of CIR generation).
snprintf and fprintf constructed via the same path emit variadic cir.func correctly. Only printf is affected. Standard Clang (which routes through Sema::ActOnFunctionDeclarator and sets BuiltinID = BIprintf) emits variadic correctly.
The malformed CIR then fails LLVM lowering with:
loc(...): error: 'cir.call' op incorrect number of operands for callee
LLVM ERROR: The pass manager failed to lower CIR to LLVMIR dialect!
Reproducer
Using a test fixture that ends up calling printf with a format + 1 arg:
builds/default/tools/patchir-decomp/Debug/patchir-decomp \
--input local_test/option2-verify/triage/FUN_00015140.json \
--use-structuring-pass --emit-cir --print-tu \
--output /tmp/repro
Then inspect the CIR:
$ grep -E 'sym_name = "printf"|sym_name = "snprintf"|sym_name = "fprintf"' /tmp/repro.cir
"cir.func"() <{function_type = !cir.func<(!cir.ptr<!s8i>, !u32i, !cir.ptr<!s8i>, ...) -> !s32i>, ..., sym_name = "snprintf", ...
"cir.func"() <{function_type = !cir.func<(!cir.ptr<!rec__IO_FILE>, !cir.ptr<!s8i>, ...) -> !s32i>, ..., sym_name = "fprintf", ...
"cir.func"() <{function_type = !cir.func<(!cir.ptr<!s8i>) -> !s32i>, ..., sym_name = "printf", ...
Compare with the printed AST (/tmp/repro.c):
int snprintf(signed char *param_0, size_t param_1, signed char *param_2, ...);
int fprintf(FILE *param_0, signed char *param_1, ...);
int printf(signed char *param_0, ...); // <-- AST is variadic
All three FunctionDecls are variadic at the AST level — but only printf's cir.func is missing the ....
Observable state of printf's FunctionDecl just before CIRGen
getNumParams() == 1
getBuiltinID() == 0 (parser bypass — Sema's builtin recognition never runs)
getCanonicalDecl() == getFirstDecl() == getMostRecentDecl() == this
redecls() yields one entry, that entry is the same FunctionDecl
getType()->castAs<FunctionProtoType>()->isVariadic() == true
hasBody() == false
snprintf and fprintf have identical structural shape (BuiltinID==0, single decl, no body, isVariadic==1) yet emit variadic cir.func correctly.
When a
clang::FunctionDeclforprintfis constructed programmatically (FunctionDecl::Create+TranslationUnitDecl::addDecl) — bypassingSema::ActOnFunctionDeclarator— CIRGen emits the resultingcir.funcas non-variadic, even though the underlyingFunctionProtoType::isVariadic()istrueat every observable point (immediately afterCreate, inredecls(), and at the start of CIR generation).snprintfandfprintfconstructed via the same path emit variadiccir.funccorrectly. Onlyprintfis affected. Standard Clang (which routes throughSema::ActOnFunctionDeclaratorand setsBuiltinID = BIprintf) emits variadic correctly.The malformed CIR then fails LLVM lowering with:
Reproducer
Using a test fixture that ends up calling
printfwith a format + 1 arg:Then inspect the CIR:
Compare with the printed AST (
/tmp/repro.c):All three FunctionDecls are variadic at the AST level — but only
printf'scir.funcis missing the....Observable state of
printf's FunctionDecl just before CIRGengetNumParams() == 1getBuiltinID() == 0(parser bypass — Sema's builtin recognition never runs)getCanonicalDecl() == getFirstDecl() == getMostRecentDecl() == thisredecls()yields one entry, that entry is the sameFunctionDeclgetType()->castAs<FunctionProtoType>()->isVariadic() == truehasBody() == falsesnprintfandfprintfhave identical structural shape (BuiltinID==0, single decl, no body,isVariadic==1) yet emit variadiccir.funccorrectly.