Skip to content

Commit 6fc5d68

Browse files
authored
Overhaul and refactor some internals of wit-component (#1810)
* Overhaul and refactor some internals of `wit-component` This commit is a lead-up to the changes proposed in #1774. The `wit-component` crate is quite old and has gone through many iterations of the component model and it's very much showing its age in a few places. Namely the correlation between component model names and core wasm names is open-coded in many places throughout validation and encoding of a component. This makes the changes in #1774 where the names may be different (e.g. core wasm 0.2.0 might import component 0.2.1). Making this change was inevitably going to require quite a lot of refactoring of `wit-component`, so that's what this commit does. It's a pretty large rewrite of the internals of validation of a core wasm module and adapter prior to creating a component. The metadata produced by this pass is now represented in a completely different format. The metadata is extensively used throughout the encoding process so encoding has been heavily refactored as well. The overall idea is that the previous "grab bag" of various items here and there produced from validation are now all unified into a single `ImportMap` and `ExportMap` for a module. These maps track all the various kinds of imports and exports and how they map back to core wasm names. Notably this means that the logic to correlate core wasm names with component model names is now happening in just one location (in theory) as opposed to implicitly all throughout encoding. I've additionally taken this opportunity to subjectively simplify much of the encoding process around managing instantiations of core wasm modules and adapters. One of the main changes in this commit is that it does away with code structure such as "do the thing for WIT" then "do the thing for resources" then "do the thing for other resources" and finally "do the thing for adapters". This was difficult to understand every time I came back to it and I can't imagine was easy for anyone else to understand either. All imports are now handled in a single location and it's intended to be much better separated who's responsible for what. For example the code satisfying an import is decoupled from what the import is going to be named and how it's provided to the main core wasm module. Overall the intention is that this does not either enhance the functionality of wit-component nor regress it. Lots of tests have changed but I've tried to verify it's just things moving around as opposed to anything that has a different semantic meaning. A future PR for #1774 will enhance the logic of connecting core wasm imports to WIT imports but that's deferred for a future PR. * Update link-related tests * Bless some more tests
1 parent 90fd388 commit 6fc5d68

File tree

61 files changed

+1955
-2481
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+1955
-2481
lines changed

crates/wasm-encoder/src/component/builder.rs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -206,20 +206,6 @@ impl ComponentBuilder {
206206
})
207207
}
208208

