File: interpret.ml

package info (click to toggle)
rust-wasmtime 26.0.1%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 48,492 kB
  • sloc: ansic: 4,003; sh: 561; javascript: 542; cpp: 254; asm: 175; ml: 96; makefile: 55
file content (140 lines) | stat: -rw-r--r-- 6,903 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
(* This module exposes an [interpret] function to Rust. It wraps several
different calls from the WebAssembly specification interpreter in a way that we
can access across the FFI boundary. To understand this better, see:
 - the OCaml manual documentation re: calling OCaml from C,
 https://ocaml.org/manual/intfc.html#s%3Ac-advexample
 - the [ocaml-interop] example,
 https://github.com/tezedge/ocaml-interop/blob/master/testing/rust-caller/ocaml/callable.ml
*)

(** Enumerate the types of values we pass across the FFI boundary. This must
match `Value` in `src/lib.rs` *)
type ffi_value =
  | I32 of int32
  | I64 of int64
  | F32 of int32
  | F64 of int64
  | V128 of Bytes.t

(** Enumerate the kinds of exported values the interpreter can retrieve. *)
type ffi_export_value =
  | Global of ffi_value
  | Memory of Bytes.t

(* Here we access the WebAssembly specification interpreter; this must be linked
in. *)
open Wasm
open Wasm.WasmRef_Isa_m.WasmRef_Isa

type spec_instance = (unit module_export_ext list * ((unit s_m_ext) ref))

(** Helper for converting the FFI values to their spec interpreter type. *)
let convert_to_wasm (v: ffi_value) : v = match v with
| I32 n -> V_num (ConstInt32 (I32_impl_abs n))
| I64 n -> V_num (ConstInt64 (I64_impl_abs n))
| F32 n -> V_num (ConstFloat32 (F32.of_bits n))
| F64 n -> V_num (ConstFloat64 (F64.of_bits n))
| V128 n -> V_vec (ConstVec128 (V128.of_bits (Bytes.to_string n)))

(** Helper for converting the spec interpreter values to their FFI type. *)
let convert_from_wasm (v: v) : ffi_value = match v with
| V_num ((ConstInt32 (I32_impl_abs n))) -> I32 n
| V_num ((ConstInt64 (I64_impl_abs n))) -> I64 n
| V_num ((ConstFloat32 n)) -> F32 (F32.to_bits n)
| V_num ((ConstFloat64 n)) -> F64 (F64.to_bits n)
| V_vec ((ConstVec128 n)) -> V128 (Bytes.of_string (V128.to_bits n))

(** Parse the given WebAssembly module binary into an Ast.module_. At some point
in the future this should also be able to parse the textual form (TODO). *)
let parse bytes =
  (* Optionally, use Bytes.unsafe_to_string here to avoid the copy *)
  let bytes_as_str = Bytes.to_string bytes in
  (Decode.decode "default" bytes_as_str)

