c bool support and c-integer coercion

This commit is contained in:
2026-06-25 17:53:04 +02:00
parent c08bc6d0cc
commit e0b6f6049c
10 changed files with 80 additions and 13 deletions
+14
View File
@@ -1882,6 +1882,20 @@ coerce_expr :: proc(
}, },
) )
} }
if types.can_coerce_c_integer(actual, expected) {
return add_hir_expr(
checker,
hir.Expr {
kind = .C_Coerce,
span = span,
type = expected,
left = expr_id,
target = hir.INVALID_REF,
right = hir.INVALID_EXPR,
diagnostic = source.INVALID_DIAGNOSTIC,
},
)
}
id := source.addf( id := source.addf(
checker.diagnostics, checker.diagnostics,
span, span,
+1
View File
@@ -9,6 +9,7 @@ INVALID_TYPE :: Type_Id(0xffff_ffff)
Type_Kind :: enum u8 { Type_Kind :: enum u8 {
Invalid, Invalid,
Void, Void,
C_Bool,
C_Char, C_Char,
C_Schar, C_Schar,
C_Uchar, C_Uchar,
+2
View File
@@ -132,6 +132,7 @@ CXTLS_None :: i32(0)
CXType_Invalid :: i32(0) CXType_Invalid :: i32(0)
CXType_Unexposed :: i32(1) CXType_Unexposed :: i32(1)
CXType_Void :: i32(2) CXType_Void :: i32(2)
CXType_Bool :: i32(3)
CXType_Char_U :: i32(4) CXType_Char_U :: i32(4)
CXType_UChar :: i32(5) CXType_UChar :: i32(5)
CXType_UShort :: i32(8) CXType_UShort :: i32(8)
@@ -435,6 +436,7 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
} }
switch value.kind { switch value.kind {
case CXType_Void: return add_type(ctx, Type{kind=.Void, child=INVALID_TYPE}) case CXType_Void: return add_type(ctx, Type{kind=.Void, child=INVALID_TYPE})
case CXType_Bool: return add_type(ctx, Type{kind=.C_Bool, child=INVALID_TYPE})
case CXType_Char_U: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE}) case CXType_Char_U: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE})
case CXType_Char_S: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE}) case CXType_Char_S: return add_type(ctx, Type{kind=.C_Char, child=INVALID_TYPE})
case CXType_SChar: return add_type(ctx, Type{kind=.C_Schar, child=INVALID_TYPE}) case CXType_SChar: return add_type(ctx, Type{kind=.C_Schar, child=INVALID_TYPE})
+1
View File
@@ -95,6 +95,7 @@ Expr_Kind :: enum u8 {
Unwrap, Unwrap,
Orelse, Orelse,
Widen, Widen,
C_Coerce,
C_Vararg_Promote, C_Vararg_Promote,
Retype, Retype,
Weaken_Pointer, Weaken_Pointer,
+1
View File
@@ -92,6 +92,7 @@ Opcode :: enum u8 {
Orelse_Begin, Orelse_Begin,
Orelse, Orelse,
Widen, Widen,
C_Coerce,
C_Vararg_Promote, C_Vararg_Promote,
Retype, Retype,
Weaken_Pointer, Weaken_Pointer,
+36 -12
View File
@@ -191,6 +191,10 @@ function_result_type :: proc(function: ir.Function, store: ^types.Store) -> stri
c_abi_extension :: proc(value: types.Type, store: ^types.Store) -> string { c_abi_extension :: proc(value: types.Type, store: ^types.Store) -> string {
resolved := types.runtime_representation(value, store) resolved := types.runtime_representation(value, store)
if types.is_bool(resolved) {
// clang lowers C `_Bool` as `zeroext i1` across the ABI boundary.
return "zeroext"
}
if !types.is_concrete_integer(resolved) { if !types.is_concrete_integer(resolved) {
return "" return ""
} }
@@ -252,7 +256,7 @@ valid_value :: proc(
.Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr, .Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr,
.Extract, .Select, .Unwrap, .Extract, .Select, .Unwrap,
.Optional_Is_Some, .Optional_Value, .Orelse, .Optional_Is_Some, .Optional_Value, .Orelse,
.Widen, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer, .Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer,
.Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .Call: .Neg_Checked, .Add_Checked, .Sub_Checked, .Mul_Checked, .Div_Checked, .Pointer_Add, .Not, .Compare, .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,
@@ -297,19 +301,15 @@ write_constant :: proc(builder: ^strings.Builder, value: i64, value_type: types.
} }
selected := store.selected if store != nil else target.DEFAULT selected := store.selected if store != nil else target.DEFAULT
if types.is_float(resolved, selected) { if types.is_float(resolved, selected) {
text := "" // LLVM rejects decimal float literals that don't round-trip exactly, so
// emit the IEEE-754 double bit pattern as a hex literal (`0x...`), which
// always parses. For `float` we widen the f32 to f64 first exact, and
// LLVM requires the value be representable as float, which it is.
number := transmute(f64)value
if types.bits(resolved, selected) == 32 { if types.bits(resolved, selected) == 32 {
bits := u32(value) number = f64(transmute(f32)u32(value))
number := transmute(f32)bits
text = fmt.tprintf("%.9g", number)
} else {
number := transmute(f64)value
text = fmt.tprintf("%.17g", number)
}
strings.write_string(builder, text)
if !strings.contains(text, ".") && !strings.contains(text, "e") && !strings.contains(text, "E") {
strings.write_string(builder, ".0")
} }
fmt.sbprintf(builder, "0x%016X", transmute(u64)number)
return return
} }
fmt.sbprintf(builder, "%d", value) fmt.sbprintf(builder, "%d", value)
@@ -1198,6 +1198,30 @@ 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_Coerce:
if !valid_instruction(instructions, instruction.a) ||
!types.can_coerce_c_integer(instructions[instruction.a].type, instruction.type) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid C integer coercion operand")
continue
}
from_type := instructions[instruction.a].type
from_bits := types.bits(from_type, emitter.module.target)
to_bits := types.bits(instruction.type, emitter.module.target)
if from_bits == to_bits {
// Same-width signedness change: c_uint and c_int both lower to the
// identical `iN`, so this is a pure reinterpret (no-op `select`).
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 := "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 .C_Vararg_Promote: case .C_Vararg_Promote:
if !valid_instruction(instructions, instruction.a) { if !valid_instruction(instructions, instruction.a) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand") emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand")
+1
View File
@@ -149,6 +149,7 @@ translate_c_type :: proc(
switch item.kind { switch item.kind {
case .Invalid: translated = types.INVALID case .Invalid: translated = types.INVALID
case .Void: translated = types.VOID case .Void: translated = types.VOID
case .C_Bool: translated = types.BOOL
case .C_Char: translated = types.C_CHAR case .C_Char: translated = types.C_CHAR
case .C_Schar: translated = types.C_SCHAR case .C_Schar: translated = types.C_SCHAR
case .C_Uchar: translated = types.C_UCHAR case .C_Uchar: translated = types.C_UCHAR
+2 -1
View File
@@ -490,7 +490,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
}) })
} }
_ = pop(&stack) _ = pop(&stack)
case .Widen, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer: case .Widen, .C_Coerce, .C_Vararg_Promote, .Retype, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_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:
@@ -547,6 +547,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
case .Weaken_Pointer: op = .Weaken_Pointer case .Weaken_Pointer: op = .Weaken_Pointer
case .Weaken_Slice: op = .Weaken_Slice case .Weaken_Slice: op = .Weaken_Slice
case .Decay_Array_Pointer: op = .Decay_Array_Pointer case .Decay_Array_Pointer: op = .Decay_Array_Pointer
case .C_Coerce: op = .C_Coerce
case .C_Vararg_Promote: op = .C_Vararg_Promote case .C_Vararg_Promote: op = .C_Vararg_Promote
case .Retype: op = .Retype case .Retype: op = .Retype
case: op = .Widen case: op = .Widen
+8
View File
@@ -77,6 +77,7 @@ render_type :: proc(b: ^strings.Builder, result: ^cimport.Result, id: cimport.Ty
switch item.kind { switch item.kind {
case .Invalid: strings.write_string(b, "void") case .Invalid: strings.write_string(b, "void")
case .Void: strings.write_string(b, "void") case .Void: strings.write_string(b, "void")
case .C_Bool: strings.write_string(b, "bool")
case .C_Char: strings.write_string(b, "c_char") case .C_Char: strings.write_string(b, "c_char")
case .C_Schar: strings.write_string(b, "c_schar") case .C_Schar: strings.write_string(b, "c_schar")
case .C_Uchar: strings.write_string(b, "c_uchar") case .C_Uchar: strings.write_string(b, "c_uchar")
@@ -210,6 +211,13 @@ emit_macros :: proc(b: ^strings.Builder, result: ^cimport.Result, record_names:
wrote = true wrote = true
continue continue
} }
// A macro whose name collides with a brolang keyword (e.g. `true`/`false`
// from <stdbool.h>) can't be a binding name; emitting it is a parse error.
if lexer.keyword_kind(macro.name) != .Identifier {
fmt.sbprintf(b, "# unsupported in bindings: macro '%s' — name is a brolang keyword\n", macro.name)
wrote = true
continue
}
if macro.aggregate { if macro.aggregate {
emit_aggregate_macro(b, result, macro, record_names) emit_aggregate_macro(b, result, macro, record_names)
wrote = true wrote = true
+14
View File
@@ -1094,6 +1094,20 @@ can_widen :: proc(from, to: Type) -> bool {
bits(from) < bits(to) bits(from) < bits(to)
} }
// can_coerce_c_integer reports whether `from` may implicitly convert to `to`
// under C's integer conversion rules. Brolang keeps its own exact-width scalars
// strict (`u32 -> i32` is rejected), but C interop types deliberately follow C:
// virtually every C library relies on it e.g. an unsigned-backed enum constant
// (`c_uint`) passed to an `int` (`c_int`) parameter so disallowing it would
// make C interop cumbersome. Scope: widening (sext/zext) and same-width
// signedness changes (no-op reinterpret); narrowing is intentionally excluded so
// lossy conversions stay an error, matching brolang's trap-on-narrow philosophy.
can_coerce_c_integer :: proc(from, to: Type) -> bool {
return from != to && is_c(from) && is_c(to) &&
is_concrete_integer(from) && is_concrete_integer(to) &&
bits(from) <= bits(to)
}
widest :: proc(a, b: Type) -> Type { widest :: proc(a, b: Type) -> Type {
if equal(a, b) && is_concrete_scalar(a) { if equal(a, b) && is_concrete_scalar(a) {
return a return a