Skip to content

Conversation

FedorSmirnov89
Copy link

@FedorSmirnov89 FedorSmirnov89 commented Aug 14, 2025

Proof of Concept: Module Preparsing for Resource-Constrained Targets

⚠️ Important Note

This is a proof-of-concept implementation intended to demonstrate the concept and gather feedback. It is NOT intended for production use and contains several incomplete and unclean aspects that would need to be addressed in a proper implementation.

I'm happy to work with the Wasmi maintainers to implement this feature properly, starting from scratch if needed.

Overview

This PR demonstrates a module preparsing feature that enables Wasmi to run on resource-constrained embedded systems by allowing modules to be parsed and validated on development machines, then serialized to a compact format for lightweight runtime loading.

Problem Statement (also described in #1637 )

When deploying Wasmi on resource-constrained targets (e.g., nRF53 with ~1MB total memory), the current implementation consumes significant memory (~480 KiB when deployed with Embassy). This limits the ability to run complex WebAssembly workloads on embedded devices.

Proposed Solution

The feature adds module serialization and deserialization capabilities, allowing:

  1. Development Phase: Parse and validate modules on resource-rich machines
  2. Deployment: Serialize modules to compact binary format
  3. Runtime: Load preprocessed modules without parsing overhead

Implementation Details

New Features Added

  • serialization feature for creating serialized modules
  • deserialization feature for lightweight runtime builds
  • parser feature-gates the wasmi logic for parsing WebAssembly modules (compiling without this feature provides slim builds by excluding the parsing/validation/translation machinery)

New Modules

  • preparsed/: Main module for serialization/deserialization
  • preparsed/serialized_module/: Data structures for serialized format
  • preparsed/serialization/: Module serialization logic
  • preparsed/deserialization/: Module deserialization logic
  • preparsed/error.rs: Comprehensive error handling

Serialization Format

  • Uses Postcard for compact binary serialization
  • Platform-independent format
  • Versioned for future compatibility

Performance Impact

Based on testing with nRF53 + Embassy + BLE stack:

  • Current Wasmi: ~480 KiB memory footprint
  • With preparsing: ~250 KiB memory footprint
  • Reduction: ~48% memory savings

What's Incomplete/Unclean

This proof-of-concept has several areas that need improvement:

1. Module Internals Visibility

  • Several internal types and functions are made more visible than they should be
  • Some pub(crate) visibility modifiers may be too permissive
  • Module architecture integration needs refinement

2. Error Handling

  • Error types could be better integrated with Wasmi's existing error system
  • Some error contexts could be more informative
  • Error recovery strategies need more consideration

3. Feature Completeness

  • Not all WebAssembly features are fully supported

4. Architecture Integration

  • Better integration with Wasmi's architecture needed
  • Potentially, the code feature-gated by the "parser" feature could be moved into an extra module

Example Repository

I've created an example repository demonstrating the usage and performance benefits: Preparsing Example

The repository shows:

  • An example of a practical integration with (embedded) Rust applications
  • Memory footprint measurements

Next Steps

If this approach is approved:

  1. Architecture Review: Work with maintainers to refine the design
  2. Clean Implementation: Start fresh with proper architecture integration
  3. Feature Completeness: Ensure all Wasm features are properly supported
  4. Testing: Expand test coverage and add performance benchmarks
  5. Documentation: Create comprehensive user documentation

Questions for Maintainers

  1. Does this general approach align with Wasmi's goals?
  2. What architectural concerns should be addressed first?
  3. How should this integrate with Wasmi's existing feature system?
  4. What level of feature completeness is required for initial merge?

Copy link
Member

@Robbepop Robbepop left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already started reviewing this PR before I noticed that it is just a "draft". So sorry for that. However, I do not want to hold back what I have already reviewed and commented in case it helps you.

Comment on lines -139 to +132
const REVCOMP_INPUT: &[u8] = include_bytes!("rust/cases/reverse_complement/input.txt");
const REVCOMP_OUTPUT: &[u8] = include_bytes!("rust/cases/reverse_complement/output.txt");
const REVCOMP_INPUT: &[u8] = &[42]; //include_bytes!("rust/cases/reverse_complement/input.txt");
const REVCOMP_OUTPUT: &[u8] = &[42]; //include_bytes!("rust/cases/reverse_complement/output.txt");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have you effectively disabled this benchmark?

Comment on lines +33 to +36
serde = { version = "1.0", default-features = false, features = [
"derive",
"alloc",
], optional = true }
postcard = { workspace = true, default-features = false, optional = true, features = [
"alloc",
] }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not re-format code that is not associated to your PR diff.

Comment on lines -14 to +11
criterion_group,
criterion_main,
measurement::WallTime,
Bencher,
BenchmarkGroup,
Criterion,
criterion_group, criterion_main, measurement::WallTime, Bencher, BenchmarkGroup, Criterion,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please stick to Wasmi's formatting rules.

Comment on lines -247 to -273
/// Initializes the [`EngineFunc`] for lazy translation.
///
/// # Panics
///
/// - If `func` is an invalid [`EngineFunc`] reference for this [`CodeMap`].
/// - If `func` refers to an already initialized [`EngineFunc`].
pub fn init_func_as_uncompiled(
&self,
func: EngineFunc,
func_idx: FuncIdx,
bytes: &[u8],
module: &ModuleHeader,
func_to_validate: Option<FuncToValidate<ValidatorResources>>,
) {
let mut funcs = self.funcs.lock();
let Some(func) = funcs.get_mut(func) else {
panic!("encountered invalid internal function: {func:?}")
};
func.init_uncompiled(UncompiledFuncEntity::new(
func_idx,
bytes,
module.clone(),
func_to_validate,
));
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't unnecessarily move functions around that have no effect on the PRs diff. This just makes it harder to review and blow-up the PR's diff.

Comment on lines 86 to 141
/// Sets the [`StackLimits`] for the [`Config`].
pub fn set_stack_limits(&mut self, stack_limits: StackLimits) -> &mut Self {
self.stack_limits = stack_limits;
self
}

/// Returns the [`StackLimits`] of the [`Config`].
pub(super) fn stack_limits(&self) -> StackLimits {
self.stack_limits
}

/// Sets the maximum amount of cached stacks for reuse for the [`Config`].
///
/// # Note
///
/// Defaults to 2.
pub fn set_cached_stacks(&mut self, amount: usize) -> &mut Self {
self.cached_stacks = amount;
self
}

/// Returns the maximum amount of cached stacks for reuse of the [`Config`].
pub(super) fn cached_stacks(&self) -> usize {
self.cached_stacks
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please rebase your work onto main. These methods already do not exist on main.

- new module in the wasmi crate
- tests for (roundtrip) (de)serialization of Wasm modules
- introduce features to enable operation modes:
  - default (current way)
  - parser + serialization
  - deserialization
- renamed module
- fixed warnings in different feature configurations
Copy link

codecov bot commented Aug 21, 2025

Codecov Report

❌ Patch coverage is 78.89908% with 92 lines in your changes missing coverage. Please review.
✅ Project coverage is 71.01%. Comparing base (b2ac2fc) to head (fae56e1).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
...tes/wasmi/src/preparsed/serialized_module/types.rs 70.58% 35 Missing ⚠️
crates/wasmi/src/preparsed/error.rs 0.00% 23 Missing ⚠️
crates/wasmi/src/engine/config.rs 14.28% 12 Missing ⚠️
crates/wasmi/src/preparsed/deserialization/mod.rs 92.10% 9 Missing ⚠️
crates/wasmi/src/preparsed/serialization/mod.rs 93.54% 6 Missing ⚠️
crates/wasmi/src/engine/code_map.rs 69.23% 4 Missing ⚠️
crates/wasmi/src/module/data.rs 60.00% 2 Missing ⚠️
crates/ir/src/primitive.rs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1636      +/-   ##
==========================================
+ Coverage   70.39%   71.01%   +0.61%     
==========================================
  Files         179      187       +8     
  Lines       15344    15799     +455     
==========================================
+ Hits        10802    11220     +418     
- Misses       4542     4579      +37     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants