From f0ca8639714cc06e845038855ae11de24ea7d857 Mon Sep 17 00:00:00 2001 From: hiroki23 <33798+hiroki23@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:02:46 +0900 Subject: [PATCH] feat: execute Gate 2 aggregate MIR --- docs/gate-2-aggregates.md | 20 + src/gate2_mir.trb | 803 +++++++++++++++++++++++++++++++ src/gate2_mir_test.trb | 454 ++++++++++++++++++ src/gate2_toolchain.trb | 113 +++++ src/qbe.trb | 72 +++ src/qbe2.trb | 975 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 2437 insertions(+) create mode 100644 src/gate2_mir.trb create mode 100644 src/gate2_mir_test.trb create mode 100644 src/gate2_toolchain.trb create mode 100644 src/qbe2.trb diff --git a/docs/gate-2-aggregates.md b/docs/gate-2-aggregates.md index 4dd2bc69..25e8c4a7 100644 --- a/docs/gate-2-aggregates.md +++ b/docs/gate-2-aggregates.md @@ -4,6 +4,20 @@ Gate 2 extends the verified QBE path from scalar SSA values to heap-free, static-layout values. It implements existing TypeRB record, payload-enum, and `Result` semantics without introducing a native-only language feature. +## Implementation status + +The backend-neutral layout model, aggregate Native MIR, verifier, and +hand-authored QBE vertical slice are implemented. The QBE adapter uses +caller-owned result storage, borrowed aggregate parameters, separate transfer +and value slots for aggregate block parameters, and checked payload projection. +It initializes aggregate storage before construction so padding and inactive +payload bytes never carry indeterminate data. + +The source snapshot producer, strict version 3 decoder, source differential +corpus, and Gate 2 measurements remain active work. This document records the +complete checkpoint rather than treating the hand-authored vertical slice as +the gate result. + ## Checkpoint boundary The supported value graph is finite and known at compile time: @@ -37,6 +51,12 @@ semantics remain value semantics even though the disposable target ABI passes addresses internally. A later allocation strategy can therefore add escaping storage without changing TypeRB record or enum behavior. +Aggregate construction zero-initializes its complete slot and uses the system +`memcpy`/`memset` ABI for fixed-size copies and initialization. These dynamic +system-library dependencies are counted with the executable in Gate 2 reports. +Block arguments use distinct transfer and value slots so control-flow edges +retain parallel-copy semantics, including aggregate swaps and back edges. + ## Exit evidence Gate 2 completes when: diff --git a/src/gate2_mir.trb b/src/gate2_mir.trb new file mode 100644 index 00000000..781a22a2 --- /dev/null +++ b/src/gate2_mir.trb @@ -0,0 +1,803 @@ +import { Diagnostic, boundary_diagnostic } from diagnostic +import { + Gate1Instruction, + ScalarType, +} from gate1_mir +import { + Gate2FieldLayout, + Gate2Layout, + Gate2LayoutKind, + Gate2TypeDefinition, + Gate2ValueType, + Gate2VariantLayout, + compute_gate2_layout, + gate2_field_layout, + gate2_value_types_equal, + gate2_variant_field_layout, + gate2_variant_layout, + verify_gate2_layouts, +} from gate2_layout +import { NativeSource } from native_mir +import { SourceOrigin } from snapshot +import { Result } from trb/std/result + +record Gate2Parameter + id: String + value_type: Gate2ValueType + origin: SourceOrigin +end + +enum Gate2ResultType + Void + Value(value_type: Gate2ValueType) +end + +enum Gate2Instruction + Scalar(instruction: Gate1Instruction) + RecordConstruct( + result: String, + type_id: String, + arguments: Array, + origin: SourceOrigin, + ) + RecordProject( + result: String, + type_id: String, + record: String, + field: String, + origin: SourceOrigin, + ) + VariantConstruct( + result: String, + type_id: String, + variant: String, + arguments: Array, + origin: SourceOrigin, + ) + VariantTest( + result: String, + type_id: String, + value: String, + variant: String, + origin: SourceOrigin, + ) + VariantProject( + result: String, + type_id: String, + value: String, + variant: String, + field: String, + origin: SourceOrigin, + ) + Call(result: String?, function_id: String, arguments: Array, origin: SourceOrigin) + end + + enum Gate2Terminator + Jump(target: String, arguments: Array, origin: SourceOrigin) + Branch( + condition: String, + when_true: String, + true_arguments: Array, + when_false: String, + false_arguments: Array, + origin: SourceOrigin, + ) + Return(value: String?, origin: SourceOrigin) + end + + record Gate2Block + id: String + parameters: Array + origin: SourceOrigin + instructions: Array + terminator: Gate2Terminator + end + + record Gate2Function + id: String + name: String + parameters: Array + result_type: Gate2ResultType + entry: String + origin: SourceOrigin + blocks: Array + end + + record Gate2Module + id: String + entry_function: String + sources: Array + types: Array + functions: Array + end + + record Gate2Definition + id: String + value_type: Gate2ValueType + end + + def gate2_result_types_equal(left: Gate2ResultType, right: Gate2ResultType): Boolean + case left + when Gate2ResultType::Void + case right + when Gate2ResultType::Void + return true + when Gate2ResultType::Value(_right_type) + return false + end + when Gate2ResultType::Value(left_type) + case right + when Gate2ResultType::Void + return false + when Gate2ResultType::Value(right_type) + return gate2_value_types_equal(left_type, right_type) + end + end + end + + def verify_gate2_mir(native_module: Gate2Module): Result + if native_module.sources.empty?() + return Result::Err( + boundary_diagnostic("TRBN0004", "/sources", "Gate 2 MIR must declare at least one source") + ) + end + if native_module.functions.empty?() + return Result::Err( + boundary_diagnostic("TRBN0004", "/functions", "Gate 2 MIR must contain at least one function") + ) + end + try verify_gate2_layouts(native_module.types) + mut source_ids: Array := [] + mut index := 0 + while index < native_module.sources.size() + source := native_module.sources[index] + if source_ids.include?(source.id) + return Result::Err( + boundary_diagnostic("TRBN0004", "/sources/" + index.to_s() + "/id", "Gate 2 MIR source identifier is duplicated") + ) + end + source_ids.push(source.id) + index += 1 + end + mut function_ids: Hash := {} + index = 0 + while index < native_module.functions.size() + function := native_module.functions[index] + path := "/functions/" + index.to_s() + if function.id.empty?() || function_ids.key?(function.id) + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/id", "Gate 2 MIR function identifier is empty or duplicated") + ) + end + function_ids[function.id] = true + try _gate2_verify_origin(function.origin, source_ids, path + "/origin") + index += 1 + end + entry := gate2_function(native_module.functions, native_module.entry_function) + if entry == nil + return Result::Err( + boundary_diagnostic("TRBN0004", "/entryFunction", "Gate 2 MIR entry function does not exist") + ) + end + if !entry.parameters.empty?() + return Result::Err( + boundary_diagnostic("TRBN0004", "/entryFunction", "Gate 2 MIR entry function must take no arguments") + ) + end + case entry.result_type + when Gate2ResultType::Void + when Gate2ResultType::Value(_value_type) + return Result::Err( + boundary_diagnostic("TRBN0004", "/entryFunction", "Gate 2 MIR entry function must return Void") + ) + end + index = 0 + while index < native_module.functions.size() + try _gate2_verify_function( + native_module, + native_module.functions[index], + source_ids, + "/functions/" + index.to_s(), + ) + index += 1 + end + return Result::Ok(true) + end + + def _gate2_verify_function( + native_module: Gate2Module, + function: Gate2Function, + source_ids: Array, + path: String, + ): Result + if function.blocks.empty?() + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/blocks", "Gate 2 MIR function has no blocks") + ) + end + mut definitions: Hash := {} + mut index := 0 + while index < function.parameters.size() + parameter := function.parameters[index] + parameter_path := path + "/parameters/" + index.to_s() + try _gate2_verify_origin(parameter.origin, source_ids, parameter_path + "/origin") + try _gate2_define(definitions, parameter.id, parameter.value_type, parameter_path + "/id") + index += 1 + end + mut block_ids: Hash := {} + mut entry_found := false + index = 0 + while index < function.blocks.size() + block := function.blocks[index] + block_path := path + "/blocks/" + index.to_s() + if block.id.empty?() || block_ids.key?(block.id) + return Result::Err( + boundary_diagnostic("TRBN0004", block_path + "/id", "Gate 2 MIR block identifier is empty or duplicated") + ) + end + block_ids[block.id] = true + if block.id == function.entry + entry_found = true + if !block.parameters.empty?() + return Result::Err( + boundary_diagnostic("TRBN0004", block_path + "/parameters", "entry block must not have parameters") + ) + end + end + try _gate2_verify_origin(block.origin, source_ids, block_path + "/origin") + mut parameter_index := 0 + while parameter_index < block.parameters.size() + parameter := block.parameters[parameter_index] + parameter_path := block_path + "/parameters/" + parameter_index.to_s() + try _gate2_verify_origin(parameter.origin, source_ids, parameter_path + "/origin") + try _gate2_define(definitions, parameter.id, parameter.value_type, parameter_path + "/id") + parameter_index += 1 + end + mut instruction_index := 0 + while instruction_index < block.instructions.size() + instruction_path := block_path + "/instructions/" + instruction_index.to_s() + definition := try _gate2_instruction_definition(native_module, block.instructions[instruction_index], instruction_path) + if definition != nil + try _gate2_define(definitions, definition.id, definition.value_type, instruction_path + "/result") + end + instruction_index += 1 + end + index += 1 + end + if !entry_found + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/entry", "Gate 2 MIR entry block does not exist") + ) + end + index = 0 + while index < function.blocks.size() + try _gate2_verify_block( + native_module, + function, + function.blocks[index], + definitions, + source_ids, + path + "/blocks/" + index.to_s(), + ) + index += 1 + end + return Result::Ok(true) + end + + def _gate2_instruction_definition( + native_module: Gate2Module, + instruction: Gate2Instruction, + path: String, + ): Result + case instruction + when Gate2Instruction::Scalar(scalar) + case scalar + when Gate1Instruction::BooleanLiteral(result, _value, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate1Instruction::IntegerLiteral(result, _value, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Integer)) + when Gate1Instruction::FloatLiteral(result, _value, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Float)) + when Gate1Instruction::IntegerBinary(result, _operator, _left, _right, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Integer)) + when Gate1Instruction::FloatBinary(result, _operator, _left, _right, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Float)) + when Gate1Instruction::IntegerCompare(result, _operator, _left, _right, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate1Instruction::FloatCompare(result, _operator, _left, _right, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate1Instruction::BooleanNot(result, _value, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate1Instruction::Call(_result, _function_id, _arguments, _origin) + return Result::Err( + boundary_diagnostic("TRBN0004", path, "Gate 1 call must be normalized to a Gate 2 call") + ) + when Gate1Instruction::WriteStatic(_value, _origin) + return Result::Ok(nil) + end + when Gate2Instruction::RecordConstruct(result, type_id, _arguments, _origin) + layout := try compute_gate2_layout(native_module.types, type_id) + if layout.kind != Gate2LayoutKind::Record + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/type", "record construction requires a record type") + ) + end + return _gate2_some_definition(result, Gate2ValueType::Aggregate(type_id)) + when Gate2Instruction::RecordProject(result, type_id, _record, field, _origin) + layout := try compute_gate2_layout(native_module.types, type_id) + field_layout := gate2_field_layout(layout, field) + if layout.kind != Gate2LayoutKind::Record || field_layout == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/field", "record projection references an unknown field") + ) + end + return _gate2_some_definition(result, field_layout.value_type) + when Gate2Instruction::VariantConstruct(result, type_id, variant, _arguments, _origin) + layout := try compute_gate2_layout(native_module.types, type_id) + if layout.kind != Gate2LayoutKind::Tagged || gate2_variant_layout(layout, variant) == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/variant", "variant construction references an unknown variant") + ) + end + return _gate2_some_definition(result, Gate2ValueType::Aggregate(type_id)) + when Gate2Instruction::VariantTest(result, _type_id, _value, _variant, _origin) + return _gate2_some_definition(result, Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate2Instruction::VariantProject(result, type_id, _value, variant, field, _origin) + layout := try compute_gate2_layout(native_module.types, type_id) + variant_layout := gate2_variant_layout(layout, variant) + if layout.kind != Gate2LayoutKind::Tagged + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/variant", "variant projection requires a tagged type") + ) + end + if variant_layout == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/variant", "variant projection references an unknown variant") + ) + end + field_layout := gate2_variant_field_layout(variant_layout, field) + if field_layout == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/field", "variant projection references an unknown field") + ) + end + return _gate2_some_definition(result, field_layout.value_type) + when Gate2Instruction::Call(result, function_id, _arguments, _origin) + callee := gate2_function(native_module.functions, function_id) + if callee == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/function", "call references an unknown function") + ) + end + case callee.result_type + when Gate2ResultType::Void + if result != nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/result", "Void call must not define a result") + ) + end + return Result::Ok(nil) + when Gate2ResultType::Value(value_type) + if result == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/result", "non-Void call must define a result") + ) + end + return _gate2_some_definition(result, value_type) + end + end + end + + def _gate2_verify_block( + native_module: Gate2Module, + function: Gate2Function, + block: Gate2Block, + definitions: Hash, + source_ids: Array, + path: String, + ): Result + mut available: Hash := {} + mut index := 0 + while index < function.parameters.size() + parameter := function.parameters[index] + available[parameter.id] = parameter.value_type + index += 1 + end + index = 0 + while index < block.parameters.size() + parameter := block.parameters[index] + available[parameter.id] = parameter.value_type + index += 1 + end + index = 0 + while index < block.instructions.size() + instruction := block.instructions[index] + instruction_path := path + "/instructions/" + index.to_s() + try _gate2_verify_instruction(native_module, instruction, available, source_ids, instruction_path) + definition := try _gate2_instruction_definition(native_module, instruction, instruction_path) + if definition != nil + available[definition.id] = definitions.fetch(definition.id) + end + index += 1 + end + try _gate2_verify_terminator(native_module, function, block.terminator, available, source_ids, path + "/terminator") + return Result::Ok(true) + end + + def _gate2_verify_instruction( + native_module: Gate2Module, + instruction: Gate2Instruction, + available: Hash, + source_ids: Array, + path: String, + ): Result + case instruction + when Gate2Instruction::Scalar(scalar) + return _gate2_verify_scalar(scalar, available, source_ids, path) + when Gate2Instruction::RecordConstruct(_result, type_id, arguments, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + layout := try compute_gate2_layout(native_module.types, type_id) + try _gate2_require_arguments(available, arguments, layout.fields, path + "/arguments") + when Gate2Instruction::RecordProject(_result, type_id, record, field, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require(available, record, Gate2ValueType::Aggregate(type_id), path + "/record") + layout := try compute_gate2_layout(native_module.types, type_id) + if layout.kind != Gate2LayoutKind::Record || gate2_field_layout(layout, field) == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/field", "record projection references an unknown field") + ) + end + when Gate2Instruction::VariantConstruct(_result, type_id, variant, arguments, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + layout := try compute_gate2_layout(native_module.types, type_id) + variant_layout := gate2_variant_layout(layout, variant) + if layout.kind != Gate2LayoutKind::Tagged || variant_layout == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/variant", "variant construction references an unknown variant") + ) + end + try _gate2_require_arguments(available, arguments, variant_layout.fields, path + "/arguments") + when Gate2Instruction::VariantTest(_result, type_id, value, variant, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require(available, value, Gate2ValueType::Aggregate(type_id), path + "/value") + layout := try compute_gate2_layout(native_module.types, type_id) + if layout.kind != Gate2LayoutKind::Tagged || gate2_variant_layout(layout, variant) == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/variant", "variant test references an unknown variant") + ) + end + when Gate2Instruction::VariantProject(_result, type_id, value, variant, field, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require(available, value, Gate2ValueType::Aggregate(type_id), path + "/value") + layout := try compute_gate2_layout(native_module.types, type_id) + variant_layout := gate2_variant_layout(layout, variant) + if layout.kind != Gate2LayoutKind::Tagged + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/variant", "variant projection requires a tagged type") + ) + end + if variant_layout == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/variant", "variant projection references an unknown variant") + ) + end + if gate2_variant_field_layout(variant_layout, field) == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/field", "variant projection references an unknown field") + ) + end + when Gate2Instruction::Call(_result, function_id, arguments, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + callee := gate2_function(native_module.functions, function_id) + if callee == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/function", "call references an unknown function") + ) + end + if arguments.size() != callee.parameters.size() + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/arguments", "call argument count does not match the callee") + ) + end + mut index := 0 + while index < arguments.size() + try _gate2_require(available, arguments[index], callee.parameters[index].value_type, path + "/arguments/" + index.to_s()) + index += 1 + end + end + return Result::Ok(true) + end + + def _gate2_verify_scalar( + instruction: Gate1Instruction, + available: Hash, + source_ids: Array, + path: String, + ): Result + case instruction + when Gate1Instruction::BooleanLiteral(_result, _value, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + when Gate1Instruction::IntegerLiteral(_result, value, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + if value < - 9007199254740991 || value > 9007199254740991 + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/value", "Integer literal is outside the portable range") + ) + end + when Gate1Instruction::FloatLiteral(_result, value, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + if !value.finite?() + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/value", "Float literal must be finite") + ) + end + when Gate1Instruction::IntegerBinary(_result, _operator, left, right, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require_scalar(available, left, ScalarType::Integer, path + "/left") + try _gate2_require_scalar(available, right, ScalarType::Integer, path + "/right") + when Gate1Instruction::FloatBinary(_result, _operator, left, right, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require_scalar(available, left, ScalarType::Float, path + "/left") + try _gate2_require_scalar(available, right, ScalarType::Float, path + "/right") + when Gate1Instruction::IntegerCompare(_result, _operator, left, right, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require_scalar(available, left, ScalarType::Integer, path + "/left") + try _gate2_require_scalar(available, right, ScalarType::Integer, path + "/right") + when Gate1Instruction::FloatCompare(_result, _operator, left, right, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require_scalar(available, left, ScalarType::Float, path + "/left") + try _gate2_require_scalar(available, right, ScalarType::Float, path + "/right") + when Gate1Instruction::BooleanNot(_result, value, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require_scalar(available, value, ScalarType::Boolean, path + "/value") + when Gate1Instruction::Call(_result, _function_id, _arguments, _origin) + return Result::Err( + boundary_diagnostic("TRBN0004", path, "Gate 1 call must be normalized to a Gate 2 call") + ) + when Gate1Instruction::WriteStatic(_value, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + end + return Result::Ok(true) + end + + def _gate2_verify_terminator( + native_module: Gate2Module, + function: Gate2Function, + terminator: Gate2Terminator, + available: Hash, + source_ids: Array, + path: String, + ): Result + case terminator + when Gate2Terminator::Jump(target, arguments, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_verify_target(function, target, arguments, available, path) + when Gate2Terminator::Branch(condition, when_true, true_arguments, when_false, false_arguments, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + try _gate2_require_scalar(available, condition, ScalarType::Boolean, path + "/condition") + try _gate2_verify_target(function, when_true, true_arguments, available, path + "/true") + try _gate2_verify_target(function, when_false, false_arguments, available, path + "/false") + when Gate2Terminator::Return(value, origin) + try _gate2_verify_origin(origin, source_ids, path + "/origin") + case function.result_type + when Gate2ResultType::Void + if value != nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/value", "Void function must not return a value") + ) + end + when Gate2ResultType::Value(value_type) + if value == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/value", "non-Void function must return a value") + ) + end + try _gate2_require(available, value, value_type, path + "/value") + end + end + return Result::Ok(true) + end + + def _gate2_verify_target( + function: Gate2Function, + target: String, + arguments: Array, + available: Hash, + path: String, + ): Result + block := gate2_block(function.blocks, target) + if block == nil + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/target", "terminator references an unknown block") + ) + end + if arguments.size() != block.parameters.size() + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/arguments", "block argument count does not match the target") + ) + end + mut index := 0 + while index < arguments.size() + try _gate2_require(available, arguments[index], block.parameters[index].value_type, path + "/arguments/" + index.to_s()) + index += 1 + end + return Result::Ok(true) + end + + def _gate2_require_arguments( + available: Hash, + arguments: Array, + fields: Array, + path: String, + ): Result + if arguments.size() != fields.size() + return Result::Err( + boundary_diagnostic("TRBN0004", path, "aggregate argument count does not match its layout") + ) + end + mut index := 0 + while index < arguments.size() + try _gate2_require(available, arguments[index], fields[index].value_type, path + "/" + index.to_s()) + index += 1 + end + return Result::Ok(true) + end + + def _gate2_define( + mut definitions: Hash, + id: String, + value_type: Gate2ValueType, + path: String, + ): Result + if id.empty?() || definitions.key?(id) + return Result::Err( + boundary_diagnostic("TRBN0004", path, "value identifier is empty or defined more than once") + ) + end + definitions[id] = value_type + return Result::Ok(true) + end + + def _gate2_require( + available: Hash, + id: String, + expected: Gate2ValueType, + path: String, + ): Result + actual := try _gate2_require_any(available, id, path) + if !gate2_value_types_equal(actual, expected) + return Result::Err( + boundary_diagnostic("TRBN0004", path, "operand type does not match the operation") + ) + end + return Result::Ok(true) + end + + def _gate2_require_scalar( + available: Hash, + id: String, + scalar: ScalarType, + path: String, + ): Result + return _gate2_require(available, id, Gate2ValueType::Scalar(scalar), path) + end + + def _gate2_require_any( + available: Hash, + id: String, + path: String, + ): Result + if !available.key?(id) + return Result::Err( + boundary_diagnostic("TRBN0004", path, "operand is not available in this block") + ) + end + return Result::Ok(available.fetch(id)) + end + + def _gate2_aggregate_layout( + native_module: Gate2Module, + value_type: Gate2ValueType, + path: String, + ): Result + case value_type + when Gate2ValueType::Scalar(_scalar) + return Result::Err( + boundary_diagnostic("TRBN0004", path, "operation requires an aggregate value") + ) + when Gate2ValueType::Aggregate(type_id) + return compute_gate2_layout(native_module.types, type_id) + end + end + + def _gate2_some_definition( + id: String, + value_type: Gate2ValueType, + ): Result + return Result::Ok(Gate2Definition.new(id: id, value_type: value_type)) + end + + def gate2_instruction_result(instruction: Gate2Instruction): String? + case instruction + when Gate2Instruction::Scalar(scalar) + case scalar + when Gate1Instruction::BooleanLiteral(result, _value, _origin) + return result + when Gate1Instruction::IntegerLiteral(result, _value, _origin) + return result + when Gate1Instruction::FloatLiteral(result, _value, _origin) + return result + when Gate1Instruction::IntegerBinary(result, _operator, _left, _right, _origin) + return result + when Gate1Instruction::FloatBinary(result, _operator, _left, _right, _origin) + return result + when Gate1Instruction::IntegerCompare(result, _operator, _left, _right, _origin) + return result + when Gate1Instruction::FloatCompare(result, _operator, _left, _right, _origin) + return result + when Gate1Instruction::BooleanNot(result, _value, _origin) + return result + when Gate1Instruction::Call(result, _function_id, _arguments, _origin) + return result + when Gate1Instruction::WriteStatic(_value, _origin) + return nil + end + when Gate2Instruction::RecordConstruct(result, _type_id, _arguments, _origin) + return result + when Gate2Instruction::RecordProject(result, _type_id, _record, _field, _origin) + return result + when Gate2Instruction::VariantConstruct(result, _type_id, _variant, _arguments, _origin) + return result + when Gate2Instruction::VariantTest(result, _type_id, _value, _variant, _origin) + return result + when Gate2Instruction::VariantProject(result, _type_id, _value, _variant, _field, _origin) + return result + when Gate2Instruction::Call(result, _function_id, _arguments, _origin) + return result + end + end + + def gate2_function(functions: Array, id: String): Gate2Function? + mut index := 0 + while index < functions.size() + if functions[index].id == id + return functions[index] + end + index += 1 + end + return nil + end + + def gate2_block(blocks: Array, id: String): Gate2Block? + mut index := 0 + while index < blocks.size() + if blocks[index].id == id + return blocks[index] + end + index += 1 + end + return nil + end + + def _gate2_verify_origin( + origin: SourceOrigin, + source_ids: Array, + path: String, + ): Result + if !source_ids.include?(origin.source_id) + return Result::Err( + boundary_diagnostic("TRBN0004", path + "/source", "origin references an unknown source") + ) + end + if origin.start_line < 1 || origin.start_column < 1 || origin.end_line < 1 || origin.end_column < 1 + return Result::Err( + boundary_diagnostic("TRBN0004", path, "origin coordinates must be positive") + ) + end + if origin.end_line < origin.start_line || (origin.end_line == origin.start_line && origin.end_column < origin.start_column) + return Result::Err( + boundary_diagnostic("TRBN0004", path, "origin end precedes its start") + ) + end + return Result::Ok(true) + end diff --git a/src/gate2_mir_test.trb b/src/gate2_mir_test.trb new file mode 100644 index 00000000..659b43ea --- /dev/null +++ b/src/gate2_mir_test.trb @@ -0,0 +1,454 @@ +import { + ComparisonOperator, + Gate1Instruction, + IntegerBinaryOperator, + ScalarType, +} from gate1_mir +import { + Gate2Field, + Gate2TypeDefinition, + Gate2ValueType, + Gate2Variant, +} from gate2_layout +import { + Gate2Block, + Gate2Function, + Gate2Instruction, + Gate2Module, + Gate2Parameter, + Gate2ResultType, + Gate2Terminator, + verify_gate2_mir, +} from gate2_mir +import { build_gate2_executable } from gate2_toolchain +import { NativeSource } from native_mir +import { emit_gate2_qbe } from qbe2 +import { SourceOrigin } from snapshot +import { create_directory } from trb/std/filesystem +import { environment, run } from trb/std/process +import { Result } from trb/std/result +import { describe, expect, test } from trb/std/test + +def _gate2_mir_origin(line: Integer): SourceOrigin + return SourceOrigin.new( + source_id: "main", + start_line: line, + start_column: 1, + end_line: line, + end_column: 8, + ) +end + +def _gate2_mir_scalar(value_type: ScalarType): Gate2ValueType + return Gate2ValueType::Scalar(value_type) +end + +def _gate2_mir_types(): Array + return [ + Gate2TypeDefinition::Record( + "example#Point", + [ + Gate2Field.new(name: "x", value_type: _gate2_mir_scalar(ScalarType::Integer)), + Gate2Field.new(name: "y", value_type: _gate2_mir_scalar(ScalarType::Integer)), + ], + ), + Gate2TypeDefinition::Tagged( + "example#Outcome", + [ + Gate2Variant.new( + name: "Found", + fields: [ + Gate2Field.new(name: "point", value_type: Gate2ValueType::Aggregate("example#Point")), + ], + ), + Gate2Variant.new( + name: "Missing", + fields: [ + Gate2Field.new(name: "code", value_type: _gate2_mir_scalar(ScalarType::Integer)), + ], + ), + ], + ), + ] +end + +def _gate2_make_point(): Gate2Function + origin := _gate2_mir_origin(1) + return Gate2Function.new( + id: "example#make_point", + name: "make_point", + parameters: [ + Gate2Parameter.new(id: "x", value_type: _gate2_mir_scalar(ScalarType::Integer), origin: origin), + Gate2Parameter.new(id: "y", value_type: _gate2_mir_scalar(ScalarType::Integer), origin: origin), + ], + result_type: Gate2ResultType::Value(Gate2ValueType::Aggregate("example#Point")), + entry: "entry", + origin: origin, + blocks: [ + Gate2Block.new( + id: "entry", + parameters: [], + origin: origin, + instructions: [ + Gate2Instruction::RecordConstruct("point", "example#Point", ["x", "y"], origin), + ], + terminator: Gate2Terminator::Return("point", origin), + ), + ], + ) +end + +def _gate2_classify(): Gate2Function + origin := _gate2_mir_origin(5) + point_type := Gate2ValueType::Aggregate("example#Point") + return Gate2Function.new( + id: "example#classify", + name: "classify", + parameters: [ + Gate2Parameter.new(id: "point", value_type: point_type, origin: origin), + Gate2Parameter.new(id: "found", value_type: _gate2_mir_scalar(ScalarType::Boolean), origin: origin), + ], + result_type: Gate2ResultType::Value(Gate2ValueType::Aggregate("example#Outcome")), + entry: "entry", + origin: origin, + blocks: [ + Gate2Block.new( + id: "entry", + parameters: [], + origin: origin, + instructions: [], + terminator: Gate2Terminator::Branch("found", "yes", ["point"], "no", [], origin), + ), + Gate2Block.new( + id: "yes", + parameters: [Gate2Parameter.new(id: "selected", value_type: point_type, origin: origin)], + origin: origin, + instructions: [ + Gate2Instruction::VariantConstruct( + "outcome", + "example#Outcome", + "Found", + ["selected"], + origin, + ), + ], + terminator: Gate2Terminator::Return("outcome", origin), + ), + Gate2Block.new( + id: "no", + parameters: [], + origin: origin, + instructions: [ + Gate2Instruction::Scalar(Gate1Instruction::IntegerLiteral("code", 404, origin)), + Gate2Instruction::VariantConstruct( + "outcome_missing", + "example#Outcome", + "Missing", + ["code"], + origin, + ), + ], + terminator: Gate2Terminator::Return("outcome_missing", origin), + ), + ], + ) +end + +def _gate2_sum_point(): Gate2Function + origin := _gate2_mir_origin(12) + return Gate2Function.new( + id: "example#sum_point", + name: "sum_point", + parameters: [ + Gate2Parameter.new( + id: "point", + value_type: Gate2ValueType::Aggregate("example#Point"), + origin: origin, + ), + ], + result_type: Gate2ResultType::Value(_gate2_mir_scalar(ScalarType::Integer)), + entry: "entry", + origin: origin, + blocks: [ + Gate2Block.new( + id: "entry", + parameters: [], + origin: origin, + instructions: [ + Gate2Instruction::RecordProject("x", "example#Point", "point", "x", origin), + Gate2Instruction::RecordProject("y", "example#Point", "point", "y", origin), + Gate2Instruction::Scalar( + Gate1Instruction::IntegerBinary("sum", IntegerBinaryOperator::Add, "x", "y", origin) + ), + ], + terminator: Gate2Terminator::Return("sum", origin), + ), + ], + ) +end + +def _gate2_entry(): Gate2Function + origin := _gate2_mir_origin(18) + outcome_type := Gate2ValueType::Aggregate("example#Outcome") + return Gate2Function.new( + id: "example#main", + name: "main", + parameters: [], + result_type: Gate2ResultType::Void, + entry: "entry", + origin: origin, + blocks: [ + Gate2Block.new( + id: "entry", + parameters: [], + origin: origin, + instructions: [ + Gate2Instruction::Scalar(Gate1Instruction::IntegerLiteral("twenty", 20, origin)), + Gate2Instruction::Scalar(Gate1Instruction::IntegerLiteral("twenty_two", 22, origin)), + Gate2Instruction::Scalar(Gate1Instruction::BooleanLiteral("found", true, origin)), + Gate2Instruction::Call("point", "example#make_point", ["twenty", "twenty_two"], origin), + Gate2Instruction::Call("outcome", "example#classify", ["point", "found"], origin), + Gate2Instruction::VariantTest("is_found", "example#Outcome", "outcome", "Found", origin), + ], + terminator: Gate2Terminator::Branch("is_found", "found", ["outcome"], "failure", [], origin), + ), + Gate2Block.new( + id: "found", + parameters: [Gate2Parameter.new(id: "matched", value_type: outcome_type, origin: origin)], + origin: origin, + instructions: [ + Gate2Instruction::VariantProject( + "point_payload", + "example#Outcome", + "matched", + "Found", + "point", + origin, + ), + Gate2Instruction::Call("answer", "example#sum_point", ["point_payload"], origin), + Gate2Instruction::Scalar(Gate1Instruction::IntegerLiteral("expected", 42, origin)), + Gate2Instruction::Scalar( + Gate1Instruction::IntegerCompare( + "matches", + ComparisonOperator::Equal, + "answer", + "expected", + origin, + ) + ), + ], + terminator: Gate2Terminator::Branch("matches", "success", [], "failure", [], origin), + ), + Gate2Block.new( + id: "success", + parameters: [], + origin: origin, + instructions: [ + Gate2Instruction::Scalar(Gate1Instruction::WriteStatic("gate2-aggregate\n", origin)), + ], + terminator: Gate2Terminator::Return(nil, origin), + ), + Gate2Block.new( + id: "failure", + parameters: [], + origin: origin, + instructions: [ + Gate2Instruction::Scalar(Gate1Instruction::WriteStatic("bad\n", origin)), + ], + terminator: Gate2Terminator::Return(nil, origin), + ), + ], + ) +end + +def gate2_mir_sample_module(): Gate2Module + return Gate2Module.new( + id: "example", + entry_function: "example#main", + sources: [NativeSource.new(id: "main", path: "src/main.trb")], + types: _gate2_mir_types(), + functions: [_gate2_make_point(), _gate2_classify(), _gate2_sum_point(), _gate2_entry()], + ) +end + +def _gate2_invalid_projection_module(): Gate2Module + origin := _gate2_mir_origin(40) + entry := Gate2Function.new( + id: "example#main", + name: "main", + parameters: [], + result_type: Gate2ResultType::Void, + entry: "entry", + origin: origin, + blocks: [ + Gate2Block.new( + id: "entry", + parameters: [], + origin: origin, + instructions: [ + Gate2Instruction::Scalar(Gate1Instruction::IntegerLiteral("code", 404, origin)), + Gate2Instruction::VariantConstruct( + "missing", + "example#Outcome", + "Missing", + ["code"], + origin, + ), + Gate2Instruction::VariantProject( + "invalid", + "example#Outcome", + "missing", + "Found", + "point", + origin, + ), + ], + terminator: Gate2Terminator::Return(nil, origin), + ), + ], + ) + return Gate2Module.new( + id: "example", + entry_function: entry.id, + sources: [NativeSource.new(id: "main", path: "src/main.trb")], + types: _gate2_mir_types(), + functions: [entry], + ) +end + +describe("Gate 2 aggregate MIR") do + test("verifies aggregate construction, projection, calls, returns, and block arguments") do + case verify_gate2_mir(gate2_mir_sample_module()) + when Result::Ok(valid) + expect(valid).to_be_true() + when Result::Err(diagnostic) + puts(diagnostic.path + ": " + diagnostic.message) + expect(false).to_be_true() + end + end + + test("emits deterministic QBE IL with stack-owned aggregate storage") do + first := emit_gate2_qbe(gate2_mir_sample_module()) + second := emit_gate2_qbe(gate2_mir_sample_module()) + case first + when Result::Ok(source) + expect(second).to_equal(first) + expect(source.include?("$memset")).to_be_true() + expect(source.include?("$memcpy")).to_be_true() + expect(source.include?("$trbn_variant_projection_fail")).to_be_true() + expect(source.include?("export function w $main()")).to_be_true() + when Result::Err(_diagnostic) + expect(false).to_be_true() + end + end + + test("builds and executes the aggregate vertical slice when QBE is configured") do + qbe_command := environment("TYPE_RB_NATIVE_QBE") + if qbe_command == nil + expect(true).to_be_true() + else + workspace := "/tmp/type-rb-native-gate2-integration" + case create_directory(workspace) + when Result::Ok(_unit) + when Result::Err(_error) + end + case build_gate2_executable( + gate2_mir_sample_module(), + qbe_command, + "/usr/bin/cc", + workspace, + workspace + "/sample", + ) + when Result::Ok(report) + case run(report.output_path, []) + when Result::Ok(process) + expect(process.success).to_be_true() + expect(process.stdout).to_equal("gate2-aggregate\n") + expect(process.stderr).to_equal("") + when Result::Err(_error) + expect(false).to_be_true() + end + when Result::Err(error) + puts(error.phase + ": " + error.message + "\n" + error.stderr) + expect(false).to_be_true() + end + end + end + + test("fails an invalid dynamic variant projection deterministically") do + qbe_command := environment("TYPE_RB_NATIVE_QBE") + if qbe_command == nil + expect(true).to_be_true() + else + workspace := "/tmp/type-rb-native-gate2-invalid-projection" + case create_directory(workspace) + when Result::Ok(_unit) + when Result::Err(_error) + end + case build_gate2_executable( + _gate2_invalid_projection_module(), + qbe_command, + "/usr/bin/cc", + workspace, + workspace + "/sample", + ) + when Result::Ok(report) + case run(report.output_path, []) + when Result::Ok(process) + expect(process.status).to_equal(2) + expect(process.stdout).to_equal("") + expect(process.stderr).to_equal("panic: invalid enum projection\n") + when Result::Err(_error) + expect(false).to_be_true() + end + when Result::Err(error) + puts(error.phase + ": " + error.message + "\n" + error.stderr) + expect(false).to_be_true() + end + end + end + + test("rejects nominally mismatched record construction") do + origin := _gate2_mir_origin(30) + broken := Gate2Function.new( + id: "example#main", + name: "main", + parameters: [], + result_type: Gate2ResultType::Void, + entry: "entry", + origin: origin, + blocks: [ + Gate2Block.new( + id: "entry", + parameters: [], + origin: origin, + instructions: [ + Gate2Instruction::Scalar(Gate1Instruction::BooleanLiteral("wrong", true, origin)), + Gate2Instruction::Scalar(Gate1Instruction::IntegerLiteral("right", 1, origin)), + Gate2Instruction::RecordConstruct( + "point", + "example#Point", + ["wrong", "right"], + origin, + ), + ], + terminator: Gate2Terminator::Return(nil, origin), + ), + ], + ) + native_module := Gate2Module.new( + id: "example", + entry_function: broken.id, + sources: [NativeSource.new(id: "main", path: "src/main.trb")], + types: _gate2_mir_types(), + functions: [broken], + ) + case verify_gate2_mir(native_module) + when Result::Ok(_valid) + expect(false).to_be_true() + when Result::Err(diagnostic) + expect(diagnostic.code).to_equal("TRBN0004") + expect(diagnostic.message).to_equal("operand type does not match the operation") + end + end +end diff --git a/src/gate2_toolchain.trb b/src/gate2_toolchain.trb new file mode 100644 index 00000000..12d56d6e --- /dev/null +++ b/src/gate2_toolchain.trb @@ -0,0 +1,113 @@ +import { Gate2Module } from gate2_mir +import { emit_gate2_qbe } from qbe2 +import { write_text } from trb/std/filesystem +import { run } from trb/std/process +import { Result } from trb/std/result +import { Instant } from trb/std/time + +record Gate2BuildError + phase: String + message: String + status: Integer? = nil + stdout: String = "" + stderr: String = "" +end + +record Gate2BuildReport + qbe_ir_path: String + assembly_path: String + output_path: String + emit_seconds: Float + qbe_seconds: Float + link_seconds: Float +end + +def build_gate2_executable( + native_module: Gate2Module, + qbe_command: String, + cc_command: String, + workspace: String, + output_path: String, + ): Result + emit_started := Instant.now() + qbe_source := case emit_gate2_qbe(native_module) + when Result::Ok(source) + source + when Result::Err(diagnostic) + return Result::Err( + Gate2BuildError.new( + phase: "verify", + message: diagnostic.code + " " + diagnostic.path + ": " + diagnostic.message, + ) + ) + end + emit_seconds := Instant.now().duration_since(emit_started).total_seconds() + qbe_ir_path := workspace + "/module.ssa" + assembly_path := workspace + "/module.s" + case write_text(qbe_ir_path, qbe_source) + when Result::Ok(_unit) + when Result::Err(error) + return Result::Err( + Gate2BuildError.new(phase: "write-qbe", message: error.operation + ": " + error.message) + ) + end + qbe_started := Instant.now() + qbe_result := case run(qbe_command, ["-t", "arm64_apple", qbe_ir_path]) + when Result::Ok(process_result) + process_result + when Result::Err(error) + return Result::Err( + Gate2BuildError.new(phase: "qbe", message: error.operation + ": " + error.message) + ) + end + qbe_seconds := Instant.now().duration_since(qbe_started).total_seconds() + if !qbe_result.success + return Result::Err( + Gate2BuildError.new( + phase: "qbe", + message: "QBE rejected the generated Gate 2 IL", + status: qbe_result.status, + stdout: qbe_result.stdout, + stderr: qbe_result.stderr, + ) + ) + end + case write_text(assembly_path, qbe_result.stdout) + when Result::Ok(_unit) + when Result::Err(error) + return Result::Err( + Gate2BuildError.new(phase: "write-assembly", message: error.operation + ": " + error.message) + ) + end + link_started := Instant.now() + cc_result := case run(cc_command, [assembly_path, "-Wl,-dead_strip", "-o", output_path]) + when Result::Ok(process_result) + process_result + when Result::Err(error) + return Result::Err( + Gate2BuildError.new(phase: "link", message: error.operation + ": " + error.message) + ) + end + link_seconds := Instant.now().duration_since(link_started).total_seconds() + if !cc_result.success + return Result::Err( + Gate2BuildError.new( + phase: "link", + message: "the system C toolchain rejected the Gate 2 QBE assembly", + status: cc_result.status, + stdout: cc_result.stdout, + stderr: cc_result.stderr, + ) + ) + end + return Result::Ok( + Gate2BuildReport.new( + qbe_ir_path: qbe_ir_path, + assembly_path: assembly_path, + output_path: output_path, + emit_seconds: emit_seconds, + qbe_seconds: qbe_seconds, + link_seconds: link_seconds, + ) + ) +end diff --git a/src/qbe.trb b/src/qbe.trb index d4ef5153..e27b2d00 100644 --- a/src/qbe.trb +++ b/src/qbe.trb @@ -815,3 +815,75 @@ def _qbe_static_symbol(static_data: Array, value: String): String end return static_data[index].symbol end + +# Internal sharing surface for the Gate 2 adapter. These functions keep the +# scalar runtime and exact arithmetic lowering in one implementation while the +# gate-specific module models remain intentionally distinct. +def qbe_line(mut writer: StringBuilder, value: String) + _qbe_line(writer, value) + return +end + +def qbe_emit_static_data(static_data: Array, mut writer: StringBuilder) + _qbe_emit_static_data(static_data, writer) + return +end + +def qbe_emit_checked_add(mut writer: StringBuilder) + _qbe_emit_checked_add(writer) + return +end + +def qbe_emit_checked_subtract(mut writer: StringBuilder) + _qbe_emit_checked_subtract(writer) + return +end + +def qbe_emit_checked_multiply(mut writer: StringBuilder) + _qbe_emit_checked_multiply(writer) + return +end + +def qbe_emit_checked_divide(mut writer: StringBuilder) + _qbe_emit_checked_divide(writer) + return +end + +def qbe_emit_checked_remainder(mut writer: StringBuilder) + _qbe_emit_checked_remainder(writer) + return +end + +def qbe_emit_checked_power(mut writer: StringBuilder) + _qbe_emit_checked_power(writer) + return +end + +def qbe_emit_integer_binary( + operator: IntegerBinaryOperator, + result: String, + left: String, + right: String, + block_index: Integer, + instruction_index: Integer, + mut writer: StringBuilder, + ) + _qbe_emit_integer_binary(operator, result, left, right, block_index, instruction_index, writer) + return +end + +def qbe_float_operator(operator: FloatBinaryOperator): String + return _qbe_float_operator(operator) +end + +def qbe_comparison(operator: ComparisonOperator, suffix: String): String + return _qbe_comparison(operator, suffix) +end + +def qbe_scalar_type(value_type: ScalarType): String + return _qbe_type(value_type) +end + +def qbe_static_symbol(static_data: Array, value: String): String + return _qbe_static_symbol(static_data, value) +end diff --git a/src/qbe2.trb b/src/qbe2.trb new file mode 100644 index 00000000..e3acb15c --- /dev/null +++ b/src/qbe2.trb @@ -0,0 +1,975 @@ +import { Diagnostic } from diagnostic +import { + ComparisonOperator, + FloatBinaryOperator, + Gate1Instruction, + IntegerBinaryOperator, + ScalarType, +} from gate1_mir +import { + Gate2FieldLayout, + Gate2Layout, + Gate2ValueType, + Gate2VariantLayout, + compute_gate2_layout, +} from gate2_layout +import { + Gate2Block, + Gate2Function, + Gate2Instruction, + Gate2Module, + Gate2ResultType, + Gate2Terminator, + gate2_block, + gate2_function, + gate2_instruction_result, + verify_gate2_mir, +} from gate2_mir +import { + QbeStaticData, + qbe_comparison, + qbe_emit_checked_add, + qbe_emit_checked_divide, + qbe_emit_checked_multiply, + qbe_emit_checked_power, + qbe_emit_checked_remainder, + qbe_emit_checked_subtract, + qbe_emit_integer_binary, + qbe_emit_static_data, + qbe_float_operator, + qbe_line, + qbe_scalar_type, + qbe_static_symbol, +} from qbe +import { SourceOrigin } from snapshot +import { Result } from trb/std/result +import trb/std/string_builder + +record Gate2QbeContext + values: Hash + value_types: Hash + transfer_slots: Hash + storage_slots: Hash +end + +def _qbe_line(mut writer: StringBuilder, value: String) + qbe_line(writer, value) + return +end + +def _qbe_emit_static_data(static_data: Array, mut writer: StringBuilder) + qbe_emit_static_data(static_data, writer) + return +end + +def _qbe_emit_checked_add(mut writer: StringBuilder) + qbe_emit_checked_add(writer) + return +end + +def _qbe_emit_checked_subtract(mut writer: StringBuilder) + qbe_emit_checked_subtract(writer) + return +end + +def _qbe_emit_checked_multiply(mut writer: StringBuilder) + qbe_emit_checked_multiply(writer) + return +end + +def _qbe_emit_checked_divide(mut writer: StringBuilder) + qbe_emit_checked_divide(writer) + return +end + +def _qbe_emit_checked_remainder(mut writer: StringBuilder) + qbe_emit_checked_remainder(writer) + return +end + +def _qbe_emit_checked_power(mut writer: StringBuilder) + qbe_emit_checked_power(writer) + return +end + +def _qbe_emit_integer_binary( + operator: IntegerBinaryOperator, + result: String, + left: String, + right: String, + block_index: Integer, + instruction_index: Integer, + mut writer: StringBuilder, + ) + qbe_emit_integer_binary(operator, result, left, right, block_index, instruction_index, writer) + return +end + +def _qbe_float_operator(operator: FloatBinaryOperator): String + return qbe_float_operator(operator) +end + +def _qbe_comparison(operator: ComparisonOperator, suffix: String): String + return qbe_comparison(operator, suffix) +end + +def _qbe_type(value_type: ScalarType): String + return qbe_scalar_type(value_type) +end + +def _qbe_static_symbol(static_data: Array, value: String): String + return qbe_static_symbol(static_data, value) +end + +def emit_gate2_qbe(native_module: Gate2Module): Result + try verify_gate2_mir(native_module) + static_data := _qbe2_collect_static_data(native_module) + mut writer := string_builder.new() + _qbe_emit_static_data(static_data, writer) + _qbe2_emit_projection_data(writer) + _qbe2_emit_runtime(writer) + mut function_index := 0 + while function_index < native_module.functions.size() + try _qbe2_emit_function( + native_module, + native_module.functions[function_index], + function_index, + static_data, + writer, + ) + function_index += 1 + end + _qbe2_emit_entry(native_module, writer) + return Result::Ok(writer.to_s()) +end + +def _qbe2_collect_static_data(native_module: Gate2Module): Array + mut static_data: Array := [] + mut symbols: Hash := {} + mut function_index := 0 + while function_index < native_module.functions.size() + function := native_module.functions[function_index] + mut block_index := 0 + while block_index < function.blocks.size() + block := function.blocks[block_index] + mut instruction_index := 0 + while instruction_index < block.instructions.size() + case block.instructions[instruction_index] + when Gate2Instruction::Scalar(scalar) + case scalar + when Gate1Instruction::WriteStatic(value, _origin) + if !symbols.key?(value) + symbol := "$trbn_data_" + static_data.size().to_s() + symbols[value] = symbol + static_data.push(QbeStaticData.new(symbol: symbol, value: value)) + end + else + end + else + end + instruction_index += 1 + end + block_index += 1 + end + function_index += 1 + end + return static_data +end + +def _qbe2_emit_projection_data(mut writer: StringBuilder) + _qbe_line(writer, "data $trbn_variant_projection_error = { b 112 97 110 105 99 58 32 105 110 118 97 108 105 100 32 101 110 117 109 32 112 114 111 106 101 99 116 105 111 110 10 }") + _qbe_line(writer, "") + return +end + +def _qbe2_emit_runtime(mut writer: StringBuilder) + _qbe_line(writer, "function $trbn_integer_range_fail() {") + _qbe_line(writer, "@start") + _qbe_line(writer, "\tcall $write(w 2, l $trbn_integer_range_error, l 45)") + _qbe_line(writer, "\tcall $exit(w 2)") + _qbe_line(writer, "\thlt") + _qbe_line(writer, "}") + _qbe_line(writer, "") + _qbe_line(writer, "function $trbn_division_fail() {") + _qbe_line(writer, "@start") + _qbe_line(writer, "\tcall $write(w 2, l $trbn_division_error, l 24)") + _qbe_line(writer, "\tcall $exit(w 2)") + _qbe_line(writer, "\thlt") + _qbe_line(writer, "}") + _qbe_line(writer, "") + _qbe_line(writer, "function $trbn_negative_exponent_fail() {") + _qbe_line(writer, "@start") + _qbe_line(writer, "\tcall $write(w 2, l $trbn_negative_exponent_error, l 33)") + _qbe_line(writer, "\tcall $exit(w 2)") + _qbe_line(writer, "\thlt") + _qbe_line(writer, "}") + _qbe_line(writer, "") + _qbe_line(writer, "function $trbn_variant_projection_fail() {") + _qbe_line(writer, "@start") + _qbe_line(writer, "\tcall $write(w 2, l $trbn_variant_projection_error, l 31)") + _qbe_line(writer, "\tcall $exit(w 2)") + _qbe_line(writer, "\thlt") + _qbe_line(writer, "}") + _qbe_line(writer, "") + _qbe_emit_checked_add(writer) + _qbe_emit_checked_subtract(writer) + _qbe_emit_checked_multiply(writer) + _qbe_emit_checked_divide(writer) + _qbe_emit_checked_remainder(writer) + _qbe_emit_checked_power(writer) + return +end + +def _qbe2_emit_function( + native_module: Gate2Module, + function: Gate2Function, + function_index: Integer, + static_data: Array, + mut writer: StringBuilder, + ): Result + context := try _qbe2_context(native_module, function) + mut parameters: Array := [] + case function.result_type + when Gate2ResultType::Void + when Gate2ResultType::Value(value_type) + case value_type + when Gate2ValueType::Scalar(_scalar) + when Gate2ValueType::Aggregate(_type_id) + parameters.push("l %return") + end + end + mut parameter_index := 0 + while parameter_index < function.parameters.size() + parameter := function.parameters[parameter_index] + parameters.push(_qbe2_abi_type(parameter.value_type) + " " + context.values.fetch(parameter.id)) + parameter_index += 1 + end + mut result_prefix := "" + case function.result_type + when Gate2ResultType::Void + when Gate2ResultType::Value(value_type) + case value_type + when Gate2ValueType::Scalar(scalar) + result_prefix = _qbe_type(scalar) + " " + when Gate2ValueType::Aggregate(_type_id) + end + end + _qbe_line(writer, "function " + result_prefix + "$trbn_g2f" + function_index.to_s() + "(" + parameters.join(", ") + ") {") + entry_index := _qbe2_block_index(function.blocks, function.entry) + try _qbe2_emit_block(native_module, function, entry_index, context, static_data, writer) + mut block_index := 0 + while block_index < function.blocks.size() + if block_index != entry_index + try _qbe2_emit_block(native_module, function, block_index, context, static_data, writer) + end + block_index += 1 + end + _qbe_line(writer, "}") + _qbe_line(writer, "") + return Result::Ok(true) +end + +def _qbe2_context( + native_module: Gate2Module, + function: Gate2Function, + ): Result + mut values: Hash := {} + mut value_types: Hash := {} + mut transfer_slots: Hash := {} + mut storage_slots: Hash := {} + mut next_value := 0 + mut next_transfer := 0 + mut next_storage := 0 + mut parameter_index := 0 + while parameter_index < function.parameters.size() + parameter := function.parameters[parameter_index] + values[parameter.id] = "%v" + next_value.to_s() + value_types[parameter.id] = parameter.value_type + next_value += 1 + parameter_index += 1 + end + mut block_index := 0 + while block_index < function.blocks.size() + block := function.blocks[block_index] + parameter_index = 0 + while parameter_index < block.parameters.size() + parameter := block.parameters[parameter_index] + transfer_slots[parameter.id] = "%transfer" + next_transfer.to_s() + next_transfer += 1 + value_types[parameter.id] = parameter.value_type + case parameter.value_type + when Gate2ValueType::Scalar(_scalar) + values[parameter.id] = "%v" + next_value.to_s() + next_value += 1 + when Gate2ValueType::Aggregate(_type_id) + storage_slots[parameter.id] = "%storage" + next_storage.to_s() + values[parameter.id] = storage_slots.fetch(parameter.id) + next_storage += 1 + end + parameter_index += 1 + end + mut instruction_index := 0 + while instruction_index < block.instructions.size() + instruction := block.instructions[instruction_index] + result := gate2_instruction_result(instruction) + if result != nil + value_type := try _qbe2_instruction_type(native_module, instruction) + value_types[result] = value_type + case value_type + when Gate2ValueType::Scalar(_scalar) + values[result] = "%v" + next_value.to_s() + next_value += 1 + when Gate2ValueType::Aggregate(_type_id) + storage_slots[result] = "%storage" + next_storage.to_s() + values[result] = storage_slots.fetch(result) + next_storage += 1 + end + end + instruction_index += 1 + end + block_index += 1 + end + return Result::Ok( + Gate2QbeContext.new( + values: values, + value_types: value_types, + transfer_slots: transfer_slots, + storage_slots: storage_slots, + ) + ) +end + +def _qbe2_instruction_type( + native_module: Gate2Module, + instruction: Gate2Instruction, + ): Result + case instruction + when Gate2Instruction::Scalar(scalar) + case scalar + when Gate1Instruction::BooleanLiteral(_result, _value, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate1Instruction::IntegerLiteral(_result, _value, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Integer)) + when Gate1Instruction::FloatLiteral(_result, _value, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Float)) + when Gate1Instruction::IntegerBinary(_result, _operator, _left, _right, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Integer)) + when Gate1Instruction::FloatBinary(_result, _operator, _left, _right, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Float)) + when Gate1Instruction::IntegerCompare(_result, _operator, _left, _right, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate1Instruction::FloatCompare(_result, _operator, _left, _right, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate1Instruction::BooleanNot(_result, _value, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate1Instruction::Call(_result, _function_id, _arguments, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Void)) + when Gate1Instruction::WriteStatic(_value, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Void)) + end + when Gate2Instruction::RecordConstruct(_result, type_id, _arguments, _origin) + return Result::Ok(Gate2ValueType::Aggregate(type_id)) + when Gate2Instruction::RecordProject(_result, type_id, _record, field, _origin) + layout := try compute_gate2_layout(native_module.types, type_id) + return Result::Ok(_qbe2_required_field(layout, field).value_type) + when Gate2Instruction::VariantConstruct(_result, type_id, _variant, _arguments, _origin) + return Result::Ok(Gate2ValueType::Aggregate(type_id)) + when Gate2Instruction::VariantTest(_result, _type_id, _value, _variant, _origin) + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Boolean)) + when Gate2Instruction::VariantProject(_result, type_id, _value, variant, field, _origin) + layout := try compute_gate2_layout(native_module.types, type_id) + variant_layout := _qbe2_required_variant(layout, variant) + return Result::Ok(_qbe2_required_variant_field(variant_layout, field).value_type) + when Gate2Instruction::Call(_result, function_id, _arguments, _origin) + callee := gate2_function(native_module.functions, function_id) + case callee.result_type + when Gate2ResultType::Void + return Result::Ok(Gate2ValueType::Scalar(ScalarType::Void)) + when Gate2ResultType::Value(value_type) + return Result::Ok(value_type) + end + end +end + +def _qbe2_emit_block( + native_module: Gate2Module, + function: Gate2Function, + block_index: Integer, + context: Gate2QbeContext, + static_data: Array, + mut writer: StringBuilder, + ): Result + block := function.blocks[block_index] + _qbe_line(writer, "@b" + block_index.to_s()) + if block.id == function.entry + try _qbe2_emit_allocations(native_module, function, context, writer) + else + try _qbe2_emit_block_parameter_loads(native_module, block, context, writer) + end + mut instruction_index := 0 + while instruction_index < block.instructions.size() + try _qbe2_emit_instruction( + native_module, + block.instructions[instruction_index], + block_index, + instruction_index, + context, + static_data, + writer, + ) + instruction_index += 1 + end + try _qbe2_emit_terminator(native_module, function, block.terminator, block_index, context, writer) + return Result::Ok(true) +end + +def _qbe2_emit_allocations( + native_module: Gate2Module, + function: Gate2Function, + context: Gate2QbeContext, + mut writer: StringBuilder, + ): Result + mut block_index := 0 + while block_index < function.blocks.size() + block := function.blocks[block_index] + mut parameter_index := 0 + while parameter_index < block.parameters.size() + parameter := block.parameters[parameter_index] + try _qbe2_emit_allocation( + native_module, + parameter.value_type, + context.transfer_slots.fetch(parameter.id), + writer, + ) + case parameter.value_type + when Gate2ValueType::Scalar(_scalar) + when Gate2ValueType::Aggregate(_type_id) + try _qbe2_emit_allocation( + native_module, + parameter.value_type, + context.storage_slots.fetch(parameter.id), + writer, + ) + end + parameter_index += 1 + end + mut instruction_index := 0 + while instruction_index < block.instructions.size() + result := gate2_instruction_result(block.instructions[instruction_index]) + if result != nil + if context.storage_slots.key?(result) + try _qbe2_emit_allocation( + native_module, + context.value_types.fetch(result), + context.storage_slots.fetch(result), + writer, + ) + end + end + instruction_index += 1 + end + block_index += 1 + end + return Result::Ok(true) +end + +def _qbe2_emit_allocation( + native_module: Gate2Module, + value_type: Gate2ValueType, + slot: String, + mut writer: StringBuilder, + ): Result + mut allocation := "alloc8" + mut size := 8 + case value_type + when Gate2ValueType::Scalar(scalar) + if scalar == ScalarType::Boolean + allocation = "alloc4" + size = 4 + end + when Gate2ValueType::Aggregate(type_id) + layout := try compute_gate2_layout(native_module.types, type_id) + size = layout.size + if layout.alignment < 8 + allocation = "alloc4" + if size < 4 + size = 4 + end + end + end + _qbe_line(writer, "\t" + slot + " =l " + allocation + " " + size.to_s()) + return Result::Ok(true) +end + +def _qbe2_emit_block_parameter_loads( + native_module: Gate2Module, + block: Gate2Block, + context: Gate2QbeContext, + mut writer: StringBuilder, + ): Result + mut index := 0 + while index < block.parameters.size() + parameter := block.parameters[index] + case parameter.value_type + when Gate2ValueType::Scalar(scalar) + _qbe_line(writer, + "\t" + context.values.fetch(parameter.id) + " =" + _qbe_type(scalar) + " " + + _qbe2_scalar_slot_load(scalar) + " " + context.transfer_slots.fetch(parameter.id) + ) + when Gate2ValueType::Aggregate(type_id) + layout := try compute_gate2_layout(native_module.types, type_id) + _qbe2_emit_copy( + context.values.fetch(parameter.id), + context.transfer_slots.fetch(parameter.id), + layout.size, + writer, + ) + end + index += 1 + end + return Result::Ok(true) +end + +def _qbe2_emit_instruction( + native_module: Gate2Module, + instruction: Gate2Instruction, + block_index: Integer, + instruction_index: Integer, + context: Gate2QbeContext, + static_data: Array, + mut writer: StringBuilder, + ): Result + prefix := "%g2_" + block_index.to_s() + "_" + instruction_index.to_s() + "_" + case instruction + when Gate2Instruction::Scalar(scalar) + _qbe2_emit_scalar(native_module, scalar, block_index, instruction_index, context.values, static_data, writer) + when Gate2Instruction::RecordConstruct(result, type_id, arguments, origin) + _qbe2_emit_origin(native_module, origin, writer) + layout := try compute_gate2_layout(native_module.types, type_id) + destination := context.values.fetch(result) + _qbe2_emit_zero(destination, layout.size, writer) + mut index := 0 + while index < layout.fields.size() + try _qbe2_emit_store_field( + native_module, + layout.fields[index], + context.values.fetch(arguments[index]), + destination, + prefix + "field" + index.to_s(), + writer, + ) + index += 1 + end + when Gate2Instruction::RecordProject(result, type_id, record, field, origin) + _qbe2_emit_origin(native_module, origin, writer) + layout := try compute_gate2_layout(native_module.types, type_id) + field_layout := _qbe2_required_field(layout, field) + try _qbe2_emit_project( + native_module, + field_layout, + context.values.fetch(record), + context.values.fetch(result), + prefix, + writer, + ) + when Gate2Instruction::VariantConstruct(result, type_id, variant, arguments, origin) + _qbe2_emit_origin(native_module, origin, writer) + layout := try compute_gate2_layout(native_module.types, type_id) + variant_layout := _qbe2_required_variant(layout, variant) + destination := context.values.fetch(result) + _qbe2_emit_zero(destination, layout.size, writer) + _qbe_line(writer, "\tstorew " + variant_layout.tag.to_s() + ", " + destination) + mut index := 0 + while index < variant_layout.fields.size() + try _qbe2_emit_store_field( + native_module, + variant_layout.fields[index], + context.values.fetch(arguments[index]), + destination, + prefix + "payload" + index.to_s(), + writer, + ) + index += 1 + end + when Gate2Instruction::VariantTest(result, type_id, value, variant, origin) + _qbe2_emit_origin(native_module, origin, writer) + layout := try compute_gate2_layout(native_module.types, type_id) + variant_layout := _qbe2_required_variant(layout, variant) + tag := prefix + "tag" + _qbe_line(writer, "\t" + tag + " =w loadw " + context.values.fetch(value)) + _qbe_line(writer, "\t" + context.values.fetch(result) + " =w ceqw " + tag + ", " + variant_layout.tag.to_s()) + when Gate2Instruction::VariantProject(result, type_id, value, variant, field, origin) + _qbe2_emit_origin(native_module, origin, writer) + layout := try compute_gate2_layout(native_module.types, type_id) + variant_layout := _qbe2_required_variant(layout, variant) + tag := prefix + "tag" + matches := prefix + "matches" + ok := "@g2_" + block_index.to_s() + "_" + instruction_index.to_s() + "_project_ok" + fail := "@g2_" + block_index.to_s() + "_" + instruction_index.to_s() + "_project_fail" + _qbe_line(writer, "\t" + tag + " =w loadw " + context.values.fetch(value)) + _qbe_line(writer, "\t" + matches + " =w ceqw " + tag + ", " + variant_layout.tag.to_s()) + _qbe_line(writer, "\tjnz " + matches + ", " + ok + ", " + fail) + _qbe_line(writer, fail) + _qbe_line(writer, "\tcall $trbn_variant_projection_fail()") + _qbe_line(writer, "\thlt") + _qbe_line(writer, ok) + field_layout := _qbe2_required_variant_field(variant_layout, field) + try _qbe2_emit_project( + native_module, + field_layout, + context.values.fetch(value), + context.values.fetch(result), + prefix, + writer, + ) + when Gate2Instruction::Call(result, function_id, arguments, origin) + _qbe2_emit_origin(native_module, origin, writer) + callee_index := _qbe2_function_index(native_module.functions, function_id) + callee := native_module.functions[callee_index] + mut call_arguments: Array := [] + case callee.result_type + when Gate2ResultType::Void + when Gate2ResultType::Value(value_type) + case value_type + when Gate2ValueType::Scalar(_scalar) + when Gate2ValueType::Aggregate(_type_id) + call_arguments.push("l " + context.values.fetch(_qbe2_required_id(result))) + end + end + mut index := 0 + while index < arguments.size() + call_arguments.push( + _qbe2_abi_type(callee.parameters[index].value_type) + " " + context.values.fetch(arguments[index]) + ) + index += 1 + end + mut call_prefix := "\t" + case callee.result_type + when Gate2ResultType::Void + when Gate2ResultType::Value(value_type) + case value_type + when Gate2ValueType::Scalar(scalar) + call_prefix = "\t" + context.values.fetch(_qbe2_required_id(result)) + " =" + _qbe_type(scalar) + " " + when Gate2ValueType::Aggregate(_type_id) + end + end + _qbe_line(writer, call_prefix + "call $trbn_g2f" + callee_index.to_s() + "(" + call_arguments.join(", ") + ")") + end + return Result::Ok(true) +end + +def _qbe2_emit_scalar( + native_module: Gate2Module, + instruction: Gate1Instruction, + block_index: Integer, + instruction_index: Integer, + values: Hash, + static_data: Array, + mut writer: StringBuilder, + ) + case instruction + when Gate1Instruction::BooleanLiteral(result, value, origin) + _qbe2_emit_origin(native_module, origin, writer) + mut literal := "0" + if value + literal = "1" + end + _qbe_line(writer, "\t" + values.fetch(result) + " =w copy " + literal) + when Gate1Instruction::IntegerLiteral(result, value, origin) + _qbe2_emit_origin(native_module, origin, writer) + _qbe_line(writer, "\t" + values.fetch(result) + " =l copy " + value.to_s()) + when Gate1Instruction::FloatLiteral(result, value, origin) + _qbe2_emit_origin(native_module, origin, writer) + _qbe_line(writer, "\t" + values.fetch(result) + " =d copy d_" + value.to_s()) + when Gate1Instruction::IntegerBinary(result, operator, left, right, origin) + _qbe2_emit_origin(native_module, origin, writer) + _qbe_emit_integer_binary( + operator, + values.fetch(result), + values.fetch(left), + values.fetch(right), + block_index, + instruction_index, + writer, + ) + when Gate1Instruction::FloatBinary(result, operator, left, right, origin) + _qbe2_emit_origin(native_module, origin, writer) + _qbe_line(writer, + "\t" + values.fetch(result) + " =d " + _qbe_float_operator(operator) + " " + + values.fetch(left) + ", " + values.fetch(right) + ) + when Gate1Instruction::IntegerCompare(result, operator, left, right, origin) + _qbe2_emit_origin(native_module, origin, writer) + _qbe_line(writer, + "\t" + values.fetch(result) + " =w " + _qbe_comparison(operator, "l") + " " + + values.fetch(left) + ", " + values.fetch(right) + ) + when Gate1Instruction::FloatCompare(result, operator, left, right, origin) + _qbe2_emit_origin(native_module, origin, writer) + _qbe_line(writer, + "\t" + values.fetch(result) + " =w " + _qbe_comparison(operator, "d") + " " + + values.fetch(left) + ", " + values.fetch(right) + ) + when Gate1Instruction::BooleanNot(result, value, origin) + _qbe2_emit_origin(native_module, origin, writer) + _qbe_line(writer, "\t" + values.fetch(result) + " =w ceqw " + values.fetch(value) + ", 0") + when Gate1Instruction::Call(_result, _function_id, _arguments, _origin) + when Gate1Instruction::WriteStatic(value, origin) + _qbe2_emit_origin(native_module, origin, writer) + symbol := _qbe_static_symbol(static_data, value) + _qbe_line(writer, + "\t%g2_io_" + block_index.to_s() + "_" + instruction_index.to_s() + + " =l call $write(w 1, l " + symbol + ", l " + value.to_bytes().size().to_s() + ")" + ) + end + return +end + +def _qbe2_emit_store_field( + native_module: Gate2Module, + field: Gate2FieldLayout, + source: String, + destination: String, + prefix: String, + mut writer: StringBuilder, + ): Result + address := _qbe2_address(destination, field.offset, prefix + "_address", writer) + case field.value_type + when Gate2ValueType::Scalar(scalar) + _qbe_line(writer, "\t" + _qbe2_scalar_field_store(scalar) + " " + source + ", " + address) + when Gate2ValueType::Aggregate(type_id) + layout := try compute_gate2_layout(native_module.types, type_id) + _qbe2_emit_copy(address, source, layout.size, writer) + end + return Result::Ok(true) +end + +def _qbe2_emit_project( + native_module: Gate2Module, + field: Gate2FieldLayout, + source: String, + destination: String, + prefix: String, + mut writer: StringBuilder, + ): Result + address := _qbe2_address(source, field.offset, prefix + "address", writer) + case field.value_type + when Gate2ValueType::Scalar(scalar) + _qbe_line(writer, + "\t" + destination + " =" + _qbe_type(scalar) + " " + _qbe2_scalar_field_load(scalar) + " " + address + ) + when Gate2ValueType::Aggregate(type_id) + layout := try compute_gate2_layout(native_module.types, type_id) + _qbe2_emit_copy(destination, address, layout.size, writer) + end + return Result::Ok(true) +end + +def _qbe2_emit_terminator( + native_module: Gate2Module, + function: Gate2Function, + terminator: Gate2Terminator, + block_index: Integer, + context: Gate2QbeContext, + mut writer: StringBuilder, + ): Result + case terminator + when Gate2Terminator::Jump(target, arguments, origin) + _qbe2_emit_origin(native_module, origin, writer) + try _qbe2_emit_edge(native_module, function, target, arguments, context, writer) + _qbe_line(writer, "\tjmp " + _qbe2_block_label(function.blocks, target)) + when Gate2Terminator::Branch(condition, when_true, true_arguments, when_false, false_arguments, origin) + _qbe2_emit_origin(native_module, origin, writer) + true_edge := "@g2_edge_" + block_index.to_s() + "_true" + false_edge := "@g2_edge_" + block_index.to_s() + "_false" + _qbe_line(writer, "\tjnz " + context.values.fetch(condition) + ", " + true_edge + ", " + false_edge) + _qbe_line(writer, true_edge) + try _qbe2_emit_edge(native_module, function, when_true, true_arguments, context, writer) + _qbe_line(writer, "\tjmp " + _qbe2_block_label(function.blocks, when_true)) + _qbe_line(writer, false_edge) + try _qbe2_emit_edge(native_module, function, when_false, false_arguments, context, writer) + _qbe_line(writer, "\tjmp " + _qbe2_block_label(function.blocks, when_false)) + when Gate2Terminator::Return(value, origin) + _qbe2_emit_origin(native_module, origin, writer) + case function.result_type + when Gate2ResultType::Void + _qbe_line(writer, "\tret") + when Gate2ResultType::Value(value_type) + case value_type + when Gate2ValueType::Scalar(_scalar) + _qbe_line(writer, "\tret " + context.values.fetch(_qbe2_required_id(value))) + when Gate2ValueType::Aggregate(type_id) + layout := try compute_gate2_layout(native_module.types, type_id) + _qbe2_emit_copy("%return", context.values.fetch(_qbe2_required_id(value)), layout.size, writer) + _qbe_line(writer, "\tret") + end + end + end + return Result::Ok(true) +end + +def _qbe2_emit_edge( + native_module: Gate2Module, + function: Gate2Function, + target: String, + arguments: Array, + context: Gate2QbeContext, + mut writer: StringBuilder, + ): Result + block := gate2_block(function.blocks, target) + mut index := 0 + while index < arguments.size() + parameter := block.parameters[index] + case parameter.value_type + when Gate2ValueType::Scalar(scalar) + _qbe_line(writer, + "\t" + _qbe2_scalar_slot_store(scalar) + " " + context.values.fetch(arguments[index]) + + ", " + context.transfer_slots.fetch(parameter.id) + ) + when Gate2ValueType::Aggregate(type_id) + layout := try compute_gate2_layout(native_module.types, type_id) + _qbe2_emit_copy( + context.transfer_slots.fetch(parameter.id), + context.values.fetch(arguments[index]), + layout.size, + writer, + ) + end + index += 1 + end + return Result::Ok(true) +end + +def _qbe2_emit_entry(native_module: Gate2Module, mut writer: StringBuilder) + entry_index := _qbe2_function_index(native_module.functions, native_module.entry_function) + _qbe_line(writer, "export function w $main() {") + _qbe_line(writer, "@start") + _qbe_line(writer, "\tcall $trbn_g2f" + entry_index.to_s() + "()") + _qbe_line(writer, "\tret 0") + _qbe_line(writer, "}") + return +end + +def _qbe2_emit_origin(native_module: Gate2Module, origin: SourceOrigin, mut writer: StringBuilder) + mut source_index := 0 + while native_module.sources[source_index].id != origin.source_id + source_index += 1 + end + _qbe_line(writer, + "\t# trb-origin source=" + source_index.to_s() + " start=" + origin.start_line.to_s() + ":" + + origin.start_column.to_s() + " end=" + origin.end_line.to_s() + ":" + origin.end_column.to_s() + ) + return +end + +def _qbe2_emit_zero(destination: String, size: Integer, mut writer: StringBuilder) + _qbe_line(writer, "\tcall $memset(l " + destination + ", w 0, l " + size.to_s() + ")") + return +end + +def _qbe2_emit_copy(destination: String, source: String, size: Integer, mut writer: StringBuilder) + _qbe_line(writer, "\tcall $memcpy(l " + destination + ", l " + source + ", l " + size.to_s() + ")") + return +end + +def _qbe2_address(base: String, offset: Integer, name: String, mut writer: StringBuilder): String + if offset == 0 + return base + end + _qbe_line(writer, "\t" + name + " =l add " + base + ", " + offset.to_s()) + return name +end + +def _qbe2_abi_type(value_type: Gate2ValueType): String + case value_type + when Gate2ValueType::Scalar(scalar) + return _qbe_type(scalar) + when Gate2ValueType::Aggregate(_type_id) + return "l" + end +end + +def _qbe2_scalar_field_load(value_type: ScalarType): String + if value_type == ScalarType::Boolean + return "loadub" + end + if value_type == ScalarType::Float + return "loadd" + end + return "loadl" +end + +def _qbe2_scalar_field_store(value_type: ScalarType): String + if value_type == ScalarType::Boolean + return "storeb" + end + if value_type == ScalarType::Float + return "stored" + end + return "storel" +end + +def _qbe2_scalar_slot_load(value_type: ScalarType): String + if value_type == ScalarType::Boolean + return "loadw" + end + return _qbe2_scalar_field_load(value_type) +end + +def _qbe2_scalar_slot_store(value_type: ScalarType): String + if value_type == ScalarType::Boolean + return "storew" + end + return _qbe2_scalar_field_store(value_type) +end + +def _qbe2_function_index(functions: Array, id: String): Integer + mut index := 0 + while functions[index].id != id + index += 1 + end + return index +end + +def _qbe2_block_label(blocks: Array, id: String): String + return "@b" + _qbe2_block_index(blocks, id).to_s() +end + +def _qbe2_block_index(blocks: Array, id: String): Integer + mut index := 0 + while blocks[index].id != id + index += 1 + end + return index +end + +def _qbe2_required_id(value: String?): String + if value == nil + return "" + end + return value +end + +def _qbe2_required_field(layout: Gate2Layout, name: String): Gate2FieldLayout + mut index := 0 + while layout.fields[index].name != name + index += 1 + end + return layout.fields[index] +end + +def _qbe2_required_variant(layout: Gate2Layout, name: String): Gate2VariantLayout + mut index := 0 + while layout.variants[index].name != name + index += 1 + end + return layout.variants[index] +end + +def _qbe2_required_variant_field(variant: Gate2VariantLayout, name: String): Gate2FieldLayout + mut index := 0 + while variant.fields[index].name != name + index += 1 + end + return variant.fields[index] +end