c variadic calls
This commit is contained in:
+1
-1
@@ -34,6 +34,7 @@
|
|||||||
- file-local relative imports, aliases, and qualified member access
|
- file-local relative imports, aliases, and qualified member access
|
||||||
- relative `.h` imports as synthetic package namespaces
|
- relative `.h` imports as synthetic package namespaces
|
||||||
- transitive external C function prototypes, typedef chains, C scalars, and pointers to opaque C records
|
- 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
|
- reference-time diagnostics for unsupported imported C declarations
|
||||||
|
|
||||||
### compiler behavior
|
### compiler behavior
|
||||||
@@ -50,7 +51,6 @@
|
|||||||
|
|
||||||
### foreign functions and linking
|
### foreign functions and linking
|
||||||
|
|
||||||
- c variadic calls with default argument promotions
|
|
||||||
- exporting brolang functions to c
|
- exporting brolang functions to c
|
||||||
|
|
||||||
### scalar and compound types
|
### 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
|
still be supplied explicitly with the C-prefixed linking options. Set
|
||||||
`BROLANG_LIBCLANG_PATH` when libclang is not installed in a standard location.
|
`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/`:
|
Compilation phases are isolated under `compiler/`:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -83,6 +95,7 @@ Current prototype features:
|
|||||||
- Qualified imported globals and functions with package-aware symbol mangling
|
- Qualified imported globals and functions with package-aware symbol mangling
|
||||||
- Demand-monomorphized Brolang and C-ABI functions
|
- Demand-monomorphized Brolang and C-ABI functions
|
||||||
- Bodyless concrete C function declarations with exact external symbol names
|
- 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
|
- Ordered linking of additional C sources, objects, archives, and libraries
|
||||||
- Checked signed addition and unary negation
|
- Checked signed addition and unary negation
|
||||||
- Static, eager runtime, and deferred problematic globals
|
- Static, eager runtime, and deferred problematic globals
|
||||||
|
|||||||
@@ -38,7 +38,7 @@
|
|||||||
- diagnose unsupported declarations when referenced
|
- diagnose unsupported declarations when referenced
|
||||||
- dynamically load libclang behind a replaceable c importer boundary
|
- 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
|
- represent c variadics as a fixed parameter count plus a variadic flag
|
||||||
- apply c default argument promotions at call sites
|
- apply c default argument promotions at call sites
|
||||||
- emit LLVM c-variadic declarations and calls
|
- emit LLVM c-variadic declarations and calls
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ Function :: struct {
|
|||||||
c_abi: bool,
|
c_abi: bool,
|
||||||
imported: bool,
|
imported: bool,
|
||||||
has_body: bool,
|
has_body: bool,
|
||||||
|
variadic: bool,
|
||||||
params: []Param,
|
params: []Param,
|
||||||
result: Type_Syntax,
|
result: Type_Syntax,
|
||||||
body: []Stmt_Id,
|
body: []Stmt_Id,
|
||||||
|
|||||||
@@ -454,7 +454,7 @@ add_unsupported_type_diagnostic :: proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function_signatures_equal :: proc(left, right: ast.Function) -> bool {
|
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
|
return false
|
||||||
}
|
}
|
||||||
for param, index in left.params {
|
for param, index in left.params {
|
||||||
@@ -465,6 +465,17 @@ function_signatures_equal :: proc(left, right: ast.Function) -> bool {
|
|||||||
return true
|
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 {
|
contains_name :: proc(names: []symbol.Id, name: symbol.Id) -> bool {
|
||||||
for existing in names {
|
for existing in names {
|
||||||
if existing == name {
|
if existing == name {
|
||||||
@@ -566,6 +577,14 @@ validate_declarations :: proc(checker: ^Checker) {
|
|||||||
symbol_text(checker, function.name),
|
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 {
|
if !function.has_body && function.c_abi {
|
||||||
for param in function.params {
|
for param in function.params {
|
||||||
if add_unsupported_type_diagnostic(checker, param.span, type_from_syntax(param.type)) !=
|
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]
|
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
|
spec := INVALID_SPEC
|
||||||
if demanded == nil {
|
if demanded == nil {
|
||||||
spec = ensure_spec(checker, frame.template, stack[frame_index].args)
|
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)
|
spec = find_spec(checker, frame.template, stack[frame_index].args)
|
||||||
mark_spec_demanded(checker, spec, demanded)
|
mark_spec_demanded(checker, spec, demanded)
|
||||||
}
|
}
|
||||||
@@ -1317,6 +1337,32 @@ coerce_expr :: proc(
|
|||||||
return invalid_hir_expr(checker, span, id, expected)
|
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(
|
build_constant_expr :: proc(
|
||||||
checker: ^Checker,
|
checker: ^Checker,
|
||||||
expr: ast.Expr,
|
expr: ast.Expr,
|
||||||
@@ -1891,13 +1937,16 @@ build_expr :: proc(
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
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(
|
id := source.addf(
|
||||||
checker.diagnostics,
|
checker.diagnostics,
|
||||||
expr.span,
|
expr.span,
|
||||||
"function '%s' expects %d arguments, got %d",
|
message,
|
||||||
symbol_text(checker, expr.name),
|
symbol_text(checker, expr.name),
|
||||||
len(checker.ast_module.functions[template].params),
|
len(function.params),
|
||||||
len(expr.args),
|
len(expr.args),
|
||||||
)
|
)
|
||||||
last = invalid_hir_expr(checker, expr.span, id)
|
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].arg_types = make([]types.Type, len(expr.args), checker.allocator)
|
||||||
stack[frame_index].stage = 3
|
stack[frame_index].stage = 3
|
||||||
if len(expr.args) > 0 {
|
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) {
|
if !is_runtime_type(checker, arg_expected) {
|
||||||
arg_expected = types.INVALID
|
arg_expected = types.INVALID
|
||||||
}
|
}
|
||||||
@@ -1980,7 +2029,7 @@ build_expr :: proc(
|
|||||||
stack[frame_index].arg_index += 1
|
stack[frame_index].arg_index += 1
|
||||||
if frame.arg_index+1 < len(expr.args) {
|
if frame.arg_index+1 < len(expr.args) {
|
||||||
next := frame.arg_index+1
|
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) {
|
if !is_runtime_type(checker, arg_expected) {
|
||||||
arg_expected = types.INVALID
|
arg_expected = types.INVALID
|
||||||
}
|
}
|
||||||
@@ -2004,7 +2053,8 @@ build_expr :: proc(
|
|||||||
_ = pop(&stack)
|
_ = pop(&stack)
|
||||||
continue
|
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(
|
stack[frame_index].built_args[index] = coerce_expr(
|
||||||
checker,
|
checker,
|
||||||
stack[frame_index].built_args[index],
|
stack[frame_index].built_args[index],
|
||||||
@@ -2012,6 +2062,14 @@ build_expr :: proc(
|
|||||||
checker.module.exprs[stack[frame_index].built_args[index]].span,
|
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
|
function_id := checker.specs[spec].hir_id
|
||||||
assert(function_id != hir.INVALID_FUNCTION)
|
assert(function_id != hir.INVALID_FUNCTION)
|
||||||
add_unique_function(calls, function_id)
|
add_unique_function(calls, function_id)
|
||||||
@@ -2128,6 +2186,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
implementation = .Declaration,
|
implementation = .Declaration,
|
||||||
linkage = .External if function.c_abi else .Internal,
|
linkage = .External if function.c_abi else .Internal,
|
||||||
is_main = function.pkg == 0 && function.name == checker.main_symbol,
|
is_main = function.pkg == 0 && function.name == checker.main_symbol,
|
||||||
|
variadic = function.variadic,
|
||||||
params = params[:],
|
params = params[:],
|
||||||
result = spec.result,
|
result = spec.result,
|
||||||
locals = hir_locals[:],
|
locals = hir_locals[:],
|
||||||
@@ -2524,6 +2583,7 @@ build_function :: proc(checker: ^Checker, id: Spec_Id) {
|
|||||||
implementation = .Definition,
|
implementation = .Definition,
|
||||||
linkage = .External if function.c_abi || (function.pkg == 0 && function.name == checker.main_symbol) else .Internal,
|
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,
|
is_main = function.pkg == 0 && function.name == checker.main_symbol,
|
||||||
|
variadic = function.variadic,
|
||||||
params = params[:],
|
params = params[:],
|
||||||
result = spec.result,
|
result = spec.result,
|
||||||
locals = hir_locals[:],
|
locals = hir_locals[:],
|
||||||
|
|||||||
@@ -46,10 +46,11 @@ Alias :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Function :: struct {
|
Function :: struct {
|
||||||
name: string,
|
name: string,
|
||||||
params: []Type_Id,
|
params: []Type_Id,
|
||||||
result: Type_Id,
|
result: Type_Id,
|
||||||
reason: string,
|
variadic: bool,
|
||||||
|
reason: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
Unsupported :: struct {
|
Unsupported :: struct {
|
||||||
|
|||||||
@@ -338,9 +338,8 @@ visit_cursor :: proc "c"(cursor, parent: CXCursor, client_data: rawptr) -> i32 {
|
|||||||
linkage := ctx.api.get_cursor_linkage(cursor)
|
linkage := ctx.api.get_cursor_linkage(cursor)
|
||||||
if linkage != CXLinkage_External {
|
if linkage != CXLinkage_External {
|
||||||
reason = "static and non-external C functions are not supported"
|
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)
|
function_type := ctx.api.get_cursor_type(cursor)
|
||||||
result_type := translate_type(ctx, ctx.api.get_result_type(function_type))
|
result_type := translate_type(ctx, ctx.api.get_result_type(function_type))
|
||||||
if result_type == INVALID_TYPE && len(reason) == 0 {
|
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),
|
name=fmt.aprintf("%s", name, allocator=ctx.allocator),
|
||||||
params=params[:],
|
params=params[:],
|
||||||
result=result_type,
|
result=result_type,
|
||||||
|
variadic=variadic,
|
||||||
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
|
reason=fmt.aprintf("%s", reason, allocator=ctx.allocator),
|
||||||
})
|
})
|
||||||
case CXCursor_TypedefDecl:
|
case CXCursor_TypedefDecl:
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ Expr_Kind :: enum u8 {
|
|||||||
Unwrap,
|
Unwrap,
|
||||||
Orelse,
|
Orelse,
|
||||||
Widen,
|
Widen,
|
||||||
|
C_Vararg_Promote,
|
||||||
Weaken_Pointer,
|
Weaken_Pointer,
|
||||||
Negate,
|
Negate,
|
||||||
Add,
|
Add,
|
||||||
@@ -144,6 +145,7 @@ Function :: struct {
|
|||||||
implementation: Implementation,
|
implementation: Implementation,
|
||||||
linkage: Linkage,
|
linkage: Linkage,
|
||||||
is_main: bool,
|
is_main: bool,
|
||||||
|
variadic: bool,
|
||||||
params: []Local_Id,
|
params: []Local_Id,
|
||||||
result: types.Type,
|
result: types.Type,
|
||||||
locals: []Local,
|
locals: []Local,
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ Opcode :: enum u8 {
|
|||||||
Orelse_Begin,
|
Orelse_Begin,
|
||||||
Orelse,
|
Orelse,
|
||||||
Widen,
|
Widen,
|
||||||
|
C_Vararg_Promote,
|
||||||
Weaken_Pointer,
|
Weaken_Pointer,
|
||||||
Neg_Checked,
|
Neg_Checked,
|
||||||
Add_Checked,
|
Add_Checked,
|
||||||
@@ -115,6 +116,7 @@ Function :: struct {
|
|||||||
implementation: Implementation,
|
implementation: Implementation,
|
||||||
linkage: Linkage,
|
linkage: Linkage,
|
||||||
is_main: bool,
|
is_main: bool,
|
||||||
|
variadic: bool,
|
||||||
param_types: []types.Type,
|
param_types: []types.Type,
|
||||||
result: types.Type,
|
result: types.Type,
|
||||||
instructions: []Instruction,
|
instructions: []Instruction,
|
||||||
|
|||||||
@@ -119,7 +119,12 @@ lex :: proc(
|
|||||||
cursor += 1
|
cursor += 1
|
||||||
if cursor < len(bytes) && bytes[cursor] == '.' {
|
if cursor < len(bytes) && bytes[cursor] == '.' {
|
||||||
cursor += 1
|
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 {
|
} else {
|
||||||
append_token(&stream, source_file, .Dot, start, cursor)
|
append_token(&stream, source_file, .Dot, start, cursor)
|
||||||
}
|
}
|
||||||
|
|||||||
+63
-7
@@ -112,7 +112,7 @@ valid_value :: proc(
|
|||||||
switch instructions[value_id].op {
|
switch instructions[value_id].op {
|
||||||
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
|
||||||
.Load_Global, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
.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
|
return true
|
||||||
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
|
||||||
.Store, .Trap, .Return, .Return_Void:
|
.Store, .Trap, .Return, .Return_Void:
|
||||||
@@ -275,14 +275,16 @@ emit_call_args :: proc(
|
|||||||
if index > 0 {
|
if index > 0 {
|
||||||
strings.write_string(builder, ", ")
|
strings.write_string(builder, ", ")
|
||||||
}
|
}
|
||||||
fmt.sbprintf(builder, "%s ", llvm_type(param_types[index], store))
|
arg_type := param_types[index] if index < len(param_types) && valid_instruction(instructions, arg) else
|
||||||
if c_abi {
|
(instructions[arg].type if valid_instruction(instructions, arg) else types.INVALID)
|
||||||
extension := c_abi_extension(param_types[index], store.selected)
|
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 {
|
if len(extension) > 0 {
|
||||||
fmt.sbprintf(builder, "%s ", extension)
|
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))
|
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)
|
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))
|
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:
|
case .Weaken_Pointer:
|
||||||
if !valid_instruction(instructions, instruction.a) ||
|
if !valid_instruction(instructions, instruction.a) ||
|
||||||
!types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
!types.can_weaken_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) {
|
||||||
@@ -806,10 +833,20 @@ emit_instruction_stream :: proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
target := emitter.module.functions[function_id]
|
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 {
|
if valid_args {
|
||||||
for arg, index in instruction.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
|
valid_args = false
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -833,6 +870,19 @@ emit_instruction_stream :: proc(
|
|||||||
strings.write_string(&emitter.builder, "fastcc ")
|
strings.write_string(&emitter.builder, "fastcc ")
|
||||||
}
|
}
|
||||||
emit_function_result(&emitter.builder, target, &emitter.module.types)
|
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)
|
fmt.sbprintf(&emitter.builder, " @%s(", target.link_name)
|
||||||
emit_call_args(
|
emit_call_args(
|
||||||
&emitter.builder, instructions, instruction.args, target.param_types,
|
&emitter.builder, instructions, instruction.args, target.param_types,
|
||||||
@@ -1037,6 +1087,12 @@ emit_functions :: proc(emitter: ^Emitter) {
|
|||||||
fmt.sbprintf(&emitter.builder, " %%v%d", index)
|
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 {
|
if function.implementation == .Declaration {
|
||||||
strings.write_string(&emitter.builder, ")\n\n")
|
strings.write_string(&emitter.builder, ")\n\n")
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -177,8 +177,8 @@ translate_c_type :: proc(
|
|||||||
return translated
|
return translated
|
||||||
}
|
}
|
||||||
|
|
||||||
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type) -> bool {
|
function_signatures_equal :: proc(left: ast.Function, params: []ast.Param, result: types.Type, variadic: bool) -> bool {
|
||||||
if left.result != result || len(left.params) != len(params) {
|
if left.result != result || left.variadic != variadic || len(left.params) != len(params) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
for param, index in params {
|
for param, index in params {
|
||||||
@@ -282,7 +282,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
duplicate = true
|
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(
|
existing.unsupported_reason = fmt.aprintf(
|
||||||
"conflicting C declarations for '%s'",
|
"conflicting C declarations for '%s'",
|
||||||
function.name,
|
function.name,
|
||||||
@@ -303,6 +303,7 @@ load_header :: proc(state: ^State, path: string, import_span: source.Span) -> as
|
|||||||
c_abi=true,
|
c_abi=true,
|
||||||
imported=true,
|
imported=true,
|
||||||
has_body=false,
|
has_body=false,
|
||||||
|
variadic=function.variadic,
|
||||||
params=params,
|
params=params,
|
||||||
result=function_result,
|
result=function_result,
|
||||||
unsupported_reason=strings.clone(unsupported_reason, state.allocator),
|
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)
|
_ = pop(&stack)
|
||||||
case .Widen, .Weaken_Pointer:
|
case .Widen, .C_Vararg_Promote, .Weaken_Pointer:
|
||||||
stack[frame_index].stage = 1
|
stack[frame_index].stage = 1
|
||||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||||
case .Negate:
|
case .Negate:
|
||||||
@@ -358,7 +358,8 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
|||||||
}
|
}
|
||||||
if frame.stage == 1 {
|
if frame.stage == 1 {
|
||||||
last = append_instruction(state, ir.Instruction{
|
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,
|
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
||||||
a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
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,
|
implementation=.Declaration if function.implementation == .Declaration else .Definition,
|
||||||
linkage=.External if function.linkage == .External else .Internal,
|
linkage=.External if function.linkage == .External else .Internal,
|
||||||
is_main=function.is_main,
|
is_main=function.is_main,
|
||||||
|
variadic=function.variadic,
|
||||||
param_types=param_types,
|
param_types=param_types,
|
||||||
result=function.result,
|
result=function.result,
|
||||||
instructions=nil if function.implementation == .Declaration else lower_body(hir_module, function, allocator),
|
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
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_params :: proc(parser: ^Parser) -> []ast.Param {
|
parse_params :: proc(parser: ^Parser) -> ([]ast.Param, bool) {
|
||||||
params: [dynamic]ast.Param
|
params: [dynamic]ast.Param
|
||||||
params.allocator = parser.module.allocator
|
params.allocator = parser.module.allocator
|
||||||
|
variadic := false
|
||||||
skip_newlines(parser)
|
skip_newlines(parser)
|
||||||
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
|
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: [dynamic]token.Token
|
||||||
names.allocator = parser.module.allocator
|
names.allocator = parser.module.allocator
|
||||||
for {
|
for {
|
||||||
@@ -923,7 +939,7 @@ parse_params :: proc(parser: ^Parser) -> []ast.Param {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
return params[:]
|
return params[:], variadic
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
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 {
|
if _, ok := allow(parser, .Left_Paren); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected '(' after 'func'")
|
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 {
|
if _, ok := allow(parser, .Right_Paren); !ok {
|
||||||
source.add(parser.diagnostics, current(parser).span, "expected ')' after parameters")
|
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,
|
file=parser.file,
|
||||||
c_abi=c_abi,
|
c_abi=c_abi,
|
||||||
has_body=false,
|
has_body=false,
|
||||||
|
variadic=variadic,
|
||||||
params=params,
|
params=params,
|
||||||
result=result,
|
result=result,
|
||||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||||
@@ -991,6 +1008,7 @@ parse_function :: proc(parser: ^Parser, name: token.Token, c_abi: bool) {
|
|||||||
file=parser.file,
|
file=parser.file,
|
||||||
c_abi=c_abi,
|
c_abi=c_abi,
|
||||||
has_body=true,
|
has_body=true,
|
||||||
|
variadic=variadic,
|
||||||
params=params,
|
params=params,
|
||||||
result=result,
|
result=result,
|
||||||
body=body[:],
|
body=body[:],
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Kind :: enum u8 {
|
|||||||
Minus,
|
Minus,
|
||||||
Dot,
|
Dot,
|
||||||
Range,
|
Range,
|
||||||
|
Ellipsis,
|
||||||
At,
|
At,
|
||||||
Star,
|
Star,
|
||||||
Ampersand,
|
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)
|
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 {
|
child_type :: proc(value: Type, store: ^Store) -> Type {
|
||||||
item, ok := node(store, value)
|
item, ok := node(store, value)
|
||||||
return item.child if ok else INVALID
|
return item.child if ok else INVALID
|
||||||
|
|||||||
@@ -229,6 +229,38 @@ main :: func() void {}
|
|||||||
testing.expect(t, module.functions[3].has_body)
|
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)
|
@(test)
|
||||||
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
|
parser_treats_c_as_contextual_only_before_func :: proc(t: ^testing.T) {
|
||||||
text := `c :: 5
|
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_FLOAT), types.F32)
|
||||||
testing.expect_value(t, types.representation(types.C_DOUBLE), types.F64)
|
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.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")
|
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"))
|
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)
|
@(test)
|
||||||
c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) {
|
c_structs_are_pointer_only_and_may_be_opaque :: proc(t: ^testing.T) {
|
||||||
text := `Defined :: c_struct {
|
text := `Defined :: c_struct {
|
||||||
@@ -1683,6 +1830,7 @@ fake_cimport_backend :: proc(user_data: rawptr, request: cimport.Request, alloca
|
|||||||
append(&result.functions, cimport.Function{
|
append(&result.functions, cimport.Function{
|
||||||
name=fmt.aprintf("fake_value", allocator=allocator),
|
name=fmt.aprintf("fake_value", allocator=allocator),
|
||||||
result=cimport.Type_Id(0),
|
result=cimport.Type_Id(0),
|
||||||
|
variadic=true,
|
||||||
reason=fmt.aprintf("", allocator=allocator),
|
reason=fmt.aprintf("", allocator=allocator),
|
||||||
})
|
})
|
||||||
result.available = true
|
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, state.calls, 1)
|
||||||
testing.expect_value(t, len(result.functions), 1)
|
testing.expect_value(t, len(result.functions), 1)
|
||||||
testing.expect_value(t, result.functions[0].name, "fake_value")
|
testing.expect_value(t, result.functions[0].name, "fake_value")
|
||||||
|
testing.expect(t, result.functions[0].variadic)
|
||||||
}
|
}
|
||||||
|
|
||||||
@(test)
|
@(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, len(module.imports), 2)
|
||||||
testing.expect_value(t, module.imports[0].target, module.imports[1].target)
|
testing.expect_value(t, module.imports[0].target, module.imports[1].target)
|
||||||
testing.expect_value(t, len(module.functions), 2)
|
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)
|
@(test)
|
||||||
|
|||||||
@@ -10,4 +10,11 @@ main :: func() void {
|
|||||||
_ = native.child_value(7)
|
_ = native.child_value(7)
|
||||||
_ = native.imported_read(native.imported_handle()?)
|
_ = native.imported_read(native.imported_handle()?)
|
||||||
_ = native.configured_value(9)
|
_ = 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
|
#endif
|
||||||
|
|
||||||
#define IMPORTED_MACRO 42
|
#define IMPORTED_MACRO 42
|
||||||
int imported_variadic(const char *format, ...);
|
int imported_variadic(int marker, ...);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include "include/native.h"
|
#include "include/native.h"
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
struct Imported_Handle {
|
struct Imported_Handle {
|
||||||
int value;
|
int value;
|
||||||
@@ -26,6 +28,28 @@ int imported_read(const Imported_Handle *value) {
|
|||||||
return value->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
|
#ifdef BROLANG_FEATURE
|
||||||
int configured_value(int value) {
|
int configured_value(int value) {
|
||||||
return 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
|
use_enum :: c_func(value native.Imported_Enum) void
|
||||||
|
|
||||||
main :: func() void {
|
main :: func() void {
|
||||||
_ = native.imported_variadic("value")
|
|
||||||
_ = native.IMPORTED_MACRO
|
_ = native.IMPORTED_MACRO
|
||||||
_ = native.imported_by_value()
|
_ = native.imported_by_value()
|
||||||
_ = native.imported_volatile()
|
_ = native.imported_volatile()
|
||||||
|
|||||||
Reference in New Issue
Block a user