memcopy! and memset! intrinsics
This commit is contained in:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user