-
Notifications
You must be signed in to change notification settings - Fork 2.6k
PVF: NaN canonicalization & deteriministic stack #9069
Changes from 6 commits
1de4cbd
9591095
eb908de
eb9e71a
c798ee6
26f2ef5
52c8a2a
d1aa568
2539bf6
45dfd2e
8261e46
8576637
e5af337
9992f2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -232,9 +232,39 @@ directory = \"{cache_dir}\" | |
| Ok(()) | ||
| } | ||
|
|
||
| fn common_config() -> wasmtime::Config { | ||
| fn common_config(semantics: &Semantics) -> wasmtime::Config { | ||
| let mut config = wasmtime::Config::new(); | ||
| config.cranelift_opt_level(wasmtime::OptLevel::SpeedAndSize); | ||
| config.cranelift_nan_canonicalization(semantics.canonicalize_nans); | ||
|
|
||
| if semantics.stack_depth_metering { | ||
| // With `stack_depth_metering` is enabled, the wasm code will instrumented with its own | ||
| // stack depth tracker. | ||
| // | ||
| // We do not ever want the wasmtime's stack overflow guard to be hit. The amount of data | ||
| // a stack frame of a compiled function consumes may depend on the wasmtime version and | ||
| // the architecture the host is running. This introduces some non-determinism and | ||
| // the `stack_depth_metering` option aims to fix exactly that. | ||
| // | ||
| // Hence 256 MiB for the stack. It seems unlikely that this will be reached even with specially | ||
| // crafted code. | ||
| config.max_wasm_stack(256 * 1024 * 1024).expect( | ||
| "this value is a literal and trivially is not 0; | ||
| we do not supply the async max stack; | ||
| according to the docs only Ok can be returned; | ||
| qed", | ||
| ); | ||
| } | ||
|
|
||
| // Be clear and specific about the extensions we support. If an update brings new features | ||
| // they should be introduced here as well. | ||
| config.wasm_reference_types(false); | ||
| config.wasm_simd(false); | ||
| config.wasm_bulk_memory(false); | ||
| config.wasm_multi_value(false); | ||
| config.wasm_multi_memory(false); | ||
| config.wasm_module_linking(false); | ||
|
athei marked this conversation as resolved.
|
||
|
|
||
| config | ||
| } | ||
|
|
||
|
|
@@ -271,7 +301,20 @@ pub struct Semantics { | |
| /// | ||
| /// [stack_height]: https://github.com/paritytech/wasm-utils/blob/d9432baf/src/stack_height/mod.rs#L1-L50 | ||
| pub stack_depth_metering: bool, | ||
| // Other things like nan canonicalization can be added here. | ||
|
|
||
| /// Controls whether wasmtime should compile floating point in a way that doesn't allow for | ||
| /// non-determinism. | ||
| /// | ||
| /// By default, the wasm spec allows some local non-determinism wrt. certain floating point | ||
| /// operations. Specifically, those operations that are not defined to operate on bits (e.g. fneg) | ||
| /// can produce NaN values. The exact bit pattern for those is not specified and may depend | ||
| /// on the particular machine that executes wasmtime generated JITed machine code. That is | ||
| /// a source of non-deterministic values. | ||
| /// | ||
| /// The classical runtime environment for Substrate allowed it and punted this on the runtime | ||
| /// developers. For PVFs, we want to ensure that execution is deterministic though. Therefore, | ||
| /// for PVF execution this flag is meant to be turned on. | ||
| pub canonicalize_nans: bool, | ||
| } | ||
|
|
||
| pub struct Config { | ||
|
|
@@ -355,7 +398,7 @@ unsafe fn do_create_runtime( | |
| host_functions: Vec<&'static dyn Function>, | ||
| ) -> std::result::Result<WasmtimeRuntime, WasmError> { | ||
| // Create the engine, store and finally the module from the given code. | ||
| let mut wasmtime_config = common_config(); | ||
| let mut wasmtime_config = common_config(&config.semantics); | ||
| if let Some(ref cache_path) = config.cache_path { | ||
| if let Err(reason) = setup_wasmtime_caching(cache_path, &mut wasmtime_config) { | ||
| log::warn!( | ||
|
|
@@ -369,8 +412,8 @@ unsafe fn do_create_runtime( | |
| .map_err(|e| WasmError::Other(format!("cannot create the engine for runtime: {}", e)))?; | ||
|
|
||
| let (module, snapshot_data) = match code_supply_mode { | ||
| CodeSupplyMode::Verbatim { mut blob } => { | ||
| instrument(&mut blob, &config.semantics); | ||
| CodeSupplyMode::Verbatim { blob } => { | ||
| let blob = instrument(blob, &config.semantics)?; | ||
|
|
||
| if config.semantics.fast_instance_reuse { | ||
| let data_segments_snapshot = DataSegmentsSnapshot::take(&blob).map_err(|e| { | ||
|
|
@@ -412,25 +455,32 @@ unsafe fn do_create_runtime( | |
| }) | ||
| } | ||
|
|
||
| fn instrument(blob: &mut RuntimeBlob, semantics: &Semantics) { | ||
| fn instrument( | ||
| mut blob: RuntimeBlob, | ||
| semantics: &Semantics, | ||
| ) -> std::result::Result<RuntimeBlob, WasmError> { | ||
| if semantics.stack_depth_metering { | ||
| const STACK_DEPTH_LIMIT: u32 = 65536; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the reason for hardcoding it is that we don't accidentally have a consensus issue?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wouldn't say that this is the reason. I am not sure if this solution provides a lot, in terms of accidentally having different numbers. I am not sure if it will stay the same forever either. It's just its simpler this way. I would imagine we would still be able to change this number by first making a change that sources this number from, e.g. persisted validation data / host configuration and then when sufficient number of nodes are updated we can change this through the configuration. If the number is different on different machines and somebody probes how deep recursion can be, this can lead to a split and consequently to disputes.
athei marked this conversation as resolved.
Outdated
|
||
| blob = blob.inject_stack_depth_metering(STACK_DEPTH_LIMIT)?; | ||
| } | ||
|
|
||
| // If enabled, this should happen after all other passes that may introduce global variables. | ||
| if semantics.fast_instance_reuse { | ||
| blob.expose_mutable_globals(); | ||
| } | ||
|
|
||
| if semantics.stack_depth_metering { | ||
| // TODO: implement deterministic stack metering https://github.com/paritytech/substrate/issues/8393 | ||
| } | ||
| Ok(blob) | ||
| } | ||
|
|
||
| /// Takes a [`RuntimeBlob`] and precompiles it returning the serialized result of compilation. It | ||
| /// can then be used for calling [`create_runtime`] avoiding long compilation times. | ||
| pub fn prepare_runtime_artifact( | ||
| mut blob: RuntimeBlob, | ||
| blob: RuntimeBlob, | ||
| semantics: &Semantics, | ||
| ) -> std::result::Result<Vec<u8>, WasmError> { | ||
| instrument(&mut blob, semantics); | ||
| let blob = instrument(blob, semantics)?; | ||
|
|
||
| let engine = Engine::new(&common_config()) | ||
| let engine = Engine::new(&common_config(semantics)) | ||
| .map_err(|e| WasmError::Other(format!("cannot create the engine: {}", e)))?; | ||
|
|
||
| engine | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.