feat: complete memory representation gate
This commit is contained in:
@@ -42,6 +42,9 @@ Address :: struct {
|
||||
scalar ?@mut ir.Value = null
|
||||
initialized ?@mut bool = null
|
||||
raw bool = false
|
||||
byte_view bool = false
|
||||
readonly bool = false
|
||||
bytes []mut ir.Value = &[]
|
||||
# source handles retain root identity and native offsets, never frame-local address IDs.
|
||||
root ?@mut ir.Value = null
|
||||
root_type ir.Type = .invalid
|
||||
@@ -55,6 +58,14 @@ Pointer :: struct {
|
||||
root_type ir.Type
|
||||
root_array ir.ArrayId
|
||||
byte_offset usize
|
||||
bytes []mut ir.Value = &[]
|
||||
readonly bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
StringView :: struct {
|
||||
root ir.Value = ir.Value{ type = .u8 }
|
||||
cells []mut ir.Value
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -103,6 +114,32 @@ run proc(
|
||||
globals[i].value = aggregate_view(global.type, global.array_type, aggregate_globals[global_offset..global_offset + cells])
|
||||
global_offset += cells
|
||||
}
|
||||
# byte reinterpretation reuses run-wide scratch; loads are copied before the next instruction.
|
||||
scratch_size usize := 16
|
||||
for (module.arrays.items) |shape| {
|
||||
if (shape.byte_size > scratch_size) scratch_size = shape.byte_size
|
||||
}
|
||||
scratch :: try mem.alloc(ir.Value, allocator, scratch_size)
|
||||
defer mem.free(allocator, scratch)
|
||||
source_scratch :: try mem.alloc(ir.Value, allocator, scratch_size)
|
||||
defer mem.free(allocator, source_scratch)
|
||||
string_size usize := 0
|
||||
for (module.strings.items) |string| {
|
||||
if (string.len == maxval!(usize) or string.len + 1 > maxval!(usize) - string_size) return .storage_limit_reached
|
||||
string_size += string.len + 1
|
||||
}
|
||||
string_cells :: try mem.alloc(ir.Value, allocator, string_size)
|
||||
defer mem.free(allocator, string_cells)
|
||||
strings :: try mem.alloc(StringView, allocator, module.strings.items.len)
|
||||
defer mem.free(allocator, strings)
|
||||
string_offset usize := 0
|
||||
for (module.strings.items) |string, index| {
|
||||
cells :: string_cells[string_offset..string_offset + string.len + 1]
|
||||
for (string.allocation[..string.len]) |byte, i| cells[i] = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = u64(byte) } }
|
||||
cells[string.len] = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } }
|
||||
strings[index] = StringView{ cells = cells }
|
||||
string_offset += cells.len
|
||||
}
|
||||
pointers := arraylist.init(Pointer, allocator)
|
||||
defer arraylist.deinit(&pointers)
|
||||
frames := arraylist.init(Frame, allocator)
|
||||
@@ -110,6 +147,9 @@ run proc(
|
||||
for (frames.items) |frame| free_frame(allocator, frame)
|
||||
arraylist.deinit(&frames)
|
||||
}
|
||||
if (module.functions.items[usize(entry)].external_name.len != 0) {
|
||||
return try runtime_trap(diagnostic_store, .unsupported_foreign_call, module.functions.items[usize(entry)].span)
|
||||
}
|
||||
try push_frame(allocator, module, &frames, entry, ir.no_value, null)
|
||||
|
||||
while frames.items.len != 0 {
|
||||
@@ -210,7 +250,14 @@ run proc(
|
||||
}
|
||||
}
|
||||
.call: {
|
||||
callee :: frames.items[frame_index].values[usize(instruction.left)].data.function
|
||||
callable :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
if (callable.data.function == maxval!(ir.FunctionId)) {
|
||||
return try runtime_trap(diagnostic_store, .null_unwrap, instruction.span)
|
||||
}
|
||||
callee :: callable.data.function
|
||||
if (module.functions.items[usize(callee)].external_name.len != 0) {
|
||||
return try runtime_trap(diagnostic_store, .unsupported_foreign_call, instruction.span)
|
||||
}
|
||||
try push_frame(allocator, module, &frames, callee, instruction.destination, null)
|
||||
# frame indices survive stack growth; pointers into frames.items do not.
|
||||
for (0..instruction.arguments_count) |argument_index| {
|
||||
@@ -251,6 +298,95 @@ run proc(
|
||||
continue
|
||||
}
|
||||
.union_tag: value = ir.union_tag(module, frames.items[frame_index].values[usize(instruction.left)])
|
||||
.string_length: {
|
||||
string :: frames.items[frame_index].values[usize(instruction.left)].data.string
|
||||
value = ir.Value{ type = .usize, data = ir.ValueData{ unsigned = u64(ir.string_bytes(module, string).len) } }
|
||||
}
|
||||
.string_pointer: {
|
||||
string :: frames.items[frame_index].values[usize(instruction.left)].data.string
|
||||
view :: &strings[usize(string)]
|
||||
value = try pointer_value(&pointers, Pointer{
|
||||
root = &view.root, root_type = .u8, root_array = ir.no_array,
|
||||
byte_offset = 0, bytes = view.cells, readonly = true,
|
||||
})
|
||||
}
|
||||
.slice_check: {
|
||||
start :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
|
||||
end :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||
if (start > end) return try runtime_trap(diagnostic_store, .invalid_slice_bounds, instruction.span)
|
||||
if (instruction.extra != ir.no_value and end > frames.items[frame_index].values[usize(instruction.extra)].data.unsigned) {
|
||||
return try runtime_trap(diagnostic_store, .invalid_slice_bounds, instruction.span)
|
||||
}
|
||||
continue
|
||||
}
|
||||
.optional_check: {
|
||||
operand :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
absent := operand.data.unsigned == maxval!(u64)
|
||||
if (operand.type == .bool) absent = !operand.data.boolean
|
||||
else if (operand.type == .function) absent = operand.data.function == maxval!(ir.FunctionId)
|
||||
if (absent) return try runtime_trap(diagnostic_store, .null_unwrap, instruction.span)
|
||||
continue
|
||||
}
|
||||
.pointer_cast: {
|
||||
value = frames.items[frame_index].values[usize(instruction.left)]
|
||||
if (value.type == .function) {
|
||||
bits :: if (value.data.function == maxval!(ir.FunctionId)) maxval!(u64) else u64(value.data.function)
|
||||
value.data = ir.ValueData{ unsigned = bits }
|
||||
}
|
||||
value.type = instruction.type
|
||||
}
|
||||
.sentinel_check: {
|
||||
handle :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
|
||||
if (handle >= u64(pointers.items.len)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
address :: pointer_address(module, pointers.items[usize(handle)], instruction.pointee, instruction.array_type)
|
||||
if (address == null) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
actual :: read_address(module, address?, scratch)
|
||||
expected :: frames.items[frame_index].values[usize(instruction.right)]
|
||||
if (!equal_value(module, actual, expected)) return try runtime_trap(diagnostic_store, .sentinel_mismatch, instruction.span)
|
||||
continue
|
||||
}
|
||||
.memory_copy, .memory_set: {
|
||||
count :: frames.items[frame_index].values[usize(instruction.extra)].data.unsigned
|
||||
width :: ir.storage_size(module, instruction.pointee, instruction.array_type)
|
||||
if (count == 0 or width == 0) continue
|
||||
if (count > u64(divtrunc!(maxval!(usize), width))) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
size :: usize(count) * width
|
||||
destination_handle :: frames.items[frame_index].values[usize(instruction.left)].data.unsigned
|
||||
if (destination_handle >= u64(pointers.items.len)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
destination := pointers.items[usize(destination_handle)]
|
||||
if (destination.readonly or !pointer_range(module, destination, size)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
source := destination
|
||||
if (instruction.op == .memory_copy) {
|
||||
source_handle :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||
if (source_handle >= u64(pointers.items.len)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
source = pointers.items[usize(source_handle)]
|
||||
if (!pointer_range(module, source, size)) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
if (ptrcast!(@usize, &source.root)^ == ptrcast!(@usize, &destination.root)^) {
|
||||
distance :: if (source.byte_offset > destination.byte_offset) source.byte_offset - destination.byte_offset else destination.byte_offset - source.byte_offset
|
||||
if (distance < size) return try runtime_trap(diagnostic_store, .overlapping_memory_copy, instruction.span)
|
||||
}
|
||||
}
|
||||
destination_size :: pointer_size(module, destination)
|
||||
destination_bytes :: scratch[..destination_size]
|
||||
mem.set(destination_bytes, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
|
||||
ir.write_storage(module, destination_bytes, destination.root^)
|
||||
target :: destination_bytes[destination.byte_offset..destination.byte_offset + size]
|
||||
if (instruction.op == .memory_copy) {
|
||||
source_bytes := source.bytes
|
||||
if (source_bytes.len == 0) {
|
||||
source_bytes = source_scratch[..pointer_size(module, source)]
|
||||
mem.set(source_bytes, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
|
||||
ir.write_storage(module, source_bytes, source.root^)
|
||||
}
|
||||
memcopy!(target, source_bytes[source.byte_offset..source.byte_offset + size])
|
||||
} else {
|
||||
element :: frames.items[frame_index].values[usize(instruction.right)]
|
||||
for (0..usize(count)) |index| ir.write_storage(module, target[index * width..], element)
|
||||
}
|
||||
restored :: ir.read_storage(module, destination.root_type, destination.root_array, destination_bytes, true)
|
||||
copy_value(module, destination.root, restored)
|
||||
continue
|
||||
}
|
||||
.index: {
|
||||
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||
@@ -324,15 +460,15 @@ run proc(
|
||||
if (index >= u64(shape.len)) {
|
||||
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
|
||||
}
|
||||
width :: if (base.raw) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
|
||||
width :: if (base.raw or base.byte_view) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
|
||||
start :: usize(index) * width
|
||||
address := base
|
||||
address.type = instruction.pointee
|
||||
address.array_type = instruction.array_type
|
||||
address.cells = base.cells[start..start + width]
|
||||
if (!base.byte_view) address.cells = base.cells[start..start + width]
|
||||
address.scalar = null
|
||||
address.byte_offset += usize(index) * ir.storage_size(module, shape.element, shape.element_array)
|
||||
if (!address.raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
if (!address.raw and !address.byte_view and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||
}
|
||||
@@ -345,21 +481,21 @@ run proc(
|
||||
if base.initialized |initialized| {
|
||||
if (!initialized^) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span)
|
||||
}
|
||||
if (!ir.union_active(module, aggregate_view(.record, base.array_type, base.cells), usize(instruction.slot))) {
|
||||
if (!ir.union_active(module, read_address(module, base, scratch), usize(instruction.slot))) {
|
||||
return try runtime_trap(diagnostic_store, .inactive_union_field, instruction.span)
|
||||
}
|
||||
}
|
||||
raw :: base.raw or shape.is_union
|
||||
width :: if (raw) ir.storage_size(module, field.type, field.array_type) else if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||
width :: if (raw or base.byte_view) ir.storage_size(module, field.type, field.array_type) else if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||
start :: if (raw) field.byte_offset else field.offset
|
||||
address := base
|
||||
address.type = instruction.pointee
|
||||
address.array_type = instruction.array_type
|
||||
address.cells = base.cells[start..start + width]
|
||||
if (!base.byte_view) address.cells = base.cells[start..start + width]
|
||||
address.scalar = null
|
||||
address.raw = raw
|
||||
address.byte_offset += field.byte_offset
|
||||
if (!raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
if (!raw and !address.byte_view and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||
}
|
||||
@@ -374,6 +510,8 @@ run proc(
|
||||
root_type = address.root_type,
|
||||
root_array = address.root_array,
|
||||
byte_offset = address.byte_offset,
|
||||
readonly = address.readonly,
|
||||
bytes = address.bytes,
|
||||
})
|
||||
}
|
||||
.pointer_address: {
|
||||
@@ -382,17 +520,7 @@ run proc(
|
||||
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
}
|
||||
location :: pointers.items[usize(handle)]
|
||||
root := Address{
|
||||
type = location.root_type,
|
||||
array_type = location.root_array,
|
||||
cells = &[],
|
||||
root = location.root,
|
||||
root_type = location.root_type,
|
||||
root_array = location.root_array,
|
||||
}
|
||||
if (ir.aggregate(root.type)) root.cells = root.root?^.data.elements
|
||||
else root.scalar = root.root
|
||||
if (project_pointer(module, root, location.byte_offset, instruction.pointee, instruction.array_type)) |address| {
|
||||
if (pointer_address(module, location, instruction.pointee, instruction.array_type)) |address| {
|
||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||
} else {
|
||||
@@ -411,7 +539,7 @@ run proc(
|
||||
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
}
|
||||
address.byte_offset += usize(index) * stride
|
||||
if (address.byte_offset > ir.storage_size(module, address.root_type, address.root_array)) {
|
||||
if (address.byte_offset > pointer_size(module, address)) {
|
||||
return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
}
|
||||
value = try pointer_value(&pointers, address)
|
||||
@@ -426,13 +554,11 @@ run proc(
|
||||
}
|
||||
}
|
||||
if (instruction.op == .load_address) {
|
||||
if (address.raw) value = ir.read_storage(module, address.type, address.array_type, address.cells, true)
|
||||
else value = if (ir.aggregate(address.type)) aggregate_view(address.type, address.array_type, address.cells) else address.scalar?^
|
||||
value = read_address(module, address, scratch)
|
||||
} else {
|
||||
source :: frames.items[frame_index].values[usize(instruction.right)]
|
||||
if (address.raw) ir.write_storage(module, address.cells, source)
|
||||
else if (ir.aggregate(address.type)) ir.copy_aggregate(module, aggregate_view(address.type, address.array_type, address.cells), source)
|
||||
else address.scalar?^ = source
|
||||
if (address.readonly) return try runtime_trap(diagnostic_store, .invalid_pointer_access, instruction.span)
|
||||
write_address(module, address, source, scratch)
|
||||
continue
|
||||
}
|
||||
}
|
||||
@@ -526,6 +652,11 @@ run proc(
|
||||
.not_equal: left.data.boolean != right.data.boolean
|
||||
else: unreachable
|
||||
}
|
||||
.function: result = match instruction.op {
|
||||
.equal: left.data.function == right.data.function
|
||||
.not_equal: left.data.function != right.data.function
|
||||
else: unreachable
|
||||
}
|
||||
.pointer: result = match instruction.op {
|
||||
.equal: left.data.unsigned == right.data.unsigned
|
||||
.not_equal: left.data.unsigned != right.data.unsigned
|
||||
@@ -543,6 +674,102 @@ run proc(
|
||||
unreachable
|
||||
}
|
||||
|
||||
@hide:file
|
||||
pointer_size proc(module @ir.Module, pointer Pointer) usize {
|
||||
if (pointer.bytes.len != 0) return pointer.bytes.len
|
||||
return ir.storage_size(module, pointer.root_type, pointer.root_array)
|
||||
}
|
||||
|
||||
@hide:file
|
||||
pointer_range proc(module @ir.Module, pointer Pointer, size usize) bool {
|
||||
bound :: pointer_size(module, pointer)
|
||||
return pointer.byte_offset <= bound and size <= bound - pointer.byte_offset
|
||||
}
|
||||
|
||||
@hide:file
|
||||
pointer_address proc(module @ir.Module, pointer Pointer, type ir.Type, shape ir.ArrayId) ?Address {
|
||||
size :: ir.storage_size(module, type, shape)
|
||||
if (!pointer_range(module, pointer, size)) return null
|
||||
base := Address{
|
||||
type = pointer.root_type, array_type = pointer.root_array, cells = &[],
|
||||
root = pointer.root, root_type = pointer.root_type, root_array = pointer.root_array,
|
||||
readonly = pointer.readonly, bytes = pointer.bytes,
|
||||
}
|
||||
if (pointer.bytes.len != 0) {
|
||||
base.type = type
|
||||
base.array_type = shape
|
||||
base.cells = pointer.bytes[pointer.byte_offset..pointer.byte_offset + size]
|
||||
base.raw = true
|
||||
base.byte_offset = pointer.byte_offset
|
||||
return base
|
||||
}
|
||||
if (ir.aggregate(base.type)) base.cells = pointer.root^.data.elements
|
||||
else base.scalar = pointer.root
|
||||
if (project_pointer(module, base, pointer.byte_offset, type, shape)) |address| return address
|
||||
# differently typed projections retain the original root and native byte offset.
|
||||
base.type = type
|
||||
base.array_type = shape
|
||||
base.byte_offset = pointer.byte_offset
|
||||
base.byte_view = true
|
||||
base.scalar = null
|
||||
return base
|
||||
}
|
||||
|
||||
@hide:file
|
||||
read_address proc(module @ir.Module, address Address, scratch []mut ir.Value) ir.Value {
|
||||
if (address.byte_view) {
|
||||
size :: ir.storage_size(module, address.root_type, address.root_array)
|
||||
cells :: scratch[..size]
|
||||
mem.set(cells, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
|
||||
ir.write_storage(module, cells, address.root?^)
|
||||
return ir.read_storage(module, address.type, address.array_type, cells[address.byte_offset..], true)
|
||||
}
|
||||
if (address.raw) return ir.read_storage(module, address.type, address.array_type, address.cells, true)
|
||||
if (ir.aggregate(address.type)) return aggregate_view(address.type, address.array_type, address.cells)
|
||||
return address.scalar?^
|
||||
}
|
||||
|
||||
@hide:file
|
||||
write_address proc(module @ir.Module, address Address, source ir.Value, scratch []mut ir.Value) void {
|
||||
if (address.byte_view) {
|
||||
size :: ir.storage_size(module, address.root_type, address.root_array)
|
||||
cells :: scratch[..size]
|
||||
mem.set(cells, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
|
||||
ir.write_storage(module, cells, address.root?^)
|
||||
ir.write_storage(module, cells[address.byte_offset..], source)
|
||||
restored :: ir.read_storage(module, address.root_type, address.root_array, cells, true)
|
||||
copy_value(module, address.root?, restored)
|
||||
} else if (address.raw) ir.write_storage(module, address.cells, source)
|
||||
else if (ir.aggregate(address.type)) ir.copy_aggregate(module, aggregate_view(address.type, address.array_type, address.cells), source)
|
||||
else address.scalar?^ = source
|
||||
}
|
||||
|
||||
@hide:file
|
||||
equal_value proc(module @ir.Module, left, right ir.Value) bool {
|
||||
if (ir.aggregate(left.type)) {
|
||||
shape :: module.arrays.items[usize(left.array_type)]
|
||||
if (shape.is_union) {
|
||||
for (left.data.elements) |byte, index| {
|
||||
if (byte.data.unsigned != right.data.elements[index].data.unsigned) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
count :: if (shape.kind == .record) shape.fields.len else shape.len
|
||||
for (0..count) |index| {
|
||||
if (!equal_value(module, ir.aggregate_item(module, left, index), ir.aggregate_item(module, right, index))) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return match left.type {
|
||||
.float: left.data.real == right.data.real
|
||||
.bool: left.data.boolean == right.data.boolean
|
||||
.function: left.data.function == right.data.function
|
||||
.string: left.data.string == right.data.string
|
||||
.void: true
|
||||
else: left.data.unsigned == right.data.unsigned
|
||||
}
|
||||
}
|
||||
|
||||
@hide:file
|
||||
pointer_value proc(pointers @mut arraylist.ArrayList(Pointer), address Pointer) ir.Value ! mem.AllocError {
|
||||
# ponytail: linear location interning; use a location-keyed map if distinct-address workloads grow.
|
||||
|
||||
Reference in New Issue
Block a user