diff --git a/LANGUAGE.md b/LANGUAGE.md index c2ed08d..97aeebd 100644 --- a/LANGUAGE.md +++ b/LANGUAGE.md @@ -17,11 +17,15 @@ - C primitives remain semantically distinct from exact-width Brolang primitives until target lowering - contextual integer literals and constant folding of addition and negation trees - strict numeric conversions, checked integer addition, and unary negation -- arrays `[N]T`, sentinel arrays `[N;S]T`, indexing, pointers, pointer offsets, slices, and explicit slicing -- immutable UTF-8 sentinel-slice strings and Unicode code-point character literals +- arrays `[N]T`, sentinel arrays `[N;S]T`, single-item pointers `@T`, many-item pointers `*T`, sentinel many-item pointers `[*;S]T`, pointer offsets, slices, and explicit slicing +- immutable UTF-8 string literals typed as pointers to static sentinel arrays: `@[N;0]u8` +- pointer-to-array `.len`, indexing, and slicing without explicit dereference +- arrays expose `.len` but not `.ptr`; slices and pointers-to-arrays expose `.ptr` and preserve sentinel information when available +- information-preserving and information-forgetting pointer-to-array decay and sentinel slice/pointer weakening; array values never implicitly decay +- narrow immutable zero-terminated byte pointer conversion to `*c_char` and `[*;0]c_char`, without general `u8`/`c_char` interchange - optionals with trapping postfix `?`, `orelse`, and nullable pointer representation - source-order native structs, keyed literals, and defined or opaque pointer-only `c_struct` -- postfix pointer dereference, explicit `.ptr`/`.len`, general writable locations, function calls, assignments, and returns +- postfix pointer dereference, general writable locations, function calls, assignments, and returns ### functions and packages diff --git a/README.md b/README.md index b028890..0fcb783 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,17 @@ Bodyless manual and imported C functions may be variadic: log_values :: c_func(tag c_int, ...) c_int ``` +Zero-terminated byte strings can be passed directly to immutable C character +pointers without making `u8` and `c_char` generally interchangeable: + +```bro +printf :: c_func(format *c_char, ...) c_int + +main :: func() void { + _ = printf("answer: %d\n", 42) +} +``` + 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 @@ -87,8 +98,9 @@ Current prototype features: - Immutable `::` bindings, mutable function-local `=` bindings, and `_` sinks - Exact-width signed/unsigned integers, `f32`, `f64`, `isize`, `usize`, and loose integer-constrained `int` - Target-dependent atomic `c_*` primitive types, `c_func`, and pointer-only `c_struct` -- Arrays, sentinel arrays, pointers, slices, sentinel slices, strings, character literals, optionals, and native structs -- Explicit `.ptr`/`.len`, slicing, postfix pointer dereference and optional unwrap, and keyed struct literals +- Arrays, sentinel arrays, single-item pointers, many-item pointers, sentinel many-item pointers, slices, sentinel slices, strings, character literals, optionals, and native structs +- String literals as immutable pointers to static zero-terminated byte arrays +- Pointer-preserving `.ptr`/`.len`, pointer-to-array indexing and slicing, postfix pointer dereference and optional unwrap, and keyed struct literals - Contextual integer constants and compile-time folding of addition and unary negation trees - Directory packages with merged declarations and file-local relative imports - Relative C header imports as synthetic package namespaces diff --git a/TODO.md b/TODO.md index 197af41..954af59 100644 --- a/TODO.md +++ b/TODO.md @@ -20,7 +20,7 @@ - `[]T`: pointer and length - `[;S]T`: pointer and length with a sentinel invariant - ordinary slices do not guarantee null termination - - string literals as immutable sentinel slices backed by static arrays + - string literals as immutable sentinel slices backed by static arrays (superseded by milestone 3.5) - character literals - optionals with trapping unwrap and fallback operations - native structs with compiler-controlled layout @@ -44,6 +44,16 @@ - emit LLVM c-variadic declarations and calls - keep native brolang variadics and tuple design separate +3.5. sentinel pointers and c strings (implemented) + - add sentinel many-item pointers: `[*;S]T` + - represent string literals as immutable pointers to statically stored sentinel arrays: `@[N;0]u8` + - arrays expose `.len` but no `.ptr`; slices and pointers-to-arrays expose sentinel-preserving `.ptr` + - allow pointer-to-array `.len`, indexing, slicing, pointer decay, and slice construction without explicit dereference + - preserve or forget sentinel information through compatible pointer and slice coercions without copying arrays + - allow zero-terminated immutable byte pointer views to convert to immutable `*c_char` and `[*;0]c_char` + - keep `u8` and `c_char` distinct to preserve target-dependent scalar c semantics + - reject general `u8`/`c_char` interchange, slice-to-pointer coercion, and conversion to mutable c character pointers + 4. advanced c interop - by-value records and unions - function pointers and callbacks diff --git a/compiler/checker/checker.odin b/compiler/checker/checker.odin index 99c4cb8..facf6f9 100644 --- a/compiler/checker/checker.odin +++ b/compiler/checker/checker.odin @@ -221,6 +221,19 @@ is_runtime_type :: proc(checker: ^Checker, value: types.Type) -> bool { return types.is_runtime_value(value, &checker.module.types) } +string_literal_type :: proc(checker: ^Checker, string_id: u64) -> types.Type { + length: u64 + if string_id < u64(len(checker.ast_module.strings)) { + length = u64(len(checker.ast_module.strings[string_id])) + } + array := types.array(&checker.module.types, types.U8, length, false, true, 0) + return types.pointer(&checker.module.types, array, false, false) +} + +container_pointer_type :: proc(store: ^types.Store, item: types.Node) -> types.Type { + return types.pointer(store, item.child, item.mutable, true, item.has_sentinel, item.sentinel) +} + resolve_inferred_array :: proc(checker: ^Checker, value: types.Type, expr_id: ast.Expr_Id) -> types.Type { item, ok := types.node(&checker.module.types, value) if !ok || item.kind != .Array || !item.inferred_count || @@ -665,7 +678,7 @@ validate_type_nodes :: proc(checker: ^Checker) { source.addf( checker.diagnostics, source.Span{}, - "sentinel value does not fit array or slice element type %s", + "sentinel value does not fit array, slice, or pointer element type %s", types.name(item.child), ) } @@ -809,7 +822,7 @@ infer_compound_expr :: proc( store := &checker.module.types #partial switch expr.kind { case .String: - return types.slice(store, types.U8, false, true, 0) + return string_literal_type(checker, expr.integer) case .Array: element := types.INVALID for arg in expr.args { @@ -835,11 +848,12 @@ infer_compound_expr :: proc( case .Index: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) _ = infer_nested_expr(checker, expr.right, locals, pkg, file, demanded) - return types.child_type(value, store) + item, ok := types.container(value, store) + return item.child if ok else types.INVALID case .Slice: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) - item, ok := types.node(store, value) - if !ok || (item.kind != .Array && item.kind != .Slice) { + item, ok := types.container(value, store) + if !ok || item.kind == .Pointer { return types.INVALID } for bound in expr.args { @@ -851,14 +865,15 @@ infer_compound_expr :: proc( return types.slice(store, item.child, item.mutable, preserve, item.sentinel) case .Field: value := infer_nested_expr(checker, expr.left, locals, pkg, file, demanded) - item, has_item := types.node(store, value) field_name := symbol_text(checker, expr.name) + item, has_item := types.container(value, store) if has_item && (item.kind == .Array || item.kind == .Slice) { if field_name == "len" { return types.USIZE } - if field_name == "ptr" { - return types.pointer(store, item.child, item.mutable, true) + if field_name == "ptr" && + (item.kind == .Slice || types.is_pointer(value, store)) { + return container_pointer_type(store, item) } } if types.is_pointer(value, store) { @@ -950,13 +965,14 @@ infer_expr :: proc( last = find_infer_local(locals, expr.name) } else { base_type := find_infer_local(locals, expr.qualifier) - item, has_item := types.node(&checker.module.types, base_type) + item, has_item := types.container(base_type, &checker.module.types) field_name := symbol_text(checker, expr.name) if has_item && (item.kind == .Array || item.kind == .Slice) { if field_name == "len" { last = types.USIZE - } else if field_name == "ptr" { - last = types.pointer(&checker.module.types, item.child, item.mutable, true) + } else if field_name == "ptr" && + (item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) { + last = container_pointer_type(&checker.module.types, item) } } if types.is_pointer(base_type, &checker.module.types) { @@ -1296,11 +1312,35 @@ coerce_expr :: proc( diagnostic=source.INVALID_DIAGNOSTIC, }) } + if types.can_weaken_slice(actual, expected, &checker.module.types) { + return add_hir_expr(checker, hir.Expr{ + kind=.Weaken_Slice, + span=span, + type=expected, + left=expr_id, + target=hir.INVALID_REF, + right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } + if types.can_decay_array_pointer(actual, expected, &checker.module.types) { + return add_hir_expr(checker, hir.Expr{ + kind=.Decay_Array_Pointer, + span=span, + type=expected, + left=expr_id, + target=hir.INVALID_REF, + right=hir.INVALID_EXPR, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } if types.is_optional(expected, &checker.module.types) { child := types.child_type(expected, &checker.module.types) if types.equal(actual, child) || types.can_widen(actual, child) || - types.can_weaken_pointer(actual, child, &checker.module.types) { + types.can_weaken_pointer(actual, child, &checker.module.types) || + types.can_weaken_slice(actual, child, &checker.module.types) || + types.can_decay_array_pointer(actual, child, &checker.module.types) { value := coerce_expr(checker, expr_id, child, span) return add_hir_expr(checker, hir.Expr{ kind=.Optional_Some, @@ -1475,7 +1515,8 @@ hir_location_writable :: proc(checker: ^Checker, expr_id: hir.Expr_Id, locals: [ return types.is_mutable(pointer_type, &checker.module.types) case .Index: container_type := checker.module.exprs[expr.left].type - return types.is_mutable(container_type, &checker.module.types) + item, ok := types.container(container_type, &checker.module.types) + return ok && item.mutable case .Field: base_type := checker.module.exprs[expr.left].type if types.is_pointer(base_type, &checker.module.types) { @@ -1539,7 +1580,7 @@ build_compound_expr :: proc( store := &checker.module.types #partial switch expr.kind { case .String: - string_type := types.slice(store, types.U8, false, true, 0) + string_type := string_literal_type(checker, expr.integer) return add_hir_expr(checker, hir.Expr{ kind=.String, span=expr.span, type=string_type, integer=i64(expr.integer), target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, @@ -1639,9 +1680,9 @@ build_compound_expr :: proc( container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) index := build_nested_expr(checker, expr.right, locals, global_reads, calls, types.USIZE, pkg, file) container_type := checker.module.exprs[container].type - item, ok := types.node(store, container_type) - if !ok || (item.kind != .Array && item.kind != .Slice && !(item.kind == .Pointer && item.many)) { - id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, or many-item pointer") + item, ok := types.container(container_type, store) + if !ok { + id := source.add(checker.diagnostics, expr.span, "indexing requires an array, slice, pointer-to-array, or many-item pointer") return invalid_hir_expr(checker, expr.span, id) } return add_hir_expr(checker, hir.Expr{ @@ -1651,9 +1692,9 @@ build_compound_expr :: proc( case .Slice: container := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) container_type := checker.module.exprs[container].type - item, ok := types.node(store, container_type) - if !ok || (item.kind != .Array && item.kind != .Slice) { - id := source.add(checker.diagnostics, expr.span, "slicing requires an array or slice") + item, ok := types.container(container_type, store) + if !ok || item.kind == .Pointer { + id := source.add(checker.diagnostics, expr.span, "slicing requires an array, slice, or pointer-to-array") return invalid_hir_expr(checker, expr.span, id) } bounds := make([]hir.Expr_Id, 2, checker.allocator) @@ -1673,8 +1714,8 @@ build_compound_expr :: proc( case .Field: base := build_nested_expr(checker, expr.left, locals, global_reads, calls, types.INVALID, pkg, file) base_type := checker.module.exprs[base].type - item, has_item := types.node(store, base_type) field_name := symbol_text(checker, expr.name) + item, has_item := types.container(base_type, store) if has_item && (item.kind == .Array || item.kind == .Slice) { if field_name == "len" { return add_hir_expr(checker, hir.Expr{ @@ -1682,13 +1723,18 @@ build_compound_expr :: proc( target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } - if field_name == "ptr" { + if field_name == "ptr" && + (item.kind == .Slice || types.is_pointer(base_type, store)) { return add_hir_expr(checker, hir.Expr{ kind=.Slice_Ptr, span=expr.span, - type=types.pointer(store, item.child, item.mutable, true), left=base, + type=container_pointer_type(store, item), left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) } + if field_name == "ptr" && item.kind == .Array { + id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first") + return invalid_hir_expr(checker, expr.span, id) + } } if types.is_pointer(base_type, store) { base_type = types.child_type(base_type, store) @@ -1848,7 +1894,7 @@ build_expr :: proc( left=hir.INVALID_EXPR, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) base_type := local.type - item, has_item := types.node(&checker.module.types, base_type) + item, has_item := types.container(base_type, &checker.module.types) field_name := symbol_text(checker, expr.name) if has_item && (item.kind == .Array || item.kind == .Slice) { if field_name == "len" { @@ -1856,12 +1902,16 @@ build_expr :: proc( kind=.Length, span=expr.span, type=types.USIZE, left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) - } else if field_name == "ptr" { + } else if field_name == "ptr" && + (item.kind == .Slice || types.is_pointer(base_type, &checker.module.types)) { last = add_hir_expr(checker, hir.Expr{ kind=.Slice_Ptr, span=expr.span, - type=types.pointer(&checker.module.types, item.child, item.mutable, true), left=base, + type=container_pointer_type(&checker.module.types, item), left=base, target=hir.INVALID_REF, right=hir.INVALID_EXPR, diagnostic=source.INVALID_DIAGNOSTIC, }) + } else if field_name == "ptr" && item.kind == .Array { + id := source.add(checker.diagnostics, expr.span, "arrays do not expose '.ptr'; take their address first") + last = invalid_hir_expr(checker, expr.span, id) } } if types.is_pointer(base_type, &checker.module.types) { diff --git a/compiler/hir/hir.odin b/compiler/hir/hir.odin index 4f34099..d135aa4 100644 --- a/compiler/hir/hir.odin +++ b/compiler/hir/hir.odin @@ -95,6 +95,8 @@ Expr_Kind :: enum u8 { Widen, C_Vararg_Promote, Weaken_Pointer, + Weaken_Slice, + Decay_Array_Pointer, Negate, Add, Pointer_Add, diff --git a/compiler/ir/ir.odin b/compiler/ir/ir.odin index 2bf49fe..30c7431 100644 --- a/compiler/ir/ir.odin +++ b/compiler/ir/ir.odin @@ -89,6 +89,8 @@ Opcode :: enum u8 { Widen, C_Vararg_Promote, Weaken_Pointer, + Weaken_Slice, + Decay_Array_Pointer, Neg_Checked, Add_Checked, Pointer_Add, diff --git a/compiler/llvm/llvm.odin b/compiler/llvm/llvm.odin index edeffb0..3289d11 100644 --- a/compiler/llvm/llvm.odin +++ b/compiler/llvm/llvm.odin @@ -112,7 +112,8 @@ 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, .C_Vararg_Promote, .Weaken_Pointer, .Neg_Checked, .Add_Checked, .Pointer_Add, .Call: + .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_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: @@ -306,21 +307,17 @@ emit_instruction_stream :: proc( case .Param, .Const: case .String: string_id := int(instruction.integer) + _, array, pointer_ok := types.array_pointer(instruction.type, &emitter.module.types) if string_id < 0 || string_id >= len(emitter.module.strings) || - !types.is_slice(instruction.type, &emitter.module.types) { + !pointer_ok || array.child != types.U8 || !array.has_sentinel || + array.sentinel != 0 || array.count != u64(len(emitter.module.strings[string_id])) { emit_recovery_value(emitter, instruction_index, instruction, "invalid string literal") continue } - type_name := llvm_type(instruction.type, &emitter.module.types) fmt.sbprintf( &emitter.builder, - " %%string_ptr%d = insertvalue %s poison, ptr @bro.str.%d, 0\n", - instruction_index, type_name, string_id, - ) - fmt.sbprintf( - &emitter.builder, - " %%v%d = insertvalue %s %%string_ptr%d, i64 %d, 1\n", - instruction_index, type_name, instruction_index, len(emitter.module.strings[string_id]), + " %%v%d = getelementptr %s, ptr @bro.str.%d, i64 0\n", + instruction_index, llvm_type(types.child_type(instruction.type, &emitter.module.types), &emitter.module.types), string_id, ) case .Aggregate: item, ok := types.node(&emitter.module.types, instruction.type) @@ -465,7 +462,7 @@ emit_instruction_stream :: proc( continue } container := instructions[instruction.a] - container_node, container_ok := types.node(&emitter.module.types, container.type) + container_node, container_ok := types.container(container.type, &emitter.module.types) if !container_ok { emit_recovery_value(emitter, instruction_index, instruction, "invalid index container") continue @@ -504,7 +501,13 @@ emit_instruction_stream :: proc( fmt.sbprintf(&emitter.builder, " unreachable\nindex_continue%d:\n", instruction_index) } if container_node.kind == .Array { - fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(container.type, &emitter.module.types), pointer_name) + array_type := container.type + _, array, array_pointer_ok := types.array_pointer(container.type, &emitter.module.types) + if array_pointer_ok { + array_type = types.child_type(container.type, &emitter.module.types) + container_node = array + } + fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 0, i64 ", instruction_index, llvm_type(array_type, &emitter.module.types), pointer_name) } else { fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %s, i64 ", instruction_index, llvm_type(instruction.type, &emitter.module.types), pointer_name) } @@ -558,7 +561,7 @@ emit_instruction_stream :: proc( continue } container := instructions[instruction.a] - item, ok := types.node(&emitter.module.types, container.type) + item, ok := types.container(container.type, &emitter.module.types) if !ok || (item.kind != .Array && item.kind != .Slice) { emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container") continue @@ -566,7 +569,11 @@ emit_instruction_stream :: proc( pointer_name := fmt.tprintf("%%v%d", instruction.a) length_name := fmt.tprintf("%d", item.count) if item.kind == .Array { - fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) + array_type := container.type + if types.is_pointer(container.type, &emitter.module.types) { + array_type = types.child_type(container.type, &emitter.module.types) + } + fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a) pointer_name = fmt.tprintf("%%slice_ptr%d", instruction_index) } else if item.kind == .Slice { fmt.sbprintf(&emitter.builder, " %%slice_ptr%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(container.type, &emitter.module.types), instruction.a) @@ -621,12 +628,21 @@ emit_instruction_stream :: proc( continue } container_type := instructions[instruction.a].type + _, _, array_pointer_ok := types.array_pointer(container_type, &emitter.module.types) if types.is_array(container_type, &emitter.module.types) { fmt.sbprintf( &emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(container_type, &emitter.module.types), instruction.a, ) + } else if array_pointer_ok { + fmt.sbprintf( + &emitter.builder, + " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", + instruction_index, + llvm_type(types.child_type(container_type, &emitter.module.types), &emitter.module.types), + instruction.a, + ) } else if types.is_slice(container_type, &emitter.module.types) { fmt.sbprintf( &emitter.builder, @@ -741,6 +757,30 @@ emit_instruction_stream :: proc( continue } fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, ptr %%v%d, ptr null\n", instruction_index, instruction.a) + case .Weaken_Slice: + if !valid_instruction(instructions, instruction.a) || + !types.can_weaken_slice(instructions[instruction.a].type, instruction.type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid slice weakening operand") + continue + } + type_name := llvm_type(instruction.type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, " %%v%d = select i1 true, %s %%v%d, %s zeroinitializer\n", instruction_index, type_name, instruction.a, type_name) + case .Decay_Array_Pointer: + if !valid_instruction(instructions, instruction.a) || + !types.can_decay_array_pointer(instructions[instruction.a].type, instruction.type, &emitter.module.types) { + emit_recovery_value(emitter, instruction_index, instruction, "invalid array pointer decay operand") + continue + } + array_type := types.child_type(instructions[instruction.a].type, &emitter.module.types) + array, _ := types.node(&emitter.module.types, array_type) + if types.is_pointer(instruction.type, &emitter.module.types) { + fmt.sbprintf(&emitter.builder, " %%v%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a) + } else { + type_name := llvm_type(instruction.type, &emitter.module.types) + fmt.sbprintf(&emitter.builder, " %%decay_ptr%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(array_type, &emitter.module.types), instruction.a) + fmt.sbprintf(&emitter.builder, " %%decay_slice%d = insertvalue %s poison, ptr %%decay_ptr%d, 0\n", instruction_index, type_name, instruction_index) + fmt.sbprintf(&emitter.builder, " %%v%d = insertvalue %s %%decay_slice%d, i64 %d, 1\n", instruction_index, type_name, instruction_index, array.count) + } case .Neg_Checked: if !valid_value(instructions, instruction.a, instruction.type, &emitter.module.types) { emit_recovery_value(emitter, instruction_index, instruction, "invalid negation operand") diff --git a/compiler/lower/lower.odin b/compiler/lower/lower.odin index 029cee5..2dd315d 100644 --- a/compiler/lower/lower.odin +++ b/compiler/lower/lower.odin @@ -227,6 +227,14 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi diagnostic=source.INVALID_DIAGNOSTIC, }) } + _, array, array_ok := types.array_pointer(container_type, &state.hir_module.types) + if array_ok { + return append_instruction(state, ir.Instruction{ + op=.Const, span=expr.span, type=types.USIZE, integer=i64(array.count), + target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, + diagnostic=source.INVALID_DIAGNOSTIC, + }) + } value := lower_nested_expr(state, expr.left) return append_instruction(state, ir.Instruction{ op=.Length, span=expr.span, type=types.USIZE, @@ -324,7 +332,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { }) } _ = pop(&stack) - case .Widen, .C_Vararg_Promote, .Weaken_Pointer: + case .Widen, .C_Vararg_Promote, .Weaken_Pointer, .Weaken_Slice, .Decay_Array_Pointer: stack[frame_index].stage = 1 append(&stack, Lower_Expr_Frame{expr=expr.left}) case .Negate: @@ -357,9 +365,16 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id { continue } if frame.stage == 1 { + op := ir.Opcode.Widen + #partial switch expr.kind { + case .Weaken_Pointer: op = .Weaken_Pointer + case .Weaken_Slice: op = .Weaken_Slice + case .Decay_Array_Pointer: op = .Decay_Array_Pointer + case .C_Vararg_Promote: op = .C_Vararg_Promote + case: op = .Widen + } last = append_instruction(state, ir.Instruction{ - op=.Weaken_Pointer if expr.kind == .Weaken_Pointer else - (.C_Vararg_Promote if expr.kind == .C_Vararg_Promote else .Widen), + op=op, span=expr.span, type=expr.type, target=ir.INVALID_REF, a=last, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC, }) diff --git a/compiler/parser/parser.odin b/compiler/parser/parser.odin index 526a3dc..132963a 100644 --- a/compiler/parser/parser.odin +++ b/compiler/parser/parser.odin @@ -166,6 +166,17 @@ parse_type :: proc(parser: ^Parser) -> ast.Type_Syntax { node := types.Node{} if _, ok := allow(parser, .Right_Bracket); ok { node.kind = .Slice + } else if _, ok := allow(parser, .Star); ok { + node.kind = .Pointer + node.many = true + node.has_sentinel = true + if _, ok = allow(parser, .Semicolon); !ok { + source.add(parser.diagnostics, current(parser).span, "expected ';' after '*' in sentinel pointer type") + } + node.sentinel, _ = parse_type_constant(parser) + if _, ok = allow(parser, .Right_Bracket); !ok { + source.add(parser.diagnostics, current(parser).span, "expected ']' after sentinel pointer type") + } } else if _, ok := allow(parser, .Semicolon); ok { node.kind = .Slice node.has_sentinel = true diff --git a/compiler/types/types.odin b/compiler/types/types.odin index 8ea72ff..b52da9a 100644 --- a/compiler/types/types.odin +++ b/compiler/types/types.odin @@ -488,13 +488,56 @@ is_many_pointer :: proc(value: Type, store: ^Store) -> bool { return ok && item.kind == .Pointer && item.many } +array_pointer :: proc(value: Type, store: ^Store) -> (pointer_item, array_item: Node, ok: bool) { + pointer_ok: bool + pointer_item, pointer_ok = node(store, value) + if !pointer_ok || pointer_item.kind != .Pointer || pointer_item.many { + return {}, {}, false + } + array_ok: bool + array_item, array_ok = node(store, pointer_item.child) + if !array_ok || array_item.kind != .Array { + return {}, {}, false + } + return pointer_item, array_item, true +} + +container :: proc(value: Type, store: ^Store) -> (Node, bool) { + item, ok := node(store, value) + if !ok { + return {}, false + } + if item.kind == .Array || item.kind == .Slice || (item.kind == .Pointer && item.many) { + return item, true + } + pointer_node, array_node, array_ok := array_pointer(value, store) + if !array_ok { + return {}, false + } + array_node.mutable = pointer_node.mutable && array_node.mutable + return array_node, true +} + is_c_struct :: proc(value: Type, store: ^Store) -> bool { item, ok := node(store, value) return ok && item.kind == .Struct && item.c_layout } -pointer :: proc(store: ^Store, child: Type, mutable, many: bool) -> Type { - return intern(store, Node{kind=.Pointer, child=child, mutable=mutable, many=many}) +pointer :: proc( + store: ^Store, + child: Type, + mutable, many: bool, + has_sentinel := false, + sentinel: u64 = 0, +) -> Type { + return intern(store, Node{ + kind=.Pointer, + child=child, + mutable=mutable, + many=many, + has_sentinel=has_sentinel, + sentinel=sentinel, + }) } slice :: proc(store: ^Store, child: Type, mutable: bool, has_sentinel := false, sentinel: u64 = 0) -> Type { @@ -540,12 +583,57 @@ with_array_count :: proc(store: ^Store, value: Type, count: u64) -> Type { } can_weaken_pointer :: proc(from, to: Type, store: ^Store) -> bool { + from_node, from_ok := node(store, from) + to_node, to_ok := node(store, to) + if !from_ok || !to_ok || from_node.kind != .Pointer || to_node.kind != .Pointer || + from_node.many != to_node.many || (to_node.mutable && !from_node.mutable) { + return false + } + if to_node.has_sentinel && + (!from_node.has_sentinel || from_node.sentinel != to_node.sentinel) { + return false + } + same_child := from_node.child == to_node.child + c_string := from_node.many && from_node.child == U8 && to_node.child == C_CHAR && + from_node.has_sentinel && from_node.sentinel == 0 && !to_node.mutable + return same_child || c_string +} + +can_weaken_slice :: proc(from, to: Type, store: ^Store) -> bool { from_node, from_ok := node(store, from) to_node, to_ok := node(store, to) return from_ok && to_ok && - from_node.kind == .Pointer && to_node.kind == .Pointer && - from_node.child == to_node.child && from_node.many == to_node.many && - from_node.mutable && !to_node.mutable + from_node.kind == .Slice && to_node.kind == .Slice && + from_node.child == to_node.child && + (!to_node.mutable || from_node.mutable) && + (!to_node.has_sentinel || + (from_node.has_sentinel && from_node.sentinel == to_node.sentinel)) +} + +can_decay_array_pointer :: proc(from, to: Type, store: ^Store) -> bool { + from_pointer, array, from_ok := array_pointer(from, store) + to_node, to_ok := node(store, to) + if !from_ok || !to_ok { + return false + } + mutable := from_pointer.mutable && array.mutable + if to_node.mutable && !mutable { + return false + } + if to_node.has_sentinel && + (!array.has_sentinel || array.sentinel != to_node.sentinel) { + return false + } + if to_node.kind == .Slice { + return array.child == to_node.child + } + if to_node.kind != .Pointer || !to_node.many { + return false + } + same_child := array.child == to_node.child + c_string := array.child == U8 && to_node.child == C_CHAR && + array.has_sentinel && array.sentinel == 0 && !to_node.mutable + return same_child || c_string } is_opaque_struct :: proc(value: Type, store: ^Store) -> bool { diff --git a/compiler_tests.odin b/compiler_tests.odin index 71293c3..9de68ff 100644 --- a/compiler_tests.odin +++ b/compiler_tests.odin @@ -174,6 +174,58 @@ main :: func() void {} testing.expect_value(t, len(module.functions[0].params), 2) } +@(test) +parser_accepts_sentinel_many_item_pointer_types :: proc(t: ^testing.T) { + text := `zero :: func(value [*;0]u8) void {} +newline :: func(value [*;'\n']mut u8) void {} +nullable :: func(value ?[*;0]u8) void {} +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) + + zero, zero_ok := types.node(&module.type_store, module.functions[0].params[0].type) + newline, newline_ok := types.node(&module.type_store, module.functions[1].params[0].type) + nullable, nullable_ok := types.node(&module.type_store, module.functions[2].params[0].type) + nullable_child, nullable_child_ok := types.node(&module.type_store, nullable.child) + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect(t, zero_ok && zero.kind == .Pointer && zero.many && zero.has_sentinel && zero.sentinel == 0) + testing.expect(t, newline_ok && newline.kind == .Pointer && newline.many && newline.mutable && + newline.has_sentinel && newline.sentinel == '\n') + testing.expect(t, nullable_ok && nullable.kind == .Optional) + testing.expect(t, nullable_child_ok && nullable_child.kind == .Pointer && + nullable_child.many && nullable_child.has_sentinel) +} + +@(test) +parser_diagnoses_malformed_sentinel_pointer_types :: proc(t: ^testing.T) { + text := `bad :: func(value [*0]u8) void {} +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 := false + for diagnostic in diagnostics.items { + found = found || strings.contains(diagnostic.message, "expected ';' after '*' in sentinel pointer type") + } + testing.expect(t, found) +} + @(test) parser_accepts_single_statement_one_line_functions :: proc(t: ^testing.T) { text := `give :: func() i8 { return 7 } @@ -742,7 +794,7 @@ main :: func() void { maybe ?i32 = 5 _ = c _ = values[2] - _ = values.ptr + 1 + _ = (&values).ptr + 1 _ = values.len _ = values[0..2] _ = "hello".ptr @@ -784,6 +836,134 @@ main :: func() void { testing.expect(t, strings.contains(llvm_text, "orelse_some")) } +@(test) +string_literals_preserve_static_length_and_sentinel_through_pointer_views :: proc(t: ^testing.T) { + text := `take_sentinel_pointer :: func(value [*;0]u8) void {} +take_mut_sentinel_pointer :: func(value [*;0]mut u8) void {} +take_pointer :: func(value *u8) void {} +take_sentinel_slice :: func(value [;0]u8) void {} +take_mut_sentinel_slice :: func(value [;0]mut u8) void {} +take_slice :: func(value []u8) void {} +take_c_string :: c_func(value *c_char) c_int +take_c_sentinel :: c_func(value [*;0]c_char) c_int +main :: func() void { + text :: "hello" + values [2;0]mut u8 = [1, 2] + pointer :: &values + _ = text.len + _ = text.ptr + _ = text[0] + _ = text[1..] + _ = pointer.len + _ = pointer.ptr + _ = pointer[0] + _ = pointer[1..] + offset [*;0]u8 :: text.ptr + 1 + suffix [*;0]u8 :: text[1..].ptr + middle []u8 :: text[1..3] + _ = offset + _ = suffix + _ = middle + take_sentinel_pointer(text) + take_pointer(text) + take_sentinel_slice(text) + take_slice(text) + take_mut_sentinel_pointer(pointer) + take_mut_sentinel_slice(pointer) + take_sentinel_pointer(pointer) + take_sentinel_slice(pointer) + _ = take_c_string(text) + _ = take_c_sentinel(text) + _ = take_c_string(text.ptr) + _ = take_c_sentinel(text.ptr) +} +` + 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) + + string_type := types.INVALID + decays := 0 + for expr in hir_module.exprs { + if expr.kind == .String { + string_type = expr.type + } + if expr.kind == .Decay_Array_Pointer { + decays += 1 + } + } + pointer, array, string_ok := types.array_pointer(string_type, &hir_module.types) + testing.expect_value(t, len(diagnostics.items), 0) + testing.expect(t, string_ok && !pointer.mutable && array.child == types.U8 && + array.count == 5 && array.has_sentinel && array.sentinel == 0) + testing.expect(t, decays >= 6) + testing.expect(t, strings.contains(llvm_text, "@bro.str.0 = private unnamed_addr constant [6 x i8] c\"hello\\00\"")) + testing.expect(t, strings.contains(llvm_text, "declare i32 @take_c_string(ptr)")) + testing.expect(t, strings.contains(llvm_text, "declare i32 @take_c_sentinel(ptr)")) +} + +@(test) +array_pointer_and_c_string_coercion_restrictions_are_diagnosed :: proc(t: ^testing.T) { + text := `take_c_string :: c_func(value *c_char) c_int +take_mut_c_string :: c_func(value *mut c_char) c_int +take_pointer :: func(value *u8) void {} +take_mut_pointer :: func(value *mut u8) void {} +take_slice :: func(value []u8) void {} +bad_sentinel :: func(value [*;256]u8) void {} +main :: func() void { + values [1;0]mut u8 = [1] + _ = values.ptr + take_pointer(values) + take_slice(values) + ordinary *u8 :: "hello" + nonzero [1;'\n']mut u8 = [1] + take_pointer("hello"[1..]) + _ = take_c_string(ordinary) + _ = take_c_string((&nonzero).ptr) + _ = take_c_string(1) + take_mut_pointer("hello") + _ = take_mut_c_string("hello") +} +` + 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) + + conversion_errors := 0 + array_ptr_error := false + sentinel_error := false + for diagnostic in diagnostics.items { + conversion_errors += 1 if strings.contains(diagnostic.message, "cannot implicitly convert") else 0 + array_ptr_error = array_ptr_error || + strings.contains(diagnostic.message, "arrays do not expose '.ptr'") + sentinel_error = sentinel_error || + strings.contains(diagnostic.message, "sentinel value does not fit array, slice, or pointer") + } + testing.expect_value(t, conversion_errors, 8) + testing.expect(t, array_ptr_error) + testing.expect(t, sentinel_error) +} + @(test) c_variadic_calls_promote_extras_and_emit_variadic_llvm :: proc(t: ^testing.T) { text := `variadic :: c_func(tag c_int, ...) c_int @@ -1471,6 +1651,26 @@ valid_program_compiles_and_runs :: proc(t: ^testing.T) { testing.expect_value(t, state.exit_code, 0) } +@(test) +c_printf_accepts_a_string_literal :: proc(t: ^testing.T) { + output := "/tmp/brolang-test-printf" + defer _ = os.remove(output) + status := compiler_core.compile_package("examples/interop/printf", output) + testing.expect_value(t, status, 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 0) +} + +@(test) +sentinel_pointer_views_compile_and_run :: proc(t: ^testing.T) { + output := "/tmp/brolang-test-sentinel-pointer" + defer _ = os.remove(output) + status := compiler_core.compile_package("examples/programs/sentinel_pointer", output) + testing.expect_value(t, status, 0) + state := run_executable(output) + testing.expect_value(t, state.exit_code, 303) +} + @(test) foreign_function_links_from_c_source :: proc(t: ^testing.T) { output := "/tmp/brolang-test-foreign-source" diff --git a/examples/interop/header/app/main.bro b/examples/interop/header/app/main.bro index e87f04c..ba95eec 100644 --- a/examples/interop/header/app/main.bro +++ b/examples/interop/header/app/main.bro @@ -9,6 +9,7 @@ main :: func() void { _ = native.imported_scalar(3, 4) _ = native.child_value(7) _ = native.imported_read(native.imported_handle()?) + _ = native.imported_string("hello") _ = native.configured_value(9) signed i8 :: -2 unsigned u16 :: 3 diff --git a/examples/interop/header/include/native.h b/examples/interop/header/include/native.h index 8a62fed..ffef1b5 100644 --- a/examples/interop/header/include/native.h +++ b/examples/interop/header/include/native.h @@ -21,6 +21,7 @@ int imported_add(imported_int_alias left, int right); unsigned long imported_scalar(unsigned char value, unsigned long extra); Imported_Handle *imported_handle(void); int imported_read(const Imported_Handle *handle); +int imported_string(const char *value); Imported_Value imported_by_value(Imported_Value value); int imported_volatile(volatile int *value); _Bool imported_bool(_Bool value); diff --git a/examples/interop/header/native.c b/examples/interop/header/native.c index ab6942f..e6165d7 100644 --- a/examples/interop/header/native.c +++ b/examples/interop/header/native.c @@ -1,6 +1,7 @@ #include "include/native.h" #include #include +#include struct Imported_Handle { int value; @@ -28,6 +29,13 @@ int imported_read(const Imported_Handle *value) { return value->value; } +int imported_string(const char *value) { + if (strcmp(value, "hello") != 0) { + abort(); + } + return 0; +} + int imported_variadic(int marker, ...) { va_list args; va_start(args, marker); diff --git a/examples/interop/printf/main.bro b/examples/interop/printf/main.bro new file mode 100644 index 0000000..fa1adfd --- /dev/null +++ b/examples/interop/printf/main.bro @@ -0,0 +1,5 @@ +printf :: c_func(format *c_char, ...) c_int + +main :: func() void { + _ = printf("answer: %d\n", 42) +} diff --git a/examples/programs/sentinel_pointer/main.bro b/examples/programs/sentinel_pointer/main.bro new file mode 100644 index 0000000..bd62a51 --- /dev/null +++ b/examples/programs/sentinel_pointer/main.bro @@ -0,0 +1,8 @@ +main :: func() i32 { + values [2;0]i32 :: [101, 101] + array_pointer :: &values + suffix [;0]i32 :: array_pointer[1..] + ordinary []i32 :: suffix + pointer [*;0]i32 :: suffix.ptr + return array_pointer[0] + pointer[0] + ordinary[0] +}