% AVM ISA v1 — Octave/MATLAB Port (Strict Functional Execution)
% Mirrors formal/SilverSight/AVMIsa/Step.lean

classdef AVM
    properties (Constant)
        AVM_CLAMP_MIN = -2147483647
        AVM_CLAMP_MAX = 2147483647
        AVM_Q0_MIN = -32767
        AVM_Q0_MAX = 32767
        Q16_SCALE = 65536
        AVM_MAX_STACK = 1024

        TY_Q0 = 0; TY_Q16 = 1; TY_BOOL = 2

        OP_PUSH_Q16 = 0; OP_PUSH_BOOL = 1; OP_PUSH_Q0 = 2
        OP_POP = 3; OP_DUP = 4; OP_SWAP = 5
        OP_LOAD = 6; OP_STORE = 7; OP_JUMP = 8
        OP_JUMP_IF = 9; OP_PRIM = 10; OP_HALT = 11

        PRIM_ADD_Q0 = 0; PRIM_SUB_Q0 = 1
        PRIM_ADD_Q16 = 2; PRIM_SUB_Q16 = 3
        PRIM_MUL_Q16 = 4; PRIM_DIV_Q16 = 5
        PRIM_LT_Q16 = 6; PRIM_EQ_Q16 = 7
        PRIM_AND = 8; PRIM_OR = 9; PRIM_NOT = 10
    end

    methods (Static)
        function r = avm_clamp(x)
            if x > AVM.AVM_CLAMP_MAX; r = AVM.AVM_CLAMP_MAX;
            elseif x < AVM.AVM_CLAMP_MIN; r = AVM.AVM_CLAMP_MIN;
            else; r = int32(x); end
        end

        function r = avm_q0_clamp(x)
            if x > AVM.AVM_Q0_MAX; r = AVM.AVM_Q0_MAX;
            elseif x < AVM.AVM_Q0_MIN; r = AVM.AVM_Q0_MIN;
            else; r = int32(x); end
        end

        function r = floor_div(a, b)
            if b == 0; error('division by zero'); end
            r = int32(floor(double(a) / double(b)));
        end

        function r = lt_q16_v6(a, b)
            sa = a < 0; sb = b < 0;
            if sa ~= sb; r = sa; else; r = a < b; end
        end

        function v = val_q16(x)
            v = struct('ty', AVM.TY_Q16, 'i', AVM.avm_clamp(x), 'b', false);
        end

        function v = val_q0(x)
            v = struct('ty', AVM.TY_Q0, 'i', AVM.avm_q0_clamp(x), 'b', false);
        end

        function v = val_bool(x)
            v = struct('ty', AVM.TY_BOOL, 'i', int32(0), 'b', x);
        end

        function s = init_state(n_locals)
            if nargin < 1; n_locals = 0; end
            s.pc = int32(1);  % 1-indexed for Octave
            s.stack = {};
            s.locals = cell(n_locals, 1);
            s.halted = false;
        end

        function r = exec_prim(p, a, b)
            r = AVM.val_q0(0);
            if p == AVM.PRIM_ADD_Q0
                if a.ty ~= AVM.TY_Q0 || b.ty ~= AVM.TY_Q0; return; end
                r = AVM.val_q0(double(a.i) + double(b.i));
            elseif p == AVM.PRIM_SUB_Q0
                if a.ty ~= AVM.TY_Q0 || b.ty ~= AVM.TY_Q0; return; end
                r = AVM.val_q0(double(a.i) - double(b.i));
            elseif p == AVM.PRIM_ADD_Q16
                if a.ty ~= AVM.TY_Q16 || b.ty ~= AVM.TY_Q16; return; end
                r = AVM.val_q16(double(a.i) + double(b.i));
            elseif p == AVM.PRIM_SUB_Q16
                if a.ty ~= AVM.TY_Q16 || b.ty ~= AVM.TY_Q16; return; end
                r = AVM.val_q16(double(a.i) - double(b.i));
            elseif p == AVM.PRIM_MUL_Q16
                if a.ty ~= AVM.TY_Q16 || b.ty ~= AVM.TY_Q16; return; end
                r = AVM.val_q16(AVM.floor_div(double(a.i) * double(b.i), AVM.Q16_SCALE));
            elseif p == AVM.PRIM_DIV_Q16
                if a.ty ~= AVM.TY_Q16 || b.ty ~= AVM.TY_Q16 || b.i == 0; return; end
                r = AVM.val_q16(AVM.floor_div(double(a.i) * AVM.Q16_SCALE, double(b.i)));
            elseif p == AVM.PRIM_LT_Q16
                if a.ty ~= AVM.TY_Q16 || b.ty ~= AVM.TY_Q16; return; end
                r = AVM.val_bool(AVM.lt_q16_v6(a.i, b.i));
            elseif p == AVM.PRIM_EQ_Q16
                if a.ty ~= AVM.TY_Q16 || b.ty ~= AVM.TY_Q16; return; end
                r = AVM.val_bool(a.i == b.i);
            elseif p == AVM.PRIM_AND
                if a.ty ~= AVM.TY_BOOL || b.ty ~= AVM.TY_BOOL; return; end
                r = AVM.val_bool(a.b && b.b);
            elseif p == AVM.PRIM_OR
                if a.ty ~= AVM.TY_BOOL || b.ty ~= AVM.TY_BOOL; return; end
                r = AVM.val_bool(a.b || b.b);
            elseif p == AVM.PRIM_NOT
                if a.ty ~= AVM.TY_BOOL; return; end
                r = AVM.val_bool(~a.b);
            end
        end

        function [s, err] = step(s, prog)
            err = 0; n = length(prog);
            if s.halted; err = -1; return; end
            if s.pc < 1 || s.pc > n; s.halted = true; return; end

            instr = prog{s.pc}; npc = s.pc + 1;

            growing = any(instr.op == [AVM.OP_PUSH_Q16, AVM.OP_PUSH_BOOL, AVM.OP_PUSH_Q0, AVM.OP_DUP, AVM.OP_LOAD]);
            if growing && length(s.stack) >= AVM.AVM_MAX_STACK; err = -2; return; end

            if instr.op == AVM.OP_PUSH_Q16
                s.stack{end+1} = AVM.val_q16(instr.arg);
            elseif instr.op == AVM.OP_PUSH_BOOL
                s.stack{end+1} = AVM.val_bool(instr.arg2);
            elseif instr.op == AVM.OP_PUSH_Q0
                s.stack{end+1} = AVM.val_q0(instr.arg);
            elseif instr.op == AVM.OP_POP
                if isempty(s.stack); err = -3; return; end; s.stack(end) = [];
            elseif instr.op == AVM.OP_DUP
                if isempty(s.stack); err = -3; return; end
                s.stack{end+1} = s.stack{end};
            elseif instr.op == AVM.OP_SWAP
                if length(s.stack) < 2; err = -4; return; end
                tmp = s.stack{end}; s.stack{end} = s.stack{end-1}; s.stack{end-1} = tmp;
            elseif instr.op == AVM.OP_LOAD
                i = instr.arg + 1;
                if i > length(s.locals) || isempty(s.locals{i}); err = -5; return; end
                s.stack{end+1} = s.locals{i};
            elseif instr.op == AVM.OP_STORE
                if isempty(s.stack); err = -3; return; end
                i = instr.arg + 1;
                if i > length(s.locals); err = -5; return; end
                s.locals{i} = s.stack{end}; s.stack(end) = [];
            elseif instr.op == AVM.OP_JUMP
                t = instr.arg + 1;
                if t < 1 || t > n; err = -6; return; end; npc = t;
            elseif instr.op == AVM.OP_JUMP_IF
                if isempty(s.stack); err = -3; return; end
                v = s.stack{end}; s.stack(end) = [];
                if v.ty ~= AVM.TY_BOOL; err = -7; return; end
                if v.b; t = instr.arg + 1;
                    if t < 1 || t > n; err = -6; return; end; npc = t; end
            elseif instr.op == AVM.OP_PRIM
                arity = 2; if instr.arg == AVM.PRIM_NOT; arity = 1; end
                if length(s.stack) < arity; err = -4; return; end
                b = []; if arity >= 2; b = s.stack{end}; s.stack(end) = []; end
                a = s.stack{end}; s.stack(end) = [];
                s.stack{end+1} = AVM.exec_prim(instr.arg, a, b);
            elseif instr.op == AVM.OP_HALT
                s.halted = true;
            end
            s.pc = npc;
        end

        function s = run(init, prog, fuel)
            if nargin < 3; fuel = 10000; end
            s = init;
            for i = 1:fuel
                if s.halted; return; end
                [s, err] = AVM.step(s, prog);
                if err; error(['AVM error: ' num2str(err)]); end
            end
        end
    end
end