209-
/// Creates an alias to a previous core instance's exported item.
210-
///
211-
/// The `instance` provided is the instance to access and the `name` is the
212-
/// item to access.
213-
///
214-
/// Returns the index of the new item defined.
215-
pub fn alias_core_export(&mut self, instance: u32, name: &str, kind: ExportKind) -> u32 {
216-
self.alias(Alias::CoreInstanceExport {
217-
instance,
218-
kind,
219-
name,
220-
})
221-
}
222-
223209
fn inc_kind(&mut self, kind: ComponentExportKind) -> u32 {
224210
match kind {
225211
ComponentExportKind::Func => inc(&mut self.funcs),

crates/wasm-encoder/src/core/exports.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use super::{
44
use crate::{encode_section, Encode, Section, SectionId};
55

66
/// Represents the kind of an export from a WebAssembly module.
7-
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7+
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
88
#[repr(u8)]
99
pub enum ExportKind {
1010
/// The export is a function.

crates/wit-component/src/encoding.rs

Lines changed: 467 additions & 545 deletions
Large diffs are not rendered by default.

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

Lines changed: 71 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
use super::{Adapter, ComponentEncoder, LibraryInfo, RequiredOptions};
22
use crate::validation::{
3-
validate_adapter_module, validate_module, RequiredImports, ValidatedAdapter, ValidatedModule,
4-
BARE_FUNC_MODULE_NAME, RESOURCE_DROP,
3+
validate_adapter_module, validate_module, Import, ImportMap, ValidatedModule, RESOURCE_DROP,
54
};
65
use anyhow::{Context, Result};
76
use indexmap::{IndexMap, IndexSet};
8-
use std::borrow::{Borrow, Cow};
7+
use std::borrow::Cow;
98
use std::collections::{HashMap, HashSet};
10-
use std::hash::Hash;
119
use wasmparser::FuncType;
1210
use wit_parser::{
1311
abi::{AbiVariant, WasmSignature, WasmType},
@@ -17,7 +15,7 @@ use wit_parser::{
1715

1816
pub struct WorldAdapter<'a> {
1917
pub wasm: Cow<'a, [u8]>,
20-
pub info: ValidatedAdapter<'a>,
18+
pub info: ValidatedModule,
2119
pub library_info: Option<&'a LibraryInfo>,
2220
}
2321

@@ -31,7 +29,7 @@ pub struct ComponentWorld<'a> {
3129
pub encoder: &'a ComponentEncoder,
3230
/// Validation information of the input module, or `None` in `--types-only`
3331
/// mode.
34-
pub info: ValidatedModule<'a>,
32+
pub info: ValidatedModule,
3533
/// Validation information about adapters populated only for required
3634
/// adapters. Additionally stores the gc'd wasm for each adapter.
3735
pub adapters: IndexMap<&'a str, WorldAdapter<'a>>,
@@ -107,14 +105,14 @@ impl<'a> ComponentWorld<'a> {
107105
name,
108106
Adapter {
109107
wasm,
110-
metadata,
108+
metadata: _,
111109
required_exports,
112110
library_info,
113111
},
114112
) in self.encoder.adapters.iter()
115113
{
116-
let required_by_import = self.info.adapters_required.get(name.as_str());
117-
let no_required_by_import = || required_by_import.map(|m| m.is_empty()).unwrap_or(true);
114+
let required_by_import = self.info.imports.required_from_adapter(name.as_str());
115+
let no_required_by_import = || required_by_import.is_empty();
118116
let no_required_exports = || {
119117
required_exports
120118
.iter()
@@ -136,7 +134,7 @@ impl<'a> ComponentWorld<'a> {
136134
resolve,
137135
world,
138136
required_exports,
139-
required_by_import,
137+
&required_by_import,
140138
);
141139

142140
Cow::Owned(
@@ -146,7 +144,7 @@ impl<'a> ComponentWorld<'a> {
146144
if self.encoder.realloc_via_memory_grow {
147145
None
148146
} else {
149-
self.info.realloc
147+
self.info.exports.realloc_to_import_into_adapter()
150148
},
151149
)
152150
.context("failed to reduce input adapter module to its minimal size")?,
@@ -156,13 +154,14 @@ impl<'a> ComponentWorld<'a> {
156154
&wasm,
157155
resolve,
158156
world,
159-
metadata,
160-
required_by_import,
157+
&required_by_import,
161158
required_exports,
162-
library_info.is_some(),
159+
library_info.as_ref(),
163160
adapters,
164161
)
165-
.context("failed to validate the imports of the minimized adapter module")?;
162+
.with_context(|| {
163+
format!("failed to validate the imports of the minimized adapter module `{name}`")
164+
})?;
166165
self.adapters.insert(
167166
name,
168167
WorldAdapter {
@@ -183,15 +182,13 @@ impl<'a> ComponentWorld<'a> {
183182
resolve: &'r Resolve,
184183
world: WorldId,
185184
required_exports: &IndexSet<WorldKey>,
186-
required_by_import: Option<&IndexMap<&str, FuncType>>,
185+
required_by_import: &IndexMap<String, FuncType>,
187186
) -> IndexMap<String, (FuncType, Option<&'r Function>)> {
188187
use wasmparser::ValType;
189188

190189
let mut required = IndexMap::new();
191-
if let Some(imports) = required_by_import {
192-
for (name, ty) in imports {
193-
required.insert(name.to_string(), (ty.clone(), None));
194-
}
190+
for (name, ty) in required_by_import {
191+
required.insert(name.to_string(), (ty.clone(), None));
195192
}
196193
let mut add_func = |func: &'r Function, name: Option<&str>| {
197194
let name = func.core_export_name(name);
@@ -241,29 +238,40 @@ impl<'a> ComponentWorld<'a> {
241238
fn process_imports(&mut self) -> Result<()> {
242239
let resolve = &self.encoder.metadata.resolve;
243240
let world = self.encoder.metadata.world;
244-
let mut all_required_imports = IndexMap::new();
245-
for map in self.adapters.values().map(|a| &a.info.required_imports) {
246-
for (k, v) in map {
247-
all_required_imports
248-
.entry(k.as_str())
249-
.or_insert_with(IndexSet::new)
250-
.extend(v.funcs.iter().map(|v| v.as_str()));
241+
242+
// Inspect all imports of the main module and adapters to find all
243+
// WIT-looking things and register those as required. This is used to
244+
// prune out unneeded things in the `add_item` function below.
245+
let mut required = Required::default();
246+
for (_, _, import) in self
247+
.adapters
248+
.values()
249+
.flat_map(|a| a.info.imports.imports())
250+
.chain(self.info.imports.imports())
251+
{
252+
match import {
253+
Import::WorldFunc(name) => {
254+
required
255+
.interface_funcs
256+
.entry(None)
257+
.or_default()
258+
.insert(name);
259+
}
260+
Import::InterfaceFunc(_, id, name) => {
261+
required
262+
.interface_funcs
263+
.entry(Some(*id))
264+
.or_default()
265+
.insert(name);
266+
}
267+
Import::ImportedResourceDrop(_, id) => {
268+
required.resource_drops.insert(*id);
269+
}
270+
_ => {}
251271
}
252272
}
253-
for (k, v) in self.info.required_imports.iter() {
254-
all_required_imports
255-
.entry(*k)
256-
.or_insert_with(IndexSet::new)
257-
.extend(v.funcs.iter().map(|v| v.as_str()));
258-
}
259273
for (name, item) in resolve.worlds[world].imports.iter() {
260-
add_item(
261-
&mut self.import_map,
262-
resolve,
263-
name,
264-
item,
265-
&all_required_imports,
266-
)?;
274+
add_item(&mut self.import_map, resolve, name, item, &required)?;
267275
}
268276
return Ok(());
269277

@@ -272,11 +280,10 @@ impl<'a> ComponentWorld<'a> {
272280
resolve: &Resolve,
273281
name: &WorldKey,
274282
item: &WorldItem,
275-
required: &IndexMap<&str, IndexSet<&str>>,
283+
required: &Required<'_>,
276284
) -> Result<()> {
277285
let name = resolve.name_world_key(name);
278286
log::trace!("register import `{name}`");
279-
let empty = IndexSet::new();
280287
let import_map_key = match item {
281288
WorldItem::Function(_) | WorldItem::Type(_) => None,
282289
WorldItem::Interface { .. } => Some(name),
@@ -285,9 +292,6 @@ impl<'a> ComponentWorld<'a> {
285292
WorldItem::Function(_) | WorldItem::Type(_) => None,
286293
WorldItem::Interface { id, .. } => Some(*id),
287294
};
288-
let required = required
289-
.get(import_map_key.as_deref().unwrap_or(BARE_FUNC_MODULE_NAME))
290-
.unwrap_or(&empty);
291295
let interface = import_map
292296
.entry(import_map_key)
293297
.or_insert_with(|| ImportedInterface {
@@ -324,10 +328,10 @@ impl<'a> ComponentWorld<'a> {
324328

325329
// First use the previously calculated metadata about live imports to
326330
// determine the set of live types in those imports.
327-
self.add_live_imports(world, &self.info.required_imports, &mut live);
331+
self.add_live_imports(world, &self.info.imports, &mut live);
328332
for (adapter_name, adapter) in self.adapters.iter() {
329333
log::trace!("processing adapter `{adapter_name}`");
330-
self.add_live_imports(world, &adapter.info.required_imports, &mut live);
334+
self.add_live_imports(world, &adapter.info.imports, &mut live);
331335
}
332336

333337
// Next any imported types used by an export must also be considered
@@ -379,43 +383,28 @@ impl<'a> ComponentWorld<'a> {
379383
}
380384
}
381385

382-
fn add_live_imports<S>(
383-
&self,
384-
world: WorldId,
385-
required: &IndexMap<S, RequiredImports>,
386-
live: &mut LiveTypes,
387-
) where
388-
S: Borrow<str> + Hash + Eq,
389-
{
386+
fn add_live_imports(&self, world: WorldId, imports: &ImportMap, live: &mut LiveTypes) {
390387
let resolve = &self.encoder.metadata.resolve;
391388
for (name, item) in resolve.worlds[world].imports.iter() {
392389
let name = resolve.name_world_key(name);
393390
match item {
394391
WorldItem::Function(func) => {
395-
let required = match required.get(BARE_FUNC_MODULE_NAME) {
396-
Some(set) => set,
397-
None => continue,
398-
};
399-
if !required.funcs.contains(name.as_str()) {
392+
if !imports.uses_toplevel_func(name.as_str()) {
400393
continue;
401394
}
402395
log::trace!("add live function import `{name}`");
403396
live.add_func(resolve, func);
404397
}
405398
WorldItem::Interface { id, .. } => {
406-
let required = match required.get(name.as_str()) {
407-
Some(set) => set,
408-
None => continue,
409-
};
410399
log::trace!("add live interface import `{name}`");
411400
for (name, func) in resolve.interfaces[*id].functions.iter() {
412-
if required.funcs.contains(name.as_str()) {
401+
if imports.uses_interface_func(*id, name.as_str()) {
413402
log::trace!("add live func `{name}`");
414403
live.add_func(resolve, func);
415404
}
416405
}
417-
for (name, ty) in resolve.interfaces[*id].types.iter() {
418-
if required.resources.contains(name.as_str()) {
406+
for (_name, ty) in resolve.interfaces[*id].types.iter() {
407+
if imports.uses_imported_resource_drop(*ty) {
419408
live.add_type_id(resolve, *ty);
420409
}
421410
}
@@ -458,10 +447,17 @@ impl<'a> ComponentWorld<'a> {
458447
}
459448
}
460449

450+
#[derive(Default)]
451+
struct Required<'a> {
452+
interface_funcs: IndexMap<Option<InterfaceId>, IndexSet<&'a str>>,
453+
resource_drops: IndexSet<TypeId>,
454+
}
455+
461456
impl ImportedInterface {
462-
fn add_func(&mut self, required: &IndexSet<&str>, resolve: &Resolve, func: &Function) {
463-
if !required.contains(func.name.as_str()) {
464-
return;
457+
fn add_func(&mut self, required: &Required<'_>, resolve: &Resolve, func: &Function) {
458+
match required.interface_funcs.get(&self.interface) {
459+
Some(set) if set.contains(func.name.as_str()) => {}
460+
_ => return,
465461
}
466462
log::trace!("add func {}", func.name);
467463
let options = RequiredOptions::for_import(resolve, func);
@@ -476,21 +472,18 @@ impl ImportedInterface {
476472
assert!(prev.is_none());
477473
}
478474

479-
fn add_type(&mut self, required: &IndexSet<&str>, resolve: &Resolve, id: TypeId) {
475+
fn add_type(&mut self, required: &Required<'_>, resolve: &Resolve, id: TypeId) {
480476
let ty = &resolve.types[id];
481477
match &ty.kind {
482478
TypeDefKind::Resource => {}
483479
_ => return,
484480
}
485481
let name = ty.name.as_deref().expect("resources must be named");
486482

487-
let mut maybe_add = |name: String, lowering: Lowering| {
488-
if !required.contains(name.as_str()) {
489-
return;
490-
}
491-
let prev = self.lowerings.insert(name, lowering);
483+
if required.resource_drops.contains(&id) {
484+
let name = format!("{RESOURCE_DROP}{name}");
485+
let prev = self.lowerings.insert(name, Lowering::ResourceDrop(id));
492486
assert!(prev.is_none());
493-
};
494-
maybe_add(format!("{RESOURCE_DROP}{name}"), Lowering::ResourceDrop(id));
487+
}
495488
}
496489
}

0 commit comments

Comments
 (0)