Skip to content

Commit 9094e79

Browse files
committed
Remove core wasm name guess in adapter GC
This commit removes the need for the GC pass on the adapter module to guess what core wasm export names are needed for WIT. Previously it was assumed that certain exports would have exact core wasm names but that's going to change soon so this refactoring is empowering these future changes. The GC pass for adapters is restructured to run validation over the non-GC'd adapter first. This validation pass will identify WIT export functions and such and then this information is used to determine the set of live exports. These live exports are then used to perform a GC pass, and then afterwards the validation pass is run a second time to recalculate information with possibly-removed imports.
1 parent 51a2f8b commit 9094e79

File tree

2 files changed

+47
-83
lines changed

2 files changed

+47
-83
lines changed

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

Lines changed: 43 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ use anyhow::{Context, Result};
66
use indexmap::{IndexMap, IndexSet};
77
use std::borrow::Cow;
88
use std::collections::{HashMap, HashSet};
9-
use wasmparser::FuncType;
109
use wit_parser::{
11-
abi::{AbiVariant, WasmSignature, WasmType},
10+
abi::{AbiVariant, WasmSignature},
1211
Function, InterfaceId, LiveTypes, Resolve, TypeDefKind, TypeId, TypeOwner, WorldId, WorldItem,
1312
WorldKey,
1413
};
@@ -130,12 +129,51 @@ impl<'a> ComponentWorld<'a> {
130129
let wasm = if library_info.is_some() {
131130
Cow::Borrowed(wasm as &[u8])
132131
} else {
133-
let required = self.required_adapter_exports(
132+
// Without `library_info` this means that this is an adapter.
133+
// The goal of the adapter is to provide a suite of symbols that
134+
// can be imported, but not all symbols may be imported. Here
135+
// the module is trimmed down to only what's needed by the
136+
// original main module.
137+
//
138+
// The main module requires `required_by_import` above, but
139+
// adapters may themselves also export WIT items. To handle this
140+
// the sequence of operations here are:
141+
//
142+
// 1. First the adapter is validated as-is. This ensures that
143+
// everything looks good before GC.
144+
// 2. The metadata from step (1) is used to determine the set of
145+
// WIT-level exports that are needed. This includes things
146+
// like realloc functions and such.
147+
// 3. The set of WIT-level functions from (2) is unioned with
148+
// `required_by_import` to create the set of required exports
149+
// of the adapter.
150+
// 4. This set of exports is used to delete some exports of the
151+
// adapter and then perform a GC pass.
152+
//
153+
// Finally at the end of all of this the
154+
// `validate_adapter_module` method is called for a second time
155+
// on the minimized adapter. This is done because deleting
156+
// imports may have deleted some imports which means that the
157+
// final component may not need to import as many interfaces.
158+
let info = validate_adapter_module(
159+
&wasm,
134160
resolve,
135161
world,
136-
required_exports,
137162
&required_by_import,
138-
);
163+
required_exports,
164+
library_info.as_ref(),
165+
adapters,
166+
)
167+
.with_context(|| {
168+
format!("failed to validate the imports of the adapter module `{name}`")
169+
})?;
170+
let mut required = IndexSet::new();
171+
for (name, _ty) in required_by_import.iter() {
172+
required.insert(name.to_string());
173+
}
174+
for (name, _export) in info.exports.iter() {
175+
required.insert(name.to_string());
176+
}
139177

140178
Cow::Owned(
141179
crate::gc::run(
@@ -174,64 +212,6 @@ impl<'a> ComponentWorld<'a> {
174212
Ok(())
175213
}
176214

177-
/// Returns the set of functions required to be exported from an adapter,
178-
/// either because they're exported from the adapter's world or because
179-
/// they're required as an import to the main module.
180-
fn required_adapter_exports<'r>(
181-
&self,
182-
resolve: &'r Resolve,
183-
world: WorldId,
184-
required_exports: &IndexSet<WorldKey>,
185-
required_by_import: &IndexMap<String, FuncType>,
186-
) -> IndexMap<String, (FuncType, Option<&'r Function>)> {
187-
use wasmparser::ValType;
188-
189-
let mut required = IndexMap::new();
190-
for (name, ty) in required_by_import {
191-
required.insert(name.to_string(), (ty.clone(), None));
192-
}
193-
let mut add_func = |func: &'r Function, name: Option<&str>| {
194-
let name = func.core_export_name(name);
195-
let ty = resolve.wasm_signature(AbiVariant::GuestExport, func);
196-
let prev = required.insert(
197-
name.into_owned(),
198-
(
199-
wasmparser::FuncType::new(
200-
ty.params.iter().map(to_valty),
201-
ty.results.iter().map(to_valty),
202-
),
203-
Some(func),
204-
),
205-
);
206-
assert!(prev.is_none());
207-
};
208-
for name in required_exports {
209-
match &resolve.worlds[world].exports[name] {
210-
WorldItem::Function(func) => add_func(func, None),
211-
WorldItem::Interface { id, .. } => {
212-
let name = resolve.name_world_key(name);
213-
for (_, func) in resolve.interfaces[*id].functions.iter() {
214-
add_func(func, Some(&name));
215-
}
216-
}
217-
WorldItem::Type(_) => {}
218-
}
219-
}
220-
return required;
221-
222-
fn to_valty(ty: &WasmType) -> ValType {
223-
match ty {
224-
WasmType::I32 => ValType::I32,
225-
WasmType::I64 => ValType::I64,
226-
WasmType::F32 => ValType::F32,
227-
WasmType::F64 => ValType::F64,
228-
WasmType::Pointer => ValType::I32,
229-
WasmType::PointerOrI64 => ValType::I64,
230-
WasmType::Length => ValType::I32,
231-
}
232-
}
233-
}
234-
235215
/// Fills out the `import_map` field of `self` by determining the live
236216
/// functions from all imports. This additionally classifies imported
237217
/// functions into direct or indirect lowerings for managing shims.

crates/wit-component/src/gc.rs

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ const PAGE_SIZE: i32 = 64 * 1024;
1919
///
2020
/// This internally performs a "gc" pass after removing exports to ensure that
2121
/// the resulting module imports the minimal set of functions necessary.
22-
pub fn run<T>(
22+
pub fn run(
2323
wasm: &[u8],
24-
required: &IndexMap<String, T>,
24+
required: &IndexSet<String>,
2525
main_module_realloc: Option<&str>,
2626
) -> Result<Vec<u8>> {
2727
assert!(!required.is_empty());
@@ -31,21 +31,14 @@ pub fn run<T>(
3131

3232
// Make sure that all required names are present in the module, and then
3333
// remove all names that are not required.
34-
for (name, _ty) in required {
34+
for name in required {
3535
if !module.exports.contains_key(name.as_str()) {
3636
bail!("adapter module does not have export `{name}`")
3737
}
3838
}
3939
let mut not_required = IndexSet::new();
4040
for name in module.exports.keys().copied() {
41-
// If we need `name` then we also need cabi_post_`name`:
42-
let name = if let Some(suffix) = name.strip_prefix("cabi_post_") {
43-
suffix
44-
} else {
45-
name
46-
};
47-
48-
if !required.contains_key(name) && !always_keep(name) {
41+
if !required.contains(name) {
4942
not_required.insert(name);
5043
}
5144
}
@@ -57,15 +50,6 @@ pub fn run<T>(
5750
module.encode(main_module_realloc)
5851
}
5952

60-
fn always_keep(name: &str) -> bool {
61-
match name {
62-
// Explicitly keep `cabi_realloc` if it's there in case an interface
63-
// needs it for a lowering.
64-
"cabi_realloc" | "cabi_import_realloc" | "cabi_export_realloc" => true,
65-
_ => false,
66-
}
67-
}
68-
6953
/// This function generates a Wasm function body which implements `cabi_realloc` in terms of `memory.grow`. It
7054
/// only accepts new, page-sized allocations.
7155
fn realloc_via_memory_grow() -> wasm_encoder::Function {

0 commit comments

Comments
 (0)