|
| 1 | +// SPDX-License-Identifier: MPL-2.0 |
| 2 | +// Bytecode.affine — bytecode instruction set for the Error-Lang VM |
| 3 | +// (ported from compiler/src/Bytecode.res). |
| 4 | +// |
| 5 | +// Stack-based bytecode VM designed for: |
| 6 | +// - Computational haptics integration |
| 7 | +// - Positional semantics preservation |
| 8 | +// - Paradox visualization |
| 9 | +// - Educational transparency |
| 10 | +// |
| 11 | +// ── AffineScript port conventions ── |
| 12 | +// * `open` becomes module imports. Bytecode defines the opcode/value/chunk |
| 13 | +// types every sibling compiler back-end module reuses, so Codegen and VM |
| 14 | +// `use Bytecode::*;` after `use Types::*;`. |
| 15 | +// * AffineScript has no mutable struct fields and no record-spread; the |
| 16 | +// types here are pure data (immutable), so the only adaptation is the |
| 17 | +// ReScript inline-record variants and inline-record struct fields, which |
| 18 | +// become positional / named-immutable respectively. |
| 19 | +// * ReScript `value` variant `VFunction({arity, address, name})` -> the |
| 20 | +// positional `VFunction(Int, Int, String)` (arity, address, name). |
| 21 | +// `VEcho({input, output})` -> `VEcho(Value, Value)`; |
| 22 | +// `VResidue({output})` -> `VResidue(Value)`. |
| 23 | +// * `positionMetadata.operatorType` field is renamed `opType` to avoid the |
| 24 | +// field/enum-name ambiguity the TypeSuperposition port flagged |
| 25 | +// (`quantumType` -> `qtype`); purely a local rename, semantics identical. |
| 26 | +// * Opcode/value constructor names do not collide with `Types` and are kept |
| 27 | +// verbatim (OpPush/OpAdd/…, VInt/VFloat/…). |
| 28 | +// |
| 29 | +// ── Faithfulness notes: documented gaps / simplifications vs Bytecode.res ── |
| 30 | +// * `paradoxType` constructors are PREFIXED `Px` (PxTypeSuperposition … |
| 31 | +// PxMemoryPhantom). Reason: the unprefixed `NullPropagation` would COLLIDE |
| 32 | +// with the global `StabilityFactor::NullPropagation(Int)` constructor in |
| 33 | +// Types.affine (constructor names are global in AffineScript), exactly the |
| 34 | +// situation TypeChecker.affine handled by C-prefixing its `ty` ctors. The |
| 35 | +// payloads are opaque to every consumer (`OpInjectParadox(_)` discards |
| 36 | +// them in Codegen and VM), so the spelling change is invisible to |
| 37 | +// behaviour. The 1:1 mapping is: PxTypeSuperposition, PxPositionalSemantics, |
| 38 | +// PxScopeLeakage, PxTemporalCorruption, PxArithmeticDrift, PxNullPropagation, |
| 39 | +// PxContextCollapse, PxReservedWordRoulette, PxGlobalEntanglement, |
| 40 | +// PxMemoryPhantom. |
| 41 | +// * `disassemble` (a `Console.log`-based debug pretty-printer; not referenced |
| 42 | +// by Codegen or the VM, not part of program behaviour) is OMITTED per the |
| 43 | +// "omit Console/test-harness code" rule. The pure helpers it called — |
| 44 | +// `value_to_string` and `opcode_to_string` — ARE ported: `value_to_string` |
| 45 | +// is needed at runtime by the VM's positional-concatenation path, and |
| 46 | +// `opcode_to_string` mirrors the .res disassembly text faithfully. |
| 47 | +// * `Int.toString` / `Float.toString` / `String.padStart` -> `int_to_string` |
| 48 | +// / `float_to_string` / (padStart only existed inside the omitted |
| 49 | +// `disassemble`, so it is not needed). Array.map/joinWith -> map + join. |
| 50 | +// * No `runTests` code exists in Bytecode.res, so nothing else is omitted. |
| 51 | + |
| 52 | +module Bytecode; |
| 53 | + |
| 54 | +use prelude::*; |
| 55 | +use string::{join}; |
| 56 | +use Types::{Location}; |
| 57 | + |
| 58 | +// ============================================ |
| 59 | +// Operator position metadata |
| 60 | +// ============================================ |
| 61 | + |
| 62 | +// Operators whose meaning flips with source position. |
| 63 | +pub enum OperatorType { |
| 64 | + PlusOp, // Can be addition OR concatenation |
| 65 | + StarOp // Can be multiplication OR exponentiation |
| 66 | +} |
| 67 | + |
| 68 | +// Position metadata for operators that change behavior based on location. |
| 69 | +// (ReScript inline record under `OpAdd`/`OpMul`; here a named struct.) |
| 70 | +pub struct PositionMetadata { |
| 71 | + line: Int, |
| 72 | + column: Int, |
| 73 | + opType: OperatorType |
| 74 | +} |
| 75 | + |
| 76 | +// Paradox kinds (Px-prefixed; see header faithfulness note re: collision with |
| 77 | +// Types::StabilityFactor::NullPropagation). |
| 78 | +pub enum ParadoxType { |
| 79 | + PxTypeSuperposition, |
| 80 | + PxPositionalSemantics, |
| 81 | + PxScopeLeakage, |
| 82 | + PxTemporalCorruption, |
| 83 | + PxArithmeticDrift, |
| 84 | + PxNullPropagation, |
| 85 | + PxContextCollapse, |
| 86 | + PxReservedWordRoulette, |
| 87 | + PxGlobalEntanglement, |
| 88 | + PxMemoryPhantom |
| 89 | +} |
| 90 | + |
| 91 | +// ============================================ |
| 92 | +// Runtime values |
| 93 | +// ============================================ |
| 94 | + |
| 95 | +pub enum Value { |
| 96 | + VInt(Int), |
| 97 | + VFloat(Float), |
| 98 | + VString(String), |
| 99 | + VBool(Bool), |
| 100 | + VNil, |
| 101 | + VArray([Value]), |
| 102 | + // ReScript VFunction({arity, address, name}) -> positional (arity, address, name). |
| 103 | + VFunction(Int, Int, String), |
| 104 | + // A single fiber witness: input `x` reached output `y` (one element of |
| 105 | + // `Echo<A, B>`). ReScript VEcho({input, output}) -> positional (input, output). |
| 106 | + VEcho(Value, Value), |
| 107 | + // The residue of an echo after erasure: the input witness is gone |
| 108 | + // (non-recoverable), only the reached output is retained. This is |
| 109 | + // `EchoR<A, B>` at runtime. ReScript VResidue({output}) -> positional (output). |
| 110 | + VResidue(Value) |
| 111 | +} |
| 112 | + |
| 113 | +// ============================================ |
| 114 | +// Bytecode instructions |
| 115 | +// ============================================ |
| 116 | + |
| 117 | +pub enum Opcode { |
| 118 | + // Stack manipulation |
| 119 | + OpPush(Value), // Push constant onto stack |
| 120 | + OpPop, // Pop top of stack |
| 121 | + OpDup, // Duplicate top of stack |
| 122 | + |
| 123 | + // Variables |
| 124 | + OpGetLocal(Int), // Get local variable |
| 125 | + OpSetLocal(Int), // Set local variable |
| 126 | + OpGetGlobal(String), // Get global variable |
| 127 | + OpSetGlobal(String), // Set global variable |
| 128 | + |
| 129 | + // Arithmetic (with positional semantics metadata) |
| 130 | + OpAdd(PositionMetadata), // Addition (or concatenation based on position!) |
| 131 | + OpSub, // Subtraction |
| 132 | + OpMul(PositionMetadata), // Multiplication (or exponentiation based on position!) |
| 133 | + OpDiv, // Division |
| 134 | + OpMod, // Modulo |
| 135 | + OpNegate, // Unary negation |
| 136 | + |
| 137 | + // Comparison |
| 138 | + OpEq, OpNeq, OpLt, OpGt, OpLte, OpGte, |
| 139 | + |
| 140 | + // Logical |
| 141 | + OpAnd, OpOr, OpNot, |
| 142 | + |
| 143 | + // Control flow |
| 144 | + OpJump(Int), // Unconditional jump |
| 145 | + OpJumpIfFalse(Int), // Conditional jump |
| 146 | + OpCall(Int), // Call function (arity) |
| 147 | + OpReturn, // Return from function |
| 148 | + |
| 149 | + // Arrays |
| 150 | + OpArray(Int), // Create array with N elements |
| 151 | + OpIndex, // Array indexing |
| 152 | + |
| 153 | + // Special (computational haptics) |
| 154 | + OpTrace(String), // Add trace point |
| 155 | + OpCheckpoint(String), // Add checkpoint |
| 156 | + OpUpdateStability, // Recalculate stability score |
| 157 | + OpInjectParadox(ParadoxType), // Activate a paradox |
| 158 | + |
| 159 | + // Echo types (structured loss) |
| 160 | + OpEcho, // Pop output then input, push a fiber witness VEcho(input, output) |
| 161 | + OpEchoToResidue, // Pop an echo, push its residue (erases the witness; costs stability) |
| 162 | + OpResidueStrictlyLoses, // Pop a value, push VBool(true) iff it is a residue |
| 163 | + OpEchoInput, // Pop an echo, push its input witness (runtime error on a residue) |
| 164 | + OpEchoOutput, // Pop an echo or residue, push its retained output |
| 165 | + |
| 166 | + // Debug |
| 167 | + OpPrint(Bool), // Print (println if true) |
| 168 | + OpHalt // Stop execution |
| 169 | +} |
| 170 | + |
| 171 | +// ============================================ |
| 172 | +// Compiled bytecode chunk + program |
| 173 | +// ============================================ |
| 174 | + |
| 175 | +pub struct Chunk { |
| 176 | + code: [Opcode], |
| 177 | + constants: [Value], |
| 178 | + // Source location mapping for error reporting. |
| 179 | + locations: [Location] |
| 180 | +} |
| 181 | + |
| 182 | +pub struct BytecodeProgram { |
| 183 | + main: Chunk, |
| 184 | + functions: [(String, Chunk)] |
| 185 | +} |
| 186 | + |
| 187 | +// ============================================ |
| 188 | +// Utilities |
| 189 | +// ============================================ |
| 190 | + |
| 191 | +pub fn value_to_string(v: Value) -> String { |
| 192 | + match v { |
| 193 | + VInt(n) => int_to_string(n), |
| 194 | + VFloat(f) => float_to_string(f), |
| 195 | + VString(s) => "\"" ++ s ++ "\"", |
| 196 | + VBool(b) => if b { "true" } else { "false" }, |
| 197 | + VNil => "nil", |
| 198 | + VArray(arr) => { |
| 199 | + let items = join(map(arr, value_to_string), ", "); |
| 200 | + "[" ++ items ++ "]" |
| 201 | + }, |
| 202 | + VFunction(_arity, _address, name) => "<function " ++ name ++ ">", |
| 203 | + VEcho(input, output) => |
| 204 | + "echo(" ++ value_to_string(input) ++ " ↦ " ++ value_to_string(output) ++ ")", |
| 205 | + VResidue(output) => "residue(↦ " ++ value_to_string(output) ++ ")" |
| 206 | + } |
| 207 | +} |
| 208 | + |
| 209 | +pub fn opcode_to_string(op: Opcode) -> String { |
| 210 | + match op { |
| 211 | + OpPush(v) => "PUSH " ++ value_to_string(v), |
| 212 | + OpPop => "POP", |
| 213 | + OpDup => "DUP", |
| 214 | + OpGetLocal(i) => "GET_LOCAL " ++ int_to_string(i), |
| 215 | + OpSetLocal(i) => "SET_LOCAL " ++ int_to_string(i), |
| 216 | + OpGetGlobal(name) => "GET_GLOBAL " ++ name, |
| 217 | + OpSetGlobal(name) => "SET_GLOBAL " ++ name, |
| 218 | + OpAdd(_p) => "ADD", |
| 219 | + OpSub => "SUB", |
| 220 | + OpMul(_p) => "MUL", |
| 221 | + OpDiv => "DIV", |
| 222 | + OpMod => "MOD", |
| 223 | + OpNegate => "NEGATE", |
| 224 | + OpEq => "EQ", |
| 225 | + OpNeq => "NEQ", |
| 226 | + OpLt => "LT", |
| 227 | + OpGt => "GT", |
| 228 | + OpLte => "LTE", |
| 229 | + OpGte => "GTE", |
| 230 | + OpAnd => "AND", |
| 231 | + OpOr => "OR", |
| 232 | + OpNot => "NOT", |
| 233 | + OpJump(offset) => "JUMP " ++ int_to_string(offset), |
| 234 | + OpJumpIfFalse(offset) => "JUMP_IF_FALSE " ++ int_to_string(offset), |
| 235 | + OpCall(arity) => "CALL " ++ int_to_string(arity), |
| 236 | + OpReturn => "RETURN", |
| 237 | + OpArray(size) => "ARRAY " ++ int_to_string(size), |
| 238 | + OpIndex => "INDEX", |
| 239 | + OpTrace(msg) => "TRACE \"" ++ msg ++ "\"", |
| 240 | + OpCheckpoint(name) => "CHECKPOINT \"" ++ name ++ "\"", |
| 241 | + OpUpdateStability => "UPDATE_STABILITY", |
| 242 | + OpInjectParadox(_p) => "INJECT_PARADOX", |
| 243 | + OpEcho => "ECHO", |
| 244 | + OpEchoToResidue => "ECHO_TO_RESIDUE", |
| 245 | + OpResidueStrictlyLoses => "RESIDUE_STRICTLY_LOSES", |
| 246 | + OpEchoInput => "ECHO_INPUT", |
| 247 | + OpEchoOutput => "ECHO_OUTPUT", |
| 248 | + OpPrint(is_println) => if is_println { "PRINTLN" } else { "PRINT" }, |
| 249 | + OpHalt => "HALT" |
| 250 | + } |
| 251 | +} |
0 commit comments