@ 261106.1
current
open Types
open Ast
open Value
open Script
open Source


(* Harness *)

let harness =
{|
'use strict';

let hostrefs = {};
let hostsym = Symbol("hostref");
function hostref(s) {
  if (! (s in hostrefs)) hostrefs[s] = {[hostsym]: s};
  return hostrefs[s];
}
function eq_ref(x, y) {
  return x === y ? 1 : 0;
}

let spectest = {
  hostref: hostref,
  eq_ref: eq_ref,
  print: console.log.bind(console),
  print_i32: console.log.bind(console),
  print_i64: console.log.bind(console),
  print_i32_f32: console.log.bind(console),
  print_f64_f64: console.log.bind(console),
  print_f32: console.log.bind(console),
  print_f64: console.log.bind(console),
  global_i32: 666,
  global_i64: 666n,
  global_f32: 666.6,
  global_f64: 666.6,
  table: new WebAssembly.Table({initial: 10, maximum: 20, element: 'anyfunc'}),
  table64: new WebAssembly.Table(
    {initial: 10n, maximum: 20n, element: 'anyfunc', address: 'i64'}),
  memory: new WebAssembly.Memory({initial: 1, maximum: 2}),
  memory64: new WebAssembly.Memory({initial: 1n, maximum: 2n, address: 'i64'})
};

let handler = {
  get(target, prop) {
    return (prop in target) ?  target[prop] : {};
  }
};
let registry = new Proxy({spectest}, handler);

function register(name, instance) {
  registry[name] = instance.exports;
}

function module(bytes, loc, valid = true) {
  let buffer = new ArrayBuffer(bytes.length);
  let view = new Uint8Array(buffer);
  for (let i = 0; i < bytes.length; ++i) {
    view[i] = bytes.charCodeAt(i);
  }
  let validated;
  try {
    validated = WebAssembly.validate(buffer);
  } catch (e) {
    throw new Error("Wasm validate throws");
  }
  if (validated !== valid) {
    throw new Error("Wasm validate failure" + (valid ? "" : " expected"));
  }
  return new WebAssembly.Module(buffer);
}

function instance(mod, imports = registry) {
  return new WebAssembly.Instance(mod, imports);
}

function call(instance, name, args) {
  return instance.exports[name](...args);
}

function get(instance, name) {
  let v = instance.exports[name];
  return (v instanceof WebAssembly.Global) ? v.value : v;
}

function exports(instance) {
  return {module: instance.exports, spectest: spectest};
}

function run(action) {
  action();
}

function assert_malformed(bytes, loc) {
  try { module(bytes, loc, false) } catch (e) {
    if (e instanceof WebAssembly.CompileError) return;
  }
  throw new Error("Wasm decoding failure expected");
}

function assert_malformed_custom(bytes) {
  return;
}

function assert_invalid(bytes, loc) {
  try { module(bytes, loc, false) } catch (e) {
    if (e instanceof WebAssembly.CompileError) return;
  }
  throw new Error("Wasm validation failure expected");
}

function assert_invalid_custom(bytes) {
  return;
}

function assert_unlinkable(mod) {
  try { new WebAssembly.Instance(mod, registry) } catch (e) {
    if (e instanceof WebAssembly.LinkError) return;
  }
  throw new Error("Wasm linking failure expected");
}

function assert_uninstantiable(mod) {
  try { new WebAssembly.Instance(mod, registry) } catch (e) {
    if (e instanceof WebAssembly.RuntimeError) return;
  }
  throw new Error("Wasm trap expected");
}

function assert_trap(action, loc) {
  try { action() } catch (e) {
    if (e instanceof WebAssembly.RuntimeError) return;
  }
  throw new Error("Wasm trap expected");
}

function assert_exception(action) {
  try { action() } catch (e) { return; }
  throw new Error("exception expected");
}

let StackOverflow;
try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor }

function assert_exhaustion(action) {
  try { action() } catch (e) {
    if (e instanceof StackOverflow) return;
  }
  throw new Error("Wasm resource exhaustion expected");
}

