655 lines
23 KiB
Plaintext
655 lines
23 KiB
Plaintext
import "@std/mem"
|
|
import "@std/arraylist"
|
|
import "@std/debug"
|
|
import "@source/diagnostics"
|
|
|
|
FunctionId :: distinct u32
|
|
GlobalId :: distinct u32
|
|
LocalId :: distinct u32
|
|
ValueId :: distinct u32
|
|
BlockId :: distinct u32
|
|
StringId :: distinct u32
|
|
ArrayId :: distinct u32
|
|
no_array :: maxval!(ArrayId)
|
|
no_value :: maxval!(ValueId)
|
|
|
|
Type :: enum {
|
|
invalid
|
|
int
|
|
float
|
|
bool
|
|
string
|
|
void
|
|
function
|
|
array
|
|
record
|
|
address
|
|
pointer
|
|
i8, i16, i32, isize
|
|
u8, u16, u32, u64, usize
|
|
}
|
|
|
|
aggregate proc(type Type) bool {
|
|
return type == .array or type == .record
|
|
}
|
|
|
|
#! integer storage widths for the supported 64-bit target; zero for noninteger types.
|
|
integer_bits proc(type Type) usize {
|
|
return match type {
|
|
.i8, .u8: 8
|
|
.i16, .u16: 16
|
|
.i32, .u32: 32
|
|
.int, .isize, .usize, .u64: 64
|
|
else: 0
|
|
}
|
|
}
|
|
|
|
unsigned_integer proc(type Type) bool {
|
|
return type == .u8 or type == .u16 or type == .u32 or type == .u64 or type == .usize
|
|
}
|
|
|
|
integer_max proc(type Type) u64 {
|
|
return match type {
|
|
.i8: 127
|
|
.i16: 32767
|
|
.i32: 2147483647
|
|
.int, .isize: u64(maxval!(i64))
|
|
.u8: 255
|
|
.u16: 65535
|
|
.u32: 4294967295
|
|
.u64, .usize: maxval!(u64)
|
|
else: unreachable
|
|
}
|
|
}
|
|
|
|
integer_min proc(type Type) i64 {
|
|
return match type {
|
|
.i8: -128
|
|
.i16: -32768
|
|
.i32: -2147483648
|
|
.int, .isize: minval!(i64)
|
|
.u8, .u16, .u32, .u64, .usize: 0
|
|
else: unreachable
|
|
}
|
|
}
|
|
|
|
ValueData :: union {
|
|
integer i64
|
|
# source pointers use run-wide handles; internal addresses use frame-local IDs below.
|
|
unsigned u64
|
|
real f64
|
|
boolean bool
|
|
string StringId
|
|
function FunctionId
|
|
elements []mut Value
|
|
address u32
|
|
}
|
|
Value :: struct {
|
|
type Type = .void
|
|
array_type ArrayId = no_array
|
|
raw bool = false
|
|
# interpreter poison survives copies; native storage uses LLVM poison directly.
|
|
uninitialized bool = false
|
|
data ValueData = ValueData{ integer = 0 }
|
|
}
|
|
|
|
RecordField :: struct {
|
|
type Type
|
|
array_type ArrayId = no_array
|
|
offset usize = 0
|
|
byte_offset usize = 0
|
|
tag Value = Value{}
|
|
}
|
|
|
|
#! 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
|
|
element_array ArrayId = no_array
|
|
len usize = 0
|
|
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.
|
|
ArrayStorage :: struct {
|
|
index u32
|
|
array_type ArrayId
|
|
offset usize
|
|
local bool
|
|
}
|
|
|
|
Op :: enum {
|
|
constant
|
|
undefined_value
|
|
load_local
|
|
store_local
|
|
assign_local
|
|
reset_local
|
|
load_global
|
|
array_literal
|
|
record_literal
|
|
union_literal
|
|
union_tag
|
|
field
|
|
index
|
|
address_local
|
|
address_global
|
|
index_address
|
|
field_address
|
|
load_address
|
|
store_address
|
|
pointer_from_address
|
|
pointer_address
|
|
pointer_offset
|
|
pointer_cast
|
|
string_pointer
|
|
string_length
|
|
slice_check
|
|
sentinel_check
|
|
optional_check
|
|
memory_copy, memory_set
|
|
negate
|
|
convert
|
|
logical_not
|
|
bit_not, bit_and, bit_or, bit_xor
|
|
shift_left, shift_right, shift_left_saturating
|
|
add, subtract
|
|
multiply, divide
|
|
call
|
|
equal, not_equal
|
|
less, less_equal
|
|
greater, greater_equal
|
|
}
|
|
|
|
Instruction :: struct {
|
|
op Op
|
|
type Type = .void
|
|
# pointer operations describe their pointee here, not aggregate result storage.
|
|
array_type ArrayId = no_array
|
|
pointee Type = .invalid
|
|
span diagnostics.Span = diagnostics.Span{ start = 0, end = 0 }
|
|
destination ValueId = no_value
|
|
left ValueId = no_value
|
|
right ValueId = no_value
|
|
extra ValueId = no_value
|
|
slot u32 = 0
|
|
constant Value = Value{}
|
|
arguments_start usize = 0
|
|
arguments_count usize = 0
|
|
}
|
|
|
|
TerminatorKind :: enum {
|
|
return_value,
|
|
trap,
|
|
jump,
|
|
branch,
|
|
unreachable,
|
|
}
|
|
|
|
Terminator :: struct {
|
|
kind TerminatorKind = .return_value
|
|
value ValueId = no_value
|
|
target BlockId = BlockId(0)
|
|
alternative BlockId = BlockId(0)
|
|
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
|
span diagnostics.Span = diagnostics.Span{ start = 0, end = 0 }
|
|
}
|
|
|
|
Block :: struct {
|
|
instructions arraylist.ArrayList(Instruction)
|
|
arguments arraylist.ArrayList(ValueId)
|
|
terminator Terminator = Terminator{}
|
|
}
|
|
|
|
#! construction derives storage segments, aggregate totals, value counts, and address presence.
|
|
#! producers use add_local, add_parameter, and emit; derived metadata is read-only to callers.
|
|
Function :: struct {
|
|
parameters arraylist.ArrayList(Type)
|
|
# owned by the module allocator; empty for interpreted/native-defined functions.
|
|
external_name []u8 = ""
|
|
locals arraylist.ArrayList(Type)
|
|
result Type = .void
|
|
result_array_type ArrayId = no_array
|
|
result_fallible bool = false
|
|
array_storage arraylist.ArrayList(ArrayStorage)
|
|
aggregate_cells usize = 0
|
|
has_addresses bool = false
|
|
blocks arraylist.ArrayList(Block)
|
|
entry BlockId = BlockId(0)
|
|
value_count usize = 0
|
|
span diagnostics.Span = diagnostics.Span{ start = 0, end = 0 }
|
|
}
|
|
|
|
Global :: struct {
|
|
type Type
|
|
array_type ArrayId = no_array
|
|
initializer FunctionId
|
|
span diagnostics.Span
|
|
}
|
|
|
|
#! retains the original allocation for freeing while exposing only decoded bytes.
|
|
StringStorage :: struct {
|
|
allocation []mut u8
|
|
len usize
|
|
}
|
|
|
|
#! owns executable storage, including decoded strings; no AST or binding table is retained.
|
|
#! spans and diagnostic IDs refer to the caller's source and diagnostic store.
|
|
Module :: struct {
|
|
allocator mem.Allocator
|
|
functions arraylist.ArrayList(Function)
|
|
globals arraylist.ArrayList(Global)
|
|
strings arraylist.ArrayList(StringStorage)
|
|
arrays arraylist.ArrayList(ArrayType)
|
|
entry ?FunctionId = null
|
|
entry_diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
|
}
|
|
|
|
#! retains `allocator` for executable storage; release the returned module with deinit.
|
|
init proc(allocator mem.Allocator) Module {
|
|
return Module{
|
|
allocator = allocator,
|
|
functions = arraylist.init(allocator),
|
|
globals = arraylist.init(allocator),
|
|
strings = arraylist.init(allocator),
|
|
arrays = arraylist.init(allocator),
|
|
}
|
|
}
|
|
|
|
#! retains `allocator` in its lists; transfer the function to a module using the same allocator for cleanup with deinit.
|
|
init_function proc(allocator mem.Allocator) Function {
|
|
return Function{
|
|
parameters = arraylist.init(allocator),
|
|
locals = arraylist.init(allocator),
|
|
blocks = arraylist.init(allocator),
|
|
array_storage = arraylist.init(allocator),
|
|
}
|
|
}
|
|
|
|
#! retains `allocator` in its lists; transfer the block to a module function for cleanup with deinit.
|
|
init_block proc(allocator mem.Allocator) Block {
|
|
return Block{
|
|
instructions = arraylist.init(allocator),
|
|
arguments = arraylist.init(allocator),
|
|
}
|
|
}
|
|
|
|
#! adds a fixed shape and derives its flattened storage size; child shapes must already exist.
|
|
#! scalar elements use no_array; zero-leaf children retain their logical lengths.
|
|
add_array proc(
|
|
module @mut Module,
|
|
element Type,
|
|
element_array ArrayId,
|
|
len usize,
|
|
mutable bool,
|
|
) ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
debug.assert(aggregate(element) == (element_array != no_array))
|
|
width :: if (aggregate(element)) module.arrays.items[usize(element_array)].cells else 1
|
|
if (width != 0 and len > divtrunc!(maxval!(usize), width)) return .storage_limit_reached
|
|
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,
|
|
element_array = element_array,
|
|
len = len,
|
|
cells = cells,
|
|
mutable = mutable,
|
|
byte_size = len * stride,
|
|
alignment = storage_alignment(module, element, element_array),
|
|
})
|
|
return id
|
|
}
|
|
|
|
#! copies declaration-order fields and derives their flattened offsets; child shapes must exist.
|
|
add_record proc(
|
|
module @mut Module,
|
|
fields []RecordField,
|
|
) 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)
|
|
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
|
|
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)
|
|
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, 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
|
|
child := shape.element_array
|
|
start usize := 0
|
|
width usize := 1
|
|
if (shape.kind == .record) {
|
|
field :: shape.fields[index]
|
|
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 (value.raw) width = storage_size(module, type, child)
|
|
else if (aggregate(type)) width = module.arrays.items[usize(child)].cells
|
|
start = index * width
|
|
}
|
|
raw :: value.raw or shape.is_union
|
|
if (!aggregate(type)) {
|
|
if (raw) return read_storage(module, type, child, value.data.elements[start..], true)
|
|
if (value.data.elements[start].uninitialized) return Value{ type = type, uninitialized = 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 or type == .pointer) 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, .pointer: 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| {
|
|
if (cells[i].uninitialized) return Value{ type = type, uninitialized = true }
|
|
}
|
|
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{ unsigned = 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 = if (value.data.function == maxval!(FunctionId)) maxval!(u64) else u64(value.data.function)
|
|
.pointer: bits = value.data.unsigned
|
|
.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, uninitialized = value.uninitialized, 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
|
|
prepare_storage proc(module @Module, function @mut Function, index u32, shape ArrayId, local bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
cells :: module.arrays.items[usize(shape)].cells
|
|
if (cells > divtrunc!(maxval!(usize), sizeof!(Value)) - function.aggregate_cells) return .storage_limit_reached
|
|
try arraylist.append(&function.array_storage, ArrayStorage{
|
|
index = index,
|
|
array_type = shape,
|
|
offset = function.aggregate_cells,
|
|
local = local,
|
|
})
|
|
function.aggregate_cells += cells
|
|
}
|
|
|
|
#! creates a local and its disjoint by-value backing together; scalar locals use no_array.
|
|
add_local proc(
|
|
module @mut Module,
|
|
function FunctionId,
|
|
type Type,
|
|
shape ArrayId,
|
|
) LocalId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
debug.assert(aggregate(type) == (shape != no_array))
|
|
body :: &module.functions.items[usize(function)]
|
|
if (body.locals.items.len >= usize(maxval!(LocalId))) return .storage_limit_reached
|
|
id :: LocalId(body.locals.items.len)
|
|
try arraylist.append(&body.locals, type)
|
|
errdefer _ = arraylist.pop(&body.locals)
|
|
if (aggregate(type)) try prepare_storage(module, body, u32(id), shape, true)
|
|
return id
|
|
}
|
|
|
|
#! creates a parameter and its local backing; all parameters precede non-parameter locals.
|
|
add_parameter proc(
|
|
module @mut Module,
|
|
function FunctionId,
|
|
type Type,
|
|
shape ArrayId,
|
|
) LocalId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
body :: &module.functions.items[usize(function)]
|
|
debug.assert(body.parameters.items.len == body.locals.items.len)
|
|
try arraylist.append(&body.parameters, type)
|
|
errdefer _ = arraylist.pop(&body.parameters)
|
|
return try add_local(module, function, type, shape)
|
|
}
|
|
|
|
#! appends an instruction and derives its result count, aggregate backing, and address presence.
|
|
#! no_value requests a fresh result; explicit result IDs support non-SSA definitions across blocks.
|
|
#! repeated aggregate results must retain their shape and reuse their existing segment.
|
|
#! failures leave instruction and storage lengths, totals, counts, and flags unchanged.
|
|
emit proc(
|
|
module @mut Module,
|
|
function FunctionId,
|
|
block BlockId,
|
|
instruction Instruction,
|
|
) ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
body :: &module.functions.items[usize(function)]
|
|
target :: &body.blocks.items[usize(block)]
|
|
operation := instruction
|
|
produces_value :: (
|
|
operation.op != .store_local
|
|
and operation.op != .assign_local
|
|
and operation.op != .reset_local
|
|
and operation.op != .store_address
|
|
and operation.op != .slice_check
|
|
and operation.op != .sentinel_check
|
|
and operation.op != .optional_check
|
|
and operation.op != .memory_copy
|
|
and operation.op != .memory_set
|
|
)
|
|
count := body.value_count
|
|
if (produces_value) {
|
|
if (operation.destination == no_value) {
|
|
if (count >= usize(no_value)) return .storage_limit_reached
|
|
operation.destination = ValueId(count)
|
|
}
|
|
if (usize(operation.destination) >= count) count = usize(operation.destination) + 1
|
|
} else {
|
|
debug.assert(operation.destination == no_value)
|
|
}
|
|
try arraylist.append(&target.instructions, operation)
|
|
errdefer _ = arraylist.pop(&target.instructions)
|
|
if (produces_value and aggregate(operation.type)) {
|
|
debug.assert(operation.array_type != no_array)
|
|
registered := false
|
|
if (usize(operation.destination) < body.value_count) {
|
|
for (body.array_storage.items) |storage| {
|
|
if (storage.local or storage.index != u32(operation.destination)) continue
|
|
debug.assert(storage.array_type == operation.array_type)
|
|
registered = true
|
|
break
|
|
}
|
|
}
|
|
if (!registered) try prepare_storage(module, body, u32(operation.destination), operation.array_type, false)
|
|
}
|
|
body.value_count = count
|
|
if (operation.type == .address) body.has_addresses = true
|
|
return operation.destination
|
|
}
|
|
|
|
#! resolves a module-local ID to bytes borrowed until module deinit.
|
|
string_bytes proc(module @Module, id StringId) []u8 {
|
|
string :: module.strings.items[usize(id)]
|
|
return string.allocation[..string.len]
|
|
}
|
|
|
|
#! releases all executable storage using the retained allocator.
|
|
deinit proc(module @mut Module) void {
|
|
for (0..(module.functions.items.len)) |i| {
|
|
function :: &module.functions.items[i]
|
|
for (0..(function.blocks.items.len)) |j| {
|
|
block :: &function.blocks.items[j]
|
|
arraylist.deinit(&block.instructions)
|
|
arraylist.deinit(&block.arguments)
|
|
}
|
|
arraylist.deinit(&function.parameters)
|
|
arraylist.deinit(&function.locals)
|
|
arraylist.deinit(&function.blocks)
|
|
arraylist.deinit(&function.array_storage)
|
|
if (function.external_name.len != 0) mem.free(module.allocator, function.external_name)
|
|
}
|
|
for (module.strings.items) |string|
|
|
mem.free(module.allocator, string.allocation)
|
|
|
|
for (module.arrays.items) |shape| {
|
|
if (shape.kind == .record) mem.free(module.allocator, shape.fields)
|
|
}
|
|
arraylist.deinit(&module.functions)
|
|
arraylist.deinit(&module.globals)
|
|
arraylist.deinit(&module.strings)
|
|
arraylist.deinit(&module.arrays)
|
|
}
|