diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 68444d102b1..afc078a8913 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -44,6 +44,8 @@ jobs: # apply some patches for `pallets/gear/src/weights.rs` cp runtime/vara/src/weights/pallet_gear.rs pallets/gear/src/weights.rs sed -i -E 's/\w+::WeightInfo for SubstrateWeight/WeightInfo for SubstrateWeight/' pallets/gear/src/weights.rs + # generate code for lightweight scheduler that is used in gtest and other crates + ./scripts/weight-dump.sh # clear the target directory because our benchmarking machine is not ephemeral cargo clean diff --git a/.gitignore b/.gitignore index b19ce35a504..38581ac360b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ target-no-lazy/ target-xwin/ log/ +weight-dumps/ .binpath .vscode .DS_Store diff --git a/Cargo.lock b/Cargo.lock index 10f49966046..006b48dd1a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4425,10 +4425,14 @@ version = "1.0.0" dependencies = [ "clap 4.5.1", "frame-support", + "gear-utils", "indexmap 2.2.3", "pallet-gear", + "proc-macro2", + "quote", "serde", "serde_json", + "syn 2.0.49", "tabled", "vara-runtime", ] @@ -4636,6 +4640,7 @@ dependencies = [ "futures-util", "gear-core", "gear-core-errors", + "gear-utils", "gsdk", "gsdk-codegen", "hex", @@ -4663,6 +4668,7 @@ dependencies = [ "color-eyre", "frame-metadata 15.1.0", "gear-runtime-interface", + "gear-utils", "heck", "parity-scale-codec", "proc-macro2", diff --git a/core-backend/src/tests.rs b/core-backend/src/tests.rs index 4f170a47f68..94420d21ead 100644 --- a/core-backend/src/tests.rs +++ b/core-backend/src/tests.rs @@ -606,8 +606,8 @@ fn test_syscalls_table() { }; use gear_core::message::DispatchKind; use gear_wasm_instrument::{ + gas_metering::CustomConstantCostRules, parity_wasm::{self, builder}, - rules::CustomConstantCostRules, InstrumentationBuilder, SyscallName, }; diff --git a/core/src/code/mod.rs b/core/src/code/mod.rs index 36e95cb1f22..aae60240bd0 100644 --- a/core/src/code/mod.rs +++ b/core/src/code/mod.rs @@ -21,9 +21,8 @@ use crate::{ids::CodeId, message::DispatchKind, pages::WasmPage}; use alloc::{collections::BTreeSet, vec::Vec}; use gear_wasm_instrument::{ + gas_metering::{CustomConstantCostRules, Rules}, parity_wasm::{self, elements::Module}, - rules::CustomConstantCostRules, - wasm_instrument::gas_metering::Rules, InstrumentationBuilder, }; @@ -376,7 +375,7 @@ impl CodeAndId { mod tests { use crate::code::{Code, CodeError, DataSectionError, ExportError}; use alloc::vec::Vec; - use gear_wasm_instrument::rules::CustomConstantCostRules; + use gear_wasm_instrument::gas_metering::CustomConstantCostRules; fn wat2wasm(s: &str) -> Vec { wabt::Wat2Wasm::new() diff --git a/core/src/program.rs b/core/src/program.rs index 38581dfb6c5..6fc6bdc05f3 100644 --- a/core/src/program.rs +++ b/core/src/program.rs @@ -120,7 +120,7 @@ mod tests { use super::Program; use crate::{code::Code, ids::ProgramId}; use alloc::vec::Vec; - use gear_wasm_instrument::rules::CustomConstantCostRules; + use gear_wasm_instrument::gas_metering::CustomConstantCostRules; fn parse_wat(source: &str) -> Vec { let module_bytes = wabt::Wat2Wasm::new() diff --git a/gsdk/Cargo.toml b/gsdk/Cargo.toml index a3e8e19017a..1720efc7156 100644 --- a/gsdk/Cargo.toml +++ b/gsdk/Cargo.toml @@ -47,5 +47,8 @@ demo-messenger.workspace = true demo-new-meta.workspace = true demo-waiter = { workspace = true, features = ["std"] } +[build-dependencies] +gear-utils.workspace = true + [features] testing = [ "rand" ] diff --git a/gsdk/api-gen/Cargo.toml b/gsdk/api-gen/Cargo.toml index 017fb8da51b..5646ed33010 100644 --- a/gsdk/api-gen/Cargo.toml +++ b/gsdk/api-gen/Cargo.toml @@ -10,6 +10,7 @@ sc-executor.workspace = true sc-executor-common.workspace = true sp-io.workspace = true gear-runtime-interface = { workspace = true, features = ["std"] } +gear-utils.workspace = true color-eyre.workspace = true proc-macro2.workspace = true quote.workspace = true diff --git a/gsdk/api-gen/src/main.rs b/gsdk/api-gen/src/main.rs index bd37cc6547f..4c541e84fab 100644 --- a/gsdk/api-gen/src/main.rs +++ b/gsdk/api-gen/src/main.rs @@ -15,7 +15,9 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . + use color_eyre::eyre::Result; +use gear_utils::codegen::LICENSE; use heck::ToSnakeCase as _; use parity_scale_codec::Decode; use proc_macro2::{Ident, Span, TokenStream}; @@ -33,26 +35,6 @@ const RUNTIME_WASM: &str = "RUNTIME_WASM"; const USAGE: &str = r#" Usage: RUNTIME_WASM= generate-client-api "#; -const LICENSE: &str = r#" -// This file is part of Gear. -// -// Copyright (C) 2021-2024 Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -#[allow(rustdoc::broken_intra_doc_links)] //subxt-codegen produces incorrect docs -"#; fn main() -> Result<()> { color_eyre::install()?; diff --git a/gsdk/build.rs b/gsdk/build.rs index a7e74efbc13..fca55a3e0c0 100644 --- a/gsdk/build.rs +++ b/gsdk/build.rs @@ -1,9 +1,5 @@ -use std::{ - env, fs, - io::Write, - path::PathBuf, - process::{Command, Stdio}, -}; +use gear_utils::codegen::format_with_rustfmt; +use std::{env, fs, path::PathBuf, process::Command}; const GSDK_API_GEN: &str = "GSDK_API_GEN"; const GSDK_API_GEN_PKG: &str = "gsdk-api-gen"; @@ -15,7 +11,7 @@ const ENV_RUNTIME_WASM: &str = "RUNTIME_WASM"; fn main() { println!("cargo:rerun-if-changed=build.rs"); - println!("cargo:rerun-if-env-changed={}", GSDK_API_GEN); + println!("cargo:rerun-if-env-changed={GSDK_API_GEN}"); // This build script should only work when building gsdk // with GSDK_API_GEN=1 @@ -23,7 +19,7 @@ fn main() { return; } - let generated = format!("{}/{}", env!("CARGO_MANIFEST_DIR"), GENERATED_API_PATH); + let generated = format!("{}/{GENERATED_API_PATH}", env!("CARGO_MANIFEST_DIR")); fs::write(generated, generate_api()).expect("Failed to write generated api"); } @@ -52,34 +48,12 @@ fn generate_api() -> Vec { .expect("Failed to generate client api.") .stdout; - format(&code).into_bytes() -} - -// Format generated code with rustfmt. -// -// - remove the incompatible attributes. -// - remove verbose whitespaces. -fn format(stream: &[u8]) -> String { - let raw = String::from_utf8_lossy(stream).to_string(); - let mut rustfmt = Command::new("rustfmt"); - let mut code = rustfmt - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .expect("Spawn rustfmt failed"); - - code.stdin - .as_mut() - .expect("Get stdin of rustfmt failed") - .write_all(raw.as_bytes()) - .expect("pipe generated code to rustfmt failed"); - - let out = code.wait_with_output().expect("Run rustfmt failed").stdout; - String::from_utf8_lossy(&out) - .to_string() + // Remove the incompatible attributes and verbose whitespaces. + format_with_rustfmt(&code) .replace(":: subxt", "::subxt") .replace(" : :: ", ": ::") .replace(" :: ", "::") + .into_bytes() } // Get the path of the compiled package. @@ -90,7 +64,7 @@ fn get_path( pkg: &str, features: Vec<&'static str>, ) -> PathBuf { - let path = PathBuf::from(format!("{}/../target/{}/{}", root, profile, relative_path)); + let path = PathBuf::from(format!("{root}/../target/{profile}/{relative_path}")); // If package has not been compiled, compile it. if !path.exists() { @@ -111,9 +85,8 @@ fn get_path( // NOTE: not gonna compile the package here since it may block the // build process. panic!( - "package {} has not been compiled yet, please run \ + "package {pkg} has not been compiled yet, please run \ `cargo {}` first, or override environment `GEN_CLIENT_API` with `0` for disabling the api generation", - pkg, args.join(" ") ); } diff --git a/gsdk/src/metadata/generated.rs b/gsdk/src/metadata/generated.rs index 94ded823eed..90e74461ec7 100644 --- a/gsdk/src/metadata/generated.rs +++ b/gsdk/src/metadata/generated.rs @@ -15,7 +15,7 @@ // // You should have received a copy of the GNU General Public License // along with this program. If not, see . -#[allow(rustdoc::broken_intra_doc_links)] //subxt-codegen produces incorrect docs + #[allow(dead_code, unused_imports, non_camel_case_types)] #[allow(clippy::all)] #[allow(rustdoc::broken_intra_doc_links)] diff --git a/gtest/src/manager.rs b/gtest/src/manager.rs index d58cfc3f7e2..4dd46fbf86f 100644 --- a/gtest/src/manager.rs +++ b/gtest/src/manager.rs @@ -42,7 +42,7 @@ use gear_core::{ reservation::{GasReservationMap, GasReserver}, }; use gear_core_errors::{ErrorReplyReason, SignalCode, SimpleExecutionError}; -use gear_wasm_instrument::rules::CustomConstantCostRules; +use gear_wasm_instrument::gas_metering::Schedule; use rand::{rngs::StdRng, RngCore, SeedableRng}; use std::{ cell::{Ref, RefCell, RefMut}, @@ -1129,11 +1129,12 @@ impl JournalHandler for ExtManager { if let Some(code) = self.opt_binaries.get(&code_id).cloned() { for (init_message_id, candidate_id) in candidates { if !self.actors.contains_key(&candidate_id) { + let schedule = Schedule::default(); let code = Code::try_new( code.clone(), - 1, - |_| CustomConstantCostRules::default(), - None, + schedule.instruction_weights.version, + |module| schedule.rules(module), + schedule.limits.stack_height, ) .expect("Program can't be constructed with provided code"); diff --git a/gtest/src/program.rs b/gtest/src/program.rs index 936d3c590c8..ae7a5ed9eac 100644 --- a/gtest/src/program.rs +++ b/gtest/src/program.rs @@ -31,7 +31,7 @@ use gear_core::{ }; use gear_core_errors::SignalCode; use gear_utils::{MemoryPageDump, ProgramMemoryDump}; -use gear_wasm_instrument::rules::CustomConstantCostRules; +use gear_wasm_instrument::gas_metering::Schedule; use path_clean::PathClean; use std::{ cell::RefCell, @@ -489,8 +489,14 @@ impl<'a> Program<'a> { optimized: Vec, metadata: Option>, ) -> Self { - let code = Code::try_new(optimized, 1, |_| CustomConstantCostRules::default(), None) - .expect("Failed to create Program from code"); + let schedule = Schedule::default(); + let code = Code::try_new( + optimized, + schedule.instruction_weights.version, + |module| schedule.rules(module), + schedule.limits.stack_height, + ) + .expect("Failed to create Program from code"); let code_and_id: InstrumentedCodeAndId = CodeAndId::new(code).into(); let (code, code_id) = code_and_id.into_parts(); diff --git a/pallets/gear/src/schedule.rs b/pallets/gear/src/schedule.rs index aa352dcb7c4..bb35710e827 100644 --- a/pallets/gear/src/schedule.rs +++ b/pallets/gear/src/schedule.rs @@ -36,7 +36,7 @@ use gear_core::{ }; use gear_wasm_instrument::{ gas_metering::{MemoryGrowCost, Rules}, - parity_wasm::elements, + parity_wasm::elements::{Instruction, Module, SignExtInstruction, Type}, }; use pallet_gear_proc_macro::{ScheduleDebug, WeightDebug}; use scale_info::TypeInfo; @@ -1125,7 +1125,7 @@ struct ScheduleRules<'a, T: Config> { } impl Schedule { - pub fn rules(&self, module: &elements::Module) -> impl Rules + '_ { + pub fn rules(&self, module: &Module) -> impl Rules + '_ { ScheduleRules { schedule: self, params: module @@ -1133,7 +1133,7 @@ impl Schedule { .iter() .flat_map(|section| section.types()) .map(|func| { - let elements::Type::Function(func) = func; + let Type::Function(func) = func; func.params().len() as u32 }) .collect(), @@ -1142,8 +1142,10 @@ impl Schedule { } impl<'a, T: Config> Rules for ScheduleRules<'a, T> { - fn instruction_cost(&self, instruction: &elements::Instruction) -> Option { - use self::elements::{Instruction::*, SignExtInstruction::*}; + fn instruction_cost(&self, instruction: &Instruction) -> Option { + use Instruction::*; + use SignExtInstruction::*; + let w = &self.schedule.instruction_weights; let max_params = self.schedule.limits.parameters; @@ -1267,10 +1269,14 @@ impl<'a, T: Config> Rules for ScheduleRules<'a, T> { mod test { use super::*; use crate::mock::Test; - use gear_wasm_instrument::{gas_metering::Rules, rules::CustomConstantCostRules}; + use gear_wasm_instrument::{ + gas_metering::{CustomConstantCostRules, Rules, Schedule as WasmInstrumentSchedule}, + parity_wasm::elements, + }; - fn all_measured_instructions() -> Vec { + fn all_measured_instructions() -> Vec { use elements::{BlockType, BrTableData, Instruction::*}; + let default_table_data = BrTableData { table: Default::default(), default: 0, @@ -1385,7 +1391,7 @@ mod test { ] } - fn default_wasm_module() -> elements::Module { + fn default_wasm_module() -> Module { let simple_wat = r#" (module (import "env" "memory" (memory 1)) @@ -1394,7 +1400,7 @@ mod test { (func $handle) (func $init) )"#; - elements::Module::from_bytes( + Module::from_bytes( wabt::Wat2Wasm::new() .validate(false) .convert(simple_wat) @@ -1411,15 +1417,23 @@ mod test { #[test] fn instructions_backward_compatibility() { let schedule = Schedule::::default(); + let wasm_instrument_schedule = WasmInstrumentSchedule::default(); // used in `pallet-gear` to estimate the gas used by the program let schedule_rules = schedule.rules(&default_wasm_module()); - // used in `gear-wasm-builder` to check program code at an early stage + // used to simulate real gas from `pallet-gear` in crates like gtest + let wasm_instrument_schedule_rules = wasm_instrument_schedule.rules(&default_wasm_module()); + + // used to simulate gas and reject unsupported instructions in unit tests let custom_cost_rules = CustomConstantCostRules::default(); all_measured_instructions().iter().for_each(|i| { assert!(schedule_rules.instruction_cost(i).is_some()); + assert_eq!( + schedule_rules.instruction_cost(i), + wasm_instrument_schedule_rules.instruction_cost(i) + ); assert!(custom_cost_rules.instruction_cost(i).is_some()); }) } diff --git a/pallets/gear/src/tests.rs b/pallets/gear/src/tests.rs index 62afb288dea..3e808a72e2e 100644 --- a/pallets/gear/src/tests.rs +++ b/pallets/gear/src/tests.rs @@ -57,7 +57,7 @@ use gear_core_backend::error::{ TrapExplanation, UnrecoverableExecutionError, UnrecoverableExtError, UnrecoverableWaitError, }; use gear_core_errors::*; -use gear_wasm_instrument::{rules::CustomConstantCostRules, STACK_END_EXPORT_NAME}; +use gear_wasm_instrument::{gas_metering::CustomConstantCostRules, STACK_END_EXPORT_NAME}; use gstd::{collections::BTreeMap, errors::Error as GstdError}; use pallet_gear_voucher::PrepaidCall; use sp_runtime::{ diff --git a/scripts/weight-dump.sh b/scripts/weight-dump.sh new file mode 100755 index 00000000000..3a386627242 --- /dev/null +++ b/scripts/weight-dump.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -e + +dump_path="weight-dumps" +mkdir -p "$dump_path" + +current_branch=$(git branch --show-current) + +dump_path1="$dump_path/${current_branch//\//-}.json" +cargo run --package gear-weight-diff --release -- dump "$dump_path1" --label "$current_branch" +cargo run --quiet --package gear-weight-diff --release -- codegen "$dump_path1" vara > utils/wasm-instrument/src/gas_metering/schedule_tmp.rs +mv utils/wasm-instrument/src/gas_metering/schedule_tmp.rs utils/wasm-instrument/src/gas_metering/schedule.rs +cargo fmt diff --git a/utils/utils/src/codegen.rs b/utils/utils/src/codegen.rs new file mode 100644 index 00000000000..c2c3112155a --- /dev/null +++ b/utils/utils/src/codegen.rs @@ -0,0 +1,66 @@ +// This file is part of Gear. + +// Copyright (C) 2021-2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! Useful things for generating code. + +use std::{ + io::Write, + process::{Command, Stdio}, +}; + +/// License header. +pub const LICENSE: &str = r#" +// This file is part of Gear. +// +// Copyright (C) 2021-2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +"#; + +/// Formats generated code with rustfmt. +pub fn format_with_rustfmt(stream: &[u8]) -> String { + let raw = String::from_utf8_lossy(stream).to_string(); + let mut rustfmt = Command::new("rustfmt"); + let mut code = rustfmt + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("Spawn rustfmt failed"); + + code.stdin + .as_mut() + .expect("Get stdin of rustfmt failed") + .write_all(raw.as_bytes()) + .expect("pipe generated code to rustfmt failed"); + + let out = code.wait_with_output().expect("Run rustfmt failed").stdout; + String::from_utf8_lossy(&out).to_string() +} diff --git a/utils/utils/src/lib.rs b/utils/utils/src/lib.rs index e9ab8ef8bf8..cc746fa479e 100644 --- a/utils/utils/src/lib.rs +++ b/utils/utils/src/lib.rs @@ -32,6 +32,8 @@ use std::{ time::{Duration, SystemTime, UNIX_EPOCH}, }; +pub mod codegen; + /// Trait describes a collection which can get a value by it's index. /// The index can be in any range, even [length(implementor), ..). /// diff --git a/utils/wasm-builder/src/code_validator.rs b/utils/wasm-builder/src/code_validator.rs index 2553464d138..76ceda8211c 100644 --- a/utils/wasm-builder/src/code_validator.rs +++ b/utils/wasm-builder/src/code_validator.rs @@ -18,7 +18,7 @@ use anyhow::{anyhow, bail}; use gear_core::code::{Code, CodeError, ExportError, ImportError, TryNewCodeConfig}; -use gear_wasm_instrument::{rules::CustomConstantCostRules, SyscallName}; +use gear_wasm_instrument::{gas_metering::CustomConstantCostRules, SyscallName}; use pwasm_utils::parity_wasm::{ self, elements::{ diff --git a/utils/wasm-gen/src/tests.rs b/utils/wasm-gen/src/tests.rs index 4c6b0c40545..a1d4e72badc 100644 --- a/utils/wasm-gen/src/tests.rs +++ b/utils/wasm-gen/src/tests.rs @@ -35,8 +35,8 @@ use gear_core_backend::{ use gear_core_processor::{ProcessorContext, ProcessorExternalities}; use gear_utils::NonEmpty; use gear_wasm_instrument::{ + gas_metering::CustomConstantCostRules, parity_wasm::{self, elements::Module}, - rules::CustomConstantCostRules, }; use proptest::prelude::*; use rand::{rngs::SmallRng, RngCore, SeedableRng}; @@ -49,7 +49,6 @@ proptest! { #[test] // Test that valid config always generates a valid gear wasm. fn test_standard_config(buf in prop::collection::vec(any::(), UNSTRUCTURED_SIZE)) { - use gear_wasm_instrument::rules::CustomConstantCostRules; let mut u = Unstructured::new(&buf); let configs_bundle: StandardGearWasmConfigsBundle = StandardGearWasmConfigsBundle { log_info: Some("Some data".into()), diff --git a/utils/wasm-instrument/src/gas_metering/mod.rs b/utils/wasm-instrument/src/gas_metering/mod.rs new file mode 100644 index 00000000000..6ea5dada419 --- /dev/null +++ b/utils/wasm-instrument/src/gas_metering/mod.rs @@ -0,0 +1,26 @@ +// This file is part of Gear. + +// Copyright (C) 2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +//! This module is used to instrument a Wasm module with gas metering code. + +pub use gwasm_instrument::gas_metering::*; +pub use rules::*; +pub use schedule::*; + +mod rules; +mod schedule; diff --git a/utils/wasm-instrument/src/gas_metering/rules.rs b/utils/wasm-instrument/src/gas_metering/rules.rs new file mode 100644 index 00000000000..cf5568ddcf2 --- /dev/null +++ b/utils/wasm-instrument/src/gas_metering/rules.rs @@ -0,0 +1,328 @@ +// This file is part of Gear. + +// Copyright (C) 2023-2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use crate::{ + gas_metering::{ConstantCostRules, MemoryGrowCost, Rules, Schedule}, + parity_wasm::elements::{self, Instruction, Module, SignExtInstruction, Type}, +}; +use alloc::vec::Vec; + +/// This type provides the functionality of [`ConstantCostRules`]. +/// +/// This implementation of [`Rules`] will also check the WASM module for +/// instructions that are not supported by Gear Protocol. +pub struct CustomConstantCostRules { + constant_cost_rules: ConstantCostRules, +} + +impl CustomConstantCostRules { + /// Create a new [`CustomConstantCostRules`]. + /// + /// Uses `instruction_cost` for every instruction and `memory_grow_cost` to + /// dynamically meter the memory growth instruction. + pub fn new(instruction_cost: u32, memory_grow_cost: u32, call_per_local_cost: u32) -> Self { + Self { + constant_cost_rules: ConstantCostRules::new( + instruction_cost, + memory_grow_cost, + call_per_local_cost, + ), + } + } +} + +impl Default for CustomConstantCostRules { + /// Uses instruction cost of `1` and disables memory growth instrumentation. + fn default() -> Self { + Self { + constant_cost_rules: ConstantCostRules::new(1, 0, 1), + } + } +} + +impl Rules for CustomConstantCostRules { + fn instruction_cost(&self, instruction: &Instruction) -> Option { + use self::elements::Instruction::*; + + // list of allowed instructions from `ScheduleRules::::instruction_cost(...)` + // method + match *instruction { + End + | Unreachable + | Return + | Else + | Block(_) + | Loop(_) + | Nop + | Drop + | I32Const(_) + | I64Const(_) + | I32Load(_, _) + | I32Load8S(_, _) + | I32Load8U(_, _) + | I32Load16S(_, _) + | I32Load16U(_, _) + | I64Load(_, _) + | I64Load8S(_, _) + | I64Load8U(_, _) + | I64Load16S(_, _) + | I64Load16U(_, _) + | I64Load32S(_, _) + | I64Load32U(_, _) + | I32Store(_, _) + | I32Store8(_, _) + | I32Store16(_, _) + | I64Store(_, _) + | I64Store8(_, _) + | I64Store16(_, _) + | I64Store32(_, _) + | Select + | If(_) + | Br(_) + | BrIf(_) + | Call(_) + | GetLocal(_) + | SetLocal(_) + | TeeLocal(_) + | GetGlobal(_) + | SetGlobal(_) + | CurrentMemory(_) + | CallIndirect(_, _) + | BrTable(_) + | I32Clz + | I64Clz + | I32Ctz + | I64Ctz + | I32Popcnt + | I64Popcnt + | I32Eqz + | I64Eqz + | I64ExtendSI32 + | I64ExtendUI32 + | I32WrapI64 + | I32Eq + | I64Eq + | I32Ne + | I64Ne + | I32LtS + | I64LtS + | I32LtU + | I64LtU + | I32GtS + | I64GtS + | I32GtU + | I64GtU + | I32LeS + | I64LeS + | I32LeU + | I64LeU + | I32GeS + | I64GeS + | I32GeU + | I64GeU + | I32Add + | I64Add + | I32Sub + | I64Sub + | I32Mul + | I64Mul + | I32DivS + | I64DivS + | I32DivU + | I64DivU + | I32RemS + | I64RemS + | I32RemU + | I64RemU + | I32And + | I64And + | I32Or + | I64Or + | I32Xor + | I64Xor + | I32Shl + | I64Shl + | I32ShrS + | I64ShrS + | I32ShrU + | I64ShrU + | I32Rotl + | I64Rotl + | I32Rotr + | I64Rotr + | SignExt(_) => Some(self.constant_cost_rules.instruction_cost(instruction)?), + _ => None, + } + } + + fn memory_grow_cost(&self) -> MemoryGrowCost { + self.constant_cost_rules.memory_grow_cost() + } + + fn call_per_local_cost(&self) -> u32 { + self.constant_cost_rules.call_per_local_cost() + } +} + +/// This type provides real gas cost of instructions on pallet-gear. +pub struct ScheduleRules<'a> { + schedule: &'a Schedule, + params: Vec, +} + +impl Schedule { + /// Returns real gas rules that are used by pallet gear. + pub fn rules(&self, module: &Module) -> impl Rules + '_ { + ScheduleRules { + schedule: self, + params: module + .type_section() + .iter() + .flat_map(|section| section.types()) + .map(|func| { + let Type::Function(func) = func; + func.params().len() as u32 + }) + .collect(), + } + } +} + +impl<'a> Rules for ScheduleRules<'a> { + fn instruction_cost(&self, instruction: &Instruction) -> Option { + use Instruction::*; + use SignExtInstruction::*; + + let w = &self.schedule.instruction_weights; + let max_params = self.schedule.limits.parameters; + + let weight = match *instruction { + End | Unreachable | Return | Else | Block(_) | Loop(_) | Nop | Drop => 0, + I32Const(_) | I64Const(_) => w.i64const, + I32Load(_, _) + | I32Load8S(_, _) + | I32Load8U(_, _) + | I32Load16S(_, _) + | I32Load16U(_, _) => w.i32load, + I64Load(_, _) + | I64Load8S(_, _) + | I64Load8U(_, _) + | I64Load16S(_, _) + | I64Load16U(_, _) + | I64Load32S(_, _) + | I64Load32U(_, _) => w.i64load, + I32Store(_, _) | I32Store8(_, _) | I32Store16(_, _) => w.i32store, + I64Store(_, _) | I64Store8(_, _) | I64Store16(_, _) | I64Store32(_, _) => w.i64store, + Select => w.select, + If(_) => w.r#if, + Br(_) => w.br, + BrIf(_) => w.br_if, + Call(_) => w.call, + GetLocal(_) => w.local_get, + SetLocal(_) => w.local_set, + TeeLocal(_) => w.local_tee, + GetGlobal(_) => w.global_get, + SetGlobal(_) => w.global_set, + CurrentMemory(_) => w.memory_current, + CallIndirect(idx, _) => *self.params.get(idx as usize).unwrap_or(&max_params), + BrTable(ref data) => w + .br_table + .saturating_add(w.br_table_per_entry.saturating_mul(data.table.len() as u32)), + I32Clz => w.i32clz, + I64Clz => w.i64clz, + I32Ctz => w.i32ctz, + I64Ctz => w.i64ctz, + I32Popcnt => w.i32popcnt, + I64Popcnt => w.i64popcnt, + I32Eqz => w.i32eqz, + I64Eqz => w.i64eqz, + I64ExtendSI32 => w.i64extendsi32, + I64ExtendUI32 => w.i64extendui32, + I32WrapI64 => w.i32wrapi64, + I32Eq => w.i32eq, + I64Eq => w.i64eq, + I32Ne => w.i32ne, + I64Ne => w.i64ne, + I32LtS => w.i32lts, + I64LtS => w.i64lts, + I32LtU => w.i32ltu, + I64LtU => w.i64ltu, + I32GtS => w.i32gts, + I64GtS => w.i64gts, + I32GtU => w.i32gtu, + I64GtU => w.i64gtu, + I32LeS => w.i32les, + I64LeS => w.i64les, + I32LeU => w.i32leu, + I64LeU => w.i64leu, + I32GeS => w.i32ges, + I64GeS => w.i64ges, + I32GeU => w.i32geu, + I64GeU => w.i64geu, + I32Add => w.i32add, + I64Add => w.i64add, + I32Sub => w.i32sub, + I64Sub => w.i64sub, + I32Mul => w.i32mul, + I64Mul => w.i64mul, + I32DivS => w.i32divs, + I64DivS => w.i64divs, + I32DivU => w.i32divu, + I64DivU => w.i64divu, + I32RemS => w.i32rems, + I64RemS => w.i64rems, + I32RemU => w.i32remu, + I64RemU => w.i64remu, + I32And => w.i32and, + I64And => w.i64and, + I32Or => w.i32or, + I64Or => w.i64or, + I32Xor => w.i32xor, + I64Xor => w.i64xor, + I32Shl => w.i32shl, + I64Shl => w.i64shl, + I32ShrS => w.i32shrs, + I64ShrS => w.i64shrs, + I32ShrU => w.i32shru, + I64ShrU => w.i64shru, + I32Rotl => w.i32rotl, + I64Rotl => w.i64rotl, + I32Rotr => w.i32rotr, + I64Rotr => w.i64rotr, + SignExt(ref s) => match s { + I32Extend8S => w.i32extend8s, + I32Extend16S => w.i32extend16s, + I64Extend8S => w.i64extend8s, + I64Extend16S => w.i64extend16s, + I64Extend32S => w.i64extend32s, + }, + // Returning None makes the gas instrumentation fail which we intend for + // unsupported or unknown instructions. + _ => return None, + }; + Some(weight) + } + + fn memory_grow_cost(&self) -> MemoryGrowCost { + MemoryGrowCost::Free + } + + fn call_per_local_cost(&self) -> u32 { + self.schedule.instruction_weights.call_per_local + } +} diff --git a/utils/wasm-instrument/src/gas_metering/schedule.rs b/utils/wasm-instrument/src/gas_metering/schedule.rs new file mode 100644 index 00000000000..af38990deda --- /dev/null +++ b/utils/wasm-instrument/src/gas_metering/schedule.rs @@ -0,0 +1,718 @@ +// This file is part of Gear. +// +// Copyright (C) 2021-2024 Gear Technologies Inc. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#![doc = r" This is auto-generated module that contains cost schedule from"] +#![doc = r" `pallets/gear/src/schedule.rs`."] +#![doc = r""] +#![doc = r" See `./scripts/weight-dump.sh` if you want to update it."] + +pub struct Schedule { + pub limits: Limits, + pub instruction_weights: InstructionWeights, + pub host_fn_weights: HostFnWeights, + pub memory_weights: MemoryWeights, + pub module_instantiation_per_byte: Weight, + pub db_write_per_byte: Weight, + pub db_read_per_byte: Weight, + pub code_instrumentation_cost: Weight, + pub code_instrumentation_byte_cost: Weight, +} + +impl Default for Schedule { + fn default() -> Self { + Self { + limits: Limits::default(), + instruction_weights: InstructionWeights::default(), + host_fn_weights: HostFnWeights::default(), + memory_weights: MemoryWeights::default(), + module_instantiation_per_byte: Weight { + ref_time: 2957, + proof_size: 0, + }, + db_write_per_byte: Weight { + ref_time: 241, + proof_size: 0, + }, + db_read_per_byte: Weight { + ref_time: 573, + proof_size: 0, + }, + code_instrumentation_cost: Weight { + ref_time: 286601000, + proof_size: 3682, + }, + code_instrumentation_byte_cost: Weight { + ref_time: 60451, + proof_size: 0, + }, + } + } +} + +pub struct Limits { + pub stack_height: Option, + pub globals: u32, + pub locals: u32, + pub parameters: u32, + pub memory_pages: u16, + pub table_size: u32, + pub br_table_size: u32, + pub subject_len: u32, + pub call_depth: u32, + pub payload_len: u32, + pub code_len: u32, +} + +impl Default for Limits { + fn default() -> Self { + Self { + stack_height: Some(36743), + globals: 256, + locals: 1024, + parameters: 128, + memory_pages: 512, + table_size: 4096, + br_table_size: 256, + subject_len: 32, + call_depth: 32, + payload_len: 8388608, + code_len: 524288, + } + } +} + +pub struct InstructionWeights { + pub version: u32, + pub i64const: u32, + pub i64load: u32, + pub i32load: u32, + pub i64store: u32, + pub i32store: u32, + pub select: u32, + pub r#if: u32, + pub br: u32, + pub br_if: u32, + pub br_table: u32, + pub br_table_per_entry: u32, + pub call: u32, + pub call_indirect: u32, + pub call_indirect_per_param: u32, + pub call_per_local: u32, + pub local_get: u32, + pub local_set: u32, + pub local_tee: u32, + pub global_get: u32, + pub global_set: u32, + pub memory_current: u32, + pub i64clz: u32, + pub i32clz: u32, + pub i64ctz: u32, + pub i32ctz: u32, + pub i64popcnt: u32, + pub i32popcnt: u32, + pub i64eqz: u32, + pub i32eqz: u32, + pub i32extend8s: u32, + pub i32extend16s: u32, + pub i64extend8s: u32, + pub i64extend16s: u32, + pub i64extend32s: u32, + pub i64extendsi32: u32, + pub i64extendui32: u32, + pub i32wrapi64: u32, + pub i64eq: u32, + pub i32eq: u32, + pub i64ne: u32, + pub i32ne: u32, + pub i64lts: u32, + pub i32lts: u32, + pub i64ltu: u32, + pub i32ltu: u32, + pub i64gts: u32, + pub i32gts: u32, + pub i64gtu: u32, + pub i32gtu: u32, + pub i64les: u32, + pub i32les: u32, + pub i64leu: u32, + pub i32leu: u32, + pub i64ges: u32, + pub i32ges: u32, + pub i64geu: u32, + pub i32geu: u32, + pub i64add: u32, + pub i32add: u32, + pub i64sub: u32, + pub i32sub: u32, + pub i64mul: u32, + pub i32mul: u32, + pub i64divs: u32, + pub i32divs: u32, + pub i64divu: u32, + pub i32divu: u32, + pub i64rems: u32, + pub i32rems: u32, + pub i64remu: u32, + pub i32remu: u32, + pub i64and: u32, + pub i32and: u32, + pub i64or: u32, + pub i32or: u32, + pub i64xor: u32, + pub i32xor: u32, + pub i64shl: u32, + pub i32shl: u32, + pub i64shrs: u32, + pub i32shrs: u32, + pub i64shru: u32, + pub i32shru: u32, + pub i64rotl: u32, + pub i32rotl: u32, + pub i64rotr: u32, + pub i32rotr: u32, +} + +impl Default for InstructionWeights { + fn default() -> Self { + Self { + version: 1200, + i64const: 155, + i64load: 9514, + i32load: 9159, + i64store: 23660, + i32store: 14219, + select: 7295, + r#if: 6348, + br: 3181, + br_if: 5915, + br_table: 9349, + br_table_per_entry: 361, + call: 4536, + call_indirect: 20157, + call_indirect_per_param: 2241, + call_per_local: 0, + local_get: 1098, + local_set: 2553, + local_tee: 2885, + global_get: 1876, + global_set: 2809, + memory_current: 14383, + i64clz: 6814, + i32clz: 6297, + i64ctz: 6250, + i32ctz: 5262, + i64popcnt: 1164, + i32popcnt: 746, + i64eqz: 3701, + i32eqz: 2380, + i32extend8s: 976, + i32extend16s: 885, + i64extend8s: 993, + i64extend16s: 1035, + i64extend32s: 936, + i64extendsi32: 866, + i64extendui32: 556, + i32wrapi64: 346, + i64eq: 3593, + i32eq: 2307, + i64ne: 3548, + i32ne: 2328, + i64lts: 3617, + i32lts: 2444, + i64ltu: 3887, + i32ltu: 2456, + i64gts: 3952, + i32gts: 2412, + i64gtu: 3784, + i32gtu: 2464, + i64les: 3607, + i32les: 2273, + i64leu: 3516, + i32leu: 2265, + i64ges: 3486, + i32ges: 2154, + i64geu: 3605, + i32geu: 2203, + i64add: 2586, + i32add: 1250, + i64sub: 2659, + i32sub: 1100, + i64mul: 3539, + i32mul: 2346, + i64divs: 3342, + i32divs: 3681, + i64divu: 4414, + i32divu: 4165, + i64rems: 15453, + i32rems: 13340, + i64remu: 4085, + i32remu: 4189, + i64and: 2692, + i32and: 1156, + i64or: 2580, + i32or: 1177, + i64xor: 2660, + i32xor: 1254, + i64shl: 2119, + i32shl: 1053, + i64shrs: 2239, + i32shrs: 1174, + i64shru: 2379, + i32shru: 1136, + i64rotl: 2250, + i32rotl: 1263, + i64rotr: 2319, + i32rotr: 1244, + } + } +} + +pub struct HostFnWeights { + pub alloc: Weight, + pub alloc_per_page: Weight, + pub free: Weight, + pub free_range: Weight, + pub free_range_per_page: Weight, + pub gr_reserve_gas: Weight, + pub gr_unreserve_gas: Weight, + pub gr_system_reserve_gas: Weight, + pub gr_gas_available: Weight, + pub gr_message_id: Weight, + pub gr_program_id: Weight, + pub gr_source: Weight, + pub gr_value: Weight, + pub gr_value_available: Weight, + pub gr_size: Weight, + pub gr_read: Weight, + pub gr_read_per_byte: Weight, + pub gr_env_vars: Weight, + pub gr_block_height: Weight, + pub gr_block_timestamp: Weight, + pub gr_random: Weight, + pub gr_reply_deposit: Weight, + pub gr_send: Weight, + pub gr_send_per_byte: Weight, + pub gr_send_wgas: Weight, + pub gr_send_wgas_per_byte: Weight, + pub gr_send_init: Weight, + pub gr_send_push: Weight, + pub gr_send_push_per_byte: Weight, + pub gr_send_commit: Weight, + pub gr_send_commit_wgas: Weight, + pub gr_reservation_send: Weight, + pub gr_reservation_send_per_byte: Weight, + pub gr_reservation_send_commit: Weight, + pub gr_reply_commit: Weight, + pub gr_reply_commit_wgas: Weight, + pub gr_reservation_reply: Weight, + pub gr_reservation_reply_per_byte: Weight, + pub gr_reservation_reply_commit: Weight, + pub gr_reply_push: Weight, + pub gr_reply: Weight, + pub gr_reply_per_byte: Weight, + pub gr_reply_wgas: Weight, + pub gr_reply_wgas_per_byte: Weight, + pub gr_reply_push_per_byte: Weight, + pub gr_reply_to: Weight, + pub gr_signal_code: Weight, + pub gr_signal_from: Weight, + pub gr_reply_input: Weight, + pub gr_reply_input_wgas: Weight, + pub gr_reply_push_input: Weight, + pub gr_reply_push_input_per_byte: Weight, + pub gr_send_input: Weight, + pub gr_send_input_wgas: Weight, + pub gr_send_push_input: Weight, + pub gr_send_push_input_per_byte: Weight, + pub gr_debug: Weight, + pub gr_debug_per_byte: Weight, + pub gr_reply_code: Weight, + pub gr_exit: Weight, + pub gr_leave: Weight, + pub gr_wait: Weight, + pub gr_wait_for: Weight, + pub gr_wait_up_to: Weight, + pub gr_wake: Weight, + pub gr_create_program: Weight, + pub gr_create_program_payload_per_byte: Weight, + pub gr_create_program_salt_per_byte: Weight, + pub gr_create_program_wgas: Weight, + pub gr_create_program_wgas_payload_per_byte: Weight, + pub gr_create_program_wgas_salt_per_byte: Weight, +} + +impl Default for HostFnWeights { + fn default() -> Self { + Self { + alloc: Weight { + ref_time: 7167172, + proof_size: 0, + }, + alloc_per_page: Weight { + ref_time: 355692, + proof_size: 0, + }, + free: Weight { + ref_time: 757895, + proof_size: 0, + }, + free_range: Weight { + ref_time: 900972, + proof_size: 0, + }, + free_range_per_page: Weight { + ref_time: 61620, + proof_size: 0, + }, + gr_reserve_gas: Weight { + ref_time: 2352421, + proof_size: 0, + }, + gr_unreserve_gas: Weight { + ref_time: 2183611, + proof_size: 0, + }, + gr_system_reserve_gas: Weight { + ref_time: 1186635, + proof_size: 0, + }, + gr_gas_available: Weight { + ref_time: 1054312, + proof_size: 0, + }, + gr_message_id: Weight { + ref_time: 1129832, + proof_size: 0, + }, + gr_program_id: Weight { + ref_time: 1059044, + proof_size: 0, + }, + gr_source: Weight { + ref_time: 1046996, + proof_size: 0, + }, + gr_value: Weight { + ref_time: 1095736, + proof_size: 0, + }, + gr_value_available: Weight { + ref_time: 1081158, + proof_size: 0, + }, + gr_size: Weight { + ref_time: 1095335, + proof_size: 0, + }, + gr_read: Weight { + ref_time: 1825905, + proof_size: 0, + }, + gr_read_per_byte: Weight { + ref_time: 165, + proof_size: 0, + }, + gr_env_vars: Weight { + ref_time: 1302076, + proof_size: 0, + }, + gr_block_height: Weight { + ref_time: 1073860, + proof_size: 0, + }, + gr_block_timestamp: Weight { + ref_time: 1091947, + proof_size: 0, + }, + gr_random: Weight { + ref_time: 2094904, + proof_size: 0, + }, + gr_reply_deposit: Weight { + ref_time: 6520451, + proof_size: 0, + }, + gr_send: Weight { + ref_time: 3157153, + proof_size: 0, + }, + gr_send_per_byte: Weight { + ref_time: 271, + proof_size: 0, + }, + gr_send_wgas: Weight { + ref_time: 3206077, + proof_size: 0, + }, + gr_send_wgas_per_byte: Weight { + ref_time: 269, + proof_size: 0, + }, + gr_send_init: Weight { + ref_time: 1179784, + proof_size: 0, + }, + gr_send_push: Weight { + ref_time: 2055072, + proof_size: 0, + }, + gr_send_push_per_byte: Weight { + ref_time: 382, + proof_size: 0, + }, + gr_send_commit: Weight { + ref_time: 2717863, + proof_size: 0, + }, + gr_send_commit_wgas: Weight { + ref_time: 2730733, + proof_size: 0, + }, + gr_reservation_send: Weight { + ref_time: 3439117, + proof_size: 0, + }, + gr_reservation_send_per_byte: Weight { + ref_time: 267, + proof_size: 0, + }, + gr_reservation_send_commit: Weight { + ref_time: 2937858, + proof_size: 0, + }, + gr_reply_commit: Weight { + ref_time: 15979646, + proof_size: 0, + }, + gr_reply_commit_wgas: Weight { + ref_time: 21362724, + proof_size: 0, + }, + gr_reservation_reply: Weight { + ref_time: 9297412, + proof_size: 0, + }, + gr_reservation_reply_per_byte: Weight { + ref_time: 432205, + proof_size: 0, + }, + gr_reservation_reply_commit: Weight { + ref_time: 7187780, + proof_size: 0, + }, + gr_reply_push: Weight { + ref_time: 1838663, + proof_size: 0, + }, + gr_reply: Weight { + ref_time: 18397180, + proof_size: 0, + }, + gr_reply_per_byte: Weight { + ref_time: 417, + proof_size: 0, + }, + gr_reply_wgas: Weight { + ref_time: 17551258, + proof_size: 0, + }, + gr_reply_wgas_per_byte: Weight { + ref_time: 423, + proof_size: 0, + }, + gr_reply_push_per_byte: Weight { + ref_time: 675, + proof_size: 0, + }, + gr_reply_to: Weight { + ref_time: 1081434, + proof_size: 0, + }, + gr_signal_code: Weight { + ref_time: 1057043, + proof_size: 0, + }, + gr_signal_from: Weight { + ref_time: 1088676, + proof_size: 0, + }, + gr_reply_input: Weight { + ref_time: 33576008, + proof_size: 0, + }, + gr_reply_input_wgas: Weight { + ref_time: 0, + proof_size: 0, + }, + gr_reply_push_input: Weight { + ref_time: 1354524, + proof_size: 0, + }, + gr_reply_push_input_per_byte: Weight { + ref_time: 163, + proof_size: 0, + }, + gr_send_input: Weight { + ref_time: 3403443, + proof_size: 0, + }, + gr_send_input_wgas: Weight { + ref_time: 3583443, + proof_size: 0, + }, + gr_send_push_input: Weight { + ref_time: 1631742, + proof_size: 0, + }, + gr_send_push_input_per_byte: Weight { + ref_time: 170, + proof_size: 0, + }, + gr_debug: Weight { + ref_time: 1452831, + proof_size: 0, + }, + gr_debug_per_byte: Weight { + ref_time: 321, + proof_size: 0, + }, + gr_reply_code: Weight { + ref_time: 1056266, + proof_size: 0, + }, + gr_exit: Weight { + ref_time: 197413680, + proof_size: 0, + }, + gr_leave: Weight { + ref_time: 191227910, + proof_size: 0, + }, + gr_wait: Weight { + ref_time: 127692646, + proof_size: 0, + }, + gr_wait_for: Weight { + ref_time: 191877852, + proof_size: 0, + }, + gr_wait_up_to: Weight { + ref_time: 188425504, + proof_size: 0, + }, + gr_wake: Weight { + ref_time: 2048190, + proof_size: 0, + }, + gr_create_program: Weight { + ref_time: 4276748, + proof_size: 0, + }, + gr_create_program_payload_per_byte: Weight { + ref_time: 86, + proof_size: 0, + }, + gr_create_program_salt_per_byte: Weight { + ref_time: 1941, + proof_size: 0, + }, + gr_create_program_wgas: Weight { + ref_time: 4280463, + proof_size: 0, + }, + gr_create_program_wgas_payload_per_byte: Weight { + ref_time: 87, + proof_size: 0, + }, + gr_create_program_wgas_salt_per_byte: Weight { + ref_time: 1925, + proof_size: 0, + }, + } + } +} + +pub struct MemoryWeights { + pub lazy_pages_signal_read: Weight, + pub lazy_pages_signal_write: Weight, + pub lazy_pages_signal_write_after_read: Weight, + pub lazy_pages_host_func_read: Weight, + pub lazy_pages_host_func_write: Weight, + pub lazy_pages_host_func_write_after_read: Weight, + pub load_page_data: Weight, + pub upload_page_data: Weight, + pub static_page: Weight, + pub mem_grow: Weight, + pub parachain_read_heuristic: Weight, +} + +impl Default for MemoryWeights { + fn default() -> Self { + Self { + lazy_pages_signal_read: Weight { + ref_time: 29086537, + proof_size: 0, + }, + lazy_pages_signal_write: Weight { + ref_time: 36014887, + proof_size: 0, + }, + lazy_pages_signal_write_after_read: Weight { + ref_time: 10581256, + proof_size: 0, + }, + lazy_pages_host_func_read: Weight { + ref_time: 30901094, + proof_size: 0, + }, + lazy_pages_host_func_write: Weight { + ref_time: 37438602, + proof_size: 0, + }, + lazy_pages_host_func_write_after_read: Weight { + ref_time: 9187150, + proof_size: 0, + }, + load_page_data: Weight { + ref_time: 10250009, + proof_size: 0, + }, + upload_page_data: Weight { + ref_time: 103952080, + proof_size: 0, + }, + static_page: Weight { + ref_time: 100, + proof_size: 0, + }, + mem_grow: Weight { + ref_time: 1297452, + proof_size: 0, + }, + parachain_read_heuristic: Weight { + ref_time: 0, + proof_size: 0, + }, + } + } +} + +pub struct Weight { + pub ref_time: u64, + pub proof_size: u64, +} diff --git a/utils/wasm-instrument/src/lib.rs b/utils/wasm-instrument/src/lib.rs index edb886b6370..bc6c57b13f0 100644 --- a/utils/wasm-instrument/src/lib.rs +++ b/utils/wasm-instrument/src/lib.rs @@ -24,7 +24,6 @@ extern crate alloc; use alloc::vec; use gwasm_instrument::{ - gas_metering::Rules, parity_wasm::{ builder, elements::{ @@ -34,13 +33,13 @@ use gwasm_instrument::{ InjectionConfig, }; -pub use crate::syscalls::SyscallName; -pub use gwasm_instrument::{self as wasm_instrument, gas_metering, parity_wasm, utils}; +pub use crate::{gas_metering::Rules, syscalls::SyscallName}; +pub use gwasm_instrument::{self as wasm_instrument, parity_wasm, utils}; #[cfg(test)] mod tests; -pub mod rules; +pub mod gas_metering; pub mod syscalls; // TODO #3057 diff --git a/utils/wasm-instrument/src/rules.rs b/utils/wasm-instrument/src/rules.rs deleted file mode 100644 index 3388ee35c55..00000000000 --- a/utils/wasm-instrument/src/rules.rs +++ /dev/null @@ -1,183 +0,0 @@ -// This file is part of Gear. - -// Copyright (C) 2023-2024 Gear Technologies Inc. -// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 - -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -//! Mock for `ScheduleRules`. - -use gwasm_instrument::{ - gas_metering::{ConstantCostRules, MemoryGrowCost, Rules}, - parity_wasm::elements::{self, Instruction}, -}; - -/// This type provides the functionality of [`ConstantCostRules`]. -/// -/// This implementation of [`Rules`] will also check the WASM module for -/// instructions that are not supported by Gear Protocol. So, it's preferable to -/// use this type instead of `pallet_gear::Schedule::default().rules()` in unit -/// testing. -pub struct CustomConstantCostRules { - constant_cost_rules: ConstantCostRules, -} - -impl CustomConstantCostRules { - /// Create a new [`CustomConstantCostRules`]. - /// - /// Uses `instruction_cost` for every instruction and `memory_grow_cost` to - /// dynamically meter the memory growth instruction. - pub fn new(instruction_cost: u32, memory_grow_cost: u32, call_per_local_cost: u32) -> Self { - Self { - constant_cost_rules: ConstantCostRules::new( - instruction_cost, - memory_grow_cost, - call_per_local_cost, - ), - } - } -} - -impl Default for CustomConstantCostRules { - /// Uses instruction cost of `1` and disables memory growth instrumentation. - fn default() -> Self { - Self { - constant_cost_rules: ConstantCostRules::new(1, 0, 1), - } - } -} - -impl Rules for CustomConstantCostRules { - fn instruction_cost(&self, instruction: &Instruction) -> Option { - use self::elements::Instruction::*; - - // list of allowed instructions from `ScheduleRules::::instruction_cost(...)` - // method - match *instruction { - End - | Unreachable - | Return - | Else - | Block(_) - | Loop(_) - | Nop - | Drop - | I32Const(_) - | I64Const(_) - | I32Load(_, _) - | I32Load8S(_, _) - | I32Load8U(_, _) - | I32Load16S(_, _) - | I32Load16U(_, _) - | I64Load(_, _) - | I64Load8S(_, _) - | I64Load8U(_, _) - | I64Load16S(_, _) - | I64Load16U(_, _) - | I64Load32S(_, _) - | I64Load32U(_, _) - | I32Store(_, _) - | I32Store8(_, _) - | I32Store16(_, _) - | I64Store(_, _) - | I64Store8(_, _) - | I64Store16(_, _) - | I64Store32(_, _) - | Select - | If(_) - | Br(_) - | BrIf(_) - | Call(_) - | GetLocal(_) - | SetLocal(_) - | TeeLocal(_) - | GetGlobal(_) - | SetGlobal(_) - | CurrentMemory(_) - | CallIndirect(_, _) - | BrTable(_) - | I32Clz - | I64Clz - | I32Ctz - | I64Ctz - | I32Popcnt - | I64Popcnt - | I32Eqz - | I64Eqz - | I64ExtendSI32 - | I64ExtendUI32 - | I32WrapI64 - | I32Eq - | I64Eq - | I32Ne - | I64Ne - | I32LtS - | I64LtS - | I32LtU - | I64LtU - | I32GtS - | I64GtS - | I32GtU - | I64GtU - | I32LeS - | I64LeS - | I32LeU - | I64LeU - | I32GeS - | I64GeS - | I32GeU - | I64GeU - | I32Add - | I64Add - | I32Sub - | I64Sub - | I32Mul - | I64Mul - | I32DivS - | I64DivS - | I32DivU - | I64DivU - | I32RemS - | I64RemS - | I32RemU - | I64RemU - | I32And - | I64And - | I32Or - | I64Or - | I32Xor - | I64Xor - | I32Shl - | I64Shl - | I32ShrS - | I64ShrS - | I32ShrU - | I64ShrU - | I32Rotl - | I64Rotl - | I32Rotr - | I64Rotr - | SignExt(_) => Some(self.constant_cost_rules.instruction_cost(instruction)?), - _ => None, - } - } - - fn memory_grow_cost(&self) -> MemoryGrowCost { - self.constant_cost_rules.memory_grow_cost() - } - - fn call_per_local_cost(&self) -> u32 { - self.constant_cost_rules.call_per_local_cost() - } -} diff --git a/utils/wasm-instrument/src/tests.rs b/utils/wasm-instrument/src/tests.rs index 1d8eeadb1e7..18cf4885562 100644 --- a/utils/wasm-instrument/src/tests.rs +++ b/utils/wasm-instrument/src/tests.rs @@ -18,7 +18,7 @@ use super::*; use crate::{ - rules::CustomConstantCostRules, + gas_metering::CustomConstantCostRules, syscalls::{ParamType::*, Ptr, RegularParamType::*, SyscallName}, }; use alloc::format; diff --git a/utils/weight-diff/Cargo.toml b/utils/weight-diff/Cargo.toml index 826dd7f7fa6..73dbfc333d4 100644 --- a/utils/weight-diff/Cargo.toml +++ b/utils/weight-diff/Cargo.toml @@ -10,6 +10,10 @@ clap = { workspace = true, features = ["derive"] } indexmap = { workspace = true, features = ["serde"] } serde = { workspace = true, features = ["derive"] } serde_json.workspace = true +gear-utils.workspace = true +syn = { workspace = true, features = ["default", "full", "visit"] } +quote.workspace = true +proc-macro2.workspace = true tabled.workspace = true pallet-gear = { workspace = true, features = ["std"] } diff --git a/utils/weight-diff/src/main.rs b/utils/weight-diff/src/main.rs index ea5ad8271f8..7ee274bcc30 100644 --- a/utils/weight-diff/src/main.rs +++ b/utils/weight-diff/src/main.rs @@ -23,12 +23,22 @@ use frame_support::{ sp_runtime::{FixedPointNumber, FixedU128 as Fixed}, weights::Weight, }; +use gear_utils::codegen::{format_with_rustfmt, LICENSE}; use indexmap::IndexMap; use pallet_gear::Schedule; +use proc_macro2::TokenStream; +use quote::quote; use serde::{Deserialize, Serialize}; -use std::{fs, path::PathBuf}; +use serde_json::Value; +use std::{fs, path::PathBuf, str::FromStr}; +use syn::{ + ext::IdentExt, + visit::{self, Visit}, + AngleBracketedGenericArguments, Fields, Generics, ItemStruct, PathArguments, Type, TypePath, +}; use tabled::{builder::Builder, Style}; +/// Utility for working with weights #[derive(Debug, Parser)] struct Cli { #[command(subcommand)] @@ -64,6 +74,15 @@ enum Commands { #[arg(long)] display_units: bool, }, + /// Creates lightweight scheduler with weights from the given json file + Codegen { + /// path to json file + #[arg(value_parser)] + path: PathBuf, + /// what runtime to use as source? + #[arg(ignore_case = true, value_enum)] + runtime: Runtime, + }, } #[derive(Debug, Copy, Clone, ValueEnum)] @@ -91,11 +110,14 @@ struct DeserializableDump { label: Option, } -#[derive(Debug, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] struct DeserializableSchedule { - instruction_weights: IndexMap, - host_fn_weights: IndexMap, - memory_weights: IndexMap, + limits: IndexMap, + instruction_weights: IndexMap, + host_fn_weights: IndexMap, + memory_weights: IndexMap, + #[serde(flatten)] + other_fields: IndexMap, } impl DeserializableSchedule { @@ -119,9 +141,7 @@ impl DeserializableSchedule { let mut map = IndexMap::new(); for (k, v) in self.host_fn_weights.clone() { - if let Ok(v) = serde_json::from_value::(v) { - map.insert(k, v.ref_time()); - } + map.insert(k, v.ref_time()); } map @@ -131,9 +151,7 @@ impl DeserializableSchedule { let mut map = IndexMap::new(); for (k, v) in self.memory_weights.clone() { - if let Ok(v) = serde_json::from_value::(v) { - map.insert(k, v.ref_time()); - } + map.insert(k, v.ref_time()); } map @@ -184,6 +202,62 @@ fn format_diff(before: Option, after: Option) -> String { } } +#[derive(Default)] +struct StructuresVisitor { + structures: IndexMap, +} + +impl<'ast> Visit<'ast> for StructuresVisitor { + fn visit_item_struct(&mut self, node: &'ast ItemStruct) { + let structure_name = node.ident.to_string(); + if !matches!( + structure_name.as_str(), + "Schedule" | "Limits" | "InstructionWeights" | "HostFnWeights" | "MemoryWeights" + ) { + return; + } + + let mut structure = node.clone(); + + structure.attrs.clear(); + structure.generics = Generics::default(); + + if let Fields::Named(ref mut fields) = structure.fields { + let last_ident = fields + .named + .last() + .and_then(|field| field.ident.as_ref().map(|ident| ident.to_string())); + if last_ident == Some(String::from("_phantom")) { + fields.named.pop(); + } + } + + for field in structure.fields.iter_mut() { + field.vis = syn::parse2(quote! { pub }).unwrap(); + + if let Type::Path(TypePath { path, .. }) = &mut field.ty { + for segment in path.segments.iter_mut() { + if let PathArguments::AngleBracketed(AngleBracketedGenericArguments { + args, + .. + }) = &mut segment.arguments + { + let token_stream = quote! { #args }; + if token_stream.to_string() == "T" { + segment.arguments = PathArguments::None; + } + } + } + } + field.attrs.clear(); + } + + self.structures.insert(structure_name, structure); + + visit::visit_item_struct(self, node); + } +} + fn main() { let Cli { command } = Cli::parse(); @@ -266,5 +340,102 @@ fn main() { println!("{table}"); println!(); } + Commands::Codegen { path, runtime } => { + let dump: DeserializableDump = + serde_json::from_str(&fs::read_to_string(path).unwrap()).unwrap(); + let raw_schedule = match runtime { + Runtime::Vara => serde_json::to_value(dump.vara_schedule).unwrap(), + }; + + let file = + syn::parse_file(&fs::read_to_string("pallets/gear/src/schedule.rs").unwrap()) + .unwrap(); + + let mut visitor = StructuresVisitor::default(); + visitor.visit_file(&file); + + let mut declarations = vec![quote! { + //! This is auto-generated module that contains cost schedule from + //! `pallets/gear/src/schedule.rs`. + //! + //! See `./scripts/weight-dump.sh` if you want to update it. + }]; + + for (structure_name, structure) in visitor.structures { + let structure_ident = &structure.ident; + + let fields = structure.fields.iter().map(|field| { + let ty = &field.ty; + let type_name = quote! { #ty }.to_string().replace(' ', ""); + + let field_ident = field.ident.as_ref().unwrap(); + let field_name = field_ident.unraw().to_string(); + + let value = match structure_name.as_str() { + "Schedule" => &raw_schedule[field_name], + "Limits" => &raw_schedule["limits"][field_name], + "InstructionWeights" => &raw_schedule["instruction_weights"][field_name], + "HostFnWeights" => &raw_schedule["host_fn_weights"][field_name], + "MemoryWeights" => &raw_schedule["memory_weights"][field_name], + _ => &raw_schedule, + }; + + let default_value = match type_name.as_str() { + "Weight" => { + let ref_time = + TokenStream::from_str(&value["ref_time"].to_string()).unwrap(); + let proof_size = + TokenStream::from_str(&value["proof_size"].to_string()).unwrap(); + quote! { + Weight { + ref_time: #ref_time, + proof_size: #proof_size, + } + } + } + "Option" => { + let value = TokenStream::from_str(&value.to_string()).unwrap(); + quote! { Some(#value) } + } + "u32" | "u16" => { + let value = TokenStream::from_str(&value.to_string()).unwrap(); + quote! { #value } + } + _ => quote! { #ty::default() }, + }; + + quote! { + #field_ident: #default_value, + } + }); + + declarations.push(quote! { #structure }); + declarations.push(quote! { + impl Default for #structure_ident { + fn default() -> Self { + Self { + #(#fields)* + } + } + } + }); + } + + declarations.push(quote! { + pub struct Weight { + pub ref_time: u64, + pub proof_size: u64, + } + }); + + let output = declarations + .into_iter() + .map(|stream| stream.to_string()) + .collect::>() + .join("\n\n"); + let formatted = + format_with_rustfmt(format!("{}{output}", LICENSE.trim_start()).as_bytes()); + println!("{formatted}"); + } } }