function assert_return(action, loc, ...expected) {
  let actual = action();
  if (actual === undefined) {
    actual = [];
  } else if (!Array.isArray(actual)) {
    actual = [actual];
  }
  if (actual.length !== expected.length) {
    throw new Error(expected.length + " value(s) expected, got " + actual.length);
  }
  for (let i = 0; i < actual.length; ++i) {
    let actual_i;
    try { actual_i = "" + actual[i] } catch { actual_i = typeof actual[i] }
    switch (expected[i]) {
      case "nan:canonical":
      case "nan:arithmetic":
      case "nan:any":
        // Note that JS can't reliably distinguish different NaN values,
        // so there's no good way to test that it's a canonical NaN.
        if (!Number.isNaN(actual[i])) {
          throw new Error("Wasm NaN return value expected, got " + actual_i);
        };
        return;
      case "ref.i31":
        if (typeof actual[i] !== "number" || (actual[i] & 0x7fffffff) !== actual[i]) {
          throw new Error("Wasm i31 return value expected, got " + actual_i);
        };
        return;
      case "ref.any":
      case "ref.eq":
      case "ref.struct":
      case "ref.array":
        // For now, JS can't distinguish exported Wasm GC values,
        // so we only test for object.
        if (typeof actual[i] !== "object") {
          throw new Error("Wasm object return value expected, got " + actual_i);
        };
        return;
      case "ref.func":
        if (typeof actual[i] !== "function") {
          throw new Error("Wasm function return value expected, got " + actual_i);
        };
        return;
      case "ref.extern":
        if (actual[i] === null) {
          throw new Error("Wasm reference return value expected, got " + actual_i);
        };
        return;
      case "ref.null":
        if (actual[i] !== null) {
          throw new Error("Wasm null return value expected, got " + actual_i);
        };
        return;
      default:
        if (!Object.is(actual[i], expected[i])) {
          throw new Error("Wasm return value " + expected[i] + " expected, got " + actual_i);
        };
    }
  }
}
|}


(* Context *)

module NameMap = Map.Make(struct type t = Ast.name let compare = compare end)
module Map = Map.Make(String)

type exports = externtype NameMap.t
type env =
  { mutable mods : exports Map.t;
    mutable insts : exports Map.t;
    mutable current_mod : int;
    mutable current_inst : int;
  }

let exports m : exports =
  let ModuleT (_, ets) = moduletype_of m in
  List.fold_left (fun map (ExportT (name, xt)) -> NameMap.add name xt map)
    NameMap.empty ets

let env () : env =
  { mods = Map.empty;
    insts = Map.empty;
    current_mod = 0;
    current_inst = 0;
  }

let current_mod (env : env) = "$$" ^ string_of_int env.current_mod
let of_mod_opt (env : env) = function
  | None -> current_mod env
  | Some x -> "$" ^ x.it

let current_inst (env : env) = "$" ^ string_of_int env.current_inst
let of_inst_opt (env : env) = function
  | None -> current_inst env
  | Some x -> x.it

let bind_mod (env : env) x_opt m =
  let exports = exports m in
  env.current_mod <- env.current_mod + 1;
  env.mods <- Map.add (of_mod_opt env x_opt) exports env.mods;
  if x_opt <> None then env.mods <- Map.add (current_mod env) exports env.mods

let bind_inst (env : env) x_opt exports =
  env.current_inst <- env.current_inst + 1;
  env.insts <- Map.add (of_inst_opt env x_opt) exports env.insts;
  if x_opt <> None then env.insts <- Map.add (current_inst env) exports env.insts

let find_mod (env : env) x_opt at =
  try Map.find (of_mod_opt env x_opt) env.mods with Not_found ->
    raise (Eval.Crash (at,
      if x_opt = None then "no module defined within script"
      else "unknown module " ^ of_mod_opt env x_opt ^ " within script"))

let find_inst (env : env) x_opt at =
  try Map.find (of_inst_opt env x_opt) env.insts with Not_found ->
    raise (Eval.Crash (at,
      if x_opt = None then "no module instance defined within script"
      else "unknown module instance " ^ of_inst_opt env x_opt ^ " within script"))

