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
+163
View File
@@ -2421,6 +2421,166 @@ ct_eval_division_call :: proc(
return ct_eval_division_builtin(state, kind, left, right, expr.span)
}
ct_memory_region_info :: proc(state: ^Ct_State, value: Ct_Value) -> (child: types.Type, count: int, mutable: bool, ok: bool) {
store := &state.checker.module.types
if value.kind == .Slice {
item, item_ok := types.node(store, types.resolve_alias(value.type, store))
if item_ok && item.kind == .Slice {
return item.child, int(value.count), item.mutable, true
}
}
if value.kind == .Pointer {
pointer, pointer_ok := types.node(store, types.resolve_alias(value.type, store))
if pointer_ok && pointer.kind == .Pointer && !pointer.many {
array, array_ok := types.node(store, types.resolve_alias(pointer.child, store))
if array_ok && array.kind == .Array {
return array.child, int(array.count), pointer.mutable && array.mutable, true
}
}
}
if value.kind == .String && value.index < u64(len(state.checker.ast_module.strings)) {
return types.U8, len(state.checker.ast_module.strings[value.index]), false, true
}
return types.INVALID, 0, false, false
}
ct_memory_element_place :: proc(state: ^Ct_State, value: Ct_Value, index: int) -> Ct_Place_Id {
if value.kind == .Slice {
place, _, _ := ct_slice_element_place(state, value, index)
return place
}
if value.kind == .Pointer {
base, array_type, writable := ct_pointer_place(state, value)
array, ok := types.node(&state.checker.module.types, types.resolve_alias(array_type, &state.checker.module.types))
if base != INVALID_CT_PLACE && ok && array.kind == .Array && index >= 0 && index < int(array.count) {
return ct_extend_place(
state, base, Ct_Path_Elem{kind=.Index, index=u32(index)}, array.child, writable && array.mutable,
)
}
}
return INVALID_CT_PLACE
}
ct_places_equal :: proc(state: ^Ct_State, left_id, right_id: Ct_Place_Id) -> bool {
if left_id == INVALID_CT_PLACE || right_id == INVALID_CT_PLACE ||
int(left_id) >= len(state.places) || int(right_id) >= len(state.places) {
return false
}
left, right := state.places[left_id], state.places[right_id]
if left.cell != right.cell || left.count != right.count {
return false
}
left_path, right_path := ct_place_path(state, left), ct_place_path(state, right)
for elem, index in left_path {
if elem != right_path[index] {
return false
}
}
return true
}
ct_eval_memory_call :: proc(
state: ^Ct_State,
expr: ast.Expr,
kind: Memory_Builtin,
depth: int,
) -> (Ct_Value_Id, Ct_Flow, bool) {
name := symbol_text(state.checker, expr.name)
if len(expr.args) != 2 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 2 arguments, got %d", name, len(expr.args))
}
destination_id, destination_flow, destination_ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if !destination_ok || destination_flow.kind != .Normal || destination_id == INVALID_CT_VALUE || int(destination_id) >= len(state.values) {
return INVALID_CT_VALUE, destination_flow, destination_ok
}
destination := state.values[destination_id]
destination_child, destination_count, destination_mutable, region_ok := ct_memory_region_info(state, destination)
if !region_ok || !destination_mutable {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(
state, .Not_Comptime, state.checker.ast_module.exprs[expr.args[0]].span,
"%s! destination must be a mutable slice or mutable pointer-to-array", name,
)
}
destination_places := make([]Ct_Place_Id, destination_count, state.checker.allocator)
defer delete(destination_places, state.checker.allocator)
for &place, index in destination_places {
place = ct_memory_element_place(state, destination, index)
if place == INVALID_CT_PLACE || !state.places[place].writable {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime memory destination no longer points to writable storage")
}
}
if kind == .Set {
value, value_flow, value_ok := ct_eval_expr(state, expr.args[1], destination_child, depth+1)
if !value_ok || value_flow.kind != .Normal {
return INVALID_CT_VALUE, value_flow, value_ok
}
value, value_ok = ct_coerce_value(state, value, destination_child, state.checker.ast_module.exprs[expr.args[1]].span)
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
for place in destination_places {
if !ct_place_set(state, place, value) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
}
return ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID}), ct_flow(.Normal), true
}
source_id, source_flow, source_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
if !source_ok || source_flow.kind != .Normal || source_id == INVALID_CT_VALUE || int(source_id) >= len(state.values) {
return INVALID_CT_VALUE, source_flow, source_ok
}
source := state.values[source_id]
source_child, source_count, _, source_region_ok := ct_memory_region_info(state, source)
if !source_region_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, state.checker.ast_module.exprs[expr.args[1]].span, "memcopy! source must be a slice or pointer-to-array")
}
if !types.equal(
types.resolve_alias(destination_child, &state.checker.module.types),
types.resolve_alias(source_child, &state.checker.module.types),
) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "memcopy! source and destination element types must match")
}
if destination_count != source_count {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "memcopy! source and destination lengths differ")
}
source_places := make([]Ct_Place_Id, source_count, state.checker.allocator)
values := make([]Ct_Value_Id, source_count, state.checker.allocator)
defer delete(source_places, state.checker.allocator)
defer delete(values, state.checker.allocator)
for index in 0..<source_count {
if source.kind == .String {
values[index] = ct_add_value(state, Ct_Value{
kind=.Integer, type=types.U8, integer=i128(state.checker.ast_module.strings[source.index][index]),
})
continue
}
source_places[index] = ct_memory_element_place(state, source, index)
if source_places[index] == INVALID_CT_PLACE {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime memory source no longer points to live storage")
}
// ponytail: O(n^2) is simplest here; use canonical intervals if large comptime copies become common.
for destination_place in destination_places {
if ct_places_equal(state, source_places[index], destination_place) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "memcopy! source and destination overlap")
}
}
value, value_ok := ct_place_get(state, source_places[index])
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
values[index] = value
}
for place, index in destination_places {
if !ct_place_set(state, place, values[index]) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
}
return ct_add_value(state, Ct_Value{kind=.Void, type=types.VOID}), ct_flow(.Normal), true
}
ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
@@ -3075,6 +3235,9 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
if builtin := division_builtin_call(checker, expr); builtin != .None {
return ct_eval_division_call(state, expr, builtin, expected, depth+1)
}
if builtin := memory_builtin_call(checker, expr); builtin != .None {
return ct_eval_memory_call(state, expr, builtin, depth+1)
}
if expr.intrinsic {
if symbol.is_valid(expr.qualifier) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "intrinsic calls must be unqualified")