(** Construct an instance from a sequence of WebAssembly bytes. This clears the
previous contents of the global store *)
let instantiate_exn module_bytes : spec_instance =
  let s = (make_empty_store_m ()) in
  let module_ = parse module_bytes in
  let m_isa = Ast_convert.convert_module (module_.it) in
  (match interp_instantiate_init_m s m_isa [] () with
  | (s', (RI_res_m(inst,v_exps,_))) -> (v_exps, ref s')
  | (s', (RI_trap_m str)) -> raise (Eval.Trap (Source.no_region, "(Isabelle) trap: " ^ str))
  | (s', (RI_crash_m (Error_exhaustion str))) -> raise (Eval.Exhaustion (Source.no_region, "(Isabelle) call stack exhausted"))
  | (s', (RI_crash_m (Error_invalid str))) -> raise (Eval.Crash (Source.no_region, "(Isabelle) error: " ^ str))
  | (s', (RI_crash_m (Error_invariant str))) -> raise (Eval.Crash (Source.no_region, "(Isabelle) error: " ^ str))
  )

let instantiate module_bytes =
  try Ok(instantiate_exn module_bytes) with
  | _ as e -> Error(Printexc.to_string e)

(** Retrieve the value of an export by name from a WebAssembly instance. *)
let export_exn (inst_s : spec_instance) (name : string) : ffi_export_value =
  let (inst, s_ref) = inst_s in
  match (e_desc (List.find (fun exp -> String.equal (e_name exp) name) inst)) with
    Ext_func _ -> raise Not_found
  | Ext_tab _ -> raise Not_found
  | Ext_mem i -> Memory (fst (Array.get (mems (!s_ref)) (Z.to_int (integer_of_nat i))))
  | Ext_glob i -> Global (convert_from_wasm (g_val (Array.get (globs (!s_ref)) (Z.to_int (integer_of_nat i)))))

let export inst name =
  try Ok(export_exn inst name) with
  | _ as e -> Error(Printexc.to_string e)

(** Interpret the first exported function and return the result. Use provided
parameters if they exist, otherwise use default (zeroed) values. *)
let interpret_legacy_exn module_bytes opt_params =
  let opt_params_ = Option.map (List.rev_map convert_to_wasm) opt_params in
  let module_ = parse module_bytes in
  let m_isa = Ast_convert.convert_module (module_.it) in
  let fuel = Z.of_string "4611686018427387904" in
  let max_call_depth = Z.of_string "300" in
  (match run_fuzz (nat_of_integer fuel) (nat_of_integer max_call_depth) (make_empty_store_m ()) m_isa [] opt_params_ () with
  | (s', RValue vs_isa') -> List.rev_map convert_from_wasm vs_isa'
  | (s', RTrap str) -> raise (Eval.Trap (Source.no_region, "(Isabelle) trap: " ^ str))
  | (s', (RCrash (Error_exhaustion str))) -> raise (Eval.Exhaustion (Source.no_region, "(Isabelle) call stack exhausted"))
  | (s', (RCrash (Error_invalid str))) -> raise (Eval.Crash (Source.no_region, "(Isabelle) error: " ^ str))
  | (s', (RCrash (Error_invariant str))) -> raise (Eval.Crash (Source.no_region, "(Isabelle) error: " ^ str))
  )

let interpret_legacy module_bytes opt_params =
  try Ok(interpret_legacy_exn module_bytes opt_params) with
  | _ as e -> Error(Printexc.to_string e)

(* process an optional list of params, generating default params if necessary *)
(* TODO: this should be done in the Isabelle model *)
let get_param_vs s_ref (vs_opt :(ffi_value list) option) i =
  (match vs_opt with
   | None -> (match cl_m_type ((array_nth heap_cl_m (funcs !s_ref) i) ()) with Tf (t1, _) -> map bitzero t1)
   | Some vs -> List.map convert_to_wasm vs)

(** Interpret the function exported at name. Use provided
parameters if they exist, otherwise use default (zeroed) values. *)
let interpret_exn (inst_s : spec_instance) (name : string) opt_params =
  (let fuel = Z.of_string "4611686018427387904" in
   let max_call_depth = Z.of_string "300" in
   let (inst, s_ref) = inst_s in
   match (e_desc (List.find (fun exp -> String.equal (e_name exp) name) inst)) with
   | Ext_func i ->
       (let params = get_param_vs s_ref opt_params i in
        let (s', res) = run_invoke_v_m (nat_of_integer fuel) (nat_of_integer max_call_depth) ((!s_ref), (params, i)) () in
        s_ref := s';
        (match res with
         | RValue vs_isa' -> List.rev_map convert_from_wasm vs_isa'
         | RTrap str -> raise (Eval.Trap (Source.no_region, "(Isabelle) trap: " ^ str))
         | (RCrash (Error_exhaustion str)) -> raise (Eval.Exhaustion (Source.no_region, "(Isabelle) call stack exhausted"))
         | (RCrash (Error_invalid str)) -> raise (Eval.Crash (Source.no_region, "(Isabelle) error: " ^ str))
         | (RCrash (Error_invariant str)) -> raise (Eval.Crash (Source.no_region, "(Isabelle) error: " ^ str))
        ))
   | _ -> raise Not_found)

let interpret inst name opt_params =
  try Ok(interpret_exn inst name opt_params) with
  | _ as e -> Error(Printexc.to_string e)

let () =
  Callback.register "instantiate" instantiate;
  Callback.register "interpret_legacy" interpret_legacy;
  Callback.register "interpret" interpret;
  Callback.register "export" export;