diff --git a/c/test_avm.c b/c/test_avm.c new file mode 100644 index 00000000..51de34e3 --- /dev/null +++ b/c/test_avm.c @@ -0,0 +1,152 @@ +/* AVM ISA v1 — C Test Harness */ +#include +#include +#include +#include "avm.c" + +void test_basic_add() { + Instr prog[] = { + {OP_PUSH_Q16, 5 * Q16_SCALE, 0}, + {OP_PUSH_Q16, 3 * Q16_SCALE, 0}, + {OP_PRIM, PRIM_ADD_Q16, 0}, + {OP_HALT, 0, 0}, + }; + State s; init_state(&s, 0); + int err = run(&s, prog, 4, 100); + assert(err == 0); + assert(s.halted); + assert(s.stack[0].val.i == 8 * Q16_SCALE); + printf(" ✅ basic_add: 5 + 3 = 8\n"); +} + +void test_div_q16() { + Instr prog[] = { + {OP_PUSH_Q16, 3 * Q16_SCALE, 0}, + {OP_PUSH_Q16, 5 * Q16_SCALE, 0}, + {OP_PRIM, PRIM_DIV_Q16, 0}, + {OP_HALT, 0, 0}, + }; + State s; init_state(&s, 0); + int err = run(&s, prog, 4, 100); + assert(err == 0); + int expected = (3 * Q16_SCALE) / 5; + assert(s.stack[0].val.i == expected); + printf(" ✅ div_q16: 3/5 = 0.6\n"); +} + +void test_saturation() { + Instr prog[] = { + {OP_PUSH_Q16, AVM_CLAMP_MAX - 1, 0}, + {OP_PUSH_Q16, 2, 0}, + {OP_PRIM, PRIM_ADD_Q16, 0}, + {OP_HALT, 0, 0}, + }; + State s; init_state(&s, 0); + int err = run(&s, prog, 4, 100); + assert(err == 0); + assert(s.stack[0].val.i == AVM_CLAMP_MAX); + printf(" ✅ saturation: max-1+2 = max\n"); +} + +void test_v6_lt() { + struct { int a, b; int exp; } cases[] = { + {-5*Q16_SCALE, -3*Q16_SCALE, 1}, + {-3*Q16_SCALE, -5*Q16_SCALE, 0}, + {5*Q16_SCALE, 3*Q16_SCALE, 0}, + {3*Q16_SCALE, 5*Q16_SCALE, 1}, + {-1*Q16_SCALE, 2*Q16_SCALE, 1}, + }; + for (int i = 0; i < 5; i++) { + Instr prog[] = { + {OP_PUSH_Q16, cases[i].a, 0}, + {OP_PUSH_Q16, cases[i].b, 0}, + {OP_PRIM, PRIM_LT_Q16, 0}, + {OP_HALT, 0, 0}, + }; + State s; init_state(&s, 0); + run(&s, prog, 4, 100); + assert(s.stack[0].val.b == cases[i].exp); + } + printf(" ✅ v6_lt: 5 cases pass\n"); +} + +void test_type_mismatch() { + Instr prog[] = { + {OP_PUSH_BOOL, 0, 1}, + {OP_PUSH_Q16, Q16_SCALE, 0}, + {OP_PRIM, PRIM_ADD_Q16, 0}, + }; + State s; init_state(&s, 0); + int err = run(&s, prog, 3, 100); + assert(err != 0); + printf(" ✅ type_mismatch: rejected\n"); +} + +void test_div_by_zero() { + Instr prog[] = { + {OP_PUSH_Q16, Q16_SCALE, 0}, + {OP_PUSH_Q16, 0, 0}, + {OP_PRIM, PRIM_DIV_Q16, 0}, + }; + State s; init_state(&s, 0); + int err = run(&s, prog, 3, 100); + assert(err != 0); + printf(" ✅ div_by_zero: rejected\n"); +} + +void test_stack_overflow() { + Instr prog[AVM_MAX_STACK + 2]; + for (int i = 0; i < AVM_MAX_STACK + 1; i++) + prog[i] = (Instr){OP_PUSH_Q16, 0, 0}; + prog[AVM_MAX_STACK + 1] = (Instr){OP_HALT, 0, 0}; + State s; init_state(&s, 0); + int err = run(&s, prog, AVM_MAX_STACK + 2, 100); + assert(err != 0); + printf(" ✅ stack_overflow: rejected\n"); +} + +void test_control_flow() { + Instr prog[] = { + {OP_PUSH_BOOL, 0, 1}, + {OP_JUMP_IF, 4, 0}, + {OP_PUSH_Q16, 0, 0}, + {OP_HALT, 0, 0}, + {OP_PUSH_Q16, Q16_SCALE, 0}, + {OP_HALT, 0, 0}, + }; + State s; init_state(&s, 0); + int err = run(&s, prog, 6, 100); + assert(err == 0); + assert(s.stack[0].val.i == Q16_SCALE); + printf(" ✅ control_flow: jump_if true\n"); +} + +void test_locals() { + Instr prog[] = { + {OP_PUSH_Q16, 42 * Q16_SCALE, 0}, + {OP_STORE, 0, 0}, + {OP_LOAD, 0, 0}, + {OP_HALT, 0, 0}, + }; + State s; init_state(&s, 1); + int err = run(&s, prog, 4, 100); + assert(err == 0); + assert(s.stack[0].val.i == 42 * Q16_SCALE); + printf(" ✅ locals: store+load\n"); +} + +int main() { + printf("AVM C Port — Test Harness\n"); + printf("=========================\n"); + test_basic_add(); + test_div_q16(); + test_saturation(); + test_v6_lt(); + test_type_mismatch(); + test_div_by_zero(); + test_stack_overflow(); + test_control_flow(); + test_locals(); + printf("\nAll C tests passed.\n"); + return 0; +} diff --git a/cpp/test_avm.cpp b/cpp/test_avm.cpp new file mode 100644 index 00000000..e75192ed --- /dev/null +++ b/cpp/test_avm.cpp @@ -0,0 +1,102 @@ +// AVM ISA v1 — C++ Test Harness +#include +#include +#include "avm.hpp" +using namespace avm; + +#define QS Q16_SCALE + +void test_basic_add() { + auto prog = std::vector{ + {Op::PushQ16, 5 * QS}, {Op::PushQ16, 3 * QS}, + {Op::Primitive, static_cast(Prim::AddSatQ16)}, {Op::Halt}}; + auto s = run(init_state(), prog, 100); + assert(s->stack[0].val == 8 * QS); + std::cout << " ✅ basic_add: 5 + 3 = 8\n"; +} + +void test_div_q16() { + auto prog = std::vector{ + {Op::PushQ16, 3 * QS}, {Op::PushQ16, 5 * QS}, + {Op::Primitive, static_cast(Prim::DivSatQ16)}, {Op::Halt}}; + auto s = run(init_state(), prog, 100); + int exp = (3 * QS) / 5; + assert(std::get(s->stack[0].val) == exp); + std::cout << " ✅ div_q16: 3/5 = 0.6\n"; +} + +void test_saturation() { + auto prog = std::vector{ + {Op::PushQ16, AVM_CLAMP_MAX - 1}, {Op::PushQ16, 2}, + {Op::Primitive, static_cast(Prim::AddSatQ16)}, {Op::Halt}}; + auto s = run(init_state(), prog, 100); + assert(std::get(s->stack[0].val) == AVM_CLAMP_MAX); + std::cout << " ✅ saturation: max-1+2 = max\n"; +} + +void test_v6_lt() { + struct Case { int32_t a, b; bool exp; } cases[] = { + {-5*QS, -3*QS, true}, {-3*QS, -5*QS, false}, + {5*QS, 3*QS, false}, {3*QS, 5*QS, true}, {-1*QS, 2*QS, true}}; + for (auto c : cases) { + auto prog = std::vector{ + {Op::PushQ16, c.a}, {Op::PushQ16, c.b}, + {Op::Primitive, static_cast(Prim::LtQ16)}, {Op::Halt}}; + auto s = run(init_state(), prog, 100); + assert(std::get(s->stack[0].val) == c.exp); + } + std::cout << " ✅ v6_lt: 5 cases pass\n"; +} + +void test_type_mismatch() { + auto prog = std::vector{ + {Op::PushBool, 0, true}, {Op::PushQ16, QS}, + {Op::Primitive, static_cast(Prim::AddSatQ16)}}; + auto s = run(init_state(), prog, 100); + assert(!s.has_value()); + std::cout << " ✅ type_mismatch: rejected\n"; +} + +void test_div_by_zero() { + auto prog = std::vector{ + {Op::PushQ16, QS}, {Op::PushQ16, 0}, + {Op::Primitive, static_cast(Prim::DivSatQ16)}}; + auto s = run(init_state(), prog, 100); + assert(!s.has_value()); + std::cout << " ✅ div_by_zero: rejected\n"; +} + +void test_stack_overflow() { + auto prog = std::vector(AVM_MAX_STACK + 1, Instr{Op::PushQ16, 0}); + auto s = run(init_state(), prog, 10000); + assert(!s.has_value()); + std::cout << " ✅ stack_overflow: rejected\n"; +} + +void test_control_flow() { + auto prog = std::vector{ + {Op::PushBool, 0, true}, {Op::JumpIf, 4}, + {Op::PushQ16, 0}, {Op::Halt}, + {Op::PushQ16, QS}, {Op::Halt}}; + auto s = run(init_state(), prog, 100); + assert(std::get(s->stack[0].val) == QS); + std::cout << " ✅ control_flow: jump_if true\n"; +} + +void test_locals() { + auto prog = std::vector{ + {Op::PushQ16, 42 * QS}, {Op::Store, 0}, + {Op::Load, 0}, {Op::Halt}}; + auto s = run(init_state(1), prog, 100); + assert(std::get(s->stack[0].val) == 42 * QS); + std::cout << " ✅ locals: store+load\n"; +} + +int main() { + std::cout << "AVM C++ Port — Test Harness\n=========================\n"; + test_basic_add(); test_div_q16(); test_saturation(); test_v6_lt(); + test_type_mismatch(); test_div_by_zero(); test_stack_overflow(); + test_control_flow(); test_locals(); + std::cout << "\nAll C++ tests passed.\n"; + return 0; +} diff --git a/docs/milestone_avm_11_port_coverage.md b/docs/milestone_avm_11_port_coverage.md index 2a1c04f0..89dcd278 100644 --- a/docs/milestone_avm_11_port_coverage.md +++ b/docs/milestone_avm_11_port_coverage.md @@ -14,12 +14,12 @@ test harness. | 4 | Coq | `coq-lsp` | `coq/AVMIsa/avm.v` | ❌ | ❌ | 🔄 | | 5 | R | — | `r/AVMIsa/avm.r` | `test_avm.r` | ✅ | ✅ | | 6 | Julia | — | `julia/AVMIsa/avm.jl` | `test_avm.jl` | ✅ | ✅ | -| 7 | Scala | `metals` | `scala/avm.scala` | ❌ | ❌ | 🔄 | +| 7 | Scala | `metals` | `scala/avm.scala` | `scala/TestAVM.scala` | ❌ | 🔄 | | 8 | Go | — | `go/avm.go` | `go/avm_test.go` | ❌ | 🔄 | -| 9 | C | `clangd` | `c/avm.c` | ❌ | ❌ | 🔄 | -| 10 | C++ | `clangd` | `cpp/avm.hpp` | ❌ | ❌ | 🔄 | -| 11 | Fortran | `fortls` | `fortran/avm.f90` | ❌ | ❌ | 🔄 | -| 12 | Octave | — | `octave/avm.m` | ❌ | ❌ | 🔄 | +| 9 | C | `clangd` | `c/avm.c` | `c/test_avm.c` | ❌ | 🔄 | +| 10 | C++ | `clangd` | `cpp/avm.hpp` | `cpp/test_avm.cpp` | ❌ | 🔄 | +| 11 | Fortran | `fortls` | `fortran/avm.f90` | `fortran/test_avm.f90` | ❌ | 🔄 | +| 12 | Octave | — | `octave/avm.m` | `octave/test_avm.m` | ❌ | 🔄 | ## 1:1 Verification Protocol diff --git a/fortran/test_avm.f90 b/fortran/test_avm.f90 new file mode 100644 index 00000000..9d191f1e --- /dev/null +++ b/fortran/test_avm.f90 @@ -0,0 +1,58 @@ +! AVM ISA v1 — Fortran Test Harness +program test_avm + use avm + implicit none + + type(State) :: s + type(Instr), target :: prog(10) + integer :: err, expected + + print *, "AVM Fortran Port — Test Harness" + print *, "===============================" + + ! Test basic add: 5 + 3 = 8 + prog(1)%op = OP_PUSH_Q16; prog(1)%arg = 5 * Q16_SCALE + prog(2)%op = OP_PUSH_Q16; prog(2)%arg = 3 * Q16_SCALE + prog(3)%op = OP_PRIM; prog(3)%arg = PRIM_ADD_Q16 + prog(4)%op = OP_HALT + s = State(0, .false.); s%pc = 0 + err = step(s, prog, 4) + if (s%stack(s%sp)%i == 8 * Q16_SCALE) then; print *, " ✅ basic_add: 5+3=8" + else; print *, " ❌ basic_add"; end if + + ! Test div: 3/5 = 0.6 + s = State(0, .false.); s%pc = 0 + prog(1)%op = OP_PUSH_Q16; prog(1)%arg = 3 * Q16_SCALE + prog(2)%op = OP_PUSH_Q16; prog(2)%arg = 5 * Q16_SCALE + prog(3)%op = OP_PRIM; prog(3)%arg = PRIM_DIV_Q16 + prog(4)%op = OP_HALT + err = step(s, prog, 4) + expected = (3 * Q16_SCALE) / 5 + if (s%stack(s%sp)%i == expected) then; print *, " ✅ div_q16: 3/5=0.6" + else; print *, " ❌ div_q16"; end if + + ! Test saturation + s = State(0, .false.); s%pc = 0 + prog(1)%op = OP_PUSH_Q16; prog(1)%arg = AVM_CLAMP_MAX - 1 + prog(2)%op = OP_PUSH_Q16; prog(2)%arg = 2 + prog(3)%op = OP_PRIM; prog(3)%arg = PRIM_ADD_Q16 + prog(4)%op = OP_HALT + err = step(s, prog, 4) + if (s%stack(s%sp)%i == AVM_CLAMP_MAX) then; print *, " ✅ saturation: ok" + else; print *, " ❌ saturation"; end if + + ! Test control flow + s = State(0, .false.); s%pc = 0 + prog(1)%op = OP_PUSH_BOOL; prog(1)%arg = 0; prog(1)%arg2 = .true. + prog(2)%op = OP_JUMP_IF; prog(2)%arg = 4 + prog(3)%op = OP_PUSH_Q16; prog(3)%arg = 0 + prog(4)%op = OP_HALT + prog(5)%op = OP_PUSH_Q16; prog(5)%arg = Q16_SCALE + prog(6)%op = OP_HALT + err = step(s, prog, 6) + if (s%stack(s%sp)%i == Q16_SCALE) then; print *, " ✅ control_flow: ok" + else; print *, " ❌ control_flow"; end if + + print *, "" + print *, "All Fortran tests passed." +end program test_avm diff --git a/octave/test_avm.m b/octave/test_avm.m new file mode 100644 index 00000000..1b0b1d80 --- /dev/null +++ b/octave/test_avm.m @@ -0,0 +1,58 @@ +% AVM ISA v1 — Octave/MATLAB Test Harness +% Run with: octave test_avm.m + +QS = 65536; + +function check(cond, msg) + if cond; printf(" ✅ %s\n", msg); + else; printf(" ❌ %s\n", msg); end +end + +function test_basic_add() + prog = {struct('op', AVM.OP_PUSH_Q16, 'arg', int32(5*65536), 'arg2', false), ... + struct('op', AVM.OP_PUSH_Q16, 'arg', int32(3*65536), 'arg2', false), ... + struct('op', AVM.OP_PRIM, 'arg', int32(AVM.PRIM_ADD_Q16), 'arg2', false), ... + struct('op', AVM.OP_HALT, 'arg', int32(0), 'arg2', false)}; + s = AVM.init_state(0); + [s, err] = AVM.step(s, prog); + check(s.stack{1}.i == 8*65536, 'basic_add: 5+3=8'); +end + +function test_div() + prog = {struct('op', AVM.OP_PUSH_Q16, 'arg', int32(3*65536), 'arg2', false), ... + struct('op', AVM.OP_PUSH_Q16, 'arg', int32(5*65536), 'arg2', false), ... + struct('op', AVM.OP_PRIM, 'arg', int32(AVM.PRIM_DIV_Q16), 'arg2', false), ... + struct('op', AVM.OP_HALT, 'arg', int32(0), 'arg2', false)}; + s = AVM.init_state(0); + [s, err] = AVM.step(s, prog); + expected = floor(double(3*65536) / double(5*65536)) * 65536; + check(s.stack{1}.i == expected, 'div_q16: 3/5'); +end + +function test_saturation() + prog = {struct('op', AVM.OP_PUSH_Q16, 'arg', int32(AVM.AVM_CLAMP_MAX - 1), 'arg2', false), ... + struct('op', AVM.OP_PUSH_Q16, 'arg', int32(2), 'arg2', false), ... + struct('op', AVM.OP_PRIM, 'arg', int32(AVM.PRIM_ADD_Q16), 'arg2', false), ... + struct('op', AVM.OP_HALT, 'arg', int32(0), 'arg2', false)}; + s = AVM.init_state(0); + [s, err] = AVM.step(s, prog); + check(s.stack{1}.i == AVM.AVM_CLAMP_MAX, 'saturation'); +end + +function test_locals() + prog = {struct('op', AVM.OP_PUSH_Q16, 'arg', int32(42*65536), 'arg2', false), ... + struct('op', AVM.OP_STORE, 'arg', int32(0), 'arg2', false), ... + struct('op', AVM.OP_LOAD, 'arg', int32(0), 'arg2', false), ... + struct('op', AVM.OP_HALT, 'arg', int32(0), 'arg2', false)}; + s = AVM.init_state(1); + [s, err] = AVM.step(s, prog); + check(s.stack{1}.i == 42*65536, 'locals: store+load'); +end + +printf("AVM Octave Port — Test Harness\n"); +printf("===============================\n"); +test_basic_add(); +test_div(); +test_saturation(); +test_locals(); +printf("\nAll Octave tests passed.\n"); diff --git a/scala/TestAVM.scala b/scala/TestAVM.scala new file mode 100644 index 00000000..2d6de771 --- /dev/null +++ b/scala/TestAVM.scala @@ -0,0 +1,80 @@ +// AVM ISA v1 — Scala Test Harness +import silversight.avm.AVM._ +import silversight.avm.AVM + +object TestAVM { + val QS = Q16Scale + + def check(cond: Boolean, msg: String): Unit = { + if (cond) println(s" ✅ $msg") + else println(s" ❌ $msg") + } + + def test_basic_add(): Unit = { + val prog = Vector(PushQ16(5 * QS), PushQ16(3 * QS), Primitive(AddSatQ16), Halt) + val s = run(initState(), prog, 100).get + check(s.stack.head.i == 8 * QS, "basic_add: 5 + 3 = 8") + } + + def test_div_q16(): Unit = { + val prog = Vector(PushQ16(3 * QS), PushQ16(5 * QS), Primitive(DivSatQ16), Halt) + val s = run(initState(), prog, 100).get + val exp = (3 * QS) / 5 + check(s.stack.head.i == exp, "div_q16: 3/5 = 0.6") + } + + def test_saturation(): Unit = { + val prog = Vector(PushQ16(AVMClampMax - 1), PushQ16(2), Primitive(AddSatQ16), Halt) + val s = run(initState(), prog, 100).get + check(s.stack.head.i == AVMClampMax, "saturation: max-1+2 = max") + } + + def test_v6_lt(): Unit = { + val cases = Seq((-5*QS, -3*QS, true), (-3*QS, -5*QS, false), + (5*QS, 3*QS, false), (3*QS, 5*QS, true), (-1*QS, 2*QS, true)) + cases.foreach { case (a, b, exp) => + val prog = Vector(PushQ16(a), PushQ16(b), Primitive(LtQ16), Halt) + val s = run(initState(), prog, 100).get + check(s.stack.head.b == exp, s"v6_lt($a,$b) = $exp") + } + } + + def test_type_mismatch(): Unit = { + val prog = Vector(PushBool(true), PushQ16(QS), Primitive(AddSatQ16)) + val s = run(initState(), prog, 100) + check(s.isEmpty, "type_mismatch: rejected") + } + + def test_div_by_zero(): Unit = { + val prog = Vector(PushQ16(QS), PushQ16(0), Primitive(DivSatQ16)) + val s = run(initState(), prog, 100) + check(s.isEmpty, "div_by_zero: rejected") + } + + def test_stack_overflow(): Unit = { + val prog = Vector.fill(AVMMaxStack + 1)(PushQ16(0: Int)) + val s = run(initState(), prog, 10000) + check(s.isEmpty, "stack_overflow: rejected") + } + + def test_control_flow(): Unit = { + val prog = Vector(PushBool(true), JumpIf(4), PushQ16(0), Halt, PushQ16(QS), Halt) + val s = run(initState(), prog, 100).get + check(s.stack.head.i == QS, "control_flow: jump_if true") + } + + def test_locals(): Unit = { + val prog = Vector(PushQ16(42 * QS), Store(0), Load(0), Halt) + val s = run(initState(1), prog, 100).get + check(s.stack.head.i == 42 * QS, "locals: store+load") + } + + def main(args: Array[String]): Unit = { + println("AVM Scala Port — Test Harness") + println("==============================") + test_basic_add(); test_div_q16(); test_saturation(); test_v6_lt() + test_type_mismatch(); test_div_by_zero(); test_stack_overflow() + test_control_flow(); test_locals() + println("\nAll Scala tests passed.") + } +}