blob: 18ff7150df5a58e0736bd1e48e820a773b2eb4fe [file] [log] [blame] [edit]
open Values
open Types
open Instance
open Ast
open Source
(* Errors *)
module Link = Error.Make ()
module Trap = Error.Make ()
module Crash = Error.Make ()
module Exhaustion = Error.Make ()
exception Link = Link.Error
exception Trap = Trap.Error
exception Crash = Crash.Error (* failure that cannot happen in valid code *)
exception Exhaustion = Exhaustion.Error
let table_error at = function
| Table.Bounds -> "out of bounds table access"
| Table.SizeOverflow -> "table size overflow"
| Table.SizeLimit -> "table size limit reached"
| Table.Type -> Crash.error at "type mismatch at table access"
| exn -> raise exn
let memory_error at = function
| Memory.Bounds -> "out of bounds memory access"
| Memory.SizeOverflow -> "memory size overflow"
| Memory.SizeLimit -> "memory size limit reached"
| Memory.Type -> Crash.error at "type mismatch at memory access"
| exn -> raise exn
let numeric_error at = function
| Ixx.Overflow -> "integer overflow"
| Ixx.DivideByZero -> "integer divide by zero"
| Ixx.InvalidConversion -> "invalid conversion to integer"
| Values.TypeError (i, v, t) ->
Crash.error at
("type error, expected " ^ Types.string_of_num_type t ^ " as operand " ^
string_of_int i ^ ", got " ^ Types.string_of_num_type (type_of_num v))
| exn -> raise exn
(* Administrative Expressions & Configurations *)
type 'a stack = 'a list
type frame =
{
inst : module_inst;
locals : value ref list;
}
type code = value stack * admin_instr list
and admin_instr = admin_instr' phrase
and admin_instr' =
| Plain of instr'
| Refer of ref_
| Invoke of func_inst
| Trapping of string
| Returning of value stack
| Breaking of int32 * value stack
| Label of int32 * instr list * code
| Frame of int32 * frame * code
type config =
{
frame : frame;
code : code;
budget : int; (* to model stack overflow *)
}
let frame inst locals = {inst; locals}
let config inst vs es =
{frame = frame inst []; code = vs, es; budget = !Flags.budget}
let plain e = Plain e.it @@ e.at
let lookup category list x =
try Lib.List32.nth list x.it with Failure _ ->
Crash.error x.at ("undefined " ^ category ^ " " ^ Int32.to_string x.it)
let type_ (inst : module_inst) x = lookup "type" inst.types x
let func (inst : module_inst) x = lookup "function" inst.funcs x
let table (inst : module_inst) x = lookup "table" inst.tables x
let memory (inst : module_inst) x = lookup "memory" inst.memories x
let global (inst : module_inst) x = lookup "global" inst.globals x
let elem (inst : module_inst) x = lookup "element segment" inst.elems x
let data (inst : module_inst) x = lookup "data segment" inst.datas x
let local (frame : frame) x = lookup "local" frame.locals x
let any_ref inst x i at =
try Table.load (table inst x) i with Table.Bounds ->
Trap.error at ("undefined element " ^ Int32.to_string i)
let func_ref inst x i at =
match any_ref inst x i at with
| FuncRef f -> f
| NullRef _ -> Trap.error at ("uninitialized element " ^ Int32.to_string i)
| _ -> Crash.error at ("type mismatch for element " ^ Int32.to_string i)
let func_type_of = function
| Func.AstFunc (t, inst, f) -> t
| Func.HostFunc (t, _) -> t
let block_type inst bt =
match bt with
| VarBlockType x -> type_ inst x
| ValBlockType None -> FuncType ([], [])
| ValBlockType (Some t) -> FuncType ([], [t])
let take n (vs : 'a stack) at =
try Lib.List32.take n vs with Failure _ -> Crash.error at "stack underflow"
let drop n (vs : 'a stack) at =
try Lib.List32.drop n vs with Failure _ -> Crash.error at "stack underflow"
(* Evaluation *)
(*
* Conventions:
* e : instr
* v : value
* es : instr list
* vs : value stack
* c : config
*)
let mem_oob frame x i n =
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
(Memory.bound (memory frame.inst x))
let data_oob frame x i n =
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
(Data.size (data frame.inst x))
let table_oob frame x i n =
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
(I64_convert.extend_i32_u (Table.size (table frame.inst x)))
let elem_oob frame x i n =
I64.gt_u (I64.add (I64_convert.extend_i32_u i) (I64_convert.extend_i32_u n))
(I64_convert.extend_i32_u (Elem.size (elem frame.inst x)))
let rec step (c : config) : config =
let {frame; code = vs, es; _} = c in
let e = List.hd es in
let vs', es' =
match e.it, vs with
| Plain e', vs ->
(match e', vs with
| Unreachable, vs ->
vs, [Trapping "unreachable executed" @@ e.at]
| Nop, vs ->
vs, []
| Block (bt, es'), vs ->
let FuncType (ts1, ts2) = block_type frame.inst bt in
let n1 = Lib.List32.length ts1 in
let n2 = Lib.List32.length ts2 in
let args, vs' = take n1 vs e.at, drop n1 vs e.at in
vs', [Label (n2, [], (args, List.map plain es')) @@ e.at]
| Loop (bt, es'), vs ->
let FuncType (ts1, ts2) = block_type frame.inst bt in
let n1 = Lib.List32.length ts1 in
let args, vs' = take n1 vs e.at, drop n1 vs e.at in
vs', [Label (n1, [e' @@ e.at], (args, List.map plain es')) @@ e.at]
| If (bt, es1, es2), Num (I32 i) :: vs' ->
if i = 0l then
vs', [Plain (Block (bt, es2)) @@ e.at]
else
vs', [Plain (Block (bt, es1)) @@ e.at]
| Br x, vs ->
[], [Breaking (x.it, vs) @@ e.at]
| BrIf x, Num (I32 i) :: vs' ->
if i = 0l then
vs', []
else
vs', [Plain (Br x) @@ e.at]
| BrTable (xs, x), Num (I32 i) :: vs' ->
if I32.ge_u i (Lib.List32.length xs) then
vs', [Plain (Br x) @@ e.at]
else
vs', [Plain (Br (Lib.List32.nth xs i)) @@ e.at]
| Return, vs ->
[], [Returning vs @@ e.at]
| Call x, vs ->
vs, [Invoke (func frame.inst x) @@ e.at]
| CallIndirect (x, y), Num (I32 i) :: vs ->
let func = func_ref frame.inst x i e.at in
if type_ frame.inst y <> Func.type_of func then
vs, [Trapping "indirect call type mismatch" @@ e.at]
else
vs, [Invoke func @@ e.at]
| Drop, v :: vs' ->
vs', []
| Select _, Num (I32 i) :: v2 :: v1 :: vs' ->
if i = 0l then
v2 :: vs', []
else
v1 :: vs', []
| LocalGet x, vs ->
!(local frame x) :: vs, []
| LocalSet x, v :: vs' ->
local frame x := v;
vs', []
| LocalTee x, v :: vs' ->
local frame x := v;
v :: vs', []
| GlobalGet x, vs ->
Global.load (global frame.inst x) :: vs, []
| GlobalSet x, v :: vs' ->
(try Global.store (global frame.inst x) v; vs', []
with Global.NotMutable -> Crash.error e.at "write to immutable global"
| Global.Type -> Crash.error e.at "type mismatch at global write")
| TableGet x, Num (I32 i) :: vs' ->
(try Ref (Table.load (table frame.inst x) i) :: vs', []
with exn -> vs', [Trapping (table_error e.at exn) @@ e.at])
| TableSet x, Ref r :: Num (I32 i) :: vs' ->
(try Table.store (table frame.inst x) i r; vs', []
with exn -> vs', [Trapping (table_error e.at exn) @@ e.at])
| TableSize x, vs ->
Num (I32 (Table.size (table frame.inst x))) :: vs, []
| TableGrow x, Num (I32 delta) :: Ref r :: vs' ->
let tab = table frame.inst x in
let old_size = Table.size tab in
let result =
try Table.grow tab delta r; old_size
with Table.SizeOverflow | Table.SizeLimit | Table.OutOfMemory -> -1l
in Num (I32 result) :: vs', []
| TableFill x, Num (I32 n) :: Ref r :: Num (I32 i) :: vs' ->
if table_oob frame x i n then
vs', [Trapping (table_error e.at Table.Bounds) @@ e.at]
else if n = 0l then
vs', []
else
let _ = assert (I32.lt_u i 0xffff_ffffl) in
vs', List.map (at e.at) [
Plain (Const (I32 i @@ e.at));
Refer r;
Plain (TableSet x);
Plain (Const (I32 (I32.add i 1l) @@ e.at));
Refer r;
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
Plain (TableFill x);
]
| TableCopy (x, y), Num (I32 n) :: Num (I32 s) :: Num (I32 d) :: vs' ->
if table_oob frame x d n || table_oob frame y s n then
vs', [Trapping (table_error e.at Table.Bounds) @@ e.at]
else if n = 0l then
vs', []
else if I32.le_u d s then
vs', List.map (at e.at) [
Plain (Const (I32 d @@ e.at));
Plain (Const (I32 s @@ e.at));
Plain (TableGet y);
Plain (TableSet x);
Plain (Const (I32 (I32.add d 1l) @@ e.at));
Plain (Const (I32 (I32.add s 1l) @@ e.at));
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
Plain (TableCopy (x, y));
]
else (* d > s *)
vs', List.map (at e.at) [
Plain (Const (I32 (I32.add d 1l) @@ e.at));
Plain (Const (I32 (I32.add s 1l) @@ e.at));
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
Plain (TableCopy (x, y));
Plain (Const (I32 d @@ e.at));
Plain (Const (I32 s @@ e.at));
Plain (TableGet y);
Plain (TableSet x);
]
| TableInit (x, y), Num (I32 n) :: Num (I32 s) :: Num (I32 d) :: vs' ->
if table_oob frame x d n || elem_oob frame y s n then
vs', [Trapping (table_error e.at Table.Bounds) @@ e.at]
else if n = 0l then
vs', []
else
let seg = elem frame.inst y in
vs', List.map (at e.at) [
Plain (Const (I32 d @@ e.at));
Refer (Elem.load seg s);
Plain (TableSet x);
Plain (Const (I32 (I32.add d 1l) @@ e.at));
Plain (Const (I32 (I32.add s 1l) @@ e.at));
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
Plain (TableInit (x, y));
]
| ElemDrop x, vs ->
let seg = elem frame.inst x in
Elem.drop seg;
vs, []
| Load {offset; ty; pack; _}, Num (I32 i) :: vs' ->
let mem = memory frame.inst (0l @@ e.at) in
let a = I64_convert.extend_i32_u i in
(try
let n =
match pack with
| None -> Memory.load_num mem a offset ty
| Some (sz, ext) -> Memory.load_num_packed sz ext mem a offset ty
in Num n :: vs', []
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at])
| Store {offset; pack; _}, Num n :: Num (I32 i) :: vs' ->
let mem = memory frame.inst (0l @@ e.at) in
let a = I64_convert.extend_i32_u i in
(try
(match pack with
| None -> Memory.store_num mem a offset n
| Some sz -> Memory.store_num_packed sz mem a offset n
);
vs', []
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]);
| VecLoad {offset; ty; pack; _}, Num (I32 i) :: vs' ->
let mem = memory frame.inst (0l @@ e.at) in
let addr = I64_convert.extend_i32_u i in
(try
let v =
match pack with
| None -> Memory.load_vec mem addr offset ty
| Some (sz, ext) ->
Memory.load_vec_packed sz ext mem addr offset ty
in Vec v :: vs', []
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at])
| VecStore {offset; _}, Vec v :: Num (I32 i) :: vs' ->
let mem = memory frame.inst (0l @@ e.at) in
let addr = I64_convert.extend_i32_u i in
(try
Memory.store_vec mem addr offset v;
vs', []
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at]);
| VecLoadLane ({offset; ty; pack; _}, j), Vec (V128 v) :: Num (I32 i) :: vs' ->
let mem = memory frame.inst (0l @@ e.at) in
let addr = I64_convert.extend_i32_u i in
(try
let v =
match pack with
| Pack8 ->
V128.I8x16.replace_lane j v
(I32Num.of_num 0 (Memory.load_num_packed Pack8 SX mem addr offset I32Type))
| Pack16 ->
V128.I16x8.replace_lane j v
(I32Num.of_num 0 (Memory.load_num_packed Pack16 SX mem addr offset I32Type))
| Pack32 ->
V128.I32x4.replace_lane j v
(I32Num.of_num 0 (Memory.load_num mem addr offset I32Type))
| Pack64 ->
V128.I64x2.replace_lane j v
(I64Num.of_num 0 (Memory.load_num mem addr offset I64Type))
in Vec (V128 v) :: vs', []
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at])
| VecStoreLane ({offset; ty; pack; _}, j), Vec (V128 v) :: Num (I32 i) :: vs' ->
let mem = memory frame.inst (0l @@ e.at) in
let addr = I64_convert.extend_i32_u i in
(try
(match pack with
| Pack8 ->
Memory.store_num_packed Pack8 mem addr offset (I32 (V128.I8x16.extract_lane_s j v))
| Pack16 ->
Memory.store_num_packed Pack16 mem addr offset (I32 (V128.I16x8.extract_lane_s j v))
| Pack32 ->
Memory.store_num mem addr offset (I32 (V128.I32x4.extract_lane_s j v))
| Pack64 ->
Memory.store_num mem addr offset (I64 (V128.I64x2.extract_lane_s j v))
);
vs', []
with exn -> vs', [Trapping (memory_error e.at exn) @@ e.at])
| MemorySize, vs ->
let mem = memory frame.inst (0l @@ e.at) in
Num (I32 (Memory.size mem)) :: vs, []
| MemoryGrow, Num (I32 delta) :: vs' ->
let mem = memory frame.inst (0l @@ e.at) in
let old_size = Memory.size mem in
let result =
try Memory.grow mem delta; old_size
with Memory.SizeOverflow | Memory.SizeLimit | Memory.OutOfMemory -> -1l
in Num (I32 result) :: vs', []
| MemoryFill, Num (I32 n) :: Num k :: Num (I32 i) :: vs' ->
if mem_oob frame (0l @@ e.at) i n then
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
else if n = 0l then
vs', []
else
vs', List.map (at e.at) [
Plain (Const (I32 i @@ e.at));
Plain (Const (k @@ e.at));
Plain (Store
{ty = I32Type; align = 0; offset = 0l; pack = Some Pack8});
Plain (Const (I32 (I32.add i 1l) @@ e.at));
Plain (Const (k @@ e.at));
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
Plain (MemoryFill);
]
| MemoryCopy, Num (I32 n) :: Num (I32 s) :: Num (I32 d) :: vs' ->
if mem_oob frame (0l @@ e.at) s n || mem_oob frame (0l @@ e.at) d n then
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
else if n = 0l then
vs', []
else if I32.le_u d s then
vs', List.map (at e.at) [
Plain (Const (I32 d @@ e.at));
Plain (Const (I32 s @@ e.at));
Plain (Load
{ty = I32Type; align = 0; offset = 0l; pack = Some (Pack8, ZX)});
Plain (Store
{ty = I32Type; align = 0; offset = 0l; pack = Some Pack8});
Plain (Const (I32 (I32.add d 1l) @@ e.at));
Plain (Const (I32 (I32.add s 1l) @@ e.at));
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
Plain (MemoryCopy);
]
else (* d > s *)
vs', List.map (at e.at) [
Plain (Const (I32 (I32.add d 1l) @@ e.at));
Plain (Const (I32 (I32.add s 1l) @@ e.at));
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
Plain (MemoryCopy);
Plain (Const (I32 d @@ e.at));
Plain (Const (I32 s @@ e.at));
Plain (Load
{ty = I32Type; align = 0; offset = 0l; pack = Some (Pack8, ZX)});
Plain (Store
{ty = I32Type; align = 0; offset = 0l; pack = Some Pack8});
]
| MemoryInit x, Num (I32 n) :: Num (I32 s) :: Num (I32 d) :: vs' ->
if mem_oob frame (0l @@ e.at) d n || data_oob frame x s n then
vs', [Trapping (memory_error e.at Memory.Bounds) @@ e.at]
else if n = 0l then
vs', []
else
let seg = data frame.inst x in
let a = I64_convert.extend_i32_u s in
let b = Data.load seg a in
vs', List.map (at e.at) [
Plain (Const (I32 d @@ e.at));
Plain (Const (I32 (I32.of_int_u (Char.code b)) @@ e.at));
Plain (Store
{ty = I32Type; align = 0; offset = 0l; pack = Some Pack8});
Plain (Const (I32 (I32.add d 1l) @@ e.at));
Plain (Const (I32 (I32.add s 1l) @@ e.at));
Plain (Const (I32 (I32.sub n 1l) @@ e.at));
Plain (MemoryInit x);
]
| DataDrop x, vs ->
let seg = data frame.inst x in
Data.drop seg;
vs, []
| RefNull t, vs' ->
Ref (NullRef t) :: vs', []
| RefIsNull, Ref r :: vs' ->
(match r with
| NullRef _ ->
Num (I32 1l) :: vs', []
| _ ->
Num (I32 0l) :: vs', []
)
| RefFunc x, vs' ->
let f = func frame.inst x in
Ref (FuncRef f) :: vs', []
| Const n, vs ->
Num n.it :: vs, []
| Test testop, Num n :: vs' ->
(try value_of_bool (Eval_num.eval_testop testop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| Compare relop, Num n2 :: Num n1 :: vs' ->
(try value_of_bool (Eval_num.eval_relop relop n1 n2) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| Unary unop, Num n :: vs' ->
(try Num (Eval_num.eval_unop unop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| Binary binop, Num n2 :: Num n1 :: vs' ->
(try Num (Eval_num.eval_binop binop n1 n2) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| Convert cvtop, Num n :: vs' ->
(try Num (Eval_num.eval_cvtop cvtop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecConst v, vs ->
Vec v.it :: vs, []
| VecTest testop, Vec n :: vs' ->
(try value_of_bool (Eval_vec.eval_testop testop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecUnary unop, Vec n :: vs' ->
(try Vec (Eval_vec.eval_unop unop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecBinary binop, Vec n2 :: Vec n1 :: vs' ->
(try Vec (Eval_vec.eval_binop binop n1 n2) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecCompare relop, Vec n2 :: Vec n1 :: vs' ->
(try Vec (Eval_vec.eval_relop relop n1 n2) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecConvert cvtop, Vec n :: vs' ->
(try Vec (Eval_vec.eval_cvtop cvtop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecShift shiftop, Num s :: Vec v :: vs' ->
(try Vec (Eval_vec.eval_shiftop shiftop v s) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecBitmask bitmaskop, Vec v :: vs' ->
(try Num (Eval_vec.eval_bitmaskop bitmaskop v) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecTestBits vtestop, Vec n :: vs' ->
(try value_of_bool (Eval_vec.eval_vtestop vtestop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecUnaryBits vunop, Vec n :: vs' ->
(try Vec (Eval_vec.eval_vunop vunop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecBinaryBits vbinop, Vec n2 :: Vec n1 :: vs' ->
(try Vec (Eval_vec.eval_vbinop vbinop n1 n2) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecTernaryBits vternop, Vec v3 :: Vec v2 :: Vec v1 :: vs' ->
(try Vec (Eval_vec.eval_vternop vternop v1 v2 v3) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecSplat splatop, Num n :: vs' ->
(try Vec (Eval_vec.eval_splatop splatop n) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecExtract extractop, Vec v :: vs' ->
(try Num (Eval_vec.eval_extractop extractop v) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| VecReplace replaceop, Num r :: Vec v :: vs' ->
(try Vec (Eval_vec.eval_replaceop replaceop v r) :: vs', []
with exn -> vs', [Trapping (numeric_error e.at exn) @@ e.at])
| _ ->
let s1 = string_of_values (List.rev vs) in
let s2 = string_of_value_types (List.map type_of_value (List.rev vs)) in
Crash.error e.at
("missing or ill-typed operand on stack (" ^ s1 ^ " : " ^ s2 ^ ")")
)
| Refer r, vs ->
Ref r :: vs, []
| Trapping msg, vs ->
assert false
| Returning vs', vs ->
Crash.error e.at "undefined frame"
| Breaking (k, vs'), vs ->
Crash.error e.at "undefined label"
| Label (n, es0, (vs', [])), vs ->
vs' @ vs, []
| Label (n, es0, (vs', {it = Trapping msg; at} :: es')), vs ->
vs, [Trapping msg @@ at]
| Label (n, es0, (vs', {it = Returning vs0; at} :: es')), vs ->
vs, [Returning vs0 @@ at]
| Label (n, es0, (vs', {it = Breaking (0l, vs0); at} :: es')), vs ->
take n vs0 e.at @ vs, List.map plain es0
| Label (n, es0, (vs', {it = Breaking (k, vs0); at} :: es')), vs ->
vs, [Breaking (Int32.sub k 1l, vs0) @@ at]
| Label (n, es0, code'), vs ->
let c' = step {c with code = code'} in
vs, [Label (n, es0, c'.code) @@ e.at]
| Frame (n, frame', (vs', [])), vs ->
vs' @ vs, []
| Frame (n, frame', (vs', {it = Trapping msg; at} :: es')), vs ->
vs, [Trapping msg @@ at]
| Frame (n, frame', (vs', {it = Returning vs0; at} :: es')), vs ->
take n vs0 e.at @ vs, []
| Frame (n, frame', code'), vs ->
let c' = step {frame = frame'; code = code'; budget = c.budget - 1} in
vs, [Frame (n, c'.frame, c'.code) @@ e.at]
| Invoke func, vs when c.budget = 0 ->
Exhaustion.error e.at "call stack exhausted"
| Invoke func, vs ->
let FuncType (ins, out) = func_type_of func in
let n1, n2 = Lib.List32.length ins, Lib.List32.length out in
let args, vs' = take n1 vs e.at, drop n1 vs e.at in
(match func with
| Func.AstFunc (t, inst', f) ->
let locals' = List.rev args @ List.map default_value f.it.locals in
let frame' = {inst = !inst'; locals = List.map ref locals'} in
let instr' = [Label (n2, [], ([], List.map plain f.it.body)) @@ f.at] in
vs', [Frame (n2, frame', ([], instr')) @@ e.at]
| Func.HostFunc (t, f) ->
try List.rev (f (List.rev args)) @ vs', []
with Crash (_, msg) -> Crash.error e.at msg
)
in {c with code = vs', es' @ List.tl es}
let rec eval (c : config) : value stack =
match c.code with
| vs, [] ->
vs
| vs, {it = Trapping msg; at} :: _ ->
Trap.error at msg
| vs, es ->
eval (step c)
(* Functions & Constants *)
let invoke (func : func_inst) (vs : value list) : value list =
let at = match func with Func.AstFunc (_, _, f) -> f.at | _ -> no_region in
let FuncType (ins, out) = Func.type_of func in
if List.length vs <> List.length ins then
Crash.error at "wrong number of arguments";
if not (List.for_all2 (fun v -> (=) (type_of_value v)) vs ins) then
Crash.error at "wrong types of arguments";
let c = config empty_module_inst (List.rev vs) [Invoke func @@ at] in
try List.rev (eval c) with Stack_overflow ->
Exhaustion.error at "call stack exhausted"
let eval_const (inst : module_inst) (const : const) : value =
let c = config inst [] (List.map plain const.it) in
match eval c with
| [v] -> v
| vs -> Crash.error const.at "wrong number of results on stack"
(* Modules *)
let create_func (inst : module_inst) (f : func) : func_inst =
Func.alloc (type_ inst f.it.ftype) (ref inst) f
let create_table (inst : module_inst) (tab : table) : table_inst =
let {ttype} = tab.it in
let TableType (_lim, t) = ttype in
Table.alloc ttype (NullRef t)
let create_memory (inst : module_inst) (mem : memory) : memory_inst =
let {mtype} = mem.it in
Memory.alloc mtype
let create_global (inst : module_inst) (glob : global) : global_inst =
let {gtype; ginit} = glob.it in
let v = eval_const inst ginit in
Global.alloc gtype v
let create_export (inst : module_inst) (ex : export) : export_inst =
let {name; edesc} = ex.it in
let ext =
match edesc.it with
| FuncExport x -> ExternFunc (func inst x)
| TableExport x -> ExternTable (table inst x)
| MemoryExport x -> ExternMemory (memory inst x)
| GlobalExport x -> ExternGlobal (global inst x)
in (name, ext)
let create_elem (inst : module_inst) (seg : elem_segment) : elem_inst =
let {etype; einit; _} = seg.it in
Elem.alloc (List.map (fun c -> as_ref (eval_const inst c)) einit)
let create_data (inst : module_inst) (seg : data_segment) : data_inst =
let {dinit; _} = seg.it in
Data.alloc dinit
let add_import (m : module_) (ext : extern) (im : import) (inst : module_inst)
: module_inst =
if not (match_extern_type (extern_type_of ext) (import_type m im)) then
Link.error im.at ("incompatible import type for " ^
"\"" ^ Utf8.encode im.it.module_name ^ "\" " ^
"\"" ^ Utf8.encode im.it.item_name ^ "\": " ^
"expected " ^ Types.string_of_extern_type (import_type m im) ^
", got " ^ Types.string_of_extern_type (extern_type_of ext));
match ext with
| ExternFunc func -> {inst with funcs = func :: inst.funcs}
| ExternTable tab -> {inst with tables = tab :: inst.tables}
| ExternMemory mem -> {inst with memories = mem :: inst.memories}
| ExternGlobal glob -> {inst with globals = glob :: inst.globals}
let init_func (inst : module_inst) (func : func_inst) =
match func with
| Func.AstFunc (_, inst_ref, _) -> inst_ref := inst
| _ -> assert false
let run_elem i elem =
let at = elem.it.emode.at in
let x = i @@ at in
match elem.it.emode.it with
| Passive -> []
| Active {index; offset} ->
offset.it @ [
Const (I32 0l @@ at) @@ at;
Const (I32 (Lib.List32.length elem.it.einit) @@ at) @@ at;
TableInit (index, x) @@ at;
ElemDrop x @@ at
]
| Declarative ->
[ElemDrop x @@ at]
let run_data i data =
let at = data.it.dmode.at in
let x = i @@ at in
match data.it.dmode.it with
| Passive -> []
| Active {index; offset} ->
assert (index.it = 0l);
offset.it @ [
Const (I32 0l @@ at) @@ at;
Const (I32 (Int32.of_int (String.length data.it.dinit)) @@ at) @@ at;
MemoryInit x @@ at;
DataDrop x @@ at
]
| Declarative -> assert false
let run_start start =
[Call start.it.sfunc @@ start.at]
let init (m : module_) (exts : extern list) : module_inst =
let
{ imports; tables; memories; globals; funcs; types;
exports; elems; datas; start
} = m.it
in
if List.length exts <> List.length imports then
Link.error m.at "wrong number of imports provided for initialisation";
let inst0 =
{ (List.fold_right2 (add_import m) exts imports empty_module_inst) with
types = List.map (fun type_ -> type_.it) types }
in
let fs = List.map (create_func inst0) funcs in
let inst1 = {inst0 with funcs = inst0.funcs @ fs} in
let inst2 =
{ inst1 with
tables = inst1.tables @ List.map (create_table inst1) tables;
memories = inst1.memories @ List.map (create_memory inst1) memories;
globals = inst1.globals @ List.map (create_global inst1) globals;
}
in
let inst =
{ inst2 with
exports = List.map (create_export inst2) exports;
elems = List.map (create_elem inst2) elems;
datas = List.map (create_data inst2) datas;
}
in
List.iter (init_func inst) fs;
let es_elem = List.concat (Lib.List32.mapi run_elem elems) in
let es_data = List.concat (Lib.List32.mapi run_data datas) in
let es_start = Lib.Option.get (Lib.Option.map run_start start) [] in
ignore (eval (config inst [] (List.map plain (es_elem @ es_data @ es_start))));
inst