Skip to content

Commit c634137

Browse files
committed
[wit-component] add shared-everything linking support
Per https://github.com/WebAssembly/component-model/blob/main/design/mvp/examples/SharedEverythingDynamicLinking.md and https://hackmd.io/IlY4lICRRNy9wQbNLdb2Wg. This adds a new `component link` subcommand, which resembles `component new` but accepts an arbitrary number of "dynamic library" modules (as defined by https://github.com/WebAssembly/tool-conventions/blob/main/DynamicLinking.md), linking them together to create a component whose type is the union of the component types found in the input modules (via the usual custom section representation). This process can also be invoked programmatically via a new `Linker` API. `Linker` analyzes and topologically sorts the input modules, then sythesizes two additional modules: - `main`: hosts the component's single memory and function table and exports any functions needed to break dependency cycles discovered in the input modules. Those functions use `call.indirect` to invoke the real functions, references to which are placed in the table by the `init` module. - `init`: populates the function table as described above, initializes global variables per the dynamic linking tool convention, and calls any static constructors and/or link-time fixup functions Finally, `Linker` generates a directed graph of modules and their instantiations and passes it to `ComponentEncoder` to actually produce the component. The implementation relies on a few modifications to the existing code in `ComponentEncoder` and friends. Specifically, I've expanded the meaning of "adapter" to include dynamic libraries as well as traditional adapters. The former are distiguished from the latter by the presence of a new `LibraryInfo` struct. This approach minimized the number of code changes in the interest of keeping this PR comprehensible, but we will probably want to refactor `ComponentEncoder` in the future to make the distinction between traditional adapters and dynamic libraries clearer, possibly generalizing how we analyze and instantiate modules of various flavors. I've created a demo which ties everything together, for reference: https://github.com/dicej/component-linking-demo. It includes `wasi-sdk` and `wasi-libc` patches which have not yet been upstreamed; I'm planning to work on that next. Signed-off-by: Joel Dice <joel.dice@fermyon.com> skip wasmtime part of linking test under Wasm Signed-off-by: Joel Dice <joel.dice@fermyon.com>
1 parent 94ad03b commit c634137

File tree

11 files changed

+2557
-112
lines changed

11 files changed

+2557
-112
lines changed

Cargo.toml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,7 @@ num_cpus = "1.13"
3838
rand = { version = "0.8.4", features = ["small_rng"] }
3939
rayon = "1.3"
4040
serde = { version = "1.0.166", features = ["derive"] }
41-
wasmtime = { version = "3.0.0", default-features = false, features = [
42-
'cranelift',
43-
] }
41+
wasmtime = { version = "11.0.1", default-features = false, features = ['cranelift', 'component-model'] }
4442
url = "2.0.0"
4543
pretty_assertions = "1.3.0"
4644
semver = "1.0.0"

crates/fuzz-stats/src/limits.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use anyhow::Result;
12
use wasmtime::*;
23

34
#[derive(Clone)]
@@ -22,12 +23,17 @@ impl StoreLimits {
2223
}
2324

2425
impl ResourceLimiter for StoreLimits {
25-
fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option<usize>) -> bool {
26-
self.alloc(desired - current)
26+
fn memory_growing(
27+
&mut self,
28+
current: usize,
29+
desired: usize,
30+
_maximum: Option<usize>,
31+
) -> Result<bool> {
32+
Ok(self.alloc(desired - current))
2733
}
2834

29-
fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> bool {
35+
fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option<u32>) -> Result<bool> {
3036
let delta = (desired - current) as usize * std::mem::size_of::<usize>();
31-
self.alloc(delta)
37+
Ok(self.alloc(delta))
3238
}
3339
}

crates/wit-component/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,5 +30,8 @@ pretty_assertions = "1.3.0"
3030
env_logger = { workspace = true }
3131
wat = { workspace = true }
3232

33+
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
34+
wasmtime = { workspace = true }
35+
3336
[features]
3437
dummy-module = ['dep:wat']

crates/wit-component/src/encoding.rs

Lines changed: 267 additions & 87 deletions
Large diffs are not rendered by default.

crates/wit-component/src/encoding/world.rs

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
use super::{ComponentEncoder, RequiredOptions};
1+
use super::{Adapter, ComponentEncoder, LibraryInfo, RequiredOptions};
22
use crate::validation::{
33
validate_adapter_module, validate_module, RequiredImports, ValidatedAdapter, ValidatedModule,
44
BARE_FUNC_MODULE_NAME, RESOURCE_DROP,
55
};
66
use anyhow::{Context, Result};
77
use indexmap::{IndexMap, IndexSet};
8-
use std::borrow::Borrow;
8+
use std::borrow::{Borrow, Cow};
99
use std::collections::{HashMap, HashSet};
1010
use std::hash::Hash;
1111
use wasmparser::FuncType;
@@ -15,6 +15,12 @@ use wit_parser::{
1515
WorldKey,
1616
};
1717