let lookup_export (env : env) x_opt name at =
  let exports = find_inst env x_opt at in
  try NameMap.find name exports with Not_found ->
    raise (Eval.Crash (at, "unknown export \"" ^
      string_of_name name ^ "\" within module instance"))


(* Transitively unsubstitute deftype into list of unrolled recursive types *)

let rec statify_list f rts = function
  | [] -> rts, []
  | x::xs ->
    let rts', x' = f rts x in
    let rts'', xs' = statify_list f rts' xs in
    rts'', x'::xs'

let rec statify_typeuse rts = function
  | Def dt ->
    let rts', i = statify_deftype rts dt in
    rts', Idx i
  | ht -> rts, ht

and statify_heaptype rts = function
  | UseHT ut ->
    let rts', ut' = statify_typeuse rts ut in
    rts', UseHT ut'
  | ht -> rts, ht

and statify_reftype rts = function
  | (nul, ht) ->
    let rts', ht' = statify_heaptype rts ht in
    rts', (nul, ht')

and statify_valtype rts = function
  | RefT rt ->
    let rts', rt' = statify_reftype rts rt in
    rts', RefT rt'
  | t -> rts, t

and statify_storagetype rts = function
  | ValStorageT t ->
    let rts', t' = statify_valtype rts t in
    rts', ValStorageT t'
  | st -> rts, st

and statify_fieldtype rts (FieldT (mut, st)) =
    let rts', st' = statify_storagetype rts st in
    rts', FieldT (mut, st')

and statify_comptype rts = function
  | StructT fts ->
    let rts', fts' = statify_list statify_fieldtype rts fts in
    rts', StructT fts'
  | ArrayT ft ->
    let rts', ft' = statify_fieldtype rts ft in
    rts', ArrayT ft'
  | FuncT (ts1, ts2) ->
    let rts', ts1' = statify_list statify_valtype rts ts1 in
    let rts'', ts2' = statify_list statify_valtype rts' ts2 in
    rts'', FuncT (ts1', ts2')

and statify_subtype rts (SubT (fin, uts, ct)) =
    let rts', uts' = statify_list statify_typeuse rts uts in
    let rts'', ct' = statify_comptype rts' ct in
    rts'', SubT (fin, uts', ct')

and statify_rectype rts (RecT sts) =
    let rts', sts' = statify_list statify_subtype rts sts in
    rts', RecT sts'

and statify_deftype rts (DefT (rt, i)) =
    match List.find_opt (fun (rt', _) -> rt = rt') rts with
    | Some (_, (rt', self)) -> rts, Int32.add self i
    | None ->
      let rts', RecT sts' = statify_rectype rts rt in
      let self =
        if rts' = [] then 0l else
        let _, (RecT sts, self) = Lib.List.last rts' in
        Int32.add self (Lib.List32.length sts)
      in
      let s = function
        | Rec j -> Idx (Int32.add self j)
        | ut -> ut
      in
      let rt' = RecT (List.map (subst_subtype s) sts') in
      rts' @ [rt, (rt', self)], Int32.add self i


(* Wrappers *)

let subject_idx = 0l
let hostref_idx = 1l
let eq_ref_idx = 2l
let subject_type_idx = 3l

let eq_of = function
  | I32T -> I32 I32Op.Eq
  | I64T -> I64 I64Op.Eq
  | F32T -> F32 F32Op.Eq
  | F64T -> F64 F64Op.Eq

let and_of = function
  | I32T | F32T -> I32 I32Op.And
  | I64T | F64T -> I64 I64Op.And

let reinterpret_of = function
  | I32T -> I32T, Nop
  | I64T -> I64T, Nop
  | F32T -> I32T, Convert (I32 I32Op.ReinterpretFloat)
  | F64T -> I64T, Convert (I64 I64Op.ReinterpretFloat)

let canonical_nan_of = function
  | I32T | F32T -> I32 (F32.to_bits F32.pos_nan)
  | I64T | F64T -> I64 (F64.to_bits F64.pos_nan)

