From b9f5daeb561380eea2fb87fc1584ad16d7649863 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 5 Dec 2022 15:23:13 +0100 Subject: [PATCH 1/5] WASM: alternative host functions registry for debug --- src/lib_scoru_wasm/host_funcs.ml | 37 ++++++++++++++++++++++--------- src/lib_scoru_wasm/host_funcs.mli | 4 ++++ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index c0cc37293674..b64455104e1e 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -598,7 +598,7 @@ 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 @@ -610,13 +610,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 +1027,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 +1035,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 +1051,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 diff --git a/src/lib_scoru_wasm/host_funcs.mli b/src/lib_scoru_wasm/host_funcs.mli index c09fd9c0085a..ca9bbdb3e557 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. *) -- GitLab From 7ffcbc967394d6f5568f7eb64e30c7586659e3d9 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 5 Dec 2022 15:53:53 +0100 Subject: [PATCH 2/5] WASM/PVM: add debug aware version of compute(s) --- src/lib_scoru_wasm/bench/exec.ml | 1 + src/lib_scoru_wasm/fast/vm.ml | 13 ++++++-- src/lib_scoru_wasm/test/helpers/wasm_utils.ml | 21 +++++++++---- src/lib_scoru_wasm/test/test_fast.ml | 1 + src/lib_scoru_wasm/test/test_wasm_vm.ml | 1 + src/lib_scoru_wasm/wasm_pvm.ml | 21 ++++++++++--- src/lib_scoru_wasm/wasm_vm.ml | 31 ++++++++++++------- src/lib_scoru_wasm/wasm_vm.mli | 1 + src/lib_scoru_wasm/wasm_vm_sig.ml | 6 ++++ .../bin_sc_rollup_node/wasm_2_0_0_pvm.ml | 2 +- src/proto_alpha/bin_wasm_repl/commands.ml | 10 ++++-- 11 files changed, 79 insertions(+), 29 deletions(-) diff --git a/src/lib_scoru_wasm/bench/exec.ml b/src/lib_scoru_wasm/bench/exec.ml index 34196e72802c..d91647a8164d 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/vm.ml b/src/lib_scoru_wasm/fast/vm.ml index 258c642721a5..d6ba1d56a127 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 @@ @@ -56,8 +57,8 @@ 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 = @@ -65,7 +66,12 @@ let rec compute_step_many accum_ticks ?builtins 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,6 +90,7 @@ 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) diff --git a/src/lib_scoru_wasm/test/helpers/wasm_utils.ml b/src/lib_scoru_wasm/test/helpers/wasm_utils.ml index 2c2ff782dd7a..8a6661c6a1ee 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_fast.ml b/src/lib_scoru_wasm/test/test_fast.ml index 0f0c548a78f7..82192cf908a9 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_wasm_vm.ml b/src/lib_scoru_wasm/test/test_wasm_vm.ml index bcccb291a7f4..1f9c24b55515 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 e653f9250dbc..800a2ddc5460 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 bb97ee534782..22d703fbdb1a 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 d5a482a26257..5b38dc1d2533 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 ed619d7f270c..576a5df6e84e 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/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 e4fca38ca161..7197297ae7b8 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 84b1b1f937e6..4691ff0aaac7 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 )) -- GitLab From 4ebdc7b62d95b2969325c8eb6fb74ca907c6db3c Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 5 Dec 2022 16:26:14 +0100 Subject: [PATCH 3/5] WASM/Host_functions: enforce flushing the output in write_debug --- src/lib_scoru_wasm/host_funcs.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index b64455104e1e..97a6fc584a36 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -602,7 +602,7 @@ 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 -- GitLab From 653836573cb212253482eceb31c16dc3a1236e5e Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 6 Dec 2022 11:50:16 +0100 Subject: [PATCH 4/5] WASM/PVM: ensure both write_debug implementation behave the same --- src/lib_scoru_wasm/host_funcs.ml | 18 ++- src/lib_scoru_wasm/host_funcs.mli | 2 + src/lib_scoru_wasm/test/test_debug.ml | 170 +++++++++++++++++++++ src/lib_scoru_wasm/test/test_scoru_wasm.ml | 1 + src/lib_webassembly/exec/eval.ml | 10 +- src/lib_webassembly/exec/eval.mli | 1 + 6 files changed, 193 insertions(+), 9 deletions(-) create mode 100644 src/lib_scoru_wasm/test/test_debug.ml diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 97a6fc584a36..8fa9cfe357aa 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 @@ -1088,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 ca9bbdb3e557..1f4ee8026e17 100644 --- a/src/lib_scoru_wasm/host_funcs.mli +++ b/src/lib_scoru_wasm/host_funcs.mli @@ -269,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/test_debug.ml b/src/lib_scoru_wasm/test/test_debug.ml new file mode 100644 index 000000000000..819b0a7bbbaa --- /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_scoru_wasm.ml b/src/lib_scoru_wasm/test/test_scoru_wasm.ml index 6d071aab6530..014330adb8f6 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_webassembly/exec/eval.ml b/src/lib_webassembly/exec/eval.ml index 9b3ceb9057b5..fdc1cf5d8209 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 325b517c8b4b..4340024a3821 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 -> -- GitLab From 9643971fc633b06edc645d61acf24edc20fa6f9d Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 12 Dec 2022 10:43:32 +0100 Subject: [PATCH 5/5] WASM/Fast: make debug output opt-out and enablable --- src/lib_scoru_wasm/fast/exec.ml | 4 +-- src/lib_scoru_wasm/fast/exec.mli | 7 +++- src/lib_scoru_wasm/fast/funcs.ml | 53 ++++++++++++++++--------------- src/lib_scoru_wasm/fast/funcs.mli | 1 + src/lib_scoru_wasm/fast/vm.ml | 11 +++++-- 5 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/lib_scoru_wasm/fast/exec.ml b/src/lib_scoru_wasm/fast/exec.ml index 352d6cf3ea02..0a3682132441 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 0c0b3626de7f..6fad9956c7cb 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 464fb7f21d0b..e560570b821c 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 e7cbd1f4af8c..b46ea495c5ef 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 d6ba1d56a127..82149aad6f1b 100644 --- a/src/lib_scoru_wasm/fast/vm.ml +++ b/src/lib_scoru_wasm/fast/vm.ml @@ -40,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 @@ -64,6 +66,7 @@ let rec compute_step_many accum_ticks ?builtins 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 @@ -97,7 +100,9 @@ let rec compute_step_many accum_ticks ?builtins | _ -> 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 -- GitLab