memcopy! and memset! intrinsics
This commit is contained in:
@@ -811,6 +811,68 @@ Division_Builtin :: enum u8 {
|
||||
Mod,
|
||||
}
|
||||
|
||||
Memory_Builtin :: enum u8 {
|
||||
None,
|
||||
Copy,
|
||||
Set,
|
||||
}
|
||||
|
||||
memory_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Memory_Builtin {
|
||||
if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
return .None
|
||||
}
|
||||
switch symbol_text(checker, expr.name) {
|
||||
case "memcopy": return .Copy
|
||||
case "memset": return .Set
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
memory_region_type :: proc(checker: ^Checker, value: types.Type) -> (child: types.Type, mutable: bool, ok: bool) {
|
||||
store := &checker.module.types
|
||||
resolved := types.resolve_alias(value, store)
|
||||
if item, item_ok := types.node(store, resolved); item_ok && item.kind == .Slice {
|
||||
return item.child, item.mutable, true
|
||||
}
|
||||
pointer, pointer_ok := types.node(store, resolved)
|
||||
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, pointer.mutable && array.mutable, true
|
||||
}
|
||||
}
|
||||
return types.INVALID, false, false
|
||||
}
|
||||
|
||||
infer_memory_builtin :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
kind: Memory_Builtin,
|
||||
locals: []Infer_Local,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
demanded: ^[dynamic]Spec_Id,
|
||||
local_types: []types.Type,
|
||||
) -> types.Type {
|
||||
if len(expr.args) != 2 {
|
||||
return types.INVALID
|
||||
}
|
||||
destination := infer_nested_expr(checker, expr.args[0], locals, pkg, file, demanded, local_types)
|
||||
child, _, ok := memory_region_type(checker, destination)
|
||||
if !ok {
|
||||
_ = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
|
||||
return types.INVALID
|
||||
}
|
||||
if kind == .Set {
|
||||
if !is_undefined_expr(checker, expr.args[1]) {
|
||||
_ = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types, child)
|
||||
}
|
||||
} else {
|
||||
_ = infer_nested_expr(checker, expr.args[1], locals, pkg, file, demanded, local_types)
|
||||
}
|
||||
return types.VOID
|
||||
}
|
||||
|
||||
division_builtin_call :: proc(checker: ^Checker, expr: ast.Expr) -> Division_Builtin {
|
||||
if !expr.intrinsic || expr.kind != .Call || expr.left != ast.INVALID_EXPR || symbol.is_valid(expr.qualifier) {
|
||||
return .None
|
||||
@@ -5036,6 +5098,11 @@ infer_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if builtin := memory_builtin_call(checker, expr); builtin != .None {
|
||||
last = infer_memory_builtin(checker, expr, builtin, locals, pkg, file, demanded, local_types)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "some") {
|
||||
if len(expr.args) == 1 && types.is_optional(frame.expected, &checker.module.types) {
|
||||
child := types.child_type(frame.expected, &checker.module.types)
|
||||
@@ -7548,6 +7615,71 @@ build_division_builtin :: proc(
|
||||
})
|
||||
}
|
||||
|
||||
build_memory_builtin :: proc(
|
||||
checker: ^Checker,
|
||||
expr: ast.Expr,
|
||||
kind: Memory_Builtin,
|
||||
locals: []Build_Local,
|
||||
global_reads: ^[dynamic]hir.Global_Id,
|
||||
calls: ^[dynamic]hir.Function_Id,
|
||||
pkg: ast.Package_Id,
|
||||
file: ast.File_Id,
|
||||
) -> hir.Expr_Id {
|
||||
name := symbol_text(checker, expr.name)
|
||||
if len(expr.args) != 2 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "%s! expects 2 arguments, got %d", name, len(expr.args))
|
||||
return invalid_hir_expr(checker, expr.span, id, types.VOID)
|
||||
}
|
||||
destination := build_nested_expr(checker, expr.args[0], locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
destination_type := checker.module.exprs[destination].type
|
||||
destination_child, destination_mutable, destination_ok := memory_region_type(checker, destination_type)
|
||||
if !destination_ok || !destination_mutable {
|
||||
id := source.addf(
|
||||
checker.diagnostics, checker.ast_module.exprs[expr.args[0]].span,
|
||||
"%s! destination must be a mutable slice or mutable pointer-to-array", name,
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id, types.VOID)
|
||||
}
|
||||
right := hir.INVALID_EXPR
|
||||
result_kind := hir.Expr_Kind.Mem_Copy
|
||||
if kind == .Set {
|
||||
if is_undefined_expr(checker, expr.args[1]) {
|
||||
right = add_hir_expr(checker, hir.Expr{
|
||||
kind=.Undefined, span=checker.ast_module.exprs[expr.args[1]].span, type=destination_child,
|
||||
target=hir.INVALID_REF, left=hir.INVALID_EXPR, right=hir.INVALID_EXPR,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
} else {
|
||||
right = build_nested_expr(checker, expr.args[1], locals, global_reads, calls, destination_child, pkg, file)
|
||||
right = coerce_expr(checker, right, destination_child, checker.ast_module.exprs[expr.args[1]].span)
|
||||
}
|
||||
result_kind = .Mem_Set
|
||||
} else {
|
||||
source_expr := build_nested_expr(checker, expr.args[1], locals, global_reads, calls, types.INVALID, pkg, file)
|
||||
source_type := checker.module.exprs[source_expr].type
|
||||
source_child, _, source_ok := memory_region_type(checker, source_type)
|
||||
if !source_ok {
|
||||
id := source.add(
|
||||
checker.diagnostics, checker.ast_module.exprs[expr.args[1]].span,
|
||||
"memcopy! source must be a slice or pointer-to-array",
|
||||
)
|
||||
return invalid_hir_expr(checker, expr.span, id, types.VOID)
|
||||
}
|
||||
if !types.equal(
|
||||
types.resolve_alias(destination_child, &checker.module.types),
|
||||
types.resolve_alias(source_child, &checker.module.types),
|
||||
) {
|
||||
id := source.add(checker.diagnostics, expr.span, "memcopy! source and destination element types must match")
|
||||
return invalid_hir_expr(checker, expr.span, id, types.VOID)
|
||||
}
|
||||
right = source_expr
|
||||
}
|
||||
return add_hir_expr(checker, hir.Expr{
|
||||
kind=result_kind, span=expr.span, type=types.VOID, left=destination, right=right,
|
||||
target=hir.INVALID_REF, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
}
|
||||
|
||||
fallible_aggregate :: proc(
|
||||
checker: ^Checker,
|
||||
span: source.Span,
|
||||
@@ -8793,6 +8925,11 @@ build_expr :: proc(
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if builtin := memory_builtin_call(checker, expr); builtin != .None {
|
||||
last = build_memory_builtin(checker, expr, builtin, locals, global_reads, calls, pkg, file)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
if is_intrinsic_call(checker, expr, "some") {
|
||||
if len(expr.args) != 1 {
|
||||
id := source.addf(checker.diagnostics, expr.span, "some! expects 1 argument, got %d", len(expr.args))
|
||||
|
||||
@@ -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