Files
honey/source/lower/lower.hon
T

966 lines
40 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)
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) {
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)
state := State{
tree = tree,
resolution = resolution,
checked = checked,
diagnostics = diagnostic_store,
module = &module,
storage = storage,
arrays = arrays,
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: {
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(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 }
}
.invalid: {}
}
}
try find_entry(&state)
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: try lower_global(&state, id)
.invalid: {}
}
}
return module
}
@hide:file
lower_type proc(type typecheck.Type) ir.Type {
return match type.kind {
.int: .int
.usize: .usize
.array: .array
.float: .float
.bool: .bool
.string: .string
.void: .void
.function: .function
.none, .invalid, .range: .invalid
}
}
@hide:file
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
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) child = try prepare_array(state, descriptor.element.array_type)
result :: try ir.add_array(
state.module,
lower_type(descriptor.element),
child,
descriptor.len,
descriptor.mutable,
)
state.arrays[usize(id)] = result
return result
}
@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) void ! (mem.AllocError | diagnostics.CapacityError) {
tree :: state.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
.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(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, tree.root))
}
@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(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(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: {
type :: typecheck.type_of(state.checked, statement)
local :: try ir.add_local(
state.module,
ir.FunctionId(state.function),
lower_type(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 ir.Type :: if (family == .usize) .usize else .int
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
_ = 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(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 = .int, data = ir.ValueData{ integer = 1 } }
if (family == .usize) unit = ir.Value{ type = .usize, 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) {
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(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 (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(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
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)
.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) == .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(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(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(typecheck.type_of(state.checked, id))
shape :: try array_shape(state, typecheck.type_of(state.checked, 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: {
if (!trap_type(state, id)) {
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
}
}
.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: {
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 type == .int and ast.expr_kind(tree, expression.operand) == .literal_int) {
text :: ast.source_text(tree, ast.node_span(tree, expression.operand))
if (integer_magnitude(text, u64(maxval!(i64)) + 1)) |magnitude| {
if (magnitude == u64(maxval!(i64)) + 1) {
return try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = minval!(i64) } }, 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: {
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 (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(typecheck.type_of(state.checked, id))
if (type == .int) {
if (integer_magnitude(text, u64(maxval!(i64)))) |magnitude| {
return try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = i64(magnitude) } }, span)
}
} else if (type == .usize) {
if (integer_magnitude(text, maxval!(u64))) |magnitude| {
return try constant(state, ir.Value{ type = .usize, 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)
}