mirror of
https://github.com/allaunthefox/SilverSight.git
synced 2026-07-31 01:25:21 +00:00
231 lines
8.1 KiB
Julia
231 lines
8.1 KiB
Julia
"""
|
|
AVM ISA v1 — Julia Port (Strict Functional Execution)
|
|
"""
|
|
module AVM
|
|
|
|
using ..Q16_16
|
|
|
|
export Prim, Instr, State, step, run, prim_add, q16_val
|
|
|
|
# ── Primitives ──────────────────────────────────────────────────────
|
|
|
|
@enum Prim begin
|
|
ADD_SAT_Q0
|
|
SUB_SAT_Q0
|
|
ADD_SAT_Q16
|
|
SUB_SAT_Q16
|
|
MUL_SAT_Q16
|
|
DIV_SAT_Q16
|
|
LT_Q16
|
|
EQ_Q16
|
|
AND_OP
|
|
OR_OP
|
|
NOT_OP
|
|
end
|
|
|
|
const prim_add = ADD_SAT_Q16
|
|
const prim_sub = SUB_SAT_Q16
|
|
const prim_mul = MUL_SAT_Q16
|
|
const prim_div = DIV_SAT_Q16
|
|
const prim_lt = LT_Q16
|
|
const prim_eq = EQ_Q16
|
|
|
|
# ── Instructions ────────────────────────────────────────────────────
|
|
|
|
struct Instr
|
|
op::Int8 # 0=push_val, 1=push_q16, 2=pop, 3=dup, 4=swap,
|
|
# 5=load, 6=store, 7=jump, 8=jump_if, 9=prim, 10=halt
|
|
arg::Int32 # payload: Q16_16 raw, local index, jump target, or Prim
|
|
arg2::Bool # for push_val: Bool value
|
|
end
|
|
|
|
# Constructors
|
|
push_q16(x::Integer) = Instr(1, Int32(x), false)
|
|
push_val(x::Bool) = Instr(0, 0, x)
|
|
pop() = Instr(2, 0, false)
|
|
dup() = Instr(3, 0, false)
|
|
swap() = Instr(4, 0, false)
|
|
load(i::Integer) = Instr(5, i, false)
|
|
store(i::Integer) = Instr(6, i, false)
|
|
jump(t::Integer) = Instr(7, t, false)
|
|
jump_if(t::Integer) = Instr(8, t, false)
|
|
prim(p::Prim) = Instr(9, Int32(p), false)
|
|
halt() = Instr(10, 0, false)
|
|
|
|
# ── Step Error ─────────────────────────────────────────────────────
|
|
|
|
@enum StepError begin
|
|
EMPTY_STACK
|
|
STACK_UNDERFLOW
|
|
TYPE_MISMATCH
|
|
MISSING_LOCAL
|
|
DIVISION_BY_ZERO
|
|
JUMP_OUT_OF_BOUNDS
|
|
STACK_OVERFLOW
|
|
HALTED
|
|
UNKNOWN_INSTR
|
|
end
|
|
|
|
# ── AVM constants ───────────────────────────────────────────────────
|
|
const AVM_CLAMP_MIN = -2147483647
|
|
const AVM_CLAMP_MAX = 2147483647
|
|
const AVM_Q0_MIN = -32767
|
|
const AVM_Q0_MAX = 32767
|
|
const AVM_MAX_STACK = 1024
|
|
|
|
# Type tags: 0=Bool, 1=Q16_16, 2=Q0_16
|
|
const TYPE_BOOL = Int8(0)
|
|
const TYPE_Q16 = Int8(1)
|
|
const TYPE_Q0 = Int8(2)
|
|
|
|
# ── State ──────────────────────────────────────────────────────────
|
|
|
|
struct State
|
|
pc::Int
|
|
stack::Vector{Union{Int32, Bool}}
|
|
types::Vector{Int8} # 0=Bool, 1=Q16_16, 2=Q0_16
|
|
locals::Vector{Union{Nothing, Union{Int32, Bool}}}
|
|
local_types::Vector{Int8}
|
|
halted::Bool
|
|
end
|
|
|
|
function State(n_locals::Int=0)
|
|
State(1, Union{Int32, Bool}[], Int8[],
|
|
[nothing for _ in 1:n_locals], zeros(Int8, n_locals),
|
|
false)
|
|
end
|
|
|
|
q16_val(s::State, i::Int) = s.stack[i]::Int32
|
|
bool_val(s::State, i::Int) = s.stack[i]::Bool
|
|
q16_val(v::Int32) = v
|
|
|
|
# ── Primitive execution ────────────────────────────────────────────
|
|
|
|
avm_clamp(x::Int64) = Int32(min(AVM_CLAMP_MAX, max(AVM_CLAMP_MIN, x)))
|
|
avm_q0_clamp(x::Int64) = Int32(min(AVM_Q0_MAX, max(AVM_Q0_MIN, x)))
|
|
|
|
lt_q16_v6(a::Int32, b::Int32) = (a < 0) != (b < 0) ? (a < 0) : (a < b)
|
|
|
|
function exec_prim(op::Prim, a::Union{Int32, Bool}, b::Union{Int32, Bool, Nothing})
|
|
if op == ADD_SAT_Q0
|
|
a::Int32; b::Int32
|
|
return (avm_q0_clamp(Int64(a) + Int64(b)), TYPE_Q0)
|
|
elseif op == SUB_SAT_Q0
|
|
a::Int32; b::Int32
|
|
return (avm_q0_clamp(Int64(a) - Int64(b)), TYPE_Q0)
|
|
elseif op == ADD_SAT_Q16
|
|
return (avm_clamp(Int64(a::Int32) + Int64(b::Int32)), TYPE_Q16)
|
|
elseif op == SUB_SAT_Q16
|
|
return (avm_clamp(Int64(a::Int32) - Int64(b::Int32)), TYPE_Q16)
|
|
elseif op == MUL_SAT_Q16
|
|
prod = Int64(a::Int32) * Int64(b::Int32)
|
|
result = div(prod, Q16_16.Q16_SCALE)
|
|
return (avm_clamp(result), TYPE_Q16)
|
|
elseif op == DIV_SAT_Q16
|
|
b::Int32 == 0 && error(DIVISION_BY_ZERO)
|
|
num = Int64(a::Int32) * Q16_16.Q16_SCALE
|
|
result = div(num, Int64(b::Int32))
|
|
return (avm_clamp(result), TYPE_Q16)
|
|
elseif op == LT_Q16
|
|
return (lt_q16_v6(a::Int32, b::Int32), TYPE_BOOL)
|
|
elseif op == EQ_Q16
|
|
return (a::Int32 == b::Int32, TYPE_BOOL)
|
|
elseif op == AND_OP
|
|
return (a::Bool && b::Bool, TYPE_BOOL)
|
|
elseif op == OR_OP
|
|
return (a::Bool || b::Bool, TYPE_BOOL)
|
|
elseif op == NOT_OP
|
|
return (!a::Bool, TYPE_BOOL)
|
|
else
|
|
error(TYPE_MISMATCH)
|
|
end
|
|
end
|
|
|
|
# ── Step ───────────────────────────────────────────────────────────
|
|
|
|
function step(s::State, program::Vector{Instr})::State
|
|
s.halted && error(HALTED)
|
|
s.pc < 1 && return State(s.pc, copy(s.stack), copy(s.types),
|
|
copy(s.locals), copy(s.local_types), true)
|
|
s.pc > length(program) && return State(s.pc, copy(s.stack), copy(s.types),
|
|
copy(s.locals), copy(s.local_types), true)
|
|
|
|
instr = program[s.pc]
|
|
stack = copy(s.stack)
|
|
types = copy(s.types)
|
|
locals = copy(s.locals)
|
|
local_types = copy(s.local_types)
|
|
pc = s.pc + 1
|
|
halted = false
|
|
|
|
length(stack) >= AVM_MAX_STACK && (instr.op in (0, 1, 3, 5)) && error(STACK_OVERFLOW)
|
|
|
|
if instr.op == 0 # push_val (Bool)
|
|
push!(stack, instr.arg2)
|
|
push!(types, TYPE_BOOL)
|
|
elseif instr.op == 1 # push_q16
|
|
push!(stack, instr.arg)
|
|
push!(types, TYPE_Q16)
|
|
elseif instr.op == 2 # pop
|
|
isempty(stack) && error(EMPTY_STACK)
|
|
pop!(stack); pop!(types)
|
|
elseif instr.op == 3 # dup
|
|
isempty(stack) && error(EMPTY_STACK)
|
|
push!(stack, stack[end]); push!(types, types[end])
|
|
elseif instr.op == 4 # swap
|
|
length(stack) < 2 && error(STACK_UNDERFLOW)
|
|
a = pop!(stack); ta = pop!(types)
|
|
b = pop!(stack); tb = pop!(types)
|
|
push!(stack, a); push!(types, ta)
|
|
push!(stack, b); push!(types, tb)
|
|
elseif instr.op == 5 # load
|
|
i = Int(instr.arg) + 1
|
|
i > length(locals) && error(MISSING_LOCAL)
|
|
locals[i] === nothing && error(MISSING_LOCAL)
|
|
push!(stack, locals[i]); push!(types, local_types[i])
|
|
elseif instr.op == 6 # store
|
|
i = Int(instr.arg) + 1
|
|
isempty(stack) && error(EMPTY_STACK)
|
|
i > length(locals) && error(MISSING_LOCAL)
|
|
locals[i] = pop!(stack); local_types[i] = pop!(types)
|
|
elseif instr.op == 7 # jump
|
|
target = Int(instr.arg) + 1
|
|
(target < 1 || target > length(program)) && error(JUMP_OUT_OF_BOUNDS)
|
|
pc = target
|
|
elseif instr.op == 8 # jump_if
|
|
isempty(stack) && error(EMPTY_STACK)
|
|
cond = pop!(stack)::Bool; pop!(types)
|
|
if cond
|
|
target = Int(instr.arg) + 1
|
|
(target < 1 || target > length(program)) && error(JUMP_OUT_OF_BOUNDS)
|
|
pc = target
|
|
end
|
|
elseif instr.op == 9 # prim
|
|
p = Prim(instr.arg)
|
|
arity = (p == NOT_OP) ? 1 : 2
|
|
length(stack) < arity && error(STACK_UNDERFLOW)
|
|
b = arity >= 2 ? pop!(stack) : nothing; if arity >= 2; pop!(types); end
|
|
a = pop!(stack); pop!(types)
|
|
result, type_tag = exec_prim(p, a, b)
|
|
push!(stack, result); push!(types, type_tag)
|
|
elseif instr.op == 10 # halt
|
|
halted = true
|
|
else
|
|
error(UNKNOWN_INSTR)
|
|
end
|
|
|
|
State(pc, stack, types, locals, local_types, halted)
|
|
end
|
|
|
|
# ── Run (fuel-bounded) ────────────────────────────────────────────
|
|
|
|
function run(initial::State, program::Vector{Instr}, fuel::Int)::State
|
|
state = initial
|
|
for _ in 1:fuel
|
|
state.halted && return state
|
|
state = step(state, program)
|
|
end
|
|
state
|
|
end
|
|
|
|
end # module AVM
|