feat: support enums, distinct types, unions and matching
This commit is contained in:
@@ -41,6 +41,7 @@ Address :: struct {
|
||||
cells []mut ir.Value
|
||||
scalar ?@mut ir.Value = null
|
||||
initialized ?@mut bool = null
|
||||
raw bool = false
|
||||
}
|
||||
|
||||
@hide:file
|
||||
@@ -104,6 +105,7 @@ run proc(
|
||||
if instruction_index == block.instructions.items.len {
|
||||
terminator :: block.terminator
|
||||
if (terminator.kind == .trap) return Outcome{ trap = terminator.diagnostic }
|
||||
if (terminator.kind == .unreachable) unreachable
|
||||
if (terminator.kind == .jump or terminator.kind == .branch) {
|
||||
target := terminator.target
|
||||
if (terminator.kind == .branch) {
|
||||
@@ -121,7 +123,7 @@ run proc(
|
||||
completed :: frames.items[frame_index]
|
||||
# transfer leaves while the completed frame still owns its backing.
|
||||
if completed.global |global_index| {
|
||||
copy_value(&globals[global_index].value, value)
|
||||
copy_value(module, &globals[global_index].value, value)
|
||||
globals[global_index].status = .ready
|
||||
}
|
||||
if (frames.items.len == 1) {
|
||||
@@ -134,7 +136,7 @@ run proc(
|
||||
return Outcome{ value = value }
|
||||
}
|
||||
if completed.destination != ir.no_value {
|
||||
copy_value(&frames.items[frame_index - 1].values[usize(completed.destination)], value)
|
||||
copy_value(module, &frames.items[frame_index - 1].values[usize(completed.destination)], value)
|
||||
}
|
||||
_ = arraylist.pop(&frames)
|
||||
free_frame(allocator, completed)
|
||||
@@ -155,7 +157,7 @@ run proc(
|
||||
}
|
||||
.store_local: {
|
||||
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
||||
copy_value(&local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||
copy_value(module, &local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||
local.initialized = true
|
||||
continue
|
||||
}
|
||||
@@ -164,7 +166,7 @@ run proc(
|
||||
if (!local.initialized) {
|
||||
return try runtime_trap(diagnostic_store, .write_before_initialization, instruction.span)
|
||||
}
|
||||
copy_value(&local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||
copy_value(module, &local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||
continue
|
||||
}
|
||||
.reset_local: {
|
||||
@@ -199,7 +201,7 @@ run proc(
|
||||
for (0..instruction.arguments_count) |argument_index| {
|
||||
argument :: block.arguments.items[instruction.arguments_start + argument_index]
|
||||
local :: &frames.items[frame_index + 1].locals[argument_index]
|
||||
copy_value(&local.value, frames.items[frame_index].values[usize(argument)])
|
||||
copy_value(module, &local.value, frames.items[frame_index].values[usize(argument)])
|
||||
local.initialized = true
|
||||
}
|
||||
continue
|
||||
@@ -212,7 +214,8 @@ run proc(
|
||||
element :: frames.items[frame_index].values[usize(operand)]
|
||||
if (ir.aggregate(element.type)) {
|
||||
count :: element.data.elements.len
|
||||
memcopy!(destination.data.elements[offset..offset + count], element.data.elements)
|
||||
target :: aggregate_view(element.type, element.array_type, destination.data.elements[offset..offset + count])
|
||||
ir.copy_aggregate(module, target, element)
|
||||
offset += count
|
||||
} else {
|
||||
destination.data.elements[offset] = element
|
||||
@@ -221,6 +224,18 @@ run proc(
|
||||
}
|
||||
continue
|
||||
}
|
||||
.union_literal: {
|
||||
destination :: frames.items[frame_index].values[usize(instruction.destination)]
|
||||
shape :: module.arrays.items[usize(instruction.array_type)]
|
||||
mem.set(destination.data.elements, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
|
||||
if (shape.tag_type != .void) ir.write_storage(module, destination.data.elements, instruction.constant)
|
||||
if (instruction.left != ir.no_value) {
|
||||
payload :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
ir.write_storage(module, destination.data.elements[shape.payload_offset..], payload)
|
||||
}
|
||||
continue
|
||||
}
|
||||
.union_tag: value = ir.union_tag(module, frames.items[frame_index].values[usize(instruction.left)])
|
||||
.index: {
|
||||
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||
@@ -232,6 +247,10 @@ run proc(
|
||||
}
|
||||
.field: {
|
||||
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||
shape :: module.arrays.items[usize(base.array_type)]
|
||||
if (shape.is_union and !ir.union_active(module, base, usize(instruction.slot))) {
|
||||
return try runtime_trap(diagnostic_store, .inactive_union_field, instruction.span)
|
||||
}
|
||||
value = ir.aggregate_item(module, base, usize(instruction.slot))
|
||||
}
|
||||
.address_local: {
|
||||
@@ -284,26 +303,37 @@ run proc(
|
||||
if (index >= u64(shape.len)) {
|
||||
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
|
||||
}
|
||||
width :: element_cells(module, shape)
|
||||
width :: if (base.raw) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
|
||||
start :: usize(index) * width
|
||||
address := Address{
|
||||
type = instruction.pointee, array_type = instruction.array_type,
|
||||
cells = base.cells[start..start + width], initialized = base.initialized,
|
||||
cells = base.cells[start..start + width], initialized = base.initialized, raw = base.raw,
|
||||
}
|
||||
if (!ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
if (!address.raw 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) } }
|
||||
}
|
||||
.field_address: {
|
||||
base_id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
||||
base :: frames.items[frame_index].addresses[usize(base_id)]
|
||||
field :: module.arrays.items[usize(base.array_type)].fields[usize(instruction.slot)]
|
||||
width :: if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||
shape :: module.arrays.items[usize(base.array_type)]
|
||||
field :: shape.fields[usize(instruction.slot)]
|
||||
if (shape.is_union and shape.tag_type != .void) {
|
||||
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))) {
|
||||
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
|
||||
start :: if (raw) field.byte_offset else field.offset
|
||||
address := Address{
|
||||
type = instruction.pointee, array_type = instruction.array_type,
|
||||
cells = base.cells[field.offset..field.offset + width], initialized = base.initialized,
|
||||
cells = base.cells[start..start + width], initialized = base.initialized, raw = raw,
|
||||
}
|
||||
if (!ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||
if (!raw 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) } }
|
||||
}
|
||||
@@ -317,10 +347,12 @@ run proc(
|
||||
}
|
||||
}
|
||||
if (instruction.op == .load_address) {
|
||||
value = if (ir.aggregate(address.type)) aggregate_view(address.type, address.array_type, address.cells) else address.scalar?^
|
||||
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?^
|
||||
} else {
|
||||
source :: frames.items[frame_index].values[usize(instruction.right)]
|
||||
if (ir.aggregate(address.type)) memcopy!(address.cells, source.data.elements)
|
||||
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
|
||||
continue
|
||||
}
|
||||
@@ -421,7 +453,7 @@ run proc(
|
||||
}
|
||||
}
|
||||
if instruction.destination != ir.no_value {
|
||||
copy_value(&frames.items[frame_index].values[usize(instruction.destination)], value)
|
||||
copy_value(module, &frames.items[frame_index].values[usize(instruction.destination)], value)
|
||||
}
|
||||
}
|
||||
unreachable
|
||||
@@ -518,10 +550,10 @@ aggregate_view proc(type ir.Type, shape ir.ArrayId, cells []mut ir.Value) ir.Val
|
||||
}
|
||||
|
||||
@hide:file
|
||||
copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
||||
copy_value proc(module @ir.Module, destination @mut ir.Value, source ir.Value) void {
|
||||
if (ir.aggregate(source.type)) {
|
||||
# aggregate destinations already own disjoint fixed segments; never install an alias.
|
||||
memcopy!(destination.data.elements, source.data.elements)
|
||||
ir.copy_aggregate(module, destination^, source)
|
||||
} else {
|
||||
destination^ = source
|
||||
}
|
||||
|
||||
@@ -1463,3 +1463,126 @@ inclusive_unsigned_range_reaches_the_maximum_without_incrementing_past_it test {
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(u64(2), outcome.value.data.unsigned)
|
||||
}
|
||||
|
||||
union_storage_overlays_nested_record_and_array_payloads_and_copies_by_value test {
|
||||
module := ir.init(mem.c_allocator)
|
||||
defer ir.deinit(&module)
|
||||
store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&store)
|
||||
record_fields [2]ir.RecordField :: [ir.RecordField{ type = .u8 }, ir.RecordField{ type = .u32 }]
|
||||
record :: try ir.add_record(&module, record_fields[..])
|
||||
bytes :: try add_array(&module, .u8, ir.no_array, 8)
|
||||
variants [2]ir.RecordField :: [
|
||||
ir.RecordField{ type = .record, array_type = record },
|
||||
ir.RecordField{ type = .array, array_type = bytes },
|
||||
]
|
||||
shape :: try ir.add_union(&module, variants[..], false)
|
||||
identity :: try add_function(&module, &[], 0, .record)
|
||||
_ = try ir.add_parameter(&module, identity, .record, shape)
|
||||
module.functions.items[usize(identity)].result_array_type = shape
|
||||
try emit(&module, identity, ir.Instruction{ op = .load_local, type = .record, array_type = shape, destination = ir.ValueId(0) })
|
||||
return_value(&module, identity, ir.ValueId(0))
|
||||
main :: try add_function(&module, &[], 0, .u32)
|
||||
_ = try ir.add_local(&module, main, .record, shape)
|
||||
module.entry = main
|
||||
try emit(&module, main, ir.Instruction{ op = .constant, type = .u8, destination = ir.ValueId(0), constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 1 } } })
|
||||
try emit(&module, main, ir.Instruction{ op = .constant, type = .u32, destination = ir.ValueId(1), constant = ir.Value{ type = .u32, data = ir.ValueData{ unsigned = 287454020 } } })
|
||||
block :: &module.functions.items[usize(main)].blocks.items[0]
|
||||
try arraylist.append(&block.arguments, ir.ValueId(0))
|
||||
try arraylist.append(&block.arguments, ir.ValueId(1))
|
||||
try emit(&module, main, ir.Instruction{ op = .record_literal, type = .record, array_type = record, destination = ir.ValueId(2), arguments_count = 2 })
|
||||
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(3), left = ir.ValueId(2) })
|
||||
try emit(&module, main, ir.Instruction{ op = .store_local, left = ir.ValueId(3) })
|
||||
try emit(&module, main, function_value(4, identity))
|
||||
try arraylist.append(&block.arguments, ir.ValueId(3))
|
||||
try emit(&module, main, ir.Instruction{ op = .call, type = .record, array_type = shape, destination = ir.ValueId(5), left = ir.ValueId(4), arguments_start = 2, arguments_count = 1 })
|
||||
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .record, array_type = shape, destination = ir.ValueId(6) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field_address, type = .address, pointee = .array, array_type = bytes, destination = ir.ValueId(7), left = ir.ValueId(6), slot = 1 })
|
||||
try emit(&module, main, unsigned(8, 4))
|
||||
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .u8, destination = ir.ValueId(9), left = ir.ValueId(7), right = ir.ValueId(8) })
|
||||
try emit(&module, main, ir.Instruction{ op = .constant, type = .u8, destination = ir.ValueId(10), constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 153 } } })
|
||||
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(9), right = ir.ValueId(10) })
|
||||
try emit(&module, main, ir.Instruction{ op = .load_local, type = .record, array_type = shape, destination = ir.ValueId(11) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = record, destination = ir.ValueId(12), left = ir.ValueId(11) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .u32, destination = ir.ValueId(13), left = ir.ValueId(12), slot = 1 })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = record, destination = ir.ValueId(14), left = ir.ValueId(5) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .u32, destination = ir.ValueId(15), left = ir.ValueId(14), slot = 1 })
|
||||
try emit(&module, main, ir.Instruction{ op = .subtract, type = .u32, destination = ir.ValueId(16), left = ir.ValueId(13), right = ir.ValueId(15) })
|
||||
return_value(&module, main, ir.ValueId(16))
|
||||
outcome := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(u64(85), outcome.value.data.unsigned)
|
||||
}
|
||||
|
||||
tagged_unions_preserve_actual_tags_and_trap_inactive_value_and_address_projections test {
|
||||
operations [2]ir.Op :: [.field, .field_address]
|
||||
for (operations) |operation| {
|
||||
module := ir.init(mem.c_allocator)
|
||||
defer ir.deinit(&module)
|
||||
store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&store)
|
||||
tag :: ir.Value{ type = .i8, data = ir.ValueData{ integer = -1 } }
|
||||
variants [2]ir.RecordField :: [
|
||||
ir.RecordField{ type = .int, tag = tag },
|
||||
ir.RecordField{ type = .int, tag = ir.Value{ type = .i8, data = ir.ValueData{ integer = 37 } } },
|
||||
]
|
||||
shape :: try ir.add_union(&module, variants[..], true)
|
||||
main :: try add_function(&module, &[], 0, .i8)
|
||||
_ = try ir.add_local(&module, main, .record, shape)
|
||||
module.entry = main
|
||||
try emit(&module, main, integer(0, 42))
|
||||
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0), constant = tag })
|
||||
try emit(&module, main, ir.Instruction{ op = .union_tag, type = .i8, destination = ir.ValueId(2), left = ir.ValueId(1) })
|
||||
return_value(&module, main, ir.ValueId(2))
|
||||
outcome := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&outcome)
|
||||
try testing.expect(outcome.trap == null)
|
||||
try testing.expect_equal(i64(-1), outcome.value.data.integer)
|
||||
base := ir.ValueId(1)
|
||||
if (operation == .field_address) {
|
||||
try emit(&module, main, ir.Instruction{ op = .store_local, left = base })
|
||||
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .record, array_type = shape, destination = ir.ValueId(3) })
|
||||
base = ir.ValueId(3)
|
||||
}
|
||||
span :: diagnostics.Span{ start = 11, end = 15 }
|
||||
type :: if (operation == .field) ir.Type.int else ir.Type.address
|
||||
try emit(&module, main, ir.Instruction{ op = operation, type = type, pointee = .int, destination = ir.ValueId(4), left = base, slot = 1, span = span })
|
||||
trapped := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&trapped)
|
||||
try expect_trap(trapped, &store, .inactive_union_field, span)
|
||||
}
|
||||
}
|
||||
|
||||
union_scalar_reinterpretation_and_zero_leaf_payloads_keep_their_shapes test {
|
||||
module := ir.init(mem.c_allocator)
|
||||
defer ir.deinit(&module)
|
||||
store := diagnostics.init(mem.c_allocator)
|
||||
defer diagnostics.deinit(&store)
|
||||
empty :: try ir.add_record(&module, &[])
|
||||
variants [3]ir.RecordField :: [
|
||||
ir.RecordField{ type = .float },
|
||||
ir.RecordField{ type = .u64 },
|
||||
ir.RecordField{ type = .record, array_type = empty },
|
||||
]
|
||||
shape :: try ir.add_union(&module, variants[..], false)
|
||||
main :: try add_function(&module, &[], 0, .u64)
|
||||
module.entry = main
|
||||
try emit(&module, main, ir.Instruction{ op = .constant, type = .float, destination = ir.ValueId(0), constant = ir.Value{ type = .float, data = ir.ValueData{ real = 1.0 } } })
|
||||
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .u64, destination = ir.ValueId(2), left = ir.ValueId(1), slot = 1 })
|
||||
return_value(&module, main, ir.ValueId(2))
|
||||
bits := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&bits)
|
||||
try testing.expect(bits.trap == null)
|
||||
try testing.expect_equal(u64(4607182418800017408), bits.value.data.unsigned)
|
||||
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = empty, destination = ir.ValueId(3), left = ir.ValueId(1), slot = 2 })
|
||||
module.functions.items[usize(main)].result = .record
|
||||
module.functions.items[usize(main)].result_array_type = empty
|
||||
return_value(&module, main, ir.ValueId(3))
|
||||
zero := try run(mem.c_allocator, &module, &store)
|
||||
defer deinit(&zero)
|
||||
try testing.expect(zero.trap == null)
|
||||
try testing.expect_equal(empty, zero.value.array_type)
|
||||
try testing.expect_equal(usize(0), zero.value.data.elements.len)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user