let abs_mask_of = function
  | I32T | F32T -> I32 Int32.max_int
  | I64T | F64T -> I64 Int64.max_int

let value v at =
  match v with
  | Num n -> [Const (n @@ at) @@ at]
  | Vec s -> [VecConst (s @@ at) @@ at]
  | Ref (HostRef n) ->
    [ Const (I32 n @@ at) @@ at;
      Call (hostref_idx @@ at) @@ at;
    ]
  | Ref (Extern.ExternRef (HostRef n)) ->
    [ Const (I32 n @@ at) @@ at;
      Call (hostref_idx @@ at) @@ at;
      ExternConvert Externalize @@ at;
    ]
  | Ref _ -> assert false

let literal lit =
  match lit.it with
  | ValLit v -> value v lit.at
  | NullLit ht -> [RefNull (Match.bot_of_heaptype [] ht) @@ lit.at]

let invoke dt lits at =
  let dummy = RecT [SubT (Final, [], FuncT ([], []))] in
  let rts0 = Lib.List32.init subject_type_idx (fun i -> dummy, (dummy, i)) in
  let rts, i = statify_deftype rts0 dt in
  List.map (fun (_, (rt, _)) -> rt @@ at) (Lib.List32.drop subject_type_idx rts),
  ExternFuncT (Idx i),
  List.concat (List.map literal lits) @ [Call (subject_idx @@ at) @@ at]

let get t at =
  [], ExternGlobalT t, [GlobalGet (subject_idx @@ at) @@ at]

let run ts at =
  [], []

let nan_bitmask_of = function
  | CanonicalNan -> abs_mask_of  (* differ from canonical NaN in sign bit *)
  | ArithmeticNan -> canonical_nan_of  (* 1 everywhere canonical NaN is *)

let type_of_num_pat = function
  | NumPat num -> Value.type_of_num num.it
  | NanPat op -> Value.type_of_op op.it

let type_of_vec_pat = function
  | VecPat vec -> Value.type_of_vec vec

let type_of_ref_pat = function
  | RefPat ref -> type_of_ref ref.it
  | RefTypePat ht -> (NoNull, ht)
  | NullPat ht -> (Null, ht)

