Files
honey/source/lower/lower.hon
T

2607 lines
130 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
slice_elements arraylist.ArrayList(typecheck.Type)
slice_backings arraylist.ArrayList(ast.NodeId)
evaluation_functions ?@mut arraylist.ArrayList(ast.NodeId) = null
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,
slice_elements = arraylist.init(typecheck.Type, allocator),
slice_backings = arraylist.init(ast.NodeId, allocator),
evaluation_functions = functions,
arguments = arraylist.init(ir.ValueId, allocator),
cleanups = arraylist.init(ast.NodeId, allocator),
}
defer arraylist.deinit(&state.arguments)
defer arraylist.deinit(&state.cleanups)
defer arraylist.deinit(&state.slice_elements)
defer arraylist.deinit(&state.slice_backings)
# 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: {}
}
}
backing_index := usize(0)
while (backing_index < state.slice_backings.items.len) {
try lower_slice_backing(&state, state.slice_backings.items[backing_index])
backing_index += 1
}
# source element shapes are metadata, not record layout dependencies. defer
# them until recursive records have cached their completed physical layout.
cursor := usize(0)
while (cursor < state.slice_elements.items.len) {
type :: state.slice_elements.items[cursor]
child :: typecheck.array_type(checked, type.array_type).element
shape :: state.arrays[usize(type.array_type)]
element_shape :: try array_shape(&state, child)
module.arrays.items[usize(shape)].element_array = element_shape
cursor += 1
}
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
if (type.kind == .slice) {
state.module.arrays.items[usize(shape)].element = lower_type(state, descriptor.element)
try arraylist.append(&state.slice_elements, type)
}
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 or state.storage[usize(id)].kind != .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_slice_backing 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
backing :: ast.slice_constant(state.tree, id).value
value :: try lower_expression(state, backing)
if (!state.terminated) finish(state, value, ast.node_span(state.tree, id))
}
@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
state.module.functions.items[state.function].variadic = ast.callable_variadic(tree, id)
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
nullable :: child.kind == .pointer or child.kind == .function
if ((source.kind == .pointer or source.kind == .function) and nullable) return value
runtime :: lower_type(state, expected)
shape :: try array_shape(state, expected)
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, 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 := converted
if (!nullable) 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, runtime, 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 = runtime, array_type = shape, slot = slot, span = span })
}
if (expected.kind == .pointer and actual.kind == .string)
return try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, 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 == .pointer) {
pointer :: typecheck.pointer_type(state.checked, actual.pointer_type)
length :: try usize_constant(state, typecheck.array_type(state.checked, pointer.element.array_type).len, span)
return try record_pair(state, try array_shape(state, expected), value, 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") or mem.eql(name, "rem")) {
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
op ir.Op :: if (mem.eql(name, "rem")) .remainder else .divide
return try emit(state, ir.Instruction{
op = op,
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))
}
.slice_constant: {
if (trap_type(state, id)) return ir.no_value
if (state.storage[usize(id)].kind == .none) {
backing :: ast.slice_constant(tree, id).value
initializer :: try add_function(state.module, span)
if (state.evaluation_functions) |declarations|
try arraylist.append(declarations, maxval!(ast.NodeId))
global :: u32(state.module.globals.items.len)
try arraylist.append(&state.module.globals, ir.Global{
type = lower_type(state, typecheck.type_of(state.checked, backing)),
array_type = try array_shape(state, typecheck.type_of(state.checked, backing)),
initializer = initializer,
span = span,
})
state.storage[usize(id)] = Storage{ kind = .global, index = global }
try arraylist.append(&state.slice_backings, id)
}
location :: state.storage[usize(id)]
global :: state.module.globals.items[usize(location.index)]
address :: try emit(state, ir.Instruction{
op = .address_global, type = .address, pointee = global.type,
array_type = global.array_type, slot = location.index, span = span,
})
pointer :: try emit(state, ir.Instruction{
op = .pointer_from_address, type = .pointer, left = address,
pointee = global.type, array_type = global.array_type, span = span,
})
descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, id).array_type)
backing :: ast.slice_constant(tree, id).value
elements :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, backing).array_type)
extra :: if (descriptor.sentinel != null) usize(1) else usize(0)
len :: elements.len - extra
return try record_pair(state, shape, pointer, try usize_constant(state, len, span), span)
}
.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)
signature :: typecheck.type_of(state.checked, ast.call_expr(tree, id).callee).function
fixed_arguments :: if (signature != ast.NodeId(maxval!(u32))) ast.function_param_count(tree, signature) else 0
for (0..count) |i| {
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,
variadic = signature != ast.NodeId(maxval!(u32)) and ast.callable_variadic(tree, signature),
fixed_arguments = fixed_arguments,
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)
}