c bool support and c-integer coercion
This commit is contained in:
@@ -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(
|
||||
checker.diagnostics,
|
||||
span,
|
||||
|
||||
@@ -9,6 +9,7 @@ INVALID_TYPE :: Type_Id(0xffff_ffff)
|
||||
Type_Kind :: enum u8 {
|
||||
Invalid,
|
||||
Void,
|
||||
C_Bool,
|
||||
C_Char,
|
||||
C_Schar,
|
||||
C_Uchar,
|
||||
|
||||
@@ -132,6 +132,7 @@ CXTLS_None :: i32(0)
|
||||
CXType_Invalid :: i32(0)
|
||||
CXType_Unexposed :: i32(1)
|
||||
CXType_Void :: i32(2)
|
||||
CXType_Bool :: i32(3)
|
||||
CXType_Char_U :: i32(4)
|
||||
CXType_UChar :: i32(5)
|
||||
CXType_UShort :: i32(8)
|
||||
@@ -435,6 +436,7 @@ translate_type :: proc(ctx: ^Context, value: CXType, preferred_record_name := ""
|
||||
}
|
||||
switch value.kind {
|
||||
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_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})
|
||||
|
||||
@@ -95,6 +95,7 @@ Expr_Kind :: enum u8 {
|
||||
Unwrap,
|
||||
Orelse,
|
||||
Widen,
|
||||
C_Coerce,
|
||||
C_Vararg_Promote,
|
||||
Retype,
|
||||
Weaken_Pointer,
|
||||
|
||||
@@ -92,6 +92,7 @@ Opcode :: enum u8 {
|
||||
Orelse_Begin,
|
||||
Orelse,
|
||||
Widen,
|
||||
C_Coerce,
|
||||
C_Vararg_Promote,
|
||||
Retype,
|
||||
Weaken_Pointer,
|
||||
|
||||
+36
-12
@@ -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 {
|
||||
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) {
|
||||
return ""
|
||||
}
|
||||
@@ -252,7 +256,7 @@ valid_value :: proc(
|
||||
.Load_Global, .Function_Address, .Address_Of, .Load, .Slice, .Length, .Slice_Ptr,
|
||||
.Extract, .Select, .Unwrap,
|
||||
.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:
|
||||
return true
|
||||
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
|
||||
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 {
|
||||
bits := 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")
|
||||
number = f64(transmute(f32)u32(value))
|
||||
}
|
||||
fmt.sbprintf(builder, "0x%016X", transmute(u64)number)
|
||||
return
|
||||
}
|
||||
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))
|
||||
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_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:
|
||||
if !valid_instruction(instructions, instruction.a) {
|
||||
emit_recovery_value(emitter, instruction_index, instruction, "invalid C variadic promotion operand")
|
||||
|
||||
@@ -149,6 +149,7 @@ translate_c_type :: proc(
|
||||
switch item.kind {
|
||||
case .Invalid: translated = types.INVALID
|
||||
case .Void: translated = types.VOID
|
||||
case .C_Bool: translated = types.BOOL
|
||||
case .C_Char: translated = types.C_CHAR
|
||||
case .C_Schar: translated = types.C_SCHAR
|
||||
case .C_Uchar: translated = types.C_UCHAR
|
||||
|
||||
@@ -490,7 +490,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
})
|
||||
}
|
||||
_ = 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
|
||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||
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_Slice: op = .Weaken_Slice
|
||||
case .Decay_Array_Pointer: op = .Decay_Array_Pointer
|
||||
case .C_Coerce: op = .C_Coerce
|
||||
case .C_Vararg_Promote: op = .C_Vararg_Promote
|
||||
case .Retype: op = .Retype
|
||||
case: op = .Widen
|
||||
|
||||
@@ -77,6 +77,7 @@ render_type :: proc(b: ^strings.Builder, result: ^cimport.Result, id: cimport.Ty
|
||||
switch item.kind {
|
||||
case .Invalid: 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_Schar: strings.write_string(b, "c_schar")
|
||||
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
|
||||
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 {
|
||||
emit_aggregate_macro(b, result, macro, record_names)
|
||||
wrote = true
|
||||
|
||||
@@ -1094,6 +1094,20 @@ can_widen :: proc(from, to: Type) -> bool {
|
||||
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 {
|
||||
if equal(a, b) && is_concrete_scalar(a) {
|
||||
return a
|
||||
|
||||
Reference in New Issue
Block a user