SilverSight/cpp/avm.hpp
allaun 863da04f21 feat(avm-ports): port AVM ISA to all 12 scientific languages
Lean (reference), Python, Rust, C, C++, Go, Julia, R, Scala, Fortran,
Coq, Octave — all implementing the same AVM ISA v1 specification.

Every port implements:
- Full type universe: Q0_16, Q16_16, Bool
- 11 primitives with floor division (Lean Int.ediv), V6 signed comparison,
  symmetric clamping [-2147483647, 2147483647]
- 12 instruction opcodes with stack depth limit (1024)
- Fuel-bounded run loop
- Error handling (stack under/overflow, type mismatch, div-by-zero, jump OOB)
2026-06-30 17:42:38 -05:00

204 lines
8.5 KiB
C++

// AVM ISA v1 — C++ Port (Strict Functional Execution)
#pragma once
#include <cstdint>
#include <vector>
#include <variant>
#include <optional>
#include <stdexcept>
namespace avm {
// ── Constants ────────────────────────────────────────────────────
constexpr int32_t AVM_CLAMP_MIN = -2147483647;
constexpr int32_t AVM_CLAMP_MAX = 2147483647;
constexpr int32_t AVM_Q0_MIN = -32767;
constexpr int32_t AVM_Q0_MAX = 32767;
constexpr int64_t Q16_SCALE = 65536;
constexpr size_t AVM_MAX_STACK = 1024;
inline int32_t avm_clamp(int64_t x) {
if (x > AVM_CLAMP_MAX) return AVM_CLAMP_MAX;
if (x < AVM_CLAMP_MIN) return AVM_CLAMP_MIN;
return static_cast<int32_t>(x);
}
inline int32_t avm_q0_clamp(int64_t x) {
if (x > AVM_Q0_MAX) return AVM_Q0_MAX;
if (x < AVM_Q0_MIN) return AVM_Q0_MIN;
return static_cast<int32_t>(x);
}
inline int32_t floor_div(int64_t a, int64_t b) {
if (b == 0) throw std::runtime_error("division by zero");
int64_t q = a / b;
if (a % b != 0 && ((a ^ b) < 0)) q--;
return static_cast<int32_t>(q);
}
inline bool lt_q16_v6(int32_t a, int32_t b) {
bool sa = a < 0, sb = b < 0;
return (sa != sb) ? sa : (a < b);
}
// ── Types ──────────────────────────────────────────────────────
enum class Ty : uint8_t { Q0_16, Q16_16, Bool };
using Val = std::variant<int32_t, bool>;
struct AnyVal { Ty ty; Val val; };
// ── Primitives ────────────────────────────────────────────────
enum class Prim : uint8_t {
AddSatQ0, SubSatQ0,
AddSatQ16, SubSatQ16, MulSatQ16, DivSatQ16,
LtQ16, EqQ16, And, Or, Not
};
inline int prim_arity(Prim p) {
return (p == Prim::Not) ? 1 : 2;
}
// ── Instructions ──────────────────────────────────────────────
enum class Op : uint8_t {
PushQ16, PushBool, PushQ0,
Pop, Dup, Swap, Load, Store,
Jump, JumpIf, Primitive, Halt
};
struct Instr {
Op op;
int32_t arg;
bool arg2;
};
// ── Primitive execution ──────────────────────────────────────
inline AnyVal exec_prim(Prim p, const AnyVal& a, const AnyVal& b) {
auto check = [](const AnyVal& v, Ty t) { if (v.ty != t) throw std::runtime_error("type mismatch"); };
switch (p) {
case Prim::AddSatQ0:
check(a, Ty::Q0_16); check(b, Ty::Q0_16);
return {Ty::Q0_16, avm_q0_clamp(static_cast<int64_t>(std::get<int32_t>(a.val)) + std::get<int32_t>(b.val))};
case Prim::SubSatQ0:
check(a, Ty::Q0_16); check(b, Ty::Q0_16);
return {Ty::Q0_16, avm_q0_clamp(static_cast<int64_t>(std::get<int32_t>(a.val)) - std::get<int32_t>(b.val))};
case Prim::AddSatQ16:
check(a, Ty::Q16_16); check(b, Ty::Q16_16);
return {Ty::Q16_16, avm_clamp(static_cast<int64_t>(std::get<int32_t>(a.val)) + std::get<int32_t>(b.val))};
case Prim::SubSatQ16:
check(a, Ty::Q16_16); check(b, Ty::Q16_16);
return {Ty::Q16_16, avm_clamp(static_cast<int64_t>(std::get<int32_t>(a.val)) - std::get<int32_t>(b.val))};
case Prim::MulSatQ16:
check(a, Ty::Q16_16); check(b, Ty::Q16_16);
return {Ty::Q16_16, avm_clamp(floor_div(static_cast<int64_t>(std::get<int32_t>(a.val)) * std::get<int32_t>(b.val), Q16_SCALE))};
case Prim::DivSatQ16:
check(a, Ty::Q16_16); check(b, Ty::Q16_16);
return {Ty::Q16_16, avm_clamp(floor_div(static_cast<int64_t>(std::get<int32_t>(a.val)) * Q16_SCALE, std::get<int32_t>(b.val)))};
case Prim::LtQ16:
check(a, Ty::Q16_16); check(b, Ty::Q16_16);
return {Ty::Bool, lt_q16_v6(std::get<int32_t>(a.val), std::get<int32_t>(b.val))};
case Prim::EqQ16:
check(a, Ty::Q16_16); check(b, Ty::Q16_16);
return {Ty::Bool, std::get<int32_t>(a.val) == std::get<int32_t>(b.val)};
case Prim::And:
check(a, Ty::Bool); check(b, Ty::Bool);
return {Ty::Bool, std::get<bool>(a.val) && std::get<bool>(b.val)};
case Prim::Or:
check(a, Ty::Bool); check(b, Ty::Bool);
return {Ty::Bool, std::get<bool>(a.val) || std::get<bool>(b.val)};
case Prim::Not:
check(a, Ty::Bool);
return {Ty::Bool, !std::get<bool>(a.val)};
}
throw std::runtime_error("unknown prim");
}
// ── State ─────────────────────────────────────────────────────
struct State {
int pc = 0;
std::vector<AnyVal> stack;
std::vector<std::optional<AnyVal>> locals;
bool halted = false;
};
inline State init_state(size_t n_locals = 0) {
return {0, {}, std::vector<std::optional<AnyVal>>(n_locals), false};
}
// ── Step ─────────────────────────────────────────────────────
inline std::optional<State> step(const State& s, const std::vector<Instr>& prog) {
if (s.halted) return std::nullopt;
if (s.pc < 0 || static_cast<size_t>(s.pc) >= prog.size())
return State{s.pc, s.stack, s.locals, true};
auto instr = prog[s.pc];
State ns = s;
int npc = s.pc + 1;
auto growing = (instr.op == Op::PushQ16 || instr.op == Op::PushBool || instr.op == Op::PushQ0 || instr.op == Op::Dup || instr.op == Op::Load);
if (growing && ns.stack.size() >= AVM_MAX_STACK) return std::nullopt; // overflow
switch (instr.op) {
case Op::PushQ16: ns.stack.push_back({Ty::Q16_16, avm_clamp(instr.arg)}); break;
case Op::PushBool: ns.stack.push_back({Ty::Bool, instr.arg2}); break;
case Op::PushQ0: ns.stack.push_back({Ty::Q0_16, avm_q0_clamp(instr.arg)}); break;
case Op::Pop:
if (ns.stack.empty()) return std::nullopt;
ns.stack.pop_back(); break;
case Op::Dup:
if (ns.stack.empty()) return std::nullopt;
ns.stack.push_back(ns.stack.back()); break;
case Op::Swap:
if (ns.stack.size() < 2) return std::nullopt;
std::swap(ns.stack[ns.stack.size()-1], ns.stack[ns.stack.size()-2]); break;
case Op::Load: {
size_t i = instr.arg;
if (i >= ns.locals.size() || !ns.locals[i].has_value()) return std::nullopt;
ns.stack.push_back(ns.locals[i].value()); break;
}
case Op::Store: {
size_t i = instr.arg;
if (ns.stack.empty() || i >= ns.locals.size()) return std::nullopt;
ns.locals[i] = ns.stack.back(); ns.stack.pop_back(); break;
}
case Op::Jump:
if (instr.arg < 0 || static_cast<size_t>(instr.arg) >= prog.size()) return std::nullopt;
npc = instr.arg; break;
case Op::JumpIf: {
if (ns.stack.empty()) return std::nullopt;
auto v = ns.stack.back(); ns.stack.pop_back();
if (v.ty != Ty::Bool) return std::nullopt;
if (std::get<bool>(v.val)) {
if (instr.arg < 0 || static_cast<size_t>(instr.arg) >= prog.size()) return std::nullopt;
npc = instr.arg;
}
break;
}
case Op::Primitive: {
auto p = static_cast<Prim>(instr.arg);
int arity = prim_arity(p);
if (static_cast<int>(ns.stack.size()) < arity) return std::nullopt;
AnyVal b{Ty::Bool, false};
if (arity >= 2) { b = ns.stack.back(); ns.stack.pop_back(); }
AnyVal a = ns.stack.back(); ns.stack.pop_back();
try { ns.stack.push_back(exec_prim(p, a, b)); }
catch (...) { return std::nullopt; }
break;
}
case Op::Halt: ns.halted = true; break;
}
ns.pc = npc;
return ns;
}
// ── Run (fuel-bounded) ───────────────────────────────────────
inline std::optional<State> run(const State& init, const std::vector<Instr>& prog, int fuel = 10000) {
State s = init;
for (int i = 0; i < fuel; i++) {
if (s.halted) return s;
auto next = step(s, prog);
if (!next.has_value()) return std::nullopt;
s = next.value();
}
return s;
}
} // namespace avm