memcopy! and memset! intrinsics

This commit is contained in:
2026-07-20 22:53:04 +02:00
parent 297f2e3078
commit dd00af7731
11 changed files with 729 additions and 11 deletions
+223 -2
View File
@@ -2230,7 +2230,9 @@ old_intrinsic_spellings_are_not_recognized :: proc(t: ^testing.T) {
@(test)
bare_intrinsic_names_are_available_to_user_functions :: proc(t: ^testing.T) {
text := `ptrcast func() i32 { return 1 }
text := `memory :: import "./memory"
ptrcast func() i32 { return 1 }
sizeof func() i32 { return 2 }
alignof func() i32 { return 3 }
minval func() i32 { return 4 }
@@ -2241,19 +2243,29 @@ divexact func() i32 { return 8 }
divceil func() i32 { return 9 }
rem func() i32 { return 10 }
mod func() i32 { return 11 }
memcopy func() i32 { return 12 }
memset func() i32 { return 13 }
main func() i32 {
return ptrcast() + sizeof() + alignof() + minval() + maxval() +
divtrunc() + divfloor() + divexact() + divceil() + rem() + mod() - 66
divtrunc() + divfloor() + divexact() + divceil() + rem() + mod() +
memcopy() + memset() + memory.memcopy() + memory.memset() - 120
}
`
directory := "/tmp/brolang-test-user-intrinsic-names"
main_path := "/tmp/brolang-test-user-intrinsic-names/main.bro"
memory_directory := "/tmp/brolang-test-user-intrinsic-names/memory"
memory_path := "/tmp/brolang-test-user-intrinsic-names/memory/memory.bro"
output := "/tmp/brolang-test-user-intrinsic-names-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.make_directory(memory_directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
memory_text := `memcopy func() i32 { return 14 }
memset func() i32 { return 15 }
`
testing.expect(t, os.write_entire_file(memory_path, transmute([]byte)memory_text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
}
@@ -2286,6 +2298,215 @@ intrinsic_call_diagnostics_are_precise :: proc(t: ^testing.T) {
testing.expect(t, found_qualified)
}
@(test)
memory_intrinsics_compile_run_and_lower :: proc(t: ^testing.T) {
text := `Pair :: struct { x i32, y i32 }
calls i32 = 0
destination_view func(value []mut u8) []mut u8 {
calls += 1
return value
}
source_view func(value []u8) []u8 {
calls += 1
return value
}
compile_value func() [4]mut u8 {
source [4]mut u8 = [1, 2, 3, 4]
destination [4]mut u8 = undefined
memset!(&destination, undefined)
memcopy!(&destination, &source)
memset!(destination[1..3], 9)
memcopy!(destination[..0], destination[..0])
return destination
}
known :: $compile_value()
main func() i32 {
if known[0] != 1 or known[1] != 9 or known[2] != 9 or known[3] != 4 { return 1 }
bytes [4]mut u8 = undefined
source [4]mut u8 = [4, 3, 2, 1]
memset!(&bytes, undefined)
memcopy!(destination_view(bytes[..]), source_view(source[..]))
if calls != 2 or bytes[0] != 4 or bytes[3] != 1 { return 2 }
memset!(bytes[1..3], 7)
if bytes[1] != 7 or bytes[2] != 7 { return 3 }
wide [2]mut i32 = undefined
memset!(&wide, 42)
if wide[0] != 42 or wide[1] != 42 { return 4 }
pairs [2]mut Pair = undefined
pair_source [2]mut Pair = [Pair{x = 1, y = 2}, Pair{x = 3, y = 4}]
memcopy!(&pairs, &pair_source)
memset!(pairs[1..], Pair{x = 7, y = 8})
if pairs[0].x != 1 or pairs[1].y != 8 { return 5 }
memcopy!(bytes[..0], bytes[..0])
return 0
}
`
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)
copy_count, set_count := 0, 0
for function in ir_module.functions {
for instruction in function.instructions {
copy_count += 1 if instruction.op == .Mem_Copy else 0
set_count += 1 if instruction.op == .Mem_Set else 0
}
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, copy_count >= 3)
testing.expect(t, set_count >= 3)
testing.expect(t, strings.contains(llvm_text, "memcopy_len_ok"))
testing.expect(t, strings.contains(llvm_text, "memcopy_size_ok"))
testing.expect(t, strings.contains(llvm_text, "memcopy_disjoint"))
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memcpy.p0.p0.i64"))
testing.expect(t, strings.contains(llvm_text, "call void @llvm.memset.p0.i64"))
testing.expect(t, strings.contains(llvm_text, "memset_loop"))
directory := "/tmp/brolang-test-memory-intrinsics"
main_path := "/tmp/brolang-test-memory-intrinsics/main.bro"
output := "/tmp/brolang-test-memory-intrinsics-output"
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
defer _ = os.remove(output)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
testing.expect_value(t, compiler_core.compile_package(directory, output), 0)
state := run_executable(output)
testing.expect_value(t, state.exit_code, 0)
}
@(test)
memory_intrinsic_diagnostics_are_precise :: proc(t: ^testing.T) {
text := `bad_length func() [1]mut u8 {
destination [1]mut u8 = undefined
source [2]mut u8 = [1, 2]
memcopy!(&destination, &source)
return destination
}
bad_overlap func() [3]mut u8 {
items [3]mut u8 = [1, 2, 3]
memcopy!(items[..2], items[1..])
return items
}
bad_undefined func() u8 {
items [1]mut u8 = [1]
memset!(&items, undefined)
return items[0]
}
length_value :: $bad_length()
overlap_value :: $bad_overlap()
undefined_value :: $bad_undefined()
main func() void {
immutable [2]u8 = [1, 2]
mutable [2]mut u8 = undefined
wide [2]mut u16 = undefined
memcopy!(&immutable, &mutable)
memcopy!(&mutable, &wide)
memcopy!(1, 2)
memset!(1, 0)
memcopy!()
memset!(&mutable)
}
`
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)
expected := [?]string{
"memcopy! source and destination lengths differ",
"memcopy! source and destination overlap",
"cannot read an undefined value at comptime",
"memcopy! destination must be a mutable slice or mutable pointer-to-array",
"memcopy! source and destination element types must match",
"memset! destination must be a mutable slice or mutable pointer-to-array",
"memcopy! expects 2 arguments, got 0",
"memset! expects 2 arguments, got 1",
}
found: [len(expected)]bool
for diagnostic in diagnostics.items {
for message, index in expected {
found[index] = found[index] || strings.contains(diagnostic.message, message)
}
}
for present in found {
testing.expect(t, present)
}
}
@(test)
memory_intrinsic_runtime_guards_trap :: proc(t: ^testing.T) {
cases := [?]struct {
directory, output, text: string,
}{
{
"/tmp/brolang-test-memcopy-length-trap",
"/tmp/brolang-test-memcopy-length-trap-output",
`copy func(destination []mut u8, source []u8) void { memcopy!(destination, source) }
main func() void {
destination [2]mut u8 = undefined
source [3]mut u8 = [1, 2, 3]
copy(destination[..], source[..])
}
`,
},
{
"/tmp/brolang-test-memcopy-overlap-trap",
"/tmp/brolang-test-memcopy-overlap-trap-output",
`copy func(destination []mut u8, source []u8) void { memcopy!(destination, source) }
main func() void {
items [4]mut u8 = [1, 2, 3, 4]
copy(items[1..], items[..3])
}
`,
},
}
for test_case in cases {
main_path := fmt.tprintf("%s/main.bro", test_case.directory)
_ = os2.remove_all(test_case.directory)
defer _ = os2.remove_all(test_case.directory)
defer _ = os.remove(test_case.output)
testing.expect(t, os.make_directory(test_case.directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)test_case.text))
testing.expect_value(t, compiler_core.compile_package(test_case.directory, test_case.output), 0)
state := run_executable(test_case.output)
testing.expect(t, !state.success)
}
}
@(test)
malformed_intrinsic_calls_have_targeted_parse_diagnostics :: proc(t: ^testing.T) {
cases := [?]struct {