c variadic calls

This commit is contained in:
2026-06-14 15:53:45 +02:00
parent 638ca57f5c
commit 2d3d0bd266
21 changed files with 409 additions and 35 deletions
+1
View File
@@ -130,6 +130,7 @@ Function :: struct {
c_abi: bool,
imported: bool,
has_body: bool,
variadic: bool,
params: []Param,
result: Type_Syntax,
body: []Stmt_Id,
+69 -9
View File
@@ -454,7 +454,7 @@ add_unsupported_type_diagnostic :: proc(
}
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
if left.result != right.result || len(left.params) != len(right.params) {
if left.result != right.result || left.variadic != right.variadic || len(left.params) != len(right.params) {
return false
}
for param, index in left.params {
@@ -465,6 +465,17 @@ function_signatures_equal :: proc(left, right: ast.Function) -> bool {
return true
}
valid_call_arity :: proc(function: ast.Function, count: int) -> bool {
return count >= len(function.params) if function.variadic else count == len(function.params)
}
call_arg_expected :: proc(function: ast.Function, index: int) -> types.Type {
if index < 0 || index >= len(function.params) {
return types.INVALID
}
return type_from_syntax(function.params[index].type)
}
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
for existing in names {
if existing == name {
@@ -566,6 +577,14 @@ validate_declarations :: proc(checker: ^Checker) {
symbol_text(checker, function.name),
)
}
if function.variadic && (!function.c_abi || function.has_body) {
checker.template_diagnostics[function_id] = source.addf(
checker.diagnostics,
function.span,
"variadic function '%s' must be a bodyless 'c_func' declaration",
symbol_text(checker, function.name),
)
}
if !function.has_body && function.c_abi {
for param in function.params {
if add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type)) !=
@@ -1029,11 +1048,12 @@ infer_expr :: proc(
}
}
function := checker.ast_module.functions[frame.template]
if can_specialize(checker, function, stack[frame_index].args) {
if valid_call_arity(function, len(expr.args)) &&
can_specialize(checker, function, stack[frame_index].args) {
spec := INVALID_SPEC
if demanded == nil {
spec = ensure_spec(checker, frame.template, stack[frame_index].args)
} else if len(expr.args) == len(function.params) {
} else {
spec = find_spec(checker, frame.template, stack[frame_index].args)
mark_spec_demanded(checker, spec, demanded)
}
@@ -1317,6 +1337,32 @@ coerce_expr :: proc(
return invalid_hir_expr(checker, span, id, expected)
}
promote_c_vararg_expr :: proc(checker: ^Checker, expr_id: hir.Expr_Id, span: source.Span) -> hir.Expr_Id {
actual := checker.module.exprs[expr_id].type
if !types.is_c_vararg_type(actual, &checker.module.types) {
id := source.addf(
checker.diagnostics,
span,
"C variadic argument must be a concrete scalar or pointer, got %s",
types.name(actual),
)
return invalid_hir_expr(checker, span, id, types.C_INT)
}
promoted := types.c_vararg_promotion(actual, checker.target)
if types.equal(actual, promoted) {
return expr_id
}
return add_hir_expr(checker, hir.Expr{
kind=.C_Vararg_Promote,
span=span,
type=promoted,
left=expr_id,
target=hir.INVALID_REF,
right=hir.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
build_constant_expr :: proc(
checker: ^Checker,
expr: ast.Expr,
@@ -1891,13 +1937,16 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
if len(expr.args) != len(checker.ast_module.functions[template].params) {
function := checker.ast_module.functions[template]
if !valid_call_arity(function, len(expr.args)) {
message := "function '%s' expects at least %d arguments, got %d" if function.variadic else
"function '%s' expects %d arguments, got %d"
id := source.addf(
checker.diagnostics,
expr.span,
"function '%s' expects %d arguments, got %d",
message,
symbol_text(checker, expr.name),
len(checker.ast_module.functions[template].params),
len(function.params),
len(expr.args),
)
last = invalid_hir_expr(checker, expr.span, id)
@@ -1909,7 +1958,7 @@ build_expr :: proc(
stack[frame_index].arg_types = make([]types.Type, len(expr.args), checker.allocator)
stack[frame_index].stage = 3
if len(expr.args) > 0 {
arg_expected := type_from_syntax(checker.ast_module.functions[template].params[0].type)
arg_expected := call_arg_expected(function, 0)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
@@ -1980,7 +2029,7 @@ build_expr :: proc(
stack[frame_index].arg_index += 1
if frame.arg_index+1 < len(expr.args) {
next := frame.arg_index+1
arg_expected := type_from_syntax(checker.ast_module.functions[frame.template].params[next].type)
arg_expected := call_arg_expected(checker.ast_module.functions[frame.template], next)
if !is_runtime_type(checker, arg_expected) {
arg_expected = types.INVALID
}
@@ -2004,7 +2053,8 @@ build_expr :: proc(
_ = pop(&stack)
continue
}
for _, index in stack[frame_index].built_args {
fixed_count := len(checker.ast_module.functions[frame.template].params)
for index in 0..<fixed_count {
stack[frame_index].built_args[index] = coerce_expr(
checker,
stack[frame_index].built_args[index],
@@ -2012,6 +2062,14 @@ build_expr :: proc(
checker.module.exprs[stack[frame_index].built_args[index]].span,
)
}
for index in fixed_count..<len(stack[frame_index].built_args) {
arg := stack[frame_index].built_args[index]
stack[frame_index].built_args[index] = promote_c_vararg_expr(
checker,
arg,
checker.module.exprs[arg].span,
)
}
function_id := checker.specs[spec].hir_id
assert(function_id != hir.INVALID_FUNCTION)
add_unique_function(calls, function_id)
@@ -2128,6 +2186,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
implementation = .Declaration,
linkage = .External if function.c_abi else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
variadic = function.variadic,
params = params[:],
result = spec.result,
locals = hir_locals[:],
@@ -2524,6 +2583,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
implementation = .Definition,
linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal,
is_main = function.pkg == 0 && function.name == checker.main_symbol,
variadic = function.variadic,
params = params[:],
result = spec.result,
locals = hir_locals[:],
+5 -4
View File
@@ -46,10 +46,11 @@ Alias :: struct {
}
Function :: struct {
name: string,
params: []Type_Id,
result: Type_Id,
reason: string,
name: string,
params: []Type_Id,
result: Type_Id,
variadic: bool,
reason: string,
}
Unsupported :: struct {
+2 -2
View File
@@ -338,9 +338,8 @@ visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
linkage := ctx.api.get_cursor_linkage(cursor)
if linkage != CXLinkage_External {
reason = "static and non-external C functions are not supported"
} else if ctx.api.cursor_is_variadic(cursor) != 0 {
reason = "C variadic functions are not supported"
}
variadic := ctx.api.cursor_is_variadic(cursor) != 0
function_type := ctx.api.get_cursor_type(cursor)
result_type := translate_type(ctx, ctx.api.get_result_type(function_type))
if result_type == INVALID_TYPE && len(reason) == 0 {
@@ -364,6 +363,7 @@ visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
params=params[:],
result=result_type,
variadic=variadic,
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
})
case CXCursor_TypedefDecl:
+2
View File
@@ -93,6 +93,7 @@ Expr_Kind :: enum u8 {
Unwrap,
Orelse,
Widen,
C_Vararg_Promote,
Weaken_Pointer,
Negate,
Add,
@@ -144,6 +145,7 @@ Function :: struct {
implementation: Implementation,
linkage: Linkage,
is_main: bool,
variadic: bool,
params: []Local_Id,
result: types.Type,
locals: []Local,
+2
View File
@@ -87,6 +87,7 @@ Opcode :: enum u8 {
Orelse_Begin,
Orelse,
Widen,
C_Vararg_Promote,
Weaken_Pointer,
Neg_Checked,
Add_Checked,
@@ -115,6 +116,7 @@ Function :: struct {
implementation: Implementation,
linkage: Linkage,
is_main: bool,
variadic: bool,
param_types: []types.Type,
result: types.Type,
instructions: []Instruction,
+6 -1
View File
@@ -119,7 +119,12 @@ lex :: proc(
cursor += 1
if cursor < len(bytes) && bytes[cursor] == '.' {
cursor += 1
append_token(&stream, source_file, .Range, start, cursor)
if cursor < len(bytes) && bytes[cursor] == '.' {
cursor += 1
append_token(&stream, source_file, .Ellipsis, start, cursor)
} else {
append_token(&stream, source_file, .Range, start, cursor)
}
} else {
append_token(&stream, source_file, .Dot, start, cursor)
}
+63 -7
View File
@@ -112,7 +112,7 @@ valid_value :: proc(
switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
.Widen, .Weaken_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
.Widen, .C_Vararg_Promote, .Weaken_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Trap, .Return, .Return_Void:
@@ -275,14 +275,16 @@ emit_call_args :: proc(
if index > 0 {
strings.write_string(builder, ", ")
}
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index], store))
if c_abi {
extension := c_abi_extension(param_types[index], store.selected)
arg_type := param_types[index] if index < len(param_types) && valid_instruction(instructions, arg) else
(instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID)
fmt.sbprintf(builder, "%s ", llvm_type(arg_type, store))
if c_abi && index < len(param_types) {
extension := c_abi_extension(arg_type, store.selected)
if len(extension) > 0 {
fmt.sbprintf(builder, "%s ", extension)
}
}
write_operand(builder, instructions, arg, param_types[index], store)
write_operand(builder, instructions, arg, arg_type, store)
}
}
@@ -707,6 +709,31 @@ emit_instruction_stream :: proc(
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
case .C_Vararg_Promote:
if !valid_instruction(instructions, instruction.a) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand")
continue
}
from_type := instructions[instruction.a].type
if types.equal(from_type, instruction.type) ||
!types.equal(types.c_vararg_promotion(from_type, emitter.module.target), instruction.type) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand")
continue
}
if types.bits(from_type, emitter.module.target) == types.bits(instruction.type, emitter.module.target) {
type_name := llvm_type(instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s ", instruction_index, type_name)
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", %s ", type_name)
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
strings.write_string(&emitter.builder, "\n")
continue
}
operation := "fpext" if types.is_float(from_type, emitter.module.target) else
("sext" if types.is_signed(from_type, emitter.module.target) else "zext")
fmt.sbprintf(&emitter.builder, " %%v%d = %s %s ", instruction_index, operation, llvm_type(from_type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.a, from_type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, " to %s\n", llvm_type(instruction.type, &emitter.module.types))
case .Weaken_Pointer:
if !valid_instruction(instructions, instruction.a) ||
!types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
@@ -806,10 +833,20 @@ emit_instruction_stream :: proc(
continue
}
target := emitter.module.functions[function_id]
valid_args := len(instruction.args) == len(target.param_types)
valid_args := (len(instruction.args) >= len(target.param_types) if target.variadic else
len(instruction.args) == len(target.param_types)) &&
(!target.variadic || target.calling_convention == .C)
if valid_args {
for arg, index in instruction.args {
if !valid_value(instructions, arg, target.param_types[index], &emitter.module.types) {
expected := target.param_types[index] if index < len(target.param_types) && valid_instruction(instructions, arg) else
(instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID)
if index >= len(target.param_types) &&
(!types.is_c_vararg_type(expected, &emitter.module.types) ||
!types.equal(types.c_vararg_promotion(expected, emitter.module.target), expected)) {
valid_args = false
break
}
if !valid_value(instructions, arg, expected, &emitter.module.types) {
valid_args = false
break
}
@@ -833,6 +870,19 @@ emit_instruction_stream :: proc(
strings.write_string(&emitter.builder, "fastcc ")
}
emit_function_result(&emitter.builder, target, &emitter.module.types)
if target.variadic {
strings.write_string(&emitter.builder, " (")
for param_type, index in target.param_types {
if index > 0 {
strings.write_string(&emitter.builder, ", ")
}
strings.write_string(&emitter.builder, llvm_type(param_type, &emitter.module.types))
}
if len(target.param_types) > 0 {
strings.write_string(&emitter.builder, ", ")
}
strings.write_string(&emitter.builder, "...)")
}
fmt.sbprintf(&emitter.builder, " @%s(", target.link_name)
emit_call_args(
&emitter.builder, instructions, instruction.args, target.param_types,
@@ -1037,6 +1087,12 @@ emit_functions :: proc(emitter: ^Emitter) {
fmt.sbprintf(&emitter.builder, " %%v%d", index)
}
}
if function.variadic {
if len(function.param_types) > 0 {
strings.write_string(&emitter.builder, ", ")
}
strings.write_string(&emitter.builder, "...")
}
if function.implementation == .Declaration {
strings.write_string(&emitter.builder, ")\n\n")
continue
+4 -3
View File
@@ -177,8 +177,8 @@ translate_c_type :: proc(
return translated
}
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type) -> bool {
if left.result != result || len(left.params) != len(params) {
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type, variadic: bool) -> bool {
if left.result != result || left.variadic != variadic || len(left.params) != len(params) {
return false
}
for param, index in params {
@@ -282,7 +282,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
continue
}
duplicate = true
if !function_signatures_equal(existing, params, function_result) && len(existing.unsupported_reason) == 0 {
if !function_signatures_equal(existing, params, function_result, function.variadic) && len(existing.unsupported_reason) == 0 {
existing.unsupported_reason = fmt.aprintf(
"conflicting C declarations for '%s'",
function.name,
@@ -303,6 +303,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
c_abi=true,
imported=true,
has_body=false,
variadic=function.variadic,
params=params,
result=function_result,
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
+4 -2
View File
@@ -324,7 +324,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
})
}
_ = pop(&stack)
case .Widen, .Weaken_Pointer:
case .Widen, .C_Vararg_Promote, .Weaken_Pointer:
stack[frame_index].stage = 1
append(&stack, Lower_Expr_Frame{expr=expr.left})
case .Negate:
@@ -358,7 +358,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}
if frame.stage == 1 {
last = append_instruction(state, ir.Instruction{
op=.Weaken_Pointer if expr.kind == .Weaken_Pointer else .Widen,
op=.Weaken_Pointer if expr.kind == .Weaken_Pointer else
(.C_Vararg_Promote if expr.kind == .C_Vararg_Promote else .Widen),
span=expr.span, type=expr.type, target=ir.INVALID_REF,
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
})
@@ -611,6 +612,7 @@ lower :: proc(hir_module: ^hir.Module, allocator := context.allocator) -> ir.Mod
implementation=.Declaration if function.implementation == .Declaration else .Definition,
linkage=.External if function.linkage == .External else .Internal,
is_main=function.is_main,
variadic=function.variadic,
param_types=param_types,
result=function.result,
instructions=nil if function.implementation == .Declaration else lower_body(hir_module, function, allocator),
+21 -3
View File
@@ -889,11 +889,27 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
return id
}
parse_params :: proc(parser: ^Parser) -> []ast.Param {
parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
params: [dynamic]ast.Param
params.allocator = parser.module.allocator
variadic := false
skip_newlines(parser)
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
if current(parser).kind == .Ellipsis {
marker := advance(parser)
if variadic {
source.add(parser.diagnostics, marker.span, "duplicate variadic marker")
}
variadic = true
skip_newlines(parser)
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
}
if current(parser).kind != .Right_Paren {
source.add(parser.diagnostics, current(parser).span, "variadic marker must be the final parameter")
}
continue
}
names: [dynamic]token.Token
names.allocator = parser.module.allocator
for {
@@ -923,7 +939,7 @@ parse_params :: proc(parser: ^Parser) -> []ast.Param {
}
break
}
return params[:]
return params[:], variadic
}
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
@@ -931,7 +947,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
if _, ok := allow(parser, .Left_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
}
params := parse_params(parser)
params, variadic := parse_params(parser)
if _, ok := allow(parser, .Right_Paren); !ok {
source.add(parser.diagnostics, current(parser).span, "expected ')' after parameters")
}
@@ -954,6 +970,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
file=parser.file,
c_abi=c_abi,
has_body=false,
variadic=variadic,
params=params,
result=result,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -991,6 +1008,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
file=parser.file,
c_abi=c_abi,
has_body=true,
variadic=variadic,
params=params,
result=result,
body=body[:],
+1
View File
@@ -19,6 +19,7 @@ Kind :: enum u8 {
Minus,
Dot,
Range,
Ellipsis,
At,
Star,
Ampersand,
+28
View File
@@ -432,6 +432,34 @@ is_c_signature_type :: proc(value: Type, store: ^Store, allow_void := false) ->
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store)
}
is_c_integer_promotion_candidate :: proc(value: Type) -> bool {
return value >= C_CHAR && value <= C_USHORT
}
c_vararg_promotion :: proc(value: Type, selected := target.DEFAULT) -> Type {
if !is_concrete_scalar(value) {
return value
}
if is_float(value, selected) && bits(value, selected) < bits(C_DOUBLE, selected) {
return C_DOUBLE
}
if is_concrete_integer(value) {
value_bits := bits(value, selected)
int_bits := bits(C_INT, selected)
if value_bits < int_bits {
return C_INT
}
if is_c_integer_promotion_candidate(value) && value_bits == int_bits {
return C_INT if is_signed(value, selected) else C_UINT
}
}
return value
}
is_c_vararg_type :: proc(value: Type, store: ^Store) -> bool {
return is_concrete_scalar(value) || is_pointer(value, store) || is_optional_pointer(value, store)
}
child_type :: proc(value: Type, store: ^Store) -> Type {
item, ok := node(store, value)
return item.child if ok else INVALID