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 cleanup_depth usize } @hide:file State :: struct { tree @ast.Tree resolution @sema.Resolution checked @typecheck.Result diagnostics @mut diagnostics.Store module @mut ir.Module storage []mut Storage arrays []mut ir.ArrayId arguments arraylist.ArrayList(ir.ValueId) cleanups arraylist.ArrayList(ast.NodeId) records []mut ir.ArrayId function usize = 0 block ir.BlockId = ir.BlockId(0) terminated bool = false loop ?LoopTargets = null return_type typecheck.Type = typecheck.Type{} fallthrough_block ?ast.NodeId = null fallthrough_diagnostic ?diagnostics.DiagnosticId = null } #! lowers checked source into caller-owned executable storage; release with ir.deinit. #! no frontend storage or source bytes are retained by the returned module. 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) { return try lower_module(allocator, tree, resolution, checked, root_package, null, diagnostic_store) } #! lowers an isolated compile-time call wrapper without applying program entry-name rules. #! the caller supplies a checked parameterless function and owns the returned IR. lower_evaluation proc( allocator mem.Allocator, tree @ast.Tree, resolution @sema.Resolution, checked @typecheck.Result, entry ast.NodeId, diagnostic_store @mut diagnostics.Store, ) ir.Module ! (mem.AllocError | diagnostics.CapacityError) { return try lower_module(allocator, tree, resolution, checked, tree.root, entry, diagnostic_store) } @hide:file lower_module proc( allocator mem.Allocator, tree @ast.Tree, resolution @sema.Resolution, checked @typecheck.Result, root_package ast.NodeId, entry ?ast.NodeId, diagnostic_store @mut diagnostics.Store, ) ir.Module ! (mem.AllocError | diagnostics.CapacityError) { module := ir.init(allocator) errdefer ir.deinit(&module) storage :: try mem.alloc(Storage, allocator, ast.node_count(tree)) defer mem.free(allocator, storage) mem.set(storage, Storage{}) arrays :: try mem.alloc(ir.ArrayId, allocator, typecheck.array_type_count(checked)) defer mem.free(allocator, arrays) mem.set(arrays, ir.no_array) records :: try mem.alloc(ir.ArrayId, allocator, typecheck.record_type_count(checked)) defer mem.free(allocator, records) mem.set(records, ir.no_array) state := State{ tree = tree, resolution = resolution, checked = checked, diagnostics = diagnostic_store, module = &module, storage = storage, arrays = arrays, records = records, arguments = arraylist.init(ir.ValueId, allocator), cleanups = arraylist.init(ast.NodeId, allocator), } defer arraylist.deinit(&state.arguments) defer arraylist.deinit(&state.cleanups) # allocate every package identity before lowering any initializer or body. for (0..ast.package_decl_count(tree)) |i| { id :: ast.package_decl(tree, i) match ast.decl_kind(tree, id) { .function: { 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: {} } } if (entry) |function| module.entry = ir.FunctionId(storage[usize(function)].index) else try find_entry(&state, root_package) for (0..ast.package_decl_count(tree)) |i| { id :: ast.package_decl(tree, i) match ast.decl_kind(tree, id) { .function: 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 }) } if (type.kind == .slice) return .record if (type.kind == .fallible) return .record if (type.kind == .optional) return lower_optional_type(state, typecheck.array_type(state.checked, type.array_type).element) return 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 .pointer: .pointer .float: .float .bool: .bool .string: .string .void: .void .function: .function .enumeration: unreachable .none, .invalid, .range, .type_value, .null, .anyopaque: .invalid .slice, .optional, .fallible: unreachable } } @hide:file lower_optional_type proc(state @State, child typecheck.Type) ir.Type { if (child.kind == .pointer or child.kind == .function) return lower_type(state, child) return .record } @hide:file array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) { if (type.kind == .record) return try prepare_record(state, type.record_type) if (type.kind == .slice or type.kind == .optional or type.kind == .fallible) return try prepare_container(state, type) if (type.kind != .array) return ir.no_array # checker history includes unresolved shapes; materialize only final used types. return try prepare_array(state, type.array_type) } @hide:file prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) { if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)] descriptor :: typecheck.array_type(state.checked, id) child := ir.no_array child = try array_shape(state, descriptor.element) result :: try ir.add_array( state.module, lower_type(state, descriptor.element), child, descriptor.len, descriptor.mutable, ) state.arrays[usize(id)] = result return result } @hide:file prepare_container proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) { descriptor :: typecheck.array_type(state.checked, type.array_type) if (type.kind == .optional and (descriptor.element.kind == .pointer or descriptor.element.kind == .function)) return ir.no_array index :: usize(type.array_type) if (state.arrays[index] != ir.no_array) return state.arrays[index] if (type.kind == .fallible) { fields [2]ir.RecordField :: [ { type = lower_type(state, descriptor.element), array_type = try array_shape(state, descriptor.element), tag = ir.Value{ type = .int, data = ir.ValueData{ integer = 0 } }, }, { type = lower_type(state, descriptor.error_type), array_type = try array_shape(state, descriptor.error_type), tag = ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }, }, ] shape :: try ir.add_union(state.module, fields[..], true) state.arrays[index] = shape return shape } fields [2]mut ir.RecordField := [{ type = .pointer }, { type = .usize }] if (type.kind == .optional) { fields[0] = ir.RecordField{ type = .bool } fields[1] = ir.RecordField{ type = lower_type(state, descriptor.element), array_type = try array_shape(state, descriptor.element) } } shape :: try ir.add_record(state.module, fields[..]) state.arrays[index] = shape return shape } @hide:file prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) { if (state.records[usize(id)] != ir.no_array) return state.records[usize(id)] descriptor :: typecheck.record_type(state.checked, id) fields := arraylist.init(ir.RecordField, state.module.allocator) defer arraylist.deinit(&fields) try arraylist.reserve(&fields, descriptor.fields.len) for (descriptor.fields) |field| { tag := ir.Value{} if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) { members :: typecheck.enum_type(state.checked, descriptor.tag_type.enum_type).members for members |member, index| { if (ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?)) { tag = enum_value(state, descriptor.tag_type, index) break } } } try arraylist.append(&fields, ir.RecordField{ type = lower_type(state, field.type), array_type = try array_shape(state, field.type), tag = tag, }) } shape :: if (descriptor.is_union) try ir.add_union(state.module, fields.items, descriptor.tag_type.kind == .enumeration) else try ir.add_record(state.module, fields.items) state.records[usize(id)] = shape return shape } @hide:file add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError { id :: ir.FunctionId(module.functions.items.len) try arraylist.append(&module.functions, ir.init_function(module.allocator)) function := &module.functions.items[usize(id)] function.span = span try arraylist.append(&function.blocks, ir.init_block(module.allocator)) return id } @hide:file find_entry proc(state @mut State, root_package ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { package_tree := state.tree^ package_tree.root = root_package tree :: &package_tree for (0..ast.package_decl_count(tree)) |i| { id :: ast.package_decl(tree, i) name := diagnostics.Span{ start = 0, end = 0 } match ast.decl_kind(tree, id) { .function: name = ast.function_decl(tree, id).name .statement: name = ast.statement_decl(tree, id).name .import, .invalid: continue } if (!mem.eql(ast.source_text(tree, name), "main")) continue if (ast.decl_kind(tree, id) != .function) { state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name) return } declaration :: ast.function_decl(tree, id) result :: lower_type(state, typecheck.type_of(state.checked, declaration.return_type)) if (ast.function_param_count(tree, id) != 0 or result == .function) { state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name) return } state.module.entry = ir.FunctionId(state.storage[usize(id)].index) return } state.module.entry_diagnostic = try diagnostics.add( state.diagnostics, .missing_entry, ast.node_span(tree, root_package), ) } @hide:file trap proc(state @mut State, diagnostic diagnostics.DiagnosticId, span diagnostics.Span) void { state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .trap, diagnostic = diagnostic, span = span, } state.terminated = true } @hide:file trap_type proc(state @mut State, id ast.NodeId) bool { type :: typecheck.type_of(state.checked, id) if (type.kind != .invalid) return false trap(state, type.diagnostic, ast.node_span(state.tree, id)) return true } @hide:file trap_binding proc(state @mut State, id ast.NodeId) bool { binding :: sema.binding(state.resolution, id) if (binding.kind != .invalid) return false trap(state, binding.data.diagnostic, ast.node_span(state.tree, id)) return true } @hide:file finish proc(state @mut State, value ir.ValueId, span diagnostics.Span) void { state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ value = value, span = span, } state.terminated = true } @hide:file emit proc(state @mut State, instruction ir.Instruction) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { return try ir.emit(state.module, ir.FunctionId(state.function), state.block, instruction) } @hide:file constant proc(state @mut State, value ir.Value, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { return try emit(state, ir.Instruction{ op = .constant, type = value.type, constant = value, span = span }) } @hide:file lower_global proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { global :: state.module.globals.items[usize(state.storage[usize(id)].index)] state.function = usize(global.initializer) state.block = ir.BlockId(0) state.terminated = false state.module.functions.items[state.function].result = global.type state.module.functions.items[state.function].result_array_type = global.array_type if (trap_binding(state, id)) return declaration :: ast.statement_decl(state.tree, id) if (declaration.type) |annotation| if (trap_type(state, annotation)) return value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id)) if (state.terminated or trap_type(state, id)) return finish(state, value, declaration.span) } @hide:file lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { tree :: state.tree declaration :: ast.function_decl(tree, id) state.function = usize(state.storage[usize(id)].index) state.block = ir.BlockId(0) state.terminated = false state.loop = null state.fallthrough_block = declaration.body state.fallthrough_diagnostic = typecheck.fallthrough_diagnostic(state.checked, id) state.return_type = typecheck.type_of(state.checked, declaration.return_type) state.module.functions.items[state.function].result = lower_type(state, typecheck.type_of(state.checked, declaration.return_type)) state.module.functions.items[state.function].result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type)) state.module.functions.items[state.function].result_fallible = state.return_type.kind == .fallible for (0..ast.function_param_count(tree, id)) |i| { parameter :: ast.function_param(tree, id, i) type :: typecheck.type_of(state.checked, parameter) local :: try ir.add_parameter( state.module, ir.FunctionId(state.function), lower_type(state, type), try array_shape(state, type), ) state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) } } if (ast.function_external(tree, id)) { if (trap_binding(state, id) or trap_type(state, id)) return name :: ast.source_text(tree, declaration.name) owned :: try mem.alloc(u8, state.module.allocator, name.len) memcopy!(owned, name) state.module.functions.items[state.function].external_name = owned return } try predeclare_locals(state, declaration.body) # signature errors are entry traps; body errors are reached in statement order. if (trap_binding(state, id) or trap_type(state, declaration.return_type)) return for (0..ast.function_param_count(tree, id)) |i| { if (trap_type(state, ast.function_param(tree, id, i))) return } if (trap_type(state, id)) return try lower_block(state, declaration.body, false) if (state.terminated) return if (typecheck.fallthrough_diagnostic(state.checked, id)) |diagnostic| { trap(state, diagnostic, ast.node_span(tree, declaration.body)) return } value := ir.no_value if (state.return_type.kind == .fallible) value = try wrap_fallible(state, value, state.return_type, false, ast.node_span(tree, declaration.body)) finish(state, value, ast.node_span(tree, declaration.body)) } @hide:file predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { for (0..ast.block_statement_count(state.tree, block)) |i| { statement :: ast.block_statement(state.tree, block, i) match ast.stmt_kind(state.tree, statement) { .declaration: { if (typecheck.type_of(state.checked, statement).kind == .type_value) continue if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, statement).initializer) == .alias_expr) continue type :: typecheck.type_of(state.checked, statement) local :: try ir.add_local( state.module, ir.FunctionId(state.function), lower_type(state, type), try array_shape(state, type), ) state.storage[usize(statement)] = Storage{ kind = .local, index = u32(local) } } .if_stmt: { conditional :: ast.if_stmt(state.tree, statement) if (conditional.capture) |capture| try predeclare_capture(state, capture) try predeclare_locals(state, conditional.then_block) if (conditional.else_block) |alternative| try predeclare_locals(state, alternative) } .while_stmt: { loop :: ast.while_stmt(state.tree, statement) if (loop.capture) |capture| try predeclare_capture(state, capture) try predeclare_locals(state, loop.body) } .for_stmt: { loop :: ast.for_stmt(state.tree, statement) 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) } .block_stmt: try predeclare_locals(state, ast.statement_block(state.tree, statement)) .defer_stmt, .errdefer_stmt: { cleanup :: ast.cleanup_stmt(state.tree, statement) if (cleanup.capture) |capture| try predeclare_capture(state, capture) try predeclare_locals(state, cleanup.body) } else: {} } } } @hide:file predeclare_capture proc(state @mut State, capture ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { type :: typecheck.type_of(state.checked, capture) slot :: try ir.add_local(state.module, ir.FunctionId(state.function), lower_type(state, type), try array_shape(state, type)) state.storage[usize(capture)] = Storage{ kind = .local, index = u32(slot) } } @hide:file store_capture proc(state @mut State, capture ast.NodeId, value ir.ValueId, optional_type typecheck.Type, span diagnostics.Span) void ! (mem.AllocError | diagnostics.CapacityError) { payload :: try optional_payload(state, value, optional_type, span) _ = try emit(state, ir.Instruction{ op = .store_local, left = payload, slot = state.storage[usize(capture)].index, span = span }) } @hide:file lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (mem.AllocError | diagnostics.CapacityError) { cleanup_depth :: state.cleanups.items.len defer state.cleanups.items = state.cleanups.items.ptr[..cleanup_depth] # reset every direct declaration before control can return or trap within the scope. if (fresh_scope) { for (0..ast.block_statement_count(state.tree, block)) |i| { declaration :: ast.block_statement(state.tree, block, i) if (ast.stmt_kind(state.tree, declaration) != .declaration) continue if (typecheck.type_of(state.checked, declaration).kind == .type_value) continue if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, declaration).initializer) == .alias_expr) continue _ = try emit(state, ir.Instruction{ op = .reset_local, slot = state.storage[usize(declaration)].index, span = ast.node_span(state.tree, declaration), }) } } for (0..ast.block_statement_count(state.tree, block)) |i| { if (state.terminated) return try lower_statement(state, ast.block_statement(state.tree, block, i)) } if (!state.terminated) { if (state.fallthrough_block) |function_body| { if (function_body == block) { if (state.fallthrough_diagnostic) |diagnostic| { trap(state, diagnostic, ast.node_span(state.tree, block)) return } } } } if (!state.terminated) try lower_cleanups(state, cleanup_depth, ir.no_value) } @hide:file lower_cleanups proc(state @mut State, depth usize, error_value ir.ValueId) void ! (mem.AllocError | diagnostics.CapacityError) { index := state.cleanups.items.len while (index > depth) { index -= 1 id :: state.cleanups.items[index] if (ast.stmt_kind(state.tree, id) == .errdefer_stmt and error_value == ir.no_value) continue cleanup :: ast.cleanup_stmt(state.tree, id) if (cleanup.capture) |capture| { _ = try emit(state, ir.Instruction{ op = .store_local, left = error_value, slot = state.storage[usize(capture)].index, span = cleanup.span, }) } try lower_block(state, cleanup.body, true) if (state.terminated) return } } @hide:file wrap_fallible proc(state @mut State, value ir.ValueId, type typecheck.Type, failed bool, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { slot :: if (failed) u32(1) else u32(0) shape :: try array_shape(state, type) return try emit(state, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, slot = slot, left = value, constant = state.module.arrays.items[usize(shape)].fields[usize(slot)].tag, span = span, }) } @hide:file fallible_failed proc(state @mut State, value ir.ValueId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { tag :: try emit(state, ir.Instruction{ op = .union_tag, type = .int, left = value, span = span }) error_tag :: try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }, span) return try emit(state, ir.Instruction{ op = .equal, type = .bool, left = tag, right = error_tag, span = span }) } @hide:file lower_return proc(state @mut State, value ir.ValueId, span diagnostics.Span) void ! (mem.AllocError | diagnostics.CapacityError) { if (state.return_type.kind != .fallible) { try lower_cleanups(state, 0, ir.no_value) if (!state.terminated) finish(state, value, span) return } failed :: try fallible_failed(state, value, span) success :: try add_block(state) failure :: try add_block(state) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .branch, value = failed, target = failure, alternative = success, span = span, } state.block = failure error_type :: typecheck.array_type(state.checked, state.return_type.array_type).error_type error_value :: try project(state, value, lower_type(state, error_type), try array_shape(state, error_type), 1, span) try lower_cleanups(state, 0, error_value) if (!state.terminated) finish(state, value, span) state.block = success state.terminated = false try lower_cleanups(state, 0, ir.no_value) if (!state.terminated) finish(state, value, span) } @hide:file add_block proc(state @mut State) ir.BlockId ! mem.AllocError { function := &state.module.functions.items[state.function] id :: ir.BlockId(function.blocks.items.len) try arraylist.append(&function.blocks, ir.init_block(state.module.allocator)) return id } @hide:file lower_if proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { conditional :: ast.if_stmt(state.tree, id) value :: try lower_expression(state, conditional.condition) if (state.terminated) return condition := value if (conditional.capture != null) condition = try optional_present(state, value, typecheck.type_of(state.checked, conditional.condition), conditional.span) origin :: state.block consequent :: try add_block(state) alternative :: try add_block(state) state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{ kind = .branch, value = condition, target = consequent, alternative = alternative, span = ast.node_span(state.tree, conditional.condition), } state.block = consequent if (conditional.capture) |capture| try store_capture(state, capture, value, typecheck.type_of(state.checked, conditional.condition), conditional.span) try lower_block(state, conditional.then_block, true) consequent_end :: state.block consequent_falls :: !state.terminated state.block = alternative state.terminated = false if (conditional.else_block) |block| try lower_block(state, block, true) alternative_end :: state.block alternative_falls :: !state.terminated if (!consequent_falls and !alternative_falls) { state.terminated = true return } join :: try add_block(state) if (consequent_falls) { state.module.functions.items[state.function].blocks.items[usize(consequent_end)].terminator = ir.Terminator{ kind = .jump, target = join, span = conditional.span, } } if (alternative_falls) { state.module.functions.items[state.function].blocks.items[usize(alternative_end)].terminator = ir.Terminator{ kind = .jump, target = join, span = conditional.span, } } state.block = join state.terminated = false } @hide:file lower_while proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { loop :: ast.while_stmt(state.tree, id) origin :: state.block guard :: try add_block(state) state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{ kind = .jump, target = guard, span = loop.span, } state.block = guard value :: try lower_expression(state, loop.condition) if (state.terminated) return condition := value if (loop.capture != null) condition = try optional_present(state, value, typecheck.type_of(state.checked, loop.condition), loop.span) guard_end :: state.block body :: try add_block(state) exit :: try add_block(state) state.module.functions.items[state.function].blocks.items[usize(guard_end)].terminator = ir.Terminator{ kind = .branch, value = condition, target = body, alternative = exit, span = ast.node_span(state.tree, loop.condition), } previous_loop :: state.loop state.loop = LoopTargets{ continue_target = guard, exit = exit, cleanup_depth = state.cleanups.items.len } defer state.loop = previous_loop state.block = body if (loop.capture) |capture| try store_capture(state, capture, value, typecheck.type_of(state.checked, loop.condition), loop.span) try lower_block(state, loop.body, true) if (!state.terminated) { state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = guard, span = loop.span, } } state.block = exit state.terminated = 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, cleanup_depth = state.cleanups.items.len } defer state.loop = previous_loop state.block = body if (loop.capture) |capture| _ = trap_binding(state, capture) if (!state.terminated) try lower_block(state, loop.body, true) if (!state.terminated) { state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = update, span = loop.span, } } state.block = update state.terminated = false last :: try emit(state, ir.Instruction{ op = .load_local, type = family, slot = slot, span = interval.span }) finished :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = last, right = end, span = interval.span }) # continue reaches this guard too, so an inclusive maximum never adds one. state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .branch, value = finished, target = exit, alternative = increment, span = interval.span, } state.block = increment unit := ir.Value{ type = family, data = ir.ValueData{ integer = 1 } } if (ir.unsigned_integer(family)) unit.data = ir.ValueData{ unsigned = 1 } one :: try constant(state, unit, interval.span) next :: try emit(state, ir.Instruction{ op = .add, type = family, left = last, right = one, span = interval.span }) _ = try emit(state, ir.Instruction{ op = .store_local, type = family, left = next, slot = slot, span = interval.span }) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = condition, span = loop.span, } state.block = exit state.terminated = false } @hide:file lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) { tree :: state.tree match ast.stmt_kind(tree, id) { .block_stmt: try lower_block(state, ast.statement_block(tree, id), true) .defer_stmt, .errdefer_stmt: { if (trap_type(state, id)) return try arraylist.append(&state.cleanups, id) } .if_stmt: try lower_if(state, id) .while_stmt: try lower_while(state, id) .for_stmt: try lower_for(state, id) .break_stmt, .continue_stmt: { if (trap_type(state, id)) return if (state.loop) |targets| { target := targets.exit # each loop supplies its own guard or guarded-update entry. if (ast.stmt_kind(tree, id) == .continue_stmt) target = targets.continue_target try lower_cleanups(state, targets.cleanup_depth, ir.no_value) if (state.terminated) return 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) == .unary or (ast.expr_kind(tree, assignment.target) == .selection and sema.binding(state.resolution, assignment.target).kind == .none)) { value :: try lower_as(state, assignment.value, typecheck.type_of(state.checked, assignment.target)) if (state.terminated) return if (!is_place(state, assignment.target)) { _ = try lower_expression(state, assignment.target) if (!state.terminated) _ = trap_type(state, id) return } address :: try lower_place(state, assignment.target) if (state.terminated or trap_type(state, id)) return _ = try emit(state, ir.Instruction{ op = .store_address, type = .void, left = address, right = value, span = ast.node_span(tree, assignment.target), }) return } if (trap_binding(state, assignment.target) or trap_type(state, id)) return value :: try lower_as(state, assignment.value, typecheck.type_of(state.checked, assignment.target)) if (state.terminated) return binding :: sema.binding(state.resolution, assignment.target) _ = try emit(state, ir.Instruction{ op = .assign_local, type = lower_type(state, typecheck.type_of(state.checked, assignment.target)), array_type = try array_shape(state, typecheck.type_of(state.checked, assignment.target)), left = value, slot = state.storage[usize(binding.data.declaration)].index, span = ast.node_span(tree, assignment.target), }) } .invalid: { invalid :: ast.invalid(tree, id) trap(state, invalid.diagnostic, invalid.span) } .expression: _ = try lower_expression(state, ast.statement_expr(tree, id)) .declaration: { if (typecheck.type_of(state.checked, id).kind == .type_value) return if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) return if (trap_binding(state, id)) return declaration :: ast.statement_decl(tree, id) if (declaration.type) |annotation| if (trap_type(state, annotation)) return value :: try lower_as(state, declaration.initializer, typecheck.type_of(state.checked, id)) if (state.terminated or trap_type(state, id)) return _ = try emit(state, ir.Instruction{ op = .store_local, type = lower_type(state, typecheck.type_of(state.checked, id)), array_type = try array_shape(state, typecheck.type_of(state.checked, id)), left = value, slot = state.storage[usize(id)].index, span = declaration.span, }) } .return_stmt: { statement :: ast.return_stmt(tree, id) value := ir.no_value if (statement.value) |expression| value = try lower_as(state, expression, state.return_type) if (state.terminated or trap_type(state, id)) return if (statement.value == null and state.return_type.kind == .fallible) { value = try wrap_fallible(state, value, state.return_type, false, statement.span) } try lower_return(state, value, statement.span) } } } @hide:file 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_as(state, arm_view.body, type) if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{ op = .store_local, left = value, slot = result_slot, span = span, }) } else 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_as(state, ast.field(tree, ast.record_field(tree, id, 0)).value?, typecheck.union_payload_type(state.checked, id)) } } if (state.terminated) return ir.no_value return try emit(state, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, slot = u32(slot), left = payload, constant = field.tag, span = ast.node_span(tree, id), }) } @hide:file lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { tree :: state.tree scratch_start :: state.arguments.items.len defer state.arguments.items = state.arguments.items.ptr[..scratch_start] explicit_count :: ast.record_field_count(tree, id) if (type.kind != .record) { for (0..explicit_count) |i| { _ = try lower_expression(state, ast.field(tree, ast.record_field(tree, id, i)).value?) if (state.terminated) return ir.no_value } _ = trap_type(state, id) return ir.no_value } descriptor :: typecheck.record_type(state.checked, type.record_type) try arraylist.reserve(&state.arguments, scratch_start + explicit_count) for (0..explicit_count) |_| try arraylist.append(&state.arguments, ir.no_value) for (0..explicit_count) |i| { initializer :: ast.field(tree, ast.record_field(tree, id, i)).value? expected := typecheck.Type{} for (descriptor.fields) |field, slot| { if (typecheck.initializer_field(state.checked, id, slot) == initializer) { expected = field.type break } } value :: try lower_as(state, initializer, expected) if (state.terminated) return ir.no_value state.arguments.items[scratch_start + i] = value } if (trap_type(state, id)) return ir.no_value ordered_start :: state.arguments.items.len try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len) for (descriptor.fields) |_| try arraylist.append(&state.arguments, ir.no_value) for (0..(descriptor.fields.len)) |i| { initializer :: typecheck.initializer_field(state.checked, id, i) value := ir.no_value # ponytail: quadratic field reorder; retain an inverse map if large records make lowering costly. for (0..explicit_count) |j| { if (ast.field(tree, ast.record_field(tree, id, j)).value? == initializer) { value = state.arguments.items[scratch_start + j] break } } if (value == ir.no_value) value = try lower_as(state, initializer, descriptor.fields[i].type) if (state.terminated) return ir.no_value state.arguments.items[ordered_start + i] = value } block := &state.module.functions.items[state.function].blocks.items[usize(state.block)] start :: block.arguments.items.len try arraylist.reserve(&block.arguments, start + descriptor.fields.len) for (0..(descriptor.fields.len)) |i| try arraylist.append(&block.arguments, state.arguments.items[ordered_start + i]) return try emit(state, ir.Instruction{ op = .record_literal, type = .record, array_type = try array_shape(state, type), arguments_start = start, arguments_count = descriptor.fields.len, span = ast.node_span(tree, id), }) } @hide:file is_place proc(state @State, id ast.NodeId) bool { match ast.expr_kind(state.tree, id) { .unary: { expression :: ast.unary_expr(state.tree, id) return expression.operator == .dereference } .index: { base :: ast.index_expr(state.tree, id).base kind :: typecheck.type_of(state.checked, base).kind return kind == .pointer or kind == .slice or kind == .string or is_place(state, base) } .selection: { if (sema.binding(state.resolution, id).kind == .none) { base :: ast.selection_expr(state.tree, id).base checked :: typecheck.type_of(state.checked, base) if (checked.kind == .pointer) { pointer :: typecheck.pointer_type(state.checked, checked.pointer_type) return pointer.element.kind == .record } return is_place(state, base) } binding :: sema.binding(state.resolution, id) if (binding.kind != .declaration) return false location :: state.storage[usize(binding.data.declaration)] return location.kind == .local or location.kind == .global } .identifier: { binding :: sema.binding(state.resolution, id) if (binding.kind == .invalid) return true if (binding.kind != .declaration) return false location :: state.storage[usize(binding.data.declaration)] return location.kind == .local or location.kind == .global } else: return false } } @hide:file pointer_address proc( state @mut State, pointer ir.ValueId, element typecheck.Type, span diagnostics.Span, ) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { return try emit(state, ir.Instruction{ op = .pointer_address, type = .address, left = pointer, pointee = lower_type(state, element), array_type = try array_shape(state, element), span = span, }) } @hide:file usize_constant proc(state @mut State, value usize, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { return try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = u64(value) } }, span) } @hide:file null_constant proc(state @mut State, type ir.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { return try constant(state, ir.Value{ type = type, data = ir.ValueData{ unsigned = maxval!(u64) } }, span) } @hide:file record_pair proc(state @mut State, shape ir.ArrayId, first, second ir.ValueId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { block :: &state.module.functions.items[state.function].blocks.items[usize(state.block)] start :: block.arguments.items.len try arraylist.append(&block.arguments, first) try arraylist.append(&block.arguments, second) return try emit(state, ir.Instruction{ op = .record_literal, type = .record, array_type = shape, arguments_start = start, arguments_count = 2, span = span, }) } @hide:file project proc(state @mut State, value ir.ValueId, type ir.Type, shape ir.ArrayId, slot u32, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { return try emit(state, ir.Instruction{ op = .field, type = type, array_type = shape, left = value, slot = slot, span = span }) } @hide:file optional_present proc(state @mut State, value ir.ValueId, type typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { child :: typecheck.array_type(state.checked, type.array_type).element runtime :: lower_type(state, child) if (child.kind == .pointer or child.kind == .function) { absent :: try null_constant(state, runtime, span) return try emit(state, ir.Instruction{ op = .not_equal, type = .bool, left = value, right = absent, span = span }) } return try project(state, value, .bool, ir.no_array, 0, span) } @hide:file optional_payload proc(state @mut State, value ir.ValueId, type typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { child :: typecheck.array_type(state.checked, type.array_type).element runtime :: lower_type(state, child) if (child.kind == .pointer or child.kind == .function) return value return try project(state, value, runtime, try array_shape(state, child), 1, span) } @hide:file zero_value proc(state @mut State, type ir.Type, shape ir.ArrayId, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { if (!ir.aggregate(type)) { value := ir.Value{ type = type, data = ir.ValueData{ unsigned = 0 } } if (type == .pointer or type == .function) value.data = ir.ValueData{ unsigned = maxval!(u64) } return try constant(state, value, span) } descriptor :: state.module.arrays.items[usize(shape)] if (descriptor.is_union) return try emit(state, ir.Instruction{ op = .union_literal, type = type, array_type = shape, span = span }) scratch :: state.arguments.items.len defer state.arguments.items = state.arguments.items.ptr[..scratch] count :: if (type == .array) descriptor.len else descriptor.fields.len for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value) for (0..count) |i| { child :: if (type == .array) descriptor.element else descriptor.fields[i].type child_shape :: if (type == .array) descriptor.element_array else descriptor.fields[i].array_type value :: try zero_value(state, child, child_shape, span) state.arguments.items[scratch + i] = value } block :: &state.module.functions.items[state.function].blocks.items[usize(state.block)] start :: block.arguments.items.len for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch + i]) op ir.Op :: if (type == .array) .array_literal else .record_literal return try emit(state, ir.Instruction{ op = op, type = type, array_type = shape, arguments_start = start, arguments_count = count, span = span, }) } @hide:file lower_as proc(state @mut State, id ast.NodeId, expected typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { actual :: typecheck.type_of(state.checked, id) span :: ast.node_span(state.tree, id) if (actual.kind == .invalid or expected.kind == .invalid) return try lower_expression(state, id) if (expected.kind == .fallible and actual.kind != .fallible) { descriptor :: typecheck.array_type(state.checked, expected.array_type) failed :: !typecheck.value_assignable(state.checked, actual, descriptor.element) channel :: if (failed) descriptor.error_type else descriptor.element payload :: try lower_as(state, id, channel) if (state.terminated) return ir.no_value return try wrap_fallible(state, payload, expected, failed, span) } if (expected.kind == .optional and actual.kind != .optional) { child :: typecheck.array_type(state.checked, expected.array_type).element runtime :: lower_type(state, child) nullable :: child.kind == .pointer or child.kind == .function if (actual.kind == .null and nullable) return try null_constant(state, runtime, span) payload := ir.no_value if (actual.kind == .null) payload = try zero_value(state, runtime, try array_shape(state, child), span) else payload = try lower_as(state, id, child) if (state.terminated or nullable) return payload present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = actual.kind != .null } }, span) return try record_pair(state, try array_shape(state, expected), present, payload, span) } if (expected.kind == .slice and actual.kind != .slice) { parts :: try slice_parts(state, id) if (state.terminated) return ir.no_value return try record_pair(state, try array_shape(state, expected), parts.pointer, parts.bound, span) } value :: try lower_expression(state, id) if (state.terminated) return ir.no_value return try convert_value(state, value, actual, expected, span) } @hide:file convert_value proc(state @mut State, value ir.ValueId, actual, expected typecheck.Type, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { if (actual.kind == .fallible and expected.kind == .fallible) { if (actual.array_type == expected.array_type) return value source :: typecheck.array_type(state.checked, actual.array_type) destination :: typecheck.array_type(state.checked, expected.array_type) shape :: try array_shape(state, expected) slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape)) failed :: try fallible_failed(state, value, span) success :: try add_block(state) failure :: try add_block(state) join :: try add_block(state) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .branch, value = failed, target = failure, alternative = success, span = span, } for (0..2) |i| { state.block = if (i == 0) success else failure child :: if (i == 0) source.element else source.error_type target :: if (i == 0) destination.element else destination.error_type payload := ir.no_value if (child.kind != .void) payload = try project(state, value, lower_type(state, child), try array_shape(state, child), u32(i), span) converted :: try convert_value(state, payload, child, target, span) wrapped :: try wrap_fallible(state, converted, expected, i == 1, span) _ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span }) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = join, span = span, } } state.block = join return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span }) } if (expected.kind == .optional and actual.kind != .optional) { child :: typecheck.array_type(state.checked, expected.array_type).element runtime :: lower_type(state, child) nullable :: child.kind == .pointer or child.kind == .function if (actual.kind == .null and nullable) return try null_constant(state, runtime, span) payload := ir.no_value if (actual.kind == .null) payload = try zero_value(state, runtime, try array_shape(state, child), span) else payload = try convert_value(state, value, actual, child, span) if (nullable) return payload present :: try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = actual.kind != .null } }, span) return try record_pair(state, try array_shape(state, expected), present, payload, span) } if (actual.kind == .optional and expected.kind == .optional and actual.array_type != expected.array_type) { source :: typecheck.array_type(state.checked, actual.array_type).element child :: typecheck.array_type(state.checked, expected.array_type).element if (source.kind == .pointer or source.kind == .function) return value shape :: try array_shape(state, expected) slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape)) present :: try optional_present(state, value, actual, span) some :: try add_block(state) none :: try add_block(state) join :: try add_block(state) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .branch, value = present, target = some, alternative = none, span = span, } state.block = some payload :: try optional_payload(state, value, actual, span) converted :: try convert_value(state, payload, source, child, span) wrapped :: try record_pair(state, shape, present, converted, span) _ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span }) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = join, span = span, } state.block = none absent :: try zero_value(state, .record, shape, span) _ = try emit(state, ir.Instruction{ op = .store_local, left = absent, slot = slot, span = span }) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = join, span = span, } state.block = join return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span }) } if (expected.kind == .slice and actual.kind == .string) { pointer :: try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span }) length :: try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span }) return try record_pair(state, try array_shape(state, expected), pointer, length, span) } if (expected.kind == .slice and actual.kind == .array) { source_shape :: try array_shape(state, actual) slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .array, source_shape)) _ = try emit(state, ir.Instruction{ op = .store_local, left = value, slot = slot, span = span }) address :: try emit(state, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = source_shape, slot = slot, span = span, }) pointer :: try emit(state, ir.Instruction{ op = .pointer_from_address, type = .pointer, left = address, pointee = .array, array_type = source_shape, span = span, }) length :: try usize_constant(state, typecheck.array_type(state.checked, actual.array_type).len, span) return try record_pair(state, try array_shape(state, expected), pointer, length, span) } if (!typecheck.error_assignable(state.checked, actual, expected) and !typecheck.error_assignable(state.checked, expected, actual)) return value source_union := false if (actual.kind == .record) source_union = typecheck.record_type(state.checked, actual.record_type).is_union if (expected.kind == .enumeration) { if (!source_union) return value return try emit(state, ir.Instruction{ op = .union_tag, type = lower_type(state, expected), left = value, span = span }) } if (expected.kind != .record) return value destination :: typecheck.record_type(state.checked, expected.record_type) if (!destination.is_union) return value if (actual.kind == .record and actual.record_type == expected.record_type) return value shape :: try array_shape(state, expected) if (actual.kind == .record and !source_union) { for destination.fields |field, i| { if (field.type.kind != .record or field.type.record_type != actual.record_type or field.type.nominal != actual.nominal) continue return try emit(state, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, slot = u32(i), left = value, constant = state.module.arrays.items[usize(shape)].fields[i].tag, span = span, }) } unreachable } discriminant := value source_shape := ir.no_array if (source_union) { source_shape = try array_shape(state, actual) tag_type :: typecheck.record_type(state.checked, actual.record_type).tag_type discriminant = try emit(state, ir.Instruction{ op = .union_tag, type = lower_type(state, tag_type), left = value, span = span }) } slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape)) join :: try add_block(state) for destination.fields |field, i| { selected :: try add_block(state) next :: try add_block(state) tag :: state.module.arrays.items[usize(shape)].fields[i].tag expected_tag :: try constant(state, tag, span) condition :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = discriminant, right = expected_tag, span = span }) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .branch, value = condition, target = selected, alternative = next, span = span, } state.block = selected payload := ir.no_value if (source_union and field.type.kind != .void) { source_fields :: state.module.arrays.items[usize(source_shape)].fields for source_fields |source_field, j| { if (source_field.tag.data.unsigned != tag.data.unsigned) continue payload = try project(state, value, source_field.type, source_field.array_type, u32(j), span) break } } wrapped :: try emit(state, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, slot = u32(i), left = payload, constant = tag, span = span, }) _ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span }) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = join, span = span, } state.block = next } state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .unreachable, span = span } state.block = join return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span }) } @hide:file lower_try proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { operand :: ast.type_operand(state.tree, id) value :: try lower_expression(state, operand) if (state.terminated or trap_type(state, id)) return ir.no_value span :: ast.node_span(state.tree, id) descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, operand).array_type) failed :: try fallible_failed(state, value, span) success :: try add_block(state) failure :: try add_block(state) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .branch, value = failed, target = failure, alternative = success, span = span, } state.block = failure payload :: try project(state, value, lower_type(state, descriptor.error_type), try array_shape(state, descriptor.error_type), 1, span) target_error :: typecheck.array_type(state.checked, state.return_type.array_type).error_type converted :: try convert_value(state, payload, descriptor.error_type, target_error, span) result :: try wrap_fallible(state, converted, state.return_type, true, span) try lower_cleanups(state, 0, converted) if (!state.terminated) finish(state, result, span) state.block = success state.terminated = false if (descriptor.element.kind == .void) return ir.no_value return try project(state, value, lower_type(state, descriptor.element), try array_shape(state, descriptor.element), 0, span) } @hide:file lower_catch proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { expression :: ast.catch_expr(state.tree, id) value :: try lower_expression(state, expression.operand) if (state.terminated or trap_type(state, id)) return ir.no_value span :: expression.span descriptor :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, expression.operand).array_type) type :: typecheck.type_of(state.checked, id) runtime :: lower_type(state, type) shape :: try array_shape(state, type) slot := u32(0) if (runtime != .void) slot = u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape)) failed :: try fallible_failed(state, value, span) success :: try add_block(state) failure :: try add_block(state) join :: try add_block(state) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .branch, value = failed, target = failure, alternative = success, span = span, } state.block = success if (runtime != .void) { payload :: try project(state, value, lower_type(state, descriptor.element), try array_shape(state, descriptor.element), 0, span) converted :: try convert_value(state, payload, descriptor.element, type, span) _ = try emit(state, ir.Instruction{ op = .store_local, left = converted, slot = slot, span = span }) } state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = join, span = span, } state.block = failure if (expression.capture) |capture| { try predeclare_capture(state, capture) payload :: try project(state, value, lower_type(state, descriptor.error_type), try array_shape(state, descriptor.error_type), 1, span) _ = try emit(state, ir.Instruction{ op = .store_local, left = payload, slot = state.storage[usize(capture)].index, span = span }) } if (!trap_type(state, expression.handler)) { if (expression.handler_is_block) { try predeclare_locals(state, expression.handler) try lower_block(state, expression.handler, true) } else { fallback :: try lower_as(state, expression.handler, type) if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{ op = .store_local, left = fallback, slot = slot, span = span, }) } } if (!state.terminated) state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{ kind = .jump, target = join, span = span, } state.block = join state.terminated = false if (runtime == .void) return ir.no_value return try emit(state, ir.Instruction{ op = .load_local, type = runtime, array_type = shape, slot = slot, span = span }) } @hide:file SliceParts :: struct { pointer ir.ValueId bound ir.ValueId element typecheck.Type } @hide:file borrow_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { if (is_place(state, id)) return try lower_place(state, id) value :: try lower_expression(state, id) if (state.terminated) return ir.no_value checked :: typecheck.type_of(state.checked, id) type :: lower_type(state, checked) shape :: try array_shape(state, checked) span :: ast.node_span(state.tree, id) slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), type, shape)) _ = try emit(state, ir.Instruction{ op = .store_local, type = type, array_type = shape, left = value, slot = slot, span = span }) return try emit(state, ir.Instruction{ op = .address_local, type = .address, pointee = type, array_type = shape, slot = slot, span = span }) } @hide:file slice_parts proc(state @mut State, id ast.NodeId) SliceParts ! (mem.AllocError | diagnostics.CapacityError) { checked :: typecheck.type_of(state.checked, id) span :: ast.node_span(state.tree, id) result := SliceParts{ pointer = ir.no_value, bound = ir.no_value, element = typecheck.Type{} } if (checked.kind == .slice) { value :: try lower_expression(state, id) if (state.terminated) return result result.pointer = try project(state, value, .pointer, ir.no_array, 0, span) result.bound = try project(state, value, .usize, ir.no_array, 1, span) result.element = typecheck.array_type(state.checked, checked.array_type).element return result } if (checked.kind == .string) { value :: try lower_expression(state, id) if (state.terminated) return result result.pointer = try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span }) result.bound = try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span }) result.element = typecheck.Type{ kind = .u8 } return result } if (checked.kind == .pointer) { descriptor :: typecheck.pointer_type(state.checked, checked.pointer_type) result.pointer = try lower_expression(state, id) if (state.terminated) return result result.element = descriptor.element if (!descriptor.many and descriptor.element.kind == .array) { array :: typecheck.array_type(state.checked, descriptor.element.array_type) result.element = array.element result.bound = try usize_constant(state, array.len, span) } return result } if (checked.kind == .array) { descriptor :: typecheck.array_type(state.checked, checked.array_type) address :: try borrow_expression(state, id) if (state.terminated) return result result.pointer = try emit(state, ir.Instruction{ op = .pointer_from_address, type = .pointer, left = address, pointee = .array, array_type = try array_shape(state, checked), span = span, }) result.bound = try usize_constant(state, descriptor.len, span) result.element = descriptor.element return result } _ = try lower_expression(state, id) return result } @hide:file lower_slice proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { expression :: ast.slice_expr(state.tree, id) span :: ast.node_span(state.tree, id) parts :: try slice_parts(state, expression.base) if (state.terminated) return ir.no_value start := ir.no_value if (expression.start) |node| start = try lower_expression(state, node) else start = try usize_constant(state, 0, span) if (state.terminated) return ir.no_value end := parts.bound if (expression.end) |node| end = try lower_expression(state, node) if (state.terminated or trap_type(state, id)) return ir.no_value _ = try emit(state, ir.Instruction{ op = .slice_check, left = start, right = end, extra = parts.bound, span = span }) child :: parts.element runtime :: lower_type(state, child) shape :: try array_shape(state, child) if (expression.sentinel) |node| { sentinel :: try lower_as(state, node, child) if (state.terminated) return ir.no_value base_type :: typecheck.type_of(state.checked, expression.base) has_sentinel := base_type.kind == .string if (base_type.kind == .slice) has_sentinel = typecheck.array_type(state.checked, base_type.array_type).sentinel != null if (parts.bound != ir.no_value and !has_sentinel) { one :: try usize_constant(state, 1, span) through :: try emit(state, ir.Instruction{ op = .add, type = .usize, left = end, right = one, span = span }) _ = try emit(state, ir.Instruction{ op = .slice_check, left = end, right = through, extra = parts.bound, span = span }) } pointer :: try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = parts.pointer, right = end, pointee = runtime, array_type = shape, span = span }) _ = try emit(state, ir.Instruction{ op = .sentinel_check, left = pointer, right = sentinel, pointee = runtime, array_type = shape, span = span }) } pointer :: try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = parts.pointer, right = start, pointee = runtime, array_type = shape, span = span }) length :: try emit(state, ir.Instruction{ op = .subtract, type = .usize, left = end, right = start, span = span }) return try record_pair(state, try array_shape(state, typecheck.type_of(state.checked, id)), pointer, length, span) } @hide:file lower_builtin proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { tree :: state.tree span :: ast.node_span(tree, id) name :: ast.source_text(tree, ast.node_span(tree, ast.builtin_call(tree, id).name)) if (trap_type(state, id)) return ir.no_value if (mem.eql(name, "sizeof") or mem.eql(name, "alignof")) { checked :: typecheck.type_of(state.checked, ast.call_arg(tree, id, 0)) type :: lower_type(state, checked) shape :: try array_shape(state, checked) size :: if (mem.eql(name, "sizeof")) ir.storage_size(state.module, type, shape) else ir.storage_alignment(state.module, type, shape) return try usize_constant(state, size, span) } if (mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) { operand :: ast.call_arg(tree, id, ast.call_arg_count(tree, id) - 1) value :: try lower_expression(state, operand) if (state.terminated or trap_type(state, id)) return ir.no_value type :: typecheck.type_of(state.checked, id) if (lower_type(state, type) == .record) return value return try emit(state, ir.Instruction{ op = .pointer_cast, type = lower_type(state, type), left = value, span = span }) } if (mem.eql(name, "memcopy") or mem.eql(name, "memset")) { destination :: try slice_parts(state, ast.call_arg(tree, id, 0)) if (state.terminated) return ir.no_value source := SliceParts{ pointer = ir.no_value, bound = ir.no_value, element = typecheck.Type{} } value := ir.no_value if (mem.eql(name, "memcopy")) source = try slice_parts(state, ast.call_arg(tree, id, 1)) else value = try lower_as(state, ast.call_arg(tree, id, 1), destination.element) if (state.terminated or trap_type(state, id)) return ir.no_value if (mem.eql(name, "memcopy")) { zero :: try usize_constant(state, 0, span) _ = try emit(state, ir.Instruction{ op = .slice_check, left = zero, right = source.bound, extra = destination.bound, span = span }) _ = try emit(state, ir.Instruction{ op = .slice_check, left = zero, right = destination.bound, extra = source.bound, span = span }) value = source.pointer } op ir.Op :: if (mem.eql(name, "memcopy")) .memory_copy else .memory_set _ = try emit(state, ir.Instruction{ op = op, left = destination.pointer, right = value, extra = destination.bound, pointee = lower_type(state, destination.element), array_type = try array_shape(state, destination.element), span = span, }) return ir.no_value } if (!trap_type(state, id)) trap(state, try diagnostics.add(state.diagnostics, .invalid_operand, span), span) return ir.no_value } @hide:file lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { span :: ast.node_span(state.tree, id) checked :: typecheck.type_of(state.checked, id) if (ast.expr_kind(state.tree, id) == .unary) { expression :: ast.unary_expr(state.tree, id) pointer :: try lower_expression(state, expression.operand) if (state.terminated or trap_type(state, id)) return ir.no_value return try pointer_address(state, pointer, checked, span) } if (ast.expr_kind(state.tree, id) == .selection and sema.binding(state.resolution, id).kind == .none) { expression :: ast.selection_expr(state.tree, id) base_type :: typecheck.type_of(state.checked, expression.base) base := ir.no_value if (base_type.kind == .pointer) { pointer :: try lower_expression(state, expression.base) if (state.terminated or trap_type(state, id)) return ir.no_value element :: typecheck.pointer_type(state.checked, base_type.pointer_type).element base = try pointer_address(state, pointer, element, span) } else base = try lower_place(state, expression.base) if (state.terminated or trap_type(state, id)) return ir.no_value return try emit(state, ir.Instruction{ op = .field_address, type = .address, pointee = lower_type(state, checked), array_type = try array_shape(state, checked), left = base, slot = u32(typecheck.field_index(state.checked, id)), span = span, }) } if (ast.expr_kind(state.tree, id) == .index) { expression :: ast.index_expr(state.tree, id) base_type :: typecheck.type_of(state.checked, expression.base) if (base_type.kind == .slice or base_type.kind == .string) { parts :: try slice_parts(state, expression.base) if (state.terminated) return ir.no_value index :: try lower_expression(state, expression.index) if (state.terminated or trap_type(state, id)) return ir.no_value one :: try usize_constant(state, 1, span) end :: try emit(state, ir.Instruction{ op = .add, type = .usize, left = index, right = one, span = span }) _ = try emit(state, ir.Instruction{ op = .slice_check, left = index, right = end, extra = parts.bound, span = span }) pointer :: try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = parts.pointer, right = index, pointee = lower_type(state, checked), array_type = try array_shape(state, checked), span = span, }) return try pointer_address(state, pointer, checked, span) } base := ir.no_value if (base_type.kind == .pointer) { pointer :: try lower_expression(state, expression.base) if (state.terminated or trap_type(state, id)) return ir.no_value descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type) if (descriptor.many) { index :: try lower_expression(state, expression.index) if (state.terminated) return ir.no_value offset :: try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = pointer, right = index, pointee = lower_type(state, checked), array_type = try array_shape(state, checked), span = span, }) return try pointer_address(state, offset, checked, span) } base = try pointer_address(state, pointer, descriptor.element, span) } else base = try lower_place(state, expression.base) if (state.terminated) return ir.no_value index :: try lower_expression(state, expression.index) if (state.terminated or trap_type(state, id)) return ir.no_value return try emit(state, ir.Instruction{ op = .index_address, type = .address, pointee = lower_type(state, checked), array_type = try array_shape(state, checked), left = base, right = index, span = span, }) } if (trap_binding(state, id) or trap_type(state, id)) return ir.no_value binding :: sema.binding(state.resolution, id) location :: state.storage[usize(binding.data.declaration)] operation ir.Op :: if (location.kind == .global) .address_global else .address_local return try emit(state, ir.Instruction{ op = operation, type = .address, pointee = lower_type(state, checked), array_type = try array_shape(state, checked), slot = location.index, span = span, }) } @hide:file lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { tree :: state.tree span :: ast.node_span(tree, id) type :: lower_type(state, typecheck.type_of(state.checked, id)) shape :: try array_shape(state, typecheck.type_of(state.checked, id)) if (typecheck.is_enum_member(state.checked, id)) { if (trap_type(state, id)) return ir.no_value return try constant(state, enum_value(state, typecheck.type_of(state.checked, id), typecheck.field_index(state.checked, id)), span) } if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, id) match ast.expr_kind(tree, id) { .typed_constant: { if (trap_type(state, id)) return ir.no_value return try lower_as(state, ast.typed_constant(tree, id).value, typecheck.type_of(state.checked, id)) } .invalid: { invalid :: ast.invalid(tree, id) trap(state, invalid.diagnostic, invalid.span) } .range: { # ranges have no physical value; only lower_for decomposes an iterable. if (!trap_type(state, id)) unreachable } .array_type, .pointer_type, .function_type, .record_type, .enum_type, .union_type, .distinct_type, .slice_type, .optional_type, .fallible_type, .sum_type: { if (!trap_type(state, id)) { trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span) } } .alias_expr: return try lower_expression(state, ast.type_operand(tree, id)) .enum_literal: { if (!trap_type(state, id)) unreachable } .match_expr: return try lower_match(state, id) .try_expr: return try lower_try(state, id) .catch_expr: return try lower_catch(state, id) .literal_null: return try null_constant(state, .pointer, span) .slice: return try lower_slice(state, id) .builtin_call: return try lower_builtin(state, id) .record_literal: { checked :: typecheck.type_of(state.checked, id) if (checked.kind == .record and typecheck.record_type(state.checked, checked.record_type).is_union) { return try lower_union(state, id) } return try lower_record_as(state, id, checked) } .array_literal: { scratch_start :: state.arguments.items.len defer state.arguments.items = state.arguments.items.ptr[..scratch_start] count :: ast.array_element_count(tree, id) try arraylist.reserve(&state.arguments, scratch_start + count) for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value) for (0..count) |i| { expected :: typecheck.array_type(state.checked, typecheck.type_of(state.checked, id).array_type).element element :: try lower_as(state, ast.array_element(tree, id, i), expected) if (state.terminated) return ir.no_value state.arguments.items[scratch_start + i] = element } if (trap_type(state, id)) return ir.no_value block := &state.module.functions.items[state.function].blocks.items[usize(state.block)] start :: block.arguments.items.len try arraylist.reserve(&block.arguments, start + count) for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch_start + i]) return try emit(state, ir.Instruction{ op = .array_literal, type = .array, array_type = shape, arguments_start = start, arguments_count = count, span = span, }) } .index: { if (is_place(state, id)) { address :: try lower_place(state, id) if (state.terminated) return ir.no_value return try emit(state, ir.Instruction{ op = .load_address, type = type, array_type = shape, left = address, span = span, }) } expression :: ast.index_expr(tree, id) base :: try lower_expression(state, expression.base) if (state.terminated) return ir.no_value index :: try lower_expression(state, expression.index) if (state.terminated or trap_type(state, id)) return ir.no_value return try emit(state, ir.Instruction{ op = .index, type = type, array_type = shape, left = base, right = index, span = span, }) } .literal_int, .literal_float: { if (trap_type(state, id)) return ir.no_value return try lower_number(state, id) } .literal_bool: { if (trap_type(state, id)) return ir.no_value return try constant(state, ir.Value{ type = .bool, data = ir.ValueData{ boolean = mem.eql(ast.literal_text(tree, id), "true") }, }, span) } .literal_string: { if (trap_type(state, id)) return ir.no_value return try lower_string(state, id) } .identifier, .selection: { if (ast.expr_kind(tree, id) == .selection and sema.binding(state.resolution, id).kind == .none) { selection :: ast.selection_expr(tree, id) base_type :: typecheck.type_of(state.checked, selection.base) if (base_type.kind == .slice or base_type.kind == .string or base_type.kind == .array) { member :: ast.source_text(tree, ast.node_span(tree, selection.member)) if (mem.eql(member, "len") or mem.eql(member, "ptr")) { parts :: try slice_parts(state, selection.base) if (state.terminated or trap_type(state, id)) return ir.no_value return if (mem.eql(member, "len")) parts.bound else parts.pointer } } if (base_type.kind == .pointer) { descriptor :: typecheck.pointer_type(state.checked, base_type.pointer_type) if (!descriptor.many and descriptor.element.kind == .array) { pointer :: try lower_expression(state, selection.base) if (state.terminated or trap_type(state, id)) return ir.no_value member :: ast.source_text(tree, ast.node_span(tree, selection.member)) array :: typecheck.array_type(state.checked, descriptor.element.array_type) if (mem.eql(member, "len")) return try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = u64(array.len) }, }, span) if (mem.eql(member, "ptr")) { zero :: try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = 0 } }, span) return try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = pointer, right = zero, pointee = lower_type(state, array.element), array_type = try array_shape(state, array.element), span = span, }) } } } if (is_place(state, id)) { address :: try lower_place(state, id) if (state.terminated) return ir.no_value return try emit(state, ir.Instruction{ op = .load_address, type = type, array_type = shape, left = address, span = span, }) } base :: try lower_expression(state, ast.selection_expr(tree, id).base) if (state.terminated or trap_type(state, id)) return ir.no_value return try emit(state, ir.Instruction{ op = .field, type = type, array_type = shape, left = base, slot = u32(typecheck.field_index(state.checked, id)), span = span, }) } if (trap_binding(state, id) or trap_type(state, id)) return ir.no_value binding :: sema.binding(state.resolution, id) location :: state.storage[usize(binding.data.declaration)] match location.kind { .function: return try constant(state, ir.Value{ type = .function, data = ir.ValueData{ function = ir.FunctionId(location.index) }, }, span) .global, .local: { declaration_type :: typecheck.type_of(state.checked, binding.data.declaration) operation :: if (location.kind == .global) ir.Op.load_global else ir.Op.load_local value :: try emit(state, ir.Instruction{ op = operation, type = lower_type(state, declaration_type), array_type = try array_shape(state, declaration_type), slot = location.index, span = span, }) return try convert_value(state, value, declaration_type, typecheck.type_of(state.checked, id), span) } .none: unreachable } } .unary: { expression :: ast.unary_expr(tree, id) if (expression.operator == .unwrap) { operand :: try lower_expression(state, expression.operand) if (state.terminated or trap_type(state, id)) return ir.no_value source :: typecheck.type_of(state.checked, expression.operand) present :: try optional_present(state, operand, source, span) _ = try emit(state, ir.Instruction{ op = .optional_check, left = present, span = span }) return try optional_payload(state, operand, source, span) } if (expression.operator == .address_of) { if (typecheck.type_of(state.checked, id).kind == .slice) { parts :: try slice_parts(state, expression.operand) if (state.terminated or trap_type(state, id)) return ir.no_value return try record_pair(state, shape, parts.pointer, parts.bound, span) } if (!is_place(state, expression.operand)) { _ = try lower_expression(state, expression.operand) if (!state.terminated) _ = trap_type(state, id) return ir.no_value } address :: try lower_place(state, expression.operand) if (state.terminated or trap_type(state, id)) return ir.no_value element :: typecheck.type_of(state.checked, expression.operand) return try emit(state, ir.Instruction{ op = .pointer_from_address, type = .pointer, left = address, pointee = lower_type(state, element), array_type = try array_shape(state, element), span = span, }) } if (expression.operator == .dereference) { address :: try lower_place(state, id) if (state.terminated) return ir.no_value return try emit(state, ir.Instruction{ op = .load_address, type = type, array_type = shape, left = address, span = span, }) } if (expression.operator == .negate and ir.integer_bits(type) != 0 and !ir.unsigned_integer(type) and ast.expr_kind(tree, expression.operand) == .literal_int) { text :: ast.literal_text(tree, expression.operand) magnitude_limit :: 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 .address_of, .dereference, .unwrap: unreachable } return try emit(state, ir.Instruction{ op = op, type = type, left = operand, span = span }) } .binary: { expression :: ast.binary_expr(tree, id) if (expression.operator == .logical_and or expression.operator == .logical_or) { return try lower_logical(state, id) } left_type :: typecheck.type_of(state.checked, expression.left) right_type :: typecheck.type_of(state.checked, expression.right) if ((expression.operator == .equal or expression.operator == .not_equal) and (left_type.kind == .null or right_type.kind == .null)) { optional_id :: if (left_type.kind == .null) expression.right else expression.left value :: try lower_expression(state, optional_id) if (state.terminated or trap_type(state, id)) return ir.no_value present :: try optional_present(state, value, typecheck.type_of(state.checked, optional_id), span) if (expression.operator == .not_equal) return present return try emit(state, ir.Instruction{ op = .logical_not, type = .bool, left = present, span = span }) } left :: try lower_expression(state, expression.left) if (state.terminated) return ir.no_value right :: try lower_expression(state, expression.right) if (state.terminated or trap_type(state, id)) return ir.no_value if (expression.operator == .add and type == .pointer) { pointer :: typecheck.pointer_type(state.checked, typecheck.type_of(state.checked, id).pointer_type) return try emit(state, ir.Instruction{ op = .pointer_offset, type = .pointer, left = left, right = right, pointee = lower_type(state, pointer.element), array_type = try array_shape(state, pointer.element), span = span, }) } op 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| { signature :: typecheck.type_of(state.checked, ast.call_expr(tree, id).callee).function expected := typecheck.Type{} if (signature != ast.NodeId(maxval!(u32)) and i < ast.function_param_count(tree, signature)) { expected = typecheck.type_of(state.checked, ast.param(tree, ast.function_param(tree, signature, i)).type) } argument :: try lower_as(state, ast.call_arg(tree, id, i), expected) if (state.terminated) return ir.no_value state.arguments.items[scratch_start + i] = argument } if (trap_type(state, id)) return ir.no_value # branchful arguments end in a different block from the callee. block := &state.module.functions.items[state.function].blocks.items[usize(state.block)] start :: block.arguments.items.len try arraylist.reserve(&block.arguments, start + count) for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch_start + i]) return try emit(state, ir.Instruction{ op = .call, type = type, array_type = shape, left = callee, arguments_start = start, arguments_count = count, span = span, }) } } return ir.no_value } @hide:file integer_magnitude proc(text []u8, limit u64) ?u64 { value := u64(0) for (text) |byte| { if (byte < '0' or byte > '9') return null digit :: u64(byte - '0') if (digit > limit or value > divtrunc!(limit - digit, u64(10))) return null value = value * 10 + digit } return value } @hide:file exact_float_integer proc(text []u8, value f64) bool { # every finite f64 integer has at most 309 decimal digits, plus a terminator. decimal [310]mut u8 := undefined count :: c.snprintf(ptrcast!((&decimal).ptr), c_ulong(decimal.len), "%.0f", value) if (count < 0 or usize(count) >= decimal.len) return false start := usize(0) while (start + 1 < text.len and text[start] == '0') start += 1 return mem.eql(text[start..], decimal[..usize(count)]) } @hide:file lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { span :: ast.node_span(state.tree, id) text :: ast.literal_text(state.tree, id) type :: lower_type(state, typecheck.type_of(state.checked, id)) if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) { if (integer_magnitude(text, 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 lower_string proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) { span :: ast.node_span(state.tree, id) text :: ast.literal_text(state.tree, id) owned :: try mem.alloc(u8, state.module.allocator, text.len - 2) transferred := false defer if (!transferred) mem.free(state.module.allocator, owned) length := usize(0) cursor := usize(1) end :: text.len - 1 while (cursor < end) { byte :: ast.string_next(text, &cursor) catch |_| { trap(state, try diagnostics.add(state.diagnostics, .invalid_escape, span), span) return ir.no_value } owned[length] = byte length += 1 } if (state.module.strings.items.len > usize(maxval!(ir.StringId))) return .out_of_memory string_id :: ir.StringId(state.module.strings.items.len) try arraylist.append(&state.module.strings, ir.StringStorage{ allocation = owned, len = length, }) transferred = true return try constant(state, ir.Value{ type = .string, data = ir.ValueData{ string = string_id }, }, span) }