Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
69 changes: 51 additions & 18 deletions crates/wit-component/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@
//! otherwise there's no way to run a `wasi_snapshot_preview1` module within the
//! component model.

use crate::StringEncoding;
use crate::metadata::{self, Bindgen, ModuleMetadata};
use crate::validation::{
Export, ExportMap, Import, ImportInstance, ImportMap, PayloadInfo, PayloadType,
};
use crate::{SemverCompat, StringEncoding};
use anyhow::{Context, Result, anyhow, bail};
use indexmap::{IndexMap, IndexSet};
use std::borrow::Cow;
Expand Down Expand Up @@ -577,12 +577,18 @@ impl<'a> EncodingState<'a> {
let instance_type_idx = self
.component
.type_instance(Some(&format!("ty-{name}")), &ty);
let version_suffix = if resolve.use_canonical_names {
resolve.version_suffix_of(interface_id)
} else {
None
};

let instance_idx = self.component.import(
wasm_encoder::ComponentExternName {
name: name.into(),
implements: info.implements.as_deref().map(|s| s.into()),
external_id: info.external_id.as_deref().map(|s| s.into()),
version_suffix: None,
version_suffix: version_suffix.as_deref().map(|s| s.into()),
},
ComponentTypeRef::Instance(instance_type_idx),
);
Expand Down Expand Up @@ -746,7 +752,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 resolve.use_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 @@ -977,12 +987,18 @@ impl<'a> EncodingState<'a> {
component_index,
imports,
);
let version_suffix = if resolve.use_canonical_names {
resolve.version_suffix_of(export)
} else {
None
};

let idx = self.component.export(
wasm_encoder::ComponentExternName {
name: export_name.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,
version_suffix: version_suffix.as_deref().map(|s| s.into()),
},
ComponentExportKind::Instance,
instance_index,
Expand Down Expand Up @@ -1808,7 +1824,13 @@ impl<'a> EncodingState<'a> {
self.materialize_wit_import(
shims,
for_module,
iface.map(|_| resolve.name_world_key(key)),
iface.map(|_| {
if resolve.use_canonical_names {
resolve.name_canonicalized_world_key(key)
} else {
resolve.name_world_key(key)
}
}),
&format!("{name}_drop"),
key,
AbiVariant::GuestImport,
Expand Down Expand Up @@ -1978,7 +2000,11 @@ impl<'a> EncodingState<'a> {
Import::InterfaceFunc(key, _, name, abi) => self.materialize_wit_import(
shims,
for_module,
Some(resolve.name_world_key(key)),
Some(if resolve.use_canonical_names {
resolve.name_canonicalized_world_key(key)
} else {
resolve.name_world_key(key)
}),
name,
key,
*abi,
Expand Down Expand Up @@ -3025,7 +3051,11 @@ impl<'a> Shims<'a> {
field,
key,
name,
Some(resolve.name_world_key(key)),
Some(if resolve.use_canonical_names {
resolve.name_canonicalized_world_key(key)
} else {
resolve.name_world_key(key)
}),
*abi,
)?;
}
Expand Down Expand Up @@ -3300,7 +3330,7 @@ pub struct ComponentEncoder {
pub(super) adapters: IndexMap<String, Adapter>,
import_name_map: HashMap<String, String>,
realloc_via_memory_grow: bool,
merge_imports_based_on_semver: Option<bool>,
semver_compat: SemverCompat,
pub(super) reject_legacy_names: bool,
debug_names: bool,
}
Expand Down Expand Up @@ -3336,7 +3366,9 @@ impl ComponentEncoder {
}

fn merge_metadata(&mut self, metadata: Bindgen) -> Result<IndexSet<WorldKey>> {
self.metadata.merge(metadata)
let result = self.metadata.merge(metadata);
self.metadata.resolve.use_canonical_names = self.semver_compat == SemverCompat::Canonical;
result
}

/// Sets whether or not the encoder will validate its output.
Expand All @@ -3351,15 +3383,16 @@ impl ComponentEncoder {
self
}

/// Sets whether to merge imports based on semver to the specified value.
///
/// This affects how when to WIT worlds are merged together, for example
/// from two different libraries, whether their imports are unified when the
/// semver version ranges for interface allow it.
/// Sets the semver compatibility mode for this encoder.
///
/// This is enabled by default.
pub fn merge_imports_based_on_semver(mut self, merge: bool) -> Self {
self.merge_imports_based_on_semver = Some(merge);
/// - `SemverCompat::None`: exact version matching, no merging. Same as the old flag
/// `merge_imports_based_on_semver(false)`.
/// - `SemverCompat::Merge`: merge imports based on semver.
/// Same as the old flag `merge_imports_based_on_semver(true)`.
/// This is the default behavior.
/// - `SemverCompat::Canonical`: merge imports based on the canonical version prefixes.
pub fn semver_compat(mut self, compat: SemverCompat) -> Self {
self.semver_compat = compat;
self
}

Expand Down Expand Up @@ -3497,7 +3530,7 @@ impl ComponentEncoder {
bail!("a module is required when encoding a component");
}

if self.merge_imports_based_on_semver.unwrap_or(true) {
if self.semver_compat != SemverCompat::None {
self.metadata
.resolve
.merge_world_imports_based_on_semver(self.metadata.world)?;
Expand Down
41 changes: 36 additions & 5 deletions crates/wit-component/src/encoding/wit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,11 +126,22 @@ fn component_extern_name(
key: &WorldKey,
item: &WorldItem,
) -> wasm_encoder::ComponentExternName<'static> {
let (name, version_suffix) = if resolve.use_canonical_names {
let name = resolve.name_canonicalized_world_key(key);
let suffix = match key {
WorldKey::Interface(id) => resolve.version_suffix_of(*id),
WorldKey::Name(_) => None,
};
(name, suffix)
} else {
(resolve.name_world_key(key), None)
};

ComponentExternName {
name: resolve.name_world_key(key).into(),
name: name.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,
version_suffix: version_suffix.map(|s| s.into()),
}
}

Expand Down Expand Up @@ -197,11 +208,29 @@ 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.resolve.use_canonical_names {
let name = self.resolve.canonicalized_id_of(interface).unwrap();
let suffix = self.resolve.version_suffix_of(interface);
ComponentExternName {
name: name.into(),
implements: None,
external_id: None,
version_suffix: suffix.map(|s| s.into()),
}
} else {
ComponentExternName {
name: self.resolve.id_of(interface).unwrap().into(),
implements: None,
external_id: None,
version_suffix: None,
}
};
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 +241,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: 5 additions & 1 deletion crates/wit-component/src/encoding/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,11 @@ impl<'a> ComponentWorld<'a> {
item: &WorldItem,
required: &Required<'_>,
) -> Result<()> {
let name = resolve.name_world_key(key);
let name = if resolve.use_canonical_names {
resolve.name_canonicalized_world_key(key)
} else {
resolve.name_world_key(key)
};
log::trace!("register import `{name}`");
let import_map_key = match item {
WorldItem::Function(_) | WorldItem::Type { .. } => None,
Expand Down
39 changes: 39 additions & 0 deletions crates/wit-component/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,45 @@ impl From<StringEncoding> for wasm_encoder::CanonicalOption {
}
}

/// Controls how semver is used when resolving and encoding interfaces.
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum SemverCompat {
/// Exact version matching everywhere. No merging or canonicalization.
None,
/// Merge imports based on semver. This is the default behavior.
/// Package identity still use exact versions.
#[default]
Merge,
/// Merge imports based on the canonical version prefixes.
/// Package identity uses canonical version prefixes only.
Canonical,
}

impl Display for SemverCompat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SemverCompat::None => write!(f, "none"),
SemverCompat::Merge => write!(f, "merge"),
SemverCompat::Canonical => write!(f, "canonical"),
}
}
}

impl FromStr for SemverCompat {
type Err = anyhow::Error;

fn from_str(s: &str) -> Result<Self> {
match s {
"none" => Ok(SemverCompat::None),
"merge" => Ok(SemverCompat::Merge),
"canonical" => Ok(SemverCompat::Canonical),
_ => {
bail!("unknown semver compat mode `{s}`, expected `none`, `merge`, or `canonical`")
}
}
}
}

/// A producer section to be added to all modules and components synthesized by
/// this crate
pub(crate) fn base_producers() -> wasm_metadata::Producers {
Expand Down
23 changes: 10 additions & 13 deletions crates/wit-component/src/linking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
//! ahead-of-time.

use {
crate::SemverCompat,
crate::encoding::{ComponentEncoder, Instance, Item, LibraryInfo, MainOrAdapter},
anyhow::{Context, Result, anyhow, bail},
indexmap::{IndexMap, IndexSet, map::Entry},
Expand Down Expand Up @@ -1682,10 +1683,8 @@ pub struct Linker {
/// If `None`, use `DEFAULT_STACK_SIZE_BYTES`.
stack_size: Option<u32>,

/// This affects how when to WIT worlds are merged together, for example
/// from two different libraries, whether their imports are unified when the
/// semver version ranges for interface allow it.
merge_imports_based_on_semver: Option<bool>,
/// Controls how semver is used when resolving and encoding interfaces.
semver_compat: Option<SemverCompat>,
}

impl Linker {
Expand Down Expand Up @@ -1739,13 +1738,11 @@ impl Linker {
self
}

/// This affects how when to WIT worlds are merged together, for example
/// from two different libraries, whether their imports are unified when the
/// semver version ranges for interface allow it.
/// Sets the semver compatibility mode for this linker.
///
/// This is enabled by default.
pub fn merge_imports_based_on_semver(mut self, merge: bool) -> Self {
self.merge_imports_based_on_semver = Some(merge);
/// See [`SemverCompat`] for available modes.
pub fn semver_compat(mut self, compat: SemverCompat) -> Self {
self.semver_compat = Some(compat);
self
}

Expand Down Expand Up @@ -1895,9 +1892,9 @@ impl Linker {
let mut encoder = ComponentEncoder::default()
.validate(self.validate)
.debug_names(self.debug_names);
if let Some(merge) = self.merge_imports_based_on_semver {
encoder = encoder.merge_imports_based_on_semver(merge);
};
if let Some(compat) = self.semver_compat {
encoder = encoder.semver_compat(compat);
}
encoder = encoder.module(&env_module)?;

for (name, module) in &self.adapters {
Expand Down
Loading