let rec type_of_result res =
  match res.it with
  | NumResult pat -> NumT (type_of_num_pat pat)
  | VecResult pat -> VecT (type_of_vec_pat pat)
  | RefResult pat -> RefT (type_of_ref_pat pat)
  | EitherResult rs ->
    let ts = List.map type_of_result rs in
    List.fold_left (fun t1 t2 ->
      if Match.match_valtype [] t1 t2 then t2 else
      if Match.match_valtype [] t2 t1 then t1 else
      if Match.(top_of_valtype [] t1 = top_of_valtype [] t2) then
        Match.top_of_valtype [] t1
      else
        BotT  (* should really be Top, but we don't have that :) *)
    ) (List.hd ts) ts

let assert_return ress ts at =
  let locals = ref [] in
  let rec test (res, t) =
    if
      not (
        Match.match_valtype [] t (type_of_result res) ||
        Match.match_valtype [] (type_of_result res) t
      )
    then
      [ Br (0l @@ at) @@ at ]
    else
    match res.it with
    | NumResult (NumPat {it = num; at = at'}) ->
      let t', reinterpret = reinterpret_of (Value.type_of_op num) in
      [ reinterpret @@ at;
        Const (num @@ at')  @@ at;
        reinterpret @@ at;
        Compare (eq_of t') @@ at;
        Test (I32 I32Op.Eqz) @@ at;
        BrIf (0l @@ at) @@ at ]
    | NumResult (NanPat nanop) ->
      let nan =
        match nanop.it with
        | Value.I32 _ | Value.I64 _ -> .
        | Value.F32 n | Value.F64 n -> n
      in
      let t', reinterpret = reinterpret_of (Value.type_of_op nanop.it) in
      [ reinterpret @@ at;
        Const (nan_bitmask_of nan t' @@ at) @@ at;
        Binary (and_of t') @@ at;
        Const (canonical_nan_of t' @@ at) @@ at;
        Compare (eq_of t') @@ at;
        Test (I32 I32Op.Eqz) @@ at;
        BrIf (0l @@ at) @@ at ]
    | VecResult (VecPat (Value.V128 (shape, pats))) ->
      let open Value in
      let mask_and_canonical = function
        | NumPat {it = I32 _ as i; _} -> I32 (Int32.minus_one), i
        | NumPat {it = I64 _ as i; _} -> I64 (Int64.minus_one), i
        | NumPat {it = F32 f; _} ->
          I32 (Int32.minus_one), I32 (Convert.I32_.reinterpret_f32 f)
        | NumPat {it = F64 f; _} ->
          I64 (Int64.minus_one), I64 (Convert.I64_.reinterpret_f64 f)
        | NanPat {it = F32 nan; _} ->
          nan_bitmask_of nan I32T, canonical_nan_of I32T
        | NanPat {it = F64 nan; _} ->
          nan_bitmask_of nan I64T, canonical_nan_of I64T
        | _ -> .
      in
      let masks, canons =
        List.split (List.map (fun p -> mask_and_canonical p) pats) in
      let all_ones =
        V128.I32x4.of_lanes (List.init 4 (fun _ -> Int32.minus_one)) in
      let mask, expected = match shape with
        | V128.I8x16 () ->
          all_ones, V128.I8x16.of_lanes (List.map Convert.I8_.wrap_i32 (List.map (I32Num.of_num 0) canons))
        | V128.I16x8 () ->
          all_ones, V128.I16x8.of_lanes (List.map Convert.I16_.wrap_i32 (List.map (I32Num.of_num 0) canons))
        | V128.I32x4 () ->
          all_ones, V128.I32x4.of_lanes (List.map (I32Num.of_num 0) canons)
        | V128.I64x2 () ->
          all_ones, V128.I64x2.of_lanes (List.map (I64Num.of_num 0) canons)
        | V128.F32x4 () ->
          V128.I32x4.of_lanes (List.map (I32Num.of_num 0) masks),
          V128.I32x4.of_lanes (List.map (I32Num.of_num 0) canons)
        | V128.F64x2 () ->
          V128.I64x2.of_lanes (List.map (I64Num.of_num 0) masks),
          V128.I64x2.of_lanes (List.map (I64Num.of_num 0) canons)
      in
      [ VecConst (V128 mask @@ at) @@ at;
        VecBinaryBits (V128 V128Op.And) @@ at;
        VecConst (V128 expected @@ at) @@ at;
        VecCompare (V128 (V128.I8x16 V128Op.Eq)) @@ at;
        (* If all lanes are non-zero, then they are equal *)
        VecTest (V128 (V128.I8x16 V128Op.AllTrue)) @@ at;
        Test (I32 I32Op.Eqz) @@ at;
        BrIf (0l @@ at) @@ at ]
    | RefResult (RefPat {it = NullRef; _}) ->
      [ RefIsNull @@ at;
        Test (Value.I32 I32Op.Eqz) @@ at;
        BrIf (0l @@ at) @@ at ]
    | RefResult (RefPat {it = HostRef n; _}) ->
      [ Const (Value.I32 n @@ at) @@ at;
        Call (hostref_idx @@ at) @@ at;
        Call (eq_ref_idx @@ at) @@ at;
        Test (Value.I32 I32Op.Eqz) @@ at;
        BrIf (0l @@ at) @@ at ]
    | RefResult (RefPat {it = Extern.ExternRef (HostRef n); _}) ->
      [ Const (Value.I32 n @@ at) @@ at;
        Call (hostref_idx @@ at) @@ at;
        ExternConvert Externalize @@ at;
        Call (eq_ref_idx @@ at) @@ at;
        Test (Value.I32 I32Op.Eqz) @@ at;
        BrIf (0l @@ at) @@ at ]
    | RefResult (RefPat _) ->
      assert false
    | RefResult (RefTypePat (ExnHT | ExternHT)) ->
      [ BrOnNull (0l @@ at) @@ at ]
    | RefResult (RefTypePat t) ->
      [ RefTest (NoNull, t) @@ at;
        Test (I32 I32Op.Eqz) @@ at;
        BrIf (0l @@ at) @@ at ]
    | RefResult (NullPat _) ->
      [ RefIsNull @@ at;
        Test (I32 I32Op.Eqz) @@ at;
        BrIf (0l @@ at) @@ at ]
    | EitherResult ress ->
      let idx = Lib.List32.length !locals in
      locals := !locals @ [Local t @@ res.at];
      [ LocalSet (idx @@ res.at) @@ res.at;
        Block (ValBlockType None,
          List.map (fun resI ->
            Block (ValBlockType None,
              [LocalGet (idx @@ resI.at) @@ resI.at] @
              test (resI, t) @
              [Br (1l @@ resI.at) @@ resI.at]
            ) @@ resI.at
          ) ress @
          [Br (1l @@ at) @@ at]
        ) @@ at
      ]
  in !locals, List.flatten (List.rev_map test (List.combine ress ts))

let i32 = NumT I32T
let anyref = RefT (Null, AnyHT)
let eqref = RefT (Null, EqHT)
let func_rectype ts1 ts2 at =
  RecT [SubT (Final, [], FuncT (ts1, ts2))] @@ at

let wrap item_name wrap_action wrap_assertion at =
  let itypes, idesc, action = wrap_action at in
  let locals, assertion = wrap_assertion at in
  let types =
    func_rectype [] [] at ::
    func_rectype [i32] [anyref] at ::
    func_rectype [eqref; eqref] [i32] at ::
    itypes
  in
  let imports =
    [ Import (Utf8.decode "module", item_name, idesc) @@ at;
      Import (Utf8.decode "spectest", Utf8.decode "hostref",
        ExternFuncT (Idx 1l)) @@ at;
      Import (Utf8.decode "spectest", Utf8.decode "eq_ref",
        ExternFuncT (Idx 2l)) @@ at;
    ]
  in
  let item =
    List.fold_left
      (fun i {it = Import (_, _, xt); _} ->
        match xt with ExternFuncT _ -> Int32.add i 1l | _ -> i
      ) 0l imports @@ at
  in
  let edesc = FuncX item @@ at in
  let exports = [Export (Utf8.decode "run", edesc) @@ at] in
  let body =
    [ Block (ValBlockType None, action @ assertion @ [Return @@ at]) @@ at;
      Unreachable @@ at ]
  in
  let funcs = [Func (0l @@ at, locals, body) @@ at] in
  let m = {empty_module with types; funcs; imports; exports} @@ at in
  (try
    ignore (Valid.check_module m);  (* sanity check *)
  with Valid.Invalid _ as exn ->
    prerr_endline (string_of_region at ^
      ": internal error in JS converter, invalid wrapper module generated:");
    Print.module_ stderr 80 m;
    raise exn
  );
  Encode.encode m


let is_js_numtype = function
  | I32T | I64T -> true
  | F32T | F64T -> false

let is_js_vectype = function
  | _ -> false

let is_js_reftype = function
  | (_, (ExnHT | NoExnHT)) -> false
  | _ -> true

let is_js_valtype = function
  | NumT t -> is_js_numtype t
  | VecT t -> is_js_vectype t
  | RefT t -> is_js_reftype t
  | BotT -> assert false

let is_js_globaltype = function
  | GlobalT (mut, t) -> is_js_valtype t && mut = Cons

let is_js_functype = function
  | (ts1, ts2) -> List.for_all is_js_valtype (ts1 @ ts2)


(* Script conversion *)

let add_hex_char buf c = Printf.bprintf buf "\\x%02x" (Char.code c)
let add_char buf c =
  if c < '\x20' || c >= '\x7f' then
    add_hex_char buf c
  else begin
    if c = '\"' || c = '\\' then Buffer.add_char buf '\\';
    Buffer.add_char buf c
  end
let add_unicode_char buf uc =
  if uc < 0x20 || uc >= 0x7f then
    Printf.bprintf buf "\\u{%02x}" uc
  else
    add_char buf (Char.chr uc)

let of_string_with iter add_char s =
  let buf = Buffer.create 256 in
  Buffer.add_char buf '\"';
  iter (add_char buf) s;
  Buffer.add_char buf '\"';
  Buffer.contents buf

let of_bytes = of_string_with String.iter add_hex_char
let of_string = of_string_with String.iter add_char
let of_name = of_string_with List.iter add_unicode_char

let of_loc_unquoted at =
  Filename.basename at.left.file ^ ":" ^ string_of_int at.left.line

let of_loc at =
  of_string (of_loc_unquoted at)

let of_float z =
  match string_of_float z with
  | "nan" -> "NaN"
  | "-nan" -> "-NaN"
  | "inf" -> "Infinity"
  | "-inf" -> "-Infinity"
  | s -> s

let of_num n =
  let open Value in
  match n with
  | I32 i -> I32.to_string_s i
  | I64 i -> I64.to_string_s i ^ "n"
  | F32 z -> of_float (F32.to_float z)
  | F64 z -> of_float (F64.to_float z)

let of_vec v =
  let open Value in
  match v with
  | V128 v -> "v128(\"" ^ V128.to_string v ^ "\")"

let of_ref r =
  let open Value in
  match r with
  | NullRef -> "null"
  | HostRef n | Extern.ExternRef (HostRef n) -> "hostref(" ^ Int32.to_string n ^ ")"
  | _ -> assert false

let of_val v =
  match v with
  | Num n -> of_num n
  | Vec v -> of_vec v
  | Ref r -> of_ref r

let of_lit lit =
  match lit.it with
  | ValLit v -> of_val v
  | NullLit _ -> "null"

let of_nan = function
  | CanonicalNan -> "\"nan:canonical\""
  | ArithmeticNan -> "\"nan:arithmetic\""

let of_num_pat = function
  | NumPat num -> of_num num.it
  | NanPat nanop ->
    match nanop.it with
    | Value.I32 _ | Value.I64 _ -> .
    | Value.F32 n | Value.F64 n -> of_nan n

let of_vec_pat = function
  | VecPat (Value.V128 (shape, pats)) ->
    Printf.sprintf "v128(\"%s\")" (String.concat " " (List.map of_num_pat pats))

let of_ref_pat = function
  | RefPat r -> of_ref r.it
  | RefTypePat t -> "\"ref." ^ string_of_heaptype t ^ "\""
  | NullPat t -> "\"ref.null\""

let rec of_result res =
  match res.it with
  | NumResult np -> of_num_pat np
  | VecResult vp -> of_vec_pat vp
  | RefResult rp -> of_ref_pat rp
  | EitherResult ress ->
    "[" ^ String.concat ", " (List.map of_result ress) ^ "]"

let rec of_definition def =
  match def.it with
  | Textual (m, _) -> of_bytes (Encode.encode m)
  | Encoded (_, bs) -> of_bytes bs.it
  | Quoted (_, s) ->
    try of_definition (snd (Parse.Module.parse_string ~offset:s.at s.it))
    with Parse.Syntax _ | Custom.Syntax _ -> of_bytes "<malformed quote>"

let of_wrapper env x_opt name wrap_action wrap_assertion at =
  let x = of_inst_opt env x_opt in
  let bs = wrap name wrap_action wrap_assertion at in
  "call(instance(module(" ^ of_bytes bs ^ ", \"wrapper\"), " ^
    "exports(" ^ x ^ ")), " ^ " \"run\", [])"

let of_action env act =
  match act.it with
  | Invoke (x_opt, name, lits) ->
    "call(" ^ of_inst_opt env x_opt ^ ", " ^ of_name name ^ ", " ^
      "[" ^ String.concat ", " (List.map of_lit lits) ^ "])",
    (match lookup_export env x_opt name act.at with
    | ExternFuncT (Def dt) ->
      let (_, ts) as ft = functype_of_comptype (expand_deftype dt) in
      if is_js_functype ft then
        None
      else
        Some (of_wrapper env x_opt name (invoke dt lits), ts)
    | _ -> None
    )
  | Get (x_opt, name) ->
    "get(" ^ of_inst_opt env x_opt ^ ", " ^ of_name name ^ ")",
    (match lookup_export env x_opt name act.at with
    | ExternGlobalT gt when not (is_js_globaltype gt) ->
      let GlobalT (_, t) = gt in
      Some (of_wrapper env x_opt name (get gt), [t])
    | _ -> None
    )

let of_assertion' env act loc name args wrapper_opt =
  let act_js, act_wrapper_opt = of_action env act in
  let js = name ^ "(() => " ^ act_js ^ ", " ^ loc ^ String.concat ", " ("" :: args) ^ ")" in
  match act_wrapper_opt with
  | None -> js ^ ";"
  | Some (act_wrapper, out) ->
    let run_name, wrapper =
      match wrapper_opt with
      | None -> name, run
      | Some wrapper -> "run", wrapper
    in run_name ^ "(() => " ^ act_wrapper (wrapper out) act.at ^ ", " ^ loc ^ ");  // " ^ js

let of_assertion env ass =
  let loc = of_loc ass.at in
  match ass.it with
  | AssertMalformed (def, _) ->
    "assert_malformed(" ^ of_definition def ^ ", " ^ loc ^ ");"
  | AssertMalformedCustom (def, _) ->
    "assert_malformed_custom(" ^ of_definition def ^ ", " ^ loc ^ ");"
  | AssertInvalid (def, _) ->
    "assert_invalid(" ^ of_definition def ^ ", " ^ loc ^ ");"
  | AssertInvalidCustom (def, _) ->
    "assert_invalid_custom(" ^ of_definition def ^ ", " ^ loc ^ ");"
  | AssertUnlinkable (x_opt, _) ->
    "assert_unlinkable(" ^ of_mod_opt env x_opt ^ ");"
  | AssertUninstantiable (x_opt, _) ->
    "assert_uninstantiable(" ^ of_mod_opt env x_opt ^ ");"
  | AssertReturn (act, ress) ->
    of_assertion' env act loc "assert_return" (List.map of_result ress)
      (Some (assert_return ress))
  | AssertTrap (act, _) ->
    of_assertion' env act loc "assert_trap" [] None
  | AssertExhaustion (act, _) ->
    of_assertion' env act loc "assert_exhaustion" [] None
  | AssertException act ->
    of_assertion' env act loc "assert_exception" [] None

let of_command env cmd =
  "\n// " ^ of_loc_unquoted cmd.at ^ "\n" ^
  let loc = of_loc cmd.at in
  match cmd.it with
  | Module (x_opt, def) ->
    let rec unquote def =
      match def.it with
      | Textual (m, _) -> m
      | Encoded (name, bs) -> Decode.decode name bs.it
      | Quoted (_, s) ->
        unquote (snd (Parse.Module.parse_string ~offset:s.at s.it))
    in bind_mod env x_opt (unquote def);
    "let " ^ current_mod env ^ " = module(" ^ of_definition def ^ ", " ^ loc ^ ");\n" ^
    (if x_opt = None then "" else
    "let " ^ of_mod_opt env x_opt ^ " = " ^ current_mod env ^ ";\n")
  | Instance (x1_opt, x2_opt) ->
    let exports = find_mod env x2_opt cmd.at in
    bind_inst env x1_opt exports;
    "let " ^ current_inst env ^ " = instance(" ^ of_mod_opt env x2_opt ^ ");\n" ^
    (if x1_opt = None then "" else
    "let " ^ of_inst_opt env x1_opt ^ " = " ^ current_inst env ^ ";\n")
  | Register (name, x_opt) ->
    "register(" ^ of_name name ^ ", " ^ of_inst_opt env x_opt ^ ")\n"
  | Action act ->
    of_assertion' env act loc "run" [] None ^ "\n"
  | Assertion ass ->
    of_assertion env ass ^ "\n"
  | Meta _ -> assert false

let of_script scr =
  (if !Flags.harness then harness else "") ^
  String.concat "" (List.map (of_command (env ())) scr)