memcopy! and memset! intrinsics
This commit is contained in:
+19
@@ -180,6 +180,25 @@ infinity/NaN behavior.
|
||||
Only these six division bang calls select integer-division behavior. Bare calls such as
|
||||
`divfloor(a, b)` and qualified calls such as `math.divfloor(a, b)` resolve to ordinary functions.
|
||||
|
||||
#### typed memory operations
|
||||
|
||||
`memcopy!(destination, source)` and `memset!(destination, value)` are available at runtime and
|
||||
comptime. A destination must be a mutable slice or mutable pointer-to-array. A `memcopy!` source
|
||||
may be a slice or pointer-to-array; many-item pointers must first be sliced. Array pointers are
|
||||
treated as regions containing their explicit logical elements, including a sentinel only when it
|
||||
is part of that array region.
|
||||
|
||||
`memcopy!` requires the same element type after alias resolution and the same element count. Its
|
||||
non-empty regions must not overlap. Comptime calls diagnose unequal lengths and overlap; runtime
|
||||
calls trap for either condition or if the element count cannot be converted to a byte count.
|
||||
Zero-sized elements still require equal counts. Empty copies are no-ops and may name the same
|
||||
region.
|
||||
|
||||
`memset!` coerces `value` to the destination element type. Use `memset!(destination, 0)` to zero a
|
||||
region; there is no separate `memzero!`, and `memset!` does not promise secure zeroing. Copying or
|
||||
filling with `undefined` transfers undefined state without reading it. Each operand is evaluated
|
||||
exactly once. Bare functions named `memcopy` or `memset` remain ordinary user functions.
|
||||
|
||||
### functions, C interop, and linking
|
||||
|
||||
- demand-monomorphized Brolang and C-ABI functions
|
||||
|
||||
@@ -244,6 +244,7 @@ Current prototype features:
|
||||
- Ordered linking of additional C sources, objects, archives, and libraries
|
||||
- Checked signed addition and unary negation
|
||||
- Float-only `/` plus explicit `divtrunc!`, `divfloor!`, `divexact!`, `divceil!`, `rem!`, and `mod!` scalar intrinsics
|
||||
- Runtime/comptime typed `memcopy!` and `memset!` over slices and pointers-to-arrays, with checked lengths and overlap
|
||||
- Static, eager runtime, mutable runtime, and deferred problematic globals
|
||||
- Runtime diagnostics followed by `llvm.trap`
|
||||
|
||||
|
||||
@@ -934,6 +934,20 @@
|
||||
- fields without defaults remain required; C-layout records, unions, tuples, and anonymous
|
||||
generated structs do not accept defaults
|
||||
|
||||
45. typed memory intrinsics (implemented)
|
||||
- `memcopy!` copies equal-length, non-overlapping slices or pointers-to-arrays with identical
|
||||
element types; comptime diagnoses invalid regions and runtime guards length, size, and overlap
|
||||
- `memset!` fills a mutable region with a value coerced to its element type; bytes lower to LLVM
|
||||
memset and wider values use typed stores
|
||||
- both operations evaluate operands once, preserve undefined state without observing it, and work
|
||||
identically during comptime evaluation; zeroing is `memset!(destination, 0)`
|
||||
- `std/mem` aligned reallocation uses `memcopy!`
|
||||
|
||||
46. fix `EnumFieldStruct` in `std/meta`
|
||||
- currently, the `|info|` `.enum` payload capture doesn't preserve the comptime-ness in the match statement
|
||||
this results in having to type other values in the scope like `[field!(typeinfo!(E), "enum").fields.len]mut []u8`
|
||||
which is obviously absurd
|
||||
|
||||
## A word on unchecked casts
|
||||
|
||||
For casts that bypass safety checks, Honey provides builtin functions:
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -79,6 +79,7 @@ Expr_Kind :: enum u8 {
|
||||
Float,
|
||||
String,
|
||||
Bool,
|
||||
Undefined,
|
||||
Array,
|
||||
Struct,
|
||||
None,
|
||||
@@ -137,6 +138,8 @@ Expr_Kind :: enum u8 {
|
||||
And,
|
||||
Or,
|
||||
Range,
|
||||
Mem_Copy,
|
||||
Mem_Set,
|
||||
Call,
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ Linkage :: enum u8 {
|
||||
Opcode :: enum u8 {
|
||||
Param,
|
||||
Const,
|
||||
Poison,
|
||||
String,
|
||||
Aggregate,
|
||||
None,
|
||||
@@ -125,6 +126,8 @@ Opcode :: enum u8 {
|
||||
Shift_Right,
|
||||
Shift_Left_Saturating,
|
||||
Compare,
|
||||
Mem_Copy,
|
||||
Mem_Set,
|
||||
Label,
|
||||
Br,
|
||||
Cond_Br,
|
||||
|
||||
+131
-3
@@ -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")
|
||||
|
||||
@@ -647,6 +647,24 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
|
||||
return append_recovery_value(state, expr.span, expr.type, expr.diagnostic)
|
||||
}
|
||||
|
||||
memory_region_element_type :: proc(value: types.Type, store: ^types.Store) -> types.Type {
|
||||
resolved := types.resolve_alias(value, store)
|
||||
item, ok := types.node(store, resolved)
|
||||
if !ok {
|
||||
return types.INVALID
|
||||
}
|
||||
if item.kind == .Slice {
|
||||
return item.child
|
||||
}
|
||||
if item.kind == .Pointer && !item.many {
|
||||
array, array_ok := types.node(store, types.resolve_alias(item.child, store))
|
||||
if array_ok && array.kind == .Array {
|
||||
return array.child
|
||||
}
|
||||
}
|
||||
return types.INVALID
|
||||
}
|
||||
|
||||
lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
stack := state.expr_stack
|
||||
state.expr_stack = nil
|
||||
@@ -691,6 +709,13 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
_ = pop(&stack)
|
||||
case .Undefined:
|
||||
last = append_instruction(state, ir.Instruction{
|
||||
op=.Poison, span=expr.span, type=expr.type,
|
||||
target=ir.INVALID_REF, a=ir.INVALID_INSTRUCTION, b=ir.INVALID_INSTRUCTION,
|
||||
diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
_ = pop(&stack)
|
||||
case .String, .Array, .Struct, .Range, .None, .Optional_Some, .Address, .Deref,
|
||||
.Index, .Slice, .Field, .Union_Tag, .Length, .Slice_Ptr, .Unwrap, .Orelse,
|
||||
.Try, .Catch, .Not, .Eq, .Ne, .Lt, .Le, .Gt, .Ge, .And, .Or:
|
||||
@@ -744,7 +769,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||
case .Add, .Sub, .Mul, .Div, .Div_Trunc, .Div_Floor, .Div_Exact, .Div_Ceil,
|
||||
.Rem, .Mod, .Pointer_Add, .Bit_And, .Bit_Or, .Bit_Xor, .Shift_Left,
|
||||
.Shift_Right, .Shift_Left_Saturating:
|
||||
.Shift_Right, .Shift_Left_Saturating, .Mem_Copy, .Mem_Set:
|
||||
stack[frame_index].stage = 2
|
||||
append(&stack, Lower_Expr_Frame{expr=expr.left})
|
||||
case .Call:
|
||||
@@ -823,6 +848,7 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
}
|
||||
if frame.stage == 3 {
|
||||
op := ir.Opcode.Add_Checked
|
||||
result_type := expr.type
|
||||
#partial switch expr.kind {
|
||||
case .Sub: op = .Sub_Checked
|
||||
case .Mul: op = .Mul_Checked
|
||||
@@ -840,10 +866,16 @@ lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
|
||||
case .Shift_Left: op = .Shift_Left
|
||||
case .Shift_Right: op = .Shift_Right
|
||||
case .Shift_Left_Saturating: op = .Shift_Left_Saturating
|
||||
case .Mem_Copy:
|
||||
op = .Mem_Copy
|
||||
result_type = memory_region_element_type(state.hir_module.exprs[expr.left].type, &state.hir_module.types)
|
||||
case .Mem_Set:
|
||||
op = .Mem_Set
|
||||
result_type = memory_region_element_type(state.hir_module.exprs[expr.left].type, &state.hir_module.types)
|
||||
}
|
||||
last = append_instruction(state, ir.Instruction{
|
||||
op=op,
|
||||
span=expr.span, type=expr.type, target=ir.INVALID_REF,
|
||||
span=expr.span, type=result_type, target=ir.INVALID_REF,
|
||||
a=frame.left, b=last, diagnostic=source.INVALID_DIAGNOSTIC,
|
||||
})
|
||||
_ = pop(&stack)
|
||||
|
||||
+223
-2
@@ -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 {
|
||||
|
||||
+1
-4
@@ -174,10 +174,7 @@ hide c_realloc func(_ ?@mut anyopaque, memory ?*mut u8, old_size usize, new_size
|
||||
if new_size < copy_size {
|
||||
copy_size = new_size
|
||||
}
|
||||
i usize = 0
|
||||
while i < copy_size : i += 1 {
|
||||
new_bytes[i] = old_memory[i]
|
||||
}
|
||||
memcopy!(new_bytes[..copy_size], old_memory[..copy_size])
|
||||
c.free(old_memory)
|
||||
}
|
||||
return new_memory
|
||||
|
||||
Reference in New Issue
Block a user