Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions crates/wasmparser/src/validator/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4663,7 +4663,7 @@ impl ComponentNameContext {
}
}

if let Some(implements) = implements {
let implements_name = if let Some(implements) = implements {
require_feature::cm_implements(
*features,
"the `cm-implements` feature is not active",
Expand All @@ -4685,7 +4685,10 @@ impl ComponentNameContext {
ComponentNameKind::Interface(_) => {}
_ => bail!(offset, "name `{implements}` must be an interface"),
}
}
Some(implements)
} else {
None
};

if let Some(_) = version_suffix {
require_feature::cm_canon_names(
Expand All @@ -4709,8 +4712,15 @@ impl ComponentNameContext {

// Validate that the kebab name, if it has structure such as
// `[method]a.b`, is indeed valid with respect to known resources.
self.validate(&kebab, version_suffix, ty, types, offset)
.with_context(|| format!("{} name `{kebab}` is not valid", kind.desc()))?;
self.validate(
&kebab,
version_suffix,
implements_name.as_ref(),
ty,
types,
offset,
)
.with_context(|| format!("{} name `{kebab}` is not valid", kind.desc()))?;

// Top-level kebab-names must all be unique, even between both imports
// and exports ot a component. For those names consult the `kebab_names`
Expand Down Expand Up @@ -4753,6 +4763,7 @@ impl ComponentNameContext {
&self,
name: &ComponentName,
version_suffix: Option<&str>,
implements: Option<&ComponentName>,
ty: &ComponentEntityType,
types: &TypeAlloc,
offset: u64,
Expand All @@ -4765,6 +4776,16 @@ impl ComponentNameContext {
Ok(&types[id])
};

// When an `implements` is present, validate the `version_suffix`
// against the implements interface name rather than the main name.
if let Some(implements) = implements {
if let ComponentNameKind::Interface(iface) = implements.kind() {
if let Err(e) = iface.version(version_suffix) {
bail!(offset, "invalid interface version: {e}");
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could this validation move up to the above method? I think it'd be a bit clearer to perform this validation when implements is specified rather than deferring it to happen later here. That'd probably require validating the version_suffix field before implements and reorderin the above if statements, but that should be fine to do.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done. Neither way is perfect. We validate version_suffix with ComponentName here. Ideally, we would also validate the suffix with implements here. But moving up does make a smaller diff.


match name.kind() {
// No validation necessary for these styles of names
ComponentNameKind::Label(_)
Expand Down
72 changes: 62 additions & 10 deletions crates/wit-component/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ const TLS_BASE_SET: &str = "$set-tls-base";
pub(crate) mod fixup;

mod wit;
pub use wit::{encode, encode_world};
pub use wit::{encode, encode_with_options, encode_world};

mod types;
use types::{InstanceTypeEncoder, RootTypeEncoder, TypeEncodingMaps, ValtypeEncoder};
Expand Down Expand Up @@ -609,15 +609,39 @@ impl<'a> EncodingState<'a> {
let instance_type_idx = self
.component
.type_instance(Some(&format!("ty-{name}")), &ty);
let instance_idx = self.component.import(

let extern_name = if self.info.encoder.emit_canonical_names {
let name = resolve
.canonicalized_id_of(interface_id)
.unwrap_or_else(|| name.to_string());
let implements = info
.implements
.map(|id| resolve.canonicalized_id_of(id).unwrap());
let suffix_id = if let Some(id) = info.implements {
id
} else {
interface_id
};
wasm_encoder::ComponentExternName {
name: name.into(),
implements: implements.map(|s| s.into()),
external_id: info.external_id.as_deref().map(|s| s.into()),
version_suffix: resolve.version_suffix_of(suffix_id).map(|s| s.into()),
}
} else {
wasm_encoder::ComponentExternName {
name: name.into(),
implements: info.implements.as_deref().map(|s| s.into()),
implements: info
.implements
.as_ref()
.map(|s| resolve.id_of(*s).unwrap().into()),
external_id: info.external_id.as_deref().map(|s| s.into()),
version_suffix: None,
},
ComponentTypeRef::Instance(instance_type_idx),
);
}
};
let instance_idx = self
.component
.import(extern_name, ComponentTypeRef::Instance(instance_type_idx));
let prev = self.instances.insert(interface_id, instance_idx);
assert!(prev.is_none());
Ok(())
Expand Down Expand Up @@ -762,7 +786,11 @@ impl<'a> EncodingState<'a> {
let world = &resolve.worlds[self.info.encoder.metadata.world];

for export_name in exports {
let export_string = resolve.name_world_key(export_name);
let export_string = if self.info.encoder.emit_canonical_names {
resolve.name_canonicalized_world_key(export_name)
} else {
resolve.name_world_key(export_name)
};
match &world.exports[export_name] {
WorldItem::Function(func) => {
let ty = self
Expand Down Expand Up @@ -993,13 +1021,24 @@ impl<'a> EncodingState<'a> {
component_index,
imports,
);
let idx = self.component.export(
let implements = resolve.implements_interface(key, item);
let extern_name = if self.info.encoder.emit_canonical_names {
wasm_encoder::ComponentExternName {
name: export_name.into(),
implements: resolve.implements_value(key, item).map(|s| s.into()),
implements: implements.map(|id| resolve.canonicalized_id_of(id).unwrap().into()),
external_id: resolve.external_id_value(key, item).map(|s| s.into()),
version_suffix: resolve.version_suffix_value(key, item).map(|s| s.into()),
}
} else {
wasm_encoder::ComponentExternName {
name: export_name.into(),
implements: implements.map(|id| resolve.id_of(id).unwrap().into()),
external_id: resolve.external_id_value(key, item).map(|s| s.into()),
version_suffix: None,
},
}
};
let idx = self.component.export(
extern_name,
ComponentExportKind::Instance,
instance_index,
None,
Expand Down Expand Up @@ -3290,6 +3329,7 @@ pub struct ComponentEncoder {
pub(super) reject_legacy_names: bool,
debug_names: bool,
shim_return_call_ref: bool,
emit_canonical_names: bool,
}

impl ComponentEncoder {
Expand Down Expand Up @@ -3357,6 +3397,18 @@ impl ComponentEncoder {
self
}

/// Sets whether to emit canonical interface names in the component binary.
///
/// When enabled, import/export names use canonical version prefixes (e.g.,
/// `wasi:cli/exit@0.2` instead of `wasi:cli/exit@0.2.1`) and the
/// `version_suffix` field is populated.
///
/// This is disabled by default.
pub fn emit_canonical_names(&mut self, emit: bool) -> &mut Self {
self.emit_canonical_names = emit;
self
}

/// Sets whether to reject the historical mangling/name scheme for core wasm
/// imports/exports as they map to the component model.
///
Expand Down
87 changes: 70 additions & 17 deletions crates/wit-component/src/encoding/wit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,16 @@ use wit_parser::*;
/// The binary returned can be [`decode`d](crate::decode) to recover the WIT
/// package provided.
pub fn encode(resolve: &Resolve, package: PackageId) -> Result<Vec<u8>> {
let mut component = encode_component(resolve, package)?;
encode_with_options(resolve, package, false)
}

/// Same as [`encode`] but with an option to emit canonical interface names.
pub fn encode_with_options(
resolve: &Resolve,
package: PackageId,
canonical_names: bool,
Comment thread
alexcrichton marked this conversation as resolved.
Outdated
) -> Result<Vec<u8>> {
let mut component = encode_component_with_options(resolve, package, canonical_names)?;
component.raw_custom_section(&crate::base_producers().raw_custom_section());
Ok(component.finish())
}
Expand All @@ -48,11 +57,16 @@ pub fn encode(resolve: &Resolve, package: PackageId) -> Result<Vec<u8>> {
///
/// The binary returned can be [`decode`d](crate::decode) to recover the WIT
/// package provided.
pub fn encode_component(resolve: &Resolve, package: PackageId) -> Result<ComponentBuilder> {
pub fn encode_component_with_options(
resolve: &Resolve,
package: PackageId,
canonical_names: bool,
) -> Result<ComponentBuilder> {
let mut encoder = Encoder {
component: ComponentBuilder::default(),
resolve,
package,
canonical_names,
};
encoder.run()?;

Expand All @@ -67,6 +81,15 @@ pub fn encode_component(resolve: &Resolve, package: PackageId) -> Result<Compone

/// Encodes a `world` as a component type.
pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result<ComponentType> {
encode_world_with_options(resolve, world_id, false)
}

/// Same as [`encode_world`] but with an option to emit canonical names.
pub fn encode_world_with_options(
resolve: &Resolve,
world_id: WorldId,
canonical_names: bool,
) -> Result<ComponentType> {
let mut component = InterfaceEncoder::new(resolve);
let world = &resolve.worlds[world_id];
log::trace!("encoding world {}", world.name);
Expand All @@ -93,9 +116,10 @@ pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result<ComponentTyp
continue;
}
};
component
.outer
.import(component_extern_name(resolve, key, import), ty);
component.outer.import(
component_extern_name(resolve, key, import, canonical_names),
ty,
);
}
// Encode the exports
for (key, export) in world.exports.iter() {
Expand All @@ -113,9 +137,10 @@ pub fn encode_world(resolve: &Resolve, world_id: WorldId) -> Result<ComponentTyp
}
WorldItem::Type { .. } => unreachable!(),
};
component
.outer
.export(component_extern_name(resolve, key, export), ty);
component.outer.export(
component_extern_name(resolve, key, export, canonical_names),
ty,
);
}

Ok(component.outer)
Expand All @@ -125,19 +150,31 @@ fn component_extern_name(
resolve: &Resolve,
key: &WorldKey,
item: &WorldItem,
canonical_names: bool,
) -> wasm_encoder::ComponentExternName<'static> {
ComponentExternName {
name: resolve.name_world_key(key).into(),
implements: resolve.implements_value(key, item).map(|s| s.into()),
external_id: resolve.external_id_value(key, item).map(|s| s.into()),
version_suffix: None,
let implements = resolve.implements_interface(key, item);
if canonical_names {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

In a previous review I was curiuos if it would be possible to deduplicate the number of places that a canonical-names option was taken into account and a ComponentExternName were created. I count currently four different locations doing very similar things:

  1. here
  2. below in this file in for interface in interfaces
  3. in encode_interface_import in encoding.rs
  4. in encode_interface_export in encoding.rs (split across two functions)

Were you able to take a look and see if these locations could be unified? Is there perhaps one, or maybe two at most, helpers that could be used to construct these names?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Will think about it tomorrow.

ComponentExternName {
name: resolve.name_canonicalized_world_key(key).into(),
implements: implements.map(|id| resolve.canonicalized_id_of(id).unwrap().into()),
external_id: resolve.external_id_value(key, item).map(|s| s.into()),
version_suffix: resolve.version_suffix_value(key, item).map(|s| s.into()),
}
} else {
ComponentExternName {
name: resolve.name_world_key(key).into(),
implements: implements.map(|id| resolve.id_of(id).unwrap().into()),
external_id: resolve.external_id_value(key, item).map(|s| s.into()),
version_suffix: None,
}
}
}

struct Encoder<'a> {
component: ComponentBuilder,
resolve: &'a Resolve,
package: PackageId,
canonical_names: bool,
}

impl Encoder<'_> {
Expand All @@ -153,7 +190,8 @@ impl Encoder<'_> {
// For each `world` encode it directly as a component and then create a
// wrapper component that exports that component.
for (name, &world) in self.resolve.packages[self.package].worlds.iter() {
let component_ty = encode_world(self.resolve, world)?;
let component_ty =
encode_world_with_options(self.resolve, world, self.canonical_names)?;

let world = &self.resolve.worlds[world];
let mut wrapper = ComponentType::new();
Expand Down Expand Up @@ -197,11 +235,24 @@ impl Encoder<'_> {
for interface in interfaces {
encoder.interface = Some(interface);
let iface = &self.resolve.interfaces[interface];
let name = self.resolve.id_of(interface).unwrap();
let extern_name = if self.canonical_names {
let name = self.resolve.canonicalized_id_of(interface).unwrap();
let version_suffix = self.resolve.version_suffix_of(interface);
ComponentExternName {
name: name.into(),
implements: None,
external_id: None,
version_suffix: version_suffix.map(|s| s.into()),
}
} else {
ComponentExternName::from(self.resolve.id_of(interface).unwrap())
};
if interface == id {
let idx = encoder.encode_instance(interface)?;
log::trace!("exporting self as {idx}");
encoder.outer.export(name, ComponentTypeRef::Instance(idx));
encoder
.outer
.export(extern_name, ComponentTypeRef::Instance(idx));
} else {
encoder.push_instance();
for (_, id) in iface.types.iter() {
Expand All @@ -212,7 +263,9 @@ impl Encoder<'_> {
encoder.outer.ty().instance(&instance);
encoder.import_map.insert(interface, encoder.instances);
encoder.instances += 1;
encoder.outer.import(name, ComponentTypeRef::Instance(idx));
encoder
.outer
.import(extern_name, ComponentTypeRef::Instance(idx));
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/wit-component/src/encoding/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ pub struct ComponentWorld<'a> {
pub struct ImportedInterface {
pub lowerings: IndexMap<(String, AbiVariant), Lowering>,
pub interface: Option<InterfaceId>,
pub implements: Option<String>,
pub implements: Option<InterfaceId>,
pub external_id: Option<String>,
}

Expand Down Expand Up @@ -293,7 +293,7 @@ impl<'a> ComponentWorld<'a> {
WorldItem::Function(_) | WorldItem::Type { .. } => None,
WorldItem::Interface { id, .. } => Some(*id),
};
let implements = resolve.implements_value(key, item);
let implements = resolve.implements_interface(key, item);
// Note that `external_id` is only tracked for interface imports
// here. World-level functions and types all share the `None` entry
// in `import_map` but each item can have its own `external-id`
Expand All @@ -307,7 +307,7 @@ impl<'a> ComponentWorld<'a> {
.or_insert_with(|| ImportedInterface {
interface: interface_id,
lowerings: Default::default(),
implements: implements.clone(),
implements,
external_id: external_id.clone(),
});
assert_eq!(interface.interface, interface_id);
Expand Down
2 changes: 1 addition & 1 deletion crates/wit-component/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod printing;
mod targets;
mod validation;

pub use encoding::{ComponentEncoder, LibraryInfo, encode};
pub use encoding::{ComponentEncoder, LibraryInfo, encode, encode_with_options};
pub use linking::Linker;
pub use printing::*;
pub use targets::*;
Expand Down
4 changes: 2 additions & 2 deletions crates/wit-component/src/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2528,8 +2528,8 @@ impl NameMangling for Legacy {
};

// Test if the two semver versions are compatible
let module_compat = PackageName::version_compat_track(&module_version);
let pkg_compat = PackageName::version_compat_track(pkg_version);
let (module_compat, _) = PackageName::version_compat_track(&module_version);
let (pkg_compat, _) = PackageName::version_compat_track(pkg_version);
if module_compat == pkg_compat {
return Ok((key.clone(), id));
}
Expand Down
Loading
Loading