diff --git a/src/lib_scoru_wasm/host_funcs.ml b/src/lib_scoru_wasm/host_funcs.ml index 3ec2cecf355b54414b404ef504305154d42e91f4..6b2334a5f7a1138dc1be919e7c8e64f9d3443d8f 100644 --- a/src/lib_scoru_wasm/host_funcs.ml +++ b/src/lib_scoru_wasm/host_funcs.ml @@ -595,7 +595,38 @@ module Aux = struct include Make (Memory_access_interpreter) end -let default_ticks = Z.zero +module Tick_model = struct + include Tezos_webassembly_interpreter.Tick_model + + (* Note that we cannot have negative key length since their unsigned + representation is taken into account, and it will result in an error before + actually reading the memory. As such, it is safe to use the "unsafe" + version here. *) + + let read_key_in_memory key_length = + of_int32_exn key_length * ticks_per_byte_read + + let value_written_in_memory value_size = + of_int32_exn value_size * ticks_per_byte_written + + let value_read_from_memory value_size = + of_int32_exn value_size * ticks_per_byte_read + + let tree_access = one + + let tree_move = one + + let tree_copy = one + + let tree_deletion = one + + let tree_write = one + + let tree_read = one + + let with_error result compute_ticks = + if result < 0l then nop else compute_ticks () +end let value i = Values.(Num (I32 i)) @@ -608,10 +639,9 @@ let read_input_type = let read_input_name = "tezos_read_input" -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let read_input_ticks _size = default_ticks +let read_input_ticks input_size = + Tick_model.( + with_error input_size (fun () -> value_written_in_memory input_size) |> to_z) let read_input = Host_funcs.Host_func @@ -624,10 +654,11 @@ let read_input = Values.(Num (I32 max_bytes)); ] -> let* memory = retrieve_memory memories in - let* read_bytes = + let* written_bytes = Aux.read_input ~input_buffer ~memory ~info_addr ~dst ~max_bytes in - Lwt.return (durable, [value read_bytes], read_input_ticks read_bytes) + Lwt.return + (durable, [value written_bytes], read_input_ticks written_bytes) | _ -> raise Bad_input) let write_output_name = "tezos_write_output" @@ -639,10 +670,10 @@ let write_output_type = let output_types = Types.[NumType I32Type] |> Vector.of_list in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let write_output_ticks _size = default_ticks +let write_output_ticks output_size = + Tick_model.( + with_error output_size (fun () -> value_read_from_memory output_size) + |> to_z) let write_output = Host_funcs.Host_func @@ -682,7 +713,7 @@ let write_debug ~implem = let+ () = run ~memory ~src ~num_bytes in (* Write_debug is considered a no-op, it shouldn't take more than the default ticks. *) - (durable, [], default_ticks) + (durable, [], Tick_model.(to_z nop)) | _ -> raise Bad_input) let store_has_name = "tezos_store_has" @@ -694,10 +725,10 @@ let store_has_type = let output_types = Types.[NumType I32Type] |> Vector.of_list in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_has_ticks _size = default_ticks +let store_has_ticks key_size result = + Tick_model.( + with_error result (fun () -> read_key_in_memory key_size + tree_access) + |> to_z) let store_has = Host_funcs.Host_func @@ -713,7 +744,7 @@ let store_has = ~key_offset ~key_length in - (durable, [value r], store_has_ticks 0l) + (durable, [value r], store_has_ticks key_length r) | _ -> raise Bad_input) let store_delete_name = "tezos_store_delete" @@ -725,10 +756,10 @@ let store_delete_type = let output_types = Vector.of_list [Types.NumType I32Type] in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_delete_ticks _size = default_ticks +let store_delete_ticks key_size result = + Tick_model.( + with_error result (fun () -> read_key_in_memory key_size + tree_deletion) + |> to_z) let store_delete = Host_funcs.Host_func @@ -744,7 +775,9 @@ let store_delete = ~key_offset ~key_length in - (Durable.to_storage durable, [value code], store_delete_ticks 0l) + ( Durable.to_storage durable, + [value code], + store_delete_ticks key_length code ) | _ -> raise Bad_input) let store_value_size_name = "tezos_store_value_size" @@ -757,10 +790,10 @@ let store_value_size_type = let output_types = Vector.of_list Types.[NumType I32Type] in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_value_size_ticks _size = default_ticks +let store_value_size_ticks key_size result = + Tick_model.( + with_error result (fun () -> read_key_in_memory key_size + tree_access) + |> to_z) let store_value_size = let open Lwt_syntax in @@ -777,7 +810,7 @@ let store_value_size = ~key_offset ~key_length in - (durable, [value res], store_value_size_ticks 0l) + (durable, [value res], store_value_size_ticks key_length res) | _ -> raise Bad_input) let store_list_size_name = "tezos_store_list_size" @@ -790,10 +823,10 @@ let store_list_size_type = let output_types = Types.[NumType I64Type] |> Vector.of_list in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_list_size_ticks _size = default_ticks +let store_list_size_ticks key_size result = + Tick_model.( + with_error result (fun () -> read_key_in_memory key_size + tree_access) + |> to_z) let store_list_size = Host_funcs.Host_func @@ -811,7 +844,7 @@ let store_list_size = in ( Durable.to_storage durable, [Values.(Num (I64 result))], - store_list_size_ticks 0l ) + store_list_size_ticks key_length (Int64.to_int32 result) ) | _ -> raise Bad_input) let store_get_nth_key_name = "tezos_store_get_nth_key_list" @@ -831,10 +864,10 @@ let store_get_nth_key_type = let output_types = Types.[NumType I32Type] |> Vector.of_list in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_get_nth_key_ticks _size = default_ticks +let store_get_nth_key_ticks key_size result = + Tick_model.( + with_error result (fun () -> read_key_in_memory key_size + tree_access) + |> to_z) let store_get_nth_key = Host_funcs.Host_func @@ -860,7 +893,7 @@ let store_get_nth_key = ~dst ~max_size in - (durable, [value result], store_get_nth_key_ticks 0l) + (durable, [value result], store_get_nth_key_ticks key_length result) | _ -> raise Bad_input) let store_copy_name = "tezos_store_copy" @@ -874,10 +907,13 @@ let store_copy_type = let output_types = Vector.of_list Types.[NumType I32Type] in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_copy_ticks _size = default_ticks +let store_copy_ticks from_key_size to_key_size result = + Tick_model.( + with_error result (fun () -> + read_key_in_memory from_key_size + + read_key_in_memory to_key_size + + tree_copy) + |> to_z) let store_copy = Host_funcs.Host_func @@ -901,7 +937,9 @@ let store_copy = ~to_key_offset ~to_key_length in - (Durable.to_storage durable, [value code], store_copy_ticks 0l) + ( Durable.to_storage durable, + [value code], + store_copy_ticks from_key_length to_key_length code ) | _ -> raise Bad_input) let store_move_name = "tezos_store_move" @@ -915,10 +953,13 @@ let store_move_type = let output_types = Vector.of_list Types.[NumType I32Type] in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_move_ticks _size = default_ticks +let store_move_ticks from_key_size to_key_size result = + Tick_model.( + with_error result (fun () -> + read_key_in_memory from_key_size + + read_key_in_memory to_key_size + + tree_move) + |> to_z) let store_move = Host_funcs.Host_func @@ -942,7 +983,9 @@ let store_move = ~to_key_offset ~to_key_length in - (Durable.to_storage durable, [value code], store_move_ticks 0l) + ( Durable.to_storage durable, + [value code], + store_move_ticks to_key_length from_key_length code ) | _ -> raise Bad_input) let store_read_name = "tezos_store_read" @@ -962,10 +1005,13 @@ let store_read_type = let output_types = Vector.of_list Types.[NumType I32Type] in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_read_ticks _size = default_ticks +let store_read_ticks key_size value_size = + Tick_model.( + with_error value_size (fun () -> + read_key_in_memory key_size + + tree_access + + value_written_in_memory value_size) + |> to_z) let store_read = Host_funcs.Host_func @@ -990,7 +1036,7 @@ let store_read = ~dest ~max_bytes in - (durable, [value len], store_read_ticks 0l) + (durable, [value len], store_read_ticks key_length len) | _ -> raise Bad_input) let reveal_preimage_name = "tezos_reveal_preimage" @@ -1059,10 +1105,13 @@ let store_write_type = let output_types = Vector.of_list Types.[NumType I32Type] in Types.FuncType (input_types, output_types) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. -*) -let store_write_ticks _size = default_ticks +let store_write_ticks key_size value_size result = + Tick_model.( + with_error result (fun () -> + read_key_in_memory key_size + + tree_access + + value_read_from_memory value_size) + |> to_z) let store_write = Host_funcs.Host_func @@ -1088,7 +1137,9 @@ let store_write = ~src ~num_bytes in - (Durable.to_storage durable, [value code], store_write_ticks 0l) + ( Durable.to_storage durable, + [value code], + store_write_ticks key_length num_bytes code ) | _ -> raise Bad_input) let lookup_opt name = diff --git a/src/lib_scoru_wasm/host_funcs.mli b/src/lib_scoru_wasm/host_funcs.mli index 5cf9600fd0216c5efa8eb67a9187834fe47fe6af..cdfadd320fab602d19ae57b8693b7756a9f379f1 100644 --- a/src/lib_scoru_wasm/host_funcs.mli +++ b/src/lib_scoru_wasm/host_funcs.mli @@ -260,6 +260,46 @@ module Aux : sig S with type memory = Tezos_webassembly_interpreter.Instance.memory_inst end +(** Defines the tick consumption of memory access for the host functions. *) +module Tick_model : sig + include module type of Tezos_webassembly_interpreter.Tick_model + + (* [read_key_in_memory length] returns the tick consumption of reading a + key of [length] bytes from the memory. *) + val read_key_in_memory : int32 -> tick + + (* [value_written_in_memory length] returns the tick consumption of writing a + value of [length] bytes to the memory. *) + val value_written_in_memory : int32 -> tick + + (* [value_copied_in_memory length] returns the tick consumption of reading a + value of [length] bytes from the memory. *) + val value_read_from_memory : int32 -> tick + + (* [tree_access] is tick consumption of accessing a tree in the durable + storage. *) + val tree_access : tick + + (* [tree_move] is tick consumption of moving a tree in the durable storage. *) + val tree_move : tick + + (* [tree_copy] is tick consumption of copying a tree in the durable + storage. *) + val tree_copy : tick + + (* [tree_deletion] is tick consumption of deleting a tree in the durable + storage. *) + val tree_deletion : tick + + (* [tree_write] is tick consumption of writing a tree in the durable + storage. *) + val tree_write : tick + + (* [tree_read] is tick consumption of reading a tree in the durable + storage. *) + val tree_read : tick +end + module Internal_for_tests : sig (** The number of bytes used to store the metadata of a rollup in memory *) val metadata_size : int diff --git a/src/lib_webassembly/exec/eval.ml b/src/lib_webassembly/exec/eval.ml index d87ce7305716f7f09f74046f6a99b9f1cb065230..3fba1c6e559ae35914c350c5a2dbccb796c9ebdf 100644 --- a/src/lib_webassembly/exec/eval.ml +++ b/src/lib_webassembly/exec/eval.ml @@ -1607,10 +1607,10 @@ let reveal_step reveal module_reg payload = in let vs, es = inv.code in let vs = Vector.cons (Num (I32 bytes_count)) vs in - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4531 - Define a tick consumption model. - *) - let ticks_to_consume = Z.zero in + (* The number of bytes cannot be negative per construction. *) + let ticks_to_consume = + Tick_model.(of_int32_exn bytes_count * ticks_per_byte_written) + in { config with step_kont = @@ -1623,7 +1623,7 @@ let reveal_step reveal module_reg payload = { code = (vs, es); fresh_frame = None; - remaining_ticks = ticks_to_consume; + remaining_ticks = Tick_model.to_z ticks_to_consume; } ) ); } | _ -> raise (Reveal_error Reveal_step) diff --git a/src/lib_webassembly/exec/tick_model.ml b/src/lib_webassembly/exec/tick_model.ml new file mode 100644 index 0000000000000000000000000000000000000000..ea3be77d673d803e33d09398b552ce978e563f1f --- /dev/null +++ b/src/lib_webassembly/exec/tick_model.ml @@ -0,0 +1,58 @@ +type tick = Z.t + +let zero = Z.zero + +let one = Z.one + +let of_int32 i = if i < 0l then None else Some (Z.of_int32 i) + +let of_int32_exn i = + match of_int32 i with + | Some i -> i + | None -> raise (Invalid_argument "Ticks cannot be negative") + +let to_int32 t = try Some (Z.to_int32 t) with Z.Overflow -> None + +let to_int32_exn t = + match to_int32 t with + | Some i -> i + | None -> + raise + (Invalid_argument + "Ticks cannot be contained in the signed int32 representation") + +let of_int64 i = if i < 0L then None else Some (Z.of_int64 i) + +let of_int64_exn i = + match of_int64 i with + | Some i -> i + | None -> raise (Invalid_argument "Ticks cannot be negative") + +let to_int64 t = try Some (Z.to_int64 t) with Z.Overflow -> None + +let to_int64_exn t = + match to_int64 t with + | Some i -> i + | None -> + raise + (Invalid_argument + "Ticks cannot be contained in the signed int64 representation") + +let to_z t = t + +let of_z z = if Z.Compare.(z < Z.zero) then None else Some z + +let ( + ) = Z.add + +let ( * ) = Z.mul + +let nop = Z.zero + +(* This value has been benchmarked from MemoryCopy. *) +let ticks_per_byte_copied = Z.of_int 48 + +(* This value has been benchmarked from MemoryFill and MemoryInit. *) +let ticks_per_byte_read = Z.of_int 42 + +(* This value has been benchmarked from MemoryFill and MemoryInit. *) +let ticks_per_byte_written = Z.of_int 42 diff --git a/src/lib_webassembly/exec/tick_model.mli b/src/lib_webassembly/exec/tick_model.mli new file mode 100644 index 0000000000000000000000000000000000000000..782b39e642c3666db53f7b9d5be728d7043fa313 --- /dev/null +++ b/src/lib_webassembly/exec/tick_model.mli @@ -0,0 +1,83 @@ +(** This module implements a generic tick model. These values were benchmarked + from `MemoryCopy`, `MemoryFill` and `MemoryInit`. *) + +type tick + +(* Tick of cost `0`. *) +val zero : tick + +(* Tick of cost `1`. *) +val one : tick + +(** [of_int32 i] returns the tick representation of [i], or {None} if [i] + is negative. +*) +val of_int32 : int32 -> tick option + +(** [of_int32_exn i] returns the tick representation of [i]. + + @raise Invalid_argument if [i] is negative. +*) +val of_int32_exn : int32 -> tick + +(** [to_int32 t] returns the signed int32 representation of [t] or {None} if [t] + overflows the signed int32 representation. +*) +val to_int32 : tick -> int32 option + +(** [to_int32_exn t] returns the signed int32 representation of [t]. + + @raise Invalid_argument if [t] overflows the signed int32 representation. +*) +val to_int32_exn : tick -> int32 + +(** [of_int64 i] returns the tick representation of [i], or {None} if [i] + is negative. +*) +val of_int64 : int64 -> tick option + +(** [of_int64_exn i] returns the tick representation of [i]. + + @raise Invalid_argument if [i] is negative. +*) +val of_int64_exn : int64 -> tick + +(** [to_int64 t] returns the signed int64 representation of [t] or {None} if [t] + overflows the signed int64 representation. +*) +val to_int64 : tick -> int64 option + +(** [to_int64_exn t] returns the signed int64 representation of [t]. + + @raise Invalid_argument if [t] overflows the signed int64 representation. +*) +val to_int64_exn : tick -> int64 + +(* [of_z z] returns the tick representation of [z], or {None} if [z] + is negative. *) +val of_z : Z.t -> tick option + +(* [to_z t] returns the Z representation of a tick counter [t]. *) +val to_z : tick -> Z.t + +(** [t1 + t2] returns the sum of [t1] and [t2] *) +val ( + ) : tick -> tick -> tick + +(** [t1 * t2] returns the product of [t1] and [t2] *) +val ( * ) : tick -> tick -> tick + +(** [nop] consumes zero tick. This is used specifically for + `write_debug`. *) +val nop : tick + +(** [ticks_per_byte_copied] is the consumption for a byte read in the memory and + written in the memory in the context of reading a chunk of bytes. *) +val ticks_per_byte_copied : tick + +(** [ticks_per_byte_read] is the consumption for a byte read from the memory in + the context of reading a chunk of bytes. *) +val ticks_per_byte_read : tick + +(** [ticks_per_byte_written] is the consumption for a byte written in the + memory in the context of writing a chunk of bytes. *) +val ticks_per_byte_written : tick