cc39c96fc0
Snapshot full union storage rather than choosing an overlapping field. Preserve structural union source syntax independently of nominal identity so direct literal arguments materialize and share normalized specialization keys. Reject uninitialized bytes and address-bearing member layouts.
2516 lines
125 KiB
Plaintext
2516 lines
125 KiB
Plaintext
import "@std/debug"
|
|
import "@std/mem"
|
|
import "@std/arraylist"
|
|
import "@std/strconv"
|
|
import "@std/enummap"
|
|
import "@ffi/c"
|
|
import "@source/ast"
|
|
import "@source/diagnostics"
|
|
import "@source/ir"
|
|
sema :: import "@source/sema"
|
|
typecheck :: import "@source/sema/check"
|
|
|
|
@hide:file
|
|
StorageKind :: enum { none, function, global, local }
|
|
|
|
@hide:file
|
|
Storage :: struct {
|
|
kind StorageKind = .none
|
|
index u32 = 0
|
|
}
|
|
|
|
@hide:file
|
|
LoopTargets :: struct {
|
|
continue_target ir.BlockId
|
|
exit ir.BlockId
|
|
cleanup_depth usize
|
|
break_reached bool = false
|
|
}
|
|
|
|
@hide:file
|
|
ValueTarget :: struct {
|
|
join ir.BlockId
|
|
slot u32
|
|
type typecheck.Type
|
|
cleanup_depth usize
|
|
reached bool = false
|
|
}
|
|
|
|
@hide:file
|
|
State :: struct {
|
|
tree @ast.Tree
|
|
resolution @sema.Resolution
|
|
checked @typecheck.Result
|
|
diagnostics @mut diagnostics.Store
|
|
module @mut ir.Module
|
|
storage []mut Storage
|
|
arrays []mut ir.ArrayId
|
|
arguments arraylist.ArrayList(ir.ValueId)
|
|
cleanups arraylist.ArrayList(ast.NodeId)
|
|
records []mut ir.ArrayId
|
|
function usize = 0
|
|
block ir.BlockId = ir.BlockId(0)
|
|
terminated bool = false
|
|
loop ?LoopTargets = null
|
|
value_target ?@mut ValueTarget = null
|
|
return_type typecheck.Type = typecheck.Type{}
|
|
fallthrough_block ?ast.NodeId = null
|
|
fallthrough_diagnostic ?diagnostics.DiagnosticId = null
|
|
}
|
|
|
|
#! lowers checked source into caller-owned executable storage; release with ir.deinit.
|
|
#! no frontend storage or source bytes are retained by the returned module.
|
|
#! retains `allocator` in the module; temporary lowering storage is released before returning.
|
|
lower proc(
|
|
allocator mem.Allocator,
|
|
tree @ast.Tree,
|
|
resolution @sema.Resolution,
|
|
checked @typecheck.Result,
|
|
diagnostic_store @mut diagnostics.Store,
|
|
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try lower_package(
|
|
allocator,
|
|
tree,
|
|
resolution,
|
|
checked,
|
|
tree.root,
|
|
diagnostic_store,
|
|
)
|
|
}
|
|
|
|
#! lowers the module arena, selecting an entry only from `root_package`.
|
|
#! retains `allocator` in the returned module; release it with ir.deinit; temporary lowering storage is released before returning.
|
|
lower_package proc(
|
|
allocator mem.Allocator,
|
|
tree @ast.Tree,
|
|
resolution @sema.Resolution,
|
|
checked @typecheck.Result,
|
|
root_package ast.NodeId,
|
|
diagnostic_store @mut diagnostics.Store,
|
|
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try lower_module(
|
|
allocator,
|
|
tree,
|
|
resolution,
|
|
checked,
|
|
root_package,
|
|
null,
|
|
null,
|
|
diagnostic_store,
|
|
)
|
|
}
|
|
|
|
#! lowers an isolated compile-time call wrapper without applying program entry-name rules.
|
|
#! the caller supplies a checked parameterless function, an empty `functions` list and owns the returned IR.
|
|
#! fills `functions` by IR FunctionId; global initializers map to maxval!(ast.NodeId).
|
|
#! retains `allocator` in the returned module; release it with ir.deinit; temporary lowering storage is released before returning.
|
|
lower_evaluation proc(
|
|
allocator mem.Allocator,
|
|
tree @ast.Tree,
|
|
resolution @sema.Resolution,
|
|
checked @typecheck.Result,
|
|
entry ast.NodeId,
|
|
functions @mut arraylist.ArrayList(ast.NodeId),
|
|
diagnostic_store @mut diagnostics.Store,
|
|
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try lower_module(
|
|
allocator,
|
|
tree,
|
|
resolution,
|
|
checked,
|
|
tree.root,
|
|
entry,
|
|
functions,
|
|
diagnostic_store,
|
|
)
|
|
}
|
|
|
|
@hide:file
|
|
lower_module proc(
|
|
allocator mem.Allocator,
|
|
tree @ast.Tree,
|
|
resolution @sema.Resolution,
|
|
checked @typecheck.Result,
|
|
root_package ast.NodeId,
|
|
entry ?ast.NodeId,
|
|
functions ?@mut arraylist.ArrayList(ast.NodeId),
|
|
diagnostic_store @mut diagnostics.Store,
|
|
) ir.Module ! (mem.AllocError | diagnostics.CapacityError) {
|
|
module := ir.init(allocator)
|
|
errdefer ir.deinit(&module)
|
|
storage :: try mem.alloc(Storage, allocator, ast.node_count(tree))
|
|
defer mem.free(allocator, storage)
|
|
mem.set(storage, Storage{})
|
|
arrays :: try mem.alloc(ir.ArrayId, allocator, typecheck.array_type_count(checked))
|
|
defer mem.free(allocator, arrays)
|
|
mem.set(arrays, ir.no_array)
|
|
records :: try mem.alloc(ir.ArrayId, allocator, typecheck.record_type_count(checked))
|
|
defer mem.free(allocator, records)
|
|
mem.set(records, ir.no_array)
|
|
state := State{
|
|
tree = tree,
|
|
resolution = resolution,
|
|
checked = checked,
|
|
diagnostics = diagnostic_store,
|
|
module = &module,
|
|
storage = storage,
|
|
arrays = arrays,
|
|
records = records,
|
|
arguments = arraylist.init(ir.ValueId, allocator),
|
|
cleanups = arraylist.init(ast.NodeId, allocator),
|
|
}
|
|
defer arraylist.deinit(&state.arguments)
|
|
defer arraylist.deinit(&state.cleanups)
|
|
|
|
# allocate every package identity before lowering any initializer or body.
|
|
for (0..ast.package_decl_count(tree)) |i| {
|
|
id :: ast.package_decl(tree, i)
|
|
match ast.decl_kind(tree, id) {
|
|
.function: {
|
|
if (ast.function_external(tree, id) and typecheck.type_of(checked, id).kind == .none) continue
|
|
function :: try add_function(&module, ast.node_span(tree, id))
|
|
if (functions) |declarations| try arraylist.append(declarations, id)
|
|
storage[usize(id)] = Storage{ kind = .function, index = u32(function) }
|
|
}
|
|
.statement: {
|
|
if (typecheck.type_of(checked, id).kind == .type_value) continue
|
|
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
|
initializer :: try add_function(&module, ast.node_span(tree, id))
|
|
if (functions) |declarations| try arraylist.append(declarations, maxval!(ast.NodeId))
|
|
global :: u32(module.globals.items.len)
|
|
try arraylist.append(&module.globals, ir.Global{
|
|
type = lower_type(&state, typecheck.type_of(checked, id)),
|
|
array_type = try array_shape(&state, typecheck.type_of(checked, id)),
|
|
initializer = initializer,
|
|
span = ast.node_span(tree, id),
|
|
})
|
|
storage[usize(id)] = Storage{ kind = .global, index = global }
|
|
}
|
|
.import, .invalid: {}
|
|
}
|
|
}
|
|
if (entry) |function| module.entry = ir.FunctionId(storage[usize(function)].index)
|
|
else try find_entry(&state, root_package)
|
|
for (0..ast.package_decl_count(tree)) |i| {
|
|
id :: ast.package_decl(tree, i)
|
|
match ast.decl_kind(tree, id) {
|
|
.function: {
|
|
if (storage[usize(id)].kind == .function) try lower_function(&state, id)
|
|
}
|
|
.statement: {
|
|
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
|
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
|
|
}
|
|
.import, .invalid: {}
|
|
}
|
|
}
|
|
return module
|
|
}
|
|
|
|
@hide:file
|
|
base_types :: $enummap.init_default(
|
|
typecheck.TypeKind, ir.Type, ir.Type.invalid, {
|
|
int = .int,
|
|
usize = .usize,
|
|
i8 = .i8,
|
|
i16 = .i16,
|
|
i32 = .i32,
|
|
isize = .isize,
|
|
u8 = .u8,
|
|
u16 = .u16,
|
|
u32 = .u32,
|
|
u64 = .u64,
|
|
array = .array,
|
|
record = .record,
|
|
pointer = .pointer,
|
|
float = .float,
|
|
bool = .bool,
|
|
string = .string,
|
|
void = .void,
|
|
function = .function,
|
|
none = .invalid,
|
|
invalid = .invalid,
|
|
range = .invalid,
|
|
type_value = .invalid,
|
|
null = .invalid,
|
|
anyopaque = .invalid,
|
|
},
|
|
)
|
|
|
|
@hide:file
|
|
lower_type proc(state @State, type typecheck.Type) ir.Type {
|
|
if (type.kind == .enumeration) return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
|
|
if (type.kind == .slice) return .record
|
|
if (type.kind == .fallible) return .record
|
|
if (type.kind == .optional) return lower_optional_type(state, typecheck.array_type(state.checked, type.array_type).element)
|
|
return enummap.get(&base_types, type.kind)?
|
|
}
|
|
|
|
@hide:file
|
|
lower_optional_type proc(state @State, child typecheck.Type) ir.Type {
|
|
if (child.kind == .pointer or child.kind == .function) return lower_type(state, child)
|
|
return .record
|
|
}
|
|
|
|
@hide:file
|
|
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (type.kind == .record) return try prepare_record(state, type.record_type)
|
|
if (type.kind == .slice or type.kind == .optional or type.kind == .fallible) return try prepare_container(state, type)
|
|
if (type.kind != .array) return ir.no_array
|
|
# checker history includes unresolved shapes; materialize only final used types.
|
|
return try prepare_array(state, type.array_type)
|
|
}
|
|
|
|
@hide:file
|
|
prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
|
|
descriptor :: typecheck.array_type(state.checked, id)
|
|
child := ir.no_array
|
|
child = try array_shape(state, descriptor.element)
|
|
result :: try ir.add_array(
|
|
state.module,
|
|
lower_type(state, descriptor.element),
|
|
child,
|
|
descriptor.len,
|
|
descriptor.mutable,
|
|
)
|
|
state.arrays[usize(id)] = result
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
prepare_container proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
descriptor :: typecheck.array_type(state.checked, type.array_type)
|
|
if (type.kind == .optional and (descriptor.element.kind == .pointer or descriptor.element.kind == .function)) return ir.no_array
|
|
index :: usize(type.array_type)
|
|
if (state.arrays[index] != ir.no_array) return state.arrays[index]
|
|
if (type.kind == .fallible) {
|
|
fields [2]ir.RecordField :: [
|
|
{
|
|
type = lower_type(state, descriptor.element),
|
|
array_type = try array_shape(state, descriptor.element),
|
|
tag = ir.Value{ type = .int, data = ir.ValueData{ integer = 0 } },
|
|
},
|
|
{
|
|
type = lower_type(state, descriptor.error_type),
|
|
array_type = try array_shape(state, descriptor.error_type),
|
|
tag = ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } },
|
|
},
|
|
]
|
|
shape :: try ir.add_union(state.module, fields[..], true)
|
|
state.arrays[index] = shape
|
|
return shape
|
|
}
|
|
fields [2]mut ir.RecordField := [{ type = .pointer }, { type = .usize }]
|
|
if (type.kind == .optional) {
|
|
fields[0] = ir.RecordField{ type = .bool }
|
|
fields[1] = ir.RecordField{ type = lower_type(state, descriptor.element), array_type = try array_shape(state, descriptor.element) }
|
|
}
|
|
shape :: try ir.add_record(state.module, fields[..])
|
|
state.arrays[index] = shape
|
|
return shape
|
|
}
|
|
|
|
@hide:file
|
|
prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (state.records[usize(id)] != ir.no_array) return state.records[usize(id)]
|
|
descriptor :: typecheck.record_type(state.checked, id)
|
|
fields := arraylist.init(ir.RecordField, state.module.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
try arraylist.reserve(&fields, descriptor.fields.len)
|
|
for (descriptor.fields) |field| {
|
|
tag := ir.Value{}
|
|
if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) {
|
|
members :: typecheck.enum_type(state.checked, descriptor.tag_type.enum_type).members
|
|
for members |member, index| {
|
|
if (ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?)) {
|
|
tag = enum_value(state, descriptor.tag_type, index)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
try arraylist.append(&fields, ir.RecordField{
|
|
type = lower_type(state, field.type),
|
|
array_type = try array_shape(state, field.type),
|
|
tag = tag,
|
|
})
|
|
}
|
|
shape :: if (descriptor.is_union) try ir.add_union(state.module, fields.items, descriptor.tag_type.kind == .enumeration)
|
|
else try ir.add_record(state.module, fields.items)
|
|
state.records[usize(id)] = shape
|
|
return shape
|
|
}
|
|
|
|
@hide:file
|
|
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
|
|
id :: ir.FunctionId(module.functions.items.len)
|
|
try arraylist.append(&module.functions, ir.init_function(module.allocator))
|
|
function := &module.functions.items[usize(id)]
|
|
function.span = span
|
|
try arraylist.append(&function.blocks, ir.init_block(module.allocator))
|
|
return id
|
|
}
|
|
|
|
@hide:file
|
|
find_entry proc(state @mut State, root_package ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
package_tree := state.tree^
|
|
package_tree.root = root_package
|
|
tree :: &package_tree
|
|
for (0..ast.package_decl_count(tree)) |i| {
|
|
id :: ast.package_decl(tree, i)
|
|
name := diagnostics.Span{ start = 0, end = 0 }
|
|
match ast.decl_kind(tree, id) {
|
|
.function: name = ast.function_decl(tree, id).name
|
|
.statement: name = ast.statement_decl(tree, id).name
|
|
.import, .invalid: continue
|
|
}
|
|
if (!mem.eql(ast.source_text(tree, name), "main")) continue
|
|
if (ast.decl_kind(tree, id) != .function) {
|
|
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
|
return
|
|
}
|
|
declaration :: ast.function_decl(tree, id)
|
|
result :: lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
|
|
if (ast.function_param_count(tree, id) != 0 or result == .function) {
|
|
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
|
return
|
|
}
|
|
state.module.entry = ir.FunctionId(state.storage[usize(id)].index)
|
|
return
|
|
}
|
|
state.module.entry_diagnostic = try diagnostics.add(
|
|
state.diagnostics,
|
|
.missing_entry,
|
|
ast.node_span(tree, root_package),
|
|
)
|
|
}
|
|
|
|
@hide:file
|
|
trap proc(state @mut State, diagnostic diagnostics.DiagnosticId, span diagnostics.Span) void {
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .trap,
|
|
diagnostic = diagnostic,
|
|
span = span,
|
|
}
|
|
state.terminated = true
|
|
}
|
|
|
|
@hide:file
|
|
trap_type proc(state @mut State, id ast.NodeId) bool {
|
|
type :: typecheck.type_of(state.checked, id)
|
|
if (type.kind != .invalid) return false
|
|
trap(state, type.diagnostic, ast.node_span(state.tree, id))
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
trap_binding proc(state @mut State, id ast.NodeId) bool {
|
|
binding :: sema.binding(state.resolution, id)
|
|
if (binding.kind != .invalid) return false
|
|
trap(state, binding.data.diagnostic, ast.node_span(state.tree, id))
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
finish proc(state @mut State, value ir.ValueId, span diagnostics.Span) void {
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
value = value,
|
|
span = span,
|
|
}
|
|
state.terminated = true
|
|
}
|
|
|
|
@hide:file
|
|
emit proc(state @mut State, instruction ir.Instruction) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try ir.emit(state.module, ir.FunctionId(state.function), state.block, instruction)
|
|
}
|
|
|
|
@hide:file
|
|
constant proc(state @mut State, value ir.Value, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try emit(state, ir.Instruction{ op = .constant, type = value.type, constant = value, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
lower_global proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
global :: state.module.globals.items[usize(state.storage[usize(id)].index)]
|
|
state.function = usize(global.initializer)
|
|
state.block = ir.BlockId(0)
|
|
state.terminated = false
|
|
state.module.functions.items[state.function].result = global.type
|
|
state.module.functions.items[state.function].result_array_type = global.array_type
|
|
if (trap_binding(state, id)) return
|
|
declaration :: ast.statement_decl(state.tree, id)
|
|
if (declaration.type) |annotation| {
|
|
if (trap_type(state, annotation)) return
|
|
}
|
|
value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id))
|
|
if (state.terminated or trap_type(state, id)) return
|
|
finish(state, value, declaration.span)
|
|
}
|
|
|
|
@hide:file
|
|
lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
declaration :: ast.function_decl(tree, id)
|
|
state.function = usize(state.storage[usize(id)].index)
|
|
state.block = ir.BlockId(0)
|
|
state.terminated = false
|
|
state.loop = null
|
|
state.value_target = null
|
|
state.fallthrough_block = declaration.body
|
|
state.fallthrough_diagnostic = typecheck.fallthrough_diagnostic(state.checked, id)
|
|
state.return_type = typecheck.type_of(state.checked, declaration.return_type)
|
|
state.module.functions.items[state.function].result = lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
|
|
state.module.functions.items[state.function].result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
|
|
state.module.functions.items[state.function].result_fallible = state.return_type.kind == .fallible
|
|
for (0..ast.function_param_count(tree, id)) |i| {
|
|
parameter :: ast.function_param(tree, id, i)
|
|
type :: typecheck.type_of(state.checked, parameter)
|
|
local :: try ir.add_parameter(
|
|
state.module,
|
|
ir.FunctionId(state.function),
|
|
lower_type(state, type),
|
|
try array_shape(state, type),
|
|
)
|
|
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) }
|
|
}
|
|
if (ast.function_external(tree, id)) {
|
|
if (trap_binding(state, id) or trap_type(state, id)) return
|
|
name :: ast.source_text(tree, declaration.name)
|
|
owned :: try mem.alloc(u8, state.module.allocator, name.len)
|
|
memcopy!(owned, name)
|
|
state.module.functions.items[state.function].external_name = owned
|
|
return
|
|
}
|
|
try predeclare_locals(state, declaration.body)
|
|
# signature errors are entry traps; body errors are reached in statement order.
|
|
if (trap_binding(state, id) or trap_type(state, declaration.return_type)) return
|
|
for (0..ast.function_param_count(tree, id)) |i| {
|
|
if (trap_type(state, ast.function_param(tree, id, i))) return
|
|
}
|
|
if (trap_type(state, id)) return
|
|
try lower_block(state, declaration.body, false)
|
|
if (state.terminated) return
|
|
if (typecheck.fallthrough_diagnostic(state.checked, id)) |diagnostic| {
|
|
trap(state, diagnostic, ast.node_span(tree, declaration.body))
|
|
return
|
|
}
|
|
value := ir.no_value
|
|
if (state.return_type.kind == .fallible) value = try wrap_fallible(state, value, state.return_type, false, ast.node_span(tree, declaration.body))
|
|
finish(state, value, ast.node_span(tree, declaration.body))
|
|
}
|
|
|
|
@hide:file
|
|
predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
|
statement :: ast.block_statement(state.tree, block, i)
|
|
match ast.stmt_kind(state.tree, statement) {
|
|
.declaration: {
|
|
if (typecheck.type_of(state.checked, statement).kind == .type_value) continue
|
|
if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, statement).initializer) == .alias_expr) continue
|
|
type :: typecheck.type_of(state.checked, statement)
|
|
local :: try ir.add_local(
|
|
state.module,
|
|
ir.FunctionId(state.function),
|
|
lower_type(state, type),
|
|
try array_shape(state, type),
|
|
)
|
|
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(local) }
|
|
}
|
|
.if_stmt: {
|
|
conditional :: ast.if_stmt(state.tree, statement)
|
|
if (conditional.capture) |capture| try predeclare_capture(state, capture)
|
|
try predeclare_locals(state, conditional.then_block)
|
|
if (conditional.else_block) |alternative| try predeclare_locals(state, alternative)
|
|
}
|
|
.while_stmt: {
|
|
loop :: ast.while_stmt(state.tree, statement)
|
|
if (loop.capture) |capture| try predeclare_capture(state, capture)
|
|
try predeclare_locals(state, loop.body)
|
|
}
|
|
.for_stmt: {
|
|
loop :: ast.for_stmt(state.tree, statement)
|
|
is_range :: ast.expr_kind(state.tree, loop.iterable) == .range
|
|
family :: typecheck.type_of(state.checked, loop.iterable).range_element
|
|
type ir.Type :: if (is_range) lower_type(state, typecheck.Type{ kind = family }) else .usize
|
|
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
|
|
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) }
|
|
if (loop.capture) |capture| {
|
|
if (is_range) state.storage[usize(capture)] = state.storage[usize(statement)]
|
|
else try predeclare_capture(state, capture)
|
|
}
|
|
if (loop.index) |index| {
|
|
index_slot :: try ir.add_local(state.module, ir.FunctionId(state.function), .usize, ir.no_array)
|
|
state.storage[usize(index)] = Storage{ kind = .local, index = u32(index_slot) }
|
|
}
|
|
try predeclare_locals(state, loop.body)
|
|
}
|
|
.block_stmt: try predeclare_locals(state, ast.statement_block(state.tree, statement))
|
|
.defer_stmt, .errdefer_stmt: {
|
|
cleanup :: ast.cleanup_stmt(state.tree, statement)
|
|
if (cleanup.capture) |capture| try predeclare_capture(state, capture)
|
|
try predeclare_locals(state, cleanup.body)
|
|
}
|
|
else: {}
|
|
}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
predeclare_capture proc(state @mut State, capture ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
type :: typecheck.type_of(state.checked, capture)
|
|
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), lower_type(state, type), try array_shape(state, type))
|
|
state.storage[usize(capture)] = Storage{ kind = .local, index = u32(slot) }
|
|
}
|
|
|
|
@hide:file
|
|
store_capture proc(state @mut State, capture ast.NodeId, value ir.ValueId, optional_type typecheck.Type, span diagnostics.Span) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
payload :: try optional_payload(state, value, optional_type, span)
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = payload, slot = state.storage[usize(capture)].index, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
cleanup_depth :: state.cleanups.items.len
|
|
defer state.cleanups.items = state.cleanups.items.ptr[..cleanup_depth]
|
|
# reset every direct declaration before control can return or trap within the scope.
|
|
if (fresh_scope) {
|
|
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
|
declaration :: ast.block_statement(state.tree, block, i)
|
|
if (ast.stmt_kind(state.tree, declaration) != .declaration) continue
|
|
if (typecheck.type_of(state.checked, declaration).kind == .type_value) continue
|
|
if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, declaration).initializer) == .alias_expr) continue
|
|
_ = try emit(state, ir.Instruction{
|
|
op = .reset_local,
|
|
slot = state.storage[usize(declaration)].index,
|
|
span = ast.node_span(state.tree, declaration),
|
|
})
|
|
}
|
|
}
|
|
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
|
if (state.terminated) return
|
|
try lower_statement(state, ast.block_statement(state.tree, block, i))
|
|
}
|
|
if (!state.terminated) {
|
|
if (state.fallthrough_block) |function_body| {
|
|
if (function_body == block) {
|
|
if (state.fallthrough_diagnostic) |diagnostic| {
|
|
trap(state, diagnostic, ast.node_span(state.tree, block))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!state.terminated) try lower_cleanups(state, cleanup_depth, ir.no_value)
|
|
}
|
|
|
|
@hide:file
|
|
lower_value_block proc(state @mut State, block, owner ast.NodeId, type typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
span :: ast.node_span(state.tree, block)
|
|
runtime :: lower_type(state, type)
|
|
shape :: try array_shape(state, type)
|
|
slot := u32(0)
|
|
if (runtime != .void) slot = u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
|
|
join :: try add_block(state)
|
|
reached :: try lower_value_body(state, block, owner, type, slot, join)
|
|
state.block = join
|
|
state.terminated = !reached
|
|
if (!reached) state.module.functions.items[state.function].blocks.items[usize(join)].terminator = ir.Terminator{ kind = .unreachable }
|
|
if (!reached or runtime == .void) return ir.no_value
|
|
return try emit(state, ir.Instruction{ op = .load_local, type = runtime, array_type = shape, slot = slot, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
lower_value_body proc(state @mut State, block, owner ast.NodeId, type typecheck.Type, slot u32, join ir.BlockId) bool ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (trap_type(state, block)) return false
|
|
target := ValueTarget{ join = join, slot = slot, type = type, cleanup_depth = state.cleanups.items.len }
|
|
previous_target :: state.value_target
|
|
state.value_target = &target
|
|
defer state.value_target = previous_target
|
|
previous_fallthrough :: state.fallthrough_block
|
|
previous_diagnostic :: state.fallthrough_diagnostic
|
|
state.fallthrough_block = block
|
|
state.fallthrough_diagnostic = typecheck.fallthrough_diagnostic(state.checked, owner)
|
|
defer state.fallthrough_block = previous_fallthrough
|
|
defer state.fallthrough_diagnostic = previous_diagnostic
|
|
try predeclare_locals(state, block)
|
|
try lower_block(state, block, true)
|
|
if (!state.terminated) {
|
|
# checking permits an implicit result only for void blocks.
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = ast.node_span(state.tree, block),
|
|
}
|
|
target.reached = true
|
|
state.terminated = true
|
|
}
|
|
return target.reached
|
|
}
|
|
|
|
@hide:file
|
|
lower_cleanups proc(state @mut State, depth usize, error_value ir.ValueId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
previous_target :: state.value_target
|
|
state.value_target = null
|
|
defer state.value_target = previous_target
|
|
index := state.cleanups.items.len
|
|
while (index > depth) {
|
|
index -= 1
|
|
id :: state.cleanups.items[index]
|
|
if (ast.stmt_kind(state.tree, id) == .errdefer_stmt and error_value == ir.no_value) continue
|
|
cleanup :: ast.cleanup_stmt(state.tree, id)
|
|
if (cleanup.capture) |capture| _ = try emit(state, ir.Instruction{
|
|
op = .store_local, left = error_value, slot = state.storage[usize(capture)].index, span = cleanup.span,
|
|
})
|
|
try lower_block(state, cleanup.body, true)
|
|
if (state.terminated) return
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
wrap_fallible proc(state @mut State, value ir.ValueId, type typecheck.Type, failed bool, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
slot :: if (failed) u32(1) else u32(0)
|
|
shape :: try array_shape(state, type)
|
|
return try emit(state, ir.Instruction{
|
|
op = .union_literal, type = .record, array_type = shape, slot = slot,
|
|
left = value, constant = state.module.arrays.items[usize(shape)].fields[usize(slot)].tag, span = span,
|
|
})
|
|
}
|
|
|
|
@hide:file
|
|
fallible_failed proc(state @mut State, value ir.ValueId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tag :: try emit(state, ir.Instruction{ op = .union_tag, type = .int, left = value, span = span })
|
|
error_tag :: try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }, span)
|
|
return try emit(state, ir.Instruction{ op = .equal, type = .bool, left = tag, right = error_tag, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
lower_return proc(state @mut State, value ir.ValueId, span diagnostics.Span) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (state.return_type.kind != .fallible) {
|
|
try lower_cleanups(state, 0, ir.no_value)
|
|
if (!state.terminated) finish(state, value, span)
|
|
return
|
|
}
|
|
failed :: try fallible_failed(state, value, span)
|
|
success :: try add_block(state)
|
|
failure :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = failed, target = failure, alternative = success, span = span,
|
|
}
|
|
state.block = failure
|
|
error_type :: typecheck.array_type(state.checked, state.return_type.array_type).error_type
|
|
error_value :: try project(state, value, lower_type(state, error_type), try array_shape(state, error_type), 1, span)
|
|
try lower_cleanups(state, 0, error_value)
|
|
if (!state.terminated) finish(state, value, span)
|
|
state.block = success
|
|
state.terminated = false
|
|
try lower_cleanups(state, 0, ir.no_value)
|
|
if (!state.terminated) finish(state, value, span)
|
|
}
|
|
|
|
@hide:file
|
|
add_block proc(state @mut State) ir.BlockId ! mem.AllocError {
|
|
function := &state.module.functions.items[state.function]
|
|
id :: ir.BlockId(function.blocks.items.len)
|
|
try arraylist.append(&function.blocks, ir.init_block(state.module.allocator))
|
|
return id
|
|
}
|
|
|
|
@hide:file
|
|
lower_if proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
conditional :: ast.if_stmt(state.tree, id)
|
|
value :: try lower_expression(state, conditional.condition)
|
|
if (state.terminated) return
|
|
condition := value
|
|
if (conditional.capture != null) condition = try optional_present(state, value, typecheck.type_of(state.checked, conditional.condition), conditional.span)
|
|
origin :: state.block
|
|
consequent :: try add_block(state)
|
|
alternative :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
|
kind = .branch,
|
|
value = condition,
|
|
target = consequent,
|
|
alternative = alternative,
|
|
span = ast.node_span(state.tree, conditional.condition),
|
|
}
|
|
state.block = consequent
|
|
if (conditional.capture) |capture| try store_capture(state, capture, value, typecheck.type_of(state.checked, conditional.condition), conditional.span)
|
|
try lower_block(state, conditional.then_block, true)
|
|
consequent_end :: state.block
|
|
consequent_falls :: !state.terminated
|
|
state.block = alternative
|
|
state.terminated = false
|
|
if (conditional.else_block) |block| try lower_block(state, block, true)
|
|
alternative_end :: state.block
|
|
alternative_falls :: !state.terminated
|
|
if (!consequent_falls and !alternative_falls) {
|
|
state.terminated = true
|
|
return
|
|
}
|
|
join :: try add_block(state)
|
|
if (consequent_falls) {
|
|
state.module.functions.items[state.function].blocks.items[usize(consequent_end)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = conditional.span,
|
|
}
|
|
}
|
|
if (alternative_falls) {
|
|
state.module.functions.items[state.function].blocks.items[usize(alternative_end)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = conditional.span,
|
|
}
|
|
}
|
|
state.block = join
|
|
state.terminated = false
|
|
}
|
|
|
|
@hide:file
|
|
lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
loop :: ast.while_stmt(state.tree, id)
|
|
origin :: state.block
|
|
guard :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
|
kind = .jump, target = guard, span = loop.span,
|
|
}
|
|
state.block = guard
|
|
value :: try lower_expression(state, loop.condition)
|
|
if (state.terminated) return
|
|
condition := value
|
|
if (loop.capture != null) condition = try optional_present(state, value, typecheck.type_of(state.checked, loop.condition), loop.span)
|
|
guard_end :: state.block
|
|
body :: try add_block(state)
|
|
exit :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(guard_end)].terminator = ir.Terminator{
|
|
kind = .branch,
|
|
value = condition,
|
|
target = body,
|
|
alternative = exit,
|
|
span = ast.node_span(state.tree, loop.condition),
|
|
}
|
|
previous_loop :: state.loop
|
|
state.loop = LoopTargets{ continue_target = guard, exit = exit, cleanup_depth = state.cleanups.items.len }
|
|
defer state.loop = previous_loop
|
|
state.block = body
|
|
if (loop.capture) |capture| try store_capture(state, capture, value, typecheck.type_of(state.checked, loop.condition), loop.span)
|
|
try lower_block(state, loop.body, true)
|
|
if (!state.terminated) {
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = guard, span = loop.span,
|
|
}
|
|
}
|
|
state.block = exit
|
|
state.terminated = (
|
|
loop.capture == null
|
|
and ast.expr_kind(state.tree, loop.condition) == .literal_bool
|
|
and mem.eql(ast.literal_text(state.tree, loop.condition), "true")
|
|
and !state.loop?.break_reached
|
|
)
|
|
if (state.terminated)
|
|
state.module.functions.items[state.function].blocks.items[usize(exit)].terminator = ir.Terminator{ kind = .unreachable, span = loop.span }
|
|
}
|
|
|
|
@hide:file
|
|
lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
loop :: ast.for_stmt(state.tree, id)
|
|
if (ast.expr_kind(state.tree, loop.iterable) != .range) {
|
|
try lower_collection_for(state, id)
|
|
return
|
|
}
|
|
interval :: ast.range_expr(state.tree, loop.iterable)
|
|
start :: try lower_expression(state, interval.start)
|
|
if (state.terminated) return
|
|
end :: try lower_expression(state, interval.end)
|
|
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
|
|
slot :: state.storage[usize(id)].index
|
|
family :: lower_type(state, typecheck.Type{ kind = typecheck.type_of(state.checked, loop.iterable).range_element })
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = family, left = start, slot = slot, span = interval.span })
|
|
origin :: state.block
|
|
condition :: try add_block(state)
|
|
body :: try add_block(state)
|
|
update :: try add_block(state)
|
|
increment :: try add_block(state)
|
|
exit :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
|
kind = .jump, target = condition, span = loop.span,
|
|
}
|
|
state.block = condition
|
|
current :: try emit(state, ir.Instruction{ op = .load_local, type = family, slot = slot, span = interval.span })
|
|
comparison ir.Op :: if (interval.inclusive) .less_equal else .less
|
|
condition_value :: try emit(state, ir.Instruction{ op = comparison, type = .bool, left = current, right = end, span = interval.span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = condition_value, target = body, alternative = exit, span = interval.span,
|
|
}
|
|
previous_loop :: state.loop
|
|
state.loop = LoopTargets{ continue_target = update, exit = exit, cleanup_depth = state.cleanups.items.len }
|
|
defer state.loop = previous_loop
|
|
state.block = body
|
|
if (loop.capture) |capture| _ = trap_binding(state, capture)
|
|
if (!state.terminated) try lower_block(state, loop.body, true)
|
|
if (!state.terminated) {
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = update, span = loop.span,
|
|
}
|
|
}
|
|
state.block = update
|
|
state.terminated = false
|
|
last :: try emit(state, ir.Instruction{ op = .load_local, type = family, slot = slot, span = interval.span })
|
|
finished :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = last, right = end, span = interval.span })
|
|
# continue reaches this guard too, so an inclusive maximum never adds one.
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = finished, target = exit, alternative = increment, span = interval.span,
|
|
}
|
|
state.block = increment
|
|
unit := ir.Value{ type = family, data = ir.ValueData{ integer = 1 } }
|
|
if (ir.unsigned_integer(family)) unit.data = ir.ValueData{ unsigned = 1 }
|
|
one :: try constant(state, unit, interval.span)
|
|
next :: try emit(state, ir.Instruction{ op = .add, type = family, left = last, right = one, span = interval.span })
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = family, left = next, slot = slot, span = interval.span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = condition, span = loop.span,
|
|
}
|
|
state.block = exit
|
|
state.terminated = false
|
|
}
|
|
|
|
@hide:file
|
|
lower_collection_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
loop :: ast.for_stmt(state.tree, id)
|
|
parts :: try slice_parts(state, loop.iterable)
|
|
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
|
|
slot :: state.storage[usize(id)].index
|
|
zero :: try usize_constant(state, 0, loop.span)
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = .usize, left = zero, slot = slot, span = loop.span })
|
|
origin :: state.block
|
|
condition :: try add_block(state)
|
|
body :: try add_block(state)
|
|
update :: try add_block(state)
|
|
exit :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
|
kind = .jump, target = condition, span = loop.span,
|
|
}
|
|
state.block = condition
|
|
current :: try emit(state, ir.Instruction{ op = .load_local, type = .usize, slot = slot, span = loop.span })
|
|
condition_value :: try emit(state, ir.Instruction{ op = .less, type = .bool, left = current, right = parts.bound, span = loop.span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = condition_value, target = body, alternative = exit, span = loop.span,
|
|
}
|
|
previous_loop :: state.loop
|
|
state.loop = LoopTargets{ continue_target = update, exit = exit, cleanup_depth = state.cleanups.items.len }
|
|
defer state.loop = previous_loop
|
|
state.block = body
|
|
if (loop.capture) |capture| {
|
|
if (!trap_binding(state, capture) and !trap_type(state, capture)) {
|
|
runtime :: lower_type(state, parts.element)
|
|
shape :: try array_shape(state, parts.element)
|
|
pointer :: try emit(state, ir.Instruction{
|
|
op = .pointer_offset, type = .pointer, left = parts.pointer, right = current,
|
|
pointee = runtime, array_type = shape, span = loop.span,
|
|
})
|
|
value := pointer
|
|
if (!loop.pointer_capture) {
|
|
address :: try pointer_address(state, pointer, parts.element, loop.span)
|
|
value = try emit(state, ir.Instruction{
|
|
op = .load_address, type = runtime, array_type = shape, left = address, span = loop.span,
|
|
})
|
|
}
|
|
checked :: typecheck.type_of(state.checked, capture)
|
|
_ = try emit(state, ir.Instruction{
|
|
op = .store_local, type = lower_type(state, checked), array_type = try array_shape(state, checked),
|
|
left = value, slot = state.storage[usize(capture)].index, span = loop.span,
|
|
})
|
|
}
|
|
}
|
|
if (loop.index) |index| {
|
|
if (!state.terminated and !trap_binding(state, index) and !trap_type(state, index)) {
|
|
_ = try emit(state, ir.Instruction{
|
|
op = .store_local, type = .usize, left = current, slot = state.storage[usize(index)].index, span = loop.span,
|
|
})
|
|
}
|
|
}
|
|
if (!state.terminated) try lower_block(state, loop.body, true)
|
|
if (!state.terminated) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = update, span = loop.span,
|
|
}
|
|
state.block = update
|
|
state.terminated = false
|
|
last :: try emit(state, ir.Instruction{ op = .load_local, type = .usize, slot = slot, span = loop.span })
|
|
one :: try usize_constant(state, 1, loop.span)
|
|
next :: try emit(state, ir.Instruction{ op = .add, type = .usize, left = last, right = one, span = loop.span })
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = .usize, left = next, slot = slot, span = loop.span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = condition, span = loop.span,
|
|
}
|
|
state.block = exit
|
|
state.terminated = false
|
|
}
|
|
|
|
@hide:file
|
|
lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
match ast.stmt_kind(tree, id) {
|
|
.block_stmt: try lower_block(state, ast.statement_block(tree, id), true)
|
|
.defer_stmt, .errdefer_stmt: {
|
|
if (trap_type(state, id)) return
|
|
try arraylist.append(&state.cleanups, id)
|
|
}
|
|
.if_stmt: try lower_if(state, id)
|
|
.while_stmt: try lower_while(state, id)
|
|
.for_stmt: try lower_for(state, id)
|
|
.unreachable_stmt: {
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .unreachable, span = ast.node_span(tree, id),
|
|
}
|
|
state.terminated = true
|
|
}
|
|
.break_stmt, .continue_stmt: {
|
|
if (trap_type(state, id)) return
|
|
if (state.loop) |targets| {
|
|
target := targets.exit
|
|
# each loop supplies its own guard or guarded-update entry.
|
|
if (ast.stmt_kind(tree, id) == .continue_stmt) target = targets.continue_target
|
|
try lower_cleanups(state, targets.cleanup_depth, ir.no_value)
|
|
if (state.terminated) return
|
|
if (ast.stmt_kind(tree, id) == .break_stmt) {
|
|
reached := targets
|
|
reached.break_reached = true
|
|
state.loop = reached
|
|
}
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = target, span = ast.node_span(tree, id),
|
|
}
|
|
state.terminated = true
|
|
} else unreachable
|
|
}
|
|
.assignment_stmt: {
|
|
assignment :: ast.assignment_stmt(tree, id)
|
|
if (assignment.compound) {
|
|
if (trap_type(state, id)) return
|
|
expression :: ast.binary_expr(tree, assignment.value)
|
|
address :: try lower_place(state, assignment.target)
|
|
if (state.terminated) return
|
|
checked :: typecheck.type_of(state.checked, assignment.target)
|
|
type :: lower_type(state, checked)
|
|
old :: try emit(state, ir.Instruction{
|
|
op = .load_address, type = type, left = address,
|
|
array_type = try array_shape(state, checked), span = ast.node_span(tree, assignment.target),
|
|
})
|
|
right :: try lower_expression(state, expression.right)
|
|
if (state.terminated) return
|
|
value :: if (expression.operator == .add and type == .pointer) {
|
|
pointer :: typecheck.pointer_type(state.checked, checked.pointer_type)
|
|
yield try emit(state, ir.Instruction{
|
|
op = .pointer_offset, type = type, left = old, right = right,
|
|
pointee = lower_type(state, pointer.element),
|
|
array_type = try array_shape(state, pointer.element), span = ast.node_span(tree, id),
|
|
})
|
|
} else try emit(state, ir.Instruction{
|
|
op = enummap.get(&binary_ops, expression.operator)?, type = type, left = old, right = right,
|
|
span = ast.node_span(tree, id),
|
|
})
|
|
_ = try emit(state, ir.Instruction{
|
|
op = .store_address, type = .void, left = address, right = value,
|
|
span = ast.node_span(tree, assignment.target),
|
|
})
|
|
return
|
|
}
|
|
if (
|
|
ast.expr_kind(tree, assignment.target) == .index
|
|
or ast.expr_kind(tree, assignment.target) == .unary
|
|
or (ast.expr_kind(tree, assignment.target) == .selection
|
|
and sema.binding(state.resolution, assignment.target).kind == .none)
|
|
) {
|
|
value :: try lower_as(state, assignment.value, typecheck.type_of(state.checked, assignment.target))
|
|
if (state.terminated) return
|
|
if (!is_place(state, assignment.target)) {
|
|
_ = try lower_expression(state, assignment.target)
|
|
if (!state.terminated) _ = trap_type(state, id)
|
|
return
|
|
}
|
|
address :: try lower_place(state, assignment.target)
|
|
if (state.terminated or trap_type(state, id)) return
|
|
_ = try emit(state, ir.Instruction{
|
|
op = .store_address,
|
|
type = .void,
|
|
left = address,
|
|
right = value,
|
|
span = ast.node_span(tree, assignment.target),
|
|
})
|
|
return
|
|
}
|
|
if (trap_binding(state, assignment.target) or trap_type(state, id)) return
|
|
value :: try lower_as(state, assignment.value, typecheck.type_of(state.checked, assignment.target))
|
|
if (state.terminated) return
|
|
binding :: sema.binding(state.resolution, assignment.target)
|
|
_ = try emit(state, ir.Instruction{
|
|
op = .assign_local,
|
|
type = lower_type(state, typecheck.type_of(state.checked, assignment.target)),
|
|
array_type = try array_shape(state, typecheck.type_of(state.checked, assignment.target)),
|
|
left = value,
|
|
slot = state.storage[usize(binding.data.declaration)].index,
|
|
span = ast.node_span(tree, assignment.target),
|
|
})
|
|
}
|
|
.invalid: {
|
|
invalid :: ast.invalid(tree, id)
|
|
trap(state, invalid.diagnostic, invalid.span)
|
|
}
|
|
.expression: _ = try lower_expression(state, ast.statement_expr(tree, id))
|
|
.declaration: {
|
|
if (typecheck.type_of(state.checked, id).kind == .type_value) return
|
|
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) return
|
|
if (trap_binding(state, id)) return
|
|
declaration :: ast.statement_decl(tree, id)
|
|
if (declaration.type) |annotation| {
|
|
if (trap_type(state, annotation)) return
|
|
}
|
|
value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id))
|
|
if (state.terminated or trap_type(state, id)) return
|
|
_ = try emit(state, ir.Instruction{
|
|
op = .store_local,
|
|
type = lower_type(state, typecheck.type_of(state.checked, id)),
|
|
array_type = try array_shape(state, typecheck.type_of(state.checked, id)),
|
|
left = value,
|
|
slot = state.storage[usize(id)].index,
|
|
span = declaration.span,
|
|
})
|
|
}
|
|
.yield_stmt: {
|
|
statement :: ast.yield_stmt(tree, id)
|
|
if (state.value_target) |target| {
|
|
value :: try lower_as(state, statement.value?, target.type)
|
|
if (state.terminated or trap_type(state, id)) return
|
|
if (lower_type(state, target.type) != .void) _ = try emit(state, ir.Instruction{
|
|
op = .store_local, left = value, slot = target.slot, span = statement.span,
|
|
})
|
|
try lower_cleanups(state, target.cleanup_depth, ir.no_value)
|
|
if (state.terminated) return
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = target.join, span = statement.span,
|
|
}
|
|
target.reached = true
|
|
state.terminated = true
|
|
} else if (!trap_type(state, id)) unreachable
|
|
}
|
|
.return_stmt: {
|
|
statement :: ast.return_stmt(tree, id)
|
|
value := ir.no_value
|
|
if (statement.value) |expression| value = try lower_as(state, expression, state.return_type)
|
|
if (state.terminated or trap_type(state, id)) return
|
|
if (statement.value == null and state.return_type.kind == .fallible) value = try wrap_fallible(state, value, state.return_type, false, statement.span)
|
|
try lower_return(state, value, statement.span)
|
|
}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
binary_ops :: $enummap.init_default(
|
|
ast.BinaryOperator, ir.Op, ir.Op.constant, {
|
|
add = .add,
|
|
subtract = .subtract,
|
|
multiply = .multiply,
|
|
divide = .divide,
|
|
bit_and = .bit_and,
|
|
bit_or = .bit_or,
|
|
bit_xor = .bit_xor,
|
|
shift_left = .shift_left,
|
|
shift_right = .shift_right,
|
|
shift_left_saturating = .shift_left_saturating,
|
|
equal = .equal,
|
|
not_equal = .not_equal,
|
|
less = .less,
|
|
less_equal = .less_equal,
|
|
greater = .greater,
|
|
greater_equal = .greater_equal,
|
|
},
|
|
)
|
|
|
|
@hide:file
|
|
unary_ops :: $enummap.init_default(
|
|
ast.UnaryOperator, ir.Op, ir.Op.constant, {
|
|
negate = .negate,
|
|
logical_not = .logical_not,
|
|
bit_not = .bit_not,
|
|
},
|
|
)
|
|
|
|
@hide:file
|
|
lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
expression :: ast.binary_expr(state.tree, id)
|
|
span :: ast.node_span(state.tree, id)
|
|
left :: try lower_expression(state, expression.left)
|
|
if (state.terminated) return ir.no_value
|
|
origin :: state.block
|
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .bool, ir.no_array))
|
|
right_block :: try add_block(state)
|
|
skip :: try add_block(state)
|
|
join :: try add_block(state)
|
|
conjunction :: expression.operator == .logical_and
|
|
target :: if (conjunction) right_block else skip
|
|
alternative :: if (conjunction) skip else right_block
|
|
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
|
kind = .branch,
|
|
value = left,
|
|
target = target,
|
|
alternative = alternative,
|
|
span = ast.node_span(state.tree, expression.left),
|
|
}
|
|
state.block = skip
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = .bool, left = left, slot = slot, span = span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
state.block = right_block
|
|
right :: try lower_expression(state, expression.right)
|
|
if (!state.terminated) {
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = .bool, left = right, slot = slot, span = span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
}
|
|
# the skip edge reaches the join even when the demanded right edge traps.
|
|
state.block = join
|
|
state.terminated = false
|
|
if (trap_type(state, id)) return ir.no_value
|
|
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
enum_value proc(state @State, type typecheck.Type, index usize) ir.Value {
|
|
descriptor :: typecheck.enum_type(state.checked, type.enum_type)
|
|
member :: descriptor.members[index]
|
|
runtime :: lower_type(state, type)
|
|
if (ir.unsigned_integer(runtime)) return ir.Value{ type = runtime, data = ir.ValueData{ unsigned = member.magnitude } }
|
|
integer := i64(0)
|
|
if (member.negative) {
|
|
if (member.magnitude == u64(maxval!(i64)) + 1) integer = minval!(i64)
|
|
else integer = -i64(member.magnitude)
|
|
} else integer = i64(member.magnitude)
|
|
return ir.Value{ type = runtime, data = ir.ValueData{ integer = integer } }
|
|
}
|
|
|
|
@hide:file
|
|
match_condition proc(
|
|
state @mut State, subject ir.ValueId, pattern ast.NodeId,
|
|
) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
span :: ast.node_span(state.tree, pattern)
|
|
if (ast.expr_kind(state.tree, pattern) == .range) {
|
|
interval :: ast.range_expr(state.tree, pattern)
|
|
start :: try lower_expression(state, interval.start)
|
|
if (state.terminated) return ir.no_value
|
|
end :: try lower_expression(state, interval.end)
|
|
if (state.terminated) return ir.no_value
|
|
lower_bound :: try emit(state, ir.Instruction{ op = .greater_equal, type = .bool, left = subject, right = start, span = span })
|
|
upper_op :: if (interval.inclusive) ir.Op.less_equal else ir.Op.less
|
|
upper :: try emit(state, ir.Instruction{ op = upper_op, type = .bool, left = subject, right = end, span = span })
|
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .bool, ir.no_array))
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = lower_bound, slot = slot, span = span })
|
|
origin :: state.block
|
|
check_upper :: try add_block(state)
|
|
join :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
|
kind = .branch, value = lower_bound, target = check_upper, alternative = join, span = span,
|
|
}
|
|
state.block = check_upper
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = upper, slot = slot, span = span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
state.block = join
|
|
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
|
}
|
|
value :: try lower_expression(state, pattern)
|
|
if (state.terminated) return ir.no_value
|
|
return try emit(state, ir.Instruction{ op = .equal, type = .bool, left = subject, right = value, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
span :: ast.node_span(tree, id)
|
|
view :: ast.match_expr(tree, id)
|
|
subject :: try lower_expression(state, view.subject)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
subject_type :: typecheck.type_of(state.checked, view.subject)
|
|
tagged :: subject_type.kind == .record
|
|
discriminant := subject
|
|
if (view.optional_capture) discriminant = try optional_present(state, subject, subject_type, span)
|
|
if (tagged) {
|
|
tag_type :: typecheck.record_type(state.checked, subject_type.record_type).tag_type
|
|
discriminant = try emit(state, ir.Instruction{
|
|
op = .union_tag, type = lower_type(state, tag_type), left = subject, span = span,
|
|
})
|
|
}
|
|
type :: typecheck.type_of(state.checked, id)
|
|
runtime :: lower_type(state, type)
|
|
shape :: try array_shape(state, type)
|
|
result_slot := u32(0)
|
|
if (runtime != .void) result_slot = u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
|
|
join :: try add_block(state)
|
|
falls := false
|
|
count :: ast.match_arm_count(tree, id)
|
|
for 0..count |i| {
|
|
arm :: ast.match_arm(tree, id, i)
|
|
arm_view :: ast.match_arm_view(tree, arm)
|
|
body :: try add_block(state)
|
|
if (arm_view.capture) |capture| {
|
|
capture_type :: typecheck.type_of(state.checked, capture)
|
|
local :: try ir.add_local(state.module, ir.FunctionId(state.function), lower_type(state, capture_type), try array_shape(state, capture_type))
|
|
state.storage[usize(capture)] = Storage{ kind = .local, index = u32(local) }
|
|
}
|
|
patterns :: ast.match_pattern_count(tree, arm)
|
|
if (arm_view.catch_all) {
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = body, span = span,
|
|
}
|
|
} else {
|
|
for 0..patterns |j| {
|
|
pattern :: ast.match_pattern(tree, arm, j)
|
|
selected :: try add_block(state)
|
|
final :: i + 1 == count and j + 1 == patterns
|
|
next := join
|
|
if (!final) {
|
|
condition :: try match_condition(state, discriminant, pattern)
|
|
if (state.terminated) return ir.no_value
|
|
next = try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = condition, target = selected, alternative = next, span = ast.node_span(tree, pattern),
|
|
}
|
|
} else {
|
|
# checking proves exhaustiveness; the final pattern is the remaining case.
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = selected, span = span,
|
|
}
|
|
}
|
|
state.block = selected
|
|
if (arm_view.capture) |capture| {
|
|
capture_type :: typecheck.type_of(state.checked, capture)
|
|
captured := subject
|
|
if (view.optional_capture) captured = try optional_payload(state, subject, subject_type, span)
|
|
if (tagged) captured = try emit(state, ir.Instruction{
|
|
op = .field, type = lower_type(state, capture_type), array_type = try array_shape(state, capture_type),
|
|
left = subject, slot = u32(typecheck.union_variant_index(state.checked, pattern)), span = ast.node_span(tree, pattern),
|
|
})
|
|
_ = try emit(state, ir.Instruction{
|
|
op = .store_local, left = captured, slot = state.storage[usize(capture)].index, span = span,
|
|
})
|
|
}
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = body, span = span,
|
|
}
|
|
state.block = next
|
|
}
|
|
}
|
|
next :: state.block
|
|
state.block = body
|
|
state.terminated = false
|
|
if (arm_view.expression) {
|
|
value :: try lower_as(state, arm_view.body, type)
|
|
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
|
|
op = .store_local, left = value, slot = result_slot, span = span,
|
|
})
|
|
} else {
|
|
reached :: try lower_value_body(state, arm_view.body, arm_view.body, type, result_slot, join)
|
|
if (reached) falls = true
|
|
}
|
|
if (!state.terminated) {
|
|
falls = true
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
}
|
|
state.block = next
|
|
state.terminated = false
|
|
}
|
|
state.block = join
|
|
state.terminated = !falls
|
|
if (!falls) state.module.functions.items[state.function].blocks.items[usize(join)].terminator = ir.Terminator{ kind = .unreachable }
|
|
if (!falls or runtime == .void) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .load_local, type = runtime, array_type = shape, slot = result_slot, span = span,
|
|
})
|
|
}
|
|
|
|
@hide:file
|
|
lower_union proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
if (trap_type(state, id)) return ir.no_value
|
|
type :: typecheck.type_of(state.checked, id)
|
|
slot :: typecheck.union_variant_index(state.checked, id)
|
|
shape :: try array_shape(state, type)
|
|
field :: state.module.arrays.items[usize(shape)].fields[slot]
|
|
payload := ir.no_value
|
|
if (ast.expr_kind(tree, id) == .record_literal) {
|
|
view :: ast.record_literal(tree, id)
|
|
count :: ast.record_field_count(tree, id)
|
|
contextual := false
|
|
if (view.type) |annotation| contextual = ast.expr_kind(tree, annotation) == .enum_literal
|
|
if (contextual and field.type == .record and (count != 1 or ast.field(tree, ast.record_field(tree, id, 0)).name != null)) payload = try lower_record_as(state, id, typecheck.union_payload_type(state.checked, id))
|
|
else if (count != 0) payload = try lower_as(state, ast.field(tree, ast.record_field(tree, id, 0)).value?, typecheck.union_payload_type(state.checked, id))
|
|
}
|
|
if (state.terminated) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .union_literal, type = .record, array_type = shape, slot = u32(slot),
|
|
left = payload, constant = field.tag, span = ast.node_span(tree, id),
|
|
})
|
|
}
|
|
|
|
@hide:file
|
|
lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
scratch_start :: state.arguments.items.len
|
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
|
explicit_count :: ast.record_field_count(tree, id)
|
|
if (type.kind != .record) {
|
|
for (0..explicit_count) |i| {
|
|
_ = try lower_expression(state, ast.field(tree, ast.record_field(tree, id, i)).value?)
|
|
if (state.terminated) return ir.no_value
|
|
}
|
|
_ = trap_type(state, id)
|
|
return ir.no_value
|
|
}
|
|
descriptor :: typecheck.record_type(state.checked, type.record_type)
|
|
try arraylist.reserve(&state.arguments, scratch_start + explicit_count)
|
|
for (0..explicit_count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
|
for (0..explicit_count) |i| {
|
|
initializer :: ast.field(tree, ast.record_field(tree, id, i)).value?
|
|
expected := typecheck.Type{}
|
|
for (descriptor.fields) |field, slot| {
|
|
if (typecheck.initializer_field(state.checked, id, slot) == initializer) {
|
|
expected = field.type
|
|
break
|
|
}
|
|
}
|
|
value :: try lower_as(state, initializer, expected)
|
|
if (state.terminated) return ir.no_value
|
|
state.arguments.items[scratch_start + i] = value
|
|
}
|
|
if (trap_type(state, id)) return ir.no_value
|
|
ordered_start :: state.arguments.items.len
|
|
try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len)
|
|
for (descriptor.fields) |_| try arraylist.append(&state.arguments, ir.no_value)
|
|
for (0..(descriptor.fields.len)) |i| {
|
|
initializer :: typecheck.initializer_field(state.checked, id, i)
|
|
value := ir.no_value
|
|
# ponytail: quadratic field reorder; retain an inverse map if large records make lowering costly.
|
|
for (0..explicit_count) |j| {
|
|
if (ast.field(tree, ast.record_field(tree, id, j)).value? == initializer) {
|
|
value = state.arguments.items[scratch_start + j]
|
|
break
|
|
}
|
|
}
|
|
if (value == ir.no_value) value = try lower_as(state, initializer, descriptor.fields[i].type)
|
|
if (state.terminated) return ir.no_value
|
|
state.arguments.items[ordered_start + i] = value
|
|
}
|
|
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
|
start :: block.arguments.items.len
|
|
try arraylist.reserve(&block.arguments, start + descriptor.fields.len)
|
|
for (0..(descriptor.fields.len)) |i| try arraylist.append(&block.arguments, state.arguments.items[ordered_start + i])
|
|
return try emit(state, ir.Instruction{
|
|
op = .record_literal,
|
|
type = .record,
|
|
array_type = try array_shape(state, type),
|
|
arguments_start = start,
|
|
arguments_count = descriptor.fields.len,
|
|
span = ast.node_span(tree, id),
|
|
})
|
|
}
|
|
|
|
@hide:file
|
|
is_place proc(state @State, id ast.NodeId) bool {
|
|
match ast.expr_kind(state.tree, id) {
|
|
.unary: {
|
|
expression :: ast.unary_expr(state.tree, id)
|
|
return expression.operator == .dereference
|
|
}
|
|
.index: {
|
|
base :: ast.index_expr(state.tree, id).base
|
|
kind :: typecheck.type_of(state.checked, base).kind
|
|
return kind == .pointer or kind == .slice or kind == .string or is_place(state, base)
|
|
}
|
|
.selection: {
|
|
if (sema.binding(state.resolution, id).kind == .none) {
|
|
base :: ast.selection_expr(state.tree, id).base
|
|
checked :: typecheck.type_of(state.checked, base)
|
|
if (checked.kind == .pointer) {
|
|
pointer :: typecheck.pointer_type(state.checked, checked.pointer_type)
|
|
return pointer.element.kind == .record
|
|
}
|
|
return is_place(state, base)
|
|
}
|
|
binding :: sema.binding(state.resolution, id)
|
|
if (binding.kind != .declaration) return false
|
|
location :: state.storage[usize(binding.data.declaration)]
|
|
return location.kind == .local or location.kind == .global
|
|
}
|
|
.identifier: {
|
|
binding :: sema.binding(state.resolution, id)
|
|
if (binding.kind == .invalid) return true
|
|
if (binding.kind != .declaration) return false
|
|
location :: state.storage[usize(binding.data.declaration)]
|
|
return location.kind == .local or location.kind == .global
|
|
}
|
|
else: return false
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
pointer_address proc(
|
|
state @mut State, pointer ir.ValueId, element typecheck.Type, span diagnostics.Span,
|
|
) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try emit(state, ir.Instruction{
|
|
op = .pointer_address, type = .address, left = pointer,
|
|
pointee = lower_type(state, element), array_type = try array_shape(state, element), span = span,
|
|
})
|
|
}
|
|
|
|
@hide:file
|
|
usize_constant proc(state @mut State, value usize, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = u64(value) } }, span)
|
|
}
|
|
|
|
@hide:file
|
|
null_constant proc(state @mut State, type ir.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ unsigned = maxval!(u64) } }, span)
|
|
}
|
|
|
|
@hide:file
|
|
record_pair proc(state @mut State, shape ir.ArrayId, first, second ir.ValueId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
block :: &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
|
start :: block.arguments.items.len
|
|
try arraylist.append(&block.arguments, first)
|
|
try arraylist.append(&block.arguments, second)
|
|
return try emit(state, ir.Instruction{
|
|
op = .record_literal, type = .record, array_type = shape,
|
|
arguments_start = start, arguments_count = 2, span = span,
|
|
})
|
|
}
|
|
|
|
@hide:file
|
|
project proc(state @mut State, value ir.ValueId, type ir.Type, shape ir.ArrayId, slot u32, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try emit(state, ir.Instruction{ op = .field, type = type, array_type = shape, left = value, slot = slot, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
optional_present proc(state @mut State, value ir.ValueId, type typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
child :: typecheck.array_type(state.checked, type.array_type).element
|
|
runtime :: lower_type(state, child)
|
|
if (child.kind == .pointer or child.kind == .function) {
|
|
absent :: try null_constant(state, runtime, span)
|
|
return try emit(state, ir.Instruction{ op = .not_equal, type = .bool, left = value, right = absent, span = span })
|
|
}
|
|
return try project(state, value, .bool, ir.no_array, 0, span)
|
|
}
|
|
|
|
@hide:file
|
|
optional_payload proc(state @mut State, value ir.ValueId, type typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
child :: typecheck.array_type(state.checked, type.array_type).element
|
|
runtime :: lower_type(state, child)
|
|
if (child.kind == .pointer or child.kind == .function) return value
|
|
return try project(state, value, runtime, try array_shape(state, child), 1, span)
|
|
}
|
|
|
|
@hide:file
|
|
zero_value proc(state @mut State, type ir.Type, shape ir.ArrayId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (!ir.aggregate(type)) {
|
|
value := ir.Value{ type = type, data = ir.ValueData{ unsigned = 0 } }
|
|
if (type == .pointer or type == .function) value.data = ir.ValueData{ unsigned = maxval!(u64) }
|
|
return try constant(state, value, span)
|
|
}
|
|
descriptor :: state.module.arrays.items[usize(shape)]
|
|
if (descriptor.is_union) return try emit(state, ir.Instruction{ op = .union_literal, type = type, array_type = shape, span = span })
|
|
scratch :: state.arguments.items.len
|
|
defer state.arguments.items = state.arguments.items.ptr[..scratch]
|
|
count :: if (type == .array) descriptor.len else descriptor.fields.len
|
|
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
|
for (0..count) |i| {
|
|
child :: if (type == .array) descriptor.element else descriptor.fields[i].type
|
|
child_shape :: if (type == .array) descriptor.element_array else descriptor.fields[i].array_type
|
|
value :: try zero_value(state, child, child_shape, span)
|
|
state.arguments.items[scratch + i] = value
|
|
}
|
|
block :: &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
|
start :: block.arguments.items.len
|
|
for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch + i])
|
|
op ir.Op :: if (type == .array) .array_literal else .record_literal
|
|
return try emit(state, ir.Instruction{
|
|
op = op, type = type, array_type = shape,
|
|
arguments_start = start, arguments_count = count, span = span,
|
|
})
|
|
}
|
|
|
|
@hide:file
|
|
lower_as proc(state @mut State, id ast.NodeId, expected typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
actual :: typecheck.type_of(state.checked, id)
|
|
span :: ast.node_span(state.tree, id)
|
|
if (actual.kind == .invalid or expected.kind == .invalid) return try lower_expression(state, id)
|
|
if (expected.kind == .fallible and actual.kind != .fallible) {
|
|
descriptor :: typecheck.array_type(state.checked, expected.array_type)
|
|
failed :: !typecheck.value_assignable(state.checked, actual, descriptor.element)
|
|
channel :: if (failed) descriptor.error_type else descriptor.element
|
|
payload :: try lower_as(state, id, channel)
|
|
if (state.terminated) return ir.no_value
|
|
return try wrap_fallible(state, payload, expected, failed, span)
|
|
}
|
|
if (expected.kind == .optional and actual.kind != .optional) {
|
|
child :: typecheck.array_type(state.checked, expected.array_type).element
|
|
runtime :: lower_type(state, child)
|
|
nullable :: child.kind == .pointer or child.kind == .function
|
|
if (actual.kind == .null and nullable) return try null_constant(state, runtime, span)
|
|
payload := ir.no_value
|
|
if (actual.kind == .null) payload = try zero_value(state, runtime, try array_shape(state, child), span)
|
|
else payload = try lower_as(state, id, child)
|
|
if (state.terminated or nullable) return payload
|
|
present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = actual.kind != .null } }, span)
|
|
return try record_pair(state, try array_shape(state, expected), present, payload, span)
|
|
}
|
|
if (expected.kind == .slice and actual.kind != .slice) {
|
|
parts :: try slice_parts(state, id)
|
|
if (state.terminated) return ir.no_value
|
|
return try record_pair(state, try array_shape(state, expected), parts.pointer, parts.bound, span)
|
|
}
|
|
value :: try lower_expression(state, id)
|
|
if (state.terminated) return ir.no_value
|
|
return try convert_value(state, value, actual, expected, span)
|
|
}
|
|
|
|
@hide:file
|
|
convert_value proc(state @mut State, value ir.ValueId, actual, expected typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (actual.kind == .fallible and expected.kind == .fallible) {
|
|
if (actual.array_type == expected.array_type) return value
|
|
source :: typecheck.array_type(state.checked, actual.array_type)
|
|
destination :: typecheck.array_type(state.checked, expected.array_type)
|
|
shape :: try array_shape(state, expected)
|
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape))
|
|
failed :: try fallible_failed(state, value, span)
|
|
success :: try add_block(state)
|
|
failure :: try add_block(state)
|
|
join :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = failed, target = failure, alternative = success, span = span,
|
|
}
|
|
for (0..2) |i| {
|
|
state.block = if (i == 0) success else failure
|
|
child :: if (i == 0) source.element else source.error_type
|
|
target :: if (i == 0) destination.element else destination.error_type
|
|
payload := ir.no_value
|
|
if (child.kind != .void) payload = try project(state, value, lower_type(state, child), try array_shape(state, child), u32(i), span)
|
|
converted :: try convert_value(state, payload, child, target, span)
|
|
wrapped :: try wrap_fallible(state, converted, expected, i == 1, span)
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
}
|
|
state.block = join
|
|
return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span })
|
|
}
|
|
if (expected.kind == .optional and actual.kind != .optional) {
|
|
child :: typecheck.array_type(state.checked, expected.array_type).element
|
|
runtime :: lower_type(state, child)
|
|
nullable :: child.kind == .pointer or child.kind == .function
|
|
if (actual.kind == .null and nullable) return try null_constant(state, runtime, span)
|
|
payload := ir.no_value
|
|
if (actual.kind == .null) payload = try zero_value(state, runtime, try array_shape(state, child), span)
|
|
else payload = try convert_value(state, value, actual, child, span)
|
|
if (nullable) return payload
|
|
present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = actual.kind != .null } }, span)
|
|
return try record_pair(state, try array_shape(state, expected), present, payload, span)
|
|
}
|
|
if (actual.kind == .optional and expected.kind == .optional and actual.array_type != expected.array_type) {
|
|
source :: typecheck.array_type(state.checked, actual.array_type).element
|
|
child :: typecheck.array_type(state.checked, expected.array_type).element
|
|
if (source.kind == .pointer or source.kind == .function) return value
|
|
shape :: try array_shape(state, expected)
|
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape))
|
|
present :: try optional_present(state, value, actual, span)
|
|
some :: try add_block(state)
|
|
none :: try add_block(state)
|
|
join :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = present, target = some, alternative = none, span = span,
|
|
}
|
|
state.block = some
|
|
payload :: try optional_payload(state, value, actual, span)
|
|
converted :: try convert_value(state, payload, source, child, span)
|
|
wrapped :: try record_pair(state, shape, present, converted, span)
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
state.block = none
|
|
absent :: try zero_value(state, .record, shape, span)
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = absent, slot = slot, span = span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
state.block = join
|
|
return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span })
|
|
}
|
|
if (expected.kind == .slice and actual.kind == .string) {
|
|
pointer :: try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
|
|
length :: try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span })
|
|
return try record_pair(state, try array_shape(state, expected), pointer, length, span)
|
|
}
|
|
if (expected.kind == .slice and actual.kind == .array) {
|
|
source_shape :: try array_shape(state, actual)
|
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .array, source_shape))
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = value, slot = slot, span = span })
|
|
address :: try emit(state, ir.Instruction{
|
|
op = .address_local, type = .address, pointee = .array, array_type = source_shape, slot = slot, span = span,
|
|
})
|
|
pointer :: try emit(state, ir.Instruction{
|
|
op = .pointer_from_address, type = .pointer, left = address, pointee = .array, array_type = source_shape, span = span,
|
|
})
|
|
length :: try usize_constant(state, typecheck.array_type(state.checked, actual.array_type).len, span)
|
|
return try record_pair(state, try array_shape(state, expected), pointer, length, span)
|
|
}
|
|
if (
|
|
!typecheck.error_assignable(state.checked, actual, expected)
|
|
and !typecheck.error_assignable(state.checked, expected, actual)
|
|
) return value
|
|
source_union := false
|
|
if (actual.kind == .record) source_union = typecheck.record_type(state.checked, actual.record_type).is_union
|
|
if (expected.kind == .enumeration) {
|
|
if (!source_union) return value
|
|
return try emit(state, ir.Instruction{ op = .union_tag, type = lower_type(state, expected), left = value, span = span })
|
|
}
|
|
if (expected.kind != .record) return value
|
|
destination :: typecheck.record_type(state.checked, expected.record_type)
|
|
if (!destination.is_union) return value
|
|
if (actual.kind == .record and actual.record_type == expected.record_type) return value
|
|
shape :: try array_shape(state, expected)
|
|
if (actual.kind == .record and !source_union) {
|
|
for destination.fields |field, i| {
|
|
if (field.type.kind != .record or field.type.record_type != actual.record_type or field.type.nominal != actual.nominal) continue
|
|
return try emit(state, ir.Instruction{
|
|
op = .union_literal, type = .record, array_type = shape, slot = u32(i), left = value,
|
|
constant = state.module.arrays.items[usize(shape)].fields[i].tag, span = span,
|
|
})
|
|
}
|
|
unreachable
|
|
}
|
|
discriminant := value
|
|
source_shape := ir.no_array
|
|
if (source_union) {
|
|
source_shape = try array_shape(state, actual)
|
|
tag_type :: typecheck.record_type(state.checked, actual.record_type).tag_type
|
|
discriminant = try emit(state, ir.Instruction{ op = .union_tag, type = lower_type(state, tag_type), left = value, span = span })
|
|
}
|
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape))
|
|
join :: try add_block(state)
|
|
for destination.fields |field, i| {
|
|
selected :: try add_block(state)
|
|
next :: try add_block(state)
|
|
tag :: state.module.arrays.items[usize(shape)].fields[i].tag
|
|
expected_tag :: try constant(state, tag, span)
|
|
condition :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = discriminant, right = expected_tag, span = span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = condition, target = selected, alternative = next, span = span,
|
|
}
|
|
state.block = selected
|
|
payload := ir.no_value
|
|
if (source_union and field.type.kind != .void) {
|
|
source_fields :: state.module.arrays.items[usize(source_shape)].fields
|
|
for source_fields |source_field, j| {
|
|
if (source_field.tag.data.unsigned != tag.data.unsigned) continue
|
|
payload = try project(state, value, source_field.type, source_field.array_type, u32(j), span)
|
|
break
|
|
}
|
|
}
|
|
wrapped :: try emit(state, ir.Instruction{
|
|
op = .union_literal, type = .record, array_type = shape, slot = u32(i), left = payload, constant = tag, span = span,
|
|
})
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
state.block = next
|
|
}
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .unreachable, span = span }
|
|
state.block = join
|
|
return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
lower_try proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
operand :: ast.type_operand(state.tree, id)
|
|
value :: try lower_expression(state, operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
span :: ast.node_span(state.tree, id)
|
|
descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, operand).array_type)
|
|
failed :: try fallible_failed(state, value, span)
|
|
success :: try add_block(state)
|
|
failure :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = failed, target = failure, alternative = success, span = span,
|
|
}
|
|
state.block = failure
|
|
payload :: try project(state, value, lower_type(state, descriptor.error_type), try array_shape(state, descriptor.error_type), 1, span)
|
|
target_error :: typecheck.array_type(state.checked, state.return_type.array_type).error_type
|
|
converted :: try convert_value(state, payload, descriptor.error_type, target_error, span)
|
|
result :: try wrap_fallible(state, converted, state.return_type, true, span)
|
|
try lower_cleanups(state, 0, converted)
|
|
if (!state.terminated) finish(state, result, span)
|
|
state.block = success
|
|
state.terminated = false
|
|
if (descriptor.element.kind == .void) return ir.no_value
|
|
return try project(state, value, lower_type(state, descriptor.element), try array_shape(state, descriptor.element), 0, span)
|
|
}
|
|
|
|
@hide:file
|
|
lower_catch proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
expression :: ast.catch_expr(state.tree, id)
|
|
value :: try lower_expression(state, expression.operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
span :: expression.span
|
|
descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, expression.operand).array_type)
|
|
type :: typecheck.type_of(state.checked, id)
|
|
runtime :: lower_type(state, type)
|
|
shape :: try array_shape(state, type)
|
|
slot := u32(0)
|
|
if (runtime != .void) slot = u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
|
|
failed :: try fallible_failed(state, value, span)
|
|
success :: try add_block(state)
|
|
failure :: try add_block(state)
|
|
join :: try add_block(state)
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .branch, value = failed, target = failure, alternative = success, span = span,
|
|
}
|
|
state.block = success
|
|
if (runtime != .void) {
|
|
payload :: try project(state, value, lower_type(state, descriptor.element), try array_shape(state, descriptor.element), 0, span)
|
|
converted :: try convert_value(state, payload, descriptor.element, type, span)
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = converted, slot = slot, span = span })
|
|
}
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
state.block = failure
|
|
if (expression.capture) |capture| {
|
|
try predeclare_capture(state, capture)
|
|
payload :: try project(state, value, lower_type(state, descriptor.error_type), try array_shape(state, descriptor.error_type), 1, span)
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = payload, slot = state.storage[usize(capture)].index, span = span })
|
|
}
|
|
if (!trap_type(state, expression.handler)) {
|
|
if (expression.handler_is_block) {
|
|
_ = try lower_value_body(state, expression.handler, expression.handler, type, slot, join)
|
|
} else {
|
|
fallback :: try lower_as(state, expression.handler, type)
|
|
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
|
|
op = .store_local, left = fallback, slot = slot, span = span,
|
|
})
|
|
}
|
|
}
|
|
if (!state.terminated) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .jump, target = join, span = span,
|
|
}
|
|
state.block = join
|
|
state.terminated = false
|
|
if (runtime == .void) return ir.no_value
|
|
return try emit(state, ir.Instruction{ op = .load_local, type = runtime, array_type = shape, slot = slot, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
SliceParts :: struct {
|
|
pointer ir.ValueId
|
|
bound ir.ValueId
|
|
element typecheck.Type
|
|
}
|
|
|
|
@hide:file
|
|
borrow_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (is_place(state, id)) return try lower_place(state, id)
|
|
value :: try lower_expression(state, id)
|
|
if (state.terminated) return ir.no_value
|
|
checked :: typecheck.type_of(state.checked, id)
|
|
type :: lower_type(state, checked)
|
|
shape :: try array_shape(state, checked)
|
|
span :: ast.node_span(state.tree, id)
|
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), type, shape))
|
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = type, array_type = shape, left = value, slot = slot, span = span })
|
|
return try emit(state, ir.Instruction{ op = .address_local, type = .address, pointee = type, array_type = shape, slot = slot, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
slice_parts proc(state @mut State, id ast.NodeId) SliceParts ! (mem.AllocError | diagnostics.CapacityError) {
|
|
checked :: typecheck.type_of(state.checked, id)
|
|
span :: ast.node_span(state.tree, id)
|
|
result := SliceParts{ pointer = ir.no_value, bound = ir.no_value, element = typecheck.Type{} }
|
|
if (checked.kind == .slice) {
|
|
value :: try lower_expression(state, id)
|
|
if (state.terminated) return result
|
|
result.pointer = try project(state, value, .pointer, ir.no_array, 0, span)
|
|
result.bound = try project(state, value, .usize, ir.no_array, 1, span)
|
|
result.element = typecheck.array_type(state.checked, checked.array_type).element
|
|
return result
|
|
}
|
|
if (checked.kind == .string) {
|
|
value :: try lower_expression(state, id)
|
|
if (state.terminated) return result
|
|
result.pointer = try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
|
|
result.bound = try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span })
|
|
result.element = typecheck.Type{ kind = .u8 }
|
|
return result
|
|
}
|
|
if (checked.kind == .pointer) {
|
|
descriptor :: typecheck.pointer_type(state.checked, checked.pointer_type)
|
|
result.pointer = try lower_expression(state, id)
|
|
if (state.terminated) return result
|
|
result.element = descriptor.element
|
|
if (!descriptor.many and descriptor.element.kind == .array) {
|
|
array :: typecheck.array_type(state.checked, descriptor.element.array_type)
|
|
result.element = array.element
|
|
result.bound = try usize_constant(state, array.len, span)
|
|
}
|
|
return result
|
|
}
|
|
if (checked.kind == .array) {
|
|
descriptor :: typecheck.array_type(state.checked, checked.array_type)
|
|
address :: try borrow_expression(state, id)
|
|
if (state.terminated) return result
|
|
result.pointer = try emit(state, ir.Instruction{
|
|
op = .pointer_from_address, type = .pointer, left = address,
|
|
pointee = .array, array_type = try array_shape(state, checked), span = span,
|
|
})
|
|
result.bound = try usize_constant(state, descriptor.len, span)
|
|
result.element = descriptor.element
|
|
return result
|
|
}
|
|
_ = try lower_expression(state, id)
|
|
return result
|
|
}
|
|
|
|
@hide:file
|
|
lower_slice proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
expression :: ast.slice_expr(state.tree, id)
|
|
span :: ast.node_span(state.tree, id)
|
|
parts :: try slice_parts(state, expression.base)
|
|
if (state.terminated) return ir.no_value
|
|
start := ir.no_value
|
|
if (expression.start) |node| start = try lower_expression(state, node)
|
|
else start = try usize_constant(state, 0, span)
|
|
if (state.terminated) return ir.no_value
|
|
end := parts.bound
|
|
if (expression.end) |node| end = try lower_expression(state, node)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
_ = try emit(state, ir.Instruction{ op = .slice_check, left = start, right = end, extra = parts.bound, span = span })
|
|
child :: parts.element
|
|
runtime :: lower_type(state, child)
|
|
shape :: try array_shape(state, child)
|
|
if (expression.sentinel) |node| {
|
|
sentinel :: try lower_as(state, node, child)
|
|
if (state.terminated) return ir.no_value
|
|
base_type :: typecheck.type_of(state.checked, expression.base)
|
|
has_sentinel := base_type.kind == .string
|
|
if (base_type.kind == .slice) has_sentinel = typecheck.array_type(state.checked, base_type.array_type).sentinel != null
|
|
if (parts.bound != ir.no_value and !has_sentinel) {
|
|
one :: try usize_constant(state, 1, span)
|
|
through :: try emit(state, ir.Instruction{ op = .add, type = .usize, left = end, right = one, span = span })
|
|
_ = try emit(state, ir.Instruction{ op = .slice_check, left = end, right = through, extra = parts.bound, span = span })
|
|
}
|
|
pointer :: try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = parts.pointer, right = end, pointee = runtime, array_type = shape, span = span })
|
|
_ = try emit(state, ir.Instruction{ op = .sentinel_check, left = pointer, right = sentinel, pointee = runtime, array_type = shape, span = span })
|
|
}
|
|
pointer :: try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = parts.pointer, right = start, pointee = runtime, array_type = shape, span = span })
|
|
length :: try emit(state, ir.Instruction{ op = .subtract, type = .usize, left = end, right = start, span = span })
|
|
return try record_pair(state, try array_shape(state, typecheck.type_of(state.checked, id)), pointer, length, span)
|
|
}
|
|
|
|
@hide:file
|
|
lower_builtin proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
span :: ast.node_span(tree, id)
|
|
name :: ast.literal_text(tree, ast.builtin_call(tree, id).name)
|
|
if (trap_type(state, id)) return ir.no_value
|
|
if (mem.eql(name, "some")) {
|
|
type :: typecheck.type_of(state.checked, id)
|
|
child :: typecheck.array_type(state.checked, type.array_type).element
|
|
payload :: try lower_as(state, ast.call_arg(tree, id, 0), child)
|
|
if (state.terminated or child.kind == .pointer or child.kind == .function) return payload
|
|
present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = true } }, span)
|
|
return try record_pair(state, try array_shape(state, type), present, payload, span)
|
|
}
|
|
if (mem.eql(name, "maxval") or mem.eql(name, "minval")) {
|
|
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
|
value := ir.Value{ type = type }
|
|
if (mem.eql(name, "maxval")) value.data = ir.ValueData{ unsigned = enummap.get(&ir.integer_maxima, type)? }
|
|
else value.data = ir.ValueData{ integer = enummap.get(&ir.integer_minima, type)? }
|
|
return try constant(state, value, span)
|
|
}
|
|
if (mem.eql(name, "divtrunc")) {
|
|
left :: try lower_expression(state, ast.call_arg(tree, id, 0))
|
|
if (state.terminated) return ir.no_value
|
|
right :: try lower_expression(state, ast.call_arg(tree, id, 1))
|
|
if (state.terminated) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .divide, type = lower_type(state, typecheck.type_of(state.checked, id)),
|
|
left = left, right = right, span = span,
|
|
})
|
|
}
|
|
if (mem.eql(name, "sizeof") or mem.eql(name, "alignof")) {
|
|
checked :: typecheck.type_of(state.checked, ast.call_arg(tree, id, 0))
|
|
type :: lower_type(state, checked)
|
|
shape :: try array_shape(state, checked)
|
|
size :: if (mem.eql(name, "sizeof")) ir.storage_size(state.module, type, shape) else ir.storage_alignment(state.module, type, shape)
|
|
return try usize_constant(state, size, span)
|
|
}
|
|
if (mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) {
|
|
operand :: ast.call_arg(tree, id, ast.call_arg_count(tree, id) - 1)
|
|
value :: try lower_expression(state, operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
type :: typecheck.type_of(state.checked, id)
|
|
if (lower_type(state, type) == .record) return value
|
|
return try emit(state, ir.Instruction{ op = .pointer_cast, type = lower_type(state, type), left = value, span = span })
|
|
}
|
|
if (mem.eql(name, "memcopy") or mem.eql(name, "memset")) {
|
|
destination :: try slice_parts(state, ast.call_arg(tree, id, 0))
|
|
if (state.terminated) return ir.no_value
|
|
source := SliceParts{ pointer = ir.no_value, bound = ir.no_value, element = typecheck.Type{} }
|
|
value := ir.no_value
|
|
if (mem.eql(name, "memcopy")) source = try slice_parts(state, ast.call_arg(tree, id, 1))
|
|
else value = try lower_as(state, ast.call_arg(tree, id, 1), destination.element)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
if (mem.eql(name, "memcopy")) {
|
|
zero :: try usize_constant(state, 0, span)
|
|
_ = try emit(state, ir.Instruction{ op = .slice_check, left = zero, right = source.bound, extra = destination.bound, span = span })
|
|
_ = try emit(state, ir.Instruction{ op = .slice_check, left = zero, right = destination.bound, extra = source.bound, span = span })
|
|
value = source.pointer
|
|
}
|
|
op ir.Op :: if (mem.eql(name, "memcopy")) .memory_copy else .memory_set
|
|
_ = try emit(state, ir.Instruction{
|
|
op = op,
|
|
left = destination.pointer, right = value, extra = destination.bound,
|
|
pointee = lower_type(state, destination.element), array_type = try array_shape(state, destination.element), span = span,
|
|
})
|
|
return ir.no_value
|
|
}
|
|
if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .invalid_operand, span), span)
|
|
return ir.no_value
|
|
}
|
|
|
|
@hide:file
|
|
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
span :: ast.node_span(state.tree, id)
|
|
checked :: typecheck.type_of(state.checked, id)
|
|
if (ast.expr_kind(state.tree, id) == .unary) {
|
|
expression :: ast.unary_expr(state.tree, id)
|
|
pointer :: try lower_expression(state, expression.operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
return try pointer_address(state, pointer, checked, span)
|
|
}
|
|
if (
|
|
ast.expr_kind(state.tree, id) == .selection
|
|
and sema.binding(state.resolution, id).kind == .none
|
|
) {
|
|
expression :: ast.selection_expr(state.tree, id)
|
|
base_type :: typecheck.type_of(state.checked, expression.base)
|
|
base := ir.no_value
|
|
if (base_type.kind == .pointer) {
|
|
pointer :: try lower_expression(state, expression.base)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
element :: typecheck.pointer_type(state.checked, base_type.pointer_type).element
|
|
base = try pointer_address(state, pointer, element, span)
|
|
} else base = try lower_place(state, expression.base)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .field_address,
|
|
type = .address,
|
|
pointee = lower_type(state, checked),
|
|
array_type = try array_shape(state, checked),
|
|
left = base,
|
|
slot = u32(typecheck.field_index(state.checked, id)),
|
|
span = span,
|
|
})
|
|
}
|
|
if (ast.expr_kind(state.tree, id) == .index) {
|
|
expression :: ast.index_expr(state.tree, id)
|
|
base_type :: typecheck.type_of(state.checked, expression.base)
|
|
if (base_type.kind == .slice or base_type.kind == .string) {
|
|
parts :: try slice_parts(state, expression.base)
|
|
if (state.terminated) return ir.no_value
|
|
index :: try lower_expression(state, expression.index)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
one :: try usize_constant(state, 1, span)
|
|
end :: try emit(state, ir.Instruction{ op = .add, type = .usize, left = index, right = one, span = span })
|
|
_ = try emit(state, ir.Instruction{ op = .slice_check, left = index, right = end, extra = parts.bound, span = span })
|
|
pointer :: try emit(state, ir.Instruction{
|
|
op = .pointer_offset, type = .pointer, left = parts.pointer, right = index,
|
|
pointee = lower_type(state, checked), array_type = try array_shape(state, checked), span = span,
|
|
})
|
|
return try pointer_address(state, pointer, checked, span)
|
|
}
|
|
base := ir.no_value
|
|
if (base_type.kind == .pointer) {
|
|
pointer :: try lower_expression(state, expression.base)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type)
|
|
if (descriptor.many) {
|
|
index :: try lower_expression(state, expression.index)
|
|
if (state.terminated) return ir.no_value
|
|
offset :: try emit(state, ir.Instruction{
|
|
op = .pointer_offset, type = .pointer, left = pointer, right = index,
|
|
pointee = lower_type(state, checked), array_type = try array_shape(state, checked), span = span,
|
|
})
|
|
return try pointer_address(state, offset, checked, span)
|
|
}
|
|
base = try pointer_address(state, pointer, descriptor.element, span)
|
|
} else base = try lower_place(state, expression.base)
|
|
if (state.terminated) return ir.no_value
|
|
index :: try lower_expression(state, expression.index)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .index_address,
|
|
type = .address,
|
|
pointee = lower_type(state, checked),
|
|
array_type = try array_shape(state, checked),
|
|
left = base,
|
|
right = index,
|
|
span = span,
|
|
})
|
|
}
|
|
if (trap_binding(state, id) or trap_type(state, id)) return ir.no_value
|
|
binding :: sema.binding(state.resolution, id)
|
|
location :: state.storage[usize(binding.data.declaration)]
|
|
operation ir.Op :: if (location.kind == .global) .address_global else .address_local
|
|
return try emit(state, ir.Instruction{
|
|
op = operation,
|
|
type = .address,
|
|
pointee = lower_type(state, checked),
|
|
array_type = try array_shape(state, checked),
|
|
slot = location.index,
|
|
span = span,
|
|
})
|
|
}
|
|
|
|
@hide:file
|
|
lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
span :: ast.node_span(tree, id)
|
|
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
|
shape :: try array_shape(state, typecheck.type_of(state.checked, id))
|
|
if (typecheck.is_enum_member(state.checked, id)) {
|
|
if (trap_type(state, id)) return ir.no_value
|
|
return try constant(state, enum_value(state, typecheck.type_of(state.checked, id), typecheck.field_index(state.checked, id)), span)
|
|
}
|
|
if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, id)
|
|
match ast.expr_kind(tree, id) {
|
|
.comptime_expr: {
|
|
if (!trap_type(state, id)) unreachable
|
|
}
|
|
.typed_constant: {
|
|
if (trap_type(state, id)) return ir.no_value
|
|
return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id))
|
|
}
|
|
.union_constant: {
|
|
if (trap_type(state, id)) return ir.no_value
|
|
bytes :: ast.union_constant(tree, id).value
|
|
text :: ast.literal_text(tree, bytes)
|
|
count :: ir.storage_size(state.module, .record, shape)
|
|
byte_shape :: try ir.add_array(state.module, .u8, ir.no_array, count, false)
|
|
scratch :: state.arguments.items.len
|
|
defer state.arguments.items = state.arguments.items.ptr[..scratch]
|
|
cursor := usize(1)
|
|
while (cursor < text.len - 1) {
|
|
byte :: ast.string_next(text, &cursor) catch |_| {
|
|
trap(state, try diagnostics.add(state.diagnostics, .invalid_escape, span), span)
|
|
return ir.no_value
|
|
}
|
|
element :: try constant(state, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = u64(byte) } }, span)
|
|
try arraylist.append(&state.arguments, element)
|
|
}
|
|
debug.assert(state.arguments.items.len - scratch == count)
|
|
block :: &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
|
start :: block.arguments.items.len
|
|
for (state.arguments.items[scratch..]) |element| try arraylist.append(&block.arguments, element)
|
|
payload :: try emit(state, ir.Instruction{
|
|
op = .array_literal, type = .array, array_type = byte_shape,
|
|
arguments_start = start, arguments_count = count, span = span,
|
|
})
|
|
return try emit(state, ir.Instruction{
|
|
op = .union_literal, type = .record, array_type = shape, left = payload, span = span,
|
|
})
|
|
}
|
|
.invalid: {
|
|
invalid :: ast.invalid(tree, id)
|
|
trap(state, invalid.diagnostic, invalid.span)
|
|
}
|
|
.range: {
|
|
# ranges have no physical value; only lower_for decomposes an iterable.
|
|
if (!trap_type(state, id)) unreachable
|
|
}
|
|
.array_type, .pointer_type, .function_type, .record_type, .enum_type, .union_type, .distinct_type, .union_tag_type, .slice_type, .optional_type, .fallible_type, .sum_type: {
|
|
if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
|
}
|
|
.alias_expr: return try lower_expression(state, ast.type_operand(tree, id))
|
|
.literal_undefined: {
|
|
if (trap_type(state, id)) return ir.no_value
|
|
checked :: typecheck.type_of(state.checked, id)
|
|
return try emit(state, ir.Instruction{
|
|
op = .undefined_value, type = lower_type(state, checked),
|
|
array_type = try array_shape(state, checked), span = span,
|
|
})
|
|
}
|
|
.enum_literal: {
|
|
if (!trap_type(state, id)) unreachable
|
|
}
|
|
.block_expr: {
|
|
if (trap_type(state, id)) return ir.no_value
|
|
return try lower_value_block(state, ast.block_expr(tree, id), id, typecheck.type_of(state.checked, id))
|
|
}
|
|
.match_expr: return try lower_match(state, id)
|
|
.try_expr: return try lower_try(state, id)
|
|
.catch_expr: return try lower_catch(state, id)
|
|
.literal_null: return try null_constant(state, .pointer, span)
|
|
.literal_void: return ir.no_value
|
|
.slice: return try lower_slice(state, id)
|
|
.builtin_call: return try lower_builtin(state, id)
|
|
.record_literal: {
|
|
checked :: typecheck.type_of(state.checked, id)
|
|
if (checked.kind == .record and typecheck.record_type(state.checked, checked.record_type).is_union) return try lower_union(state, id)
|
|
return try lower_record_as(state, id, checked)
|
|
}
|
|
.array_literal: {
|
|
scratch_start :: state.arguments.items.len
|
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
|
count :: ast.array_element_count(tree, id)
|
|
try arraylist.reserve(&state.arguments, scratch_start + count)
|
|
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
|
for (0..count) |i| {
|
|
expected :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, id).array_type).element
|
|
element :: try lower_as(state, ast.array_element(tree, id, i), expected)
|
|
if (state.terminated) return ir.no_value
|
|
state.arguments.items[scratch_start + i] = element
|
|
}
|
|
if (trap_type(state, id)) return ir.no_value
|
|
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
|
start :: block.arguments.items.len
|
|
try arraylist.reserve(&block.arguments, start + count)
|
|
for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch_start + i])
|
|
return try emit(state, ir.Instruction{
|
|
op = .array_literal,
|
|
type = .array,
|
|
array_type = shape,
|
|
arguments_start = start,
|
|
arguments_count = count,
|
|
span = span,
|
|
})
|
|
}
|
|
.index: {
|
|
if (is_place(state, id)) {
|
|
address :: try lower_place(state, id)
|
|
if (state.terminated) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .load_address, type = type, array_type = shape, left = address, span = span,
|
|
})
|
|
}
|
|
expression :: ast.index_expr(tree, id)
|
|
base :: try lower_expression(state, expression.base)
|
|
if (state.terminated) return ir.no_value
|
|
index :: try lower_expression(state, expression.index)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .index, type = type, array_type = shape, left = base, right = index, span = span,
|
|
})
|
|
}
|
|
.literal_int, .literal_float: {
|
|
if (trap_type(state, id)) return ir.no_value
|
|
return try lower_number(state, id)
|
|
}
|
|
.literal_bool: {
|
|
if (trap_type(state, id)) return ir.no_value
|
|
return try constant(state, ir.Value{
|
|
type = .bool,
|
|
data = ir.ValueData{ boolean = mem.eql(ast.literal_text(tree, id), "true") },
|
|
}, span)
|
|
}
|
|
.literal_string: {
|
|
if (trap_type(state, id)) return ir.no_value
|
|
return try lower_string(state, id)
|
|
}
|
|
.identifier, .selection: {
|
|
if (
|
|
ast.expr_kind(tree, id) == .selection
|
|
and sema.binding(state.resolution, id).kind == .none
|
|
) {
|
|
selection :: ast.selection_expr(tree, id)
|
|
if (trap_type(state, id)) return ir.no_value
|
|
base_type :: typecheck.type_of(state.checked, selection.base)
|
|
if (base_type.kind == .slice or base_type.kind == .string or base_type.kind == .array) {
|
|
member :: ast.literal_text(tree, selection.member)
|
|
if (mem.eql(member, "len") or mem.eql(member, "ptr")) {
|
|
parts :: try slice_parts(state, selection.base)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
return if (mem.eql(member, "len")) parts.bound else parts.pointer
|
|
}
|
|
}
|
|
if (base_type.kind == .pointer) {
|
|
descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type)
|
|
if (!descriptor.many and descriptor.element.kind == .array) {
|
|
pointer :: try lower_expression(state, selection.base)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
member :: ast.literal_text(tree, selection.member)
|
|
array :: typecheck.array_type(state.checked, descriptor.element.array_type)
|
|
if (mem.eql(member, "len")) return try constant(state, ir.Value{
|
|
type = .usize, data = ir.ValueData{ unsigned = u64(array.len) },
|
|
}, span)
|
|
if (mem.eql(member, "ptr")) {
|
|
zero :: try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = 0 } }, span)
|
|
return try emit(state, ir.Instruction{
|
|
op = .pointer_offset, type = .pointer, left = pointer, right = zero,
|
|
pointee = lower_type(state, array.element), array_type = try array_shape(state, array.element), span = span,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
if (is_place(state, id)) {
|
|
address :: try lower_place(state, id)
|
|
if (state.terminated) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .load_address, type = type, array_type = shape, left = address, span = span,
|
|
})
|
|
}
|
|
base :: try lower_expression(state, ast.selection_expr(tree, id).base)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .field, type = type, array_type = shape, left = base,
|
|
slot = u32(typecheck.field_index(state.checked, id)), span = span,
|
|
})
|
|
}
|
|
if (trap_binding(state, id) or trap_type(state, id)) return ir.no_value
|
|
binding :: sema.binding(state.resolution, id)
|
|
location :: state.storage[usize(binding.data.declaration)]
|
|
match location.kind {
|
|
.function: return try constant(state, ir.Value{
|
|
type = .function,
|
|
data = ir.ValueData{ function = ir.FunctionId(location.index) },
|
|
}, span)
|
|
.global, .local: {
|
|
declaration_type :: typecheck.type_of(state.checked, binding.data.declaration)
|
|
operation :: if (location.kind == .global) ir.Op.load_global else ir.Op.load_local
|
|
value :: try emit(state, ir.Instruction{
|
|
op = operation, type = lower_type(state, declaration_type),
|
|
array_type = try array_shape(state, declaration_type), slot = location.index, span = span,
|
|
})
|
|
return try convert_value(state, value, declaration_type, typecheck.type_of(state.checked, id), span)
|
|
}
|
|
.none: unreachable
|
|
}
|
|
}
|
|
.unary: {
|
|
expression :: ast.unary_expr(tree, id)
|
|
if (expression.operator == .unwrap) {
|
|
operand :: try lower_expression(state, expression.operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
source :: typecheck.type_of(state.checked, expression.operand)
|
|
present :: try optional_present(state, operand, source, span)
|
|
_ = try emit(state, ir.Instruction{ op = .optional_check, left = present, span = span })
|
|
return try optional_payload(state, operand, source, span)
|
|
}
|
|
if (expression.operator == .address_of) {
|
|
if (typecheck.type_of(state.checked, id).kind == .slice) {
|
|
parts :: try slice_parts(state, expression.operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
return try record_pair(state, shape, parts.pointer, parts.bound, span)
|
|
}
|
|
if (!is_place(state, expression.operand)) {
|
|
_ = try lower_expression(state, expression.operand)
|
|
if (!state.terminated) _ = trap_type(state, id)
|
|
return ir.no_value
|
|
}
|
|
address :: try lower_place(state, expression.operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
element :: typecheck.type_of(state.checked, expression.operand)
|
|
return try emit(state, ir.Instruction{
|
|
op = .pointer_from_address, type = .pointer, left = address,
|
|
pointee = lower_type(state, element), array_type = try array_shape(state, element), span = span,
|
|
})
|
|
}
|
|
if (expression.operator == .dereference) {
|
|
address :: try lower_place(state, id)
|
|
if (state.terminated) return ir.no_value
|
|
return try emit(state, ir.Instruction{
|
|
op = .load_address, type = type, array_type = shape, left = address, span = span,
|
|
})
|
|
}
|
|
if (expression.operator == .negate and ir.integer_bits(type) != 0 and !ir.unsigned_integer(type) and ast.expr_kind(tree, expression.operand) == .literal_int) {
|
|
text :: ast.literal_text(tree, expression.operand)
|
|
magnitude_limit :: enummap.get(&ir.integer_maxima, type)? + 1
|
|
if (integer_magnitude(text, magnitude_limit)) |magnitude| {
|
|
if (magnitude == magnitude_limit) return try constant(state, ir.Value{ type = type, data = ir.ValueData{ integer = enummap.get(&ir.integer_minima, type)? } }, span)
|
|
}
|
|
}
|
|
operand :: try lower_expression(state, expression.operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
op :: enummap.get(&unary_ops, expression.operator)?
|
|
return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span })
|
|
}
|
|
.binary: {
|
|
expression :: ast.binary_expr(tree, id)
|
|
if (expression.operator == .logical_and or expression.operator == .logical_or) return try lower_logical(state, id)
|
|
left_type :: typecheck.type_of(state.checked, expression.left)
|
|
right_type :: typecheck.type_of(state.checked, expression.right)
|
|
if (
|
|
(expression.operator == .equal or expression.operator == .not_equal)
|
|
and (left_type.kind == .null or right_type.kind == .null)
|
|
) {
|
|
optional_id :: if (left_type.kind == .null) expression.right else expression.left
|
|
value :: try lower_expression(state, optional_id)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
present :: try optional_present(state, value, typecheck.type_of(state.checked, optional_id), span)
|
|
if (expression.operator == .not_equal) return present
|
|
return try emit(state, ir.Instruction{ op = .logical_not, type = .bool, left = present, span = span })
|
|
}
|
|
left :: try lower_expression(state, expression.left)
|
|
if (state.terminated) return ir.no_value
|
|
right :: try lower_expression(state, expression.right)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
if (expression.operator == .add and type == .pointer) {
|
|
pointer :: typecheck.pointer_type(state.checked, typecheck.type_of(state.checked, id).pointer_type)
|
|
return try emit(state, ir.Instruction{
|
|
op = .pointer_offset, type = .pointer, left = left, right = right,
|
|
pointee = lower_type(state, pointer.element),
|
|
array_type = try array_shape(state, pointer.element), span = span,
|
|
})
|
|
}
|
|
op :: enummap.get(&binary_ops, expression.operator)?
|
|
return try emit(state, ir.Instruction{ op = op, type = type, left = left, right = right, span = span })
|
|
}
|
|
.call: {
|
|
target :: sema.binding(state.resolution, ast.call_expr(tree, id).callee)
|
|
if (target.kind == .builtin or typecheck.is_type_constructor(state.checked, id)) {
|
|
operand := ir.no_value
|
|
for (0..ast.call_arg_count(tree, id)) |i| {
|
|
value :: try lower_expression(state, ast.call_arg(tree, id, i))
|
|
if (state.terminated) return ir.no_value
|
|
if (i == 0) operand = value
|
|
}
|
|
if (trap_type(state, id)) return ir.no_value
|
|
if (typecheck.is_type_constructor(state.checked, id)) {
|
|
source :: lower_type(state, typecheck.type_of(state.checked, ast.call_arg(tree, id, 0)))
|
|
if (source == type) return operand
|
|
}
|
|
return try emit(state, ir.Instruction{ op = .convert, type = type, left = operand, span = span })
|
|
}
|
|
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
|
|
if (state.terminated) return ir.no_value
|
|
# recursion borrows later scratch slots; no items pointer survives a child.
|
|
scratch_start :: state.arguments.items.len
|
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
|
count :: ast.call_arg_count(tree, id)
|
|
try arraylist.reserve(&state.arguments, scratch_start + count)
|
|
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
|
for (0..count) |i| {
|
|
signature :: typecheck.type_of(state.checked, ast.call_expr(tree, id).callee).function
|
|
expected := typecheck.Type{}
|
|
if (signature != ast.NodeId(maxval!(u32)) and i < ast.function_param_count(tree, signature)) expected = typecheck.type_of(state.checked, ast.param(tree, ast.function_param(tree, signature, i)).type)
|
|
argument :: try lower_as(state, ast.call_arg(tree, id, i), expected)
|
|
if (state.terminated) return ir.no_value
|
|
state.arguments.items[scratch_start + i] = argument
|
|
}
|
|
if (trap_type(state, id)) return ir.no_value
|
|
# branchful arguments end in a different block from the callee.
|
|
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
|
start :: block.arguments.items.len
|
|
try arraylist.reserve(&block.arguments, start + count)
|
|
for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch_start + i])
|
|
return try emit(state, ir.Instruction{
|
|
op = .call,
|
|
type = type,
|
|
array_type = shape,
|
|
left = callee,
|
|
arguments_start = start,
|
|
arguments_count = count,
|
|
span = span,
|
|
})
|
|
}
|
|
}
|
|
return ir.no_value
|
|
}
|
|
|
|
@hide:file
|
|
integer_magnitude proc(text []u8, limit u64) ?u64 {
|
|
value := u64(0)
|
|
for (text) |byte| {
|
|
if (byte < '0' or byte > '9') return null
|
|
digit :: u64(byte - '0')
|
|
if (digit > limit or value > divtrunc!(limit - digit, u64(10))) return null
|
|
value = value * 10 + digit
|
|
}
|
|
return value
|
|
}
|
|
|
|
@hide:file
|
|
exact_float_integer proc(text []u8, value f64) bool {
|
|
# every finite f64 integer has at most 309 decimal digits, plus a terminator.
|
|
decimal [310]mut u8 := undefined
|
|
count :: c.snprintf(ptrcast!((&decimal).ptr), c_ulong(decimal.len), "%.0f", value)
|
|
if (count < 0 or usize(count) >= decimal.len) return false
|
|
start := usize(0)
|
|
while (start + 1 < text.len and text[start] == '0') start += 1
|
|
return mem.eql(text[start..], decimal[..usize(count)])
|
|
}
|
|
|
|
@hide:file
|
|
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
span :: ast.node_span(state.tree, id)
|
|
if (ast.float_value(state.tree, id)) |value| {
|
|
return try constant(state, ir.Value{
|
|
type = .float,
|
|
data = ir.ValueData{ real = value },
|
|
}, span)
|
|
}
|
|
text :: ast.literal_text(state.tree, id)
|
|
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
|
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
|
|
if (integer_magnitude(text, enummap.get(&ir.integer_maxima, type)?)) |magnitude| return try constant(state, ir.Value{ type = type, data = ir.ValueData{ integer = i64(magnitude) } }, span)
|
|
} else if (ir.unsigned_integer(type)) {
|
|
if (integer_magnitude(text, enummap.get(&ir.integer_maxima, type)?)) |magnitude| return try constant(state, ir.Value{ type = type, data = ir.ValueData{ unsigned = magnitude } }, span)
|
|
} else {
|
|
value :: strconv.parse_float(state.module.allocator, text) catch |err| {
|
|
match err {
|
|
.out_of_memory: return err
|
|
.invalid_number, .out_of_range: {
|
|
trap(state, try diagnostics.add(state.diagnostics, .literal_out_of_range, span), span)
|
|
return ir.no_value
|
|
}
|
|
}
|
|
}
|
|
if (ast.expr_kind(state.tree, id) == .literal_int and !exact_float_integer(text, value)) {
|
|
trap(state, try diagnostics.add(state.diagnostics, .inexact_integer_literal, span), span)
|
|
return ir.no_value
|
|
}
|
|
return try constant(state, ir.Value{ type = .float, data = ir.ValueData{ real = value } }, span)
|
|
}
|
|
trap(state, try diagnostics.add(state.diagnostics, .literal_out_of_range, span), span)
|
|
return ir.no_value
|
|
}
|
|
|
|
@hide:file
|
|
lower_string proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
span :: ast.node_span(state.tree, id)
|
|
text :: ast.literal_text(state.tree, id)
|
|
owned :: try mem.alloc(u8, state.module.allocator, text.len - 2)
|
|
transferred := false
|
|
defer if (!transferred) mem.free(state.module.allocator, owned)
|
|
length := usize(0)
|
|
cursor := usize(1)
|
|
end :: text.len - 1
|
|
while (cursor < end) {
|
|
byte :: ast.string_next(text, &cursor) catch |_| {
|
|
trap(state, try diagnostics.add(state.diagnostics, .invalid_escape, span), span)
|
|
return ir.no_value
|
|
}
|
|
owned[length] = byte
|
|
length += 1
|
|
}
|
|
if (state.module.strings.items.len > usize(maxval!(ir.StringId)))
|
|
return .out_of_memory
|
|
|
|
string_id :: ir.StringId(state.module.strings.items.len)
|
|
try arraylist.append(&state.module.strings, ir.StringStorage{
|
|
allocation = owned,
|
|
len = length,
|
|
})
|
|
transferred = true
|
|
return try constant(state, ir.Value{
|
|
type = .string,
|
|
data = ir.ValueData{ string = string_id },
|
|
}, span)
|
|
} |