18+
pub struct WorldAdapter<'a> {
19+
pub wasm: Cow<'a, [u8]>,
20+
pub info: ValidatedAdapter<'a>,
21+
pub library_info: Option<&'a LibraryInfo>,
22+
}
23+
1824
/// Metadata discovered from the state configured in a `ComponentEncoder`.
1925
///
2026
/// This is stored separately from `EncodingState` to be stored as a borrow in
@@ -28,7 +34,7 @@ pub struct ComponentWorld<'a> {
2834
pub info: ValidatedModule<'a>,
2935
/// Validation information about adapters populated only for required
3036
/// adapters. Additionally stores the gc'd wasm for each adapter.
31-
pub adapters: IndexMap<&'a str, (ValidatedAdapter<'a>, Vec<u8>)>,
37+
pub adapters: IndexMap<&'a str, WorldAdapter<'a>>,
3238
/// Map of all imports and descriptions of what they're importing.
3339
pub import_map: IndexMap<Option<String>, ImportedInterface>,
3440
/// Set of all live types which must be exported either because they're
@@ -96,18 +102,47 @@ impl<'a> ComponentWorld<'a> {
96102
fn process_adapters(&mut self) -> Result<()> {
97103
let resolve = &self.encoder.metadata.resolve;
98104
let world = self.encoder.metadata.world;
99-
for (name, (wasm, metadata, required_exports)) in self.encoder.adapters.iter() {
105+
for (
106+
name,
107+
Adapter {
108+
wasm,
109+
metadata,
110+
required_exports,
111+
library_info,
112+
},
113+
) in self.encoder.adapters.iter()
114+
{
100115
let required_by_import = self.info.adapters_required.get(name.as_str());
101116
let required =
102117
self.required_adapter_exports(resolve, world, required_exports, required_by_import);
103-
if required.is_empty() {
118+
if required.is_empty() && library_info.is_none() {
104119
continue;
105120
}
106-
let wasm = crate::gc::run(wasm, &required, self.info.realloc)
107-
.context("failed to reduce input adapter module to its minimal size")?;
108-
let info = validate_adapter_module(&wasm, resolve, world, metadata, &required)
109-
.context("failed to validate the imports of the minimized adapter module")?;
110-
self.adapters.insert(name, (info, wasm));
121+
let wasm = if library_info.is_some() {
122+
Cow::Borrowed(wasm as &[u8])
123+
} else {
124+
Cow::Owned(
125+
crate::gc::run(wasm, &required, self.info.realloc)
126+
.context("failed to reduce input adapter module to its minimal size")?,
127+
)
128+
};
129+
let info = validate_adapter_module(
130+
&wasm,
131+
resolve,
132+
world,
133+
metadata,
134+
&required,
135+
library_info.is_some(),
136+
)
137+
.context("failed to validate the imports of the minimized adapter module")?;
138+
self.adapters.insert(
139+
name,
140+
WorldAdapter {
141+
info,
142+
wasm,
143+
library_info: library_info.as_ref(),
144+
},
145+
);
111146
}
112147
Ok(())
113148
}
@@ -173,7 +208,7 @@ impl<'a> ComponentWorld<'a> {
173208
let resolve = &self.encoder.metadata.resolve;
174209
let world = self.encoder.metadata.world;
175210
let mut all_required_imports = IndexMap::new();
176-
for map in self.adapters.values().map(|(i, _)| &i.required_imports) {
211+
for map in self.adapters.values().map(|a| &a.info.required_imports) {
177212
for (k, v) in map {
178213
all_required_imports
179214
.entry(k.as_str())
@@ -256,9 +291,9 @@ impl<'a> ComponentWorld<'a> {
256291
// First use the previously calculated metadata about live imports to
257292
// determine the set of live types in those imports.
258293
self.add_live_imports(world, &self.info.required_imports, &mut live);
259-
for (adapter_name, (info, _wasm)) in self.adapters.iter() {
294+
for (adapter_name, adapter) in self.adapters.iter() {
260295
log::trace!("processing adapter `{adapter_name}`");
261-
self.add_live_imports(world, &info.required_imports, &mut live);
296+
self.add_live_imports(world, &adapter.info.required_imports, &mut live);
262297
}
263298

264299
// Next any imported types used by an export must also be considered

crates/wit-component/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ use wasm_encoder::CanonicalOption;
1010
mod decoding;
1111
mod encoding;
1212
mod gc;
13+
mod linking;
1314
mod printing;
1415
mod targets;
1516
mod validation;
1617

1718
pub use decoding::{decode, DecodedWasm};
1819
pub use encoding::{encode, ComponentEncoder};
20+
pub use linking::Linker;
1921
pub use printing::*;
2022
pub use targets::*;
2123

0 commit comments

Comments
 (0)