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
+131 -3
View File
@@ -240,6 +240,29 @@ valid_instruction :: proc(instructions: []ir.Instruction, instruction_id: ir.Ins
return instruction_id != ir.INVALID_INSTRUCTION && int(instruction_id) < len(instructions)
}
memory_region :: proc(value: types.Type, store: ^types.Store) -> (
child, array_type: types.Type,
count: u64,
mutable, is_slice, ok: bool,
) {
resolved := types.resolve_alias(value, store)
item, item_ok := types.node(store, resolved)
if !item_ok {
return types.INVALID, types.INVALID, 0, false, false, false
}
if item.kind == .Slice {
return item.child, types.INVALID, 0, item.mutable, true, true
}
if item.kind == .Pointer && !item.many {
array_type = types.resolve_alias(item.child, store)
array, array_ok := types.node(store, array_type)
if array_ok && array.kind == .Array {
return array.child, array_type, array.count, item.mutable && array.mutable, false, true
}
}
return types.INVALID, types.INVALID, 0, false, false, false
}
valid_value :: proc(
instructions: []ir.Instruction,
value_id: ir.Instruction_Id,
@@ -252,7 +275,7 @@ valid_value :: proc(
return false
}
switch instructions[value_id].op {
case .Param, .Const, .String, .Aggregate, .None, .Optional_Some,
case .Param, .Const, .Poison, .String, .Aggregate, .None, .Optional_Some,
.Load_Global, .Function_Address, .Address_Of, .Load, .Union_Tag, .Slice, .Length, .Slice_Ptr,
.Fallible_Error, .Extract, .Select, .Unwrap,
.Optional_Is_Some, .Optional_Value, .Orelse,
@@ -264,7 +287,7 @@ valid_value :: proc(
.Shift_Left, .Shift_Right, .Shift_Left_Saturating, .Compare, .Call:
return true
case .Address_Global, .Alloca, .Index_Address, .Field_Address, .Orelse_Begin,
.Store, .Fill, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
.Store, .Fill, .Mem_Copy, .Mem_Set, .Trap, .Label, .Br, .Cond_Br, .Return, .Return_Void:
return false
}
return false
@@ -333,6 +356,8 @@ write_operand :: proc(
value := instructions[value_id]
if value.op == .Const {
write_constant(builder, value.integer, expected, store)
} else if value.op == .Poison {
strings.write_string(builder, "poison")
} else {
fmt.sbprintf(builder, "%%v%d", value_id)
}
@@ -925,7 +950,7 @@ emit_instruction_stream :: proc(
after_terminator = false
}
switch instruction.op {
case .Param, .Const:
case .Param, .Const, .Poison:
case .String:
string_id := int(instruction.integer)
_, array, pointer_ok := types.array_pointer(instruction.type, &emitter.module.types)
@@ -1368,6 +1393,109 @@ emit_instruction_stream :: proc(
instruction.a,
types.size(instruction.type, &emitter.module.types, emitter.module.target),
)
case .Mem_Copy:
if !valid_instruction(instructions, instruction.a) || !valid_instruction(instructions, instruction.b) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid memcopy operands")
continue
}
destination_type := instructions[instruction.a].type
source_type := instructions[instruction.b].type
destination_child, destination_array, destination_count, destination_mutable, destination_is_slice, destination_ok := memory_region(destination_type, &emitter.module.types)
source_child, source_array, source_count, _, source_is_slice, source_ok := memory_region(source_type, &emitter.module.types)
if !destination_ok || !destination_mutable || !source_ok ||
!types.equal(types.resolve_alias(destination_child, &emitter.module.types), types.resolve_alias(source_child, &emitter.module.types)) ||
!types.equal(types.resolve_alias(destination_child, &emitter.module.types), types.resolve_alias(instruction.type, &emitter.module.types)) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid memcopy region types")
continue
}
if destination_is_slice {
fmt.sbprintf(&emitter.builder, " %%memcopy_dst%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(destination_type, &emitter.module.types), instruction.a)
fmt.sbprintf(&emitter.builder, " %%memcopy_dst_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(destination_type, &emitter.module.types), instruction.a)
} else {
fmt.sbprintf(&emitter.builder, " %%memcopy_dst%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(destination_array, &emitter.module.types), instruction.a)
fmt.sbprintf(&emitter.builder, " %%memcopy_dst_len%d = add i64 0, %d\n", instruction_index, destination_count)
}
if source_is_slice {
fmt.sbprintf(&emitter.builder, " %%memcopy_src%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(source_type, &emitter.module.types), instruction.b)
fmt.sbprintf(&emitter.builder, " %%memcopy_src_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(source_type, &emitter.module.types), instruction.b)
} else {
fmt.sbprintf(&emitter.builder, " %%memcopy_src%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(source_array, &emitter.module.types), instruction.b)
fmt.sbprintf(&emitter.builder, " %%memcopy_src_len%d = add i64 0, %d\n", instruction_index, source_count)
}
fmt.sbprintf(&emitter.builder, " %%memcopy_len_ok%d = icmp eq i64 %%memcopy_dst_len%d, %%memcopy_src_len%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " br i1 %%memcopy_len_ok%d, label %%memcopy_size_check%d, label %%memcopy_len_trap%d\nmemcopy_len_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "memcopy! source and destination lengths differ")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nmemcopy_size_check%d:\n", instruction_index)
element_size := types.size(instruction.type, &emitter.module.types, emitter.module.target)
if element_size == 0 {
continue
}
max_count := u64(0xffff_ffff_ffff_ffff)/element_size
fmt.sbprintf(&emitter.builder, " %%memcopy_size_ok%d = icmp ule i64 %%memcopy_dst_len%d, %d\n", instruction_index, instruction_index, max_count)
fmt.sbprintf(&emitter.builder, " br i1 %%memcopy_size_ok%d, label %%memcopy_overlap_check%d, label %%memcopy_size_trap%d\nmemcopy_size_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
message = diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "memory operation size overflow")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nmemcopy_overlap_check%d:\n", instruction_index)
fmt.sbprintf(&emitter.builder, " %%memcopy_bytes%d = mul i64 %%memcopy_dst_len%d, %d\n", instruction_index, instruction_index, element_size)
fmt.sbprintf(&emitter.builder, " %%memcopy_dst_end%d = getelementptr i8, ptr %%memcopy_dst%d, i64 %%memcopy_bytes%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memcopy_src_end%d = getelementptr i8, ptr %%memcopy_src%d, i64 %%memcopy_bytes%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memcopy_before%d = icmp ule ptr %%memcopy_dst_end%d, %%memcopy_src%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memcopy_after%d = icmp ule ptr %%memcopy_src_end%d, %%memcopy_dst%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memcopy_disjoint%d = or i1 %%memcopy_before%d, %%memcopy_after%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memcopy_empty%d = icmp eq i64 %%memcopy_bytes%d, 0\n", instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memcopy_ok%d = or i1 %%memcopy_empty%d, %%memcopy_disjoint%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " br i1 %%memcopy_ok%d, label %%memcopy_continue%d, label %%memcopy_overlap_trap%d\nmemcopy_overlap_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
message = diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "memcopy! source and destination overlap")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nmemcopy_continue%d:\n", instruction_index)
fmt.sbprintf(&emitter.builder, " call void @llvm.memcpy.p0.p0.i64(ptr %%memcopy_dst%d, ptr %%memcopy_src%d, i64 %%memcopy_bytes%d, i1 false)\n", instruction_index, instruction_index, instruction_index)
case .Mem_Set:
if !valid_instruction(instructions, instruction.a) || !valid_value(instructions, instruction.b, instruction.type, &emitter.module.types) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid memset operands")
continue
}
destination_type := instructions[instruction.a].type
destination_child, destination_array, destination_count, destination_mutable, destination_is_slice, destination_ok := memory_region(destination_type, &emitter.module.types)
if !destination_ok || !destination_mutable ||
!types.equal(types.resolve_alias(destination_child, &emitter.module.types), types.resolve_alias(instruction.type, &emitter.module.types)) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid memset destination")
continue
}
if destination_is_slice {
fmt.sbprintf(&emitter.builder, " %%memset_dst%d = extractvalue %s %%v%d, 0\n", instruction_index, llvm_type(destination_type, &emitter.module.types), instruction.a)
fmt.sbprintf(&emitter.builder, " %%memset_len%d = extractvalue %s %%v%d, 1\n", instruction_index, llvm_type(destination_type, &emitter.module.types), instruction.a)
} else {
fmt.sbprintf(&emitter.builder, " %%memset_dst%d = getelementptr %s, ptr %%v%d, i64 0, i64 0\n", instruction_index, llvm_type(destination_array, &emitter.module.types), instruction.a)
fmt.sbprintf(&emitter.builder, " %%memset_len%d = add i64 0, %d\n", instruction_index, destination_count)
}
element_size := types.size(instruction.type, &emitter.module.types, emitter.module.target)
if element_size == 0 {
continue
}
max_count := u64(0xffff_ffff_ffff_ffff)/element_size
fmt.sbprintf(&emitter.builder, " %%memset_size_ok%d = icmp ule i64 %%memset_len%d, %d\n", instruction_index, instruction_index, max_count)
fmt.sbprintf(&emitter.builder, " br i1 %%memset_size_ok%d, label %%memset_start%d, label %%memset_size_trap%d\nmemset_size_trap%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
message := diagnostic_message(emitter, source.INVALID_DIAGNOSTIC, instruction.span, "memory operation size overflow")
emit_trap_call(emitter, message)
fmt.sbprintf(&emitter.builder, " unreachable\nmemset_start%d:\n", instruction_index)
representation := types.runtime_representation(instruction.type, &emitter.module.types)
if types.is_concrete_integer(representation) && types.bits(representation, emitter.module.target) == 8 {
fmt.sbprintf(&emitter.builder, " %%memset_bytes%d = mul i64 %%memset_len%d, %d\n", instruction_index, instruction_index, element_size)
fmt.sbprintf(&emitter.builder, " call void @llvm.memset.p0.i64(ptr %%memset_dst%d, i8 ", instruction_index)
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", i64 %%memset_bytes%d, i1 false)\n", instruction_index)
continue
}
fmt.sbprintf(&emitter.builder, " %%memset_index_slot%d = alloca i64\n store i64 0, ptr %%memset_index_slot%d\n br label %%memset_loop%d\nmemset_loop%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memset_index%d = load i64, ptr %%memset_index_slot%d\n", instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memset_more%d = icmp ult i64 %%memset_index%d, %%memset_len%d\n", instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " br i1 %%memset_more%d, label %%memset_body%d, label %%memset_done%d\nmemset_body%d:\n", instruction_index, instruction_index, instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " %%memset_element%d = getelementptr %s, ptr %%memset_dst%d, i64 %%memset_index%d\n", instruction_index, llvm_type(instruction.type, &emitter.module.types), instruction_index, instruction_index)
fmt.sbprintf(&emitter.builder, " store %s ", llvm_type(instruction.type, &emitter.module.types))
write_operand(&emitter.builder, instructions, instruction.b, instruction.type, &emitter.module.types)
fmt.sbprintf(&emitter.builder, ", ptr %%memset_element%d\n", instruction_index)
fmt.sbprintf(&emitter.builder, " %%memset_next%d = add i64 %%memset_index%d, 1\n store i64 %%memset_next%d, ptr %%memset_index_slot%d\n br label %%memset_loop%d\nmemset_done%d:\n", instruction_index, instruction_index, instruction_index, instruction_index, instruction_index, instruction_index)
case .Slice:
if !valid_instruction(instructions, instruction.a) {
emit_recovery_value(emitter, instruction_index, instruction, "invalid slice container")