c variadic calls
This commit is contained in:
+1
-1
@@ -34,6 +34,7 @@
|
||||
- file-local relative imports, aliases, and qualified member access
|
||||
- relative `.h` imports as synthetic package namespaces
|
||||
- transitive external C function prototypes, typedef chains, C scalars, and pointers to opaque C records
|
||||
- bodyless manual and imported C variadic declarations with target-aware default argument promotions
|
||||
- reference-time diagnostics for unsupported imported C declarations
|
||||
|
||||
### compiler behavior
|
||||
@@ -50,7 +51,6 @@
|
||||
|
||||
### foreign functions and linking
|
||||
|
||||
- c variadic calls with default argument promotions
|
||||
- exporting brolang functions to c
|
||||
|
||||
### scalar and compound types
|
||||
|
||||
@@ -45,6 +45,18 @@ pointers to opaque records. They never add linker inputs; implementations must
|
||||
still be supplied explicitly with the C-prefixed linking options. Set
|
||||
`BROLANG_LIBCLANG_PATH` when libclang is not installed in a standard location.
|
||||
|
||||
Bodyless manual and imported C functions may be variadic:
|
||||
|
||||
```bro
|
||||
log_values :: c_func(tag c_int, ...) c_int
|
||||
```
|
||||
|
||||
Arguments after `...` accept concrete scalars, pointers, and nullable pointers.
|
||||
Narrow integers are promoted to the target C `int` or `unsigned int`, and
|
||||
`f32`/`c_float` are promoted to `c_double`. Arrays, slices, structs, and other
|
||||
compound values must be converted to an explicit C-compatible representation
|
||||
before the call.
|
||||
|
||||
Compilation phases are isolated under `compiler/`:
|
||||
|
||||
```text
|
||||
@@ -83,6 +95,7 @@ Current prototype features:
|
||||
- Qualified imported globals and functions with package-aware symbol mangling
|
||||
- Demand-monomorphized Brolang and C-ABI functions
|
||||
- Bodyless concrete C function declarations with exact external symbol names
|
||||
- Bodyless manual and imported C variadic declarations with default argument promotions
|
||||
- Ordered linking of additional C sources, objects, archives, and libraries
|
||||
- Checked signed addition and unary negation
|
||||
- Static, eager runtime, and deferred problematic globals
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
- diagnose unsupported declarations when referenced
|
||||
- dynamically load libclang behind a replaceable c importer boundary
|
||||
|
||||
3. c variadic calls
|
||||
3. c variadic calls (implemented)
|
||||
- represent c variadics as a fixed parameter count plus a variadic flag
|
||||
- apply c default argument promotions at call sites
|
||||
- emit LLVM c-variadic declarations and calls
|
||||
|
||||
@@ -130,6 +130,7 @@ Function :: struct {
|
||||
c_abi: bool,
|
||||
imported: bool,
|
||||
has_body: bool,
|
||||
variadic: bool,
|
||||
params: []Param,
|
||||
result: Type_Syntax,
|
||||
body: []Stmt_Id,
|
||||
|
||||
@@ -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[:],
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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[:],
|
||||
|
||||
@@ -19,6 +19,7 @@ Kind :: enum u8 {
|
||||
Minus,
|
||||
Dot,
|
||||
Range,
|
||||
Ellipsis,
|
||||
At,
|
||||
Star,
|
||||
Ampersand,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -229,6 +229,38 @@ main :: func() void {}
|
||||
testing.expect(t, module.functions[3].has_body)
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_accepts_terminal_c_variadic_markers_and_recovers_nonterminal_markers :: proc(t: ^testing.T) {
|
||||
text := `fixed :: c_func(value c_int, ...) c_int
|
||||
zero :: c_func(...) void
|
||||
bad :: c_func(..., value c_int) c_int
|
||||
main :: func() void {}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&module)
|
||||
|
||||
found_ellipsis := false
|
||||
for tok in stream.items {
|
||||
found_ellipsis = found_ellipsis || tok.kind == .Ellipsis
|
||||
}
|
||||
testing.expect(t, found_ellipsis)
|
||||
testing.expect(t, module.functions[0].variadic)
|
||||
testing.expect_value(t, len(module.functions[0].params), 1)
|
||||
testing.expect(t, module.functions[1].variadic)
|
||||
testing.expect_value(t, len(module.functions[1].params), 0)
|
||||
testing.expect(t, module.functions[2].variadic)
|
||||
testing.expect_value(t, len(module.functions[2].params), 1)
|
||||
testing.expect_value(t, len(diagnostics.items), 1)
|
||||
testing.expect(t, strings.contains(diagnostics.items[0].message, "final parameter"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
|
||||
text := `c :: 5
|
||||
@@ -680,6 +712,14 @@ c_primitives_remain_distinct_with_apple_silicon_representations :: proc(t: ^test
|
||||
testing.expect_value(t, types.representation(types.C_FLOAT), types.F32)
|
||||
testing.expect_value(t, types.representation(types.C_DOUBLE), types.F64)
|
||||
testing.expect_value(t, types.representation(types.C_LONGDOUBLE), types.F64)
|
||||
testing.expect_value(t, types.c_vararg_promotion(types.I8), types.C_INT)
|
||||
testing.expect_value(t, types.c_vararg_promotion(types.U16), types.C_INT)
|
||||
testing.expect_value(t, types.c_vararg_promotion(types.C_CHAR), types.C_INT)
|
||||
testing.expect_value(t, types.c_vararg_promotion(types.C_USHORT), types.C_INT)
|
||||
testing.expect_value(t, types.c_vararg_promotion(types.F32), types.C_DOUBLE)
|
||||
testing.expect_value(t, types.c_vararg_promotion(types.C_FLOAT), types.C_DOUBLE)
|
||||
testing.expect_value(t, types.c_vararg_promotion(types.U32), types.U32)
|
||||
testing.expect_value(t, types.c_vararg_promotion(types.C_DOUBLE), types.C_DOUBLE)
|
||||
testing.expect_value(t, target.llvm_triple(target.DEFAULT), "arm64-apple-macosx13.0.0")
|
||||
}
|
||||
|
||||
@@ -744,6 +784,113 @@ main :: func() void {
|
||||
testing.expect(t, strings.contains(llvm_text, "orelse_some"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) {
|
||||
text := `variadic :: c_func(tag c_int, ...) c_int
|
||||
zero :: c_func(...) void
|
||||
main :: func() void {
|
||||
narrow i8 :: -2
|
||||
unsigned u16 :: 3
|
||||
float_value f32 :: 4.0
|
||||
c_float_value c_float :: 5.0
|
||||
pointer *u8 :: "ok".ptr
|
||||
nullable ?*u8 :: pointer
|
||||
zero(pointer)
|
||||
_ = variadic(7, narrow, unsigned, float_value, c_float_value, pointer, nullable)
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
ir_module := lower.lower(&hir_module)
|
||||
defer ir.destroy_module(&ir_module)
|
||||
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
|
||||
defer delete(llvm_text)
|
||||
|
||||
promotions := 0
|
||||
for expr in hir_module.exprs {
|
||||
if expr.kind == .C_Vararg_Promote {
|
||||
promotions += 1
|
||||
}
|
||||
}
|
||||
testing.expect_value(t, len(diagnostics.items), 0)
|
||||
testing.expect_value(t, promotions, 4)
|
||||
found_hir_variadic := false
|
||||
for function in hir_module.functions {
|
||||
found_hir_variadic = found_hir_variadic || function.variadic
|
||||
}
|
||||
found_ir_variadic := false
|
||||
for function in ir_module.functions {
|
||||
found_ir_variadic = found_ir_variadic || function.variadic
|
||||
}
|
||||
testing.expect(t, found_hir_variadic)
|
||||
testing.expect(t, found_ir_variadic)
|
||||
testing.expect(t, strings.contains(llvm_text, "declare i32 @variadic(i32, ...)"))
|
||||
testing.expect(t, strings.contains(llvm_text, "declare void @zero(...)"))
|
||||
testing.expect(t, strings.contains(llvm_text, "sext i8"))
|
||||
testing.expect(t, strings.contains(llvm_text, "zext i16"))
|
||||
testing.expect(t, strings.contains(llvm_text, "fpext float"))
|
||||
testing.expect(t, strings.contains(llvm_text, "call void (...) @zero(ptr"))
|
||||
testing.expect(t, strings.contains(llvm_text, "call i32 (i32, ...) @variadic(i32 7, i32"))
|
||||
testing.expect(t, strings.contains(llvm_text, "double"))
|
||||
testing.expect(t, strings.contains(llvm_text, "ptr"))
|
||||
}
|
||||
|
||||
@(test)
|
||||
c_variadic_restrictions_and_extra_argument_types_are_diagnosed :: proc(t: ^testing.T) {
|
||||
text := `foreign :: c_func(...) void
|
||||
requires :: c_func(value c_int, ...) void
|
||||
native :: func(...) void
|
||||
bodyful :: c_func(...) void {}
|
||||
main :: func() void {
|
||||
values [1]u8 :: [1]
|
||||
foreign(values)
|
||||
requires()
|
||||
}
|
||||
`
|
||||
source_file := source.Source{path="test.bro", text=text}
|
||||
diagnostics := source.init_diagnostics(&source_file)
|
||||
defer source.destroy_diagnostics(&diagnostics)
|
||||
symbols := symbol.init_table()
|
||||
defer symbol.destroy_table(&symbols)
|
||||
stream := lexer.lex(&source_file, &diagnostics, &symbols)
|
||||
defer delete(stream.items)
|
||||
ast_module := parser.parse(&stream, &source_file, &diagnostics)
|
||||
defer ast.destroy_module(&ast_module)
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
restricted := 0
|
||||
found_extra := false
|
||||
found_arity := false
|
||||
for diagnostic in diagnostics.items {
|
||||
if strings.contains(diagnostic.message, "must be a bodyless 'c_func' declaration") {
|
||||
restricted += 1
|
||||
}
|
||||
found_extra = found_extra || strings.contains(diagnostic.message, "C variadic argument must be a concrete scalar or pointer")
|
||||
found_arity = found_arity || strings.contains(diagnostic.message, "expects at least 1 arguments")
|
||||
}
|
||||
testing.expect_value(t, restricted, 2)
|
||||
testing.expect(t, found_extra)
|
||||
testing.expect(t, found_arity)
|
||||
}
|
||||
|
||||
@(test)
|
||||
variadicness_is_part_of_c_function_signature_compatibility :: proc(t: ^testing.T) {
|
||||
fixed := ast.Function{result=types.C_INT}
|
||||
variadic := ast.Function{result=types.C_INT, variadic=true}
|
||||
testing.expect(t, !checker.function_signatures_equal(fixed, variadic))
|
||||
testing.expect(t, !loader.function_signatures_equal(fixed, nil, types.C_INT, true))
|
||||
}
|
||||
|
||||
@(test)
|
||||
c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) {
|
||||
text := `Defined :: c_struct {
|
||||
@@ -1683,6 +1830,7 @@ fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, alloca
|
||||
append(&result.functions, cimport.Function{
|
||||
name=fmt.aprintf("fake_value", allocator=allocator),
|
||||
result=cimport.Type_Id(0),
|
||||
variadic=true,
|
||||
reason=fmt.aprintf("", allocator=allocator),
|
||||
})
|
||||
result.available = true
|
||||
@@ -1700,6 +1848,7 @@ cimport_backend_is_replaceable :: proc(t: ^testing.T) {
|
||||
testing.expect_value(t, state.calls, 1)
|
||||
testing.expect_value(t, len(result.functions), 1)
|
||||
testing.expect_value(t, result.functions[0].name, "fake_value")
|
||||
testing.expect(t, result.functions[0].variadic)
|
||||
}
|
||||
|
||||
@(test)
|
||||
@@ -1732,6 +1881,11 @@ loader_injects_and_caches_cimport_backend_per_compilation :: proc(t: ^testing.T)
|
||||
testing.expect_value(t, len(module.imports), 2)
|
||||
testing.expect_value(t, module.imports[0].target, module.imports[1].target)
|
||||
testing.expect_value(t, len(module.functions), 2)
|
||||
found_variadic := false
|
||||
for function in module.functions {
|
||||
found_variadic = found_variadic || function.variadic
|
||||
}
|
||||
testing.expect(t, found_variadic)
|
||||
}
|
||||
|
||||
@(test)
|
||||
|
||||
@@ -10,4 +10,11 @@ main :: func() void {
|
||||
_ = native.child_value(7)
|
||||
_ = native.imported_read(native.imported_handle()?)
|
||||
_ = native.configured_value(9)
|
||||
signed i8 :: -2
|
||||
unsigned u16 :: 3
|
||||
float_value f32 :: 4.0
|
||||
c_float_value c_float :: 5.0
|
||||
pointer *u8 :: "ok".ptr
|
||||
nullable ?*u8 :: pointer
|
||||
_ = native.imported_variadic(7, signed, unsigned, float_value, c_float_value, pointer, nullable)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,6 @@ int configured_value(int value);
|
||||
#endif
|
||||
|
||||
#define IMPORTED_MACRO 42
|
||||
int imported_variadic(const char *format, ...);
|
||||
int imported_variadic(int marker, ...);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "include/native.h"
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct Imported_Handle {
|
||||
int value;
|
||||
@@ -26,6 +28,28 @@ int imported_read(const Imported_Handle *value) {
|
||||
return value->value;
|
||||
}
|
||||
|
||||
int imported_variadic(int marker, ...) {
|
||||
va_list args;
|
||||
va_start(args, marker);
|
||||
int narrow_signed = va_arg(args, int);
|
||||
int narrow_unsigned = va_arg(args, int);
|
||||
double float_value = va_arg(args, double);
|
||||
double c_float_value = va_arg(args, double);
|
||||
const unsigned char *pointer = va_arg(args, const unsigned char *);
|
||||
const unsigned char *nullable = va_arg(args, const unsigned char *);
|
||||
va_end(args);
|
||||
if (!(marker == 7 &&
|
||||
narrow_signed == -2 &&
|
||||
narrow_unsigned == 3 &&
|
||||
float_value == 4.0 &&
|
||||
c_float_value == 5.0 &&
|
||||
pointer[0] == 'o' &&
|
||||
nullable == pointer)) {
|
||||
abort();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef BROLANG_FEATURE
|
||||
int configured_value(int value) {
|
||||
return value;
|
||||
|
||||
@@ -5,7 +5,6 @@ use_union :: c_func(value native.Imported_Union) void
|
||||
use_enum :: c_func(value native.Imported_Enum) void
|
||||
|
||||
main :: func() void {
|
||||
_ = native.imported_variadic("value")
|
||||
_ = native.IMPORTED_MACRO
|
||||
_ = native.imported_by_value()
|
||||
_ = native.imported_volatile()
|
||||
|
||||
Reference in New Issue
Block a user