Compiler/virtual machine interpreter: Difference between revisions

Content added Content deleted
Line 607: Line 607:
end
end
end.</lang>
end.</lang>

=={{header|ATS}}==
<lang ats>(*

The Rosetta Code virtual machine task in ATS2 (also known as
Postiats).
Some implementation notes:

* Values are stored as uint32, and it is checked that uint32
really is 32 bits, two’s-complement. Addition and subtraction
are allowed to roll around, and so can be done without casting
to int32. (The C standard specifies that unsigned integer values
will roll around, rather than signal an overflow.)

* Where it matters, the uint32 are stored in little-endian
order. I have *not* optimized the code for x86/AMD64 (which are
little-endian and also can address unaligned data).

* Here I am often writing out code instead of using some library
function. Partly this is to improve code safety (proof at
compile-time that buffers are not overrun, proof of loop
termination, etc.). Partly this is because I do not feel like
using the C library (or ATS interfaces to it) all that much.

* I am using linear types and so forth, because I think it
interesting to do so. It is unnecessary to use a garbage
collector, because there are no memory leaks. (Not that we
couldn’t simply let memory leak, for this little program with no
REPL.)

*)

#define ATS_EXTERN_PREFIX "rosettacode_vm_"
#define ATS_DYNLOADFLAG 0 (* No initialization is needed. *)

#include "share/atspre_define.hats"
#include "share/atspre_staload.hats"

staload UN = "prelude/SATS/unsafe.sats"

#define NIL list_vt_nil ()
#define :: list_vt_cons

(* The stack has a fixed size but is very large. (Alternatively, one
could make the stack double in size whenever it overflows. Design
options such as using a linked list for the stack come with a
performance penalty.) *)
#define VMSTACK_SIZE 65536
macdef vmstack_size = (i2sz VMSTACK_SIZE)

(* In this program, exceptions are not meant to be caught, unless
the catcher terminates the program. Linear types and
general exception-catching do not go together well. *)
exception bad_vm of string
exception vm_runtime_error of string

(********************************************************************)
(* *)
(* Some string functions that are safe against buffer overruns. *)
(* *)

fn
skip_whitespace {n, i : int | 0 <= i; i <= n}
(s : string n,
n : size_t n,
i : size_t i) :
[j : int | i <= j; j <= n]
size_t j =
let
fun
loop {k : int | i <= k; k <= n} .<n - k>.
(k : size_t k) :
[j : int | i <= j; j <= n]
size_t j =
if k = n then
k
else if isspace (s[k]) then
loop (succ k)
else
k
in
loop (i)
end

fn
skip_non_whitespace {n, i : int | 0 <= i; i <= n}
(s : string n,
n : size_t n,
i : size_t i) :
[j : int | i <= j; j <= n]
size_t j =
let
fun
loop {k : int | i <= k; k <= n} .<n - k>.
(k : size_t k) :
[j : int | i <= j; j <= n]
size_t j =
if k = n then
k
else if isspace (s[k]) then
k
else
loop (succ k)
in
loop (i)
end

fn
substr_equal {n, i, j : int | 0 <= i; i <= j; j <= n}
{m : int | 0 <= m}
(s : string n,
i : size_t i,
j : size_t j,
t : string m) : bool =
(* Is s[i .. j-1] equal to t? *)
let
val m = string_length t
in
if m <> j - i then
false
else
let
fun
loop {k : int | 0 <= k; k <= m} .<m - k>.
(k : size_t k) : bool =
if k = m then
true
else if s[i + k] <> t[k] then
false
else
loop (succ k)
in
loop (i2sz 0)
end
end

(********************************************************************)
(* *)
(* vmint = 32-bit two’s-complement numbers. *)
(* *)

stadef vmint_kind = uint32_kind
typedef vmint = uint32

extern castfn i2vm : int -<> vmint
extern castfn u2vm : uint -<> vmint
extern castfn byte2vm : byte -<> vmint

extern castfn vm2i : vmint -<> int
extern castfn vm2sz : vmint -<> size_t
extern castfn vm2byte : vmint -<> byte

