1351 lines
60 KiB
Plaintext
1351 lines
60 KiB
Plaintext
import "@std/mem"
|
|
import "@std/arraylist"
|
|
import "@std/strconv"
|
|
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
|
|
}
|
|
|
|
@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)
|
|
records []mut ir.ArrayId
|
|
function usize = 0
|
|
block ir.BlockId = ir.BlockId(0)
|
|
terminated bool = false
|
|
loop ?LoopTargets = 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.
|
|
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`.
|
|
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) {
|
|
module := ir.init(allocator)
|
|
errdefer ir.deinit(&module)
|
|
storage :: try mem.alloc(Storage, allocator, ast.node_count(tree))
|
|
defer mem.free(allocator, storage)
|
|
mem.set(storage, Storage{})
|
|
arrays :: try mem.alloc(ir.ArrayId, allocator, typecheck.array_type_count(checked))
|
|
defer mem.free(allocator, arrays)
|
|
mem.set(arrays, ir.no_array)
|
|
records :: try mem.alloc(ir.ArrayId, allocator, typecheck.record_type_count(checked))
|
|
defer mem.free(allocator, records)
|
|
mem.set(records, ir.no_array)
|
|
state := State{
|
|
tree = tree,
|
|
resolution = resolution,
|
|
checked = checked,
|
|
diagnostics = diagnostic_store,
|
|
module = &module,
|
|
storage = storage,
|
|
arrays = arrays,
|
|
records = records,
|
|
arguments = arraylist.init(ir.ValueId, allocator),
|
|
}
|
|
defer arraylist.deinit(&state.arguments)
|
|
|
|
# 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: {
|
|
function :: try add_function(&module, ast.node_span(tree, 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))
|
|
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: {}
|
|
}
|
|
}
|
|
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: try lower_function(&state, id)
|
|
.statement: {
|
|
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
|
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
|
|
}
|
|
.import, .invalid: {}
|
|
}
|
|
}
|
|
return module
|
|
}
|
|
|
|
@hide:file
|
|
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 })
|
|
}
|
|
return match type.kind {
|
|
.int: .int
|
|
.usize: .usize
|
|
.i8: .i8
|
|
.i16: .i16
|
|
.i32: .i32
|
|
.isize: .isize
|
|
.u8: .u8
|
|
.u16: .u16
|
|
.u32: .u32
|
|
.u64: .u64
|
|
.array: .array
|
|
.record: .record
|
|
.float: .float
|
|
.bool: .bool
|
|
.string: .string
|
|
.void: .void
|
|
.function: .function
|
|
.enumeration: unreachable
|
|
.none, .invalid, .range, .type_value: .invalid
|
|
}
|
|
}
|
|
|
|
@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 != .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
|
|
if (descriptor.element.kind == .array or descriptor.element.kind == .record) 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_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
if (state.records[usize(id)] != ir.no_array) return state.records[usize(id)]
|
|
descriptor :: typecheck.record_type(state.checked, id)
|
|
fields := arraylist.init(ir.RecordField, state.module.allocator)
|
|
defer arraylist.deinit(&fields)
|
|
try arraylist.reserve(&fields, descriptor.fields.len)
|
|
for (descriptor.fields) |field| {
|
|
tag := ir.Value{}
|
|
if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) {
|
|
members :: typecheck.enum_type(state.checked, descriptor.tag_type.enum_type).members
|
|
for members |member, index| {
|
|
if (ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?)) {
|
|
tag = enum_value(state, descriptor.tag_type, index)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
try arraylist.append(&fields, ir.RecordField{
|
|
type = lower_type(state, field.type),
|
|
array_type = try array_shape(state, field.type),
|
|
tag = tag,
|
|
})
|
|
}
|
|
shape :: if (descriptor.is_union) try ir.add_union(state.module, fields.items, descriptor.tag_type.kind == .enumeration)
|
|
else try ir.add_record(state.module, fields.items)
|
|
state.records[usize(id)] = shape
|
|
return shape
|
|
}
|
|
|
|
@hide:file
|
|
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
|
|
id :: ir.FunctionId(module.functions.items.len)
|
|
try arraylist.append(&module.functions, ir.init_function(module.allocator))
|
|
function := &module.functions.items[usize(id)]
|
|
function.span = span
|
|
try arraylist.append(&function.blocks, ir.init_block(module.allocator))
|
|
return id
|
|
}
|
|
|
|
@hide:file
|
|
find_entry proc(state @mut State, root_package ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
package_tree := state.tree^
|
|
package_tree.root = root_package
|
|
tree :: &package_tree
|
|
for (0..ast.package_decl_count(tree)) |i| {
|
|
id :: ast.package_decl(tree, i)
|
|
name := diagnostics.Span{ start = 0, end = 0 }
|
|
match ast.decl_kind(tree, id) {
|
|
.function: name = ast.function_decl(tree, id).name
|
|
.statement: name = ast.statement_decl(tree, id).name
|
|
.import, .invalid: continue
|
|
}
|
|
if (!mem.eql(ast.source_text(tree, name), "main")) continue
|
|
if (ast.decl_kind(tree, id) != .function) {
|
|
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
|
return
|
|
}
|
|
declaration :: ast.function_decl(tree, id)
|
|
result :: lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
|
|
if (ast.function_param_count(tree, id) != 0 or result == .function) {
|
|
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
|
return
|
|
}
|
|
state.module.entry = ir.FunctionId(state.storage[usize(id)].index)
|
|
return
|
|
}
|
|
state.module.entry_diagnostic = try diagnostics.add(
|
|
state.diagnostics,
|
|
.missing_entry,
|
|
ast.node_span(tree, root_package),
|
|
)
|
|
}
|
|
|
|
@hide:file
|
|
trap proc(state @mut State, diagnostic diagnostics.DiagnosticId, span diagnostics.Span) void {
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
kind = .trap,
|
|
diagnostic = diagnostic,
|
|
span = span,
|
|
}
|
|
state.terminated = true
|
|
}
|
|
|
|
@hide:file
|
|
trap_type proc(state @mut State, id ast.NodeId) bool {
|
|
type :: typecheck.type_of(state.checked, id)
|
|
if (type.kind != .invalid) return false
|
|
trap(state, type.diagnostic, ast.node_span(state.tree, id))
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
trap_binding proc(state @mut State, id ast.NodeId) bool {
|
|
binding :: sema.binding(state.resolution, id)
|
|
if (binding.kind != .invalid) return false
|
|
trap(state, binding.data.diagnostic, ast.node_span(state.tree, id))
|
|
return true
|
|
}
|
|
|
|
@hide:file
|
|
finish proc(state @mut State, value ir.ValueId, span diagnostics.Span) void {
|
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
|
value = value,
|
|
span = span,
|
|
}
|
|
state.terminated = true
|
|
}
|
|
|
|
@hide:file
|
|
emit proc(state @mut State, instruction ir.Instruction) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try ir.emit(state.module, ir.FunctionId(state.function), state.block, instruction)
|
|
}
|
|
|
|
@hide:file
|
|
constant proc(state @mut State, value ir.Value, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
return try emit(state, ir.Instruction{ op = .constant, type = value.type, constant = value, span = span })
|
|
}
|
|
|
|
@hide:file
|
|
lower_global proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
global :: state.module.globals.items[usize(state.storage[usize(id)].index)]
|
|
state.function = usize(global.initializer)
|
|
state.block = ir.BlockId(0)
|
|
state.terminated = false
|
|
state.module.functions.items[state.function].result = global.type
|
|
state.module.functions.items[state.function].result_array_type = global.array_type
|
|
if (trap_binding(state, id)) return
|
|
declaration :: ast.statement_decl(state.tree, id)
|
|
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
|
|
value :: try lower_expression(state, declaration.initializer)
|
|
if (state.terminated or trap_type(state, id)) return
|
|
finish(state, value, declaration.span)
|
|
}
|
|
|
|
@hide:file
|
|
lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
declaration :: ast.function_decl(tree, id)
|
|
state.function = usize(state.storage[usize(id)].index)
|
|
state.block = ir.BlockId(0)
|
|
state.terminated = false
|
|
state.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))
|
|
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) }
|
|
}
|
|
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
|
|
}
|
|
finish(state, ir.no_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)
|
|
try predeclare_locals(state, conditional.then_block)
|
|
if (conditional.else_block) |alternative| try predeclare_locals(state, alternative)
|
|
}
|
|
.while_stmt: try predeclare_locals(state, ast.while_stmt(state.tree, statement).body)
|
|
.for_stmt: {
|
|
loop :: ast.for_stmt(state.tree, statement)
|
|
family :: typecheck.type_of(state.checked, loop.iterable).range_element
|
|
type :: lower_type(state, typecheck.Type{ kind = family })
|
|
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| state.storage[usize(capture)] = state.storage[usize(statement)]
|
|
if (loop.index) |index| {
|
|
index_slot :: try ir.add_local(state.module, ir.FunctionId(state.function), .int, ir.no_array)
|
|
state.storage[usize(index)] = Storage{ kind = .local, index = u32(index_slot) }
|
|
}
|
|
try predeclare_locals(state, loop.body)
|
|
}
|
|
else: {}
|
|
}
|
|
}
|
|
}
|
|
|
|
@hide:file
|
|
lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
# 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))
|
|
}
|
|
}
|
|
|
|
@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)
|
|
condition :: try lower_expression(state, conditional.condition)
|
|
if (state.terminated) return
|
|
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
|
|
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
|
|
condition :: try lower_expression(state, loop.condition)
|
|
if (state.terminated) return
|
|
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 }
|
|
defer state.loop = previous_loop
|
|
state.block = body
|
|
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 = false
|
|
}
|
|
|
|
@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_expression(state, loop.iterable)
|
|
if (!state.terminated) _ = trap_type(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 }
|
|
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_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
|
tree :: state.tree
|
|
match ast.stmt_kind(tree, id) {
|
|
.if_stmt: try lower_if(state, id)
|
|
.while_stmt: try lower_while(state, id)
|
|
.for_stmt: try lower_for(state, id)
|
|
.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
|
|
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 (ast.expr_kind(tree, assignment.target) == .index
|
|
or (ast.expr_kind(tree, assignment.target) == .selection
|
|
and sema.binding(state.resolution, assignment.target).kind == .none)) {
|
|
value :: try lower_expression(state, assignment.value)
|
|
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_expression(state, assignment.value)
|
|
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_expression(state, declaration.initializer)
|
|
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,
|
|
})
|
|
}
|
|
.return_stmt: {
|
|
statement :: ast.return_stmt(tree, id)
|
|
value := ir.no_value
|
|
if (statement.value) |expression| value = try lower_expression(state, expression)
|
|
if (state.terminated or trap_type(state, id)) return
|
|
finish(state, value, statement.span)
|
|
}
|
|
}
|
|
}
|
|
|
|
@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 (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) }
|
|
}
|
|
if (!arm_view.expression) try predeclare_locals(state, arm_view.body)
|
|
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 (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_expression(state, arm_view.body)
|
|
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
|
|
op = .store_local, left = value, slot = result_slot, span = span,
|
|
})
|
|
} else try lower_block(state, arm_view.body, 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_expression(state, ast.field(tree, ast.record_field(tree, id, 0)).value?)
|
|
}
|
|
}
|
|
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)
|
|
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| {
|
|
value :: try lower_expression(state, ast.field(tree, ast.record_field(tree, id, i)).value?)
|
|
if (state.terminated) return ir.no_value
|
|
state.arguments.items[scratch_start + i] = value
|
|
}
|
|
if (trap_type(state, id)) return ir.no_value
|
|
descriptor :: typecheck.record_type(state.checked, type.record_type)
|
|
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_expression(state, initializer)
|
|
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) {
|
|
.index: return is_place(state, ast.index_expr(state.tree, id).base)
|
|
.selection: {
|
|
if (sema.binding(state.resolution, id).kind == .none) {
|
|
return is_place(state, ast.selection_expr(state.tree, id).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
|
|
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) == .selection
|
|
and sema.binding(state.resolution, id).kind == .none) {
|
|
expression :: ast.selection_expr(state.tree, id)
|
|
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 :: 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) {
|
|
.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, .record_type, .enum_type, .union_type, .distinct_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))
|
|
.enum_literal: {
|
|
if (!trap_type(state, id)) unreachable
|
|
}
|
|
.match_expr: return try lower_match(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| {
|
|
element :: try lower_expression(state, ast.array_element(tree, id, i))
|
|
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.source_text(tree, span), "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) {
|
|
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: return try emit(state, ir.Instruction{ op = .load_global, type = type, array_type = shape, slot = location.index, span = span })
|
|
.local: return try emit(state, ir.Instruction{ op = .load_local, type = type, array_type = shape, slot = location.index, span = span })
|
|
.none: unreachable
|
|
}
|
|
}
|
|
.unary: {
|
|
expression :: ast.unary_expr(tree, id)
|
|
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.source_text(tree, ast.node_span(tree, expression.operand))
|
|
magnitude_limit :: ir.integer_max(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 = ir.integer_min(type) } }, span)
|
|
}
|
|
}
|
|
}
|
|
operand :: try lower_expression(state, expression.operand)
|
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
|
op ir.Op :: match expression.operator {
|
|
.negate: .negate
|
|
.logical_not: .logical_not
|
|
}
|
|
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 :: 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
|
|
op ir.Op :: match expression.operator {
|
|
.add: .add
|
|
.subtract: .subtract
|
|
.multiply: .multiply
|
|
.divide: .divide
|
|
.equal: .equal
|
|
.not_equal: .not_equal
|
|
.less: .less
|
|
.less_equal: .less_equal
|
|
.greater: .greater
|
|
.greater_equal: .greater_equal
|
|
.logical_and, .logical_or: unreachable
|
|
}
|
|
return try emit(state, ir.Instruction{ op = op, type = type, left = left, right = right, span = span })
|
|
}
|
|
.call: {
|
|
target :: sema.binding(state.resolution, ast.call_expr(tree, id).callee)
|
|
if (target.kind == .builtin or typecheck.is_type_constructor(state.checked, id)) {
|
|
operand := ir.no_value
|
|
for (0..ast.call_arg_count(tree, id)) |i| {
|
|
value :: try lower_expression(state, ast.call_arg(tree, id, i))
|
|
if (state.terminated) return ir.no_value
|
|
if (i == 0) operand = value
|
|
}
|
|
if (trap_type(state, id)) return ir.no_value
|
|
if (typecheck.is_type_constructor(state.checked, id)) {
|
|
source :: lower_type(state, typecheck.type_of(state.checked, ast.call_arg(tree, id, 0)))
|
|
if (source == type) return operand
|
|
}
|
|
return try emit(state, ir.Instruction{ op = .convert, type = type, left = operand, span = span })
|
|
}
|
|
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
|
|
if (state.terminated) return ir.no_value
|
|
# recursion borrows later scratch slots; no items pointer survives a child.
|
|
scratch_start :: state.arguments.items.len
|
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
|
count :: ast.call_arg_count(tree, id)
|
|
try arraylist.reserve(&state.arguments, scratch_start + count)
|
|
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
|
for (0..count) |i| {
|
|
argument :: try lower_expression(state, ast.call_arg(tree, id, i))
|
|
if (state.terminated) return ir.no_value
|
|
state.arguments.items[scratch_start + i] = argument
|
|
}
|
|
if (trap_type(state, id)) return ir.no_value
|
|
# branchful arguments end in a different block from the callee.
|
|
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
|
start :: block.arguments.items.len
|
|
try arraylist.reserve(&block.arguments, start + count)
|
|
for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch_start + i])
|
|
return try emit(state, ir.Instruction{
|
|
op = .call,
|
|
type = type,
|
|
array_type = shape,
|
|
left = callee,
|
|
arguments_start = start,
|
|
arguments_count = count,
|
|
span = span,
|
|
})
|
|
}
|
|
}
|
|
return ir.no_value
|
|
}
|
|
|
|
@hide:file
|
|
integer_magnitude proc(text []u8, limit u64) ?u64 {
|
|
value := u64(0)
|
|
for (text) |byte| {
|
|
if (byte < '0' or byte > '9') return null
|
|
digit :: u64(byte - '0')
|
|
if (digit > limit or value > divtrunc!(limit - digit, u64(10))) return null
|
|
value = value * 10 + digit
|
|
}
|
|
return value
|
|
}
|
|
|
|
@hide:file
|
|
exact_float_integer proc(text []u8, value f64) bool {
|
|
# every finite f64 integer has at most 309 decimal digits, plus a terminator.
|
|
decimal [310]mut u8 := undefined
|
|
count :: c.snprintf(ptrcast!((&decimal).ptr), c_ulong(decimal.len), "%.0f", value)
|
|
if (count < 0 or usize(count) >= decimal.len) return false
|
|
start := usize(0)
|
|
while (start + 1 < text.len and text[start] == '0') start += 1
|
|
return mem.eql(text[start..], decimal[..usize(count)])
|
|
}
|
|
|
|
@hide:file
|
|
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
|
span :: ast.node_span(state.tree, id)
|
|
text :: ast.source_text(state.tree, span)
|
|
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, ir.integer_max(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, ir.integer_max(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
|
|
hex_digit proc(byte u8) ?u8 {
|
|
if (byte >= '0' and byte <= '9') return byte - '0'
|
|
if (byte >= 'a' and byte <= 'f') return byte - 'a' + 10
|
|
if (byte >= 'A' and byte <= 'F') return byte - 'A' + 10
|
|
return null
|
|
}
|
|
|
|
@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.source_text(state.tree, span)
|
|
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
|
|
valid := true
|
|
while (cursor < end) {
|
|
byte := text[cursor]
|
|
cursor += 1
|
|
if (byte == '\\') {
|
|
if (cursor >= end) {
|
|
valid = false
|
|
break
|
|
}
|
|
escape :: text[cursor]
|
|
cursor += 1
|
|
match escape {
|
|
'\\': byte = '\\'
|
|
'"': byte = '"'
|
|
'n': byte = '\n'
|
|
'r': byte = '\r'
|
|
't': byte = '\t'
|
|
'0': byte = 0
|
|
'x': {
|
|
if (end - cursor < 2) {
|
|
valid = false
|
|
break
|
|
}
|
|
high := u8(0)
|
|
low := u8(0)
|
|
if (hex_digit(text[cursor])) |digit| high = digit
|
|
else {
|
|
valid = false
|
|
break
|
|
}
|
|
if (hex_digit(text[cursor + 1])) |digit| low = digit
|
|
else {
|
|
valid = false
|
|
break
|
|
}
|
|
byte = high * 16 + low
|
|
cursor += 2
|
|
}
|
|
else: {
|
|
valid = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
owned[length] = byte
|
|
length += 1
|
|
}
|
|
if (!valid) {
|
|
trap(state, try diagnostics.add(state.diagnostics, .invalid_escape, span), span)
|
|
return ir.no_value
|
|
}
|
|
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)
|
|
} |