blob: 16878b257267719da1dca089ced47e34dc4caf37 [file] [log] [blame] [edit]
open Reference_interpreter
open Ds
open Al
open Ast
open Al_util
open Xl
open Construct
open Print
open Util
open Source
open Printf
(* Errors *)
let empty = ""
let error at msg step = raise (Exception.Error (at, msg, step))
let fail_expr expr msg =
failwith ("on expr `" ^ string_of_expr expr ^ "` " ^ msg)
let fail_path path msg =
failwith ("on path `" ^ string_of_path path ^ "` " ^ msg)
let try_with_error fname at stringifier f step =
let prefix = if fname <> empty then "$" ^ fname ^ ": " else fname in
try f step with
|(Exception.WrongConversion _
| Exception.ArgMismatch _
| Exception.UnknownFunc _
| Exception.FreeVar _) as e -> error at (prefix ^ Printexc.to_string e) (stringifier step)
| Failure msg -> error at (prefix ^ msg) (stringifier step)
let warn msg = print_endline ("warning: " ^ msg)
(* Hints *)
(* Try to find a hint `hintid` on a spectec function definition `fname`. *)
let find_hint fname hintid =
let open Il.Ast in
let open Il2al.Il2al_util in
List.find_map (fun hintdef ->
match hintdef.it with
| DecH (id', hints) when fname = id'.it ->
List.find_opt (fun hint -> hint.hintid.it = hintid) hints
| _ -> None
) !hintdefs
(* Matrix operations *)
let is_matrix matrix =
match matrix with
| [] -> true (* Empty matrix is considered a valid matrix *)
| row :: rows -> List.for_all (fun r -> List.length row = List.length r) rows
let transpose matrix =
assert (is_matrix matrix);
let rec transpose' = function
| [] -> []
| [] :: _ -> []
| (x :: xs) :: xss ->
let new_row = (x :: List.map List.hd xss) in
let new_rows = transpose' (xs :: List.map List.tl xss) in
new_row :: new_rows in
transpose' matrix
let is_not_typarg a =
match a.it with
| TypA _ -> false
| _ -> true
let remove_typargs = List.filter is_not_typarg
let dispatch_fname f env =
match lookup_env_opt ("$" ^ f) env with
| Some (FnameV f') -> f'
| _ -> f
let rec create_sub_env (iter, xes) env =
let length_to_list l = List.init l al_of_nat in
let xe_to_values (x, e) =
match iter with
| ListN (e_n, Some x') when x = x' ->
eval_expr env e_n |> al_to_nat |> length_to_list
| Opt ->
eval_expr env e |> unwrap_optv |> Option.to_list
| _ ->
eval_expr env e |> unwrap_listv_to_list
in
let xs = List.map fst xes in
xes
|> List.map xe_to_values
|> transpose
|> List.map (fun vs -> List.fold_right2 Env.add xs vs env)
and access_path env base path =
match path.it with
| IdxP e' ->
let a = base |> unwrap_listv_to_array in
let i = eval_expr env e' |> al_to_nat in
begin try Array.get a i with
| Invalid_argument _ ->
fail_path path
(sprintf "failed Array.get on base %s and index %s"
(string_of_value base) (string_of_int i))
end
| SliceP (e1, e2) ->
let a = base |> unwrap_listv_to_array in
let i1 = eval_expr env e1 |> al_to_nat in
let i2 = eval_expr env e2 |> al_to_nat in
Array.sub a i1 i2 |> listV
| DotP str -> (
let str = Print.string_of_atom str in
match base with
| StrV r -> Record.find str r
| v ->
fail_path path
(sprintf "base %s is not a record" (string_of_value v))
)
and replace_path env base path v_new =
match path.it with
| IdxP e' ->
let a = unwrap_listv_to_array base |> Array.copy in
let i = eval_expr env e' |> al_to_nat in
Array.set a i v_new;
listV a
| SliceP (e1, e2) ->
let a = unwrap_listv_to_array base |> Array.copy in
let i1 = eval_expr env e1 |> al_to_nat in
let i2 = eval_expr env e2 |> al_to_nat in
Array.blit (unwrap_listv_to_array v_new) 0 a i1 i2;
listV a
| DotP str ->
let str = Print.string_of_atom str in
let r =
match base with
| StrV r -> r
| v ->
fail_path path
(sprintf "base %s is not a record" (string_of_value v))
in
let r_new = Record.clone r in
Record.replace str v_new r_new;
strV r_new
and check_type ty v expr =
(* type definition *)
let addr_refs = [
"REF.I31_NUM"; "REF.STRUCT_ADDR"; "REF.ARRAY_ADDR";
"REF.FUNC_ADDR"; "REF.HOST_ADDR"; "REF.EXTERN";
] in
let pnn_types = [ "I8"; "I16" ] in
let inn_types = [ "I32"; "I64" ] in
let fnn_types = [ "F32"; "F64" ] in
let vnn_types = [ "V128"; ] in
let abs_heaptypes = [
"ANY"; "EQ"; "I31"; "STRUCT"; "ARRAY"; "NONE"; "FUNC";
"NOFUNC"; "EXN"; "NOEXN"; "EXTERN"; "NOEXTERN"
] in
match v with
(* addrref *)
| CaseV (ar, _) when List.mem ar addr_refs->
boolV (ty = "addrref" ||ty = "ref" || ty = "val")
(* nul *)
| CaseV ("REF.NULL", _) ->
boolV (ty = "nul" || ty = "ref" || ty = "val")
(* values *)
| CaseV ("CONST", CaseV (nt, []) ::_) when List.mem nt inn_types ->
boolV (ty = "val")
| CaseV ("CONST", CaseV (nt, []) ::_) when List.mem nt fnn_types ->
boolV (ty = "val")
| CaseV ("VCONST", CaseV (vt, [])::_) when List.mem vt vnn_types ->
boolV (ty = "val")
(* numtype *)
| CaseV (nt, []) when List.mem nt inn_types ->
boolV (ty = "Inn" || ty = "Jnn" || ty = "numtype" || ty = "valtype" || ty = "consttype")
| CaseV (nt, []) when List.mem nt fnn_types ->
boolV (ty = "Fnn" || ty = "numtype" || ty = "valtype" || ty = "consttype")
| CaseV (vt, []) when List.mem vt vnn_types ->
boolV (ty = "Vnn" || ty = "vectype" || ty = "valtype" || ty = "consttype")
(* valtype *)
| CaseV ("REF", _) ->
boolV (ty = "reftype" || ty = "valtype" || ty = "val")
(* absheaptype *)
| CaseV (aht, []) when List.mem aht abs_heaptypes ->
boolV (ty = "absheaptype" || ty = "heaptype")
(* deftype *)
| CaseV ("_DEF", [ _; _ ]) ->
boolV (ty = "heaptype" || ty = "typeuse" || ty = "deftype")
(* typevar *)
| CaseV ("_IDX", [ _ ]) ->
boolV (ty = "heaptype" || ty = "typeuse" || ty = "typevar")
(* heaptype *)
| CaseV ("REC", [ _ ]) ->
boolV (ty = "heaptype" || ty = "typeuse" || ty = "typevar")
(* packval *)
| CaseV ("PACK", CaseV (pt, [])::_) when List.mem pt pnn_types ->
boolV (ty = "val")
(* packtype *)
| CaseV (pt, []) when List.mem pt pnn_types ->
boolV (ty = "Pnn" || ty = "Jnn" || ty = "packtype" || ty = "storagetype")
| v -> fail_expr expr
(sprintf "cannot decide if %s has type %s" (structured_string_of_value v) ty)
and eval_expr env expr =
let rec to_bool source = function
| BoolV b -> b
| ListV _ as v -> List.for_all (to_bool source) (unwrap_listv_to_list v)
| _ -> fail_expr source "type mismatch for boolean value"
in
match expr.it with
(* Value *)
| NumE n -> numV n
| BoolE b -> boolV b
(* Numeric Operation *)
| CvtE (e1, _, nt) ->
(match eval_expr env e1 with
| NumV n1 ->
(match Num.cvt nt n1 with
| Some n -> numV n
| None -> fail_expr expr ("conversion not defined for " ^ string_of_value (NumV n1))
)
| v -> fail_expr expr ("type mismatch for conversion operation on " ^ string_of_value v)
)
| UnE (op, e1) ->
(match op, eval_expr env e1 with
| #Bool.unop as op', v -> Bool.un op' (to_bool e1 v) |> boolV
| #Num.unop as op', NumV n1 ->
(match Num.un op' n1 with
| Some n -> numV n
| None -> fail_expr expr ("unary operation `" ^ Num.string_of_unop op' ^ "` not defined for " ^ string_of_value (NumV n1))
)
| _, v -> fail_expr expr ("type mismatch for unary operation on " ^ string_of_value v)
)
| BinE (op, e1, e2) ->
(match op, eval_expr env e1, eval_expr env e2 with
| #Bool.binop as op', v1, v2 -> Bool.bin op' (to_bool e1 v1) (to_bool e2 v2) |> boolV
| #Num.binop as op', NumV n1, NumV n2 ->
(match Num.bin op' n1 n2 with
| Some n -> numV n
| None -> fail_expr expr ("binary operation `" ^ Num.string_of_binop op' ^ "` not defined for " ^ string_of_value (NumV n1) ^ ", " ^ string_of_value (NumV n2))
)
| #Bool.cmpop as op', v1, v2 -> boolV (Bool.cmp op' v1 v2)
| #Num.cmpop as op', NumV n1, NumV n2 ->
(match Num.cmp op' n1 n2 with
| Some b -> boolV b
| None -> fail_expr expr ("comparison operation `" ^ Num.string_of_cmpop op' ^ "` not defined for " ^ string_of_value (NumV n1) ^ ", " ^ string_of_value (NumV n2))
)
| _, v1, v2 -> fail_expr expr ("type mismatch for binary operation on " ^ string_of_value v1 ^ " and " ^ string_of_value v2)
)
(* Set Operation *)
| MemE (e1, e2) ->
let v1 = eval_expr env e1 in
eval_expr env e2 |> unwrap_listv_to_array |> Array.exists ((=) v1) |> boolV
(* Function Call *)
| CallE (fname, al) ->
let fname' = dispatch_fname fname env in
let el = remove_typargs al in
let args = List.map (eval_arg env) el in
(match call_func fname' args with
| Some v -> v
| _ -> raise (Exception.MissingReturnValue fname)
)
| InvCallE (fname, _, al) ->
let fname' = dispatch_fname fname env in
let el = remove_typargs al in
(* TODO: refactor numerics function name *)
let args = List.map (eval_arg env) el in
let inv_fname =
(* If function $f has hint(inverse $invf), but $invf is defined in terms
* of the inversion of $f, then infinite loop! Implement loop checks? *)
match find_hint fname' "inverse" with
| None ->
fail_expr expr (sprintf "no inverse hint is given for definition `%s`" fname')
| Some hint ->
(* We assume that there is only one way to invert the function, on the
* last argument. We could extend the hint syntax to denote an argument. *)
match hint.hintexp.it with
| CallE (fid, []) -> fid.it
| _ -> failwith (sprintf "ill-formed inverse hint for definition `%s`" fname')
in
(match call_func inv_fname args with
| Some v -> v
| _ -> raise (Exception.MissingReturnValue fname)
)
(* Data Structure *)
| ListE el -> List.map (eval_expr env) el |> listV_of_list
| CompE (e1, e2) ->
let s1 = eval_expr env e1 |> unwrap_strv in
let s2 = eval_expr env e2 |> unwrap_strv in
List.map
(fun (id, v) ->
let arr1 = match !v with
| ListV arr_ref -> arr_ref
| _ -> failwith (sprintf "`%s` is not a list" (string_of_value !v))
in
let arr2 = match Record.find id s2 with
| ListV arr_ref -> arr_ref
| v -> failwith (sprintf "`%s` is not a list" (string_of_value v))
in
(id, Array.append !arr1 !arr2 |> listV |> ref)
) s1 |> strV
| LiftE e1 ->
eval_expr env e1 |> unwrap_optv |> Option.to_list |> listV_of_list
| CatE (e1, e2) ->
let a1 = eval_expr env e1 |> unwrap_seq_to_array in
let a2 = eval_expr env e2 |> unwrap_seq_to_array in
Array.append a1 a2 |> listV
| LenE e ->
eval_expr env e |> unwrap_listv_to_array |> Array.length |> Z.of_int |> natV
| StrE r ->
r |> Record.map Print.string_of_atom (eval_expr env) |> strV
| AccE (e, p) ->
let base = eval_expr env e in
access_path env base p
| ExtE (e1, ps, e2, dir) ->
let rec extend ps base =
match ps with
| path :: rest -> access_path env base path |> extend rest |> replace_path env base path
| [] ->
let v = eval_expr env e2 |> unwrap_listv_to_array in
let a_copy = base |> unwrap_listv_to_array |> Array.copy in
let a_new =
match dir with
| Front -> Array.append v a_copy
| Back -> Array.append a_copy v
in
listV a_new
in
eval_expr env e1 |> extend ps
| UpdE (e1, ps, e2) ->
let rec replace ps base =
match ps with
| path :: rest -> access_path env base path |> replace rest |> replace_path env base path
| [] -> eval_expr env e2 in
eval_expr env e1 |> replace ps
| CaseE (op, el) ->
(match Mixop.head op with
| Some a -> caseV (Print.string_of_atom a, List.map (eval_expr env) el)
| None -> caseV ("", List.map (eval_expr env) el)
)
| OptE opt -> Option.map (eval_expr env) opt |> optV
| TupE el -> List.map (eval_expr env) el |> tupV
(* Context *)
| GetCurContextE { it = Atom a; _ } when List.mem a context_names ->
WasmContext.get_current_context a
| ChooseE e ->
let a = eval_expr env e |> unwrap_listv_to_array in
if Array.length a = 0 then
fail_expr expr (sprintf "cannot choose an element from %s because it's empty" (string_of_expr e))
else
Array.get a 0
| VarE "s" -> Store.get ()
| VarE name -> lookup_env name env
(* Optimized getter for simple IterE(VarE, ...) *)
| IterE ({ it = VarE name; _ }, (_, [name', e])) when name = name' ->
eval_expr env e
(* Optimized getter for list init *)
| IterE (e1, (ListN (e2, None), [])) ->
let v = eval_expr env e1 in
let i = eval_expr env e2 |> al_to_nat in
if i > 1024 * 64 * 1024 (* 1024 pages *) then
raise Exception.OutOfMemory
else
Array.make i v |> listV
(* HARDCODE: The case where itered variable does not appear in xes.
--> Insert itered variable. This was instroduced due to the change of IrerE's ListN. *)
| IterE (e1, (ListN (e2, Some x), [])) ->
let dummy_expr = VarE "_" $$ no_region % (Il.Ast.VarT ("_" $ no_region, []) $ no_region) in
let expr' = {expr with it = IterE (e1, (ListN (e2, Some x), [(x, dummy_expr)]))} in
eval_expr env expr'
| IterE (inner_e, (iter, xes)) ->
let vs =
env
|> create_sub_env (iter, xes)
|> List.map (fun env' -> eval_expr env' inner_e)
in
(match vs, iter with
| [], Opt -> optV None
| [v], Opt -> Option.some v |> optV
| l, _ -> listV_of_list l)
(* condition *)
| ContextKindE a ->
let ctx = WasmContext.get_top_context () in
(match a.it, ctx with
| Atom case, CaseV (case', _) -> boolV (case = case')
| _ -> boolV false)
| IsDefinedE e ->
e
|> eval_expr env
|> unwrap_optv
|> Option.is_some
|> boolV
| IsCaseOfE (e, expected_tag) ->
let expected_tag = Print.string_of_atom expected_tag in
(match eval_expr env e with
| CaseV (tag, _) -> boolV (expected_tag = tag)
| _ -> boolV false)
(* TODO : This should be replaced with executing the validation algorithm *)
| IsValidE e ->
let valid_lim k = function
| TupV [ NumV (`Nat n); NumV (`Nat m) ] -> n <= m && m <= k
| _ -> false
in
(match eval_expr env e with
(* valid_tabletype *)
| TupV [ lim; _ ] -> valid_lim (Z.of_int 0xffffffff) lim |> boolV
(* valid_memtype *)
| CaseV ("PAGE", [ lim ]) -> valid_lim (Z.of_int 0x10000) lim |> boolV
(* valid_other *)
| _ ->
fail_expr expr "TODO: deferring validation to reference interpreter"
)
| HasTypeE (e, t) ->
(* TODO: This shouldn't be hardcoded *)
(* check type *)
let v = eval_expr env e in
check_type (string_of_typ t) v expr
| MatchE (e1, e2) ->
(* Deferred to reference interpreter *)
let rt1 = e1 |> eval_expr env |> Construct.al_to_reftype in
let rt2 = e2 |> eval_expr env |> Construct.al_to_reftype in
boolV (Match.match_reftype [] rt1 rt2)
| TopValueE _ ->
(* TODO: type check *)
boolV (List.length (WasmContext.get_value_stack ()) > 0)
| TopValuesE e ->
let i = eval_expr env e |> al_to_nat in
boolV (List.length (WasmContext.get_value_stack ()) >= i)
| _ -> fail_expr expr "cannot evaluate expr"
and eval_arg env a =
match a.it with
| ExpA e -> eval_expr env e
| TypA _ -> assert false
| DefA id -> FnameV (dispatch_fname id env)
(* Assignment *)
and has_same_keys re rv =
let k1 = Record.keys re |> List.map string_of_atom |> List.sort String.compare in
let k2 = Record.keys rv |> List.sort String.compare in
k1 = k2
and assign lhs rhs env =
match lhs.it, rhs with
| VarE name, _ -> Env.add name rhs env
| IterE ({ it = VarE x1; _ }, ((List|List1), [x2, lhs'])), ListV _ when x1 = x2 ->
assign lhs' rhs env
| IterE (e, (iter, xes)), _ ->
let vs = unwrap_seqv_to_list rhs in
let envs = List.map (fun v -> assign e v Env.empty) vs in
(* Assign length variable *)
let env' =
match iter with
| ListN (expr, None) ->
let length = natV_of_int (List.length vs) in
assign expr length env
| ListN _ ->
fail_expr lhs "invalid assignment: iter with index cannot be an assignment target"
| _ -> env
in
List.fold_left (fun env (x, e) ->
let vs = List.map (lookup_env x) envs in
let v =
match iter with
| Opt -> optV (List.nth_opt vs 0)
| _ -> listV_of_list vs
in
assign e v env
) env' xes
| TupE lhs_s, TupV rhs_s
when List.length lhs_s = List.length rhs_s ->
List.fold_right2 assign lhs_s rhs_s env
| ListE lhs_s, ListV rhs_s
when List.length lhs_s = Array.length !rhs_s ->
List.fold_right2 assign lhs_s (Array.to_list !rhs_s) env
| CaseE (op, lhs_s), CaseV (rhs_tag, rhs_s) when List.length lhs_s = List.length rhs_s ->
(match Mixop.head op with
| Some lhs_tag when (Print.string_of_atom lhs_tag) = rhs_tag ->
List.fold_right2 assign lhs_s rhs_s env
| None when "" = rhs_tag ->
List.fold_right2 assign lhs_s rhs_s env
| _ -> fail_expr lhs
(sprintf "invalid assignment: cannot be an assignment target for %s" (string_of_value rhs))
)
| OptE (Some lhs), OptV (Some rhs) -> assign lhs rhs env
| CvtE (e1, nt, _), NumV n ->
(match Num.cvt nt n with
| Some n' -> assign e1 (NumV n') env
| None -> fail_expr lhs ("inverse conversion not defined for " ^ string_of_value (NumV n))
)
(* Assumption: e1 is the assign target *)
| BinE (binop, e1, e2), NumV m ->
let invop =
match binop with
| `AddOp -> `SubOp
| `SubOp -> `AddOp
| `MulOp -> `DivOp
| `DivOp -> `MulOp
| _ -> fail_expr lhs "invalid assignment: logical binop cannot be an assignment target" in
let v =
match Num.bin invop m (eval_expr env e2 |> unwrap_numv) with
| Some n -> numV n
| None -> fail_expr lhs ("invalid assignment: inverted binop not defined for " ^ string_of_value rhs)
in
assign e1 v env
| CatE _, ListV vs -> assign_split lhs !vs env
| StrE r1, StrV r2 when has_same_keys r1 r2 ->
Record.fold (fun k v acc -> (Record.find (Print.string_of_atom k) r2 |> assign v) acc) r1 env
| _, _ ->
fail_expr lhs
(sprintf "invalid assignment: on rhs %s" (string_of_value rhs))
and assign_split lhs vs env =
let ep, es = unwrap_cate lhs in
let len = Array.length vs in
let prefix_len, suffix_len =
let get_fixed_length e =
match e.it with
| ListE es -> Some (List.length es)
| IterE (_, (ListN (e, None), _)) -> Some (al_to_nat (eval_expr env e))
| _ -> None
in
match get_fixed_length ep, get_fixed_length es with
| None, None ->
fail_expr lhs
"invalid assignment: non-deterministic pattern cannot be an assignment target"
| Some l, None -> l, len - l
| None, Some l -> len - l, l
| Some l1, Some l2 -> l1, l2
in
if prefix_len < 0 || suffix_len < 0 then
fail_expr lhs "invalid assignment: negative length cannot be an assignment target"
else if prefix_len + suffix_len <> len then
fail_expr lhs
(sprintf "invalid assignment: %s's length is not equal to lhs"
(string_of_value (listV vs))
)
else (
let prefix = Array.sub vs 0 prefix_len |> listV in
let suffix = Array.sub vs prefix_len suffix_len |> listV in
env |> assign ep prefix |> assign es suffix
)
and assign_param lhs rhs env =
match lhs.it with
| ExpA e -> assign e rhs env
| TypA _ -> assert false
| DefA id -> Env.add ("$" ^ id) rhs env
(* Step *)
and step_instr (fname: string) (ctx: AlContext.t) (env: value Env.t) (instr: instr) : AlContext.t =
(Info.find instr.note).covered <- true;
let rec is_true = function
| BoolV true -> true
| OptV v_opt -> v_opt |> Option.map is_true |> Option.value ~default:true
| ListV a -> Array.for_all is_true !a
| _ -> false
in
match instr.it with
(* Block instruction *)
| IfI (e, il1, il2) ->
if is_true (eval_expr env e) then
AlContext.add_instrs il1 ctx
else
AlContext.add_instrs il2 ctx
| EitherI (il1, il2) ->
(try
ctx |> AlContext.add_instrs il1 |> run
with
| Exception.Fail
| Exception.OutOfMemory ->
AlContext.add_instrs il2 ctx
)
| AssertI e ->
if is_true (eval_expr env e) then
ctx
else
fail_expr e "assertion fail"
| PushI e ->
(match eval_expr env e with
| CaseV ("FRAME_", _) as v ->
WasmContext.push_context (v, [], []);
AlContext.increase_depth ctx
| ListV vs ->
Array.iter WasmContext.push_value !vs;
ctx
| v ->
WasmContext.push_value v;
ctx
)
| PopI e ->
(match e.it with
| CaseE (op, [_; inner_e]) when (Option.get (Mixop.head op)).it = Atom.Atom "FRAME_" ->
(match WasmContext.pop_context () with
| CaseV ("FRAME_", [_; inner_v]), _, _ ->
let new_env = assign inner_e inner_v env in
ctx
|> AlContext.set_env new_env
|> AlContext.decrease_depth
| v, _, _ -> failwith (sprintf "current context `%s` is not a frame" (string_of_value v))
)
| IterE ({ it = VarE name; _ }, (ListN (e_n, None), [name', e'])) when name = name' ->
let i = eval_expr env e_n |> al_to_nat in
let v =
List.init i (fun _ -> WasmContext.pop_value ())
|> List.rev
|> listV_of_list
in
let new_env = assign e' v env in
AlContext.set_env new_env ctx
| _ ->
let new_env = assign e (WasmContext.pop_value ()) env in
AlContext.set_env new_env ctx
)
| PopAllI e ->
let v = WasmContext.pop_value_stack () |> List.rev |> listV_of_list in
let new_env = assign e v env in
AlContext.set_env new_env ctx
| LetI (e1, e2) ->
let new_env = ctx |> AlContext.get_env |> assign e1 (eval_expr env e2) in
AlContext.set_env new_env ctx
| PerformI (f, al) ->
let el = remove_typargs al in
let args = List.map (eval_arg env) el in
call_func f args |> ignore;
ctx
| TrapI -> raise Exception.Trap
| ThrowI _ -> raise Exception.Throw
| FailI -> raise Exception.Fail
| NopI -> ctx
| ReturnI None -> AlContext.tl ctx
| ReturnI (Some e) ->
AlContext.return (eval_expr env e) :: AlContext.tl ctx
| ExecuteI e ->
let v = eval_expr env e in
AlContext.execute v :: ctx
| ExecuteSeqI e ->
let v = eval_expr env e in
(match v with
| ListV _ ->
let ctx' = v |> unwrap_listv_to_list |> List.map AlContext.execute in
ctx' @ ctx
| _ -> failwith (sprintf "%s is not a sequence value" (string_of_value v))
)
| EnterI (e1, e2, il) ->
let v1 = eval_expr env e1 in
let v2 = eval_expr env e2 in
WasmContext.push_context (v1, [], unwrap_listv_to_list v2);
AlContext.enter (fname, il, env) :: ctx
| ExitI _ ->
WasmContext.pop_context () |> ignore;
AlContext.decrease_depth ctx
| ReplaceI (e1, { it = IdxP e2; _ }, e3) ->
let a = eval_expr env e1 |> unwrap_listv_to_array in
let i = eval_expr env e2 |> al_to_nat in
let v = eval_expr env e3 in
Array.set a i v;
ctx
| ReplaceI (e1, { it = SliceP (e2, e3); _ }, e4) ->
let a1 = eval_expr env e1 |> unwrap_listv_to_array in (* dest *)
let i1 = eval_expr env e2 |> al_to_nat in (* start index *)
let i2 = eval_expr env e3 |> al_to_nat in (* length *)
let a2 = eval_expr env e4 |> unwrap_listv_to_array in (* src *)
assert (Array.length a2 = i2);
Array.blit a2 0 a1 i1 i2;
ctx
| ReplaceI (r, { it = DotP s; _ }, e) ->
let s = Print.string_of_atom s in
r
|> eval_expr env
|> unwrap_strv
|> Record.replace s (eval_expr env e);
ctx
| AppendI (e1, e2) ->
let a = eval_expr env e1 |> unwrap_listv in
(match e2.note.it, eval_expr env e2 with
| IterT _, ListV arr_ref -> a := Array.append !a !arr_ref
| IterT (_, Opt), OptV opt ->
a := opt |> Option.to_list |> Array.of_list |> Array.append !a
| IterT _, v ->
v
|> string_of_value
|> sprintf "the expression is evaluated to %s, not a iterable data type"
|> fail_expr e2
| _, v -> a := Array.append !a [|v|]
);
ctx
| _ -> failwith "cannot step instr"
and try_step_instr fname ctx env instr =
try_with_error fname instr.at string_of_instr (step_instr fname ctx env) instr
and step_wasm (ctx: AlContext.t) : value -> AlContext.t = function
| CaseV ("REF.NULL" as name, ([ CaseV ("_IDX", _) ] as args)) ->
create_context name args :: ctx
| CaseV ("REF.NULL", _)
| CaseV ("CONST", _)
| CaseV ("VCONST", _) as v -> WasmContext.push_value v; ctx
| CaseV (name, []) when Host.is_host name -> Host.call name; ctx
| CaseV (name, args) -> create_context name args :: ctx
| v -> fail_value "cannot step a wasm instr" v
and try_step_wasm ctx v =
try_with_error empty no_region structured_string_of_value (step_wasm ctx) v
and step (ctx: AlContext.t) : AlContext.t =
let open AlContext in
Debugger.run ctx;
match ctx with
| Al (name, args, il, env, n) :: ctx ->
(match il with
| [] -> ctx
| [ instr ]
when can_tail_call instr && n = 0 && not !Debugger.debug ->
try_step_instr name ctx env instr
| h :: t ->
let new_ctx = Al (name, args, t, env, n) :: ctx in
try_step_instr name new_ctx env h
)
| Wasm n :: ctx ->
if n = 0 then
ctx
else
try_step_wasm (Wasm n :: ctx) (WasmContext.pop_instr ())
| Enter (name, il, env) :: ctx ->
(match il with
| [] ->
(match ctx with
| Wasm n :: t when not !Debugger.debug -> Wasm (n + 1) :: t
| Enter (_, [], _) :: t -> Wasm 2 :: t
| ctx -> Wasm 1 :: ctx
)
| h :: t ->
let new_ctx = Enter (name, t, env) :: ctx in
try_step_instr name new_ctx env h
)
| Execute v :: ctx -> try_step_wasm ctx v
| _ -> assert false
(* AL interpreter Entry *)
and run (ctx: AlContext.t) : AlContext.t =
if AlContext.is_reducible ctx then run (step ctx) else ctx
and create_context (name: string) (args: value list) : AlContext.mode =
let algo = lookup_algo name in
let params = params_of_algo algo in
let body = body_of_algo algo in
let params = params |> remove_typargs in
if List.length args <> List.length params then (
error
algo.at
(Printf.sprintf "Expected %d arguments for the algorithm `%s` but %d arguments are given"
(List.length params)
name
(List.length args))
(string_of_value (CaseV (name, args)))
);
let env =
Env.empty
|> List.fold_right2 assign_param params args
in
AlContext.al (name, params, body, env, 0)
and call_func (name: string) (args: value list) : value option =
let builtin_name, is_builtin =
match find_hint name "builtin" with
| None -> name, false
| Some hint ->
match hint.hintexp.it with
| SeqE [] -> name, true (* hint(builtin) *)
| TextE fname -> fname, true (* hint(builtin "g") *)
| _ -> failwith (sprintf "ill-formed builtin hint for definition `%s`" name)
in
(* Function *)
if bound_func name && not is_builtin then
[create_context name args]
|> run
|> AlContext.get_return_value
(* Numerics *)
else if Numerics.mem builtin_name then (
if not is_builtin then
warn (sprintf "Numeric function `%s` is not defined in source, consider adding a hint(builtin)" name);
Some (Numerics.call_numerics builtin_name args)
)
(* Relation *)
else if Relation.mem name then (
if bound_rule name then
[create_context name args]
|> run
|> AlContext.get_return_value
else
Some (Relation.call_func name args)
)
else
raise (Exception.UnknownFunc ("There is no function named: " ^ name))
(* Wasm interpreter entry *)
let instantiate (args: value list) : value =
WasmContext.init_context ();
match call_func "instantiate" args with
| Some module_inst -> module_inst
| None -> failwith "Instantiation doesn't return module instance"
let invoke (args: value list) : value =
WasmContext.init_context ();
match call_func "invoke" args with
| Some v -> v
| None -> failwith "Invocation doesn't return any values"