From a1913ebcf178f9d721142e3882b549e4f645da7d Mon Sep 17 00:00:00 2001 From: Alex Uta Date: Thu, 9 Jan 2025 17:24:40 +0000 Subject: [PATCH 1/8] Prepare infrastructure to increase Wasm64 heap memory size. --- rs/config/src/embedders.rs | 9 ++++++++- rs/embedders/benches/embedders_bench/src/lib.rs | 2 +- rs/embedders/src/wasm_utils.rs | 13 +++++++++++-- rs/embedders/src/wasm_utils/validation.rs | 14 +++++++++++++- rs/embedders/tests/validation.rs | 2 +- rs/embedders/tests/wasmtime_embedder.rs | 2 +- rs/execution_environment/benches/lib/src/common.rs | 2 +- rs/test_utilities/execution_environment/src/lib.rs | 7 +++++++ rs/types/types/src/lib.rs | 7 ++++++- 9 files changed, 49 insertions(+), 9 deletions(-) diff --git a/rs/config/src/embedders.rs b/rs/config/src/embedders.rs index 9c6ef4db9d7..837f62f5cbb 100644 --- a/rs/config/src/embedders.rs +++ b/rs/config/src/embedders.rs @@ -3,7 +3,10 @@ use std::time::Duration; use ic_base_types::NumBytes; use ic_registry_subnet_type::SubnetType; use ic_sys::PAGE_SIZE; -use ic_types::{NumInstructions, NumOsPages, MAX_STABLE_MEMORY_IN_BYTES, MAX_WASM_MEMORY_IN_BYTES}; +use ic_types::{ + NumInstructions, NumOsPages, MAX_STABLE_MEMORY_IN_BYTES, MAX_WASM64_MEMORY_IN_BYTES, + MAX_WASM_MEMORY_IN_BYTES, +}; use serde::{Deserialize, Serialize}; use crate::flag_status::FlagStatus; @@ -245,6 +248,9 @@ pub struct Config { /// The maximum size of the wasm heap memory. pub max_wasm_memory_size: NumBytes, + /// The maximum size of the wasm heap memory for Wasm64 canisters. + pub max_wasm64_memory_size: NumBytes, + /// The maximum size of the stable memory. pub max_stable_memory_size: NumBytes, } @@ -284,6 +290,7 @@ impl Config { dirty_page_copy_overhead: DIRTY_PAGE_COPY_OVERHEAD, wasm_max_size: WASM_MAX_SIZE, max_wasm_memory_size: NumBytes::new(MAX_WASM_MEMORY_IN_BYTES), + max_wasm64_memory_size: NumBytes::new(MAX_WASM64_MEMORY_IN_BYTES), max_stable_memory_size: NumBytes::new(MAX_STABLE_MEMORY_IN_BYTES), wasm64_dirty_page_overhead_multiplier: WASM64_DIRTY_PAGE_OVERHEAD_MULTIPLIER, } diff --git a/rs/embedders/benches/embedders_bench/src/lib.rs b/rs/embedders/benches/embedders_bench/src/lib.rs index 977e5b79b46..a162ff9e9ad 100644 --- a/rs/embedders/benches/embedders_bench/src/lib.rs +++ b/rs/embedders/benches/embedders_bench/src/lib.rs @@ -57,7 +57,7 @@ fn initialize_execution_test( if is_wasm64 { test = test.with_wasm64(); // Set memory size to 8 GiB for Wasm64. - test = test.with_max_wasm_memory_size(NumBytes::from(8 * 1024 * 1024 * 1024)); + test = test.with_max_wasm64_memory_size(NumBytes::from(8 * 1024 * 1024 * 1024)); } let mut test = test.build(); diff --git a/rs/embedders/src/wasm_utils.rs b/rs/embedders/src/wasm_utils.rs index 31e8b4ce0c5..caefa24cead 100644 --- a/rs/embedders/src/wasm_utils.rs +++ b/rs/embedders/src/wasm_utils.rs @@ -14,7 +14,9 @@ use ic_types::{methods::WasmMethod, NumInstructions}; use ic_wasm_types::{BinaryEncodedWasm, WasmInstrumentationError}; use serde::{Deserialize, Serialize}; -use self::{instrumentation::instrument, validation::validate_wasm_binary}; +use self::{ + instrumentation::instrument, validation::has_wasm64_memory, validation::validate_wasm_binary, +}; use crate::wasmtime_embedder::StoreData; use crate::{serialized_module::SerializedModule, CompilationResult, WasmtimeEmbedder}; use wasmtime::InstancePre; @@ -201,6 +203,13 @@ fn validate_and_instrument( config: &EmbeddersConfig, ) -> HypervisorResult<(WasmValidationDetails, InstrumentationOutput)> { let (wasm_validation_details, module) = validate_wasm_binary(wasm, config)?; + // Instrumentation bytemap depends on the Wasm memory size, so for larger heaps we need + // to pass in the corresponding Wasm64 heap memory size. + let max_wasm_memory_size = if has_wasm64_memory(&module) { + config.max_wasm64_memory_size + } else { + config.max_wasm_memory_size + }; let instrumentation_output = instrument( module, config.cost_to_compile_wasm_instruction, @@ -209,7 +218,7 @@ fn validate_and_instrument( config.metering_type, config.subnet_type, config.dirty_page_overhead, - config.max_wasm_memory_size, + max_wasm_memory_size, config.max_stable_memory_size, )?; Ok((wasm_validation_details, instrumentation_output)) diff --git a/rs/embedders/src/wasm_utils/validation.rs b/rs/embedders/src/wasm_utils/validation.rs index abba3ebac2e..394e5f70ae9 100644 --- a/rs/embedders/src/wasm_utils/validation.rs +++ b/rs/embedders/src/wasm_utils/validation.rs @@ -1068,6 +1068,11 @@ fn validate_function_section( Ok(()) } +// Checks if the module has a Wasm64 memory. +pub fn has_wasm64_memory(module: &Module) -> bool { + module.memories.first().map_or(false, |m| m.memory64) +} + // Checks that the initial size of the wasm (heap) memory is not larger than // the allowed maximum size. This is only needed for Wasm64, because in Wasm32 this // is checked by Wasmtime. @@ -1555,7 +1560,14 @@ pub(super) fn validate_wasm_binary<'a>( validate_data_section(&module)?; validate_global_section(&module, config.max_globals)?; validate_function_section(&module, config.max_functions)?; - validate_initial_wasm_memory_size(&module, config.max_wasm_memory_size)?; + // The maximum Wasm memory size is different for Wasm32 and Wasm64 and + // each needs to be validated accordingly. + let max_wasm_memory_size = if has_wasm64_memory(&module) { + config.max_wasm64_memory_size + } else { + config.max_wasm_memory_size + }; + validate_initial_wasm_memory_size(&module, max_wasm_memory_size)?; let (largest_function_instruction_count, max_complexity) = validate_code_section(&module)?; let wasm_metadata = validate_custom_section(&module, config)?; Ok(( diff --git a/rs/embedders/tests/validation.rs b/rs/embedders/tests/validation.rs index f6a840dc21b..9ac68287ef3 100644 --- a/rs/embedders/tests/validation.rs +++ b/rs/embedders/tests/validation.rs @@ -1178,7 +1178,7 @@ fn test_wasm64_initial_wasm_memory_size_validation() { ..Default::default() }; let allowed_wasm_memory_size_in_pages = - embedders_config.max_wasm_memory_size.get() / WASM_PAGE_SIZE as u64; + embedders_config.max_wasm64_memory_size.get() / WASM_PAGE_SIZE as u64; let declared_wasm_memory_size_in_pages = allowed_wasm_memory_size_in_pages + 10; let wasm = wat2wasm(&format!( r#"(module diff --git a/rs/embedders/tests/wasmtime_embedder.rs b/rs/embedders/tests/wasmtime_embedder.rs index b5076020bb4..728e09210d2 100644 --- a/rs/embedders/tests/wasmtime_embedder.rs +++ b/rs/embedders/tests/wasmtime_embedder.rs @@ -3032,7 +3032,7 @@ fn large_wasm64_stable_read_write_test() { config.feature_flags.wasm64 = FlagStatus::Enabled; config.feature_flags.wasm_native_stable_memory = FlagStatus::Enabled; // Declare a large heap. - config.max_wasm_memory_size = NumBytes::from(10 * gb); + config.max_wasm64_memory_size = NumBytes::from(10 * gb); let mut instance = WasmtimeInstanceBuilder::new() .with_config(config) diff --git a/rs/execution_environment/benches/lib/src/common.rs b/rs/execution_environment/benches/lib/src/common.rs index 500cee842e2..d45826c47f5 100644 --- a/rs/execution_environment/benches/lib/src/common.rs +++ b/rs/execution_environment/benches/lib/src/common.rs @@ -282,7 +282,7 @@ where }; // Set up larger heap, of 8GB for the Wasm64 feature. - embedders_config.max_wasm_memory_size = NumBytes::from(8 * 1024 * 1024 * 1024); + embedders_config.max_wasm64_memory_size = NumBytes::from(8 * 1024 * 1024 * 1024); let config = Config { embedders_config, diff --git a/rs/test_utilities/execution_environment/src/lib.rs b/rs/test_utilities/execution_environment/src/lib.rs index dda93c74257..3c5dc85e87b 100644 --- a/rs/test_utilities/execution_environment/src/lib.rs +++ b/rs/test_utilities/execution_environment/src/lib.rs @@ -2113,6 +2113,13 @@ impl ExecutionTestBuilder { self } + pub fn with_max_wasm64_memory_size(mut self, wasm_memory_size: NumBytes) -> Self { + self.execution_config + .embedders_config + .max_wasm64_memory_size = wasm_memory_size; + self + } + pub fn with_metering_type(mut self, metering_type: MeteringType) -> Self { self.execution_config.embedders_config.metering_type = metering_type; self diff --git a/rs/types/types/src/lib.rs b/rs/types/types/src/lib.rs index 2f5f8951ac0..7af735fd63e 100644 --- a/rs/types/types/src/lib.rs +++ b/rs/types/types/src/lib.rs @@ -489,9 +489,14 @@ pub const MAX_STABLE_MEMORY_IN_BYTES: u64 = 500 * GIB; /// it is public and `u64` (`NumBytes` cannot be used in const expressions). pub const MAX_WASM_MEMORY_IN_BYTES: u64 = 4 * GIB; +/// The upper limit on the Wasm64 canister memory size. +/// This constant is used by other crates to define other constants, that's why +/// it is public and `u64` (`NumBytes` cannot be used in const expressions). +pub const MAX_WASM64_MEMORY_IN_BYTES: u64 = 4 * GIB; + const MIN_MEMORY_ALLOCATION: NumBytes = NumBytes::new(0); pub const MAX_MEMORY_ALLOCATION: NumBytes = - NumBytes::new(MAX_STABLE_MEMORY_IN_BYTES + MAX_WASM_MEMORY_IN_BYTES); + NumBytes::new(MAX_STABLE_MEMORY_IN_BYTES + MAX_WASM64_MEMORY_IN_BYTES); impl InvalidMemoryAllocationError { pub fn new(given: candid::Nat) -> Self { From f2350995cebf115b0b8463a80f2c64d5074ace31 Mon Sep 17 00:00:00 2001 From: Alex Uta Date: Tue, 14 Jan 2025 13:14:58 +0000 Subject: [PATCH 2/8] Fix drun. --- rs/drun/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/drun/src/main.rs b/rs/drun/src/main.rs index 7d2af5662f3..51b666c9ada 100644 --- a/rs/drun/src/main.rs +++ b/rs/drun/src/main.rs @@ -67,9 +67,9 @@ async fn drun_main() -> Result<(), String> { .embedders_config .feature_flags .best_effort_responses = FlagStatus::Enabled; - hypervisor_config.embedders_config.max_wasm_memory_size = MAIN_MEMORY_CAPACITY; + hypervisor_config.embedders_config.max_wasm64_memory_size = MAIN_MEMORY_CAPACITY; hypervisor_config.max_canister_memory_size = - hypervisor_config.embedders_config.max_wasm_memory_size + hypervisor_config.embedders_config.max_wasm64_memory_size + hypervisor_config.embedders_config.max_stable_memory_size; let cfg = Config::load_with_default(&source, default_config).unwrap_or_else(|err| { From 681e7fb31f661dce53641dfd93e7f06919dc250c Mon Sep 17 00:00:00 2001 From: Alex Uta Date: Tue, 14 Jan 2025 16:21:44 +0000 Subject: [PATCH 3/8] Use Wasm64 heap size constant in exec env config. --- rs/config/src/execution_environment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/config/src/execution_environment.rs b/rs/config/src/execution_environment.rs index d6e47d83aa6..e96cb3a735f 100644 --- a/rs/config/src/execution_environment.rs +++ b/rs/config/src/execution_environment.rs @@ -2,7 +2,7 @@ use crate::embedders::Config as EmbeddersConfig; use crate::flag_status::FlagStatus; use ic_base_types::{CanisterId, NumSeconds}; use ic_types::{ - Cycles, NumBytes, NumInstructions, MAX_STABLE_MEMORY_IN_BYTES, MAX_WASM_MEMORY_IN_BYTES, + Cycles, NumBytes, NumInstructions, MAX_STABLE_MEMORY_IN_BYTES, MAX_WASM64_MEMORY_IN_BYTES, }; use serde::{Deserialize, Serialize}; use std::{str::FromStr, time::Duration}; @@ -343,7 +343,7 @@ impl Default for Config { SUBNET_WASM_CUSTOM_SECTIONS_MEMORY_CAPACITY, subnet_memory_reservation: SUBNET_MEMORY_RESERVATION, max_canister_memory_size: NumBytes::new( - MAX_STABLE_MEMORY_IN_BYTES + MAX_WASM_MEMORY_IN_BYTES, + MAX_STABLE_MEMORY_IN_BYTES + MAX_WASM64_MEMORY_IN_BYTES, ), subnet_callback_soft_limit: SUBNET_CALLBACK_SOFT_LIMIT, canister_guaranteed_callback_quota: CANISTER_GUARANTEED_CALLBACK_QUOTA, From 69deb42f935fc6dd4687395ec22e0118e826d6c9 Mon Sep 17 00:00:00 2001 From: Alex Uta Date: Fri, 17 Jan 2025 15:03:32 +0000 Subject: [PATCH 4/8] Exec env config exposes a max_canister_memory_size for each of Wasm32 and Wasm64; this info is propagated in the places where it is needed. --- rs/config/src/execution_environment.rs | 13 ++++++++-- rs/drun/src/main.rs | 2 +- .../src/canister_manager.rs | 24 +++++++++++++++---- .../src/canister_manager/tests.rs | 1 + .../src/execution_environment.rs | 21 ++++++++++++---- rs/execution_environment/src/query_handler.rs | 4 ++-- .../src/query_handler/query_context.rs | 22 +++++++++++++---- rs/execution_environment/src/scheduler.rs | 10 +++++++- .../tests/execution_test.rs | 2 +- .../src/routing/stream_handler/tests.rs | 4 +++- 10 files changed, 83 insertions(+), 20 deletions(-) diff --git a/rs/config/src/execution_environment.rs b/rs/config/src/execution_environment.rs index e96cb3a735f..84636d07e8f 100644 --- a/rs/config/src/execution_environment.rs +++ b/rs/config/src/execution_environment.rs @@ -3,6 +3,7 @@ use crate::flag_status::FlagStatus; use ic_base_types::{CanisterId, NumSeconds}; use ic_types::{ Cycles, NumBytes, NumInstructions, MAX_STABLE_MEMORY_IN_BYTES, MAX_WASM64_MEMORY_IN_BYTES, + MAX_WASM_MEMORY_IN_BYTES, }; use serde::{Deserialize, Serialize}; use std::{str::FromStr, time::Duration}; @@ -204,7 +205,12 @@ pub struct Config { pub subnet_memory_reservation: NumBytes, /// The maximum amount of memory that can be utilized by a single canister. - pub max_canister_memory_size: NumBytes, + /// running in Wasm32 mode. + pub max_canister_memory_size_wasm32: NumBytes, + + /// The maximum amount of memory that can be utilized by a single canister. + /// running in Wasm64 mode. + pub max_canister_memory_size_wasm64: NumBytes, /// The soft limit on the subnet-wide number of callbacks. Beyond this limit, /// canisters are only allowed to make downstream calls up to their individual @@ -342,7 +348,10 @@ impl Default for Config { subnet_wasm_custom_sections_memory_capacity: SUBNET_WASM_CUSTOM_SECTIONS_MEMORY_CAPACITY, subnet_memory_reservation: SUBNET_MEMORY_RESERVATION, - max_canister_memory_size: NumBytes::new( + max_canister_memory_size_wasm32: NumBytes::new( + MAX_STABLE_MEMORY_IN_BYTES + MAX_WASM_MEMORY_IN_BYTES, + ), + max_canister_memory_size_wasm64: NumBytes::new( MAX_STABLE_MEMORY_IN_BYTES + MAX_WASM64_MEMORY_IN_BYTES, ), subnet_callback_soft_limit: SUBNET_CALLBACK_SOFT_LIMIT, diff --git a/rs/drun/src/main.rs b/rs/drun/src/main.rs index 51b666c9ada..1c3b70b37f1 100644 --- a/rs/drun/src/main.rs +++ b/rs/drun/src/main.rs @@ -68,7 +68,7 @@ async fn drun_main() -> Result<(), String> { .feature_flags .best_effort_responses = FlagStatus::Enabled; hypervisor_config.embedders_config.max_wasm64_memory_size = MAIN_MEMORY_CAPACITY; - hypervisor_config.max_canister_memory_size = + hypervisor_config.max_canister_memory_size_wasm64 = hypervisor_config.embedders_config.max_wasm64_memory_size + hypervisor_config.embedders_config.max_stable_memory_size; diff --git a/rs/execution_environment/src/canister_manager.rs b/rs/execution_environment/src/canister_manager.rs index 14877845010..e73c6d1e886 100644 --- a/rs/execution_environment/src/canister_manager.rs +++ b/rs/execution_environment/src/canister_manager.rs @@ -121,7 +121,8 @@ pub(crate) struct CanisterMgrConfig { pub(crate) own_subnet_id: SubnetId, pub(crate) own_subnet_type: SubnetType, pub(crate) max_controllers: usize, - pub(crate) max_canister_memory_size: NumBytes, + pub(crate) max_canister_memory_size_wasm32: NumBytes, + pub(crate) max_canister_memory_size_wasm64: NumBytes, pub(crate) rate_limiting_of_instructions: FlagStatus, rate_limiting_of_heap_delta: FlagStatus, heap_delta_rate_limit: NumBytes, @@ -141,7 +142,8 @@ impl CanisterMgrConfig { own_subnet_type: SubnetType, max_controllers: usize, compute_capacity: usize, - max_canister_memory_size: NumBytes, + max_canister_memory_size_wasm32: NumBytes, + max_canister_memory_size_wasm64: NumBytes, rate_limiting_of_instructions: FlagStatus, allocatable_capacity_in_percent: usize, rate_limiting_of_heap_delta: FlagStatus, @@ -160,7 +162,8 @@ impl CanisterMgrConfig { max_controllers, compute_capacity: (compute_capacity * allocatable_capacity_in_percent.min(100) / 100) as u64, - max_canister_memory_size, + max_canister_memory_size_wasm32, + max_canister_memory_size_wasm64, rate_limiting_of_instructions, rate_limiting_of_heap_delta, heap_delta_rate_limit, @@ -2197,7 +2200,10 @@ impl CanisterManager { let mut new_canister = CanisterState::new(system_state, new_execution_state, scheduler_state); let new_memory_usage = new_canister.memory_usage(); - let memory_allocation_given = canister.memory_limit(self.config.max_canister_memory_size); + + let memory_allocation_given = + canister.memory_limit(self.get_max_canister_memory_size(is_wasm64_execution)); + if new_memory_usage > memory_allocation_given { return ( Err(CanisterManagerError::NotEnoughMemoryAllocationGiven { @@ -2338,6 +2344,16 @@ impl CanisterManager { ); Ok(()) } + + /// Depending on the canister architecture (Wasm32 or Wasm64), returns the + /// maximum memory size that can be allocated by a canister. + pub(crate) fn get_max_canister_memory_size(&self, is_wasm64_execution: bool) -> NumBytes { + if is_wasm64_execution { + self.config.max_canister_memory_size_wasm64 + } else { + self.config.max_canister_memory_size_wasm32 + } + } } #[derive(Eq, PartialEq, Debug)] diff --git a/rs/execution_environment/src/canister_manager/tests.rs b/rs/execution_environment/src/canister_manager/tests.rs index f0dfc4057b9..b3588f494ca 100644 --- a/rs/execution_environment/src/canister_manager/tests.rs +++ b/rs/execution_environment/src/canister_manager/tests.rs @@ -306,6 +306,7 @@ fn canister_manager_config( // TODO(RUN-319): the capacity should be defined based on actual `scheduler_cores` 100, MAX_CANISTER_MEMORY_SIZE, + MAX_CANISTER_MEMORY_SIZE, rate_limiting_of_instructions, 100, FlagStatus::Enabled, diff --git a/rs/execution_environment/src/execution_environment.rs b/rs/execution_environment/src/execution_environment.rs index 0733c850446..7d6a87baa6d 100644 --- a/rs/execution_environment/src/execution_environment.rs +++ b/rs/execution_environment/src/execution_environment.rs @@ -402,7 +402,8 @@ impl ExecutionEnvironment { own_subnet_type, config.max_controllers, compute_capacity, - config.max_canister_memory_size, + config.max_canister_memory_size_wasm32, + config.max_canister_memory_size_wasm64, config.rate_limiting_of_instructions, config.allocatable_compute_capacity_in_percent, config.rate_limiting_of_heap_delta, @@ -1805,8 +1806,12 @@ impl ExecutionEnvironment { /// Returns the maximum amount of memory that can be utilized by a single /// canister. - pub fn max_canister_memory_size(&self) -> NumBytes { - self.config.max_canister_memory_size + pub fn max_canister_memory_size(&self, is_wasm64: bool) -> NumBytes { + if is_wasm64 { + self.config.max_canister_memory_size_wasm64 + } else { + self.config.max_canister_memory_size_wasm32 + } } /// Returns the subnet memory capacity. @@ -1823,9 +1828,17 @@ impl ExecutionEnvironment { execution_mode: ExecutionMode, subnet_memory_saturation: ResourceSaturation, ) -> ExecutionParameters { + let is_wasm64_execution = match &canister.execution_state { + // The canister is not already installed, so we do not know what kind of canister it is. + // Therefore we can assume it is Wasm64 because Wasm64 can have a larger memory limit. + None => true, + Some(execution_state) => execution_state.is_wasm64, + }; + let max_memory_size = self.max_canister_memory_size(is_wasm64_execution); + ExecutionParameters { instruction_limits, - canister_memory_limit: canister.memory_limit(self.config.max_canister_memory_size), + canister_memory_limit: canister.memory_limit(max_memory_size), wasm_memory_limit: canister.wasm_memory_limit(), memory_allocation: canister.memory_allocation(), canister_guaranteed_callback_quota: self.config.canister_guaranteed_callback_quota diff --git a/rs/execution_environment/src/query_handler.rs b/rs/execution_environment/src/query_handler.rs index 88213e3ed4f..31970026329 100644 --- a/rs/execution_environment/src/query_handler.rs +++ b/rs/execution_environment/src/query_handler.rs @@ -258,7 +258,6 @@ impl InternalHttpQueryHandler { // instruction limit for the whole composite query tree imposes a much lower // implicit bound anyway. let subnet_available_callbacks = self.config.subnet_callback_soft_limit as i64; - let max_canister_memory_size = self.config.max_canister_memory_size; let mut context = query_context::QueryContext::new( &self.log, @@ -273,7 +272,8 @@ impl InternalHttpQueryHandler { subnet_available_memory, subnet_available_callbacks, self.config.canister_guaranteed_callback_quota as u64, - max_canister_memory_size, + self.config.max_canister_memory_size_wasm32, + self.config.max_canister_memory_size_wasm64, self.max_instructions_per_query, self.config.max_query_call_graph_depth, self.config.max_query_call_graph_instructions, diff --git a/rs/execution_environment/src/query_handler/query_context.rs b/rs/execution_environment/src/query_handler/query_context.rs index 547d5a6777a..b82f9a76dd3 100644 --- a/rs/execution_environment/src/query_handler/query_context.rs +++ b/rs/execution_environment/src/query_handler/query_context.rs @@ -95,7 +95,8 @@ pub(super) struct QueryContext<'a> { network_topology: Arc, // Certificate for certified queries + canister ID of the root query of this context data_certificate: (Vec, CanisterId), - max_canister_memory_size: NumBytes, + max_canister_memory_size_wasm32: NumBytes, + max_canister_memory_size_wasm64: NumBytes, max_instructions_per_query: NumInstructions, max_query_call_graph_depth: usize, instruction_overhead_per_query_call: RoundInstructions, @@ -130,7 +131,8 @@ impl<'a> QueryContext<'a> { subnet_available_memory: SubnetAvailableMemory, subnet_available_callbacks: i64, canister_guaranteed_callback_quota: u64, - max_canister_memory_size: NumBytes, + max_canister_memory_size_wasm32: NumBytes, + max_canister_memory_size_wasm64: NumBytes, max_instructions_per_query: NumInstructions, max_query_call_graph_depth: usize, max_query_call_graph_instructions: NumInstructions, @@ -158,7 +160,8 @@ impl<'a> QueryContext<'a> { state, network_topology, data_certificate: (data_certificate, canister_id), - max_canister_memory_size, + max_canister_memory_size_wasm32, + max_canister_memory_size_wasm64, max_instructions_per_query, max_query_call_graph_depth, instruction_overhead_per_query_call: as_round_instructions( @@ -1083,9 +1086,20 @@ impl<'a> QueryContext<'a> { canister: &CanisterState, instruction_limits: InstructionLimits, ) -> ExecutionParameters { + let is_wasm64_execution = canister + .execution_state + .as_ref() + .map_or(false, |es| es.is_wasm64); + + let max_canister_memory_size = if is_wasm64_execution { + self.max_canister_memory_size_wasm64 + } else { + self.max_canister_memory_size_wasm32 + }; + ExecutionParameters { instruction_limits, - canister_memory_limit: canister.memory_limit(self.max_canister_memory_size), + canister_memory_limit: canister.memory_limit(max_canister_memory_size), wasm_memory_limit: canister.wasm_memory_limit(), memory_allocation: canister.memory_allocation(), canister_guaranteed_callback_quota: self.canister_guaranteed_callback_quota, diff --git a/rs/execution_environment/src/scheduler.rs b/rs/execution_environment/src/scheduler.rs index bd7a475ef66..9446795bf3c 100644 --- a/rs/execution_environment/src/scheduler.rs +++ b/rs/execution_environment/src/scheduler.rs @@ -1070,7 +1070,15 @@ impl SchedulerImpl { ) -> bool { for canister_id in canister_ids { let canister = state.canister_states.get(canister_id).unwrap(); - if let Err(err) = canister.check_invariants(self.exec_env.max_canister_memory_size()) { + + let canister_is_wasm64 = canister + .execution_state + .as_ref() + .map_or(false, |es| es.is_wasm64); + + if let Err(err) = canister + .check_invariants(self.exec_env.max_canister_memory_size(canister_is_wasm64)) + { let msg = format!( "{}: At Round {} @ time {}, canister {} has invalid state after execution. Invariant check failed with err: {}", CANISTER_INVARIANT_BROKEN, diff --git a/rs/execution_environment/tests/execution_test.rs b/rs/execution_environment/tests/execution_test.rs index 68031280b6f..4046040dd52 100644 --- a/rs/execution_environment/tests/execution_test.rs +++ b/rs/execution_environment/tests/execution_test.rs @@ -995,7 +995,7 @@ fn max_canister_memory_respected_even_when_no_memory_allocation_is_set() { let env = StateMachine::new_with_config(StateMachineConfig::new( subnet_config, HypervisorConfig { - max_canister_memory_size: NumBytes::from(10 * MIB), + max_canister_memory_size_wasm32: NumBytes::from(10 * MIB), ..Default::default() }, )); diff --git a/rs/messaging/src/routing/stream_handler/tests.rs b/rs/messaging/src/routing/stream_handler/tests.rs index 2d5eab55676..aded678223b 100644 --- a/rs/messaging/src/routing/stream_handler/tests.rs +++ b/rs/messaging/src/routing/stream_handler/tests.rs @@ -592,7 +592,9 @@ fn legacy_induct_loopback_stream_with_zero_subnet_wasm_custom_sections_limit() { fn system_subnet_induct_loopback_stream_ignores_canister_memory_limit() { // A stream handler with a canister memory limit that only allows up to 3 reservations. induct_loopback_stream_ignores_memory_limit_impl(HypervisorConfig { - max_canister_memory_size: NumBytes::new(MAX_RESPONSE_COUNT_BYTES as u64 * 7 / 2), + max_canister_memory_size_wasm32: NumBytes::new(MAX_RESPONSE_COUNT_BYTES as u64 * 7 / 2), + // For consistency reasons in case this test is run against Wasm64 canisters. + max_canister_memory_size_wasm64: NumBytes::new(MAX_RESPONSE_COUNT_BYTES as u64 * 7 / 2), ..Default::default() }); } From 1eff3d318e2f3cbc74ce2633a8c7a7c23e0874a7 Mon Sep 17 00:00:00 2001 From: Alex Uta Date: Fri, 17 Jan 2025 16:18:43 +0000 Subject: [PATCH 5/8] Add tests which checks if max_canister_memory_size behaves correctly when different values are set for Wasm32 vs Wasm64 --- .../tests/execution_test.rs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/rs/execution_environment/tests/execution_test.rs b/rs/execution_environment/tests/execution_test.rs index 4046040dd52..04d605d1fb6 100644 --- a/rs/execution_environment/tests/execution_test.rs +++ b/rs/execution_environment/tests/execution_test.rs @@ -1340,6 +1340,95 @@ fn canister_with_memory_allocation_cannot_grow_wasm_memory_above_allocation_wasm assert_eq!(err.code(), ErrorCode::CanisterOutOfMemory); } +#[test] +fn max_canister_memory_size_is_different_between_wasm32_vs_wasm64() { + fn create_wat(memory_increase_in_pages: i64, is_wasm64: bool) -> String { + // Wat that grows the memory by parameter and can be formatted to Wasm32 and Wasm64. + let mem_declaration = if is_wasm64 { + "(memory $memory i64 1 250)" + } else { + "(memory $memory 1 250)" + }; + let func_msg_decl = if is_wasm64 { + "(import \"ic0\" \"msg_reply_data_append\" (func $msg_reply_data_append (param i64 i64)))" + } else { + "(import \"ic0\" \"msg_reply_data_append\" (func $msg_reply_data_append (param i32 i32)))" + }; + let call_msg_reply_append = if is_wasm64 { + "(call $msg_reply_data_append (i64.const 0) (i64.const 1))" + } else { + "(call $msg_reply_data_append (i32.const 0) (i32.const 1))" + }; + let memory_grow_instruction = if is_wasm64 { + format!( + "(drop (memory.grow (i64.const {})))", + memory_increase_in_pages + ) + } else { + format!( + "(drop (memory.grow (i32.const {})))", + memory_increase_in_pages + ) + }; + format!( + r#" + (module + (import "ic0" "msg_reply" (func $msg_reply)) + {} + (func $update + {} + {} + (call $msg_reply) + ) + {} + (export "canister_update update" (func $update)) + )"#, + func_msg_decl, memory_grow_instruction, call_msg_reply_append, mem_declaration + ) + } + + let subnet_config = SubnetConfig::new(SubnetType::Application); + let env = StateMachine::new_with_config(StateMachineConfig::new( + subnet_config, + HypervisorConfig { + max_canister_memory_size_wasm32: NumBytes::from(10 * MIB), + max_canister_memory_size_wasm64: NumBytes::from(20 * MIB), + ..Default::default() + }, + )); + + // Create wat that grows a Wasm32 canister to the 15 MiB, which is over the Wasm32 limit. + // A Wasm page is 64 KiB. Therefore 15 MiB is 240 pages. + let num_pages = 240; + let wat_32 = create_wat(num_pages, false); + // Create wat that grows a Wasm64 canister to the 15 MiB, which is below the Wasm64 limit. + let wat_64 = create_wat(num_pages, true); + + let wasm32_canister = create_canister_with_cycles( + &env, + wat::parse_str(&wat_32).unwrap(), + Some(CanisterSettingsArgsBuilder::new().build()), + INITIAL_CYCLES_BALANCE, + ); + let wasm64_canister = create_canister_with_cycles( + &env, + wat::parse_str(&wat_64).unwrap(), + Some(CanisterSettingsArgsBuilder::new().build()), + INITIAL_CYCLES_BALANCE, + ); + + // When running, the wasm32 canister should trap because it tries to grow the memory beyond its limit of 15 MiB. + // and the Wasm64 canister should succeed because it has a higher limit. + let res32 = env.execute_ingress(wasm32_canister, "update", vec![]); + let res64 = env.execute_ingress(wasm64_canister, "update", vec![]); + + println!("{:?}", res32); + println!("{:?}", res64); + + assert_eq!(res32.unwrap_err().code(), ErrorCode::CanisterOutOfMemory); + assert_replied(res64); +} + #[test] fn canister_with_memory_allocation_cannot_grow_stable_memory_above_allocation() { let subnet_config = SubnetConfig::new(SubnetType::Application); From e49eca26f947a2721b4ea34f74ba283e266260cf Mon Sep 17 00:00:00 2001 From: Alex Uta Date: Fri, 17 Jan 2025 16:22:37 +0000 Subject: [PATCH 6/8] Remove leftover print code. --- rs/execution_environment/tests/execution_test.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/rs/execution_environment/tests/execution_test.rs b/rs/execution_environment/tests/execution_test.rs index 04d605d1fb6..34996f454b5 100644 --- a/rs/execution_environment/tests/execution_test.rs +++ b/rs/execution_environment/tests/execution_test.rs @@ -1422,9 +1422,6 @@ fn max_canister_memory_size_is_different_between_wasm32_vs_wasm64() { let res32 = env.execute_ingress(wasm32_canister, "update", vec![]); let res64 = env.execute_ingress(wasm64_canister, "update", vec![]); - println!("{:?}", res32); - println!("{:?}", res64); - assert_eq!(res32.unwrap_err().code(), ErrorCode::CanisterOutOfMemory); assert_replied(res64); } From 07aa1ddc9dea737bee76b2f6a22b8d87b3068179 Mon Sep 17 00:00:00 2001 From: Alex Uta Date: Tue, 21 Jan 2025 09:07:57 +0000 Subject: [PATCH 7/8] Fix clippy. --- rs/embedders/src/wasm_utils/validation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs/embedders/src/wasm_utils/validation.rs b/rs/embedders/src/wasm_utils/validation.rs index 876568c19a9..2193a407235 100644 --- a/rs/embedders/src/wasm_utils/validation.rs +++ b/rs/embedders/src/wasm_utils/validation.rs @@ -1070,7 +1070,7 @@ fn validate_function_section( // Checks if the module has a Wasm64 memory. pub fn has_wasm64_memory(module: &Module) -> bool { - module.memories.first().map_or(false, |m| m.memory64) + module.memories.first().is_some_and(|m| m.memory64) } // Checks that the initial size of the wasm (heap) memory is not larger than From d18ba451893d798354823c0fef8c148ecbc25bee Mon Sep 17 00:00:00 2001 From: Alex Uta Date: Tue, 21 Jan 2025 09:26:42 +0000 Subject: [PATCH 8/8] Fix clippy. --- rs/execution_environment/src/query_handler/query_context.rs | 2 +- rs/execution_environment/src/scheduler.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rs/execution_environment/src/query_handler/query_context.rs b/rs/execution_environment/src/query_handler/query_context.rs index b82f9a76dd3..70b909c7b71 100644 --- a/rs/execution_environment/src/query_handler/query_context.rs +++ b/rs/execution_environment/src/query_handler/query_context.rs @@ -1089,7 +1089,7 @@ impl<'a> QueryContext<'a> { let is_wasm64_execution = canister .execution_state .as_ref() - .map_or(false, |es| es.is_wasm64); + .is_some_and(|es| es.is_wasm64); let max_canister_memory_size = if is_wasm64_execution { self.max_canister_memory_size_wasm64 diff --git a/rs/execution_environment/src/scheduler.rs b/rs/execution_environment/src/scheduler.rs index 9446795bf3c..39116a2fa8b 100644 --- a/rs/execution_environment/src/scheduler.rs +++ b/rs/execution_environment/src/scheduler.rs @@ -1074,7 +1074,7 @@ impl SchedulerImpl { let canister_is_wasm64 = canister .execution_state .as_ref() - .map_or(false, |es| es.is_wasm64); + .is_some_and(|es| es.is_wasm64); if let Err(err) = canister .check_invariants(self.exec_env.max_canister_memory_size(canister_is_wasm64))