From b25cdac863d5eeba4ce8553eff2a3557cc3e01bd Mon Sep 17 00:00:00 2001 From: Graydon Hoare Date: Mon, 18 Mar 2024 21:35:36 -0700 Subject: [PATCH] Add tests and calibration for new VM instantiation cost model --- .../benches/common/cost_types/vm_ops.rs | 41 +- .../common/cost_types/wasm_insn_exec.rs | 83 ++-- soroban-env-host/benches/common/mod.rs | 4 +- .../benches/worst_case_linear_models.rs | 91 ++++- soroban-env-host/src/budget.rs | 115 +++--- .../src/cost_runner/cost_types/vm_ops.rs | 77 ++-- soroban-env-host/src/test/lifecycle.rs | 377 ++++++++++++++++++ soroban-env-host/src/testutils.rs | 26 ++ soroban-env-host/src/vm.rs | 1 + soroban-env-host/src/vm/parsed_module.rs | 74 ++-- 10 files changed, 720 insertions(+), 169 deletions(-) diff --git a/soroban-env-host/benches/common/cost_types/vm_ops.rs b/soroban-env-host/benches/common/cost_types/vm_ops.rs index af4e2334d..a71c9db84 100644 --- a/soroban-env-host/benches/common/cost_types/vm_ops.rs +++ b/soroban-env-host/benches/common/cost_types/vm_ops.rs @@ -69,23 +69,22 @@ pub(crate) use v21::*; #[cfg(feature = "next")] mod v21 { use super::super::wasm_insn_exec::{ - wasm_module_with_n_data_segments, wasm_module_with_n_elem_segments, - wasm_module_with_n_exports, wasm_module_with_n_globals, wasm_module_with_n_imports, - wasm_module_with_n_insns, wasm_module_with_n_internal_funcs, - wasm_module_with_n_memory_pages, wasm_module_with_n_table_entries, - wasm_module_with_n_types, + wasm_module_with_n_data_segment_bytes, wasm_module_with_n_data_segments, + wasm_module_with_n_elem_segments, wasm_module_with_n_exports, wasm_module_with_n_globals, + wasm_module_with_n_imports, wasm_module_with_n_insns, wasm_module_with_n_internal_funcs, + wasm_module_with_n_table_entries, wasm_module_with_n_types, }; use super::*; use soroban_env_host::{ cost_runner::{ - InstantiateWasmDataSegmentsRun, InstantiateWasmElemSegmentsRun, - InstantiateWasmExportsRun, InstantiateWasmFunctionsRun, InstantiateWasmGlobalsRun, - InstantiateWasmImportsRun, InstantiateWasmInstructionsRun, - InstantiateWasmMemoryPagesRun, InstantiateWasmTableEntriesRun, InstantiateWasmTypesRun, + InstantiateWasmDataSegmentBytesRun, InstantiateWasmDataSegmentsRun, + InstantiateWasmElemSegmentsRun, InstantiateWasmExportsRun, InstantiateWasmFunctionsRun, + InstantiateWasmGlobalsRun, InstantiateWasmImportsRun, InstantiateWasmInstructionsRun, + InstantiateWasmTableEntriesRun, InstantiateWasmTypesRun, ParseWasmDataSegmentBytesRun, ParseWasmDataSegmentsRun, ParseWasmElemSegmentsRun, ParseWasmExportsRun, ParseWasmFunctionsRun, ParseWasmGlobalsRun, ParseWasmImportsRun, - ParseWasmInstructionsRun, ParseWasmMemoryPagesRun, ParseWasmTableEntriesRun, - ParseWasmTypesRun, VmCachedInstantiationRun, VmInstantiationSample, + ParseWasmInstructionsRun, ParseWasmTableEntriesRun, ParseWasmTypesRun, + VmCachedInstantiationRun, VmInstantiationSample, }, xdr, Host, }; @@ -101,7 +100,7 @@ mod v21 { pub(crate) struct ParseWasmElemSegmentsMeasure; pub(crate) struct ParseWasmImportsMeasure; pub(crate) struct ParseWasmExportsMeasure; - pub(crate) struct ParseWasmMemoryPagesMeasure; + pub(crate) struct ParseWasmDataSegmentBytesMeasure; pub(crate) struct InstantiateWasmInstructionsMeasure; pub(crate) struct InstantiateWasmFunctionsMeasure; @@ -112,7 +111,7 @@ mod v21 { pub(crate) struct InstantiateWasmElemSegmentsMeasure; pub(crate) struct InstantiateWasmImportsMeasure; pub(crate) struct InstantiateWasmExportsMeasure; - pub(crate) struct InstantiateWasmMemoryPagesMeasure; + pub(crate) struct InstantiateWasmDataSegmentBytesMeasure; // Protocol 21 coarse instantiation-phase cost model impl_measurement_for_instantiation_cost_type!( @@ -188,11 +187,11 @@ mod v21 { 30 ); impl_measurement_for_instantiation_cost_type!( - ParseWasmMemoryPagesRun, - ParseWasmMemoryPagesMeasure, - wasm_module_with_n_memory_pages, + ParseWasmDataSegmentBytesRun, + ParseWasmDataSegmentBytesMeasure, + wasm_module_with_n_data_segment_bytes, true, - 30 + 200000 ); impl_measurement_for_instantiation_cost_type!( @@ -259,10 +258,10 @@ mod v21 { 30 ); impl_measurement_for_instantiation_cost_type!( - InstantiateWasmMemoryPagesRun, - InstantiateWasmMemoryPagesMeasure, - wasm_module_with_n_memory_pages, + InstantiateWasmDataSegmentBytesRun, + InstantiateWasmDataSegmentBytesMeasure, + wasm_module_with_n_data_segment_bytes, true, - 30 + 200000 ); } diff --git a/soroban-env-host/benches/common/cost_types/wasm_insn_exec.rs b/soroban-env-host/benches/common/cost_types/wasm_insn_exec.rs index 9b90938ba..67cf4f345 100644 --- a/soroban-env-host/benches/common/cost_types/wasm_insn_exec.rs +++ b/soroban-env-host/benches/common/cost_types/wasm_insn_exec.rs @@ -14,22 +14,56 @@ struct WasmModule { overhead: u64, } +// ModEmitter's default constructors are a little too spartan for our needs, we +// want our benchmarks to all have at least one imported function and at least +// one defined and exported function, so we're in the right performance tier. +// But we also don't want to go changing those constructors since it'll perturb +// a lot of non-benchmark users. +trait ModEmitterExt { + fn bench_default() -> Self; + fn bench_from_configs(mem_pages: u32, elem_count: u32) -> Self; + fn add_bench_import(self) -> Self; + fn add_bench_export(self) -> Self; + fn add_bench_baseline_material(self) -> Self; +} + +impl ModEmitterExt for ModEmitter { + fn add_bench_import(mut self) -> Self { + self.import_func("t", "_", Arity(0)); + self + } + fn add_bench_export(self) -> Self { + let mut fe = self.func(Arity(0), 0); + fe.push(Symbol::try_from_small_str("pass").unwrap()); + fe.finish_and_export("default") + } + fn add_bench_baseline_material(self) -> Self { + self.add_bench_import().add_bench_export() + } + + fn bench_default() -> Self { + Self::add_bench_baseline_material(ModEmitter::default()) + } + + fn bench_from_configs(mem_pages: u32, elem_count: u32) -> Self { + Self::add_bench_baseline_material(ModEmitter::from_configs(mem_pages, elem_count)) + } +} + pub fn wasm_module_with_n_internal_funcs(n: usize) -> Vec { - let mut me = ModEmitter::default(); + let mut me = ModEmitter::bench_default(); for _ in 0..n { let mut fe = me.func(Arity(0), 0); fe.push(Symbol::try_from_small_str("pass").unwrap()); (me, _) = fe.finish(); } - let mut fe = me.func(Arity(0), 0); - fe.push(Symbol::try_from_small_str("pass").unwrap()); - fe.finish_and_export("test").finish() + me.finish() } pub fn wasm_module_with_n_insns(n: usize) -> Vec { // We actually emit 4 instructions per loop iteration, so we need to divide by 4. let n = 1 + (n / 4); - let mut fe = ModEmitter::default().func(Arity(1), 0); + let mut fe = ModEmitter::bench_default().func(Arity(1), 0); let arg = fe.args[0]; fe.push(Operand::Const64(1)); for i in 0..n { @@ -43,17 +77,15 @@ pub fn wasm_module_with_n_insns(n: usize) -> Vec { fe.finish_and_export("test").finish() } pub fn wasm_module_with_n_globals(n: usize) -> Vec { - let mut me = ModEmitter::default(); + let mut me = ModEmitter::bench_default(); for i in 0..n { me.global(ValType::I64, true, &ConstExpr::i64_const(i as i64)); } - let mut fe = me.func(Arity(0), 0); - fe.push(Symbol::try_from_small_str("pass").unwrap()); - fe.finish_and_export("test").finish() + me.finish() } pub fn wasm_module_with_n_imports(n: usize) -> Vec { - let mut me = ModEmitter::default(); + let mut me = ModEmitter::default().add_bench_import(); let names = Vm::get_all_host_functions(); for (module, name, arity) in names.iter().take(n) { if *module == "t" { @@ -61,13 +93,11 @@ pub fn wasm_module_with_n_imports(n: usize) -> Vec { } me.import_func(module, name, Arity(*arity)); } - let mut fe = me.func(Arity(0), 0); - fe.push(Symbol::try_from_small_str("pass").unwrap()); - fe.finish_and_export("test").finish() + me.add_bench_export().finish() } pub fn wasm_module_with_n_exports(n: usize) -> Vec { - let me = ModEmitter::default(); + let me = ModEmitter::bench_default(); let mut fe = me.func(Arity(0), 0); fe.push(Symbol::try_from_small_str("pass").unwrap()); let (mut me, fid) = fe.finish(); @@ -78,7 +108,7 @@ pub fn wasm_module_with_n_exports(n: usize) -> Vec { } pub fn wasm_module_with_n_table_entries(n: usize) -> Vec { - let me = ModEmitter::from_configs(1, n as u32); + let me = ModEmitter::bench_from_configs(1, n as u32); let mut fe = me.func(Arity(0), 0); fe.push(Symbol::try_from_small_str("pass").unwrap()); let (mut me, f) = fe.finish(); @@ -88,7 +118,7 @@ pub fn wasm_module_with_n_table_entries(n: usize) -> Vec { } pub fn wasm_module_with_n_types(mut n: usize) -> Vec { - let mut me = ModEmitter::default(); + let mut me = ModEmitter::bench_default(); // There's a max of 1,000,000 types, so we just make a loop // that covers more than that many combinations, and break when we've got // to the requested number. @@ -151,13 +181,11 @@ pub fn wasm_module_with_n_types(mut n: usize) -> Vec { } } } - let mut fe = me.func(Arity(0), 0); - fe.push(Symbol::try_from_small_str("pass").unwrap()); - fe.finish_and_export("test").finish() + me.finish() } pub fn wasm_module_with_n_elem_segments(n: usize) -> Vec { - let me = ModEmitter::from_configs(1, n as u32); + let me = ModEmitter::bench_from_configs(1, n as u32); let mut fe = me.func(Arity(0), 0); fe.push(Symbol::try_from_small_str("pass").unwrap()); let (mut me, f) = fe.finish(); @@ -169,22 +197,17 @@ pub fn wasm_module_with_n_elem_segments(n: usize) -> Vec { pub fn wasm_module_with_n_data_segments(n: usize) -> Vec { let mem_offset = n as u32 * 1024; - let me = ModEmitter::from_configs(1 + mem_offset / 65536, 0); - let mut fe = me.func(Arity(0), 0); - fe.push(Symbol::try_from_small_str("pass").unwrap()); - let (mut me, _) = fe.finish(); + let mut me = ModEmitter::bench_from_configs(1 + mem_offset / 65536, 0); for _ in 0..n { me.define_data_segment(n as u32 * 1024, vec![1, 2, 3, 4]); } me.finish() } -pub fn wasm_module_with_n_memory_pages(n: usize) -> Vec { - let mut me = ModEmitter::from_configs(n as u32, 0); - me.define_data_segment(0, vec![0xff; n * 0x10000]); - let mut fe = me.func(Arity(0), 0); - fe.push(Symbol::try_from_small_str("pass").unwrap()); - fe.finish_and_export("test").finish() +pub fn wasm_module_with_n_data_segment_bytes(n: usize) -> Vec { + let mut me = ModEmitter::bench_from_configs(1 + (n / 0x10000) as u32, 0); + me.define_data_segment(0, vec![0xff; n]); + me.finish() } fn wasm_module_with_mem_grow(n_pages: usize) -> Vec { diff --git a/soroban-env-host/benches/common/mod.rs b/soroban-env-host/benches/common/mod.rs index 1cb3e035e..249d88c9e 100644 --- a/soroban-env-host/benches/common/mod.rs +++ b/soroban-env-host/benches/common/mod.rs @@ -96,7 +96,7 @@ pub(crate) fn for_each_host_cost_measurement( call_bench::(&mut params)?; call_bench::(&mut params)?; call_bench::(&mut params)?; - call_bench::(&mut params)?; + call_bench::(&mut params)?; call_bench::(&mut params)?; call_bench::(&mut params)?; @@ -107,7 +107,7 @@ pub(crate) fn for_each_host_cost_measurement( call_bench::(&mut params)?; call_bench::(&mut params)?; call_bench::(&mut params)?; - call_bench::(&mut params)?; + call_bench::(&mut params)?; } // These three mem ones are derived analytically, we do not calibrate them typically if std::env::var("INCLUDE_ANALYTICAL_COSTTYPES").is_ok() { diff --git a/soroban-env-host/benches/worst_case_linear_models.rs b/soroban-env-host/benches/worst_case_linear_models.rs index d8a3c84c0..87a5f188f 100644 --- a/soroban-env-host/benches/worst_case_linear_models.rs +++ b/soroban-env-host/benches/worst_case_linear_models.rs @@ -68,6 +68,87 @@ fn write_cost_params_table( tw.flush() } +fn correct_multi_variable_models( + params: &mut BTreeMap, +) { + // Several cost types actually represent additional terms a cost model that + // we're decomposing into multiple variables, such as the cost of VM + // instantiation. When we charge these costs, we charge each variable + // separately, i.e. to charge a 5-variable cost we'll make 5 calls to the + // budget. Only the first of these 5 calls should have a constant factor, + // the rest should have zero as their constant (since they only contribute a + // new linear term), but the calibration code will have put the same (or + // nearly-the-same) nonzero constant term in each `CostComponent`. We + // correct this here by zeroing out the constant term in all but the first + // `CostComponent` of each set, (and attempting to confirm that they all + // have roughly-the-same constant term). + use ContractCostType::*; + const MULTI_VARIABLE_COST_GROUPS: &[&[ContractCostType]] = &[ + &[ + ParseWasmInstructions, + ParseWasmFunctions, + ParseWasmGlobals, + ParseWasmTableEntries, + ParseWasmTypes, + ParseWasmDataSegments, + ParseWasmElemSegments, + ParseWasmImports, + ParseWasmExports, + ParseWasmDataSegmentBytes, + ], + &[ + InstantiateWasmInstructions, + InstantiateWasmFunctions, + InstantiateWasmGlobals, + InstantiateWasmTableEntries, + InstantiateWasmTypes, + InstantiateWasmDataSegments, + InstantiateWasmElemSegments, + InstantiateWasmImports, + InstantiateWasmExports, + InstantiateWasmDataSegmentBytes, + ], + ]; + for group in MULTI_VARIABLE_COST_GROUPS { + let mut iter = group.iter(); + if let Some(first) = iter.next() { + let Some((first_cpu, first_mem)) = params.get(&CostType::Contract(*first)).cloned() + else { + continue; + }; + for ty in iter { + let Some((cpu, mem)) = params.get_mut(&CostType::Contract(*ty)) else { + continue; + }; + let cpu_const_diff_ratio = (cpu.const_term as f64 - first_cpu.const_term as f64) + / first_cpu.const_term as f64; + let mem_const_diff_ratio = (mem.const_term as f64 - first_mem.const_term as f64) + / first_mem.const_term as f64; + assert!( + cpu_const_diff_ratio < 0.25, + "cost type {:?} has too large a constant CPU term over {:?}: {:?} vs. {:?} ({:?} diff)", + ty, + first, + cpu.const_term, + first_cpu.const_term, + cpu_const_diff_ratio + ); + assert!( + mem_const_diff_ratio < 0.25, + "cost type {:?} has too large a constant memory term over {:?}: {:?} vs. {:?} ({:?} diff)", + ty, + first, + mem.const_term, + first_mem.const_term, + mem_const_diff_ratio + ); + cpu.const_term = 0; + mem.const_term = 0; + } + } + } +} + fn write_budget_params_code( params: &BTreeMap, wasm_tier_cost: &BTreeMap, @@ -335,12 +416,18 @@ fn extract_wasmi_fuel_costs( #[cfg(all(test, any(target_os = "linux", target_os = "macos")))] fn main() -> std::io::Result<()> { - let params = if std::env::var("RUN_EXPERIMENT").is_err() { + let mut params = if std::env::var("RUN_EXPERIMENT").is_err() { for_each_host_cost_measurement::()? } else { for_each_experimental_cost_measurement::()? }; - let params_wasm = for_each_wasm_insn_measurement::()?; + let params_wasm = if std::env::var("SKIP_WASM_INSNS").is_err() { + for_each_wasm_insn_measurement::()? + } else { + BTreeMap::new() + }; + + correct_multi_variable_models(&mut params); let mut tw = TabWriter::new(vec![]) .padding(5) diff --git a/soroban-env-host/src/budget.rs b/soroban-env-host/src/budget.rs index 7d23c6dc1..8b3e1ddbb 100644 --- a/soroban-env-host/src/budget.rs +++ b/soroban-env-host/src/budget.rs @@ -112,9 +112,9 @@ impl Default for BudgetTracker { #[cfg(feature = "next")] ContractCostType::ParseWasmExports => init_input(), #[cfg(feature = "next")] - ContractCostType::ParseWasmMemoryPages => init_input(), + ContractCostType::ParseWasmDataSegmentBytes => init_input(), #[cfg(feature = "next")] - ContractCostType::InstantiateWasmInstructions => init_input(), + ContractCostType::InstantiateWasmInstructions => (), #[cfg(feature = "next")] ContractCostType::InstantiateWasmFunctions => init_input(), #[cfg(feature = "next")] @@ -122,7 +122,7 @@ impl Default for BudgetTracker { #[cfg(feature = "next")] ContractCostType::InstantiateWasmTableEntries => init_input(), #[cfg(feature = "next")] - ContractCostType::InstantiateWasmTypes => init_input(), + ContractCostType::InstantiateWasmTypes => (), #[cfg(feature = "next")] ContractCostType::InstantiateWasmDataSegments => init_input(), #[cfg(feature = "next")] @@ -132,7 +132,7 @@ impl Default for BudgetTracker { #[cfg(feature = "next")] ContractCostType::InstantiateWasmExports => init_input(), #[cfg(feature = "next")] - ContractCostType::InstantiateWasmMemoryPages => init_input(), + ContractCostType::InstantiateWasmDataSegmentBytes => init_input(), } } mt @@ -409,106 +409,106 @@ impl Default for BudgetImpl { cpu.const_term = 1058; cpu.lin_term = ScaledU64(501); } + #[cfg(feature = "next")] ContractCostType::ParseWasmInstructions => { - cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.const_term = 72736; + cpu.lin_term = ScaledU64(25420); } #[cfg(feature = "next")] ContractCostType::ParseWasmFunctions => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(536688); } #[cfg(feature = "next")] ContractCostType::ParseWasmGlobals => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(176902); } #[cfg(feature = "next")] ContractCostType::ParseWasmTableEntries => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(29639); } #[cfg(feature = "next")] ContractCostType::ParseWasmTypes => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(1048891); } #[cfg(feature = "next")] ContractCostType::ParseWasmDataSegments => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(236970); } #[cfg(feature = "next")] ContractCostType::ParseWasmElemSegments => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(317249); } #[cfg(feature = "next")] ContractCostType::ParseWasmImports => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(694667); } #[cfg(feature = "next")] ContractCostType::ParseWasmExports => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(427037); } #[cfg(feature = "next")] - ContractCostType::ParseWasmMemoryPages => { - cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + ContractCostType::ParseWasmDataSegmentBytes => { + cpu.const_term = 66075; + cpu.lin_term = ScaledU64(28); } - #[cfg(feature = "next")] ContractCostType::InstantiateWasmInstructions => { - cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.const_term = 25059; + cpu.lin_term = ScaledU64(0); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmFunctions => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(7503); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmGlobals => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(10761); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmTableEntries => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(3211); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmTypes => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(0); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmDataSegments => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(16370); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmElemSegments => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(28309); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmImports => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(683461); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmExports => { cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + cpu.lin_term = ScaledU64(297065); } #[cfg(feature = "next")] - ContractCostType::InstantiateWasmMemoryPages => { - cpu.const_term = 0; - cpu.lin_term = ScaledU64(1); + ContractCostType::InstantiateWasmDataSegmentBytes => { + cpu.const_term = 25191; + cpu.lin_term = ScaledU64(14); } } @@ -613,106 +613,107 @@ impl Default for BudgetImpl { mem.const_term = 0; mem.lin_term = ScaledU64(0); } + #[cfg(feature = "next")] ContractCostType::ParseWasmInstructions => { - mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.const_term = 17564; + mem.lin_term = ScaledU64(6457); } #[cfg(feature = "next")] ContractCostType::ParseWasmFunctions => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(47464); } #[cfg(feature = "next")] ContractCostType::ParseWasmGlobals => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(13420); } #[cfg(feature = "next")] ContractCostType::ParseWasmTableEntries => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(6285); } #[cfg(feature = "next")] ContractCostType::ParseWasmTypes => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(64670); } #[cfg(feature = "next")] ContractCostType::ParseWasmDataSegments => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(29074); } #[cfg(feature = "next")] ContractCostType::ParseWasmElemSegments => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(48095); } #[cfg(feature = "next")] ContractCostType::ParseWasmImports => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(102890); } #[cfg(feature = "next")] ContractCostType::ParseWasmExports => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(36394); } #[cfg(feature = "next")] - ContractCostType::ParseWasmMemoryPages => { - mem.const_term = 0; - mem.lin_term = ScaledU64(1); + ContractCostType::ParseWasmDataSegmentBytes => { + mem.const_term = 17580; + mem.lin_term = ScaledU64(257); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmInstructions => { - mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.const_term = 70192; + mem.lin_term = ScaledU64(0); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmFunctions => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(14613); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmGlobals => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(6833); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmTableEntries => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(1025); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmTypes => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(0); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmDataSegments => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(129632); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmElemSegments => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(13665); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmImports => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(77273); } #[cfg(feature = "next")] ContractCostType::InstantiateWasmExports => { mem.const_term = 0; - mem.lin_term = ScaledU64(1); + mem.lin_term = ScaledU64(9176); } #[cfg(feature = "next")] - ContractCostType::InstantiateWasmMemoryPages => { - mem.const_term = 0; - mem.lin_term = ScaledU64(1); + ContractCostType::InstantiateWasmDataSegmentBytes => { + mem.const_term = 69256; + mem.lin_term = ScaledU64(126); } } } diff --git a/soroban-env-host/src/cost_runner/cost_types/vm_ops.rs b/soroban-env-host/src/cost_runner/cost_types/vm_ops.rs index fdd19cd9e..b21d02c0e 100644 --- a/soroban-env-host/src/cost_runner/cost_types/vm_ops.rs +++ b/soroban-env-host/src/cost_runner/cost_types/vm_ops.rs @@ -58,12 +58,13 @@ mod v21 { use super::*; use crate::vm::ParsedModule; use crate::xdr::ContractCostType::{ - InstantiateWasmDataSegments, InstantiateWasmElemSegments, InstantiateWasmExports, - InstantiateWasmFunctions, InstantiateWasmGlobals, InstantiateWasmImports, - InstantiateWasmInstructions, InstantiateWasmMemoryPages, InstantiateWasmTableEntries, - InstantiateWasmTypes, ParseWasmDataSegments, ParseWasmElemSegments, ParseWasmExports, - ParseWasmFunctions, ParseWasmGlobals, ParseWasmImports, ParseWasmInstructions, - ParseWasmMemoryPages, ParseWasmTableEntries, ParseWasmTypes, VmCachedInstantiation, + InstantiateWasmDataSegmentBytes, InstantiateWasmDataSegments, InstantiateWasmElemSegments, + InstantiateWasmExports, InstantiateWasmFunctions, InstantiateWasmGlobals, + InstantiateWasmImports, InstantiateWasmInstructions, InstantiateWasmTableEntries, + InstantiateWasmTypes, ParseWasmDataSegmentBytes, ParseWasmDataSegments, + ParseWasmElemSegments, ParseWasmExports, ParseWasmFunctions, ParseWasmGlobals, + ParseWasmImports, ParseWasmInstructions, ParseWasmTableEntries, ParseWasmTypes, + VmCachedInstantiation, }; macro_rules! impl_costrunner_for_parse_cost_type { @@ -107,7 +108,7 @@ mod v21 { } macro_rules! impl_costrunner_for_instantiation_cost_type { - ($RUNNER:ty, $COST:ident) => { + ($RUNNER:ty, $COST:ident, $IS_CONST:expr) => { impl CostRunner for $RUNNER { const COST_TYPE: CostType = CostType::Contract($COST); @@ -133,7 +134,11 @@ mod v21 { _iter: u64, sample: Self::SampleType, ) -> Self::RecycledType { - black_box(host.charge_budget($COST, Some(0)).unwrap()); + if $IS_CONST { + black_box(host.charge_budget($COST, None).unwrap()); + } else { + black_box(host.charge_budget($COST, Some(0)).unwrap()); + } black_box((None, sample.wasm)) } } @@ -154,7 +159,7 @@ mod v21 { pub struct ParseWasmElemSegmentsRun; pub struct ParseWasmImportsRun; pub struct ParseWasmExportsRun; - pub struct ParseWasmMemoryPagesRun; + pub struct ParseWasmDataSegmentBytesRun; pub struct InstantiateWasmInstructionsRun; pub struct InstantiateWasmFunctionsRun; @@ -165,7 +170,7 @@ mod v21 { pub struct InstantiateWasmElemSegmentsRun; pub struct InstantiateWasmImportsRun; pub struct InstantiateWasmExportsRun; - pub struct InstantiateWasmMemoryPagesRun; + pub struct InstantiateWasmDataSegmentBytesRun; impl_costrunner_for_parse_cost_type!(VmInstantiationRun, VmInstantiation); impl_costrunner_for_parse_cost_type!(ParseWasmInstructionsRun, ParseWasmInstructions); @@ -177,35 +182,61 @@ mod v21 { impl_costrunner_for_parse_cost_type!(ParseWasmElemSegmentsRun, ParseWasmElemSegments); impl_costrunner_for_parse_cost_type!(ParseWasmImportsRun, ParseWasmImports); impl_costrunner_for_parse_cost_type!(ParseWasmExportsRun, ParseWasmExports); - impl_costrunner_for_parse_cost_type!(ParseWasmMemoryPagesRun, ParseWasmMemoryPages); + impl_costrunner_for_parse_cost_type!(ParseWasmDataSegmentBytesRun, ParseWasmDataSegmentBytes); - impl_costrunner_for_instantiation_cost_type!(VmCachedInstantiationRun, VmCachedInstantiation); + impl_costrunner_for_instantiation_cost_type!( + VmCachedInstantiationRun, + VmCachedInstantiation, + false + ); impl_costrunner_for_instantiation_cost_type!( InstantiateWasmInstructionsRun, - InstantiateWasmInstructions + InstantiateWasmInstructions, + true ); impl_costrunner_for_instantiation_cost_type!( InstantiateWasmFunctionsRun, - InstantiateWasmFunctions + InstantiateWasmFunctions, + false + ); + impl_costrunner_for_instantiation_cost_type!( + InstantiateWasmGlobalsRun, + InstantiateWasmGlobals, + false ); - impl_costrunner_for_instantiation_cost_type!(InstantiateWasmGlobalsRun, InstantiateWasmGlobals); impl_costrunner_for_instantiation_cost_type!( InstantiateWasmTableEntriesRun, - InstantiateWasmTableEntries + InstantiateWasmTableEntries, + false + ); + impl_costrunner_for_instantiation_cost_type!( + InstantiateWasmTypesRun, + InstantiateWasmTypes, + true ); - impl_costrunner_for_instantiation_cost_type!(InstantiateWasmTypesRun, InstantiateWasmTypes); impl_costrunner_for_instantiation_cost_type!( InstantiateWasmDataSegmentsRun, - InstantiateWasmDataSegments + InstantiateWasmDataSegments, + false ); impl_costrunner_for_instantiation_cost_type!( InstantiateWasmElemSegmentsRun, - InstantiateWasmElemSegments + InstantiateWasmElemSegments, + false + ); + impl_costrunner_for_instantiation_cost_type!( + InstantiateWasmImportsRun, + InstantiateWasmImports, + false + ); + impl_costrunner_for_instantiation_cost_type!( + InstantiateWasmExportsRun, + InstantiateWasmExports, + false ); - impl_costrunner_for_instantiation_cost_type!(InstantiateWasmImportsRun, InstantiateWasmImports); - impl_costrunner_for_instantiation_cost_type!(InstantiateWasmExportsRun, InstantiateWasmExports); impl_costrunner_for_instantiation_cost_type!( - InstantiateWasmMemoryPagesRun, - InstantiateWasmMemoryPages + InstantiateWasmDataSegmentBytesRun, + InstantiateWasmDataSegmentBytes, + false ); } diff --git a/soroban-env-host/src/test/lifecycle.rs b/soroban-env-host/src/test/lifecycle.rs index 10ecd31be..bc4f64171 100644 --- a/soroban-env-host/src/test/lifecycle.rs +++ b/soroban-env-host/src/test/lifecycle.rs @@ -566,3 +566,380 @@ fn test_large_contract() { assert!(err.error.is_type(ScErrorType::Budget)); assert!(err.error.is_code(ScErrorCode::ExceededLimit)); } + +#[cfg(feature = "next")] +#[allow(dead_code)] +mod cap_54_55_56 { + + use super::*; + use crate::{ + storage::{FootprintMap, StorageMap}, + test::observe::ObservedHost, + testutils::wasm::wasm_module_with_a_bit_of_everything, + vm::ModuleCache, + xdr::{ + ContractCostType::{self, *}, + LedgerEntry, LedgerKey, + }, + AddressObject, HostError, + }; + use std::rc::Rc; + + const V_NEW: u32 = ModuleCache::MIN_LEDGER_VERSION; + const V_OLD: u32 = V_NEW - 1; + const NEW_COST_TYPES: &'static [ContractCostType] = &[ + ParseWasmInstructions, + ParseWasmFunctions, + ParseWasmGlobals, + ParseWasmTableEntries, + ParseWasmTypes, + ParseWasmDataSegments, + ParseWasmElemSegments, + ParseWasmImports, + ParseWasmExports, + ParseWasmDataSegmentBytes, + InstantiateWasmInstructions, + InstantiateWasmFunctions, + InstantiateWasmGlobals, + InstantiateWasmTableEntries, + InstantiateWasmTypes, + InstantiateWasmDataSegments, + InstantiateWasmElemSegments, + InstantiateWasmImports, + InstantiateWasmExports, + InstantiateWasmDataSegmentBytes, + ]; + + fn new_host_with_protocol_and_uploaded_contract( + hostname: &'static str, + proto: u32, + ) -> Result<(ObservedHost, AddressObject), HostError> { + let host = Host::test_host_with_recording_footprint(); + let host = ObservedHost::new(hostname, host); + host.with_mut_ledger_info(|ledger_info| ledger_info.protocol_version = proto)?; + let contract_addr_obj = + host.register_test_contract_wasm(&wasm_module_with_a_bit_of_everything(proto)); + Ok((host, contract_addr_obj)) + } + + struct ContractAndWasmEntries { + contract_key: Rc, + contract_entry: Rc, + wasm_key: Rc, + wasm_entry: Rc, + } + + impl ContractAndWasmEntries { + fn from_contract_addr( + host: &Host, + contract_addr_obj: AddressObject, + ) -> Result { + let contract_id = host.contract_id_from_address(contract_addr_obj)?; + Self::from_contract_id(host, contract_id) + } + fn reload(self, host: &Host) -> Result { + host.with_mut_storage(|storage| { + let budget = host.budget_cloned(); + let contract_entry = storage.get(&self.contract_key, &budget)?; + let wasm_entry = storage.get(&self.wasm_key, &budget)?; + Ok(ContractAndWasmEntries { + contract_key: self.contract_key, + contract_entry, + wasm_key: self.wasm_key, + wasm_entry, + }) + }) + } + fn from_contract_id(host: &Host, contract_id: Hash) -> Result { + let contract_key = host.contract_instance_ledger_key(&contract_id)?; + let wasm_hash = get_contract_wasm_ref(host, contract_id); + let wasm_key = host.contract_code_ledger_key(&wasm_hash)?; + + host.with_mut_storage(|storage| { + let budget = host.budget_cloned(); + let contract_entry = storage.get(&contract_key, &budget)?; + let wasm_entry = storage.get(&wasm_key, &budget)?; + Ok(ContractAndWasmEntries { + contract_key, + contract_entry, + wasm_key, + wasm_entry, + }) + }) + } + fn read_only_footprint(&self, budget: &Budget) -> Footprint { + Footprint( + FootprintMap::new() + .insert(self.contract_key.clone(), AccessType::ReadOnly, budget) + .unwrap() + .insert(self.wasm_key.clone(), AccessType::ReadOnly, budget) + .unwrap(), + ) + } + fn wasm_writing_footprint(&self, budget: &Budget) -> Footprint { + Footprint( + FootprintMap::new() + .insert(self.contract_key.clone(), AccessType::ReadOnly, budget) + .unwrap() + .insert(self.wasm_key.clone(), AccessType::ReadWrite, budget) + .unwrap(), + ) + } + fn storage_map(&self, budget: &Budget) -> StorageMap { + StorageMap::new() + .insert( + self.contract_key.clone(), + Some((self.contract_entry.clone(), Some(99999))), + budget, + ) + .unwrap() + .insert( + self.wasm_key.clone(), + Some((self.wasm_entry.clone(), Some(99999))), + budget, + ) + .unwrap() + } + fn read_only_storage(&self, budget: &Budget) -> Storage { + Storage::with_enforcing_footprint_and_map( + self.read_only_footprint(budget), + self.storage_map(budget), + ) + } + fn wasm_writing_storage(&self, budget: &Budget) -> Storage { + Storage::with_enforcing_footprint_and_map( + self.wasm_writing_footprint(budget), + self.storage_map(budget), + ) + } + } + + fn upload_and_get_contract_and_wasm_entries( + upload_hostname: &'static str, + upload_proto: u32, + ) -> Result { + let (host, contract) = + new_host_with_protocol_and_uploaded_contract(upload_hostname, upload_proto)?; + ContractAndWasmEntries::from_contract_addr(&host, contract) + } + + fn upload_and_call( + upload_hostname: &'static str, + upload_proto: u32, + call_hostname: &'static str, + call_proto: u32, + ) -> Result<(Budget, Storage), HostError> { + // Phase 1: upload contract, tear down host, "close the ledger" and possibly change protocol. + let (host, contract) = + new_host_with_protocol_and_uploaded_contract(upload_hostname, upload_proto)?; + let contract_id = host.contract_id_from_address(contract)?; + let realhost = host.clone(); + drop(host); + let (storage, _events) = realhost.try_finish()?; + + // Phase 2: build new host with previous ledger output as storage, call contract. Possibly on new protocol. + let host = Host::with_storage_and_budget(storage, Budget::default()); + host.enable_debug()?; + let host = ObservedHost::new(call_hostname, host); + host.set_ledger_info(LedgerInfo { + protocol_version: call_proto, + ..Default::default() + })?; + let contract = host.add_host_object(crate::xdr::ScAddress::Contract(contract_id))?; + let _ = host.call( + contract, + Symbol::try_from_small_str("test").unwrap(), + host.vec_new()?, + )?; + let realhost = host.clone(); + drop(host); + let budget = realhost.budget_cloned(); + let (storage, _events) = realhost.try_finish()?; + Ok((budget, storage)) + } + + fn code_entry_has_cost_inputs(entry: &Rc) -> bool { + match &entry.data { + LedgerEntryData::ContractCode(cce) => match &cce.ext { + crate::xdr::ContractCodeEntryExt::V1(_v1) => return true, + _ => (), + }, + _ => panic!("expected LedgerEntryData::ContractCode"), + } + false + } + + // Test that running on protocol vOld only charges the VmInstantiation cost + // type. + #[test] + fn test_v_old_only_charges_vm_instantiation() -> Result<(), HostError> { + let (budget, _storage) = upload_and_call( + "test_v_old_only_charges_vminstantiation_upload", + V_OLD, + "test_v_old_only_charges_vm_instantiation_call", + V_OLD, + )?; + assert_ne!(budget.get_tracker(VmInstantiation)?.cpu, 0); + assert_eq!(budget.get_tracker(VmCachedInstantiation)?.cpu, 0); + for ct in NEW_COST_TYPES { + assert_eq!(budget.get_tracker(*ct)?.cpu, 0); + } + Ok(()) + } + + // Test that running on protocol vNew on a ContractCode LE that does not have + // ContractCodeCostInputs charges the VmInstantiation and VmCachedInstantiation + // cost types. + #[test] + fn test_v_new_no_contract_code_cost_inputs() -> Result<(), HostError> { + let (budget, _storage) = upload_and_call( + "test_v_new_no_contract_code_cost_inputs_upload", + V_OLD, + "test_v_new_no_contract_code_cost_inputs_call", + V_NEW, + )?; + assert_ne!(budget.get_tracker(VmInstantiation)?.cpu, 0); + assert_ne!(budget.get_tracker(VmCachedInstantiation)?.cpu, 0); + for ct in NEW_COST_TYPES { + assert_eq!(budget.get_tracker(*ct)?.cpu, 0); + } + Ok(()) + } + + // Test that running on protocol vNew does add ContractCodeCostInputs to a + // newly uploaded ContractCode LE. + #[test] + fn test_v_new_gets_contract_code_cost_inputs() -> Result<(), HostError> { + let entries = upload_and_get_contract_and_wasm_entries( + "test_v_new_gets_contract_code_cost_inputs_upload", + V_NEW, + )?; + assert!(code_entry_has_cost_inputs(&entries.wasm_entry)); + Ok(()) + } + + // Test that running on protocol vNew on a ContractCode LE that does have + // ContractCodeCostInputs charges the new cost model types nonzero costs + // (both parsing and instantiation). + #[test] + fn test_v_new_with_contract_code_cost_inputs_causes_nonzero_costs() -> Result<(), HostError> { + let (budget, _storage) = upload_and_call( + "test_v_new_with_contract_code_cost_inputs_causes_nonzero_costs_upload", + V_NEW, + "test_v_new_with_contract_code_cost_inputs_causes_nonzero_costs_call", + V_NEW, + )?; + assert_eq!(budget.get_tracker(VmInstantiation)?.cpu, 0); + assert_eq!(budget.get_tracker(VmCachedInstantiation)?.cpu, 0); + for ct in NEW_COST_TYPES { + if *ct == InstantiateWasmTypes { + // This is a zero-cost type in the current calibration of the + // new model -- and exceptional case in this test -- though we + // keep it in case it becomes nonzero at some point (it's + // credible that it would). + continue; + } + assert_ne!(budget.get_tracker(*ct)?.cpu, 0); + } + Ok(()) + } + + // Test that running on protocol vOld does not add ContractCodeCostInputs to a + // newly uploaded ContractCode LE. + #[test] + fn test_v_old_no_contract_code_cost_inputs() -> Result<(), HostError> { + let entries = upload_and_get_contract_and_wasm_entries( + "test_v_old_no_contract_code_cost_inputs_upload", + V_OLD, + )?; + assert!(!code_entry_has_cost_inputs(&entries.wasm_entry)); + Ok(()) + } + + // Test that running on protocol vOld does not rewrite a ContractCode LE when it + // already exists. + #[test] + fn test_v_old_no_rewrite() -> Result<(), HostError> { + let entries = + upload_and_get_contract_and_wasm_entries("test_v_old_no_rewrite_upload", V_OLD)?; + // make a new storage map for a new run + let budget = Budget::default(); + let storage = entries.read_only_storage(&budget); + let host = Host::with_storage_and_budget(storage, budget); + let host = ObservedHost::new("test_v_old_no_rewrite_call", host); + host.set_ledger_info(LedgerInfo { + protocol_version: V_OLD, + ..Default::default() + })?; + host.upload_contract_wasm(wasm_module_with_a_bit_of_everything(V_OLD))?; + Ok(()) + } + + // Test that running on protocol vNew does rewrite a ContractCode LE when it + // already exists but doesn't yet have ContractCodeCostInputs. + #[test] + fn test_v_new_rewrite() -> Result<(), HostError> { + let entries = upload_and_get_contract_and_wasm_entries("test_v_new_rewrite_upload", V_OLD)?; + assert!(!code_entry_has_cost_inputs(&entries.wasm_entry)); + + // make a new storage map for a new upload but with read-only footprint -- this should fail + let budget = Budget::default(); + let storage = entries.read_only_storage(&budget); + let host = Host::with_storage_and_budget(storage, budget); + let host = ObservedHost::new("test_v_new_rewrite_call_fail", host); + host.set_ledger_info(LedgerInfo { + protocol_version: V_NEW, + ..Default::default() + })?; + let wasm_blob = match &entries.wasm_entry.data { + LedgerEntryData::ContractCode(cce) => cce.code.to_vec(), + _ => panic!("expected ContractCode"), + }; + assert!(host.upload_contract_wasm(wasm_blob.clone()).is_err()); + let entries = entries.reload(&host)?; + assert!(!code_entry_has_cost_inputs(&entries.wasm_entry)); + + // make a new storage map for a new upload but with read-write footprint -- this should pass + let budget = Budget::default(); + let storage = entries.wasm_writing_storage(&budget); + let host = Host::with_storage_and_budget(storage, budget); + let host = ObservedHost::new("test_v_new_rewrite_call_succeed", host); + host.set_ledger_info(LedgerInfo { + protocol_version: V_NEW, + ..Default::default() + })?; + host.upload_contract_wasm(wasm_blob)?; + let entries = entries.reload(&host)?; + assert!(code_entry_has_cost_inputs(&entries.wasm_entry)); + + Ok(()) + } + + // Test that running on protocol vNew does not rewrite a ContractCode LE when it + // already exists and already has ContractCodeCostInputs. + #[test] + fn test_v_new_no_rewrite() -> Result<(), HostError> { + let entries = + upload_and_get_contract_and_wasm_entries("test_v_new_no_rewrite_upload", V_NEW)?; + assert!(code_entry_has_cost_inputs(&entries.wasm_entry)); + + // make a new storage map for a new upload but with read-only footprint -- this should pass + let budget = Budget::default(); + let storage = entries.read_only_storage(&budget); + let host = Host::with_storage_and_budget(storage, budget); + let host = ObservedHost::new("test_v_new_no_rewrite_call_pass", host); + host.set_ledger_info(LedgerInfo { + protocol_version: V_NEW, + ..Default::default() + })?; + let wasm_blob = match &entries.wasm_entry.data { + LedgerEntryData::ContractCode(cce) => cce.code.to_vec(), + _ => panic!("expected ContractCode"), + }; + host.upload_contract_wasm(wasm_blob.clone())?; + let entries = entries.reload(&host)?; + assert!(code_entry_has_cost_inputs(&entries.wasm_entry)); + + Ok(()) + } +} diff --git a/soroban-env-host/src/testutils.rs b/soroban-env-host/src/testutils.rs index 5e914e194..82b826a4e 100644 --- a/soroban-env-host/src/testutils.rs +++ b/soroban-env-host/src/testutils.rs @@ -1110,4 +1110,30 @@ pub(crate) mod wasm { fe.call_func(f0); fe.finish_and_export("test").finish() } + + #[cfg(feature = "next")] + pub(crate) fn wasm_module_with_a_bit_of_everything(wasm_proto: u32) -> Vec { + let mut me = ModEmitter::new(); + let pre = get_pre_release_version(INTERFACE_VERSION); + me.custom_section( + &"contractenvmetav0", + interface_meta_with_custom_versions(wasm_proto, pre).as_slice(), + ); + me.table(RefType::FUNCREF, 128, None); + me.memory(1, None, false, false); + me.global(wasm_encoder::ValType::I64, true, &ConstExpr::i64_const(42)); + me.export("memory", wasm_encoder::ExportKind::Memory, 0); + let _f0 = me.import_func("t", "_", Arity(0)); + let mut fe = me.func(Arity(0), 0); + fe.push(Operand::Const64(1)); + fe.push(Operand::Const64(2)); + fe.i64_add(); + fe.drop(); + fe.push(Symbol::try_from_small_str("pass").unwrap()); + let (mut me, fid) = fe.finish(); + me.export_func(fid, "test"); + me.define_elem_funcs(&[fid]); + me.define_data_segment(0x1234, vec![0; 8]); + me.finish() + } } diff --git a/soroban-env-host/src/vm.rs b/soroban-env-host/src/vm.rs index 13ad6cccf..74bd8e4a0 100644 --- a/soroban-env-host/src/vm.rs +++ b/soroban-env-host/src/vm.rs @@ -45,6 +45,7 @@ use wasmi::{Caller, StoreContextMut}; impl wasmi::core::HostError for HostError {} const MAX_VM_ARGS: usize = 32; +#[cfg(feature = "next")] const WASM_STD_MEM_PAGE_SIZE_IN_BYTES: u32 = 0x10000; struct VmInstantiationTimer { diff --git a/soroban-env-host/src/vm/parsed_module.rs b/soroban-env-host/src/vm/parsed_module.rs index 4c28b28f7..018cac0e1 100644 --- a/soroban-env-host/src/vm/parsed_module.rs +++ b/soroban-env-host/src/vm/parsed_module.rs @@ -1,14 +1,13 @@ use crate::{ err, meta::{self, get_ledger_protocol_version}, - vm::WASM_STD_MEM_PAGE_SIZE_IN_BYTES, xdr::{ContractCostType, Limited, ReadXdr, ScEnvMetaEntry, ScErrorCode, ScErrorType}, Host, HostError, DEFAULT_XDR_RW_LIMITS, }; use wasmi::{Engine, Module}; -use super::MAX_VM_ARGS; +use super::{ModuleCache, MAX_VM_ARGS}; use std::io::Cursor; #[derive(Debug, Clone)] @@ -72,8 +71,8 @@ impl VersionedContractCodeCostInputs { Some(inputs.n_exports as u64), )?; host.charge_budget( - ContractCostType::ParseWasmMemoryPages, - Some(inputs.n_memory_pages as u64), + ContractCostType::ParseWasmDataSegmentBytes, + Some(inputs.n_data_segment_bytes as u64), )?; } } @@ -82,17 +81,24 @@ impl VersionedContractCodeCostInputs { pub fn charge_for_instantiation(&self, _host: &Host) -> Result<(), HostError> { match self { Self::V0 { wasm_bytes } => { - _host.charge_budget( - ContractCostType::VmCachedInstantiation, - Some(*wasm_bytes as u64), - )?; + // Before soroban supported cached instantiation, the full cost + // of parsing-and-instantiation was charged to the + // VmInstantiation cost type and we already charged it by the + // time we got here, in `charge_for_parsing` above. At-and-after + // the protocol that enabled cached instantiation, the + // VmInstantiation cost type was repurposed to only cover the + // cost of parsing, so we have to charge the "second half" cost + // of instantiaiton separately here. + if _host.get_ledger_protocol_version()? >= ModuleCache::MIN_LEDGER_VERSION { + _host.charge_budget( + ContractCostType::VmCachedInstantiation, + Some(*wasm_bytes as u64), + )?; + } } #[cfg(feature = "next")] Self::V1(inputs) => { - _host.charge_budget( - ContractCostType::InstantiateWasmInstructions, - Some(inputs.n_instructions as u64), - )?; + _host.charge_budget(ContractCostType::InstantiateWasmInstructions, None)?; _host.charge_budget( ContractCostType::InstantiateWasmFunctions, Some(inputs.n_functions as u64), @@ -105,10 +111,7 @@ impl VersionedContractCodeCostInputs { ContractCostType::InstantiateWasmTableEntries, Some(inputs.n_table_entries as u64), )?; - _host.charge_budget( - ContractCostType::InstantiateWasmTypes, - Some(inputs.n_types as u64), - )?; + _host.charge_budget(ContractCostType::InstantiateWasmTypes, None)?; _host.charge_budget( ContractCostType::InstantiateWasmDataSegments, Some(inputs.n_data_segments as u64), @@ -126,8 +129,8 @@ impl VersionedContractCodeCostInputs { Some(inputs.n_exports as u64), )?; _host.charge_budget( - ContractCostType::InstantiateWasmMemoryPages, - Some(inputs.n_memory_pages as u64), + ContractCostType::InstantiateWasmDataSegmentBytes, + Some(inputs.n_data_segment_bytes as u64), )?; } } @@ -374,12 +377,12 @@ impl ParsedModule { n_elem_segments: 0, n_imports: 0, n_exports: 0, - n_memory_pages: 0, + n_data_segment_bytes: 0, }; let parser = Parser::new(0); let mut elements: u32 = 0; - let mut data: u32 = 0; + let mut available_memory: u32 = 0; for section in parser.parse_all(wasm) { let section = host.map_err(section)?; match section { @@ -443,8 +446,10 @@ impl ParsedModule { &[], )); } - costs.n_memory_pages = - costs.n_memory_pages.saturating_add(mem.initial as u32); + available_memory = available_memory.saturating_add( + (mem.initial as u32) + .saturating_mul(crate::vm::WASM_STD_MEM_PAGE_SIZE_IN_BYTES), + ); } } @@ -475,8 +480,7 @@ impl ParsedModule { } ExportSection(s) => costs.n_exports = costs.n_exports.saturating_add(s.count()), ElementSection(s) => { - costs.n_elem_segments = costs.n_elem_segments.saturating_add(1); - elements = elements.saturating_add(s.count()); + costs.n_elem_segments = costs.n_elem_segments.saturating_add(s.count()); for elem in s { let elem = host.map_err(elem)?; match elem.kind { @@ -486,8 +490,11 @@ impl ParsedModule { } } match elem.items { - ElementItems::Functions(_) => (), + ElementItems::Functions(fs) => { + elements = elements.saturating_add(fs.count()); + } ElementItems::Expressions(_, exprs) => { + elements = elements.saturating_add(exprs.count()); for expr in exprs { let expr = host.map_err(expr)?; Self::check_const_expr_simple(&host, &expr)?; @@ -497,7 +504,7 @@ impl ParsedModule { } } DataSection(s) => { - costs.n_data_segments = costs.n_data_segments.saturating_add(1); + costs.n_data_segments = costs.n_data_segments.saturating_add(s.count()); for d in s { let d = host.map_err(d)?; if d.data.len() > u32::MAX as usize { @@ -508,7 +515,9 @@ impl ParsedModule { &[], )); } - data = data.saturating_add(d.data.len() as u32); + costs.n_data_segment_bytes = costs + .n_data_segment_bytes + .saturating_add(d.data.len() as u32); match d.kind { wasmparser::DataKind::Active { offset_expr, .. } => { Self::check_const_expr_simple(&host, &offset_expr)? @@ -525,15 +534,12 @@ impl ParsedModule { } } } - let available_memory = costs - .n_memory_pages - .saturating_mul(WASM_STD_MEM_PAGE_SIZE_IN_BYTES); - if data > available_memory { + if costs.n_data_segment_bytes > available_memory { return Err(err!( host, (ScErrorType::WasmVm, ScErrorCode::InvalidInput), - "data segments exceed memory size", - data, + "data segment(s) content exceeds memory size", + costs.n_data_segment_bytes, available_memory )); } @@ -541,7 +547,7 @@ impl ParsedModule { return Err(err!( host, (ScErrorType::WasmVm, ScErrorCode::InvalidInput), - "too many elements in Wasm elem section(s)", + "elem segments(s) content exceeds table size", elements, costs.n_table_entries ));