Skip to content

Commit

Permalink
Implement support for the standard mangling scheme in the component m…
Browse files Browse the repository at this point in the history
…odel (bytecodealliance#1828)

* Use `ExportMap` for naming component exports

Use the map's metadata to determine what the core wasm name is for each
export instead of recalculating it in the encoder which would duplicate
work done in validation.

* Decouple import/export encodings from core names

This commit decouples the string encodings listed for imports/exports
from their core wasm names to instead being registered with WIT-level
constructs instead. Previously the parsing phase of a module would
register a string encoding for core wasm import/export names but this
subverted the logic of validation where detection of how exactly an
import lines up with WIT-level items is determined. The goal of this
commit is to decouple this relation.

Worlds are encoding into custom sections with a known string encoding
for all imports/exports of that world. This can possibly differ for
different parts of an application to theoretically enable one interface
to be imported with UTF-8 and another with UTF-16. This means that
encodings are tracked per-import/export rather than per-world.
Previously this process would assume that there is a single name for an
import's/export's encoding but with new detection and names coming down
the line this is no longer going to be the case. For example with the
new names in WebAssembly/component-model#378 there are new names to be
supported meaning that there's not one single name to register encodings
with.

To help bridge this gap the abstraction here is changed to where
metadata for a module records string encodings on a WIT level, for
example per WIT import/export, instead of per core wasm import/export.
Then during encoding of a component the WIT level constructs are matched
up instead of the core names to determine the string encoding in the
lift/lower operation.

The end goal is that the connection between core wasm names and WIT
names continues to be decoupled where validation is the only location
concerned about this.

* 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.

* Support the new name mangling scheme for components

This commit adds support for WebAssembly/component-model#378 to
`wit-component`. Notably a new set of alternative names are registered
and recognized during the module-to-component translation process.
Support for the previous set of names are all preserved and will
continue to be supported for some time. The new names are, for now,
recognized in parallel to the old names.

This involved some refactoring to the validation part of `wit-component`
and further encapsulation of various names to one small location instead
of a shared location for everywhere else to use as well.

* Update `embed --dummy` with new ABI names

This commit updates the `wasm-tools component embed` subcommand,
specifically the `--dummy` flag. This flag now uses the new "standard32"
names for the core module that is generated. Additionally a new
`--dummy-names $FOO` option has been added to enable generating the old
names as well as the new names.

Utilities have also been added to `Resolve` for bindings generators to
avoid hardcoding ABI names and instead use the add categories of
imports/exports to name items.

* Add a flag to require the new mangling scheme

This commit adds a new `--reject-legacy-names` flag to the `wasm-tools
component new` subcommand which can be used to disable support for the
legacy naming scheme. This is intended to help with testing out the new
naming scheme for tools and to help evaluate in the future if it's
theoretically possible to remove support for the old naming scheme.

* Fix tests

* Update some test expectations
  • Loading branch information
alexcrichton authored Oct 2, 2024
1 parent a0004ed commit 42e1c77
Show file tree
Hide file tree
Showing 24 changed files with 1,825 additions and 590 deletions.
148 changes: 112 additions & 36 deletions crates/wit-component/src/dummy.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use wit_parser::abi::{AbiVariant, WasmType};
use wit_parser::{Function, Resolve, TypeDefKind, TypeId, WorldId, WorldItem};
use wit_parser::{
Function, Mangling, Resolve, ResourceIntrinsic, TypeDefKind, TypeId, WasmExport, WasmImport,
WorldId, WorldItem, WorldKey,
};

/// Generate a dummy implementation core Wasm module for a given WIT document
pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec<u8> {
pub fn dummy_module(resolve: &Resolve, world: WorldId, mangling: Mangling) -> Vec<u8> {
let world = &resolve.worlds[world];
let mut wat = String::new();
wat.push_str("(module\n");
Expand All @@ -11,27 +14,41 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec<u8> {
WorldItem::Function(func) => {
let sig = resolve.wasm_signature(AbiVariant::GuestImport, func);

wat.push_str(&format!("(import \"$root\" \"{}\" (func", func.name));
let (module, name) = resolve.wasm_import_name(
mangling,
WasmImport::Func {
interface: None,
func,
},
);

wat.push_str(&format!("(import {module:?} {name:?} (func"));
push_tys(&mut wat, "param", &sig.params);
push_tys(&mut wat, "result", &sig.results);
wat.push_str("))\n");
}
WorldItem::Interface { id: import, .. } => {
let name = resolve.name_world_key(name);
for (_, func) in resolve.interfaces[*import].functions.iter() {
let sig = resolve.wasm_signature(AbiVariant::GuestImport, func);

wat.push_str(&format!("(import \"{name}\" \"{}\" (func", func.name));
let (module, name) = resolve.wasm_import_name(
mangling,
WasmImport::Func {
interface: Some(name),
func,
},
);
wat.push_str(&format!("(import {module:?} {name:?} (func"));
push_tys(&mut wat, "param", &sig.params);
push_tys(&mut wat, "result", &sig.results);
wat.push_str("))\n");
}
for (_, ty) in resolve.interfaces[*import].types.iter() {
push_resource_func_imports(&mut wat, resolve, &name, *ty);
push_resource_func_imports(&mut wat, resolve, Some(name), *ty, mangling);
}
}
WorldItem::Type(id) => {
push_resource_func_imports(&mut wat, resolve, "$root", *id);
push_resource_func_imports(&mut wat, resolve, None, *id, mangling);
}
}
}
Expand All @@ -42,77 +59,136 @@ pub fn dummy_module(resolve: &Resolve, world: WorldId) -> Vec<u8> {
WorldItem::Interface { id, .. } => *id,
_ => continue,
};
let module = format!("[export]{}", resolve.name_world_key(name));
for (name, ty) in resolve.interfaces[export].types.iter() {
let ty = &resolve.types[*ty];
for resource in resolve.interfaces[export].types.values().copied() {
let ty = &resolve.types[resource];
match ty.kind {
TypeDefKind::Resource => {}
_ => continue,
}
wat.push_str(&format!(
"\
(import \"{module}\" \"[resource-drop]{name}\" (func (param i32)))
(import \"{module}\" \"[resource-new]{name}\" (func (param i32) (result i32)))
(import \"{module}\" \"[resource-rep]{name}\" (func (param i32) (result i32)))
"
));
let intrinsics = [
(ResourceIntrinsic::ExportedDrop, "(func (param i32))"),
(
ResourceIntrinsic::ExportedNew,
"(func (param i32) (result i32))",
),
(
ResourceIntrinsic::ExportedRep,
"(func (param i32) (result i32))",
),
];
for (intrinsic, sig) in intrinsics {
let (module, name) = resolve.wasm_import_name(
mangling,
WasmImport::ResourceIntrinsic {
interface: Some(name),
resource,
intrinsic,
},
);
wat.push_str(&format!("(import {module:?} {name:?} {sig})\n"));
}
}
}

for (name, export) in world.exports.iter() {
match export {
WorldItem::Function(func) => {
push_func(&mut wat, &func.name, resolve, func);
push_func_export(&mut wat, resolve, None, func, mangling);
}
WorldItem::Interface { id: export, .. } => {
let name = resolve.name_world_key(name);
for (_, func) in resolve.interfaces[*export].functions.iter() {
let name = func.core_export_name(Some(&name));
push_func(&mut wat, &name, resolve, func);
push_func_export(&mut wat, resolve, Some(name), func, mangling);
}

// Feign destructors for any resource that this interface
// exports
for (resource_name, ty) in resolve.interfaces[*export].types.iter() {
let ty = &resolve.types[*ty];
for resource in resolve.interfaces[*export].types.values().copied() {
let ty = &resolve.types[resource];
match ty.kind {
TypeDefKind::Resource => {}
_ => continue,
}
wat.push_str(&format!(
"(func (export \"{name}#[dtor]{resource_name}\") (param i32))"
));
let name = resolve.wasm_export_name(
mangling,
WasmExport::ResourceDtor {
interface: name,
resource,
},
);
wat.push_str(&format!("(func (export {name:?}) (param i32))"));
}
}
WorldItem::Type(_) => {}
}
}

wat.push_str("(memory (export \"memory\") 0)\n");
wat.push_str(
"(func (export \"cabi_realloc\") (param i32 i32 i32 i32) (result i32) unreachable)\n",
);
let memory = resolve.wasm_export_name(mangling, WasmExport::Memory);
wat.push_str(&format!("(memory (export {memory:?}) 0)\n"));
let realloc = resolve.wasm_export_name(mangling, WasmExport::Realloc);
wat.push_str(&format!(
"(func (export {realloc:?}) (param i32 i32 i32 i32) (result i32) unreachable)\n"
));
let initialize = resolve.wasm_export_name(mangling, WasmExport::Initialize);
wat.push_str(&format!("(func (export {initialize:?}))"));
wat.push_str(")\n");

return wat::parse_str(&wat).unwrap();

fn push_resource_func_imports(wat: &mut String, resolve: &Resolve, module: &str, ty: TypeId) {
let ty = &resolve.types[ty];
fn push_resource_func_imports(
wat: &mut String,
resolve: &Resolve,
interface: Option<&WorldKey>,
resource: TypeId,
mangling: Mangling,
) {
let ty = &resolve.types[resource];
match ty.kind {
TypeDefKind::Resource => {}
_ => return,
}
let name = ty.name.as_ref().unwrap();
wat.push_str(&format!("(import \"{module}\" \"[resource-drop]{name}\""));
wat.push_str(" (func (param i32)))\n");
let (module, name) = resolve.wasm_import_name(
mangling,
WasmImport::ResourceIntrinsic {
interface,
resource,
intrinsic: ResourceIntrinsic::ImportedDrop,
},
);
wat.push_str(&format!("(import {module:?} {name:?} (func (param i32)))"));
}

fn push_func(wat: &mut String, name: &str, resolve: &Resolve, func: &Function) {
fn push_func_export(
wat: &mut String,
resolve: &Resolve,
interface: Option<&WorldKey>,
func: &Function,
mangling: Mangling,
) {
let sig = resolve.wasm_signature(AbiVariant::GuestExport, func);
let name = resolve.wasm_export_name(
mangling,
WasmExport::Func {
interface,
func,
post_return: false,
},
);
wat.push_str(&format!("(func (export \"{name}\")"));
push_tys(wat, "param", &sig.params);
push_tys(wat, "result", &sig.results);
wat.push_str(" unreachable)\n");

let name = resolve.wasm_export_name(
mangling,
WasmExport::Func {
interface,
func,
post_return: true,
},
);
wat.push_str(&format!("(func (export \"{name}\")"));
push_tys(wat, "param", &sig.results);
wat.push_str(")\n");
}

fn push_tys(dst: &mut String, desc: &str, params: &[WasmType]) {
Expand Down
Loading

0 comments on commit 42e1c77

Please sign in to comment.