diff --git a/src/lib_scoru_wasm/bench/exec.ml b/src/lib_scoru_wasm/bench/exec.ml index 34196e72802c4538ffe91394e3fd8d5b543bed43..d91647a8164d2371a817cf8e7276954361e8c01b 100644 --- a/src/lib_scoru_wasm/bench/exec.ml +++ b/src/lib_scoru_wasm/bench/exec.ml @@ -56,6 +56,7 @@ let finish_top_level_call_on_state pvm_state = let execute_on_state phase state = Wasm_vm.compute_step_many_until + ~debug_flag:false ~max_steps:Int64.max_int (should_continue phase) state diff --git a/src/lib_scoru_wasm/fast/exec.ml b/src/lib_scoru_wasm/fast/exec.ml index 352d6cf3ea02a59ecd05fc19898d9865cdaffbf0..0a368213244183dfd22aa155561bcb8eeb2bca85 100644 --- a/src/lib_scoru_wasm/fast/exec.ml +++ b/src/lib_scoru_wasm/fast/exec.ml @@ -39,7 +39,7 @@ let load_kernel durable = let store = Lazy.force store in Module_cache.load_kernel store durable -let compute builtins durable buffers = +let compute ~enable_debugging builtins durable buffers = let open Lwt.Syntax in let* module_ = load_kernel durable in @@ -48,7 +48,7 @@ let compute builtins durable buffers = match !main_mem with Some x -> x () | None -> assert false in - let host_state = Funcs.{retrieve_mem; buffers; durable} in + let host_state = Funcs.{retrieve_mem; buffers; durable; enable_debugging} in let host_funcs = Funcs.make builtins host_state in let with_durable f = diff --git a/src/lib_scoru_wasm/fast/exec.mli b/src/lib_scoru_wasm/fast/exec.mli index 0c0b3626de7f19b0cb10c8fa9e1386f1f3043065..6fad9956c7cbdc96cb5458ae85709da811e03eeb 100644 --- a/src/lib_scoru_wasm/fast/exec.mli +++ b/src/lib_scoru_wasm/fast/exec.mli @@ -27,4 +27,9 @@ open Tezos_scoru_wasm open Tezos_webassembly_interpreter (** [compute durable buffers] applies one call to [kernel_run]. *) -val compute : Builtins.t -> Durable.t -> Eval.buffers -> Durable.t Lwt.t +val compute : + enable_debugging:bool -> + Builtins.t -> + Durable.t -> + Eval.buffers -> + Durable.t Lwt.t diff --git a/src/lib_scoru_wasm/fast/funcs.ml b/src/lib_scoru_wasm/fast/funcs.ml index 464fb7f21d0bcd7e8c0cdd1df205e38376b0e273..e560570b821ca6e77e36a5d2fe2b145b14e1a527 100644 --- a/src/lib_scoru_wasm/fast/funcs.ml +++ b/src/lib_scoru_wasm/fast/funcs.ml @@ -76,8 +76,35 @@ type host_state = { retrieve_mem : unit -> Wasmer.Memory.t; buffers : Tezos_webassembly_interpreter.Eval.buffers; mutable durable : Durable.t; + enable_debugging : bool; } +let write_debug_impl state = + if state.enable_debugging then (fun key_offset key_length -> + let mem = state.retrieve_mem () in + let len = Wasmer.Memory.length mem |> Int32.of_int in + let key_offset, key_length = + match () with + | () when key_offset >= len -> + (* Start of key is out of bounds *) + (0l, 0l) + | () when key_length > Int32.sub len key_offset -> + (* End of key would exceeds bounds *) + (key_offset, Int32.sub len key_offset) + | () -> + (* Everything is ok *) + (key_offset, key_length) + in + let key_offset = Int32.to_int key_offset in + let str = + String.init (Int32.to_int key_length) (fun i -> + Wasmer.Memory.get mem (key_offset + i) + |> Unsigned.UInt8.to_int |> Char.chr) + in + Printf.printf "DEBUG: %s\n" str ; + Lwt.return ()) + else fun _ _ -> Lwt.return () + let make (module Builtins : Builtins.S) state = let open Wasmer in let open Lwt.Syntax in @@ -154,31 +181,7 @@ let make (module Builtins : Builtins.S) state = result) in let write_debug = - fn - (i32 @-> i32 @-> returning nothing) - (fun key_offset key_length -> - let mem = state.retrieve_mem () in - let len = Wasmer.Memory.length mem |> Int32.of_int in - let key_offset, key_length = - match () with - | () when key_offset >= len -> - (* Start of key is out of bounds *) - (0l, 0l) - | () when key_length > Int32.sub len key_offset -> - (* End of key would exceeds bounds *) - (key_offset, Int32.sub len key_offset) - | () -> - (* Everything is ok *) - (key_offset, key_length) - in - let key_offset = Int32.to_int key_offset in - let str = - String.init (Int32.to_int key_length) (fun i -> - Wasmer.Memory.get mem (key_offset + i) - |> Unsigned.UInt8.to_int |> Char.chr) - in - Printf.printf "DEBUG: %s\n" str ; - Lwt.return ()) + fn (i32 @-> i32 @-> returning nothing) (write_debug_impl state) in let store_copy = fn diff --git a/src/lib_scoru_wasm/fast/funcs.mli b/src/lib_scoru_wasm/fast/funcs.mli index e7cbd1f4af8c2092b1084a8ee0f3fded10d1e719..b46ea495c5efbc77fd3af2c580458be1ef9117d0 100644 --- a/src/lib_scoru_wasm/fast/funcs.mli +++ b/src/lib_scoru_wasm/fast/funcs.mli @@ -32,6 +32,7 @@ type host_state = { retrieve_mem : unit -> Memory.t; buffers : Eval.buffers; mutable durable : Durable.t; + enable_debugging : bool; (** Use `write_output` alternative implementation *) } (** [make builtins host_state] generates a list of host functions that can be diff --git a/src/lib_scoru_wasm/fast/vm.ml b/src/lib_scoru_wasm/fast/vm.ml index 258c642721a545dc78ced6caa545880e142c8550..82149aad6f1b0a17f4d408dc353c1933fe01364f 100644 --- a/src/lib_scoru_wasm/fast/vm.ml +++ b/src/lib_scoru_wasm/fast/vm.ml @@ -31,6 +31,7 @@ include (Wasm_vm : Wasm_vm_sig.S) let compute_until_snapshot ~max_steps pvm_state = Wasm_vm.compute_step_many_until ~max_steps + ~debug_flag:false (fun pvm_state -> Lwt.return @@ @@ -39,12 +40,14 @@ let compute_until_snapshot ~max_steps pvm_state = | _ -> Wasm_vm.should_compute pvm_state) pvm_state -let compute_fast builtins pvm_state = +let compute_fast ~enable_debugging builtins pvm_state = let open Lwt.Syntax in (* Execute! *) (* TODO: https://gitlab.com/tezos/tezos/-/issues/4123 Support performing multiple calls to [Eval.compute]. *) - let* durable = Exec.compute builtins pvm_state.durable pvm_state.buffers in + let* durable = + Exec.compute ~enable_debugging builtins pvm_state.durable pvm_state.buffers + in (* Compute the new tick counter. *) let ticks = pvm_state.max_nb_ticks in let current_tick = Z.(pred @@ add pvm_state.last_top_level_call ticks) in @@ -56,16 +59,22 @@ let compute_fast builtins pvm_state = (pvm_state, Z.to_int64 ticks) let rec compute_step_many accum_ticks ?builtins - ?(after_fast_exec = fun () -> ()) ?(stop_at_snapshot = false) ~max_steps - pvm_state = + ?(after_fast_exec = fun () -> ()) ?(stop_at_snapshot = false) ?debug_flag + ~max_steps pvm_state = let open Lwt.Syntax in assert (max_steps > 0L) ; let eligible_for_fast_exec = Z.Compare.(pvm_state.max_nb_ticks <= Z.of_int64 max_steps) in + let enable_debugging = Option.value ~default:false debug_flag in let backup pvm_state = let+ pvm_state, ticks = - Wasm_vm.compute_step_many ?builtins ~stop_at_snapshot ~max_steps pvm_state + Wasm_vm.compute_step_many + ?builtins + ~stop_at_snapshot + ?debug_flag + ~max_steps + pvm_state in (pvm_state, Int64.add accum_ticks ticks) in @@ -84,13 +93,16 @@ let rec compute_step_many accum_ticks ?builtins ~builtins ~stop_at_snapshot ~after_fast_exec + ?debug_flag ~max_steps pvm_state else Lwt.return (pvm_state, accum_ticks) | _ -> Lwt.return (pvm_state, ticks) in let go_like_the_wind () = - let+ pvm_state, ticks = compute_fast builtins pvm_state in + let+ pvm_state, ticks = + compute_fast ~enable_debugging builtins pvm_state + in after_fast_exec () ; (pvm_state, Int64.(add ticks accum_ticks)) in diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index c0cc37293674bfe93404a4634bf2a41c36738c4b..8fa9cfe357aa16afdc4b2cd8eee012996c95a8e7 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -98,17 +98,22 @@ let extract_error durable = function | Error error -> Lwt.return (durable, Error.code error) | Ok res -> Lwt.return res -let retrieve_memory memories = - let crash_with msg = raise (Eval.Crash (Source.no_region, msg)) in +let check_memory memories = + let crash_with msg = Error (Eval.Crash (Source.no_region, msg)) in match memories with | Host_funcs.No_memories_during_init -> crash_with "host functions must not access memory during initialisation" | Host_funcs.Available_memories memories when Vector.num_elements memories = 1l -> - Vector.get 0l memories + Ok memories | Host_funcs.Available_memories _ -> crash_with "caller module must have exactly 1 memory instance" +let retrieve_memory memories = + match check_memory memories with + | Ok memories -> Vector.get 0l memories + | Error exn -> raise exn + type read_input_info = {level : int32; id : int32} let read_input_info_encoding = @@ -589,7 +594,10 @@ let write_debug_type = (** [write_debug] is a noop. Switch manually to _alternative_write_debug_impl for printing de debug info *) -let write_debug_impl durable _memories _src _num_bytes = Lwt.return (durable, []) +let write_debug_impl durable memories _src _num_bytes = + match check_memory memories with + | Ok _ -> Lwt.return (durable, []) + | Error exn -> raise exn (* [alternate_write_debug_impl] may be used to replace the default [write_debug_impl] implementation when debugging the PVM/kernel, and @@ -598,11 +606,11 @@ let write_debug_impl durable _memories _src _num_bytes = Lwt.return (durable, [] NB this does slightly change the semantics of 'write_debug', as it may load the memory pointed to by [src] & [num_bytes]. *) -let _alternate_write_debug_impl durable memories src num_bytes = +let alternate_write_debug_impl durable memories src num_bytes = let open Lwt.Syntax in let* memory = retrieve_memory memories in let* result = Aux.read_mem ~memory ~src ~num_bytes in - Printf.printf "DEBUG: %s\n" result ; + Format.printf "DEBUG: %s\n%!" result ; Lwt.return (durable, []) (* [write_debug] accepts a pointer to the start of a sequence of @@ -610,13 +618,21 @@ let _alternate_write_debug_impl durable memories src num_bytes = The PVM, however, does not check that these are valid: from its point of view, [write_debug] is a no-op. *) -let write_debug = - Host_funcs.Host_func - (fun _input_buffer _output_buffer durable memories inputs -> - match inputs with - | [Values.(Num (I32 src)); Values.(Num (I32 num_bytes))] -> - write_debug_impl durable memories src num_bytes - | _ -> raise Bad_input) +let write_debug debug = + if debug then + Host_funcs.Host_func + (fun _input_buffer _output_buffer durable memories inputs -> + match inputs with + | [Values.(Num (I32 src)); Values.(Num (I32 num_bytes))] -> + alternate_write_debug_impl durable memories src num_bytes + | _ -> raise Bad_input) + else + Host_funcs.Host_func + (fun _input_buffer _output_buffer durable memories inputs -> + match inputs with + | [Values.(Num (I32 src)); Values.(Num (I32 num_bytes))] -> + write_debug_impl durable memories src num_bytes + | _ -> raise Bad_input) let store_has_name = "tezos_store_has" @@ -1019,7 +1035,7 @@ let lookup_opt name = let lookup name = match lookup_opt name with Some f -> f | None -> raise Not_found -let register_host_funcs registry = +let register_host_funcs ~enable_debugging registry = List.fold_left (fun _acc (global_name, host_function) -> Host_funcs.register ~global_name host_function registry) @@ -1027,7 +1043,7 @@ let register_host_funcs registry = [ (read_input_name, read_input); (write_output_name, write_output); - (write_debug_name, write_debug); + (write_debug_name, write_debug enable_debugging); (store_has_name, store_has); (store_list_size_name, store_list_size); (store_get_nth_key_name, store_get_nth_key); @@ -1043,7 +1059,14 @@ let register_host_funcs registry = let all = let registry = Host_funcs.empty () in - register_host_funcs registry ; + register_host_funcs ~enable_debugging:false registry ; + registry + +(* We build the registry at toplevel of the module to prevent recomputing it at + each initialization tick. *) +let all_debug = + let registry = Host_funcs.empty () in + register_host_funcs ~enable_debugging:true registry ; registry module Internal_for_tests = struct @@ -1073,4 +1096,6 @@ module Internal_for_tests = struct let store_get_nth_key = Func.HostFunc (store_get_nth_key_type, store_get_nth_key_name) + + let write_debug = Func.HostFunc (write_debug_type, write_debug_name) end diff --git a/src/lib_scoru_wasm/host_funcs.mli b/src/lib_scoru_wasm/host_funcs.mli index c09fd9c0085aeef8e40cf5cbfee1fdbb7197e5f3..1f4ee8026e174d75835aaac1539c75e20cdd39c9 100644 --- a/src/lib_scoru_wasm/host_funcs.mli +++ b/src/lib_scoru_wasm/host_funcs.mli @@ -41,6 +41,10 @@ val lookup_opt : SCORU WASM PVM. *) val all : Tezos_webassembly_interpreter.Host_funcs.registry +(** [all_debug] contains the same functions as [all], with the alternative + implementation of [write_debug]. *) +val all_debug : Tezos_webassembly_interpreter.Host_funcs.registry + exception Bad_input (** A durable key was given by the kernel with a longer-than-allowed length. *) @@ -265,4 +269,6 @@ module Internal_for_tests : sig val store_list_size : Tezos_webassembly_interpreter.Instance.func_inst val store_get_nth_key : Tezos_webassembly_interpreter.Instance.func_inst + + val write_debug : Tezos_webassembly_interpreter.Instance.func_inst end diff --git a/src/lib_scoru_wasm/test/helpers/wasm_utils.ml b/src/lib_scoru_wasm/test/helpers/wasm_utils.ml index 2c2ff782dd7ae14ed3e7aaf4c217f01afbe9b795..8a6661c6a1eeb971cdabde45b0c2b1dd86c19229 100644 --- a/src/lib_scoru_wasm/test/helpers/wasm_utils.ml +++ b/src/lib_scoru_wasm/test/helpers/wasm_utils.ml @@ -75,10 +75,13 @@ end let builtins = (module Builtins : Tezos_scoru_wasm.Builtins.S) -let eval_until_stuck ?(builtins = builtins) ?(max_steps = 20000L) tree = +let eval_until_stuck ?(builtins = builtins) ?(max_steps = 20000L) + ?(debug_flag = false) tree = let open Lwt.Syntax in let rec go counter tree = - let* tree, _ = Wasm.compute_step_many ~builtins ~max_steps tree in + let* tree, _ = + Wasm.compute_step_many ~builtins ~debug_flag ~max_steps tree + in let* stuck = Wasm.Internal_for_tests.is_stuck tree in match stuck with | Some stuck -> Lwt_result.return (stuck, tree) @@ -92,11 +95,16 @@ let eval_until_stuck ?(builtins = builtins) ?(max_steps = 20000L) tree = stop at a Snapshot or an input request, and never start another `kernel_run`. *) let rec eval_to_snapshot ?(builtins = builtins) ?(max_steps = Int64.max_int) - tree = + ?(debug_flag = false) tree = let open Lwt_syntax in let eval tree = let* tree, _ = - Wasm.compute_step_many ~builtins ~stop_at_snapshot:true ~max_steps tree + Wasm.compute_step_many + ~builtins + ~stop_at_snapshot:true + ~debug_flag + ~max_steps + tree in let* state = Wasm.Internal_for_tests.get_tick_state tree in match state with @@ -110,7 +118,8 @@ let rec eval_to_snapshot ?(builtins = builtins) ?(max_steps = Int64.max_int) Stdlib.failwith "Cannot reach snapshot point" let rec eval_until_input_requested ?(builtins = builtins) ?after_fast_exec - ?(fast_exec = false) ?(max_steps = Int64.max_int) tree = + ?(fast_exec = false) ?(max_steps = Int64.max_int) ?(debug_flag = false) tree + = let open Lwt_syntax in let run = if fast_exec then @@ -120,7 +129,7 @@ let rec eval_until_input_requested ?(builtins = builtins) ?after_fast_exec let* info = Wasm.get_info tree in match info.input_request with | No_input_required -> - let* tree, _ = run ~builtins ~max_steps tree in + let* tree, _ = run ~builtins ~debug_flag ~max_steps tree in eval_until_input_requested ~max_steps tree | Input_required | Reveal_required _ -> return tree diff --git a/src/lib_scoru_wasm/test/test_debug.ml b/src/lib_scoru_wasm/test/test_debug.ml new file mode 100644 index 0000000000000000000000000000000000000000..819b0a7bbbaa0c39e0144329186662b17117ecd4 --- /dev/null +++ b/src/lib_scoru_wasm/test/test_debug.ml @@ -0,0 +1,170 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +(** Testing + ------- + Component: Tree_encoding_decoding + Invocation: dune exec src/lib_scoru_wasm/test/test_scoru_wasm.exe \ + -- test "^Debug$" + Subject: Debug facilities tests for the tezos-scoru-wasm library +*) + +open Tztest +open Tezos_lazy_containers +open Tezos_webassembly_interpreter +open Tezos_scoru_wasm + +let write_debug ~debug ~init ~values memories = + let input = Input_buffer.alloc () in + let module_inst = Tezos_webassembly_interpreter.Instance.empty_module_inst in + let memories = + List.fold_left + (fun memories memory -> Lazy_vector.Int32Vector.cons memory memories) + module_inst.memories + memories + in + let module_inst = {module_inst with memories} in + + let module_reg = Instance.ModuleMap.create () in + let module_key = Instance.Module_key "test" in + Instance.update_module_ref module_reg module_key module_inst ; + Eval.invoke + ~module_reg + ~caller:module_key + (if debug then Host_funcs.all_debug else Host_funcs.all) + ~input + ~init + Host_funcs.Internal_for_tests.write_debug + values + +let test_write_debug_ok () = + let open Lwt_syntax in + let memories_ok = + [Memory.alloc (MemoryType Types.{min = 20l; max = Some 3600l})] + in + let values = Values.[Num (I32 4l); Num (I32 10l)] in + let* _, result_noop = + write_debug ~debug:false ~init:false ~values memories_ok + in + + let* _, result_alternative = + write_debug ~debug:true ~init:false ~values memories_ok + in + assert (result_noop = result_alternative) ; + return_ok_unit + +let test_write_debug_init () = + let open Lwt_syntax in + let memories = + [Memory.alloc (MemoryType Types.{min = 20l; max = Some 3600l})] + in + let values = Values.[Num (I32 4l); Num (I32 10l)] in + let* () = + Lwt.catch + (fun () -> + let* _, _ = write_debug ~debug:false ~init:true ~values memories in + assert false) + (function Eval.Crash _ -> Lwt.return_unit | _ -> assert false) + in + let* () = + Lwt.catch + (fun () -> + let* _, _ = write_debug ~debug:true ~init:true ~values memories in + assert false) + (function Eval.Crash _ -> Lwt.return_unit | _ -> assert false) + in + return_ok_unit + +let test_write_debug_too_many_memories () = + let open Lwt_syntax in + let memories_two = + [ + Memory.alloc (MemoryType Types.{min = 20l; max = Some 3600l}); + Memory.alloc (MemoryType Types.{min = 20l; max = Some 3600l}); + ] + in + let values = Values.[Num (I32 4l); Num (I32 10l)] in + let* () = + Lwt.catch + (fun () -> + let* _, _ = write_debug ~debug:false ~init:false ~values memories_two in + assert false) + (function Eval.Crash _ -> Lwt.return_unit | _ -> assert false) + in + let* () = + Lwt.catch + (fun () -> + let* _, _ = write_debug ~debug:false ~init:false ~values memories_two in + assert false) + (function Eval.Crash _ -> Lwt.return_unit | _ -> assert false) + in + return_ok_unit + +let test_write_debug_invalid_length () = + let open Lwt_syntax in + let memory = Memory.alloc (MemoryType Types.{min = 0l; max = Some 1l}) in + let offset = 0l in + let length = 10_000l in + assert (Memory.bound memory < Int64.of_int32 (Int32.add offset length)) ; + + let values = Values.[Num (I32 offset); Num (I32 length)] in + let* _, result_noop = write_debug ~debug:false ~init:false ~values [memory] in + + let* _, result_alternative = + write_debug ~debug:true ~init:false ~values [memory] + in + assert (result_noop = result_alternative) ; + return_ok_unit + +let test_write_debug_invalid_offset () = + let open Lwt_syntax in + let memory = Memory.alloc (MemoryType Types.{min = 0l; max = Some 1l}) in + let offset = 10_000l in + assert (Memory.bound memory < Int64.of_int32 offset) ; + let values = Values.[Num (I32 offset); Num (I32 10l)] in + let* _, result_noop = write_debug ~debug:false ~init:false ~values [memory] in + let* _, result_alternative = + write_debug ~debug:true ~init:false ~values [memory] + in + assert (result_noop = result_alternative) ; + return_ok_unit + +let tests = + [ + tztest "debug on correct inputs and memory" `Quick test_write_debug_ok; + tztest + "debug on more than one memory" + `Quick + test_write_debug_too_many_memories; + tztest "debug during init" `Quick test_write_debug_init; + tztest + "debug with inputs outside of the memory" + `Quick + test_write_debug_invalid_length; + tztest + "debug with inputs outside of the memory" + `Quick + test_write_debug_invalid_offset; + ] diff --git a/src/lib_scoru_wasm/test/test_fast.ml b/src/lib_scoru_wasm/test/test_fast.ml index 0f0c548a78f74cbc3b1efd5e9d514142731adf35..82192cf908a967354b580c09c1bab5edc953e9ed 100644 --- a/src/lib_scoru_wasm/test/test_fast.ml +++ b/src/lib_scoru_wasm/test/test_fast.ml @@ -325,6 +325,7 @@ let test_compute_step_many_pauses_at_snapshot_when_flag_set = Wasm_utils.Wasm_fast.compute_step_many ~builtins ~stop_at_snapshot:true + ~debug_flag:false ~max_steps:Int64.max_int tree in diff --git a/src/lib_scoru_wasm/test/test_scoru_wasm.ml b/src/lib_scoru_wasm/test/test_scoru_wasm.ml index 6d071aab65304dc44b42ab7bee074c5289b72ec9..014330adb8f643ee92d166ae9b90b6e1b9d8c7e1 100644 --- a/src/lib_scoru_wasm/test/test_scoru_wasm.ml +++ b/src/lib_scoru_wasm/test/test_scoru_wasm.ml @@ -50,5 +50,6 @@ let () = ("Reveal", Test_reveal.tests); ("Fast Execution", Test_fast.tests); ("Fast Execution cache", Test_fast_cache.tests); + ("Debug", Test_debug.tests); ] |> Lwt_main.run diff --git a/src/lib_scoru_wasm/test/test_wasm_vm.ml b/src/lib_scoru_wasm/test/test_wasm_vm.ml index bcccb291a7f4aea2b1613ba8b963241d4fbb6ed1..1f9c24b555156e25a472b6174b415b3d75617aa6 100644 --- a/src/lib_scoru_wasm/test/test_wasm_vm.ml +++ b/src/lib_scoru_wasm/test/test_wasm_vm.ml @@ -36,6 +36,7 @@ let test_padding_state () = in let*! pvm_state, _ = Wasm_vm.compute_step_many_until + ~debug_flag:false ~max_steps:Int64.max_int should_continue pvm_state diff --git a/src/lib_scoru_wasm/wasm_pvm.ml b/src/lib_scoru_wasm/wasm_pvm.ml index e653f9250dbcb34b367126e3ca56b6ed2c349f93..800a2ddc5460514f9e5a448b0fa4ab8a1126aeb8 100644 --- a/src/lib_scoru_wasm/wasm_pvm.ml +++ b/src/lib_scoru_wasm/wasm_pvm.ml @@ -272,21 +272,31 @@ module Make_pvm (Wasm_vm : Wasm_vm_sig.S) (T : Tezos_tree_encoding.TREE) : in encode pvm tree - let compute_step_many ?builtins ?stop_at_snapshot ~max_steps tree = + let compute_step_many ?builtins ?stop_at_snapshot ?debug_flag ~max_steps tree + = let open Lwt.Syntax in let* pvm_state = decode tree in let* pvm_state, executed_ticks = - Wasm_vm.compute_step_many ?builtins ?stop_at_snapshot ~max_steps pvm_state + Wasm_vm.compute_step_many + ?builtins + ?stop_at_snapshot + ?debug_flag + ~max_steps + pvm_state in let+ tree = encode pvm_state tree in (tree, executed_ticks) - let compute_step tree = + let compute_step_with_debug ~debug_flag tree = let open Lwt.Syntax in let* initial_state = decode tree in - let* final_state = Wasm_vm.compute_step initial_state in + let* final_state = + Wasm_vm.compute_step_with_debug ~debug_flag initial_state + in encode final_state tree + let compute_step tree = compute_step_with_debug ~debug_flag:false tree + let get_output output_info tree = let open Lwt_syntax in let* candidate = @@ -389,7 +399,7 @@ module Make_pvm (Wasm_vm : Wasm_vm_sig.S) (T : Tezos_tree_encoding.TREE) : pvm.buffers.input let compute_step_many_with_hooks ?builtins ?after_fast_exec - ?stop_at_snapshot ~max_steps tree = + ?stop_at_snapshot ?debug_flag ~max_steps tree = let open Lwt.Syntax in let* pvm_state = Tree_encoding_runner.decode pvm_state_encoding tree in let* pvm_state, ticks = @@ -397,6 +407,7 @@ module Make_pvm (Wasm_vm : Wasm_vm_sig.S) (T : Tezos_tree_encoding.TREE) : ?builtins ?after_fast_exec ?stop_at_snapshot + ?debug_flag ~max_steps pvm_state in diff --git a/src/lib_scoru_wasm/wasm_vm.ml b/src/lib_scoru_wasm/wasm_vm.ml index bb97ee53478244d5c751f24ef0d3bb668f4e9de4..22d703fbdb1afe22eec1046c1914377d9eba54e2 100644 --- a/src/lib_scoru_wasm/wasm_vm.ml +++ b/src/lib_scoru_wasm/wasm_vm.ml @@ -115,11 +115,15 @@ let save_fallback_kernel durable = Constants.kernel_fallback_key else Lwt.return durable -let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = +let unsafe_next_tick_state ~debug_flag + ({buffers; durable; tick_state; _} as pvm_state) = let open Lwt_syntax in let return ?(status = Running) ?(durable = durable) state = Lwt.return (durable, state, status) in + let host_function_registry = + if debug_flag then Host_funcs.all_debug else Host_funcs.all + in match tick_state with | Stuck ((Decode_error _ | Init_error _ | Link_error _) as e) -> let cause = @@ -233,7 +237,7 @@ let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = (* Clear the values and the locals in the frame. *) let config = Wasm.Eval.config - Host_funcs.all + host_function_registry self (Tezos_lazy_containers.Lazy_vector.Int32Vector.empty ()) (Tezos_lazy_containers.Lazy_vector.Int32Vector.singleton @@ -261,7 +265,7 @@ let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = ~module_reg ~self buffers - Host_funcs.all + host_function_registry ast_module init_kont in @@ -290,7 +294,7 @@ let unsafe_next_tick_state ({buffers; durable; tick_state; _} as pvm_state) = let durable' = Durable.of_storage ~default:durable store' in return ~durable:durable' (Eval {config; module_reg}) -let next_tick_state pvm_state = +let next_tick_state ~debug_flag pvm_state = let to_stuck exn = let error = Wasm_pvm_errors.extract_interpreter_error exn in let wasm_error = @@ -307,7 +311,7 @@ let next_tick_state pvm_state = in Lwt.return (pvm_state.durable, Stuck wasm_error, Failing) in - Lwt.catch (fun () -> unsafe_next_tick_state pvm_state) to_stuck + Lwt.catch (fun () -> unsafe_next_tick_state ~debug_flag pvm_state) to_stuck let next_last_top_level_call {current_tick; last_top_level_call; _} = function | Forcing_yield | Yielding | Reboot -> Z.succ current_tick @@ -389,10 +393,10 @@ let clean_up_input_buffer buffers = (** [compute_step pvm_state] does one computation step on [pvm_state]. Returns the new state. *) -let compute_step pvm_state = +let compute_step_with_debug ~debug_flag pvm_state = let open Lwt_syntax in (* Calculate the next tick state. *) - let* durable, tick_state, status = next_tick_state pvm_state in + let* durable, tick_state, status = next_tick_state ~debug_flag pvm_state in let current_tick = Z.succ pvm_state.current_tick in let last_top_level_call = next_last_top_level_call pvm_state status in let reboot_counter = next_reboot_counter pvm_state status in @@ -412,6 +416,8 @@ let compute_step pvm_state = in return pvm_state +let compute_step pvm_state = compute_step_with_debug ~debug_flag:false pvm_state + let input_request pvm_state = match pvm_state.tick_state with | Stuck (Decode_error _ | Init_error _ | Link_error _) -> @@ -440,7 +446,7 @@ let measure_executed_ticks (transition : pvm_state -> pvm_state Lwt.t) let ticks_executed = final_state.current_tick - initial_state.current_tick in (final_state, to_int64 ticks_executed) -let compute_step_many_until ?(max_steps = 1L) should_continue = +let compute_step_many_until ?(max_steps = 1L) ~debug_flag should_continue = let open Lwt.Syntax in assert (max_steps > 0L) ; let rec go steps_left pvm_state = @@ -462,14 +468,14 @@ let compute_step_many_until ?(max_steps = 1L) should_continue = in go (Int64.sub steps_left (Z.to_int64 bulk_ticks)) pvm_state else - let* pvm_state = compute_step pvm_state in + let* pvm_state = compute_step_with_debug ~debug_flag pvm_state in go (Int64.pred steps_left) pvm_state else Lwt.return pvm_state in let one_or_more_steps pvm_state = (* Make sure we perform at least 1 step. The assertion above ensures that we were asked to perform at least 1. *) - let* pvm_state = compute_step pvm_state in + let* pvm_state = compute_step_with_debug ~debug_flag pvm_state in go (Int64.pred max_steps) pvm_state in measure_executed_ticks one_or_more_steps @@ -480,10 +486,11 @@ let should_compute pvm_state = | Reveal_required _ | Input_required -> false | No_input_required -> true -let compute_step_many ?builtins:_ ?(stop_at_snapshot = false) ~max_steps - pvm_state = +let compute_step_many ?builtins:_ ?(stop_at_snapshot = false) + ?(debug_flag = false) ~max_steps pvm_state = compute_step_many_until ~max_steps + ~debug_flag (fun pvm_state -> Lwt.return (* should_compute && (stop_at_snapshot -> tick_state <> snapshot) *) diff --git a/src/lib_scoru_wasm/wasm_vm.mli b/src/lib_scoru_wasm/wasm_vm.mli index d5a482a2625715e8402b38b4797006c6f5374e56..5b38dc1d2533fd3a2005cbef0f52fd21b96ea216 100644 --- a/src/lib_scoru_wasm/wasm_vm.mli +++ b/src/lib_scoru_wasm/wasm_vm.mli @@ -41,6 +41,7 @@ include Wasm_vm_sig.S *) val compute_step_many_until : ?max_steps:int64 -> + debug_flag:bool -> (pvm_state -> bool Lwt.t) -> pvm_state -> (pvm_state * int64) Lwt.t diff --git a/src/lib_scoru_wasm/wasm_vm_sig.ml b/src/lib_scoru_wasm/wasm_vm_sig.ml index ed619d7f270ca37b243aa23f496d2271f33d6949..576a5df6e84e966aef38ce3f3613da6cc0b1d671 100644 --- a/src/lib_scoru_wasm/wasm_vm_sig.ml +++ b/src/lib_scoru_wasm/wasm_vm_sig.ml @@ -33,6 +33,7 @@ module type Internal_for_tests = sig ?builtins:Builtins.t -> ?after_fast_exec:(unit -> unit) -> ?stop_at_snapshot:bool -> + ?debug_flag:bool -> max_steps:int64 -> state -> (state * int64) Lwt.t @@ -52,6 +53,7 @@ module type Generic = sig val compute_step_many : ?builtins:Builtins.t -> ?stop_at_snapshot:bool -> + ?debug_flag:bool -> max_steps:int64 -> state -> (state * int64) Lwt.t @@ -62,6 +64,10 @@ module type Generic = sig [compute_step_many ~max_step=1 pvm_state]. *) val compute_step : state -> state Lwt.t + (** [compute_step_with_debug ~debug_flag pvm_state] is exactly [compute_step] + but it has the ability to enable the debugging host functions. *) + val compute_step_with_debug : debug_flag:bool -> state -> state Lwt.t + (** [set_input_step input_info message pvm_state] forwards the VM by one input tick. If the VM is not expecting input, it gets stuck. If the VM is already stuck, this function may raise an exception. Note at this point diff --git a/src/lib_webassembly/exec/eval.ml b/src/lib_webassembly/exec/eval.ml index 9b3ceb9057b5b81edb177869d2fa893df97d2b4c..fdc1cf5d82092605f61746c5bc718ebaa0a953c2 100644 --- a/src/lib_webassembly/exec/eval.ml +++ b/src/lib_webassembly/exec/eval.ml @@ -1544,7 +1544,7 @@ let step ?(init = false) ?(durable = Durable_storage.empty) module_reg c buffers in (durable, {c with step_kont}) -let rec eval durable module_reg (c : config) buffers : +let rec eval ?(init = false) durable module_reg (c : config) buffers : (Durable_storage.t * value list) Lwt.t = match c.step_kont with | SK_Result vs -> @@ -1552,8 +1552,8 @@ let rec eval durable module_reg (c : config) buffers : (durable, values) | SK_Trapped {it = msg; at} -> Trap.error at msg | _ -> - let* durable, c = step ~init:false ~durable module_reg c buffers in - eval durable module_reg c buffers + let* durable, c = step ~init ~durable module_reg c buffers in + eval ~init durable module_reg c buffers type reveal_error = | Reveal_step @@ -1604,7 +1604,7 @@ let reveal_step reveal module_reg payload = let invoke ~module_reg ~caller ?(input = Input_buffer.alloc ()) ?(output = default_output_buffer ()) ?(durable = Durable_storage.empty) - host_funcs (func : func_inst) (vs : value list) : + ?(init = false) host_funcs (func : func_inst) (vs : value list) : (Durable_storage.t * value list) Lwt.t = let at = match func with Func.AstFunc (_, _, f) -> f.at | _ -> no_region in let (FuncType (ins, out)) = Func.type_of func in @@ -1633,7 +1633,7 @@ let invoke ~module_reg ~caller ?(input = Input_buffer.alloc ()) let buffers = buffers ~input ~output () in Lwt.catch (fun () -> - let+ durable, values = eval durable module_reg c buffers in + let+ durable, values = eval ~init durable module_reg c buffers in (durable, List.rev values)) (function | Stack_overflow -> Exhaustion.error at "call stack exhausted" diff --git a/src/lib_webassembly/exec/eval.mli b/src/lib_webassembly/exec/eval.mli index 325b517c8b4b42ba4f4c5f7491e0ce1bcc9a4ad4..4340024a382187ad63a0d6e0dc0ed7450fbd856f 100644 --- a/src/lib_webassembly/exec/eval.mli +++ b/src/lib_webassembly/exec/eval.mli @@ -260,6 +260,7 @@ val invoke : ?input:Input_buffer.t -> ?output:Output_buffer.t -> ?durable:Durable_storage.t -> + ?init:bool -> Host_funcs.registry -> func_inst -> value list -> diff --git a/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml index e4fca38ca161c355fbbf553d4d227bd30c45a117..7197297ae7b88e1c949329e8b0e4b41b903dcdd0 100644 --- a/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml @@ -88,7 +88,7 @@ module Impl : Pvm.S = struct module Backend = Make_backend (Wasm_2_0_0_proof_format.Tree) - let eval_many = Backend.compute_step_many + let eval_many = Backend.compute_step_many ~debug_flag:false end include Impl diff --git a/src/proto_alpha/bin_wasm_repl/commands.ml b/src/proto_alpha/bin_wasm_repl/commands.ml index 84b1b1f937e69aff0ba4934a13d9057d04f93f58..4691ff0aaac714282367bc4bfd9c9a2221525647 100644 --- a/src/proto_alpha/bin_wasm_repl/commands.ml +++ b/src/proto_alpha/bin_wasm_repl/commands.ml @@ -80,7 +80,7 @@ let parse_commands s = let compute_step tree = let open Lwt_syntax in trap_exn (fun () -> - let+ tree = Wasm.compute_step tree in + let+ tree = Wasm.compute_step_with_debug ~debug_flag:true tree in (tree, 1L)) (** [eval_to_result tree] tries to evaluates the PVM until the next `SK_Result` @@ -118,6 +118,7 @@ let eval_to_result tree = in let* pvm_state, ticks = Tezos_scoru_wasm.Wasm_vm.compute_step_many_until + ~debug_flag:true ~max_steps:Int64.max_int should_compute pvm_state @@ -146,7 +147,12 @@ let eval_until_input_requested tree = let open Lwt_syntax in trap_exn (fun () -> let* info_before = Wasm.get_info tree in - let* tree = eval_until_input_requested ~max_steps:Int64.max_int tree in + let* tree = + eval_until_input_requested + ~debug_flag:true + ~max_steps:Int64.max_int + tree + in let+ info_after = Wasm.get_info tree in ( tree, Z.to_int64 @@ Z.sub info_after.current_tick info_before.current_tick ))