From 2761c77182924b02a57a7f729a976b492d8d6140 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sun, 25 Oct 2026 01:21:02 +0200 Subject: [PATCH] refactor: use contextual empty slice literals --- README.md | 2 + TODO.md | 1 - source/interpreter/interpreter.hon | 21 +++++--- source/interpreter/interpreter.test.hon | 66 ++++++++++++------------- std/arraylist/arraylist.hon | 4 +- std/hashmap/hashmap.hon | 2 +- std/mem/mem.hon | 15 ++---- 7 files changed, 57 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 698e1f3..2a972a8 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ build Honey from this directory: bro build ``` +the bootstrap must support contextual `&[]` literals for mutable slices; collection initialization and reset use these literals instead of a dedicated empty-slice helper. if the installed `bro` predates that feature, use `../brolang/build/brolang build` after rebuilding Bro. zero-sized elements with nonzero logical lengths still use the memory module's specialized storage handling. + run a source file with the interpreter: ```sh diff --git a/TODO.md b/TODO.md index efb09ba..e69de29 100644 --- a/TODO.md +++ b/TODO.md @@ -1 +0,0 @@ -- [ ] stdlib: add an auto-hashmap implementation (inspired by zig) \ No newline at end of file diff --git a/source/interpreter/interpreter.hon b/source/interpreter/interpreter.hon index cffa549..6779726 100644 --- a/source/interpreter/interpreter.hon +++ b/source/interpreter/interpreter.hon @@ -233,13 +233,18 @@ run proc( .address_local: { local :: &frames.items[frame_index].locals[usize(instruction.slot)] address := Address{ - type = instruction.pointee, array_type = instruction.array_type, - cells = mem.empty(ir.Value), initialized = &local.initialized, + type = instruction.pointee, + array_type = instruction.array_type, + cells = &[], + initialized = &local.initialized, } if (address.type == .array) address.cells = local.value.data.elements else address.scalar = &local.value frames.items[frame_index].addresses[usize(instruction.destination)] = address - value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } } + value = ir.Value{ + type = .address, + data = ir.ValueData{ address = u32(instruction.destination) }, + } } .address_global: { global_index :: usize(instruction.slot) @@ -255,13 +260,17 @@ run proc( } global :: &globals[global_index] address := Address{ - type = instruction.pointee, array_type = instruction.array_type, - cells = mem.empty(ir.Value), + type = instruction.pointee, + array_type = instruction.array_type, + cells = &[], } if (address.type == .array) address.cells = global.value.data.elements else address.scalar = &global.value frames.items[frame_index].addresses[usize(instruction.destination)] = address - value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } } + value = ir.Value{ + type = .address, + data = ir.ValueData{ address = u32(instruction.destination) }, + } } .index_address: { base_id :: frames.items[frame_index].values[usize(instruction.left)].data.address diff --git a/source/interpreter/interpreter.test.hon b/source/interpreter/interpreter.test.hon index 2a70d2e..3e22278 100644 --- a/source/interpreter/interpreter.test.hon +++ b/source/interpreter/interpreter.test.hon @@ -98,8 +98,8 @@ repeated_calls_have_fresh_parameters_locals_and_temporaries test { try emit(&module, bump, ir.Instruction{ op = .store_local, left = ir.ValueId(2), slot = 1 }) try emit(&module, bump, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(3), slot = 1 }) return_value(&module, bump, ir.ValueId(3)) - noop :: try add_function(&module, mem.empty(ir.Type), 0, .void, 0) - main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 8) + noop :: try add_function(&module, &[], 0, .void, 0) + main :: try add_function(&module, &[], 0, .int, 8) module.entry = main try emit(&module, main, function_value(0, bump)) try emit(&module, main, integer(1, 19)) @@ -125,8 +125,8 @@ lazy_globals_follow_forward_dependencies_and_reset_between_runs test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - first :: try add_function(&module, mem.empty(ir.Type), 0, .int, 3) - second :: try add_function(&module, mem.empty(ir.Type), 0, .int, 1) + first :: try add_function(&module, &[], 0, .int, 3) + second :: try add_function(&module, &[], 0, .int, 1) try emit(&module, first, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 1 }) try emit(&module, first, integer(1, 1)) try emit(&module, first, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) }) @@ -135,7 +135,7 @@ lazy_globals_follow_forward_dependencies_and_reset_between_runs test { return_value(&module, second, ir.ValueId(0)) try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = first, span = diagnostics.Span{ start = 0, end = 0 } }) try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = second, span = diagnostics.Span{ start = 0, end = 0 } }) - main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 3) + main :: try add_function(&module, &[], 0, .int, 3) module.entry = main try emit(&module, main, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0 }) try emit(&module, main, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(1), slot = 0 }) @@ -164,7 +164,7 @@ function_aliases_and_borrowed_string_values_execute test { memcopy!(owned, text) try arraylist.append(&module.strings, ir.StringStorage{ allocation = owned, len = text.len }) transferred = true - target :: try add_function(&module, mem.empty(ir.Type), 0, .string, 1) + target :: try add_function(&module, &[], 0, .string, 1) try emit(&module, target, ir.Instruction{ op = .constant, type = .string, @@ -172,11 +172,11 @@ function_aliases_and_borrowed_string_values_execute test { constant = ir.Value{ type = .string, data = ir.ValueData{ string = ir.StringId(0) } }, }) return_value(&module, target, ir.ValueId(0)) - initializer :: try add_function(&module, mem.empty(ir.Type), 0, .function, 1) + initializer :: try add_function(&module, &[], 0, .function, 1) try emit(&module, initializer, function_value(0, target)) return_value(&module, initializer, ir.ValueId(0)) try arraylist.append(&module.globals, ir.Global{ type = .function, initializer = initializer, span = diagnostics.Span{ start = 0, end = 0 } }) - main :: try add_function(&module, mem.empty(ir.Type), 0, .string, 2) + main :: try add_function(&module, &[], 0, .string, 2) module.entry = main try emit(&module, main, ir.Instruction{ op = .load_global, type = .function, destination = ir.ValueId(0), slot = 0 }) try emit(&module, main, ir.Instruction{ op = .call, type = .string, destination = ir.ValueId(1), left = ir.ValueId(0) }) @@ -203,8 +203,8 @@ premature_local_reads_and_global_cycles_report_instruction_spans test { defer deinit(&premature) try expect_trap(premature, &store, .read_before_initialization, span) - first :: try add_function(&module, mem.empty(ir.Type), 0, .int, 1) - second :: try add_function(&module, mem.empty(ir.Type), 0, .int, 1) + first :: try add_function(&module, &[], 0, .int, 1) + second :: try add_function(&module, &[], 0, .int, 1) cycle_span :: diagnostics.Span{ start = 40, end = 44 } try emit(&module, first, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 1 }) try emit(&module, second, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0, span = cycle_span }) @@ -232,9 +232,9 @@ existing_trap_identity_entry_failures_and_unused_errors_survive test { defer deinit(&absent) try testing.expect_equal(entry_id, absent.trap?) invalid_id :: try diagnostics.add(&store, .unknown_name, diagnostics.Span{ start = 12, end = 17 }) - bad :: try add_function(&module, mem.empty(ir.Type), 0, .invalid, 0) + bad :: try add_function(&module, &[], 0, .invalid, 0) module.functions.items[usize(bad)].blocks.items[0].terminator = ir.Terminator{ kind = .trap, diagnostic = invalid_id } - main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 2) + main :: try add_function(&module, &[], 0, .int, 2) module.entry = main try emit(&module, main, integer(0, 42)) return_value(&module, main, ir.ValueId(0)) @@ -286,7 +286,7 @@ integer_arithmetic_checks_boundaries_before_host_operations test { store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) span :: diagnostics.Span{ start = 8, end = 16 } - main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 3) + main :: try add_function(&module, &[], 0, .int, 3) module.entry = main try emit(&module, main, integer(0, case.left)) try emit(&module, main, integer(1, case.right)) @@ -309,7 +309,7 @@ integer_negation_traps_only_at_minimum test { store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) span :: diagnostics.Span{ start = 21, end = 27 } - main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 2) + main :: try add_function(&module, &[], 0, .int, 2) module.entry = main try emit(&module, main, integer(0, minval!(i64))) try emit(&module, main, ir.Instruction{ op = .negate, type = .int, destination = ir.ValueId(1), left = ir.ValueId(0), span = span }) @@ -329,7 +329,7 @@ float_arithmetic_and_ieee_zero_division_execute test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - main :: try add_function(&module, mem.empty(ir.Type), 0, .float, 7) + main :: try add_function(&module, &[], 0, .float, 7) module.entry = main try emit(&module, main, ir.Instruction{ op = .constant, type = .float, destination = ir.ValueId(0), constant = ir.Value{ type = .float, data = ir.ValueData{ real = 10.5 } } }) try emit(&module, main, ir.Instruction{ op = .constant, type = .float, destination = ir.ValueId(1), constant = ir.Value{ type = .float, data = ir.ValueData{ real = 2.0 } } }) @@ -413,7 +413,7 @@ recursive_invocations_use_heap_frames_and_free_on_allocation_failure test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - main :: try add_function(&module, mem.empty(ir.Type), 0, .void, 2) + main :: try add_function(&module, &[], 0, .void, 2) module.entry = main recursive_block :: try add_block(&module, main) module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = recursive_block } @@ -552,7 +552,7 @@ branching_recursive_calls_and_lazy_initializers_resume_the_current_block test { try emit_at(&module, count, recurse, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(7), left = ir.ValueId(6), right = ir.ValueId(4) }) module.functions.items[usize(count)].blocks.items[usize(recurse)].terminator = ir.Terminator{ value = ir.ValueId(7) } - initializer :: try add_function(&module, mem.empty(ir.Type), 0, .int, 3) + initializer :: try add_function(&module, &[], 0, .int, 3) init_entry :: try add_block(&module, initializer) init_exit :: try add_block(&module, initializer) trap_id :: try diagnostics.add(&store, .invalid_escape, diagnostics.Span{ start = 0, end = 0 }) @@ -566,7 +566,7 @@ branching_recursive_calls_and_lazy_initializers_resume_the_current_block test { module.functions.items[usize(initializer)].blocks.items[usize(init_exit)].terminator = ir.Terminator{ value = ir.ValueId(2) } try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = initializer, span = diagnostics.Span{ start = 0, end = 0 } }) - main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 5) + main :: try add_function(&module, &[], 0, .int, 5) main_entry :: try add_block(&module, main) main_exit :: try add_block(&module, main) module.entry = main @@ -611,7 +611,7 @@ all_numeric_comparisons_dispatch_on_operand_type_not_bool_result_type test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - main :: try add_function(&module, mem.empty(ir.Type), 0, .bool, 3) + main :: try add_function(&module, &[], 0, .bool, 3) module.entry = main left_value := ir.Value{ type = type, data = ir.ValueData{ integer = left } } right_value := ir.Value{ type = type, data = ir.ValueData{ integer = right } } @@ -650,7 +650,7 @@ nan_comparisons_obey_ieee_rules_in_either_operand_position test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - main :: try add_function(&module, mem.empty(ir.Type), 0, .bool, 3) + main :: try add_function(&module, &[], 0, .bool, 3) module.entry = main try emit(&module, main, ir.Instruction{ op = .constant, type = .float, destination = ir.ValueId(0), @@ -680,7 +680,7 @@ boolean_equality_and_branching_use_boolean_payloads test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 5) + main :: try add_function(&module, &[], 0, .int, 5) module.entry = main yes :: try add_block(&module, main) no :: try add_block(&module, main) @@ -825,7 +825,7 @@ logical_not_inverts_boolean_payload_and_composes_in_typed_ir test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - main :: try add_function(&module, mem.empty(ir.Type), 0, .bool, 3) + main :: try add_function(&module, &[], 0, .bool, 3) module.entry = main try emit(&module, main, ir.Instruction{ op = .constant, @@ -970,7 +970,7 @@ array_arguments_and_returns_are_private_values_before_frame_release test { try emit(&module, callee, integer(4, 7)) try emit(&module, callee, ir.Instruction{ op = .store_address, left = ir.ValueId(3), right = ir.ValueId(4) }) return_value(&module, callee, ir.ValueId(5)) - main :: try add_function(&module, mem.empty(ir.Type), 0, .array, 8) + main :: try add_function(&module, &[], 0, .array, 8) module.entry = main module.functions.items[usize(main)].result_array_type = pair_shape try emit(&module, main, integer(0, 42)) @@ -999,8 +999,8 @@ lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary t store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) shape :: try add_array(&module, .int, ir.no_array, 1) - first :: try add_function(&module, mem.empty(ir.Type), 0, .array, 1) - second :: try add_function(&module, mem.empty(ir.Type), 0, .array, 2) + first :: try add_function(&module, &[], 0, .array, 1) + second :: try add_function(&module, &[], 0, .array, 2) module.functions.items[usize(first)].result_array_type = shape module.functions.items[usize(second)].result_array_type = shape try array_storage(&module, first, 0, shape, false) @@ -1017,7 +1017,7 @@ lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary t span = diagnostics.Span{ start = 0, end = 0 }, }) } - main :: try add_function(&module, mem.empty(ir.Type), 0, .array, 2) + main :: try add_function(&module, &[], 0, .array, 2) module.entry = main module.functions.items[usize(main)].result_array_type = shape try emit(&module, main, ir.Instruction{ op = .address_global, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(0), slot = 0 }) @@ -1129,10 +1129,10 @@ zero_leaf_nested_arrays_return_a_shape_without_an_owned_allocation test { defer diagnostics.deinit(&store) empty :: try add_array(&module, .bool, ir.no_array, 0) rows :: try add_array(&module, .array, empty, 3) - main :: try add_function(&module, mem.empty(ir.Type), 0, .array, 4) + main :: try add_function(&module, &[], 0, .array, 4) module.entry = main module.functions.items[usize(main)].result_array_type = rows - try literal(&module, main, 0, empty, mem.empty(u32)) + try literal(&module, main, 0, empty, &[]) operands [3]u32 :: [0, 0, 0] try literal(&module, main, 1, rows, operands[..]) return_value(&module, main, ir.ValueId(1)) @@ -1189,7 +1189,7 @@ unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - main :: try add_function(&module, mem.empty(ir.Type), 0, .usize, 3) + main :: try add_function(&module, &[], 0, .usize, 3) module.entry = main span :: diagnostics.Span{ start = 7, end = 12 } try emit(&module, main, unsigned(0, case.left)) @@ -1211,7 +1211,7 @@ unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test { defer ir.deinit(&module) store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - main :: try add_function(&module, mem.empty(ir.Type), 0, .bool, 3) + main :: try add_function(&module, &[], 0, .bool, 3) module.entry = main try emit(&module, main, unsigned(0, maxval!(u64))) try emit(&module, main, unsigned(1, 1)) @@ -1244,11 +1244,11 @@ all_scalar_leaf_families_survive_aggregate_return_and_function_extraction test { } store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&store) - target :: try add_function(&module, mem.empty(ir.Type), 0, .int, 1) + target :: try add_function(&module, &[], 0, .int, 1) try emit(&module, target, integer(0, 42)) return_value(&module, target, ir.ValueId(0)) shape :: try add_array(&module, scalar.type, ir.no_array, 1) - main :: try add_function(&module, mem.empty(ir.Type), 0, .array, 5) + main :: try add_function(&module, &[], 0, .array, 5) module.entry = main module.functions.items[usize(main)].result_array_type = shape try emit(&module, main, ir.Instruction{ op = .constant, type = scalar.type, destination = ir.ValueId(0), constant = scalar }) @@ -1413,7 +1413,7 @@ recursive_array_backings_and_addresses_remain_fresh_across_stack_growth test { try emit_at(&module, recursive, recurse, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(10), left = ir.ValueId(4) }) try emit_at(&module, recursive, recurse, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(11), left = ir.ValueId(9), right = ir.ValueId(10) }) module.functions.items[usize(recursive)].blocks.items[usize(recurse)].terminator = ir.Terminator{ value = ir.ValueId(11) } - main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 3) + main :: try add_function(&module, &[], 0, .int, 3) module.entry = main try emit(&module, main, function_value(0, recursive)) try emit(&module, main, integer(1, 64)) diff --git a/std/arraylist/arraylist.hon b/std/arraylist/arraylist.hon index f048ca8..efb5b56 100644 --- a/std/arraylist/arraylist.hon +++ b/std/arraylist/arraylist.hon @@ -13,7 +13,7 @@ ArrayList proc($T type) type { #! retain `allocator` for later growth and cleanup; this call does not allocate. init proc($T type, allocator mem.Allocator) ArrayList(T) { return ArrayList(T) { - items = mem.empty(T), + items = &[], capacity = 0, allocator = allocator, } @@ -23,7 +23,7 @@ init proc($T type, allocator mem.Allocator) ArrayList(T) { deinit proc($T type, list @mut ArrayList(T)) void { allocation :: list.items.ptr[..list.capacity] mem.free(list.allocator, allocation) - list.items = mem.empty(T) + list.items = &[] list.capacity = 0 } diff --git a/std/hashmap/hashmap.hon b/std/hashmap/hashmap.hon index 5a95cbb..89e7d1b 100644 --- a/std/hashmap/hashmap.hon +++ b/std/hashmap/hashmap.hon @@ -46,7 +46,7 @@ init proc( allocator mem.Allocator, ) HashMap(K, V, hash_key, keys_eql) { return HashMap(K, V, hash_key, keys_eql){ - entries = mem.empty(Entry(K, V)), + entries = &[], count = 0, allocator = allocator, } diff --git a/std/mem/mem.hon b/std/mem/mem.hon index dee5c1d..1a08c9f 100644 --- a/std/mem/mem.hon +++ b/std/mem/mem.hon @@ -62,7 +62,7 @@ set proc($T type, destination []mut T, value T) void { #! allocate memory for a slice of type `T` with `count` elements. #! returned storage belongs to the caller; release it with free using the same allocator. alloc proc($T type, allocator Allocator, count usize) []mut T ! AllocError { - if (count == 0) return empty_slice(T, 0) + if (count == 0) return &[] element_size :: sizeof!(T) if (element_size == 0) return empty_slice(T, count) @@ -95,7 +95,7 @@ realloc proc( if new_count == 0 { free(allocator, memory) - return empty_slice(T, 0) + return &[] } element_size :: sizeof!(T) @@ -137,21 +137,14 @@ free proc($T type, allocator Allocator, memory []T) void { ) } -#! create a slice backed by `empty_storage` without allocating. -#! precondition: `count` must be 0 or `sizeof!(T)` must be 0; otherwise the slice -#! exceeds its backing storage. +#! create a logically nonempty slice of zero-sized elements without allocating. +#! precondition: sizeof!(T) must be 0; no element storage is accessed. @hide:file empty_slice proc($T type, count usize) []mut T { pointer *mut T :: ptrcast!((&empty_storage).ptr) return pointer[..count] } -#! get an empty slice of type `T` with 0 elements. -#! the view borrows shared empty storage and requires no cleanup. -empty proc($T type) []mut T { - return empty_slice(T, 0) -} - @hide:file empty_storage [1]mut u64 := [0]