feat: support enums, distinct types, unions and matching
This commit is contained in:
+189
-9
@@ -85,6 +85,7 @@ ValueData :: union {
|
||||
Value :: struct {
|
||||
type Type = .void
|
||||
array_type ArrayId = no_array
|
||||
raw bool = false
|
||||
data ValueData = ValueData{ integer = 0 }
|
||||
}
|
||||
|
||||
@@ -92,10 +93,12 @@ RecordField :: struct {
|
||||
type Type
|
||||
array_type ArrayId = no_array
|
||||
offset usize = 0
|
||||
byte_offset usize = 0
|
||||
tag Value = Value{}
|
||||
}
|
||||
|
||||
#! fixed recursive aggregate shape; constructors derive the flattened scalar-leaf count.
|
||||
#! record fields are owned by the module; array metadata retains its existing representation.
|
||||
#! fixed recursive aggregate shape; cells count interpreter slots, byte_size describes native layout.
|
||||
#! unions retain overlapping byte cells; ordinary aggregates retain flattened scalar leaves.
|
||||
ArrayType :: struct {
|
||||
kind Type = .array
|
||||
element Type = .invalid
|
||||
@@ -104,6 +107,11 @@ ArrayType :: struct {
|
||||
cells usize = 0
|
||||
mutable bool = false
|
||||
fields []mut RecordField = &[]
|
||||
is_union bool = false
|
||||
tag_type Type = .void
|
||||
payload_offset usize = 0
|
||||
byte_size usize = 0
|
||||
alignment usize = 1
|
||||
}
|
||||
|
||||
#! maps a local slot or temporary to its preallocated, by-value frame segment.
|
||||
@@ -123,6 +131,8 @@ Op :: enum {
|
||||
load_global
|
||||
array_literal
|
||||
record_literal
|
||||
union_literal
|
||||
union_tag
|
||||
field
|
||||
index
|
||||
address_local
|
||||
@@ -167,6 +177,7 @@ TerminatorKind :: enum {
|
||||
trap,
|
||||
jump,
|
||||
branch,
|
||||
unreachable,
|
||||
}
|
||||
|
||||
Terminator :: struct {
|
||||
@@ -266,6 +277,8 @@ add_array proc(
|
||||
cells :: len * width
|
||||
if (cells > divtrunc!(maxval!(usize), sizeof!(Value))) return .storage_limit_reached
|
||||
if (module.arrays.items.len >= usize(no_array)) return .storage_limit_reached
|
||||
stride :: storage_size(module, element, element_array)
|
||||
if (stride != 0 and len > divtrunc!(maxval!(usize), stride)) return .storage_limit_reached
|
||||
id :: ArrayId(module.arrays.items.len)
|
||||
try arraylist.append(&module.arrays, ArrayType{
|
||||
element = element,
|
||||
@@ -273,6 +286,8 @@ add_array proc(
|
||||
len = len,
|
||||
cells = cells,
|
||||
mutable = mutable,
|
||||
byte_size = len * stride,
|
||||
alignment = storage_alignment(module, element, element_array),
|
||||
})
|
||||
return id
|
||||
}
|
||||
@@ -286,19 +301,35 @@ add_record proc(
|
||||
owned :: try mem.alloc(RecordField, module.allocator, fields.len)
|
||||
errdefer mem.free(module.allocator, owned)
|
||||
cells usize := 0
|
||||
bytes usize := 0
|
||||
alignment usize := 1
|
||||
for (fields) |field, index| {
|
||||
debug.assert(aggregate(field.type) == (field.array_type != no_array))
|
||||
width :: if (aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||
if (width > divtrunc!(maxval!(usize), sizeof!(Value)) - cells) return .storage_limit_reached
|
||||
owned[index] = RecordField{ type = field.type, array_type = field.array_type, offset = cells }
|
||||
field_alignment :: storage_alignment(module, field.type, field.array_type)
|
||||
if (field_alignment > alignment) alignment = field_alignment
|
||||
if (bytes > maxval!(usize) - (field_alignment - 1)) return .storage_limit_reached
|
||||
bytes = aligned_size(bytes, field_alignment)
|
||||
size :: storage_size(module, field.type, field.array_type)
|
||||
if (size > maxval!(usize) - bytes) return .storage_limit_reached
|
||||
owned[index] = RecordField{
|
||||
type = field.type, array_type = field.array_type, offset = cells,
|
||||
byte_offset = bytes, tag = field.tag,
|
||||
}
|
||||
bytes += size
|
||||
cells += width
|
||||
}
|
||||
id :: ArrayId(module.arrays.items.len)
|
||||
try arraylist.append(&module.arrays, ArrayType{ kind = .record, fields = owned, cells = cells })
|
||||
if (bytes > maxval!(usize) - (alignment - 1)) return .storage_limit_reached
|
||||
try arraylist.append(&module.arrays, ArrayType{
|
||||
kind = .record, fields = owned, cells = cells,
|
||||
byte_size = aligned_size(bytes, alignment), alignment = alignment,
|
||||
})
|
||||
return id
|
||||
}
|
||||
|
||||
#! returns a borrowed child value from flattened aggregate storage, preserving zero-cell shapes.
|
||||
#! returns a borrowed child, including a raw view when projecting overlapping union storage.
|
||||
aggregate_item proc(module @Module, value Value, index usize) Value {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
type := shape.element
|
||||
@@ -310,13 +341,162 @@ aggregate_item proc(module @Module, value Value, index usize) Value {
|
||||
type = field.type
|
||||
child = field.array_type
|
||||
start = field.offset
|
||||
if (value.raw) start = field.byte_offset
|
||||
if (shape.is_union) start = shape.payload_offset
|
||||
} else {
|
||||
if (aggregate(type)) width = module.arrays.items[usize(child)].cells
|
||||
if (value.raw) width = storage_size(module, type, child)
|
||||
else if (aggregate(type)) width = module.arrays.items[usize(child)].cells
|
||||
start = index * width
|
||||
}
|
||||
if (!aggregate(type)) return value.data.elements[start]
|
||||
width = module.arrays.items[usize(child)].cells
|
||||
return Value{ type = type, array_type = child, data = ValueData{ elements = value.data.elements[start..start + width] } }
|
||||
raw :: value.raw or shape.is_union
|
||||
if (!aggregate(type)) {
|
||||
if (raw) return read_storage(module, type, child, value.data.elements[start..], true)
|
||||
return value.data.elements[start]
|
||||
}
|
||||
width = if (raw) storage_size(module, type, child) else module.arrays.items[usize(child)].cells
|
||||
return Value{
|
||||
type = type, array_type = child, raw = raw,
|
||||
data = ValueData{ elements = value.data.elements[start..start + width] },
|
||||
}
|
||||
}
|
||||
|
||||
#! copies variant descriptors; all payloads share one eight-byte-aligned storage region.
|
||||
add_union proc(module @mut Module, fields []RecordField, tagged bool) ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||
if (module.arrays.items.len >= usize(no_array)) return .storage_limit_reached
|
||||
owned :: try mem.alloc(RecordField, module.allocator, fields.len)
|
||||
errdefer mem.free(module.allocator, owned)
|
||||
size usize := 0
|
||||
for (fields) |field, index| {
|
||||
width :: storage_size(module, field.type, field.array_type)
|
||||
if (width > size) size = width
|
||||
owned[index] = field
|
||||
owned[index].offset = if (tagged) 8 else 0
|
||||
owned[index].byte_offset = owned[index].offset
|
||||
}
|
||||
prefix usize :: if (tagged) 8 else 0
|
||||
if (size > divtrunc!(maxval!(usize), sizeof!(Value)) - 15) return .storage_limit_reached
|
||||
size = aligned_size(size, 8) + prefix
|
||||
id :: ArrayId(module.arrays.items.len)
|
||||
tag_type :: if (tagged and fields.len != 0) fields[0].tag.type else Type.void
|
||||
try arraylist.append(&module.arrays, ArrayType{
|
||||
kind = .record, fields = owned, is_union = true,
|
||||
tag_type = tag_type,
|
||||
payload_offset = prefix, cells = size, byte_size = size, alignment = 8,
|
||||
})
|
||||
return id
|
||||
}
|
||||
|
||||
@hide:file
|
||||
aligned_size proc(size, alignment usize) usize {
|
||||
return divtrunc!(size + alignment - 1, alignment) * alignment
|
||||
}
|
||||
|
||||
storage_alignment proc(module @Module, type Type, shape ArrayId) usize {
|
||||
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
|
||||
bits :: integer_bits(type)
|
||||
if (bits != 0) return divtrunc!(bits, 8)
|
||||
return if (type == .float or type == .string or type == .function) 8 else 1
|
||||
}
|
||||
|
||||
storage_size proc(module @Module, type Type, shape ArrayId) usize {
|
||||
if (aggregate(type)) return module.arrays.items[usize(shape)].byte_size
|
||||
bits :: integer_bits(type)
|
||||
if (bits != 0) return divtrunc!(bits, 8)
|
||||
return match type {
|
||||
.float, .function: 8
|
||||
.string: 16
|
||||
.bool: 1
|
||||
.void: 0
|
||||
else: unreachable
|
||||
}
|
||||
}
|
||||
|
||||
#! loads a scalar or borrows an aggregate from byte cells without allocating.
|
||||
read_storage proc(module @Module, type Type, shape ArrayId, cells []mut Value, raw bool) Value {
|
||||
if (aggregate(type)) {
|
||||
return Value{ type = type, array_type = shape, raw = raw, data = ValueData{ elements = cells[..storage_size(module, type, shape)] } }
|
||||
}
|
||||
bits u64 := 0
|
||||
size :: storage_size(module, type, shape)
|
||||
count :: if (size > 8) usize(8) else size
|
||||
for (0..count) |i| bits += cells[i].data.unsigned << u64(i * 8)
|
||||
data := ValueData{ unsigned = bits }
|
||||
match type {
|
||||
.i8: data = ValueData{ integer = i64(ptrcast!(@i8, &bits)^) }
|
||||
.i16: data = ValueData{ integer = i64(ptrcast!(@i16, &bits)^) }
|
||||
.i32: data = ValueData{ integer = i64(ptrcast!(@i32, &bits)^) }
|
||||
.int, .isize: data = ValueData{ integer = ptrcast!(@i64, &bits)^ }
|
||||
.float: data = ValueData{ real = ptrcast!(@f64, &bits)^ }
|
||||
.bool: data = ValueData{ boolean = (bits & 1) != 0 }
|
||||
.string: data = ValueData{ string = StringId(bits) }
|
||||
.function: data = ValueData{ function = FunctionId(bits) }
|
||||
.void: return Value{}
|
||||
else: {}
|
||||
}
|
||||
return Value{ type = type, data = data }
|
||||
}
|
||||
|
||||
#! stores a value in shared byte cells; recursive aggregates preserve their native field padding.
|
||||
write_storage proc(module @Module, cells []mut Value, value Value) void {
|
||||
if (aggregate(value.type)) {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
if (value.raw or shape.is_union) {
|
||||
memcopy!(cells[..shape.byte_size], value.data.elements)
|
||||
return
|
||||
}
|
||||
count :: if (shape.kind == .record) shape.fields.len else shape.len
|
||||
for (0..count) |i| {
|
||||
offset :: if (shape.kind == .record) shape.fields[i].byte_offset else i * storage_size(module, shape.element, shape.element_array)
|
||||
write_storage(module, cells[offset..], aggregate_item(module, value, i))
|
||||
}
|
||||
return
|
||||
}
|
||||
bits u64 := 0
|
||||
match value.type {
|
||||
.float: bits = ptrcast!(@u64, &value.data.real)^
|
||||
.bool: bits = if (value.data.boolean) 1 else 0
|
||||
.string: bits = u64(value.data.string)
|
||||
.function: bits = u64(value.data.function)
|
||||
.void: return
|
||||
else: bits = ptrcast!(@u64, &value.data.integer)^
|
||||
}
|
||||
size :: storage_size(module, value.type, value.array_type)
|
||||
for (0..size) |i| {
|
||||
byte u64 := 0
|
||||
if (i < 8) byte = (bits >> u64(i * 8)) & 255
|
||||
else byte = (u64(string_bytes(module, value.data.string).len) >> u64((i - 8) * 8)) & 255
|
||||
cells[i] = Value{ type = .u8, data = ValueData{ unsigned = byte } }
|
||||
}
|
||||
}
|
||||
|
||||
#! converts raw aggregate projections into the caller's preallocated flattened segment.
|
||||
copy_aggregate proc(module @Module, destination Value, source Value) void {
|
||||
shape :: module.arrays.items[usize(source.array_type)]
|
||||
if (!source.raw or shape.is_union) {
|
||||
memcopy!(destination.data.elements, source.data.elements)
|
||||
return
|
||||
}
|
||||
count :: if (shape.kind == .record) shape.fields.len else shape.len
|
||||
for (0..count) |i| {
|
||||
child :: aggregate_item(module, source, i)
|
||||
if (aggregate(child.type)) copy_aggregate(module, aggregate_item(module, destination, i), child)
|
||||
else {
|
||||
offset :: if (shape.kind == .record) shape.fields[i].offset else i
|
||||
destination.data.elements[offset] = child
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
union_tag proc(module @Module, value Value) Value {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
return read_storage(module, shape.tag_type, no_array, value.data.elements, true)
|
||||
}
|
||||
|
||||
union_active proc(module @Module, value Value, field usize) bool {
|
||||
shape :: module.arrays.items[usize(value.array_type)]
|
||||
if (shape.tag_type == .void) return true
|
||||
tag :: union_tag(module, value)
|
||||
return tag.data.unsigned == shape.fields[field].tag.data.unsigned
|
||||
}
|
||||
|
||||
@hide:file
|
||||
|
||||
Reference in New Issue
Block a user