From 706d6504e21ad377e29d2dadb23a0691d15f79b3 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 14:44:51 +0100 Subject: [PATCH 01/14] feat: Progress reporting and abort for module compilation Add a new Module::new_with_progress function, which will call a progress callback with progress information. The callback may also return an error , which will abort compilation. --- lib/api/src/backend/js/entities/module.rs | 14 ++- lib/api/src/backend/jsc/entities/module.rs | 12 +- lib/api/src/backend/sys/entities/module.rs | 56 ++++++++- lib/api/src/backend/v8/entities/module.rs | 14 ++- lib/api/src/backend/wamr/entities/module.rs | 14 ++- lib/api/src/backend/wasmi/entities/module.rs | 14 ++- lib/api/src/entities/module/inner.rs | 80 ++++++++++++- lib/api/src/entities/module/mod.rs | 13 ++- lib/compiler-cranelift/src/compiler.rs | 62 +++++++++- lib/compiler-llvm/src/compiler.rs | 76 ++++++++++-- lib/compiler-singlepass/src/compiler.rs | 5 +- .../src/artifact_builders/artifact_builder.rs | 4 +- lib/compiler/src/compiler.rs | 3 +- lib/compiler/src/engine/artifact.rs | 11 +- lib/compiler/src/engine/inner.rs | 19 ++- lib/types/src/error.rs | 11 +- lib/types/src/lib.rs | 2 + lib/types/src/progress.rs | 110 ++++++++++++++++++ 18 files changed, 477 insertions(+), 43 deletions(-) create mode 100644 lib/types/src/progress.rs diff --git a/lib/api/src/backend/js/entities/module.rs b/lib/api/src/backend/js/entities/module.rs index 38d83bce464..947f92bcdbd 100644 --- a/lib/api/src/backend/js/entities/module.rs +++ b/lib/api/src/backend/js/entities/module.rs @@ -5,9 +5,9 @@ use js_sys::{Reflect, Uint8Array, WebAssembly}; use tracing::{debug, warn}; use wasm_bindgen::{JsValue, prelude::*}; use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ExternType, FunctionType, - GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, Mutability, Pages, - SerializeError, TableType, Type, + CompilationProgressCallback, CompileError, DeserializeError, ExportType, ExportsIterator, + ExternType, FunctionType, GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, + Mutability, Pages, SerializeError, TableType, Type, }; use crate::{ @@ -67,6 +67,14 @@ impl Module { unsafe { Self::from_binary_unchecked(_engine, binary) } } + pub(crate) fn from_binary_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + _callback: CompilationProgressCallback, + ) -> Result { + Self::from_binary(engine, binary) + } + pub(crate) unsafe fn from_binary_unchecked( _engine: &impl AsEngineRef, binary: &[u8], diff --git a/lib/api/src/backend/jsc/entities/module.rs b/lib/api/src/backend/jsc/entities/module.rs index 4d7e2f11363..5a5e871f2ea 100644 --- a/lib/api/src/backend/jsc/entities/module.rs +++ b/lib/api/src/backend/jsc/entities/module.rs @@ -4,8 +4,8 @@ use bytes::Bytes; use rusty_jsc::{JSObject, JSValue}; use tracing::warn; use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ImportType, ImportsIterator, - ModuleInfo, SerializeError, + CompilationProgressCallback, CompileError, DeserializeError, ExportType, ExportsIterator, + ImportType, ImportsIterator, ModuleInfo, SerializeError, }; use crate::{ @@ -43,6 +43,14 @@ impl Module { unsafe { Self::from_binary_unchecked(_engine, binary) } } + pub(crate) fn from_binary_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + _callback: CompilationProgressCallback, + ) -> Result { + Self::from_binary(engine, binary) + } + pub(crate) unsafe fn from_binary_unchecked( engine: &impl AsEngineRef, binary: &[u8], diff --git a/lib/api/src/backend/sys/entities/module.rs b/lib/api/src/backend/sys/entities/module.rs index 167092c6ece..46154ef5bfb 100644 --- a/lib/api/src/backend/sys/entities/module.rs +++ b/lib/api/src/backend/sys/entities/module.rs @@ -5,8 +5,8 @@ use std::sync::Arc; use bytes::Bytes; use wasmer_compiler::{Artifact, ArtifactCreate, Engine}; use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ImportType, ImportsIterator, - ModuleInfo, SerializeError, + CompilationProgressCallback, CompileError, DeserializeError, ExportType, ExportsIterator, + ImportType, ImportsIterator, ModuleInfo, SerializeError, }; use crate::{ @@ -45,12 +45,46 @@ impl Module { unsafe { Self::from_binary_unchecked(engine, binary) } } + pub(crate) fn from_binary_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + callback: CompilationProgressCallback, + ) -> Result { + Self::validate(engine, binary)?; + unsafe { Self::from_binary_unchecked_with_progress(engine, binary, Some(callback)) } + } + pub(crate) unsafe fn from_binary_unchecked( engine: &impl AsEngineRef, binary: &[u8], ) -> Result { - let module = Self::compile(engine, binary)?; - Ok(module) + #[cfg(feature = "compiler")] + { + Self::compile(engine, binary) + } + + #[cfg(not(feature = "compiler"))] + { + Self::compile(engine, binary) + } + } + + pub(crate) unsafe fn from_binary_unchecked_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + callback: Option, + ) -> Result { + #[cfg(feature = "compiler")] + { + let module = Self::compile_with_progress(engine, binary, callback)?; + Ok(module) + } + + #[cfg(not(feature = "compiler"))] + { + let _ = callback; + Self::compile(engine, binary) + } } #[cfg(feature = "compiler")] @@ -72,6 +106,20 @@ impl Module { Ok(Self::from_artifact(artifact)) } + #[cfg(feature = "compiler")] + fn compile_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + callback: Option, + ) -> Result { + let artifact = engine + .as_engine_ref() + .engine() + .as_sys() + .compile_with_progress(binary, callback)?; + Ok(Self::from_artifact(artifact)) + } + #[cfg(not(feature = "compiler"))] fn compile(_engine: &impl AsEngineRef, _binary: &[u8]) -> Result { Err(CompileError::UnsupportedTarget( diff --git a/lib/api/src/backend/v8/entities/module.rs b/lib/api/src/backend/v8/entities/module.rs index 87db4d5332f..16520dc96be 100644 --- a/lib/api/src/backend/v8/entities/module.rs +++ b/lib/api/src/backend/v8/entities/module.rs @@ -8,9 +8,9 @@ use crate::{ use bytes::Bytes; use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ExternType, FunctionType, - GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, Mutability, Pages, - SerializeError, TableType, Type, + CompilationProgressCallback, CompileError, DeserializeError, ExportType, ExportsIterator, + ExternType, FunctionType, GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, + Mutability, Pages, SerializeError, TableType, Type, }; #[derive(Debug)] @@ -152,6 +152,14 @@ impl Module { unsafe { Self::from_binary_unchecked(engine, binary) } } + pub(crate) fn from_binary_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + _callback: CompilationProgressCallback, + ) -> Result { + Self::from_binary(engine, binary) + } + #[allow(clippy::arc_with_non_send_sync)] #[tracing::instrument(skip(engine, binary))] pub(crate) unsafe fn from_binary_unchecked( diff --git a/lib/api/src/backend/wamr/entities/module.rs b/lib/api/src/backend/wamr/entities/module.rs index 830c7d87095..7767b73ff0f 100644 --- a/lib/api/src/backend/wamr/entities/module.rs +++ b/lib/api/src/backend/wamr/entities/module.rs @@ -5,9 +5,9 @@ use crate::{AsEngineRef, BackendModule, IntoBytes, backend::wamr::bindings::*}; use bytes::Bytes; use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ExternType, FunctionType, - GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, Mutability, Pages, - SerializeError, TableType, Type, + CompilationProgressCallback, CompileError, DeserializeError, ExportType, ExportsIterator, + ExternType, FunctionType, GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, + Mutability, Pages, SerializeError, TableType, Type, }; pub(crate) struct ModuleHandle { pub(crate) inner: *mut wasm_module_t, @@ -75,6 +75,14 @@ impl Module { unsafe { Self::from_binary_unchecked(_engine, binary) } } + pub(crate) fn from_binary_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + _callback: CompilationProgressCallback, + ) -> Result { + Self::from_binary(engine, binary) + } + pub(crate) unsafe fn from_binary_unchecked( engine: &impl AsEngineRef, binary: &[u8], diff --git a/lib/api/src/backend/wasmi/entities/module.rs b/lib/api/src/backend/wasmi/entities/module.rs index a0b5d98241b..35feb5715d9 100644 --- a/lib/api/src/backend/wasmi/entities/module.rs +++ b/lib/api/src/backend/wasmi/entities/module.rs @@ -10,9 +10,9 @@ use crate::{ use bytes::Bytes; use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ExternType, FunctionType, - GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, Mutability, Pages, - SerializeError, TableType, Type, + CompilationProgressCallback, CompileError, DeserializeError, ExportType, ExportsIterator, + ExternType, FunctionType, GlobalType, ImportType, ImportsIterator, MemoryType, ModuleInfo, + Mutability, Pages, SerializeError, TableType, Type, }; pub(crate) struct ModuleHandle { pub(crate) inner: *mut wasm_module_t, @@ -74,6 +74,14 @@ impl Module { unsafe { Self::from_binary_unchecked(_engine, binary) } } + pub(crate) fn from_binary_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + _callback: CompilationProgressCallback, + ) -> Result { + Self::from_binary(engine, binary) + } + #[allow(clippy::arc_with_non_send_sync)] pub(crate) unsafe fn from_binary_unchecked( engine: &impl AsEngineRef, diff --git a/lib/api/src/entities/module/inner.rs b/lib/api/src/entities/module/inner.rs index 6c9fd76242f..c7cdf1a3faa 100644 --- a/lib/api/src/entities/module/inner.rs +++ b/lib/api/src/entities/module/inner.rs @@ -8,8 +8,8 @@ use thiserror::Error; #[cfg(feature = "wat")] use wasmer_types::WasmError; use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ImportType, ImportsIterator, - ModuleInfo, SerializeError, + CompilationProgressCallback, CompileError, DeserializeError, ExportType, ExportsIterator, + ImportType, ImportsIterator, ModuleInfo, SerializeError, }; use crate::{ @@ -43,6 +43,21 @@ impl BackendModule { Self::from_binary(engine, bytes.as_ref()) } + #[inline] + pub fn new_with_progress( + engine: &impl AsEngineRef, + bytes: impl AsRef<[u8]>, + callback: CompilationProgressCallback, + ) -> Result { + #[cfg(feature = "wat")] + let bytes = wat::parse_bytes(bytes.as_ref()).map_err(|e| { + CompileError::Wasm(WasmError::Generic(format!( + "Error when converting wat: {e}", + ))) + })?; + Self::from_binary_with_progress(engine, bytes.as_ref(), callback) + } + /// Creates a new WebAssembly module from a file path. #[inline] pub fn from_file( @@ -100,6 +115,67 @@ impl BackendModule { } } + #[inline] + pub fn from_binary_with_progress( + engine: &impl AsEngineRef, + binary: &[u8], + callback: CompilationProgressCallback, + ) -> Result { + match engine.as_engine_ref().inner.be { + #[cfg(feature = "sys")] + crate::BackendEngine::Sys(_) => Ok(Self::Sys( + crate::backend::sys::entities::module::Module::from_binary_with_progress( + engine, + binary, + callback.clone(), + )?, + )), + + #[cfg(feature = "wamr")] + crate::BackendEngine::Wamr(_) => Ok(Self::Wamr( + crate::backend::wamr::entities::module::Module::from_binary_with_progress( + engine, + binary, + callback.clone(), + )?, + )), + + #[cfg(feature = "wasmi")] + crate::BackendEngine::Wasmi(_) => Ok(Self::Wasmi( + crate::backend::wasmi::entities::module::Module::from_binary_with_progress( + engine, + binary, + callback.clone(), + )?, + )), + + #[cfg(feature = "v8")] + crate::BackendEngine::V8(_) => Ok(Self::V8( + crate::backend::v8::entities::module::Module::from_binary_with_progress( + engine, + binary, + callback.clone(), + )?, + )), + + #[cfg(feature = "js")] + crate::BackendEngine::Js(_) => Ok(Self::Js( + crate::backend::js::entities::module::Module::from_binary_with_progress( + engine, + binary, + callback.clone(), + )?, + )), + + #[cfg(feature = "jsc")] + crate::BackendEngine::Jsc(_) => Ok(Self::Jsc( + crate::backend::jsc::entities::module::Module::from_binary_with_progress( + engine, binary, callback, + )?, + )), + } + } + /// Creates a new WebAssembly module from a Wasm binary, /// skipping any kind of validation on the WebAssembly file. /// diff --git a/lib/api/src/entities/module/mod.rs b/lib/api/src/entities/module/mod.rs index 15c2cbe3490..4f9040737c0 100644 --- a/lib/api/src/entities/module/mod.rs +++ b/lib/api/src/entities/module/mod.rs @@ -11,8 +11,8 @@ use thiserror::Error; #[cfg(feature = "wat")] use wasmer_types::WasmError; use wasmer_types::{ - CompileError, DeserializeError, ExportType, ExportsIterator, ImportType, ImportsIterator, - ModuleInfo, SerializeError, + CompilationProgress, CompilationProgressCallback, CompileError, DeserializeError, ExportType, + ExportsIterator, ImportType, ImportsIterator, ModuleInfo, SerializeError, UserAbort, }; use crate::{AsEngineRef, macros::backend::match_rt, utils::IntoBytes}; @@ -113,6 +113,15 @@ impl Module { BackendModule::new(engine, bytes).map(Self) } + /// Creates a new WebAssembly Module and reports compilation progress through `callback`. + pub fn new_with_progress( + engine: &impl AsEngineRef, + bytes: impl AsRef<[u8]>, + on_progress: CompilationProgressCallback, + ) -> Result { + BackendModule::new_with_progress(engine, bytes, on_progress).map(Self) + } + /// Creates a new WebAssembly module from a file path. pub fn from_file( engine: &impl AsEngineRef, diff --git a/lib/compiler-cranelift/src/compiler.rs b/lib/compiler-cranelift/src/compiler.rs index 98d8b58a273..5ceedf80804 100644 --- a/lib/compiler-cranelift/src/compiler.rs +++ b/lib/compiler-cranelift/src/compiler.rs @@ -27,7 +27,13 @@ use gimli::write::{Address, EhFrame, FrameTable, Writer}; #[cfg(feature = "rayon")] use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; -use std::sync::Arc; +use std::{ + borrow::Cow, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; #[cfg(feature = "unwind")] use wasmer_compiler::types::{section::SectionIndex, unwind::CompiledFunctionUnwindInfo}; @@ -49,8 +55,8 @@ use wasmer_types::entity::PrimaryMap; use wasmer_types::target::CallingConvention; use wasmer_types::target::Target; use wasmer_types::{ - CompileError, FunctionIndex, LocalFunctionIndex, ModuleInfo, SignatureIndex, TrapCode, - TrapInformation, + CompilationProgress, CompilationProgressCallback, CompileError, FunctionIndex, + LocalFunctionIndex, ModuleInfo, SignatureIndex, TrapCode, TrapInformation, }; /// A compiler that compiles a WebAssembly module with Cranelift, translating the Wasm to Cranelift IR, @@ -79,6 +85,7 @@ impl CraneliftCompiler { compile_info: &CompileModuleInfo, module_translation_state: &ModuleTranslationState, function_body_inputs: PrimaryMap>, + progress_callback: Option<&CompilationProgressCallback>, ) -> Result { let isa = self .config() @@ -94,6 +101,11 @@ impl CraneliftCompiler { .map(|(_sig_index, func_type)| signature_to_cranelift_ir(func_type, frontend_config)) .collect::>(); + let total_functions = function_body_inputs.len() as u64; + let progress = progress_callback + .cloned() + .map(|cb| ProgressContext::new(cb, total_functions, "cranelift::functions")); + // Generate the frametable #[cfg(feature = "unwind")] let dwarf_frametable = if function_body_inputs.is_empty() { @@ -222,6 +234,10 @@ impl CraneliftCompiler { let range = reader.range(); let address_map = get_function_address_map(&context, range, code_buf.len()); + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(( CompiledFunction { body: FunctionBody { @@ -355,6 +371,10 @@ impl CraneliftCompiler { let range = reader.range(); let address_map = get_function_address_map(&context, range, code_buf.len()); + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(( CompiledFunction { body: FunctionBody { @@ -493,6 +513,7 @@ impl Compiler for CraneliftCompiler { compile_info: &CompileModuleInfo, module_translation_state: &ModuleTranslationState, function_body_inputs: PrimaryMap>, + progress_callback: Option<&CompilationProgressCallback>, ) -> Result { #[cfg(feature = "rayon")] { @@ -508,6 +529,7 @@ impl Compiler for CraneliftCompiler { compile_info, module_translation_state, function_body_inputs, + progress_callback, ) }) } @@ -519,6 +541,7 @@ impl Compiler for CraneliftCompiler { compile_info, module_translation_state, function_body_inputs, + progress_callback, ) } } @@ -565,6 +588,39 @@ fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation { } } +#[derive(Clone)] +struct ProgressContext { + callback: CompilationProgressCallback, + counter: Arc, + total: u64, + phase_name: &'static str, +} + +impl ProgressContext { + fn new(callback: CompilationProgressCallback, total: u64, phase_name: &'static str) -> Self { + Self { + callback, + counter: Arc::new(AtomicU64::new(0)), + total, + phase_name, + } + } + + fn notify(&self) -> Result<(), CompileError> { + if self.total == 0 { + return Ok(()); + } + let step = self.counter.fetch_add(1, Ordering::SeqCst) + 1; + self.callback + .notify(CompilationProgress::new( + Some(Cow::Borrowed(self.phase_name)), + Some(self.total), + Some(step), + )) + .map_err(CompileError::from) + } +} + /// Translates the Cranelift IR TrapCode into generic Trap Code fn translate_ir_trapcode(trap: ir::TrapCode) -> TrapCode { if trap == ir::TrapCode::STACK_OVERFLOW { diff --git a/lib/compiler-llvm/src/compiler.rs b/lib/compiler-llvm/src/compiler.rs index 19a106e613c..d90ef4879df 100644 --- a/lib/compiler-llvm/src/compiler.rs +++ b/lib/compiler-llvm/src/compiler.rs @@ -9,9 +9,14 @@ use inkwell::targets::FileType; use rayon::ThreadPoolBuilder; use rayon::iter::ParallelBridge; use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; -use std::borrow::Cow; -use std::collections::{HashMap, HashSet}; -use std::sync::Arc; +use std::{ + borrow::Cow, + collections::{HashMap, HashSet}, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; use wasmer_compiler::misc::CompiledKind; use wasmer_compiler::types::function::{Compilation, UnwindInfo}; use wasmer_compiler::types::module::CompileModuleInfo; @@ -26,7 +31,10 @@ use wasmer_compiler::{ }; use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::target::Target; -use wasmer_types::{CompileError, FunctionIndex, LocalFunctionIndex, ModuleInfo, SignatureIndex}; +use wasmer_types::{ + CompilationProgress, CompilationProgressCallback, CompileError, FunctionIndex, + LocalFunctionIndex, ModuleInfo, SignatureIndex, +}; use wasmer_vm::LibCall; /// A compiler that compiles a WebAssembly module with LLVM, translating the Wasm to LLVM IR, @@ -162,6 +170,39 @@ impl SymbolRegistry for ModuleBasedSymbolRegistry { } } +#[derive(Clone)] +struct ProgressContext { + callback: CompilationProgressCallback, + counter: Arc, + total: u64, + phase_name: &'static str, +} + +impl ProgressContext { + fn new(callback: CompilationProgressCallback, total: u64, phase_name: &'static str) -> Self { + Self { + callback, + counter: Arc::new(AtomicU64::new(0)), + total, + phase_name, + } + } + + fn notify(&self) -> Result<(), CompileError> { + if self.total == 0 { + return Ok(()); + } + let step = self.counter.fetch_add(1, Ordering::SeqCst) + 1; + self.callback + .notify(CompilationProgress::new( + Some(Cow::Borrowed(self.phase_name)), + Some(self.total), + Some(step), + )) + .map_err(CompileError::from) + } +} + impl LLVMCompiler { #[allow(clippy::too_many_arguments)] fn compile_native_object( @@ -352,6 +393,7 @@ impl Compiler for LLVMCompiler { compile_info: &CompileModuleInfo, module_translation: &ModuleTranslationState, function_body_inputs: PrimaryMap>, + progress_callback: Option<&CompilationProgressCallback>, ) -> Result { //let data = Arc::new(Mutex::new(0)); @@ -360,6 +402,10 @@ impl Compiler for LLVMCompiler { let binary_format = self.config.target_binary_format(target); let module = &compile_info.module; + let total_functions = function_body_inputs.len() as u64; + let progress = progress_callback + .cloned() + .map(|cb| ProgressContext::new(cb, total_functions, "Compiling functions")); // TODO: merge constants in sections. @@ -383,6 +429,7 @@ impl Compiler for LLVMCompiler { let symbol_registry = ModuleBasedSymbolRegistry::new(module.clone()); let functions = if self.config.num_threads.get() > 1 { + let progress = progress.clone(); let pool = ThreadPoolBuilder::new() .num_threads(self.config.num_threads.get()) .build() @@ -401,7 +448,7 @@ impl Compiler for LLVMCompiler { // TODO: remove (to serialize) //let _data = data.lock().unwrap(); - func_translator.translate( + let translated = func_translator.translate( module, module_translation, i, @@ -410,12 +457,19 @@ impl Compiler for LLVMCompiler { memory_styles, table_styles, &symbol_registry, - ) + )?; + + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + + Ok(translated) }, ) .collect::, CompileError>>() })? } else { + let progress = progress.clone(); let target_machine = self.config().target_machine(target); let func_translator = FuncTranslator::new(target_machine, binary_format).unwrap(); @@ -427,7 +481,7 @@ impl Compiler for LLVMCompiler { // TODO: remove (to serialize) //let _data = data.lock().unwrap(); - func_translator.translate( + let translated = func_translator.translate( module, module_translation, &i, @@ -436,7 +490,13 @@ impl Compiler for LLVMCompiler { memory_styles, table_styles, &symbol_registry, - ) + )?; + + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + + Ok(translated) }) .collect::, CompileError>>()? }; diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 17dd79ab785..7f7f1a65e8f 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -32,8 +32,8 @@ use wasmer_compiler::{ use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::target::{Architecture, CallingConvention, CpuFeature, Target}; use wasmer_types::{ - CompileError, FunctionIndex, FunctionType, LocalFunctionIndex, MemoryIndex, ModuleInfo, - TableIndex, TrapCode, TrapInformation, VMOffsets, + CompilationProgressCallback, CompileError, FunctionIndex, FunctionType, LocalFunctionIndex, + MemoryIndex, ModuleInfo, TableIndex, TrapCode, TrapInformation, VMOffsets, }; /// A compiler that compiles a WebAssembly module with Singlepass. @@ -77,6 +77,7 @@ impl Compiler for SinglepassCompiler { compile_info: &CompileModuleInfo, _module_translation: &ModuleTranslationState, function_body_inputs: PrimaryMap>, + _on_progress: Option<&CompilationProgressCallback>, ) -> Result { match target.triple().architecture { Architecture::X86_64 => {} diff --git a/lib/compiler/src/artifact_builders/artifact_builder.rs b/lib/compiler/src/artifact_builders/artifact_builder.rs index 8d607b02e5d..ebc275c10cf 100644 --- a/lib/compiler/src/artifact_builders/artifact_builder.rs +++ b/lib/compiler/src/artifact_builders/artifact_builder.rs @@ -31,7 +31,7 @@ use self_cell::self_cell; use shared_buffer::OwnedBuffer; use std::sync::Arc; use wasmer_types::{ - DeserializeError, + CompilationProgressCallback, DeserializeError, entity::{ArchivedPrimaryMap, PrimaryMap}, target::CpuFeature, }; @@ -64,6 +64,7 @@ impl ArtifactBuild { memory_styles: PrimaryMap, table_styles: PrimaryMap, hash_algorithm: Option, + progress_callback: Option<&CompilationProgressCallback>, ) -> Result { let environ = ModuleEnvironment::new(); let features = inner_engine.features().clone(); @@ -104,6 +105,7 @@ impl ArtifactBuild { // `module_translation_state`. translation.module_translation_state.as_ref().unwrap(), translation.function_body_inputs, + progress_callback, )?; let data_initializers = translation diff --git a/lib/compiler/src/compiler.rs b/lib/compiler/src/compiler.rs index 43fb8685ae3..01cf9f4a491 100644 --- a/lib/compiler/src/compiler.rs +++ b/lib/compiler/src/compiler.rs @@ -10,7 +10,7 @@ use crate::{ }; use enumset::EnumSet; use wasmer_types::{ - Features, LocalFunctionIndex, + CompilationProgressCallback, Features, LocalFunctionIndex, entity::PrimaryMap, error::CompileError, target::{CpuFeature, Target, UserCompilerOptimizations}, @@ -154,6 +154,7 @@ pub trait Compiler: Send + std::fmt::Debug { module_translation: &ModuleTranslationState, // The list of function bodies function_body_inputs: PrimaryMap>, + progress_callback: Option<&CompilationProgressCallback>, ) -> Result; /// Compiles a module into a native object file. diff --git a/lib/compiler/src/engine/artifact.rs b/lib/compiler/src/engine/artifact.rs index 2189bf80e4a..e24644caa28 100644 --- a/lib/compiler/src/engine/artifact.rs +++ b/lib/compiler/src/engine/artifact.rs @@ -36,10 +36,10 @@ use crate::object::{ #[cfg(feature = "compiler")] use wasmer_types::HashAlgorithm; use wasmer_types::{ - ArchivedDataInitializerLocation, ArchivedOwnedDataInitializer, CompileError, DataInitializer, - DataInitializerLike, DataInitializerLocation, DataInitializerLocationLike, DeserializeError, - FunctionIndex, LocalFunctionIndex, MemoryIndex, ModuleInfo, OwnedDataInitializer, - SerializeError, SignatureIndex, TableIndex, + ArchivedDataInitializerLocation, ArchivedOwnedDataInitializer, CompilationProgressCallback, + CompileError, DataInitializer, DataInitializerLike, DataInitializerLocation, + DataInitializerLocationLike, DeserializeError, FunctionIndex, LocalFunctionIndex, MemoryIndex, + ModuleInfo, OwnedDataInitializer, SerializeError, SignatureIndex, TableIndex, entity::{BoxedSlice, PrimaryMap}, target::{CpuFeature, Target}, }; @@ -127,6 +127,7 @@ impl Artifact { data: &[u8], tunables: &dyn Tunables, hash_algorithm: Option, + progress_callback: Option, ) -> Result { let mut inner_engine = engine.inner_mut(); let environ = ModuleEnvironment::new(); @@ -150,6 +151,7 @@ impl Artifact { memory_styles, table_styles, hash_algorithm, + progress_callback.as_ref(), )?; Self::from_parts( @@ -1099,6 +1101,7 @@ impl Artifact { &metadata.compile_info, module_translation.as_ref().unwrap(), function_body_inputs, + None, )?; let mut obj = get_object_for_target(target_triple).map_err(to_compile_error)?; diff --git a/lib/compiler/src/engine/inner.rs b/lib/compiler/src/engine/inner.rs index abf898c2cec..ce78d7f3aca 100644 --- a/lib/compiler/src/engine/inner.rs +++ b/lib/compiler/src/engine/inner.rs @@ -9,7 +9,7 @@ use crate::{Compiler, CompilerConfig}; #[cfg(feature = "compiler")] use wasmer_types::Features; -use wasmer_types::{CompileError, HashAlgorithm, target::Target}; +use wasmer_types::{CompilationProgressCallback, CompileError, HashAlgorithm, target::Target}; #[cfg(not(target_arch = "wasm32"))] use shared_buffer::OwnedBuffer; @@ -193,6 +193,23 @@ impl Engine { binary, self.tunables.as_ref(), self.hash_algorithm, + None, + )?)) + } + + /// Compile a WebAssembly binary with a progress callback. + #[cfg(feature = "compiler")] + pub fn compile_with_progress( + &self, + binary: &[u8], + progress_callback: Option, + ) -> Result, CompileError> { + Ok(Arc::new(Artifact::new( + self, + binary, + self.tunables.as_ref(), + self.hash_algorithm, + progress_callback, )?)) } diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index 80de22e4631..9d6b55b1118 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -1,5 +1,5 @@ //! The WebAssembly possible errors -use crate::{ExternType, Pages}; +use crate::{ExternType, Pages, progress::UserAbort}; use std::io; use thiserror::Error; @@ -178,6 +178,9 @@ pub enum CompileError { /// Middleware error occurred. #[cfg_attr(feature = "std", error("Middleware error: {0}"))] MiddlewareError(String), + /// Compilation aborted by a user callback. + #[cfg_attr(feature = "std", error("Compilation aborted: {0}"))] + User(UserAbort), } impl From for CompileError { @@ -186,6 +189,12 @@ impl From for CompileError { } } +impl From for CompileError { + fn from(abort: UserAbort) -> Self { + Self::User(abort) + } +} + /// A error in the middleware. #[derive(Debug)] #[cfg_attr(feature = "std", derive(Error))] diff --git a/lib/types/src/lib.rs b/lib/types/src/lib.rs index e9594a88510..2aa60c19e11 100644 --- a/lib/types/src/lib.rs +++ b/lib/types/src/lib.rs @@ -58,6 +58,7 @@ mod libcalls; mod memory; mod module; mod module_hash; +mod progress; mod serialize; mod stack; mod store_id; @@ -91,6 +92,7 @@ pub use crate::initializers::{ pub use crate::memory::{Memory32, Memory64, MemorySize}; pub use crate::module::{ExportsIterator, ImportKey, ImportsIterator, ModuleInfo}; pub use crate::module_hash::{HashAlgorithm, ModuleHash}; +pub use crate::progress::{CompilationProgress, CompilationProgressCallback, UserAbort}; pub use crate::types::{ ExportType, ExternType, FunctionType, GlobalInit, GlobalType, ImportType, MemoryType, Mutability, TableType, TagKind, TagType, Type, V128, diff --git a/lib/types/src/progress.rs b/lib/types/src/progress.rs new file mode 100644 index 00000000000..6e7868594d0 --- /dev/null +++ b/lib/types/src/progress.rs @@ -0,0 +1,110 @@ +//! Types used to report and handle compilation progress. + +use crate::lib::std::{borrow::Cow, fmt, string::String, sync::Arc}; + +/// Indicates the current compilation progress. +// NOTE: fields are kept private on purpose to enable forwards compatibility and future extension. +#[derive(Clone, Debug)] +pub struct CompilationProgress { + phase_name: Option>, + phase_step_count: Option, + phase_step: Option, +} + +impl CompilationProgress { + /// Creates a new [`CompilationProgress`]. + pub fn new( + phase_name: Option>, + phase_step_count: Option, + phase_step: Option, + ) -> Self { + Self { + phase_name, + phase_step_count, + phase_step, + } + } + + /// Returns the name of the phase currently being executed. + pub fn phase_name(&self) -> Option<&str> { + self.phase_name.as_deref() + } + + /// Returns the total number of steps in the current phase, if known. + pub fn phase_step_count(&self) -> Option { + self.phase_step_count + } + + /// Returns the index of the current step within the phase, if known. + pub fn phase_step(&self) -> Option { + self.phase_step + } +} + +impl Default for CompilationProgress { + fn default() -> Self { + Self { + phase_name: None, + phase_step_count: None, + phase_step: None, + } + } +} + +/// Error returned when the user requests to abort compilation. +#[derive(Clone, Debug)] +pub struct UserAbort { + reason: String, +} + +impl UserAbort { + /// Creates a new [`UserAbort`]. + pub fn new(reason: impl Into) -> Self { + Self { + reason: reason.into(), + } + } + + /// Returns the configured reason. + pub fn reason(&self) -> &str { + &self.reason + } +} + +impl fmt::Display for UserAbort { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.reason.fmt(f) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for UserAbort {} + +#[derive(Clone)] +/// Wraps a boxed callback that can receive compilation progress notifications. +pub struct CompilationProgressCallback { + callback: Arc Result<(), UserAbort> + Send + Sync + 'static>, +} + +impl CompilationProgressCallback { + /// Create a new callback wrapper. + pub fn new(callback: F) -> Self + where + F: Fn(CompilationProgress) -> Result<(), UserAbort> + Send + Sync + 'static, + { + Self { + callback: Arc::new(callback), + } + } + + /// Notify the callback about new progress information. + pub fn notify(&self, progress: CompilationProgress) -> Result<(), UserAbort> { + (self.callback)(progress) + } +} + +impl fmt::Debug for CompilationProgressCallback { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("CompilationProgressCallback").finish() + } +} From 9d8e66465b999bb62cb9469f551e1a4ec39d7263 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 14:46:08 +0100 Subject: [PATCH 02/14] feat(wasix): Module load/compile progress reporting Implement progress reporting for module loading. * Extends ModuleCache with a load_with_progress() method, which receives a progress callback * Clean up and unify the module loading methods on the Runtime trait. Replace old methods with a new, general fn resolve_module(), which replaces all the old methods. This new method also takes an optional progress callback. --- lib/wasix/src/bin_factory/binary_package.rs | 10 +- lib/wasix/src/bin_factory/exec.rs | 7 +- lib/wasix/src/runners/wcgi/runner.rs | 7 +- lib/wasix/src/runtime/mod.rs | 244 +++++++++++------- lib/wasix/src/runtime/module_cache/mod.rs | 1 + .../src/runtime/module_cache/progress.rs | 83 ++++++ lib/wasix/src/runtime/module_cache/types.rs | 16 +- 7 files changed, 265 insertions(+), 103 deletions(-) create mode 100644 lib/wasix/src/runtime/module_cache/progress.rs diff --git a/lib/wasix/src/bin_factory/binary_package.rs b/lib/wasix/src/bin_factory/binary_package.rs index bc13a77a833..2380c758e2f 100644 --- a/lib/wasix/src/bin_factory/binary_package.rs +++ b/lib/wasix/src/bin_factory/binary_package.rs @@ -62,6 +62,12 @@ impl BinaryPackageCommand { self.atom.clone() } + /// Get a reference to this [`BinaryPackageCommand`]'s atom as a cheap + /// clone of the internal OwnedBuffer. + pub fn atom_ref(&self) -> &SharedBytes { + &self.atom + } + pub fn hash(&self) -> &ModuleHash { &self.hash } @@ -352,10 +358,10 @@ mod tests { name = "foo" source = "foo.wasm" abi = "wasi" - + [[command]] name = "cmd" - module = "foo" + module = "foo" "#; let manifest = temp.path().join("wasmer.toml"); std::fs::write(&manifest, wasmer_toml).unwrap(); diff --git a/lib/wasix/src/bin_factory/exec.rs b/lib/wasix/src/bin_factory/exec.rs index d077394ddb8..f04b06c8882 100644 --- a/lib/wasix/src/bin_factory/exec.rs +++ b/lib/wasix/src/bin_factory/exec.rs @@ -1,5 +1,5 @@ #![allow(clippy::result_large_err)] -use std::sync::Arc; +use std::{borrow::Cow, sync::Arc}; use crate::{ RewindState, SpawnError, WasiError, WasiRuntimeError, @@ -8,7 +8,7 @@ use crate::{ thread::{RewindResultType, WasiThreadRunGuard}, }, runtime::{ - TaintReason, + ModuleInput, TaintReason, module_cache::HashedModuleData, task_manager::{ TaskWasm, TaskWasmRecycle, TaskWasmRecycleProperties, TaskWasmRunProperties, @@ -34,7 +34,8 @@ pub async fn spawn_exec( spawn_union_fs(&env, &binary).await?; let cmd = package_command_by_name(&binary, name)?; - let module = runtime.load_command_module(cmd).await?; + let input = ModuleInput::Command(Cow::Borrowed(cmd)); + let module = runtime.resolve_module(input, None, None).await?; // Free the space used by the binary, since we don't need it // any longer diff --git a/lib/wasix/src/runners/wcgi/runner.rs b/lib/wasix/src/runners/wcgi/runner.rs index 59a793105b9..83803b58bd8 100644 --- a/lib/wasix/src/runners/wcgi/runner.rs +++ b/lib/wasix/src/runners/wcgi/runner.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddr, sync::Arc}; +use std::{borrow::Cow, net::SocketAddr, sync::Arc}; use super::super::Body; use anyhow::{Context, Error}; @@ -21,7 +21,7 @@ use crate::{ wasi_common::CommonWasiOptions, wcgi::handler::{Handler, SharedState}, }, - runtime::task_manager::VirtualTaskManagerExt, + runtime::{ModuleInput, task_manager::VirtualTaskManagerExt}, }; use super::Callbacks; @@ -62,7 +62,8 @@ impl WcgiRunner { .annotation("wasi")? .unwrap_or_else(|| Wasi::new(command_name)); - let module = runtime.load_command_module_sync(cmd)?; + let input = ModuleInput::Command(Cow::Borrowed(cmd)); + let module = runtime.resolve_module_sync(input, None, None)?; let Wcgi { dialect, .. } = metadata.annotation("wcgi")?.unwrap_or_default(); let dialect = match dialect { diff --git a/lib/wasix/src/runtime/mod.rs b/lib/wasix/src/runtime/mod.rs index a288e0f0a35..106b4562e78 100644 --- a/lib/wasix/src/runtime/mod.rs +++ b/lib/wasix/src/runtime/mod.rs @@ -7,9 +7,13 @@ use self::module_cache::CacheError; pub use self::task_manager::{SpawnType, VirtualTaskManager}; use module_cache::HashedModuleData; use wasmer_config::package::SuggestedCompilerOptimizations; -use wasmer_types::target::UserCompilerOptimizations as WasmerSuggestedCompilerOptimizations; +use wasmer_types::{ + CompilationProgressCallback, ModuleHash, + target::UserCompilerOptimizations as WasmerSuggestedCompilerOptimizations, +}; use std::{ + borrow::Cow, fmt, ops::Deref, sync::{Arc, Mutex}, @@ -29,7 +33,10 @@ use crate::{ http::{DynHttpClient, HttpClient}, os::TtyBridge, runtime::{ - module_cache::{ModuleCache, ThreadLocalCache}, + module_cache::{ + ModuleCache, ThreadLocalCache, + progress::{ModuleLoadProgress, ModuleLoadProgressReporter}, + }, package_loader::{PackageLoader, UnsupportedPackageLoader}, resolver::{BackendSource, MultiSource, Source}, }, @@ -43,6 +50,61 @@ pub enum TaintReason { DlSymbolResolutionFailed(String), } +/// The input to load a module. +/// +/// Exists because the semantics for resolving modules can vary between +/// different sources. +pub enum ModuleInput<'a> { + Bytes(Cow<'a, [u8]>), + Hashed(Cow<'a, HashedModuleData>), + Command(Cow<'a, BinaryPackageCommand>), +} + +impl<'a> ModuleInput<'a> { + pub fn to_owned(&'a self) -> ModuleInput<'static> { + // The manual code below is needed due to compiler issues with the lifetime. + match self { + Self::Bytes(Cow::Borrowed(b)) => { + let v: Vec = (*b).to_owned(); + let c: Cow<'static, [u8]> = Cow::from(v); + ModuleInput::Bytes(c) + } + Self::Bytes(Cow::Owned(b)) => ModuleInput::Bytes(Cow::Owned((*b).clone())), + Self::Hashed(Cow::Borrowed(h)) => ModuleInput::Hashed(Cow::Owned((*h).clone())), + Self::Hashed(Cow::Owned(h)) => ModuleInput::Hashed(Cow::Owned(h.clone())), + Self::Command(Cow::Borrowed(c)) => ModuleInput::Command(Cow::Owned((*c).clone())), + Self::Command(Cow::Owned(c)) => ModuleInput::Command(Cow::Owned(c.clone())), + } + } + + pub fn hash(&self) -> ModuleHash { + match self { + Self::Bytes(b) => { + // Hash on the fly + ModuleHash::sha256(b) + } + Self::Hashed(hashed) => *hashed.hash(), + Self::Command(cmd) => *cmd.hash(), + } + } + + pub fn wasm(&self) -> &[u8] { + match self { + Self::Bytes(b) => b, + Self::Hashed(hashed) => hashed.wasm().as_ref(), + Self::Command(cmd) => cmd.atom_ref().as_ref(), + } + } + + pub fn to_hashed(&self) -> HashedModuleData { + match self { + Self::Bytes(b) => HashedModuleData::new(b.as_ref()), + Self::Hashed(hashed) => hashed.as_ref().clone(), + Self::Command(cmd) => HashedModuleData::from_command(cmd), + } + } +} + /// Runtime components used when running WebAssembly programs. /// /// Think of this as the "System" in "WebAssembly Systems Interface". @@ -113,54 +175,86 @@ where None } + /// The primary way to load a module given a module input. + /// + /// The engine to use can be optionally provided, otherwise the most appropriate engine + /// should be selected. + /// + /// An optional progress reporter callback can be provided to report progress during module loading. + fn resolve_module<'a>( + &'a self, + input: ModuleInput<'a>, + engine: Option<&Engine>, + on_progress: Option, + ) -> BoxFuture<'a, Result> { + let data = input.to_hashed(); + + let engine = if let Some(e) = engine { + e.clone() + } else { + match &input { + ModuleInput::Bytes(_) => self.engine(), + ModuleInput::Hashed(_) => self.engine(), + ModuleInput::Command(cmd) => { + match self + .engine_with_suggested_opts(&cmd.as_ref().suggested_compiler_optimizations) + { + Ok(engine) => engine, + Err(error) => { + return Box::pin(async move { + Err(SpawnError::CompileError { + module_hash: *data.hash(), + error, + }) + }); + } + } + } + } + }; + + let module_cache = self.module_cache(); + + let task = async move { load_module(&engine, &module_cache, input, on_progress).await }; + Box::pin(task) + } + + /// Sync variant of [`Self::resolve_module`]. + fn resolve_module_sync( + &self, + input: ModuleInput<'_>, + engine: Option<&Engine>, + on_progress: Option, + ) -> Result { + block_on(self.resolve_module(input, engine, on_progress)) + } + /// Load the module for a command. /// /// Will load the module from the cache if possible, otherwise will compile. /// /// NOTE: This always be preferred over [`Self::load_module`] to avoid /// re-hashing the module! + #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")] fn load_command_module( &self, cmd: &BinaryPackageCommand, ) -> BoxFuture<'_, Result> { - let module_cache = self.module_cache(); - let data = HashedModuleData::from_command(cmd); - - let engine = match self.engine_with_suggested_opts(&cmd.suggested_compiler_optimizations) { - Ok(engine) => engine, - Err(error) => { - return Box::pin(async move { - Err(SpawnError::CompileError { - module_hash: *data.hash(), - error, - }) - }); - } - }; - - let task = async move { load_module(&engine, &module_cache, &data).await }; - - Box::pin(task) + self.resolve_module(ModuleInput::Command(Cow::Owned(cmd.clone())), None, None) } /// Sync version of [`Self::load_command_module`]. + #[deprecated(since = "0.601.0", note = "Use `resolve_module_sync` instead")] fn load_command_module_sync(&self, cmd: &BinaryPackageCommand) -> Result { - block_on(self.load_command_module(cmd)) + block_on(self.resolve_module(ModuleInput::Command(Cow::Borrowed(cmd)), None, None)) } /// Load a WebAssembly module from raw bytes. /// /// Will load the module from the cache if possible, otherwise will compile. - #[deprecated( - since = "0.601.0", - note = "Use `load_command_module` or `load_hashed_module` instead - this method can have high overhead" - )] + #[deprecated(since = "0.601.0", note = "Use `resolve_module` instead")] fn load_module<'a>(&'a self, wasm: &'a [u8]) -> BoxFuture<'a, Result> { - let engine = self.engine(); - let module_cache = self.module_cache(); - let data = HashedModuleData::new(wasm.to_vec()); - let task = async move { load_module(&engine, &module_cache, &data).await }; - Box::pin(task) + self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None) } /// Synchronous version of [`Self::load_module`]. @@ -169,8 +263,7 @@ where note = "Use `load_command_module` or `load_hashed_module` instead - this method can have high overhead" )] fn load_module_sync(&self, wasm: &[u8]) -> Result { - #[allow(deprecated)] - block_on(self.load_module(wasm)) + block_on(self.resolve_module(ModuleInput::Bytes(Cow::Borrowed(wasm)), None, None)) } /// Load a WebAssembly module from pre-hashed data. @@ -181,10 +274,7 @@ where module: HashedModuleData, engine: Option<&Engine>, ) -> BoxFuture<'_, Result> { - let engine = engine.cloned().unwrap_or_else(|| self.engine()); - let module_cache = self.module_cache(); - let task = async move { load_module(&engine, &module_cache, &module).await }; - Box::pin(task) + self.resolve_module(ModuleInput::Hashed(Cow::Owned(module)), engine, None) } /// Synchronous version of [`Self::load_hashed_module`]. @@ -193,7 +283,7 @@ where wasm: HashedModuleData, engine: Option<&Engine>, ) -> Result { - block_on(self.load_hashed_module(wasm, engine)) + block_on(self.resolve_module(ModuleInput::Hashed(Cow::Owned(wasm)), engine, None)) } /// Callback thats invokes whenever the instance is tainted, tainting can occur @@ -231,10 +321,17 @@ pub type DynRuntime = dyn Runtime + Send + Sync; pub async fn load_module( engine: &Engine, module_cache: &(dyn ModuleCache + Send + Sync), - module: &HashedModuleData, + input: ModuleInput<'_>, + on_progress: Option, ) -> Result { - let wasm_hash = *module.hash(); - let result = module_cache.load(wasm_hash, engine).await; + let wasm_hash = input.hash(); + let result = if let Some(on_progress) = &on_progress { + module_cache + .load_with_progress(wasm_hash, engine, on_progress.clone()) + .await + } else { + module_cache.load(wasm_hash, engine).await + }; match result { Ok(module) => return Ok(module), @@ -248,11 +345,19 @@ pub async fn load_module( } } - let module = - Module::new(&engine, module.wasm()).map_err(|err| crate::SpawnError::CompileError { - module_hash: wasm_hash, - error: err, - })?; + let res = if let Some(progress) = on_progress { + let p = CompilationProgressCallback::new(move |p| { + progress.notify(ModuleLoadProgress::CompilingModule(p)) + }); + Module::new_with_progress(&engine, input.wasm(), p) + } else { + Module::new(&engine, input.wasm()) + }; + + let module = res.map_err(|err| crate::SpawnError::CompileError { + module_hash: wasm_hash, + error: err, + })?; // TODO: pass a [`HashedModule`] struct that is safe by construction. if let Err(e) = module_cache.save(wasm_hash, engine, &module).await { @@ -651,53 +756,4 @@ impl Runtime for OverriddenRuntime { self.inner.active_journal() } } - - fn load_module<'a>(&'a self, data: &[u8]) -> BoxFuture<'a, Result> { - #[allow(deprecated)] - if let (Some(engine), Some(module_cache)) = (&self.engine, self.module_cache.clone()) { - let engine = engine.clone(); - let hashed_data = HashedModuleData::new(data.to_vec()); - let task = async move { load_module(&engine, &module_cache, &hashed_data).await }; - Box::pin(task) - } else { - let data = data.to_vec(); - Box::pin(async move { self.inner.load_module(&data).await }) - } - } - - fn load_module_sync(&self, wasm: &[u8]) -> Result { - #[allow(deprecated)] - if self.engine.is_some() || self.module_cache.is_some() { - block_on(self.load_module(wasm)) - } else { - self.inner.load_module_sync(wasm) - } - } - - fn load_hashed_module( - &self, - data: HashedModuleData, - engine: Option<&Engine>, - ) -> BoxFuture<'_, Result> { - if self.engine.is_some() || self.module_cache.is_some() { - let engine = self.engine(); - let module_cache = self.module_cache(); - let task = async move { load_module(&engine, &module_cache, &data).await }; - Box::pin(task) - } else { - self.inner.load_hashed_module(data, engine) - } - } - - fn load_hashed_module_sync( - &self, - wasm: HashedModuleData, - engine: Option<&Engine>, - ) -> Result { - if self.engine.is_some() || self.module_cache.is_some() { - block_on(self.load_hashed_module(wasm, engine)) - } else { - self.inner.load_hashed_module_sync(wasm, engine) - } - } } diff --git a/lib/wasix/src/runtime/module_cache/mod.rs b/lib/wasix/src/runtime/module_cache/mod.rs index ba1d5242fbd..f9437cb489c 100644 --- a/lib/wasix/src/runtime/module_cache/mod.rs +++ b/lib/wasix/src/runtime/module_cache/mod.rs @@ -36,6 +36,7 @@ mod hashed_module; mod fallback; #[cfg(feature = "sys-thread")] mod filesystem; +pub mod progress; mod shared; mod thread_local; mod types; diff --git a/lib/wasix/src/runtime/module_cache/progress.rs b/lib/wasix/src/runtime/module_cache/progress.rs new file mode 100644 index 00000000000..595395d1af1 --- /dev/null +++ b/lib/wasix/src/runtime/module_cache/progress.rs @@ -0,0 +1,83 @@ +use std::sync::Arc; + +use wasmer_types::UserAbort; + +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum ModuleLoadProgress { + LocalCacheHit, + RemoteArtifact(ModuleLoadProgressRemote), + DownloadingModule(DownloadProgress), + CompilingModule(wasmer_types::CompilationProgress), +} + +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum ModuleLoadProgressRemote { + RemoteCacheCheck, + RemoteCacheHit, + LoadingArtifact(DownloadProgress), + /// This exists as a variant since local compilation can be used instead if artifact fails. + ArtifactLoadFailed(ProgressError), +} + +#[derive(Clone, Debug)] +#[non_exhaustive] +pub struct DownloadProgress { + pub total_bytes: u64, + pub downloaded_bytes: Option, +} + +impl DownloadProgress { + pub fn new(total_bytes: u64, downloaded_bytes: Option) -> Self { + Self { + total_bytes, + downloaded_bytes, + } + } +} + +#[derive(Clone, Debug)] +pub struct ProgressError { + message: String, +} + +impl ProgressError { + pub fn new(message: impl Into) -> Self { + Self { + message: message.into(), + } + } +} + +impl std::fmt::Display for ProgressError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.message) + } +} + +#[derive(Clone)] +pub struct ModuleLoadProgressReporter { + callback: Arc Result<(), UserAbort> + Send + Sync>, +} + +impl ModuleLoadProgressReporter { + pub fn new(callback: F) -> Self + where + F: Fn(ModuleLoadProgress) -> Result<(), UserAbort> + Send + Sync + 'static, + { + Self { + callback: Arc::new(callback), + } + } + + pub fn notify(&self, progress: ModuleLoadProgress) -> Result<(), UserAbort> { + (self.callback)(progress) + } +} + +impl std::fmt::Debug for ModuleLoadProgressReporter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ModuleLoadProgressReporter").finish() + } +} diff --git a/lib/wasix/src/runtime/module_cache/types.rs b/lib/wasix/src/runtime/module_cache/types.rs index a053b412be6..5099a53c918 100644 --- a/lib/wasix/src/runtime/module_cache/types.rs +++ b/lib/wasix/src/runtime/module_cache/types.rs @@ -3,7 +3,7 @@ use std::{fmt::Debug, ops::Deref, path::PathBuf}; use wasmer::{Engine, Module}; use wasmer_types::ModuleHash; -use crate::runtime::module_cache::FallbackCache; +use crate::runtime::module_cache::{FallbackCache, progress::ModuleLoadProgressReporter}; /// A cache for compiled WebAssembly modules. /// @@ -26,6 +26,20 @@ pub trait ModuleCache: Debug { /// Load a module based on its hash. async fn load(&self, key: ModuleHash, engine: &Engine) -> Result; + /// Load a module based on its hash, with progress reporting. + /// + /// The provided progress reporter will receive updates about the loading process, if supported. + async fn load_with_progress( + &self, + key: ModuleHash, + engine: &Engine, + on_progress: ModuleLoadProgressReporter, + ) -> Result { + // Default implementation just ignores progress reporting. + let _ = on_progress; + self.load(key, engine).await + } + /// Check if a module is present in the cache. async fn contains(&self, key: ModuleHash, engine: &Engine) -> Result; From 43027f2b997ad1ccb429db7e428a5e9112cf6a6c Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 14:47:51 +0100 Subject: [PATCH 03/14] feat(cli): run command: Show compilation progress Show compilation progress using the newly added progress tracking. --- lib/cli/src/commands/run/runtime.rs | 187 +++++++++------------------- 1 file changed, 57 insertions(+), 130 deletions(-) diff --git a/lib/cli/src/commands/run/runtime.rs b/lib/cli/src/commands/run/runtime.rs index d0e712b6e06..273686c765f 100644 --- a/lib/cli/src/commands/run/runtime.rs +++ b/lib/cli/src/commands/run/runtime.rs @@ -13,7 +13,10 @@ use wasmer_wasix::{ SpawnError, bin_factory::{BinaryPackage, BinaryPackageCommand}, runtime::{ - module_cache::HashedModuleData, + module_cache::{ + HashedModuleData, + progress::{ModuleLoadProgress, ModuleLoadProgressReporter}, + }, resolver::{PackageSummary, QueryError}, }, }; @@ -108,138 +111,62 @@ impl wasmer_wasix::Runtime for Monitorin self.runtime.active_journal() } - fn load_hashed_module( - &self, - module: HashedModuleData, - engine: Option<&Engine>, - ) -> BoxFuture<'_, Result> { - if self.quiet_mode { - Box::pin(self.runtime.load_hashed_module(module, engine)) - } else { - let hash = *module.hash(); - let fut = self.runtime.load_hashed_module(module, engine); - Box::pin(compile_with_progress( - &self.progress, - fut, - hash, - None, - self.quiet_mode, - )) - } - } - - fn load_hashed_module_sync( - &self, - wasm: HashedModuleData, + fn resolve_module<'a>( + &'a self, + input: wasmer_wasix::runtime::ModuleInput<'a>, engine: Option<&Engine>, - ) -> Result { - if self.quiet_mode { - self.runtime.load_hashed_module_sync(wasm, engine) - } else { - let hash = *wasm.hash(); - compile_with_progress_sync( - &self.progress, - move || self.runtime.load_hashed_module_sync(wasm, engine), - &hash, - None, - ) - } - } - - fn load_command_module( - &self, - cmd: &BinaryPackageCommand, - ) -> BoxFuture<'_, Result> { - if self.quiet_mode { - self.runtime.load_command_module(cmd) - } else { - let fut = self.runtime.load_command_module(cmd); - - Box::pin(compile_with_progress( - &self.progress, - fut, - *cmd.hash(), - Some(cmd.name().to_owned()), - self.quiet_mode, - )) - } - } - - fn load_command_module_sync( - &self, - cmd: &wasmer_wasix::bin_factory::BinaryPackageCommand, - ) -> Result { - if self.quiet_mode { - self.runtime.load_command_module_sync(cmd) - } else { - compile_with_progress_sync( - &self.progress, - || self.runtime.load_command_module_sync(cmd), - cmd.hash(), - Some(cmd.name()), - ) - } - } -} - -async fn compile_with_progress<'a, F, T>( - bar: &ProgressBar, - fut: F, - hash: ModuleHash, - name: Option, - quiet_mode: bool, -) -> T -where - F: std::future::Future + Send + 'a, - T: Send + 'static, -{ - if quiet_mode { - fut.await - } else { - let should_clear = bar.is_finished() || bar.is_hidden(); - show_compile_progress(bar, &hash, name.as_deref()); - let res = fut.await; - if should_clear { - bar.finish_and_clear(); + on_progress: Option, + ) -> BoxFuture<'a, Result> { + if on_progress.is_some() || self.quiet_mode { + return self.runtime.resolve_module(input, engine, on_progress); } - res - } -} - -fn compile_with_progress_sync( - bar: &ProgressBar, - f: F, - hash: &ModuleHash, - name: Option<&str>, -) -> T -where - F: FnOnce() -> T, -{ - let should_clear = bar.is_finished() || bar.is_hidden(); - show_compile_progress(bar, hash, name); - let res = f(); - if should_clear { - bar.finish_and_clear(); - } - res -} - -fn show_compile_progress(bar: &ProgressBar, hash: &ModuleHash, name: Option<&str>) { - // Only show a spinner if we're running in a TTY - let hash = hash.to_string(); - let hash = &hash[0..8]; - let msg = if let Some(name) = name { - format!("Compiling WebAssembly module for command '{name}' ({hash})...") - } else { - format!("Compiling WebAssembly module {hash}...") - }; - - bar.set_message(msg); - bar.enable_steady_tick(Duration::from_millis(100)); - - if bar.is_finished() || bar.is_hidden() { - bar.reset(); + use std::fmt::Write as _; + + let pb = self.progress.clone(); + let on_progress = Some(ModuleLoadProgressReporter::new(move |prog| { + let msg = match prog { + ModuleLoadProgress::CompilingModule(c) => { + if let (Some(step), Some(step_count)) = (c.phase_step(), c.phase_step_count()) { + pb.set_length(step_count); + pb.set_position(step); + }; + + let mut msg = if let Some(phase) = c.phase_name() { + format!("Compiling module: {}", phase) + } else { + "Compiling module".to_string() + }; + + let progress = if let (Some(step), Some(step_count)) = + (c.phase_step(), c.phase_step_count()) + { + pb.set_length(step_count); + pb.set_position(step); + write!(msg, " ({}/{})", step, step_count).unwrap(); + }; + + msg + } + _ => "Compiling module...".to_string(), + }; + + pb.set_message(msg); + Ok(()) + })); + + let engine = engine.cloned(); + + let f = async move { + let res = self + .runtime + .resolve_module(input, engine.as_ref(), on_progress) + .await; + self.progress.finish_and_clear(); + res + }; + + Box::pin(f) } } From 17ae0ced60ed96991ab790cd680cc6c8fd7dca1f Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 17:53:45 +0100 Subject: [PATCH 04/14] Fix clippy warnings --- lib/cli/src/commands/run/runtime.rs | 9 ++++----- lib/compiler-singlepass/src/codegen.rs | 1 + lib/types/src/progress.rs | 12 +----------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/cli/src/commands/run/runtime.rs b/lib/cli/src/commands/run/runtime.rs index 273686c765f..38791113d7e 100644 --- a/lib/cli/src/commands/run/runtime.rs +++ b/lib/cli/src/commands/run/runtime.rs @@ -133,17 +133,16 @@ impl wasmer_wasix::Runtime for Monitorin }; let mut msg = if let Some(phase) = c.phase_name() { - format!("Compiling module: {}", phase) + format!("Compiling module: {phase}") } else { "Compiling module".to_string() }; - let progress = if let (Some(step), Some(step_count)) = - (c.phase_step(), c.phase_step_count()) - { + if let (Some(step), Some(step_count)) = (c.phase_step(), c.phase_step_count()) { pb.set_length(step_count); pb.set_position(step); - write!(msg, " ({}/{})", step, step_count).unwrap(); + // Note: writing to strings can not fail. + write!(msg, " ({step}/{step_count})").unwrap(); }; msg diff --git a/lib/compiler-singlepass/src/codegen.rs b/lib/compiler-singlepass/src/codegen.rs index 2a38184ee90..563021318f1 100644 --- a/lib/compiler-singlepass/src/codegen.rs +++ b/lib/compiler-singlepass/src/codegen.rs @@ -3617,6 +3617,7 @@ impl<'a, M: Machine> FuncGen<'a, M> { self.machine.emit_function_epilog()?; // Make a copy of the return value in XMM0, as required by the SysV CC. + #[allow(clippy::collapsible_if, reason = "hard to read otherwise")] if let Ok(&return_type) = self.signature.results().iter().exactly_one() { if return_type == Type::F32 || return_type == Type::F64 { self.machine.emit_function_return_float()?; diff --git a/lib/types/src/progress.rs b/lib/types/src/progress.rs index 6e7868594d0..11ab35b312d 100644 --- a/lib/types/src/progress.rs +++ b/lib/types/src/progress.rs @@ -4,7 +4,7 @@ use crate::lib::std::{borrow::Cow, fmt, string::String, sync::Arc}; /// Indicates the current compilation progress. // NOTE: fields are kept private on purpose to enable forwards compatibility and future extension. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct CompilationProgress { phase_name: Option>, phase_step_count: Option, @@ -41,16 +41,6 @@ impl CompilationProgress { } } -impl Default for CompilationProgress { - fn default() -> Self { - Self { - phase_name: None, - phase_step_count: None, - phase_step: None, - } - } -} - /// Error returned when the user requests to abort compilation. #[derive(Clone, Debug)] pub struct UserAbort { From 3aab1ac8261c0e7eb44211794474151b514e58ff Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 20:52:14 +0100 Subject: [PATCH 05/14] Rename CompileError::User to CompileError::Aborted Makes more sense. --- lib/types/src/error.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/types/src/error.rs b/lib/types/src/error.rs index 9d6b55b1118..a70a4d0fa28 100644 --- a/lib/types/src/error.rs +++ b/lib/types/src/error.rs @@ -178,9 +178,10 @@ pub enum CompileError { /// Middleware error occurred. #[cfg_attr(feature = "std", error("Middleware error: {0}"))] MiddlewareError(String), + /// Compilation aborted by a user callback. #[cfg_attr(feature = "std", error("Compilation aborted: {0}"))] - User(UserAbort), + Aborted(UserAbort), } impl From for CompileError { @@ -191,7 +192,7 @@ impl From for CompileError { impl From for CompileError { fn from(abort: UserAbort) -> Self { - Self::User(abort) + Self::Aborted(abort) } } From 965e038b6e742d9b6de60ad970fe5886f86ce35d Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 20:51:27 +0100 Subject: [PATCH 06/14] Refactor Module::new_with_progress to ProgressEngineExt::new_module_with_progress To keep the main API clean. --- lib/api/src/backend/sys/entities/engine.rs | 4 ++- lib/api/src/entities/engine/mod.rs | 3 +++ lib/api/src/entities/engine/progress_ext.rs | 27 +++++++++++++++++++++ lib/api/src/entities/module/mod.rs | 9 ------- lib/wasix/src/runtime/mod.rs | 6 +++-- 5 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 lib/api/src/entities/engine/progress_ext.rs diff --git a/lib/api/src/backend/sys/entities/engine.rs b/lib/api/src/backend/sys/entities/engine.rs index 53648e7049a..e21127e2d25 100644 --- a/lib/api/src/backend/sys/entities/engine.rs +++ b/lib/api/src/backend/sys/entities/engine.rs @@ -4,7 +4,9 @@ use std::{path::Path, sync::Arc}; use shared_buffer::OwnedBuffer; pub use wasmer_compiler::{Artifact, BaseTunables, Engine, EngineBuilder, Tunables}; -use wasmer_types::{DeserializeError, Features, HashAlgorithm, target::Target}; +use wasmer_types::{ + CompilationProgressCallback, DeserializeError, Features, HashAlgorithm, target::Target, +}; use crate::{BackendEngine, BackendModule}; diff --git a/lib/api/src/entities/engine/mod.rs b/lib/api/src/entities/engine/mod.rs index 1537507a9de..3c5e6cbdeb2 100644 --- a/lib/api/src/entities/engine/mod.rs +++ b/lib/api/src/entities/engine/mod.rs @@ -18,6 +18,9 @@ use crate::{BackendKind, IntoBytes, Store}; /// Create temporary handles to engines. mod engine_ref; +mod progress_ext; +pub use progress_ext::ProgressEngineExt; + /// The actual (private) definition of the engines. mod inner; pub(crate) use inner::BackendEngine; diff --git a/lib/api/src/entities/engine/progress_ext.rs b/lib/api/src/entities/engine/progress_ext.rs new file mode 100644 index 00000000000..8477457c52e --- /dev/null +++ b/lib/api/src/entities/engine/progress_ext.rs @@ -0,0 +1,27 @@ +use wasmer_types::CompilationProgressCallback; + +/// Provides progress-related extensions to the `Engine` trait. +pub trait ProgressEngineExt { + /// Compile a module from bytes with a progress callback. + /// + /// The callback is invoked with progress updates during the compilation process. + /// The callback also may return an error to abort the compilation. + /// + /// See [`CompilationProgressCallback::new`]. + fn new_module_with_progress( + &self, + bytes: &[u8], + on_progress: CompilationProgressCallback, + ) -> Result; +} + +impl ProgressEngineExt for crate::Engine { + /// See [`NativeEngineExt::new_module_with_progress`]. + fn new_module_with_progress( + &self, + bytes: &[u8], + on_progress: CompilationProgressCallback, + ) -> Result { + crate::BackendModule::new_with_progress(self, bytes, on_progress).map(crate::Module) + } +} diff --git a/lib/api/src/entities/module/mod.rs b/lib/api/src/entities/module/mod.rs index 4f9040737c0..10d541dfa1b 100644 --- a/lib/api/src/entities/module/mod.rs +++ b/lib/api/src/entities/module/mod.rs @@ -113,15 +113,6 @@ impl Module { BackendModule::new(engine, bytes).map(Self) } - /// Creates a new WebAssembly Module and reports compilation progress through `callback`. - pub fn new_with_progress( - engine: &impl AsEngineRef, - bytes: impl AsRef<[u8]>, - on_progress: CompilationProgressCallback, - ) -> Result { - BackendModule::new_with_progress(engine, bytes, on_progress).map(Self) - } - /// Creates a new WebAssembly module from a file path. pub fn from_file( engine: &impl AsEngineRef, diff --git a/lib/wasix/src/runtime/mod.rs b/lib/wasix/src/runtime/mod.rs index 106b4562e78..8b92eb5085d 100644 --- a/lib/wasix/src/runtime/mod.rs +++ b/lib/wasix/src/runtime/mod.rs @@ -22,7 +22,7 @@ use std::{ use futures::future::BoxFuture; use virtual_mio::block_on; use virtual_net::{DynVirtualNetworking, VirtualNetworking}; -use wasmer::{CompileError, Engine, Module, RuntimeError}; +use wasmer::{CompileError, Engine, Module, ProgressEngineExt as _, RuntimeError}; use wasmer_wasix_types::wasi::ExitCode; #[cfg(feature = "journal")] @@ -325,6 +325,8 @@ pub async fn load_module( on_progress: Option, ) -> Result { let wasm_hash = input.hash(); + + #[cfg(feature = "sys")] let result = if let Some(on_progress) = &on_progress { module_cache .load_with_progress(wasm_hash, engine, on_progress.clone()) @@ -349,7 +351,7 @@ pub async fn load_module( let p = CompilationProgressCallback::new(move |p| { progress.notify(ModuleLoadProgress::CompilingModule(p)) }); - Module::new_with_progress(&engine, input.wasm(), p) + engine.new_module_with_progress(input.wasm(), p) } else { Module::new(&engine, input.wasm()) }; From a8619853c6d4bd3e1b2e1bac58e9b58df0837ef4 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 20:52:38 +0100 Subject: [PATCH 07/14] tests: Add module compile progress/abort tests --- lib/api/tests/module_compilation_progress.rs | 91 ++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 lib/api/tests/module_compilation_progress.rs diff --git a/lib/api/tests/module_compilation_progress.rs b/lib/api/tests/module_compilation_progress.rs new file mode 100644 index 00000000000..95b5b349bcc --- /dev/null +++ b/lib/api/tests/module_compilation_progress.rs @@ -0,0 +1,91 @@ +#![cfg(any(feature = "cranelift", feature = "llvm"))] + +use std::sync::{Arc, Mutex}; + +use wasmer::{CompileError, Engine, ProgressEngineExt as _}; +use wasmer_types::{CompilationProgress, UserAbort}; + +#[cfg(feature = "cranelift")] +#[test] +fn test_module_compilation_progress_cranelift() { + let compiler = wasmer::sys::Cranelift::default(); + let engine: wasmer::Engine = wasmer::sys::EngineBuilder::new(compiler).engine().into(); + test_module_compilation_progress(engine); +} + +#[cfg(feature = "cranelift")] +#[test] +fn test_module_compilation_abort_cranelift() { + let compiler = wasmer::sys::Cranelift::default(); + let engine: wasmer::Engine = wasmer::sys::EngineBuilder::new(compiler).engine().into(); + test_module_compilation_abort(engine); +} + +#[cfg(feature = "llvm")] +#[test] +fn test_module_compilation_progress_llvm() { + let compiler = wasmer::sys::LLVM::default(); + let engine: wasmer::Engine = wasmer::sys::EngineBuilder::new(compiler).engine().into(); + test_module_compilation_progress(engine); +} + +#[cfg(feature = "llvm")] +#[test] +fn test_module_compilation_abort_cranelift() { + let compiler = wasmer::sys::LLVM::default(); + let engine: wasmer::Engine = wasmer::sys::EngineBuilder::new(compiler).engine().into(); + test_module_compilation_abort(engine); +} + +const SIMPLE_WAT: &str = r#"(module + (func (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + (func (export "sub") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub) +) "#; + +fn test_module_compilation_progress(engine: Engine) { + let items = Arc::new(Mutex::new(Vec::::new())); + + let cb = wasmer_types::CompilationProgressCallback::new({ + let items = items.clone(); + move |p| { + items.lock().unwrap().push(p); + Ok(()) + } + }); + let _module = engine + .new_module_with_progress(SIMPLE_WAT.as_bytes(), cb) + .unwrap(); + + let last = items + .lock() + .unwrap() + .last() + .expect("expected at least one progress item") + .clone(); + + assert_eq!(last.phase_step_count(), Some(2)); + assert_eq!(last.phase_step(), Some(2)); +} + +fn test_module_compilation_abort(engine: Engine) { + let reason = "my reason"; + let cb = wasmer_types::CompilationProgressCallback::new(move |_p| Err(UserAbort::new(reason))); + let err = engine + .new_module_with_progress(SIMPLE_WAT.as_bytes(), cb) + .expect_err("should fail"); + + match err { + CompileError::Aborted(e) => { + assert_eq!(e.reason(), reason) + } + other => { + panic!("expected CompileError::Aborted, got {:?}", other); + } + } +} From 5fe730dc3041ae2a5ef5778119814ba048697d13 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 21:38:57 +0100 Subject: [PATCH 08/14] chore(cli): Improve module compilation progress status --- lib/cli/src/commands/run/runtime.rs | 33 ++++++++++++++++++++++++----- lib/types/src/module_hash.rs | 17 +++++++++++++++ 2 files changed, 45 insertions(+), 5 deletions(-) diff --git a/lib/cli/src/commands/run/runtime.rs b/lib/cli/src/commands/run/runtime.rs index 38791113d7e..d97d1e05913 100644 --- a/lib/cli/src/commands/run/runtime.rs +++ b/lib/cli/src/commands/run/runtime.rs @@ -117,12 +117,19 @@ impl wasmer_wasix::Runtime for Monitorin engine: Option<&Engine>, on_progress: Option, ) -> BoxFuture<'a, Result> { + // If a progress reporter is already provided, or quiet mode is enabled, + // just delegate to the inner runtime. if on_progress.is_some() || self.quiet_mode { return self.runtime.resolve_module(input, engine, on_progress); } + // Compile with progress monitoring through the progress bar. + use std::fmt::Write as _; + let short_hash = input.hash().short_hash(); + let progress_msg = format!("Compiling module ({short_hash})"); + let pb = self.progress.clone(); let on_progress = Some(ModuleLoadProgressReporter::new(move |prog| { let msg = match prog { @@ -132,11 +139,11 @@ impl wasmer_wasix::Runtime for Monitorin pb.set_position(step); }; - let mut msg = if let Some(phase) = c.phase_name() { - format!("Compiling module: {phase}") - } else { - "Compiling module".to_string() - }; + let mut msg = format!("Compiling module ({short_hash})"); + if let Some(phase) = c.phase_name() { + // Note: writing to strings can not fail. + write!(msg, " - {phase}").unwrap(); + } if let (Some(step), Some(step_count)) = (c.phase_step(), c.phase_step_count()) { pb.set_length(step_count); @@ -144,6 +151,7 @@ impl wasmer_wasix::Runtime for Monitorin // Note: writing to strings can not fail. write!(msg, " ({step}/{step_count})").unwrap(); }; + pb.tick(); msg } @@ -156,12 +164,27 @@ impl wasmer_wasix::Runtime for Monitorin let engine = engine.cloned(); + let style = indicatif::ProgressStyle::default_bar() + .template("{spinner} {wide_bar:.cyan/blue} {msg}") + .expect("invalid progress bar template"); + self.progress.set_style(style); + + self.progress.reset(); + self.progress.set_message(progress_msg); + let f = async move { let res = self .runtime .resolve_module(input, engine.as_ref(), on_progress) .await; + + // Hide the progress bar and reset it to the default spinner style. + // Needed because future module downloads should not show a bar. + self.progress + .set_style(indicatif::ProgressStyle::default_spinner()); + self.progress.reset(); self.progress.finish_and_clear(); + res }; diff --git a/lib/types/src/module_hash.rs b/lib/types/src/module_hash.rs index 7d2538c841f..f831e3d17b1 100644 --- a/lib/types/src/module_hash.rs +++ b/lib/types/src/module_hash.rs @@ -105,6 +105,23 @@ impl ModuleHash { Self::Sha256(bytes) => bytes.as_slice(), } } + + /// Build a short hex representation of the hash (first 4 bytes). + pub fn short_hash(&self) -> String { + use std::fmt::Write as _; + + let bytes = match self { + Self::XXHash(bytes) => &bytes[0..4], + Self::Sha256(bytes) => &bytes[0..4], + }; + + let mut s = String::new(); + for byte in bytes { + write!(s, "{byte:02x}").unwrap(); + } + + s + } } impl Display for ModuleHash { From a9a070bf0855b63a0e71752da387f9983a4c7ad8 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 22:19:14 +0100 Subject: [PATCH 09/14] Lift ProgressContext to wasmer-compiler crate To enable reuse. --- lib/compiler-cranelift/src/compiler.rs | 46 ++--------------------- lib/compiler-llvm/src/compiler.rs | 43 ++------------------- lib/compiler/src/lib.rs | 1 + lib/compiler/src/progress.rs | 52 ++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 81 deletions(-) create mode 100644 lib/compiler/src/progress.rs diff --git a/lib/compiler-cranelift/src/compiler.rs b/lib/compiler-cranelift/src/compiler.rs index 5ceedf80804..577048fe4c4 100644 --- a/lib/compiler-cranelift/src/compiler.rs +++ b/lib/compiler-cranelift/src/compiler.rs @@ -27,14 +27,9 @@ use gimli::write::{Address, EhFrame, FrameTable, Writer}; #[cfg(feature = "rayon")] use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; -use std::{ - borrow::Cow, - sync::{ - Arc, - atomic::{AtomicU64, Ordering}, - }, -}; +use std::sync::Arc; +use wasmer_compiler::progress::ProgressContext; #[cfg(feature = "unwind")] use wasmer_compiler::types::{section::SectionIndex, unwind::CompiledFunctionUnwindInfo}; use wasmer_compiler::{ @@ -55,8 +50,8 @@ use wasmer_types::entity::PrimaryMap; use wasmer_types::target::CallingConvention; use wasmer_types::target::Target; use wasmer_types::{ - CompilationProgress, CompilationProgressCallback, CompileError, FunctionIndex, - LocalFunctionIndex, ModuleInfo, SignatureIndex, TrapCode, TrapInformation, + CompilationProgressCallback, CompileError, FunctionIndex, LocalFunctionIndex, ModuleInfo, + SignatureIndex, TrapCode, TrapInformation, }; /// A compiler that compiles a WebAssembly module with Cranelift, translating the Wasm to Cranelift IR, @@ -588,39 +583,6 @@ fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation { } } -#[derive(Clone)] -struct ProgressContext { - callback: CompilationProgressCallback, - counter: Arc, - total: u64, - phase_name: &'static str, -} - -impl ProgressContext { - fn new(callback: CompilationProgressCallback, total: u64, phase_name: &'static str) -> Self { - Self { - callback, - counter: Arc::new(AtomicU64::new(0)), - total, - phase_name, - } - } - - fn notify(&self) -> Result<(), CompileError> { - if self.total == 0 { - return Ok(()); - } - let step = self.counter.fetch_add(1, Ordering::SeqCst) + 1; - self.callback - .notify(CompilationProgress::new( - Some(Cow::Borrowed(self.phase_name)), - Some(self.total), - Some(step), - )) - .map_err(CompileError::from) - } -} - /// Translates the Cranelift IR TrapCode into generic Trap Code fn translate_ir_trapcode(trap: ir::TrapCode) -> TrapCode { if trap == ir::TrapCode::STACK_OVERFLOW { diff --git a/lib/compiler-llvm/src/compiler.rs b/lib/compiler-llvm/src/compiler.rs index d90ef4879df..4b77206f956 100644 --- a/lib/compiler-llvm/src/compiler.rs +++ b/lib/compiler-llvm/src/compiler.rs @@ -12,12 +12,10 @@ use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIter use std::{ borrow::Cow, collections::{HashMap, HashSet}, - sync::{ - Arc, - atomic::{AtomicU64, Ordering}, - }, + sync::Arc, }; use wasmer_compiler::misc::CompiledKind; +use wasmer_compiler::progress::ProgressContext; use wasmer_compiler::types::function::{Compilation, UnwindInfo}; use wasmer_compiler::types::module::CompileModuleInfo; use wasmer_compiler::types::relocation::RelocationKind; @@ -32,8 +30,8 @@ use wasmer_compiler::{ use wasmer_types::entity::{EntityRef, PrimaryMap}; use wasmer_types::target::Target; use wasmer_types::{ - CompilationProgress, CompilationProgressCallback, CompileError, FunctionIndex, - LocalFunctionIndex, ModuleInfo, SignatureIndex, + CompilationProgressCallback, CompileError, FunctionIndex, LocalFunctionIndex, ModuleInfo, + SignatureIndex, }; use wasmer_vm::LibCall; @@ -170,39 +168,6 @@ impl SymbolRegistry for ModuleBasedSymbolRegistry { } } -#[derive(Clone)] -struct ProgressContext { - callback: CompilationProgressCallback, - counter: Arc, - total: u64, - phase_name: &'static str, -} - -impl ProgressContext { - fn new(callback: CompilationProgressCallback, total: u64, phase_name: &'static str) -> Self { - Self { - callback, - counter: Arc::new(AtomicU64::new(0)), - total, - phase_name, - } - } - - fn notify(&self) -> Result<(), CompileError> { - if self.total == 0 { - return Ok(()); - } - let step = self.counter.fetch_add(1, Ordering::SeqCst) + 1; - self.callback - .notify(CompilationProgress::new( - Some(Cow::Borrowed(self.phase_name)), - Some(self.total), - Some(step), - )) - .map_err(CompileError::from) - } -} - impl LLVMCompiler { #[allow(clippy::too_many_arguments)] fn compile_native_object( diff --git a/lib/compiler/src/lib.rs b/lib/compiler/src/lib.rs index c01a61a5632..3965d06c257 100644 --- a/lib/compiler/src/lib.rs +++ b/lib/compiler/src/lib.rs @@ -51,6 +51,7 @@ mod traits; pub mod misc; pub mod object; +pub mod progress; pub mod serialize; pub mod types; diff --git a/lib/compiler/src/progress.rs b/lib/compiler/src/progress.rs new file mode 100644 index 00000000000..123771af73f --- /dev/null +++ b/lib/compiler/src/progress.rs @@ -0,0 +1,52 @@ +//! Shared helpers for reporting compilation progress across the different backends. + +use crate::lib::std::{ + borrow::Cow, + sync::{ + Arc, + atomic::{AtomicU64, Ordering}, + }, +}; +use wasmer_types::{CompilationProgress, CompilationProgressCallback, CompileError}; + +/// Tracks progress within a compilation phase and forwards updates to a callback. +/// +/// Convenience wrapper around a [`CompilationProgressCallback`] for the compilers. +#[derive(Clone)] +pub struct ProgressContext { + callback: CompilationProgressCallback, + counter: Arc, + total: u64, + phase_name: &'static str, +} + +impl ProgressContext { + /// Creates a new [`ProgressContext`] for the given phase. + pub fn new( + callback: CompilationProgressCallback, + total: u64, + phase_name: &'static str, + ) -> Self { + Self { + callback, + counter: Arc::new(AtomicU64::new(0)), + total, + phase_name, + } + } + + /// Notifies the callback that the next step in the phase has completed. + pub fn notify(&self) -> Result<(), CompileError> { + if self.total == 0 { + return Ok(()); + } + let step = self.counter.fetch_add(1, Ordering::SeqCst) + 1; + self.callback + .notify(CompilationProgress::new( + Some(Cow::Borrowed(self.phase_name)), + Some(self.total), + Some(step), + )) + .map_err(CompileError::from) + } +} From 9b1f8a92bf09ab7681ef2569b2cf59cee058eaa8 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 22:19:32 +0100 Subject: [PATCH 10/14] Extend singlepass compiler with progress reporting. --- lib/compiler-singlepass/src/compiler.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 7f7f1a65e8f..9882aee314c 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -20,6 +20,7 @@ use gimli::write::{EhFrame, FrameTable, Writer}; #[cfg(feature = "rayon")] use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use std::sync::Arc; +use wasmer_compiler::progress::ProgressContext; use wasmer_compiler::{ Compiler, CompilerConfig, FunctionBinaryReader, FunctionBodyData, MiddlewareBinaryReader, ModuleMiddleware, ModuleMiddlewareChain, ModuleTranslationState, @@ -77,7 +78,7 @@ impl Compiler for SinglepassCompiler { compile_info: &CompileModuleInfo, _module_translation: &ModuleTranslationState, function_body_inputs: PrimaryMap>, - _on_progress: Option<&CompilationProgressCallback>, + progress_callback: Option<&CompilationProgressCallback>, ) -> Result { match target.triple().architecture { Architecture::X86_64 => {} @@ -100,6 +101,11 @@ impl Compiler for SinglepassCompiler { } }; + let total_functions = function_body_inputs.len() as u64; + let progress = progress_callback + .cloned() + .map(|cb| ProgressContext::new(cb, total_functions, "singlepass::functions")); + // Generate the frametable #[cfg(feature = "unwind")] let dwarf_frametable = if function_body_inputs.is_empty() { @@ -168,7 +174,7 @@ impl Compiler for SinglepassCompiler { } } - match target.triple().architecture { + let result = match target.triple().architecture { Architecture::X86_64 => { let machine = MachineX86_64::new(Some(target.clone()))?; let mut generator = FuncGen::new( @@ -212,7 +218,13 @@ impl Compiler for SinglepassCompiler { generator.finalize(input) } _ => unimplemented!(), + }?; + + if let Some(progress) = progress.as_ref() { + progress.notify()?; } + + Ok(result) }) .collect::, CompileError>>()? .into_iter() From 26507da56d5f3f77cc1c1a2634c849654b219e86 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 22:29:37 +0100 Subject: [PATCH 11/14] Include trampolines in compilation progress. --- lib/api/tests/module_compilation_progress.rs | 9 +++-- lib/compiler-cranelift/src/compiler.rs | 38 ++++++++++++++++---- lib/compiler-llvm/src/compiler.rs | 33 ++++++++++++++--- lib/compiler-singlepass/src/compiler.rs | 22 +++++++++--- 4 files changed, 84 insertions(+), 18 deletions(-) diff --git a/lib/api/tests/module_compilation_progress.rs b/lib/api/tests/module_compilation_progress.rs index 95b5b349bcc..de328633d91 100644 --- a/lib/api/tests/module_compilation_progress.rs +++ b/lib/api/tests/module_compilation_progress.rs @@ -38,6 +38,7 @@ fn test_module_compilation_abort_cranelift() { } const SIMPLE_WAT: &str = r#"(module + (import "env" "div" (func $div (param i32 i32) (result i32))) (func (export "add") (param i32 i32) (result i32) local.get 0 local.get 1 @@ -69,8 +70,12 @@ fn test_module_compilation_progress(engine: Engine) { .expect("expected at least one progress item") .clone(); - assert_eq!(last.phase_step_count(), Some(2)); - assert_eq!(last.phase_step(), Some(2)); + // 4 total steps: + // - 2 functions + // - 1 trampoline for exports (both share same signature) + // - 1 trampoline for imported function + assert_eq!(last.phase_step_count(), Some(4)); + assert_eq!(last.phase_step(), Some(4)); } fn test_module_compilation_abort(engine: Engine) { diff --git a/lib/compiler-cranelift/src/compiler.rs b/lib/compiler-cranelift/src/compiler.rs index 577048fe4c4..515e6adf8b0 100644 --- a/lib/compiler-cranelift/src/compiler.rs +++ b/lib/compiler-cranelift/src/compiler.rs @@ -97,9 +97,13 @@ impl CraneliftCompiler { .collect::>(); let total_functions = function_body_inputs.len() as u64; + let total_function_call_trampolines = module.signatures.len() as u64; + let total_dynamic_trampolines = module.num_imported_functions as u64; + let total_steps = + total_functions + total_function_call_trampolines + total_dynamic_trampolines; let progress = progress_callback .cloned() - .map(|cb| ProgressContext::new(cb, total_functions, "cranelift::functions")); + .map(|cb| ProgressContext::new(cb, total_steps, "cranelift::functions")); // Generate the frametable #[cfg(feature = "unwind")] @@ -412,7 +416,14 @@ impl CraneliftCompiler { .values() .collect::>() .into_iter() - .map(|sig| make_trampoline_function_call(&self.config().callbacks, &*isa, &mut cx, sig)) + .map(|sig| { + let trampoline = + make_trampoline_function_call(&self.config().callbacks, &*isa, &mut cx, sig)?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) + }) .collect::, CompileError>>()? .into_iter() .collect::>(); @@ -423,7 +434,12 @@ impl CraneliftCompiler { .collect::>() .par_iter() .map_init(FunctionBuilderContext::new, |cx, sig| { - make_trampoline_function_call(&self.config().callbacks, &*isa, cx, sig) + let trampoline = + make_trampoline_function_call(&self.config().callbacks, &*isa, cx, sig)?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) }) .collect::, CompileError>>()? .into_iter() @@ -440,13 +456,17 @@ impl CraneliftCompiler { .collect::>() .into_iter() .map(|func_type| { - make_trampoline_dynamic_function( + let trampoline = make_trampoline_dynamic_function( &self.config().callbacks, &*isa, &offsets, &mut cx, &func_type, - ) + )?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) }) .collect::, CompileError>>()? .into_iter() @@ -457,13 +477,17 @@ impl CraneliftCompiler { .collect::>() .par_iter() .map_init(FunctionBuilderContext::new, |cx, func_type| { - make_trampoline_dynamic_function( + let trampoline = make_trampoline_dynamic_function( &self.config().callbacks, &*isa, &offsets, cx, func_type, - ) + )?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) }) .collect::, CompileError>>()? .into_iter() diff --git a/lib/compiler-llvm/src/compiler.rs b/lib/compiler-llvm/src/compiler.rs index 4b77206f956..8d676d94d45 100644 --- a/lib/compiler-llvm/src/compiler.rs +++ b/lib/compiler-llvm/src/compiler.rs @@ -368,9 +368,13 @@ impl Compiler for LLVMCompiler { let module = &compile_info.module; let total_functions = function_body_inputs.len() as u64; + let total_function_call_trampolines = module.signatures.len() as u64; + let total_dynamic_trampolines = module.num_imported_functions as u64; + let total_steps = + total_functions + total_function_call_trampolines + total_dynamic_trampolines; let progress = progress_callback .cloned() - .map(|cb| ProgressContext::new(cb, total_functions, "Compiling functions")); + .map(|cb| ProgressContext::new(cb, total_steps, "Compiling functions")); // TODO: merge constants in sections. @@ -542,6 +546,7 @@ impl Compiler for LLVMCompiler { .collect::>(); let function_call_trampolines = if self.config.num_threads.get() > 1 { + let progress = progress.clone(); let pool = ThreadPoolBuilder::new() .num_threads(self.config.num_threads.get()) .build() @@ -558,7 +563,12 @@ impl Compiler for LLVMCompiler { FuncTrampoline::new(target_machine, binary_format).unwrap() }, |func_trampoline, sig| { - func_trampoline.trampoline(sig, self.config(), "", compile_info) + let trampoline = + func_trampoline.trampoline(sig, self.config(), "", compile_info)?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) }, ) .collect::>() @@ -566,6 +576,7 @@ impl Compiler for LLVMCompiler { .collect::, CompileError>>() })? } else { + let progress = progress.clone(); let target_machine = self.config().target_machine(target); let func_trampoline = FuncTrampoline::new(target_machine, binary_format).unwrap(); module @@ -573,7 +584,14 @@ impl Compiler for LLVMCompiler { .values() .collect::>() .into_iter() - .map(|sig| func_trampoline.trampoline(sig, self.config(), "", compile_info)) + .map(|sig| { + let trampoline = + func_trampoline.trampoline(sig, self.config(), "", compile_info)?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) + }) .collect::>() .into_iter() .collect::, CompileError>>()? @@ -584,6 +602,7 @@ impl Compiler for LLVMCompiler { // We can move that logic out and re-enable parallel processing. Hopefully, there aren't // enough dynamic trampolines to actually cause a noticeable performance degradation. let dynamic_function_trampolines = { + let progress = progress.clone(); let target_machine = self.config().target_machine(target); let func_trampoline = FuncTrampoline::new(target_machine, binary_format).unwrap(); module @@ -592,7 +611,7 @@ impl Compiler for LLVMCompiler { .into_iter() .enumerate() .map(|(index, func_type)| { - func_trampoline.dynamic_trampoline( + let trampoline = func_trampoline.dynamic_trampoline( &func_type, self.config(), "", @@ -602,7 +621,11 @@ impl Compiler for LLVMCompiler { &mut eh_frame_section_relocations, &mut compact_unwind_section_bytes, &mut compact_unwind_section_relocations, - ) + )?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) }) .collect::>() .into_iter() diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 9882aee314c..73d0ab5b15d 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -102,9 +102,13 @@ impl Compiler for SinglepassCompiler { }; let total_functions = function_body_inputs.len() as u64; + let total_function_call_trampolines = module.signatures.len() as u64; + let total_dynamic_trampolines = module.num_imported_functions as u64; + let total_steps = + total_functions + total_function_call_trampolines + total_dynamic_trampolines; let progress = progress_callback .cloned() - .map(|cb| ProgressContext::new(cb, total_functions, "singlepass::functions")); + .map(|cb| ProgressContext::new(cb, total_steps, "singlepass::functions")); // Generate the frametable #[cfg(feature = "unwind")] @@ -235,7 +239,13 @@ impl Compiler for SinglepassCompiler { .values() .collect::>() .into_par_iter_if_rayon() - .map(|func_type| gen_std_trampoline(func_type, target, calling_convention)) + .map(|func_type| { + let trampoline = gen_std_trampoline(func_type, target, calling_convention)?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) + }) .collect::, _>>()? .into_iter() .collect::>(); @@ -245,12 +255,16 @@ impl Compiler for SinglepassCompiler { .collect::>() .into_par_iter_if_rayon() .map(|func_type| { - gen_std_dynamic_import_trampoline( + let trampoline = gen_std_dynamic_import_trampoline( &vmoffsets, &func_type, target, calling_convention, - ) + )?; + if let Some(progress) = progress.as_ref() { + progress.notify()?; + } + Ok(trampoline) }) .collect::, _>>()? .into_iter() From 327b3b91254a5669562b166dddf1e5265b4cfde5 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 22:36:07 +0100 Subject: [PATCH 12/14] Add singlepass progress tests --- lib/api/tests/module_compilation_progress.rs | 20 ++++++++++++++++++-- lib/compiler-singlepass/src/compiler.rs | 9 +++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/lib/api/tests/module_compilation_progress.rs b/lib/api/tests/module_compilation_progress.rs index de328633d91..d62054f6c5b 100644 --- a/lib/api/tests/module_compilation_progress.rs +++ b/lib/api/tests/module_compilation_progress.rs @@ -1,10 +1,26 @@ -#![cfg(any(feature = "cranelift", feature = "llvm"))] +#![cfg(any(feature = "cranelift", feature = "llvm", feature = "singlepass"))] use std::sync::{Arc, Mutex}; use wasmer::{CompileError, Engine, ProgressEngineExt as _}; use wasmer_types::{CompilationProgress, UserAbort}; +#[cfg(feature = "singlepass")] +#[test] +fn test_module_compilation_progress_singlepass() { + let compiler = wasmer::sys::Singlepass::default(); + let engine: wasmer::Engine = wasmer::sys::EngineBuilder::new(compiler).engine().into(); + test_module_compilation_progress(engine); +} + +#[cfg(feature = "singlepass")] +#[test] +fn test_module_compilation_abort_singlepass() { + let compiler = wasmer::sys::Cranelift::default(); + let engine: wasmer::Engine = wasmer::sys::EngineBuilder::new(compiler).engine().into(); + test_module_compilation_abort(engine); +} + #[cfg(feature = "cranelift")] #[test] fn test_module_compilation_progress_cranelift() { @@ -31,7 +47,7 @@ fn test_module_compilation_progress_llvm() { #[cfg(feature = "llvm")] #[test] -fn test_module_compilation_abort_cranelift() { +fn test_module_compilation_abort_llvm() { let compiler = wasmer::sys::LLVM::default(); let engine: wasmer::Engine = wasmer::sys::EngineBuilder::new(compiler).engine().into(); test_module_compilation_abort(engine); diff --git a/lib/compiler-singlepass/src/compiler.rs b/lib/compiler-singlepass/src/compiler.rs index 73d0ab5b15d..25580638ed4 100644 --- a/lib/compiler-singlepass/src/compiler.rs +++ b/lib/compiler-singlepass/src/compiler.rs @@ -101,6 +101,7 @@ impl Compiler for SinglepassCompiler { } }; + let module = &compile_info.module; let total_functions = function_body_inputs.len() as u64; let total_function_call_trampolines = module.signatures.len() as u64; let total_dynamic_trampolines = module.num_imported_functions as u64; @@ -239,7 +240,7 @@ impl Compiler for SinglepassCompiler { .values() .collect::>() .into_par_iter_if_rayon() - .map(|func_type| { + .map(|func_type| -> Result<_, CompileError> { let trampoline = gen_std_trampoline(func_type, target, calling_convention)?; if let Some(progress) = progress.as_ref() { progress.notify()?; @@ -254,7 +255,7 @@ impl Compiler for SinglepassCompiler { .imported_function_types() .collect::>() .into_par_iter_if_rayon() - .map(|func_type| { + .map(|func_type| -> Result<_, CompileError> { let trampoline = gen_std_dynamic_import_trampoline( &vmoffsets, &func_type, @@ -360,7 +361,7 @@ mod tests { // Compile for 32bit Linux let linux32 = Target::new(triple!("i686-unknown-linux-gnu"), CpuFeature::for_host()); let (info, translation, inputs) = dummy_compilation_ingredients(); - let result = compiler.compile_module(&linux32, &info, &translation, inputs); + let result = compiler.compile_module(&linux32, &info, &translation, inputs, None); match result.unwrap_err() { CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"), error => panic!("Unexpected error: {error:?}"), @@ -369,7 +370,7 @@ mod tests { // Compile for win32 let win32 = Target::new(triple!("i686-pc-windows-gnu"), CpuFeature::for_host()); let (info, translation, inputs) = dummy_compilation_ingredients(); - let result = compiler.compile_module(&win32, &info, &translation, inputs); + let result = compiler.compile_module(&win32, &info, &translation, inputs, None); match result.unwrap_err() { CompileError::UnsupportedTarget(name) => assert_eq!(name, "i686"), // Windows should be checked before architecture error => panic!("Unexpected error: {error:?}"), From 457126286a9eb89b53bf1975db698a3835519d36 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Tue, 18 Nov 2025 22:46:52 +0100 Subject: [PATCH 13/14] chore: Docs improvements and small clippy fixes --- lib/api/src/backend/sys/entities/module.rs | 10 +--- lib/api/src/entities/engine/progress_ext.rs | 12 +++- lib/cli/src/commands/run/runtime.rs | 60 ++++++++++--------- lib/types/src/progress.rs | 13 +++- lib/wasix/src/runtime/mod.rs | 14 ++++- .../src/runtime/module_cache/progress.rs | 33 +++++++++- 6 files changed, 95 insertions(+), 47 deletions(-) diff --git a/lib/api/src/backend/sys/entities/module.rs b/lib/api/src/backend/sys/entities/module.rs index 46154ef5bfb..ea8c1e4a095 100644 --- a/lib/api/src/backend/sys/entities/module.rs +++ b/lib/api/src/backend/sys/entities/module.rs @@ -58,15 +58,7 @@ impl Module { engine: &impl AsEngineRef, binary: &[u8], ) -> Result { - #[cfg(feature = "compiler")] - { - Self::compile(engine, binary) - } - - #[cfg(not(feature = "compiler"))] - { - Self::compile(engine, binary) - } + Self::compile(engine, binary) } pub(crate) unsafe fn from_binary_unchecked_with_progress( diff --git a/lib/api/src/entities/engine/progress_ext.rs b/lib/api/src/entities/engine/progress_ext.rs index 8477457c52e..c01f51b829e 100644 --- a/lib/api/src/entities/engine/progress_ext.rs +++ b/lib/api/src/entities/engine/progress_ext.rs @@ -7,7 +7,15 @@ pub trait ProgressEngineExt { /// The callback is invoked with progress updates during the compilation process. /// The callback also may return an error to abort the compilation. /// - /// See [`CompilationProgressCallback::new`]. + /// Signature of the callback function: `Fn(CompilationProgress) -> Result<(), UserAbort> + Send + Sync + 'static` + /// + /// # Aborting compilation + /// + /// The callback has to return a `Result<(), UserAbort>`. + /// + /// If the callback returns an error, the compilation will fail with a `CompileError::Aborted`. + /// + /// See [`CompilationProgressCallback::new`] for more details. fn new_module_with_progress( &self, bytes: &[u8], @@ -16,7 +24,7 @@ pub trait ProgressEngineExt { } impl ProgressEngineExt for crate::Engine { - /// See [`NativeEngineExt::new_module_with_progress`]. + /// See [`ProgressEngineExt::new_module_with_progress`]. fn new_module_with_progress( &self, bytes: &[u8], diff --git a/lib/cli/src/commands/run/runtime.rs b/lib/cli/src/commands/run/runtime.rs index d97d1e05913..abd9331f0a0 100644 --- a/lib/cli/src/commands/run/runtime.rs +++ b/lib/cli/src/commands/run/runtime.rs @@ -13,6 +13,7 @@ use wasmer_wasix::{ SpawnError, bin_factory::{BinaryPackage, BinaryPackageCommand}, runtime::{ + ModuleInput, module_cache::{ HashedModuleData, progress::{ModuleLoadProgress, ModuleLoadProgressReporter}, @@ -113,7 +114,7 @@ impl wasmer_wasix::Runtime for Monitorin fn resolve_module<'a>( &'a self, - input: wasmer_wasix::runtime::ModuleInput<'a>, + input: ModuleInput<'a>, engine: Option<&Engine>, on_progress: Option, ) -> BoxFuture<'a, Result> { @@ -128,38 +129,39 @@ impl wasmer_wasix::Runtime for Monitorin use std::fmt::Write as _; let short_hash = input.hash().short_hash(); - let progress_msg = format!("Compiling module ({short_hash})"); + let progress_msg = match &input { + ModuleInput::Bytes(_) | ModuleInput::Hashed(_) => { + format!("Compiling module ({short_hash})") + } + ModuleInput::Command(cmd) => format!("Compiling {}", cmd.name()), + }; let pb = self.progress.clone(); - let on_progress = Some(ModuleLoadProgressReporter::new(move |prog| { - let msg = match prog { - ModuleLoadProgress::CompilingModule(c) => { - if let (Some(step), Some(step_count)) = (c.phase_step(), c.phase_step_count()) { - pb.set_length(step_count); - pb.set_position(step); - }; - - let mut msg = format!("Compiling module ({short_hash})"); - if let Some(phase) = c.phase_name() { - // Note: writing to strings can not fail. - write!(msg, " - {phase}").unwrap(); + + let on_progress = Some(ModuleLoadProgressReporter::new({ + let base_msg = progress_msg.clone(); + move |prog| { + let msg = match prog { + ModuleLoadProgress::CompilingModule(c) => { + let mut msg = base_msg.clone(); + if let (Some(step), Some(step_count)) = + (c.phase_step(), c.phase_step_count()) + { + pb.set_length(step_count); + pb.set_position(step); + // Note: writing to strings can not fail. + write!(msg, " ({step}/{step_count})").unwrap(); + }; + pb.tick(); + + msg } + _ => base_msg.clone(), + }; - if let (Some(step), Some(step_count)) = (c.phase_step(), c.phase_step_count()) { - pb.set_length(step_count); - pb.set_position(step); - // Note: writing to strings can not fail. - write!(msg, " ({step}/{step_count})").unwrap(); - }; - pb.tick(); - - msg - } - _ => "Compiling module...".to_string(), - }; - - pb.set_message(msg); - Ok(()) + pb.set_message(msg); + Ok(()) + } })); let engine = engine.cloned(); diff --git a/lib/types/src/progress.rs b/lib/types/src/progress.rs index 11ab35b312d..dba0a46eaad 100644 --- a/lib/types/src/progress.rs +++ b/lib/types/src/progress.rs @@ -3,7 +3,9 @@ use crate::lib::std::{borrow::Cow, fmt, string::String, sync::Arc}; /// Indicates the current compilation progress. -// NOTE: fields are kept private on purpose to enable forwards compatibility and future extension. +/// +/// All fields are kept private for forwards compatibility and future extension. +/// Use the provided methods to access progress data. #[derive(Clone, Debug, Default)] pub struct CompilationProgress { phase_name: Option>, @@ -41,7 +43,7 @@ impl CompilationProgress { } } -/// Error returned when the user requests to abort compilation. +/// Error returned when the user requests to abort an expensive computation. #[derive(Clone, Debug)] pub struct UserAbort { reason: String, @@ -70,14 +72,19 @@ impl fmt::Display for UserAbort { #[cfg(feature = "std")] impl std::error::Error for UserAbort {} -#[derive(Clone)] /// Wraps a boxed callback that can receive compilation progress notifications. +#[derive(Clone)] pub struct CompilationProgressCallback { callback: Arc Result<(), UserAbort> + Send + Sync + 'static>, } impl CompilationProgressCallback { /// Create a new callback wrapper. + /// + /// The provided callback will be invoked with progress updates during the compilation process, + /// and has to return a `Result<(), UserAbort>`. + /// + /// If the callback returns an error, the compilation will be aborted with a `CompileError::Aborted`. pub fn new(callback: F) -> Self where F: Fn(CompilationProgress) -> Result<(), UserAbort> + Send + Sync + 'static, diff --git a/lib/wasix/src/runtime/mod.rs b/lib/wasix/src/runtime/mod.rs index 8b92eb5085d..69506720793 100644 --- a/lib/wasix/src/runtime/mod.rs +++ b/lib/wasix/src/runtime/mod.rs @@ -54,13 +54,19 @@ pub enum TaintReason { /// /// Exists because the semantics for resolving modules can vary between /// different sources. +/// +/// All variants are wrapped in `Cow` to allow for zero-copy usage when possible. pub enum ModuleInput<'a> { + /// Raw bytes. Bytes(Cow<'a, [u8]>), + /// Pre-hashed module data. Hashed(Cow<'a, HashedModuleData>), + /// A binary package command. Command(Cow<'a, BinaryPackageCommand>), } impl<'a> ModuleInput<'a> { + /// Convert to an owned version of the module input. pub fn to_owned(&'a self) -> ModuleInput<'static> { // The manual code below is needed due to compiler issues with the lifetime. match self { @@ -77,6 +83,9 @@ impl<'a> ModuleInput<'a> { } } + /// Get the module hash. + /// + /// NOTE: may be expensive, depending on the variant. pub fn hash(&self) -> ModuleHash { match self { Self::Bytes(b) => { @@ -88,6 +97,7 @@ impl<'a> ModuleInput<'a> { } } + /// Get the raw WebAssembly bytes. pub fn wasm(&self) -> &[u8] { match self { Self::Bytes(b) => b, @@ -96,6 +106,9 @@ impl<'a> ModuleInput<'a> { } } + /// Convert to a `HashedModuleData`. + /// + /// May involve cloning and hashing. pub fn to_hashed(&self) -> HashedModuleData { match self { Self::Bytes(b) => HashedModuleData::new(b.as_ref()), @@ -326,7 +339,6 @@ pub async fn load_module( ) -> Result { let wasm_hash = input.hash(); - #[cfg(feature = "sys")] let result = if let Some(on_progress) = &on_progress { module_cache .load_with_progress(wasm_hash, engine, on_progress.clone()) diff --git a/lib/wasix/src/runtime/module_cache/progress.rs b/lib/wasix/src/runtime/module_cache/progress.rs index 595395d1af1..499035f295b 100644 --- a/lib/wasix/src/runtime/module_cache/progress.rs +++ b/lib/wasix/src/runtime/module_cache/progress.rs @@ -2,25 +2,37 @@ use std::sync::Arc; use wasmer_types::UserAbort; +/// Progress during module loading. #[derive(Clone, Debug)] #[non_exhaustive] pub enum ModuleLoadProgress { + /// Module was found in local cache. LocalCacheHit, - RemoteArtifact(ModuleLoadProgressRemote), + /// Module is being downloaded. DownloadingModule(DownloadProgress), + /// Module artifact was found in local cache. + ArtifactCacheHit, + /// Module artifact is being loaded from remote source. + RemoteArtifact(RemoteArtifactProgress), + /// Module is being compiled. CompilingModule(wasmer_types::CompilationProgress), } +/// Progress during remote module artifact resolution. #[derive(Clone, Debug)] #[non_exhaustive] -pub enum ModuleLoadProgressRemote { +pub enum RemoteArtifactProgress { + /// Checking remote cache for artifact. RemoteCacheCheck, + /// Artifact found in remote cache. RemoteCacheHit, + /// Artifact is being downloaded. LoadingArtifact(DownloadProgress), - /// This exists as a variant since local compilation can be used instead if artifact fails. + /// Artifact loading failed - module will be compiled locally instead. ArtifactLoadFailed(ProgressError), } +/// Generic download progress. #[derive(Clone, Debug)] #[non_exhaustive] pub struct DownloadProgress { @@ -29,6 +41,7 @@ pub struct DownloadProgress { } impl DownloadProgress { + /// Creates a new [`DownloadProgress`]. pub fn new(total_bytes: u64, downloaded_bytes: Option) -> Self { Self { total_bytes, @@ -37,6 +50,7 @@ impl DownloadProgress { } } +/// Error that can occur during module loading. #[derive(Clone, Debug)] pub struct ProgressError { message: String, @@ -56,12 +70,24 @@ impl std::fmt::Display for ProgressError { } } +/// Reports progress during module loading. +/// +/// See [`ModuleLoadProgressReporter::new`] for details. #[derive(Clone)] pub struct ModuleLoadProgressReporter { callback: Arc Result<(), UserAbort> + Send + Sync>, } impl ModuleLoadProgressReporter { + /// Construct a new `ModuleLoadProgressReporter`. + /// + /// The callback function has the signature: + /// `Fn(ModuleLoadProgress) -> Result<(), UserAbort> + Send + Sync + 'static'. + /// + /// # Aborting module loading + /// + /// The callback has to return a `Result<(), UserAbort>`. + /// If an error is returned, the module loading process will be aborted. pub fn new(callback: F) -> Self where F: Fn(ModuleLoadProgress) -> Result<(), UserAbort> + Send + Sync + 'static, @@ -71,6 +97,7 @@ impl ModuleLoadProgressReporter { } } + /// Notify the reporter about new progress information. pub fn notify(&self, progress: ModuleLoadProgress) -> Result<(), UserAbort> { (self.callback)(progress) } From 4aadf39bd97e946e5291b93566abeea7f02ded68 Mon Sep 17 00:00:00 2001 From: Christoph Herzog Date: Thu, 20 Nov 2025 12:53:35 +0100 Subject: [PATCH 14/14] tmp --- Cargo.lock | 2 + lib/api/src/backend/sys/entities/module.rs | 13 +--- lib/api/src/entities/engine/progress_ext.rs | 2 + lib/cli/src/commands/run/runtime.rs | 3 + lib/compiler-cranelift/Cargo.toml | 1 + lib/compiler-cranelift/tests/progress.rs | 67 ++++++++++++++++++++ lib/compiler-singlepass/Cargo.toml | 1 + lib/compiler-singlepass/tests/progress.rs | 70 +++++++++++++++++++++ lib/compiler/src/progress.rs | 4 +- 9 files changed, 149 insertions(+), 14 deletions(-) create mode 100644 lib/compiler-cranelift/tests/progress.rs create mode 100644 lib/compiler-singlepass/tests/progress.rs diff --git a/Cargo.lock b/Cargo.lock index d41d172bc08..4d8db0876e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7275,6 +7275,7 @@ dependencies = [ "tracing", "wasmer-compiler", "wasmer-types", + "wat", ] [[package]] @@ -7318,6 +7319,7 @@ dependencies = [ "tempfile", "wasmer-compiler", "wasmer-types", + "wat", ] [[package]] diff --git a/lib/api/src/backend/sys/entities/module.rs b/lib/api/src/backend/sys/entities/module.rs index ea8c1e4a095..3a86b07b132 100644 --- a/lib/api/src/backend/sys/entities/module.rs +++ b/lib/api/src/backend/sys/entities/module.rs @@ -66,17 +66,8 @@ impl Module { binary: &[u8], callback: Option, ) -> Result { - #[cfg(feature = "compiler")] - { - let module = Self::compile_with_progress(engine, binary, callback)?; - Ok(module) - } - - #[cfg(not(feature = "compiler"))] - { - let _ = callback; - Self::compile(engine, binary) - } + let module = Self::compile_with_progress(engine, binary, callback)?; + Ok(module) } #[cfg(feature = "compiler")] diff --git a/lib/api/src/entities/engine/progress_ext.rs b/lib/api/src/entities/engine/progress_ext.rs index c01f51b829e..c01f5e69bc7 100644 --- a/lib/api/src/entities/engine/progress_ext.rs +++ b/lib/api/src/entities/engine/progress_ext.rs @@ -16,6 +16,8 @@ pub trait ProgressEngineExt { /// If the callback returns an error, the compilation will fail with a `CompileError::Aborted`. /// /// See [`CompilationProgressCallback::new`] for more details. + /// + /// **NOTE**: Not all engines/backends support progress reporting. fn new_module_with_progress( &self, bytes: &[u8], diff --git a/lib/cli/src/commands/run/runtime.rs b/lib/cli/src/commands/run/runtime.rs index abd9331f0a0..1e415296c5a 100644 --- a/lib/cli/src/commands/run/runtime.rs +++ b/lib/cli/src/commands/run/runtime.rs @@ -172,6 +172,9 @@ impl wasmer_wasix::Runtime for Monitorin self.progress.set_style(style); self.progress.reset(); + if self.progress.is_hidden() { + self.progress.set_draw_target(indicatif::ProgressDrawTarget::stderr()); + } self.progress.set_message(progress_msg); let f = async move { diff --git a/lib/compiler-cranelift/Cargo.toml b/lib/compiler-cranelift/Cargo.toml index 5081e31d2a0..d06b8193544 100644 --- a/lib/compiler-cranelift/Cargo.toml +++ b/lib/compiler-cranelift/Cargo.toml @@ -41,6 +41,7 @@ target-lexicon = { workspace = true, default-features = false } [dev-dependencies] cranelift-codegen = { version = "=0.124.2", features = ["all-arch"] } +wat = "1.216.0" [badges] maintenance = { status = "actively-developed" } diff --git a/lib/compiler-cranelift/tests/progress.rs b/lib/compiler-cranelift/tests/progress.rs new file mode 100644 index 00000000000..a76fb71f23d --- /dev/null +++ b/lib/compiler-cranelift/tests/progress.rs @@ -0,0 +1,67 @@ +#![cfg(feature = "std")] + +use std::sync::{Arc, Mutex}; + +use wasmer_compiler::EngineBuilder; +use wasmer_types::{CompilationProgressCallback, CompileError, UserAbort}; + +const SIMPLE_WAT: &str = r#"(module + (import "env" "div" (func $div (param i32 i32) (result i32))) + (func (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + (func (export "sub") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub) +)"#; + +fn cranelift_engine() -> wasmer_compiler::Engine { + let compiler = wasmer_compiler_cranelift::Cranelift::default(); + EngineBuilder::new(compiler).engine() +} + +fn wasm_bytes() -> Vec { + wat::parse_str(SIMPLE_WAT).expect("valid wat") +} + +#[test] +fn cranelift_reports_progress_steps() { + let engine = cranelift_engine(); + let wasm = wasm_bytes(); + + let events = Arc::new(Mutex::new(Vec::new())); + let cb = CompilationProgressCallback::new({ + let events = events.clone(); + move |progress| { + events.lock().unwrap().push(progress); + Ok(()) + } + }); + + engine + .compile_with_progress(&wasm, Some(cb)) + .expect("compilation succeeds"); + + let events = events.lock().unwrap(); + assert!( + !events.is_empty(), + "expected at least one progress notification" + ); + let last = events.last().unwrap(); + assert_eq!(last.phase_step_count(), Some(4)); + assert_eq!(last.phase_step(), Some(4)); +} + +#[test] +fn cranelift_progress_can_abort() { + let engine = cranelift_engine(); + let wasm = wasm_bytes(); + + let cb = CompilationProgressCallback::new(|_| Err(UserAbort::new("abort"))); + match engine.compile_with_progress(&wasm, Some(cb)) { + Err(CompileError::Aborted(e)) => assert_eq!(e.reason(), "abort"), + other => panic!("expected CompileError::Aborted, got {:?}", other), + } +} diff --git a/lib/compiler-singlepass/Cargo.toml b/lib/compiler-singlepass/Cargo.toml index bedcf6c548d..c75c47afa64 100644 --- a/lib/compiler-singlepass/Cargo.toml +++ b/lib/compiler-singlepass/Cargo.toml @@ -32,6 +32,7 @@ rayon = { workspace = true, optional = true } [dev-dependencies] target-lexicon = { workspace = true, default-features = false } +wat = "1.216.0" [badges] maintenance = { status = "actively-developed" } diff --git a/lib/compiler-singlepass/tests/progress.rs b/lib/compiler-singlepass/tests/progress.rs new file mode 100644 index 00000000000..a3bb3bcd3cc --- /dev/null +++ b/lib/compiler-singlepass/tests/progress.rs @@ -0,0 +1,70 @@ +#![cfg(all( + feature = "std", + any(target_arch = "x86_64", target_arch = "aarch64") +))] + +use std::sync::{Arc, Mutex}; + +use wasmer_compiler::EngineBuilder; +use wasmer_types::{CompilationProgressCallback, CompileError, UserAbort}; + +const SIMPLE_WAT: &str = r#"(module + (import "env" "div" (func $div (param i32 i32) (result i32))) + (func (export "add") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + (func (export "sub") (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.sub) +)"#; + +fn singlepass_engine() -> wasmer_compiler::Engine { + let compiler = wasmer_compiler_singlepass::Singlepass::new(); + EngineBuilder::new(compiler).engine() +} + +fn wasm_bytes() -> Vec { + wat::parse_str(SIMPLE_WAT).expect("valid wat") +} + +#[test] +fn singlepass_reports_progress_steps() { + let engine = singlepass_engine(); + let wasm = wasm_bytes(); + + let events = Arc::new(Mutex::new(Vec::new())); + let cb = CompilationProgressCallback::new({ + let events = events.clone(); + move |progress| { + events.lock().unwrap().push(progress); + Ok(()) + } + }); + + engine + .compile_with_progress(&wasm, Some(cb)) + .expect("compilation succeeds"); + + let events = events.lock().unwrap(); + assert!( + !events.is_empty(), + "expected at least one progress notification" + ); + let last = events.last().unwrap(); + assert_eq!(last.phase_step_count(), Some(4)); + assert_eq!(last.phase_step(), Some(4)); +} + +#[test] +fn singlepass_progress_can_abort() { + let engine = singlepass_engine(); + let wasm = wasm_bytes(); + + let cb = CompilationProgressCallback::new(|_| Err(UserAbort::new("abort"))); + match engine.compile_with_progress(&wasm, Some(cb)) { + Err(CompileError::Aborted(e)) => assert_eq!(e.reason(), "abort"), + other => panic!("expected CompileError::Aborted, got {:?}", other), + } +} diff --git a/lib/compiler/src/progress.rs b/lib/compiler/src/progress.rs index 123771af73f..62ffa3594af 100644 --- a/lib/compiler/src/progress.rs +++ b/lib/compiler/src/progress.rs @@ -37,9 +37,7 @@ impl ProgressContext { /// Notifies the callback that the next step in the phase has completed. pub fn notify(&self) -> Result<(), CompileError> { - if self.total == 0 { - return Ok(()); - } + dbg!("notify", self.phase_name, self.total, self.counter.load(Ordering::SeqCst)); let step = self.counter.fetch_add(1, Ordering::SeqCst) + 1; self.callback .notify(CompilationProgress::new(