%{^

/*
* The ATS prelude might not have C implementations of all the
* operations we would like to have, so here are some.
*/

typedef uint32_t vmint_t;

ATSinline() vmint_t
rosettacode_vm_g0uint_add_vmint (vmint_t x, vmint_t y)
{
return (x + y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_sub_vmint (vmint_t x, vmint_t y)
{
return (x - y);
}

ATSinline() int
rosettacode_vm_g0uint_eq_vmint (vmint_t x, vmint_t y)
{
return (x == y);
}

typedef uint32_t vmint_t;

ATSinline() int
rosettacode_vm_g0uint_neq_vmint (vmint_t x, vmint_t y)
{
return (x != y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_equality_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) (x == y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_inequality_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) (x != y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_signed_lt_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) ((int32_t) x < (int32_t) y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_signed_gt_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) ((int32_t) x > (int32_t) y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_signed_lte_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) ((int32_t) x <= (int32_t) y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_signed_gte_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) ((int32_t) x >= (int32_t) y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_signed_mul_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) ((int32_t) x * (int32_t) y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_signed_div_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) ((int32_t) x / (int32_t) y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_signed_mod_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) ((int32_t) x % (int32_t) y);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_logical_not_vmint (vmint_t x)
{
return (vmint_t) (!x);
}

ATSinline() vmint_t
rosettacode_vm_g0uint_logical_and_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) ((!!x) * (!!y));
}

ATSinline() vmint_t
rosettacode_vm_g0uint_logical_or_vmint (vmint_t x, vmint_t y)
{
return (vmint_t) (1 - ((!x) * (!y)));
}

%}

extern fn g0uint_add_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn g0uint_sub_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn g0uint_eq_vmint (x : vmint, y : vmint) :<> bool = "mac#%"
extern fn g0uint_neq_vmint (x : vmint, y : vmint) :<> bool = "mac#%"

implement g0uint_add<vmint_kind> (x, y) = g0uint_add_vmint (x, y)
implement g0uint_sub<vmint_kind> (x, y) = g0uint_sub_vmint (x, y)
implement g0uint_eq<vmint_kind> (x, y) = g0uint_eq_vmint (x, y)
implement g0uint_neq<vmint_kind> (x, y) = g0uint_neq_vmint (x, y)

extern fn
g0uint_signed_mul_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_signed_div_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_signed_mod_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_equality_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_inequality_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_signed_lt_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_signed_gt_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_signed_lte_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_signed_gte_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_logical_not_vmint (x : vmint) :<> vmint = "mac#%"
extern fn
g0uint_logical_and_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"
extern fn
g0uint_logical_or_vmint (x : vmint, y : vmint) :<> vmint = "mac#%"

overload signed_mul with g0uint_signed_mul_vmint
overload signed_div with g0uint_signed_div_vmint
overload signed_mod with g0uint_signed_mod_vmint
overload equality with g0uint_equality_vmint
overload inequality with g0uint_inequality_vmint
overload signed_lt with g0uint_signed_lt_vmint
overload signed_gt with g0uint_signed_gt_vmint
overload signed_lte with g0uint_signed_lte_vmint
overload signed_gte with g0uint_signed_gte_vmint
overload logical_not with g0uint_logical_not_vmint
overload logical_and with g0uint_logical_and_vmint
overload logical_or with g0uint_logical_or_vmint

fn {}
twos_complement (x : vmint) :<>
vmint =
(~x) + i2vm 1

fn
ensure_that_vmint_is_suitable () : void =
{
val _ = assertloc (u2vm (0xFFFFFFFFU) + u2vm 1U = u2vm 0U)
val _ = assertloc (u2vm 0U - u2vm 1U = u2vm (0xFFFFFFFFU))
val _ = assertloc (i2vm (~1234) = twos_complement (i2vm 1234))
}

fn
parse_digits {n, i, j : int | 0 <= i; i <= j; j <= n}
(s : string n,
i : size_t i,
j : size_t j) :
vmint =
let
val bad_integer = "Bad integer."
fun
loop {k : int | i <= k; k <= j} .<j - k>.
(k : size_t k,
x : vmint) : vmint =
if k = j then
x
else if ~isdigit (s[k]) then
$raise bad_vm (bad_integer)
else
(* The result is allowed to overflow freely. *)
loop (succ k, (i2vm 10 * x) + i2vm (char2i s[k] - char2i '0'))
in
if j = i then
$raise bad_vm (bad_integer)
else
loop (i, i2vm 0)
end

fn
parse_integer {n, i, j : int | 0 <= i; i <= j; j <= n}
(s : string n,
i : size_t i,
j : size_t j) :
vmint =
let
val bad_integer = "Bad integer."
in
if j = i then
$raise bad_vm (bad_integer)
else if j = succ i && ~isdigit (s[i]) then
$raise bad_vm (bad_integer)
else if s[i] <> '-' then
parse_digits (s, i, j)
else if succ i = j then
$raise bad_vm (bad_integer)
else
twos_complement (parse_digits (s, succ i, j))
end

(********************************************************************)
(* *)
(* A linear array type for elements of vmint, byte, etc. *)
(* *)

vtypedef vmarray_vt (t : t@ype+, n : int, p : addr) =
@{
pf = @[t][n] @ p,
pfgc = mfree_gc_v p |
n = size_t n,
p = ptr p
}
vtypedef vmarray_vt (t : t@ype+, n : int) =
[p : addr] vmarray_vt (t, n, p)

fn {t : t@ype}
vmarray_vt_alloc {n : int}
(n : size_t n,
fill : t) :
[p : addr | null < p]
vmarray_vt (t, n, p) =
let
val @(pf, pfgc | p) = array_ptr_alloc<t> (n)
val _ = array_initize_elt (!p, n, fill)
in
@{
pf = pf,
pfgc = pfgc |
n = n,
p = p
}
end

fn {t : t@ype}
vmarray_vt_free {n : int}
{p : addr}
(arr : vmarray_vt (t, n, p)) :
void =
let
val @{
pf = pf,
pfgc = pfgc |
n = n,
p = p
} = arr
in
array_ptr_free (pf, pfgc | p)
end

fn {t : t@ype}
vmarray_vt_fill {n : int}
{p : addr}
(arr : !vmarray_vt (t, n, p),
fill : t) :
void =
array_initize_elt (!(arr.p), (arr.n), fill)

fn {t : t@ype}
{tk : tkind}
vmarray_vt_get_at_g1int {n, i : int | 0 <= i; i < n}
(arr : !vmarray_vt (t, n),
i : g1int (tk, i)) :
t =
array_get_at (!(arr.p), i)

fn {t : t@ype}
{tk : tkind}
vmarray_vt_get_at_g1uint {n, i : int | 0 <= i; i < n}
(arr : !vmarray_vt (t, n),
i : g1uint (tk, i)) :
t =
array_get_at (!(arr.p), i)

overload [] with vmarray_vt_get_at_g1int
overload [] with vmarray_vt_get_at_g1uint

fn {t : t@ype}
{tk : tkind}
vmarray_vt_set_at_g1int {n, i : int | 0 <= i; i < n}
(arr : !vmarray_vt (t, n),
i : g1int (tk, i),
x : t) :
void =
array_set_at (!(arr.p), i, x)

fn {t : t@ype}
{tk : tkind}
vmarray_vt_set_at_g1uint {n, i : int | 0 <= i; i < n}
(arr : !vmarray_vt (t, n),
i : g1uint (tk, i),
x : t) :
void =
array_set_at (!(arr.p), i, x)

overload [] with vmarray_vt_set_at_g1int
overload [] with vmarray_vt_set_at_g1uint

fn {t : t@ype}
vmarray_vt_length {n : int}
(arr : !vmarray_vt (t, n)) :<>
size_t n =
arr.n

(********************************************************************)
(* *)
(* Storage for the strings section. *)
(* *)

vtypedef vmstring_vt (n : int, p : addr) =
@{
(* A vmstring_vt is NUL-terminated, and thus there is [n + 1]
instead of [n] in the following declaration. *)
pf = @[char][n + 1] @ p,
pfgc = mfree_gc_v p |
length = size_t n,
p = ptr p
}
vtypedef vmstring_vt (n : int) = [p : addr] vmstring_vt (n, p)
vtypedef vmstring_vt = [n : int | 0 <= n] vmstring_vt (n)

vtypedef vmstrings_section_vt (n : int, p : addr) =
@{
pf = @[vmstring_vt][n] @ p,
pfgc = mfree_gc_v p |
n = size_t n,
p = ptr p
}
vtypedef vmstrings_section_vt (n : int) =
[p : addr] vmstrings_section_vt (n, p)

fn {t : t@ype}
vmstrings_section_vt_length {n : int}
(arr : !vmstrings_section_vt (n)) :<>
size_t n =
arr.n

fn
vmstring_vt_free {n : int}
{p : addr}
(s : vmstring_vt (n, p)) :
void =
array_ptr_free (s.pf, s.pfgc | s.p)

fn
vmstrings_section_vt_free {n : int}
{p : addr}
(strings : vmstrings_section_vt (n, p)) :
void =
{
fun
free_the_strings {n : int | 0 <= n}
{p : addr} .<n>.
(pf : !(@[vmstring_vt][n] @ p) >>
@[vmstring_vt?][n] @ p |
n : size_t n,
p : ptr p) : void =
if n = 0 then
{
prval _ = pf :=
array_v_unnil_nil {vmstring_vt, vmstring_vt?} pf
}
else
{
prval @(pf_element, pf_rest) = array_v_uncons pf
val _ = vmstring_vt_free (!p)
val p_next = ptr_succ<vmstring_vt> (p)
val _ = free_the_strings (pf_rest | pred n, p_next)
prval _ = pf := array_v_cons (pf_element, pf_rest)
}

val @{
pf = pf,
pfgc = pfgc |
n = n,
p = p
} = strings
prval _ = lemma_g1uint_param n
val _ = free_the_strings (pf | n, p)
val _ = array_ptr_free (pf, pfgc | p)
}

fn
quoted_string_length {n : int | 0 <= n}
(s : string n,
n : size_t n) :
[m : int | 0 <= m; m <= n - 2]
size_t m =
let
val bad_quoted_string = "Bad quoted string."

fun
loop {i : int | 1 <= i; i <= n - 1}
{j : int | 0 <= j; j <= i - 1} .<n - i>.
(i : size_t i,
j : size_t j) :
[k : int | 0 <= k; k <= n - 2]
size_t k =
if i = pred n then
j
else if s[i] <> '\\' then
loop (succ i, succ j)
else if succ i = pred n then
$raise bad_vm (bad_quoted_string)
else if s[succ i] = 'n' || s[succ i] = '\\' then
loop (succ (succ i), succ j)
else
$raise bad_vm (bad_quoted_string)
in
if n < i2sz 2 then
$raise bad_vm (bad_quoted_string)
else if s[0] <> '"' then
$raise bad_vm (bad_quoted_string)
else if s[pred n] <> '"' then
$raise bad_vm (bad_quoted_string)
else
loop (i2sz 1, i2sz 0)
end

fn
dequote_string {m, n : int | 0 <= m; m <= n - 2}
(s : string n,
n : size_t n,
t : !vmstring_vt m) :
void =
let
fun
loop {i : int | 1 <= i; i <= n - 1}
{j : int | 0 <= j; j <= i - 1} .<n - i>.
(t : !vmstring_vt m,
i : size_t i,
j : size_t j) : void =
let
macdef t_str = !(t.p)
in
if i = pred n then
()
else if (t.length) < j then
assertloc (false)
else if s[i] <> '\\' then
begin
t_str[j] := s[i];
loop (t, succ i, succ j)
end
else if succ i = pred n then
assertloc (false)
else if s[succ i] = 'n' then
begin
t_str[j] := '\n';
loop (t, succ (succ i), succ j)
end
else
begin
t_str[j] := s[succ i];
loop (t, succ (succ i), succ j)
end
end
in
loop (t, i2sz 1, i2sz 0)
end

fn
read_vmstrings {strings_size : int}
{strings_addr : addr}
(pf_strings :
!(@[vmstring_vt?][strings_size] @ strings_addr) >>
@[vmstring_vt][strings_size] @ strings_addr |
f : FILEref,
strings_size : size_t strings_size,
strings : ptr strings_addr) :
void =
let
prval _ = lemma_g1uint_param strings_size

fun
loop {k : int | 0 <= k; k <= strings_size} .<strings_size - k>.
(lst : list_vt (vmstring_vt, k),
k : size_t k) :
list_vt (vmstring_vt, strings_size) =
if k = strings_size then
list_vt_reverse (lst)
else
let
val bad_quoted_string = "Bad quoted string."
val line = fileref_get_line_string (f)
val s = $UN.strptr2string (line)
val n = string_length s
val str_length = quoted_string_length (s, n)
val (pf, pfgc | p) =
array_ptr_alloc<char> (succ str_length)
val _ = array_initize_elt (!p, succ str_length, '\0')
val vmstring =
@{
pf = pf,
pfgc = pfgc |
length = str_length,
p = p
}
in
dequote_string (s, n, vmstring);
free line;
loop (vmstring :: lst, succ k)
end

val lst = loop (NIL, i2sz 0)
in
array_initize_list_vt<vmstring_vt>
(!strings, sz2i strings_size, lst)
end

fn
vmstrings_section_vt_read {strings_size : int}
(f : FILEref,
strings_size : size_t strings_size) :
[p : addr]
vmstrings_section_vt (strings_size, p) =
let
val @(pf, pfgc | p) = array_ptr_alloc<vmstring_vt> strings_size
val _ = read_vmstrings (pf | f, strings_size, p)
in
@{
pf = pf,
pfgc = pfgc |
n = strings_size,
p = p
}
end

fn
vmstring_fprint {n, i : int | i < n}
(f : FILEref,
strings : !vmstrings_section_vt n,
i : size_t i) :
void =
{

(*
* The following code does some ‘unsafe’ tricks. For instance, it
* is assumed each stored string is NUL-terminated.
*)

fn
print_it (str : !vmstring_vt) : void =
fileref_puts (f, $UN.cast{string} (str.p))

prval _ = lemma_g1uint_param i
val p_element = array_getref_at (!(strings.p), i)
val @(pf_element | p_element) =
$UN.castvwtp0
{[n : int; p : addr] @(vmstring_vt @ p | ptr p)}
(p_element)
val _ = print_it (!p_element)
prval _ = $UN.castview0{void} pf_element
}

(********************************************************************)
(* *)
(* vm_vt: the dataviewtype for a virtual machine. *)
(* *)

datatype instruction_t =
| instruction_t_1 of (byte)
| instruction_t_5 of (byte, byte, byte, byte, byte)

#define OPCODE_COUNT 24

#define OP_HALT 0x0000 // 00000
#define OP_ADD 0x0001 // 00001
#define OP_SUB 0x0002 // 00010
#define OP_MUL 0x0003 // 00011
#define OP_DIV 0x0004 // 00100
#define OP_MOD 0x0005 // 00101
#define OP_LT 0x0006 // 00110
#define OP_GT 0x0007 // 00111
#define OP_LE 0x0008 // 01000
#define OP_GE 0x0009 // 01001
#define OP_EQ 0x000A // 01010
#define OP_NE 0x000B // 01011
#define OP_AND 0x000C // 01100
#define OP_OR 0x000D // 01101
#define OP_NEG 0x000E // 01110
#define OP_NOT 0x000F // 01111
#define OP_PRTC 0x0010 // 10000
#define OP_PRTI 0x0011 // 10001
#define OP_PRTS 0x0012 // 10010
#define OP_FETCH 0x0013 // 10011
#define OP_STORE 0x0014 // 10100
#define OP_PUSH 0x0015 // 10101
#define OP_JMP 0x0016 // 10110
#define OP_JZ 0x0017 // 10111

#define REGISTER_PC 0
#define REGISTER_SP 1
#define MAX_REGISTER REGISTER_SP

vtypedef vm_vt (strings_size : int,
strings_addr : addr,
code_size : int,
code_addr : addr,
data_size : int,
data_addr : addr,
stack_size : int,
stack_addr : addr) =
@{
strings = vmstrings_section_vt (strings_size, strings_addr),
code = vmarray_vt (byte, code_size, code_addr),
data = vmarray_vt (vmint, data_size, data_addr),
stack = vmarray_vt (vmint, stack_size, stack_addr),
registers = vmarray_vt (vmint, MAX_REGISTER + 1)
}

vtypedef vm_vt (strings_size : int,
code_size : int,
data_size : int,
stack_size : int) =
[strings_addr : addr]
[code_addr : addr]
[data_addr : addr]
[stack_addr : addr]
vm_vt (strings_size, strings_addr,
code_size, code_addr,
data_size, data_addr,
stack_size, stack_addr)

vtypedef vm_vt =
[strings_size : int]
[code_size : int]
[data_size : int]
[stack_size : int]
vm_vt (strings_size, code_size, data_size, stack_size)

fn
vm_vt_free (vm : vm_vt) :
void =
let
val @{
strings = strings,
code = code,
data = data,
stack = stack,
registers = registers
} = vm
in
vmstrings_section_vt_free strings;
vmarray_vt_free<byte> code;
vmarray_vt_free<vmint> data;
vmarray_vt_free<vmint> stack;
vmarray_vt_free<vmint> registers
end

fn
opcode_name_to_byte {n, i, j : int | 0 <= i; i <= j; j <= n}
(arr : &(@[String0][OPCODE_COUNT]),
str : string n,
i : size_t i,
j : size_t j) :
byte =
let
fun
loop {k : int | 0 <= k; k <= OPCODE_COUNT} .<OPCODE_COUNT - k>.
(arr : &(@[String0][OPCODE_COUNT]),
k : int k) : byte =
if k = OPCODE_COUNT then
$raise bad_vm ("Unrecognized opcode name.")
else if substr_equal (str, i, j, arr[k]) then
i2byte k
else
loop (arr, succ k)
in
loop (arr, 0)
end

fn {}
vmint_byte0 (i : vmint) :<>
byte =
vm2byte (i land (u2vm 0xFFU))

fn {}
vmint_byte1 (i : vmint) :<>
byte =
vm2byte ((i >> 8) land (u2vm 0xFFU))

fn {}
vmint_byte2 (i : vmint) :<>
byte =
vm2byte ((i >> 16) land (u2vm 0xFFU))

fn {}
vmint_byte3 (i : vmint) :<>
byte =
vm2byte (i >> 24)

fn
parse_instruction {n : int | 0 <= n}
(arr : &(@[String0][OPCODE_COUNT]),
line : string n) :
instruction_t =
let
val bad_instruction = "Bad VM instruction."
val n = string_length (line)
val i = skip_whitespace (line, n, i2sz 0)

(* Skip the address field*)
val i = skip_non_whitespace (line, n, i)

val i = skip_whitespace (line, n, i)
val j = skip_non_whitespace (line, n, i)
val opcode = opcode_name_to_byte (arr, line, i, j)

val start_of_argument = j

fn
finish_push () :
instruction_t =
let
val i1 = skip_whitespace (line, n, start_of_argument)
val j1 = skip_non_whitespace (line, n, i1)
val arg = parse_integer (line, i1, j1)
in
(* Little-endian storage. *)
instruction_t_5 (opcode, vmint_byte0 arg, vmint_byte1 arg,
vmint_byte2 arg, vmint_byte3 arg)
end

fn
finish_fetch_or_store () :
instruction_t =
let
val i1 = skip_whitespace (line, n, start_of_argument)
val j1 = skip_non_whitespace (line, n, i1)
in
if j1 - i1 < i2sz 3 then
$raise bad_vm (bad_instruction)
else if line[i1] <> '\[' || line[pred j1] <> ']' then
$raise bad_vm (bad_instruction)
else
let
val arg = parse_integer (line, succ i1, pred j1)
in
(* Little-endian storage. *)
instruction_t_5 (opcode, vmint_byte0 arg, vmint_byte1 arg,
vmint_byte2 arg, vmint_byte3 arg)
end
end

fn
finish_jmp_or_jz () :
instruction_t =
let
val i1 = skip_whitespace (line, n, start_of_argument)
val j1 = skip_non_whitespace (line, n, i1)
in
if j1 - i1 < i2sz 3 then
$raise bad_vm (bad_instruction)
else if line[i1] <> '\(' || line[pred j1] <> ')' then
$raise bad_vm (bad_instruction)
else
let
val arg = parse_integer (line, succ i1, pred j1)
in
(* Little-endian storage. *)
instruction_t_5 (opcode, vmint_byte0 arg, vmint_byte1 arg,
vmint_byte2 arg, vmint_byte3 arg)
end
end
in
case+ byte2int0 opcode of
| OP_PUSH => finish_push ()
| OP_FETCH => finish_fetch_or_store ()
| OP_STORE => finish_fetch_or_store ()
| OP_JMP => finish_jmp_or_jz ()
| OP_JZ => finish_jmp_or_jz ()
| _ => instruction_t_1 (opcode)
end

fn
read_instructions (f : FILEref,
arr : &(@[String0][OPCODE_COUNT])) :
(List_vt (instruction_t), Size_t) =
(* Read the instructions from the input, producing a list of
instruction_t objects, and also calculating the total
number of bytes in the instructions. *)
let
fun
loop (arr : &(@[String0][OPCODE_COUNT]),
lst : List_vt (instruction_t),
bytes_needed : Size_t) :
@(List_vt (instruction_t), Size_t) =
if fileref_is_eof f then
@(list_vt_reverse lst, bytes_needed)
else
let
val line = fileref_get_line_string (f)
in
if fileref_is_eof f then
begin
free line;
@(list_vt_reverse lst, bytes_needed)
end
else
let
val instruction =
parse_instruction (arr, $UN.strptr2string line)
val _ = free line
prval _ = lemma_list_vt_param lst
in
case+ instruction of
| instruction_t_1 _ =>
loop (arr, instruction :: lst, bytes_needed + i2sz 1)
| instruction_t_5 _ =>
loop (arr, instruction :: lst, bytes_needed + i2sz 5)
end
end
in
loop (arr, NIL, i2sz 0)
end

fn
list_of_instructions_to_code {bytes_needed : int}
(lst : List_vt (instruction_t),
bytes_needed : size_t bytes_needed) :
[bytes_needed : int]
vmarray_vt (byte, bytes_needed) =
(* This routine consumes and destroys lst. *)
let
fun
loop {n : int | 0 <= n} .<n>.
(code : &vmarray_vt (byte, bytes_needed),
lst : list_vt (instruction_t, n),
i : Size_t) : void =
case+ lst of
| ~ NIL => ()
| ~ (instruction_t_1 (byte1) :: tail) =>
let
val _ = assertloc (i < bytes_needed)
in
code[i] := byte1;
loop (code, tail, i + i2sz 1)
end
| ~ (instruction_t_5 (byte1, byte2, byte3, byte4, byte5)
:: tail) =>
let
val _ = assertloc (i + i2sz 4 < bytes_needed)
in
code[i] := byte1;
code[i + i2sz 1] := byte2;
code[i + i2sz 2] := byte3;
code[i + i2sz 3] := byte4;
code[i + i2sz 4] := byte5;
loop (code, tail, i + i2sz 5)
end

var code = vmarray_vt_alloc<byte> (bytes_needed, i2byte OP_HALT)

prval _ = lemma_list_vt_param lst
prval _ = lemma_g1uint_param bytes_needed
val _ = loop (code, lst, i2sz 0)
in
code
end

fn
read_and_parse_code (f : FILEref,
arr : &(@[String0][OPCODE_COUNT])) :
[bytes_needed : int]
vmarray_vt (byte, bytes_needed) =
let
val @(instructions, bytes_needed) = read_instructions (f, arr)
in
list_of_instructions_to_code (instructions, bytes_needed)
end

fn
parse_header_line {n : int | 0 <= n}
(line : string n) :
@(vmint, vmint) =
let
val bad_vm_header_line = "Bad VM header line."
val n = string_length (line)
val i = skip_whitespace (line, n, i2sz 0)
val j = skip_non_whitespace (line, n, i)
val _ = if ~substr_equal (line, i, j, "Datasize:") then
$raise bad_vm (bad_vm_header_line)
val i = skip_whitespace (line, n, j)
val j = skip_non_whitespace (line, n, i)
val data_size = parse_integer (line, i, j)
val i = skip_whitespace (line, n, j)
val j = skip_non_whitespace (line, n, i)
val _ = if ~substr_equal (line, i, j, "Strings:") then
$raise bad_vm (bad_vm_header_line)
val i = skip_whitespace (line, n, j)
val j = skip_non_whitespace (line, n, i)
val strings_size = parse_integer (line, i, j)
in
@(data_size, strings_size)
end

fn
read_vm (f : FILEref,
opcode_names_arr : &(@[String0][OPCODE_COUNT])) :
vm_vt =
let
val line = fileref_get_line_string (f)

val @(data_size, strings_size) =
parse_header_line ($UN.strptr2string line)

val _ = free line

val [data_size : int] data_size =
g1ofg0 (vm2sz data_size)
val [strings_size : int] strings_size =
g1ofg0 (vm2sz strings_size)

prval _ = lemma_g1uint_param data_size
prval _ = lemma_g1uint_param strings_size

prval _ = prop_verify {0 <= data_size} ()
prval _ = prop_verify {0 <= strings_size} ()

val strings = vmstrings_section_vt_read (f, strings_size)
val code = read_and_parse_code (f, opcode_names_arr)
val data = vmarray_vt_alloc<vmint> (data_size, i2vm 0)
val stack = vmarray_vt_alloc<vmint> (vmstack_size, i2vm 0)
val registers = vmarray_vt_alloc<vmint> (i2sz (MAX_REGISTER + 1),
i2vm 0)
in
@{
strings = strings,
code = code,
data = data,
stack = stack,
registers = registers
}
end

fn {}
pop (vm : &vm_vt) :
vmint =
let
macdef registers = vm.registers
macdef stack = vm.stack
val sp_before = registers[REGISTER_SP]
in
if sp_before = i2vm 0 then
$raise vm_runtime_error ("Stack underflow.")
else
let
val sp_after = sp_before - i2vm 1
val _ = registers[REGISTER_SP] := sp_after
val i = g1ofg0 (vm2sz sp_after)

(* What follows is a runtime assertion that the upper stack
boundary is not gone past, even though it certainly will
not. This is necessary (assuming one does not use something
such as $UN.prop_assert) because the stack pointer is a
vmint, whose bounds cannot be proven at compile time.

If you comment out the assertloc, the program will not pass
typechecking.

Compilers for many other languages will just insert such
checks willy-nilly, leading programmers to turn off such
instrumentation in the very code they provide to users.

One might be tempted to use Size_t instead for the stack
pointer, but what if the instruction set were later
augmented with ways to read from or write into the stack
pointer? *)
val _ = assertloc (i < vmarray_vt_length stack)
in
stack[i]
end
end

fn {}
push (vm : &vm_vt,
x : vmint) :
void =
let
macdef registers = vm.registers
macdef stack = vm.stack
val sp_before = registers[REGISTER_SP]
val i = g1ofg0 (vm2sz sp_before)
in
if vmarray_vt_length stack <= i then
$raise vm_runtime_error ("Stack overflow.")
else
let
val sp_after = sp_before + i2vm 1
in
registers[REGISTER_SP] := sp_after;
stack[i] := x
end
end

fn {}
fetch_data (vm : &vm_vt,
index : vmint) :
vmint =
let
macdef data = vm.data
val i = g1ofg0 (vm2sz index)
in
if vmarray_vt_length data <= i then
$raise vm_runtime_error ("Fetch from outside the data section.")
else
data[i]
end

fn {}
store_data (vm : &vm_vt,
index : vmint,
x : vmint) :
void =
let
macdef data = vm.data
val i = g1ofg0 (vm2sz index)
in
if vmarray_vt_length data <= i then
$raise vm_runtime_error ("Store to outside the data section.")
else
data[i] := x
end

fn {}
get_argument (vm : &vm_vt) :
vmint =
let
macdef code = vm.code
macdef registers = vm.registers
val pc = registers[REGISTER_PC]
val i = g1ofg0 (vm2sz pc)
in
if vmarray_vt_length code <= i + i2sz 4 then
$raise (vm_runtime_error
("The program counter is out of bounds."))
else
let
(* The data is stored little-endian. *)
val byte0 = byte2vm code[i]
val byte1 = byte2vm code[i + i2sz 1]
val byte2 = byte2vm code[i + i2sz 2]
val byte3 = byte2vm code[i + i2sz 3]
in
(byte0) lor (byte1 << 8) lor (byte2 << 16) lor (byte3 << 24)
end
end

fn {}
skip_argument (vm : &vm_vt) :
void =
let
macdef registers = vm.registers
val pc = registers[REGISTER_PC]
in
registers[REGISTER_PC] := pc + i2vm 4
end

extern fun {}
unary_operation$inner : vmint -<> vmint
fn {}
unary_operation (vm : &vm_vt) :
void =
let
macdef registers = vm.registers
macdef stack = vm.stack
val sp = registers[REGISTER_SP]
val i = g1ofg0 (vm2sz (sp))
prval _ = lemma_g1uint_param i
in
if i = i2sz 0 then
$raise vm_runtime_error ("Stack underflow.")
else
let
val _ = assertloc (i < vmarray_vt_length stack)

(* The actual unary operation is inserted here during
template expansion. *)
val result = unary_operation$inner<> (stack[i - 1])
in
stack[i - 1] := result
end
end

extern fun {}
binary_operation$inner : (vmint, vmint) -<> vmint
fn {}
binary_operation (vm : &vm_vt) :
void =
let
macdef registers = vm.registers
macdef stack = vm.stack
val sp_before = registers[REGISTER_SP]
val i = g1ofg0 (vm2sz (sp_before))
prval _ = lemma_g1uint_param i
in
if i <= i2sz 1 then
$raise vm_runtime_error ("Stack underflow.")
else
let
val _ = registers[REGISTER_SP] := sp_before - i2vm 1
val _ = assertloc (i < vmarray_vt_length stack)

(* The actual binary operation is inserted here during
template expansion. *)
val result =
binary_operation$inner<> (stack[i - 2], stack[i - 1])
in
stack[i - 2] := result
end
end

fn {}
uop_neg (vm : &vm_vt) :
void =
let
implement {}
unary_operation$inner (x) =
twos_complement x
in
unary_operation (vm)
end

fn {}
uop_not (vm : &vm_vt) :
void =
let
implement {}
unary_operation$inner (x) =
logical_not x
in
unary_operation (vm)
end

fn {}
binop_add (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x + y
in
binary_operation (vm)
end

fn {}
binop_sub (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x - y
in
binary_operation (vm)
end

fn {}
binop_mul (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \signed_mul y
in
binary_operation (vm)
end

fn {}
binop_div (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \signed_div y
in
binary_operation (vm)
end

fn {}
binop_mod (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \signed_mod y
in
binary_operation (vm)
end

fn {}
binop_eq (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \equality y
in
binary_operation (vm)
end

fn {}
binop_ne (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \inequality y
in
binary_operation (vm)
end

fn {}
binop_lt (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \signed_lt y
in
binary_operation (vm)
end

fn {}
binop_gt (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \signed_gt y
in
binary_operation (vm)
end

fn {}
binop_le (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \signed_lte y
in
binary_operation (vm)
end

fn {}
binop_ge (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \signed_gte y
in
binary_operation (vm)
end

fn {}
binop_and (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \logical_and y
in
binary_operation (vm)
end

fn {}
binop_or (vm : &vm_vt) :
void =
let
implement {}
binary_operation$inner (x, y) =
x \logical_or y
in
binary_operation (vm)
end

fn {}
do_push (vm : &vm_vt) :
void =
let
val arg = get_argument (vm)
in
push (vm, arg);
skip_argument (vm)
end

fn {}
do_fetch (vm : &vm_vt) :
void =
let
val i = get_argument (vm)
val x = fetch_data (vm, i)
in
push (vm, x);
skip_argument (vm)
end

fn {}
do_store (vm : &vm_vt) :
void =
let
val i = get_argument (vm)
val x = pop (vm)
in
store_data (vm, i, x);
skip_argument (vm)
end

fn {}
do_jmp (vm : &vm_vt) :
void =
let
macdef registers = vm.registers
val arg = get_argument (vm)
val pc = registers[REGISTER_PC]
in
registers[REGISTER_PC] := pc + arg
end

fn {}
do_jz (vm : &vm_vt) :
void =
let
val x = pop (vm)
in
if x = i2vm 0 then
do_jmp (vm)
else
skip_argument (vm)
end

fn {}
do_prtc (f_output : FILEref,
vm : &vm_vt) :
void =
let
val x = pop (vm)
in
fileref_putc (f_output, vm2i x)
end

fn {}
do_prti (f_output : FILEref,
vm : &vm_vt) :
void =
let
val x = pop (vm)
in
fprint! (f_output, vm2i x)
end

fn {}
do_prts (f_output : FILEref,
vm : &vm_vt) :
void =
let
val i = g1ofg0 (vm2sz (pop (vm)))
in
if vmstrings_section_vt_length (vm.strings) <= i then
$raise vm_runtime_error ("String index out of bounds.")
else
vmstring_fprint (f_output, vm.strings, i)
end

fn
vm_step (f_output : FILEref,
vm : &vm_vt,
machine_halt : &bool,
bad_opcode : &bool) :
void =
let
macdef code = vm.code
macdef registers = vm.registers

val pc = registers[REGISTER_PC]

val i = g1ofg0 (vm2sz (pc))
prval _ = lemma_g1uint_param i
in
if vmarray_vt_length (code) <= i then
$raise (vm_runtime_error
("The program counter is out of bounds."))
else
let
val _ = registers[REGISTER_PC] := pc + i2vm 1

val opcode = code[i]
val u_opcode = byte2uint0 opcode
in
(* Dispatch by bifurcation on the bit pattern of the
opcode. This method is logarithmic in the number
of opcode values. *)
machine_halt := false;
bad_opcode := false;
if (u_opcode land (~0x1FU)) = 0U then
begin
if (u_opcode land 0x10U) = 0U then
begin
if (u_opcode land 0x08U) = 0U then
begin
if (u_opcode land 0x04U) = 0U then
begin
if (u_opcode land 0x02U) = 0U then
begin
if (u_opcode land 0x01U) = 0U then
(* OP_HALT *)
machine_halt := true
else
binop_add (vm)
end
else
begin
if (u_opcode land 0x01U) = 0U then
binop_sub (vm)
else
binop_mul (vm)
end
end
else
begin
if (u_opcode land 0x02U) = 0U then
begin
if (u_opcode land 0x01U) = 0U then
binop_div (vm)
else
binop_mod (vm)
end
else
begin
if (u_opcode land 0x01U) = 0U then
binop_lt (vm)
else
binop_gt (vm)
end
end
end
else
begin
if (u_opcode land 0x04U) = 0U then
begin
if (u_opcode land 0x02U) = 0U then
begin
if (u_opcode land 0x01U) = 0U then
binop_le (vm)
else
binop_ge (vm)
end
else
begin
if (u_opcode land 0x01U) = 0U then
binop_eq (vm)
else
binop_ne (vm)
end
end
else
begin
if (u_opcode land 0x02U) = 0U then
begin
if (u_opcode land 0x01U) = 0U then
binop_and (vm)
else
binop_or (vm)
end
else
begin
if (u_opcode land 0x01U) = 0U then
uop_neg (vm)
else
uop_not (vm)
end
end
end
end
else
begin
if (u_opcode land 0x08U) = 0U then
begin
if (u_opcode land 0x04U) = 0U then
begin
if (u_opcode land 0x02U) = 0U then
begin
if (u_opcode land 0x01U) = 0U then
do_prtc (f_output, vm)
else
do_prti (f_output, vm)
end
else
begin
if (u_opcode land 0x01U) = 0U then
do_prts (f_output, vm)
else
do_fetch (vm)
end
end
else
begin
if (u_opcode land 0x02U) = 0U then
begin
if (u_opcode land 0x01U) = 0U then
do_store (vm)
else
do_push (vm)
end
else
begin
if (u_opcode land 0x01U) = 0U then
do_jmp (vm)
else
do_jz (vm)
end
end
end
else
bad_opcode := true
end
end
else
bad_opcode := true
end
end

fn
vm_continue (f_output : FILEref,
vm : &vm_vt) :
void =
let
fun
loop (vm : &vm_vt,
machine_halt : &bool,
bad_opcode : &bool) : void =
if ~machine_halt && ~bad_opcode then
begin
vm_step (f_output, vm, machine_halt, bad_opcode);
loop (vm, machine_halt, bad_opcode)
end

var machine_halt : bool = false
var bad_opcode : bool = false
in
loop (vm, machine_halt, bad_opcode);
if bad_opcode then
$raise vm_runtime_error ("Unrecognized opcode at runtime.")
end

fn
vm_initialize (vm : &vm_vt) :
void =
let
macdef data = vm.data
macdef registers = vm.registers
in
vmarray_vt_fill (data, i2vm 0);
registers[REGISTER_PC] := i2vm 0;
registers[REGISTER_SP] := i2vm 0
end


fn
vm_run (f_output : FILEref,
vm : &vm_vt) :
void =
begin
vm_initialize (vm);
vm_continue (f_output, vm)
end

(********************************************************************)

implement
main0 (argc, argv) =
{
(* The following order must match that established by
OP_HALT, OP_ADD, OP_SUB, etc. *)
var opcode_order =
@[String0][OPCODE_COUNT] ("halt", // 00000 bit pattern
"add", // 00001
"sub", // 00010
"mul", // 00011
"div", // 00100
"mod", // 00101
"lt", // 00110
"gt", // 00111
"le", // 01000
"ge", // 01001
"eq", // 01010
"ne", // 01011
"and", // 01100
"or", // 01101
"neg", // 01110
"not", // 01111
"prtc", // 10000
"prti", // 10001
"prts", // 10010
"fetch", // 10011
"store", // 10100
"push", // 10101
"jmp", // 10110
"jz") // 10111

val _ = ensure_that_vmint_is_suitable ()
var vm = read_vm (stdin_ref, opcode_order)
val _ = vm_run (stdout_ref, vm)
val _ = vm_vt_free vm
}

(********************************************************************)</lang>


=={{header|AWK}}==
=={{header|AWK}}==