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) { 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) 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 } } .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: try lower_global(&state, id) .import, .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, 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(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(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, .selection: { 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) }