kestrel/lib/Machine.ml

308 lines
10 KiB
OCaml

(* Unoptimized Uxn virtual machine. *)
open Effect
type stack = Stack of { data : bytes; mutable sp : int }
type mode = Mode of { short : bool; keep : bool; mutable temp : int }
let stack_create () =
let data = Bytes.create 256 in
Bytes.unsafe_fill data 0 256 '\x00';
Stack { data; sp = 0 }
let peek (Mode { short; keep; temp }) (Stack { data; sp }) : int =
let amt = if short then 2 else 1 in
let sp = if keep then (temp - amt) land 0xff else (sp - amt) land 0xff in
if short then Util.get_uint16_wrap data sp else Bytes.get_uint8 data sp
[@@inline]
let pop (Mode m as m' : mode) (Stack s' as s : stack) =
let res = peek m' s in
let amt = if m.short then 2 else 1 in
if m.keep then m.temp <- (m.temp - amt) land 0xff
else s'.sp <- (s'.sp - amt) land 0xff;
res
[@@inline]
let push (Mode ({ short; keep; _ } as m) : mode) (Stack s : stack) (v : int) =
if short then Util.set_uint16_wrap s.data s.sp (v land 0xffff)
else Bytes.set_uint8 s.data s.sp (v land 0xff);
let amt = if m.short then 2 else 1 in
if keep then m.temp <- (m.temp + amt) land 0xff;
s.sp <- (s.sp + amt) land 0xff
[@@inline]
let pushbyte (Mode m) s v =
let m' = Mode { m with short = false } in
push m' s v;
let (Mode { temp; _ }) = m' in
m.temp <- temp
[@@inline]
let popbyte (Mode m) s =
let m' = Mode { m with short = false } in
let r = pop m' s in
let (Mode { temp; _ }) = m' in
m.temp <- temp;
r
[@@inline]
let popshort (Mode m) s =
let m' = Mode { m with short = true } in
let r = pop m' s in
let (Mode { temp; _ }) = m' in
m.temp <- temp;
r
[@@inline]
let pop1 s = pop (Mode { short = false; keep = false; temp = 0 }) s [@@inline]
let push1 s v = push (Mode { short = false; keep = false; temp = 0 }) s v
[@@inline]
let push2 s v = push (Mode { short = true; keep = false; temp = 0 }) s v
[@@inline]
type machine =
| Machine of { data : bytes; dev : bytes; stack : stack; callstack : stack }
type _ Effect.t +=
| BRK : int Effect.t
| DEI : ([ `Byte | `Short ] * int) -> int Effect.t
| DEO : (int * int) -> unit Effect.t
| Trace : (int * Instr.t * int list) -> unit Effect.t
let ram (Machine { data; _ }) = data
let dev (Machine { dev; _ }) = dev
let wst (Machine { stack; _ }) = stack
let rst (Machine { callstack; _ }) = callstack
let create code =
let data = Bytes.create 65536 in
let dev = Bytes.create 256 in
Bytes.unsafe_fill data 0 65536 '\x00';
Bytes.unsafe_fill dev 0 256 '\x00';
Bytes.blit_string code 0 data 0x100 (String.length code);
Machine { data; dev; stack = stack_create (); callstack = stack_create () }
let dispatch ?(trace = false) (Machine m) (pc : int) =
let pc = ref pc in
while true do
pc := !pc land 0xffff;
let op = Bytes.get_uint8 m.data !pc in
let instr = Instr.of_int op in
let trace l = if trace then perform (Trace (!pc, instr, l)) in
pc := (!pc + 1) land 0xffff;
match op with
| 0x00 -> pc := perform BRK
| 0x20 (* JCI *) ->
let cond = pop1 m.stack in
let addr = Util.get_int16_wrap ~wrap:0xffff m.data !pc in
trace [ Bytes.get_uint16_be m.data !pc; cond ];
if cond != 0 then pc := !pc + addr + 2 else pc := !pc + 2
| 0x40 (* JMI *) ->
let addr = Util.get_int16_wrap ~wrap:0xffff m.data !pc in
trace [ Bytes.get_uint16_be m.data !pc ];
pc := !pc + addr + 2
| 0x60 (* JSI *) ->
let addr = Util.get_int16_wrap ~wrap:0xffff m.data !pc in
trace [ Bytes.get_uint16_be m.data !pc ];
push2 m.callstack (!pc + 2);
pc := !pc + addr + 2
| 0x80 (* LIT *) ->
let lit = Bytes.get_uint8 m.data !pc in
trace [ lit ];
push1 m.stack lit;
pc := !pc + 1
| 0xa0 (* LIT2 *) ->
let lit = Util.get_uint16_wrap ~wrap:0xffff m.data !pc in
trace [ lit ];
push2 m.stack lit;
pc := !pc + 2
| 0xc0 (* LITr *) ->
let lit = Bytes.get_uint8 m.data !pc in
trace [ lit ];
push1 m.callstack lit;
pc := !pc + 1
| 0xe0 (* LIT2r *) ->
let lit = Util.get_uint16_wrap ~wrap:0xffff m.data !pc in
trace [ lit ];
push2 m.callstack lit;
pc := !pc + 2
| _ -> begin
let (Instruction { short; keep; return; opcode }) = Instr.of_int op in
let stk = if return then m.callstack else m.stack in
let stk' = if return then m.stack else m.callstack in
let mode =
Mode { short; keep; temp = (match stk with Stack { sp; _ } -> sp) }
in
match[@warning "-8"] opcode with
| 0x01 (* INC *) ->
let r = pop mode stk in
trace [ r ];
push mode stk (r + 1)
| 0x02 (* POP *) -> trace [ pop mode stk ]
| 0x03 (* NIP *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk b
| 0x04 (* SWP *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk b;
push mode stk a
| 0x05 (* ROT *) ->
let c = pop mode stk in
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b; c ];
push mode stk b;
push mode stk c;
push mode stk a
| 0x06 (* DUP *) ->
let a = pop mode stk in
trace [ a ];
push mode stk a;
push mode stk a
| 0x07 (* OVR *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk a;
push mode stk b;
push mode stk a
| 0x08 (* EQU *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
pushbyte mode stk (if a = b then 1 else 0)
| 0x09 (* NEQ *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
pushbyte mode stk (if a != b then 1 else 0)
| 0x0a (* GTH *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
pushbyte mode stk (if a > b then 1 else 0)
| 0x0b (* LTH *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
pushbyte mode stk (if a < b then 1 else 0)
| 0x0c (* JMP *) ->
let addr = pop mode stk in
trace [ addr ];
if short then pc := addr else pc := !pc + Util.uint8_to_int8 addr
| 0x0d (* JCN *) ->
let addr = pop mode stk in
let cond = popbyte mode stk in
trace [ cond; addr ];
if cond != 0 then
if short then pc := addr else pc := !pc + Util.uint8_to_int8 addr
| 0x0e (* JSR *) ->
push2 m.callstack !pc;
let addr = pop mode stk in
trace [ addr ];
if short then pc := addr else pc := !pc + Util.uint8_to_int8 addr
| 0x0f (* STH *) ->
let a = pop mode stk in
trace [ a ];
push mode stk' a
| 0x10 (* LDZ *) ->
let addr = popbyte mode stk in
trace [ addr ];
push mode stk
(if short then Util.get_uint16_wrap m.data addr
else Bytes.get_uint8 m.data addr)
| 0x11 (* STZ *) ->
let addr = popbyte mode stk in
let v = pop mode stk in
trace [ v; addr ];
if short then Util.set_uint16_wrap m.data addr v
else Bytes.set_uint8 m.data addr v
| 0x12 (* LDR *) ->
let addr = !pc + Util.uint8_to_int8 (popbyte mode stk) in
trace [ addr ];
push mode stk
(if short then Util.get_uint16_wrap ~wrap:0xffff m.data addr
else Bytes.get_uint8 m.data addr)
| 0x13 (* STR *) ->
let addr = !pc + Util.uint8_to_int8 (popbyte mode stk) in
let v = pop mode stk in
trace [ v; addr ];
if short then Util.set_uint16_wrap ~wrap:0xffff m.data addr v
else Bytes.set_uint8 m.data addr v
| 0x14 (* LDA *) ->
let addr = popshort mode stk in
trace [ addr ];
push mode stk
(if short then Util.get_uint16_wrap ~wrap:0xffff m.data addr
else Bytes.get_uint8 m.data addr)
| 0x15 (* STA *) ->
let addr = popshort mode stk in
let v = pop mode stk in
trace [ v; addr ];
if short then Util.set_uint16_wrap ~wrap:0xffff m.data addr v
else Bytes.set_uint8 m.data addr v
| 0x16 (* DEI *) ->
let port = popbyte mode stk in
trace [ port ];
push mode stk
(perform (DEI ((if short then `Short else `Byte), port)))
| 0x17 (* DEO *) ->
let port = popbyte mode stk in
let value = pop mode stk in
trace [ value; port ];
if short then Util.set_uint16_wrap m.dev port value
else Bytes.set_uint8 m.dev port value;
perform (DEO (port, value))
| 0x18 (* ADD *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk (a + b)
| 0x19 (* SUB *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk (a - b)
| 0x1a (* MUL *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk (a * b)
| 0x1b (* DIV *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk (if b = 0 then 0 else a / b)
| 0x1c (* AND *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk (a land b)
| 0x1d (* ORA *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk (a lor b)
| 0x1e (* EOR *) ->
let b = pop mode stk in
let a = pop mode stk in
trace [ a; b ];
push mode stk (a lxor b)
| 0x1f (* SFT *) ->
let sft = popbyte mode stk in
let value = pop mode stk in
trace [ value; sft ];
push mode stk ((value lsr (sft land 0xf)) lsl sft lsr 4)
end
done