wasm_compose/
graph.rs

1//! Module for WebAssembly composition graphs.
2use crate::encoding::{CompositionGraphEncoder, TypeEncoder};
3use anyhow::{Context, Result, anyhow, bail};
4use indexmap::{IndexMap, IndexSet};
5use petgraph::{EdgeDirection, algo::toposort, graphmap::DiGraphMap};
6use std::{
7    borrow::Cow,
8    cell::RefCell,
9    collections::{HashMap, HashSet, hash_map::Entry},
10    path::{Path, PathBuf},
11    sync::atomic::{AtomicUsize, Ordering},
12};
13use wasmparser::{
14    Chunk, ComponentExternalKind, ComponentTypeRef, Encoding, Parser, Payload, ValidPayload,
15    Validator,
16    component_types::{
17        ComponentAnyTypeId, ComponentEntityType, ComponentInstanceTypeId, Remap, Remapping,
18        ResourceId, SubtypeCx,
19    },
20    names::ComponentName,
21    types::{Types, TypesRef},
22};
23
24pub(crate) fn type_desc(item: ComponentEntityType) -> &'static str {
25    match item {
26        ComponentEntityType::Instance(_) => "instance",
27        ComponentEntityType::Module(_) => "module",
28        ComponentEntityType::Func(_) => "function",
29        ComponentEntityType::Value(_) => "value",
30        ComponentEntityType::Type { .. } => "type",
31        ComponentEntityType::Component(_) => "component",
32    }
33}
34
35/// Represents a component in a composition graph.
36pub struct Component<'a> {
37    /// The name of the component.
38    pub(crate) name: String,
39    /// The path to the component file if parsed via `Component::from_file`.
40    pub(crate) path: Option<PathBuf>,
41    /// The raw bytes of the component.
42    pub(crate) bytes: Cow<'a, [u8]>,
43    /// The type information of the component.
44    pub(crate) types: Types,
45    /// The import map of the component.
46    pub(crate) imports: IndexMap<String, ComponentTypeRef>,
47    /// The export map of the component.
48    pub(crate) exports: IndexMap<String, (ComponentExternalKind, u32)>,
49}
50
51impl<'a> Component<'a> {
52    /// Constructs a new component from reading the given file.
53    pub fn from_file(
54        validator: &mut Validator,
55        name: &str,
56        path: impl AsRef<Path>,
57    ) -> Result<Self> {
58        let path = path.as_ref();
59        log::info!("parsing WebAssembly component file `{}`", path.display());
60        let component = Self::parse(
61            validator,
62            ComponentName::new(name, 0)?.to_string(),
63            Some(path.to_owned()),
64            wat::parse_file(path)
65                .with_context(|| {
66                    format!("failed to parse component `{path}`", path = path.display())
67                })?
68                .into(),
69        )
70        .with_context(|| format!("failed to parse component `{path}`", path = path.display()))?;
71
72        log::debug!(
73            "WebAssembly component `{path}` parsed:\n{component:#?}",
74            path = path.display()
75        );
76
77        Ok(component)
78    }
79
80    /// Constructs a new component from the given bytes.
81    pub fn from_bytes(
82        validator: &mut Validator,
83        name: impl Into<String>,
84        bytes: impl Into<Cow<'a, [u8]>>,
85    ) -> Result<Self> {
86        let mut bytes = bytes.into();
87
88        match wat::parse_bytes(bytes.as_ref()).context("failed to parse component")? {
89            Cow::Borrowed(_) => {
90                // Original bytes were not modified
91            }
92            Cow::Owned(v) => bytes = v.into(),
93        }
94
95        log::info!("parsing WebAssembly component from bytes");
96        let component = Self::parse(
97            validator,
98            ComponentName::new(&name.into(), 0)?.to_string(),
99            None,
100            bytes,
101        )
102        .context("failed to parse component")?;
103
104        log::debug!("WebAssembly component parsed:\n{component:#?}",);
105
106        Ok(component)
107    }
108
109    fn parse(
110        validator: &mut Validator,
111        name: String,
112        path: Option<PathBuf>,
113        bytes: Cow<'a, [u8]>,
114    ) -> Result<Self> {
115        let mut parser = Parser::new(0);
116        let mut parsers = Vec::new();
117        let mut imports = IndexMap::new();
118        let mut exports = IndexMap::new();
119
120        validator.reset();
121
122        let mut cur = bytes.as_ref();
123        loop {
124            match parser.parse(cur, true)? {
125                Chunk::Parsed { payload, consumed } => {
126                    cur = &cur[consumed..];
127
128                    match validator.payload(&payload)? {
129                        ValidPayload::Ok => {
130                            // Don't parse any sub-components or sub-modules
131                            if !parsers.is_empty() {
132                                continue;
133                            }
134
135                            match payload {
136                                Payload::Version { encoding, .. } => {
137                                    if encoding != Encoding::Component {
138                                        bail!(
139                                            "the {} is not a WebAssembly component",
140                                            if path.is_none() { "given data" } else { "file" }
141                                        );
142                                    }
143                                }
144                                Payload::ComponentImportSection(s) => {
145                                    for import in s {
146                                        let import = import?;
147                                        let name = import.name.0.to_string();
148                                        imports.insert(name, import.ty);
149                                    }
150                                }
151                                Payload::ComponentExportSection(s) => {
152                                    for export in s {
153                                        let export = export?;
154                                        let name = export.name.0.to_string();
155                                        exports.insert(name, (export.kind, export.index));
156                                    }
157                                }
158                                _ => {}
159                            }
160                        }
161                        ValidPayload::Func(_, _) => {}
162                        ValidPayload::Parser(next) => {
163                            parsers.push(parser);
164                            parser = next;
165                        }
166                        ValidPayload::End(types) => match parsers.pop() {
167                            Some(parent) => parser = parent,
168                            None => {
169                                return Ok(Component {
170                                    name,
171                                    path,
172                                    bytes,
173                                    types,
174                                    imports,
175                                    exports,
176                                });
177                            }
178                        },
179                    }
180                }
181                Chunk::NeedMoreData(_) => unreachable!(),
182            }
183        }
184    }
185
186    /// Gets the name of the component.
187    ///
188    /// Names must be unique within a composition graph.
189    pub fn name(&self) -> &str {
190        &self.name
191    }
192
193    /// Gets the path of the component.
194    ///
195    /// Returns `None` if the component was not loaded from a file.
196    pub fn path(&self) -> Option<&Path> {
197        self.path.as_deref()
198    }
199
200    /// Gets the bytes of the component.
201    pub fn bytes(&self) -> &[u8] {
202        self.bytes.as_ref()
203    }
204
205    /// Gets the type information of the component.
206    pub fn types(&self) -> TypesRef<'_> {
207        self.types.as_ref()
208    }
209
210    /// Gets an export from the component for the given export index.
211    pub fn export(
212        &self,
213        index: impl Into<ExportIndex>,
214    ) -> Option<(&str, ComponentExternalKind, u32)> {
215        let index = index.into();
216        self.exports
217            .get_index(index.0)
218            .map(|(name, (kind, index))| (name.as_str(), *kind, *index))
219    }
220
221    /// Gets an export from the component for the given export name.
222    pub fn export_by_name(&self, name: &str) -> Option<(ExportIndex, ComponentExternalKind, u32)> {
223        self.exports
224            .get_full(name)
225            .map(|(i, _, (kind, index))| (ExportIndex(i), *kind, *index))
226    }
227
228    /// Gets an iterator over the component's exports.
229    pub fn exports(
230        &self,
231    ) -> impl ExactSizeIterator<Item = (ExportIndex, &str, ComponentExternalKind, u32)> {
232        self.exports
233            .iter()
234            .enumerate()
235            .map(|(i, (name, (kind, index)))| (ExportIndex(i), name.as_str(), *kind, *index))
236    }
237
238    /// Gets an import from the component for the given import index.
239    pub fn import(&self, index: impl Into<ImportIndex>) -> Option<(&str, ComponentTypeRef)> {
240        let index = index.into();
241        self.imports
242            .get_index(index.0)
243            .map(|(name, ty)| (name.as_str(), *ty))
244    }
245
246    /// Gets an import from the component for the given import name.
247    pub fn import_by_name(&self, name: &str) -> Option<(ImportIndex, ComponentTypeRef)> {
248        self.imports
249            .get_full(name)
250            .map(|(i, _, ty)| (ImportIndex(i), *ty))
251    }
252
253    /// Gets an iterator over the component's imports.
254    pub fn imports(&self) -> impl ExactSizeIterator<Item = (ImportIndex, &str, ComponentTypeRef)> {
255        self.imports
256            .iter()
257            .enumerate()
258            .map(|(i, (name, ty))| (ImportIndex(i), name.as_str(), *ty))
259    }
260
261    pub(crate) fn ty(&self) -> wasm_encoder::ComponentType {
262        let encoder = TypeEncoder::new(self);
263
264        encoder.component(
265            &mut Default::default(),
266            self.imports()
267                .map(|(i, ..)| self.import_entity_type(i).unwrap()),
268            self.exports()
269                .map(|(i, ..)| self.export_entity_type(i).unwrap()),
270        )
271    }
272
273    pub(crate) fn export_entity_type(
274        &self,
275        index: ExportIndex,
276    ) -> Option<(&str, ComponentEntityType)> {
277        let (name, _kind, _index) = self.export(index)?;
278        Some((
279            name,
280            self.types.as_ref().component_entity_type_of_export(name)?,
281        ))
282    }
283
284    pub(crate) fn import_entity_type(
285        &self,
286        index: ImportIndex,
287    ) -> Option<(&str, ComponentEntityType)> {
288        let (name, _ty) = self.import(index)?;
289        Some((
290            name,
291            self.types.as_ref().component_entity_type_of_import(name)?,
292        ))
293    }
294
295    /// Finds a compatible instance export on the component for the given instance type.
296    pub(crate) fn find_compatible_export(
297        &self,
298        ty: ComponentInstanceTypeId,
299        types: TypesRef,
300        export_component_id: ComponentId,
301        graph: &CompositionGraph,
302    ) -> Option<ExportIndex> {
303        self.exports
304            .iter()
305            .position(|(_, (kind, index))| {
306                if *kind != ComponentExternalKind::Instance {
307                    return false;
308                }
309
310                graph.try_connection(
311                    export_component_id,
312                    ComponentEntityType::Instance(
313                        self.types.as_ref().component_instance_at(*index),
314                    ),
315                    self.types(),
316                    ComponentEntityType::Instance(ty),
317                    types,
318                )
319            })
320            .map(ExportIndex)
321    }
322
323    /// Checks to see if an instance of this component would be a
324    /// subtype of the given instance type.
325    pub(crate) fn is_instance_subtype_of(
326        &self,
327        ty: ComponentInstanceTypeId,
328        types: TypesRef,
329    ) -> bool {
330        let exports = types[ty].exports.iter();
331
332        for (k, b) in exports {
333            match self.exports.get_full(k.as_str()) {
334                Some((ai, _, _)) => {
335                    let (_, a) = self.export_entity_type(ExportIndex(ai)).unwrap();
336                    if !ComponentEntityType::is_subtype_of(&a, self.types(), b, types) {
337                        return false;
338                    }
339                }
340                None => return false,
341            }
342        }
343
344        true
345    }
346}
347
348impl std::fmt::Debug for Component<'_> {
349    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
350        f.debug_struct("Component")
351            .field("imports", &self.imports)
352            .field("exports", &self.exports)
353            .finish_non_exhaustive()
354    }
355}
356
357static NEXT_COMPONENT_ID: AtomicUsize = AtomicUsize::new(0);
358
359/// Represents an identifier of a component in a composition graph.
360#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
361pub struct ComponentId(pub usize);
362
363impl ComponentId {
364    fn next() -> Result<Self> {
365        let next = NEXT_COMPONENT_ID.fetch_add(1, Ordering::SeqCst);
366        if next == usize::MAX {
367            bail!("component limit reached");
368        }
369        Ok(Self(next))
370    }
371}
372
373impl std::fmt::Display for ComponentId {
374    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
375        write!(f, "{}", self.0)
376    }
377}
378
379impl From<usize> for ComponentId {
380    fn from(id: usize) -> Self {
381        Self(id)
382    }
383}
384
385static NEXT_INSTANCE_ID: AtomicUsize = AtomicUsize::new(0);
386
387/// Represents an identifier of an instance in a composition graph.
388#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
389pub struct InstanceId(pub usize);
390
391impl std::fmt::Display for InstanceId {
392    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
393        write!(f, "{}", self.0)
394    }
395}
396
397impl InstanceId {
398    fn next() -> Result<Self> {
399        let next = NEXT_INSTANCE_ID.fetch_add(1, Ordering::SeqCst);
400        if next == usize::MAX {
401            bail!("instance limit reached");
402        }
403        Ok(Self(next))
404    }
405}
406
407impl From<usize> for InstanceId {
408    fn from(id: usize) -> Self {
409        Self(id)
410    }
411}
412
413/// Represents an index into a component's import list.
414#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
415pub struct ImportIndex(pub usize);
416
417impl std::fmt::Display for ImportIndex {
418    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
419        write!(f, "{}", self.0)
420    }
421}
422
423impl From<usize> for ImportIndex {
424    fn from(id: usize) -> Self {
425        Self(id)
426    }
427}
428
429/// Represents an index into a component's export list.
430#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
431pub struct ExportIndex(pub usize);
432
433impl std::fmt::Display for ExportIndex {
434    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
435        write!(f, "{}", self.0)
436    }
437}
438
439impl From<usize> for ExportIndex {
440    fn from(id: usize) -> Self {
441        Self(id)
442    }
443}
444
445#[derive(Debug)]
446pub(crate) struct ComponentEntry<'a> {
447    pub(crate) component: Component<'a>,
448    pub(crate) instances: HashSet<InstanceId>,
449}
450
451#[derive(Debug)]
452pub(crate) struct Instance {
453    pub(crate) component: ComponentId,
454    pub(crate) connected: IndexSet<ImportIndex>,
455}
456
457/// The options for encoding a composition graph.
458#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
459pub struct EncodeOptions {
460    /// Whether or not to define instantiated components.
461    ///
462    /// If `false`, components will be imported instead.
463    pub define_components: bool,
464
465    /// The instance in the graph to export.
466    ///
467    /// If non-empty, the instance's exports will be aliased and
468    /// exported from the resulting component.
469    pub export: Option<InstanceId>,
470
471    /// Whether or not to validate the encoded output.
472    pub validate: bool,
473}
474
475#[derive(Clone, Debug, Default)]
476pub(crate) struct ResourceMapping {
477    map: im_rc::HashMap<ResourceId, (ComponentId, ResourceId)>,
478}
479
480impl ResourceMapping {
481    fn add_pairs(
482        mut self,
483        export_component: ComponentId,
484        export_type: ComponentEntityType,
485        export_types: TypesRef,
486        import_type: ComponentEntityType,
487        import_types: TypesRef,
488    ) -> Option<Self> {
489        if let (
490            ComponentEntityType::Instance(export_type),
491            ComponentEntityType::Instance(import_type),
492        ) = (export_type, import_type)
493        {
494            let mut exports = HashMap::new();
495            for (export_name, ty) in &export_types[export_type].exports {
496                // TODO: support nested instances
497                if let ComponentEntityType::Type {
498                    referenced: ComponentAnyTypeId::Resource(resource_id),
499                    ..
500                } = ty
501                {
502                    exports.insert(export_name, (export_component, resource_id.resource()));
503                }
504            }
505
506            for (export_name, ty) in &import_types[import_type].exports {
507                // TODO: support nested instances
508                if let ComponentEntityType::Type {
509                    referenced: ComponentAnyTypeId::Resource(resource_id),
510                    ..
511                } = ty
512                {
513                    let import_resource = resource_id.resource();
514                    if let Some((export_component, export_resource)) =
515                        exports.get(&export_name).copied()
516                    {
517                        let value = self
518                            .map
519                            .get(&export_resource)
520                            .copied()
521                            .or_else(|| self.map.get(&import_resource).copied())
522                            .unwrap_or((export_component, export_resource));
523
524                        if value.1 == export_resource {
525                            self.map.insert(export_resource, value);
526                            self.map.insert(import_resource, value);
527                        }
528                    } else {
529                        // Couldn't find an export with a name that matches this
530                        // import -- give up.
531                        return None;
532                    }
533                }
534            }
535        }
536
537        Some(self)
538    }
539
540    pub(crate) fn remapping(&self) -> Remapping {
541        let mut remapping = Remapping::default();
542        for (old, (_, new)) in &self.map {
543            if old != new {
544                remapping.add(*old, *new)
545            }
546        }
547        remapping
548    }
549}
550
551/// Represents a composition graph used to compose a new component
552/// from other components.
553#[derive(Debug, Default)]
554pub struct CompositionGraph<'a> {
555    names: HashMap<String, ComponentId>,
556    pub(crate) components: IndexMap<ComponentId, ComponentEntry<'a>>,
557    pub(crate) instances: IndexMap<InstanceId, Instance>,
558    // Map where each node is an instance in the graph.
559    // An edge between nodes stores a map of target import index to source export index.
560    // A source export index of `None` means that the source instance itself is being used.
561    pub(crate) graph: DiGraphMap<InstanceId, IndexMap<ImportIndex, Option<ExportIndex>>>,
562    pub(crate) resource_mapping: RefCell<ResourceMapping>,
563}
564
565impl<'a> CompositionGraph<'a> {
566    /// Constructs a new composition graph.
567    pub fn new() -> Self {
568        Self::default()
569    }
570
571    /// Gather any remaining resource imports which have not already been
572    /// connected to exports, group them by name, and update the resource
573    /// mapping to make all resources within each group equivalent.
574    ///
575    /// This ensures that each set of identical imports in the composed
576    /// components can be merged into a single import in the output component.
577    //
578    // TODO: How do we balance the need to call this early (so we can match up
579    // imports with exports which mutually import the same resources) with the
580    // need to delay decisions about where resources are coming from (so that we
581    // can match up imported resources with exported resources)?  Right now I
582    // think we're erring on the side if the former at the expense of the
583    // latter.
584    pub(crate) fn unify_imported_resources(&self) {
585        let mut resource_mapping = self.resource_mapping.borrow_mut();
586
587        let mut resource_imports = IndexMap::<_, IndexSet<_>>::new();
588        for (component_id, component) in &self.components {
589            let component = &component.component;
590            for import_name in component.imports.keys() {
591                let ty = component
592                    .types
593                    .as_ref()
594                    .component_entity_type_of_import(import_name)
595                    .unwrap();
596
597                if let ComponentEntityType::Instance(instance_id) = ty {
598                    for (export_name, ty) in &component.types[instance_id].exports {
599                        // TODO: support nested instances
600                        if let ComponentEntityType::Type {
601                            referenced: ComponentAnyTypeId::Resource(resource_id),
602                            ..
603                        } = ty
604                        {
605                            let set = resource_imports
606                                .entry(vec![import_name.to_string(), export_name.to_string()])
607                                .or_default();
608
609                            if let Some(pair) = resource_mapping.map.get(&resource_id.resource()) {
610                                set.insert(*pair);
611                            }
612                            set.insert((*component_id, resource_id.resource()));
613                        }
614                    }
615                }
616            }
617        }
618
619        for resources in resource_imports.values() {
620            match &resources.iter().copied().collect::<Vec<_>>()[..] {
621                [] => unreachable!(),
622                [_] => {}
623                [first, rest @ ..] => {
624                    resource_mapping.map.insert(first.1, *first);
625                    for resource in rest {
626                        resource_mapping.map.insert(resource.1, *first);
627                    }
628                }
629            }
630        }
631    }
632
633    /// Attempt to connect the specified import to the specified export.
634    ///
635    /// This will attempt to match up any resource types by name by name and
636    /// optimistically produce a remapping that sets identically-named pairs
637    /// equal to each other, provided that remapping does not contradict any
638    /// previous remappings.  If the import is not a subtype of the export
639    /// (either because a consistent remapping could not be created or because
640    /// the instances were incompatible for other reasons), we discard the
641    /// remapping changes and return `false`.  Otherwise, we store the remapping
642    /// changes and return `true`.
643    ///
644    /// Note that although this method takes a shared reference, it uses
645    /// internal mutability to update the remapping.
646    pub(crate) fn try_connection(
647        &self,
648        export_component: ComponentId,
649        mut export_type: ComponentEntityType,
650        export_types: TypesRef,
651        mut import_type: ComponentEntityType,
652        import_types: TypesRef,
653    ) -> bool {
654        let resource_mapping = self.resource_mapping.borrow().clone();
655
656        if let Some(resource_mapping) = resource_mapping.add_pairs(
657            export_component,
658            export_type,
659            export_types,
660            import_type,
661            import_types,
662        ) {
663            let remapping = &mut resource_mapping.remapping();
664            let mut context = SubtypeCx::new_with_refs(export_types, import_types);
665
666            context
667                .a
668                .remap_component_entity(&mut export_type, remapping);
669            remapping.reset_type_cache();
670
671            context
672                .b
673                .remap_component_entity(&mut import_type, remapping);
674            remapping.reset_type_cache();
675
676            let v = context.component_entity_type(&export_type, &import_type, 0);
677            if v.is_ok() {
678                *self.resource_mapping.borrow_mut() = resource_mapping;
679                true
680            } else {
681                false
682            }
683        } else {
684            false
685        }
686    }
687
688    pub(crate) fn remapping_map<'b>(
689        &'b self,
690    ) -> HashMap<ResourceId, (&'b Component<'a>, ResourceId)> {
691        let mut map = HashMap::new();
692        for (old, (component, new)) in &self.resource_mapping.borrow().map {
693            if old != new {
694                let component = &self.components.get(component).unwrap().component;
695                map.insert(*old, (component, *new));
696            }
697        }
698        map
699    }
700
701    /// Adds a new component to the graph.
702    ///
703    /// The component name must be unique.
704    pub fn add_component(&mut self, component: Component<'a>) -> Result<ComponentId> {
705        let id = match self.names.entry(component.name.clone()) {
706            Entry::Occupied(e) => {
707                bail!(
708                    "a component with name `{name}` already exists",
709                    name = e.key()
710                )
711            }
712            Entry::Vacant(e) => *e.insert(ComponentId::next()?),
713        };
714
715        log::info!(
716            "adding WebAssembly component `{name}` ({id}) to the graph",
717            name = component.name(),
718        );
719
720        let entry = ComponentEntry {
721            component,
722            instances: HashSet::new(),
723        };
724
725        assert!(self.components.insert(id, entry).is_none());
726
727        if self.components.len() > 1 {
728            self.unify_imported_resources();
729        }
730
731        Ok(id)
732    }
733
734    /// Gets a component from the graph.
735    pub fn get_component(&self, id: impl Into<ComponentId>) -> Option<&Component<'a>> {
736        self.components.get(&id.into()).map(|e| &e.component)
737    }
738
739    /// Gets a component from the graph by name.
740    pub fn get_component_by_name(&self, name: &str) -> Option<(ComponentId, &Component<'a>)> {
741        let id = self.names.get(name)?;
742        let entry = &self.components[id];
743        Some((*id, &entry.component))
744    }
745
746    /// Removes a component from the graph.
747    ///
748    /// All instances and connections relating to the component
749    /// will also be removed.
750    pub fn remove_component(&mut self, id: impl Into<ComponentId>) {
751        let id = id.into();
752        if let Some(entry) = self.components.swap_remove(&id) {
753            log::info!(
754                "removing WebAssembly component `{name}` ({id}) from the graph",
755                name = entry.component.name(),
756            );
757
758            assert!(self.names.remove(&entry.component.name).is_some());
759
760            for instance_id in entry.instances.iter().copied() {
761                self.instances.swap_remove(&instance_id);
762
763                // Remove any connected indexes from outward edges from the instance being removed
764                for (_, target_id, map) in self
765                    .graph
766                    .edges_directed(instance_id, EdgeDirection::Outgoing)
767                {
768                    let target = self.instances.get_mut(&target_id).unwrap();
769                    for index in map.keys() {
770                        target.connected.swap_remove(index);
771                    }
772                }
773
774                self.graph.remove_node(instance_id);
775            }
776        }
777    }
778
779    /// Creates a new instance of a component in the composition graph.
780    pub fn instantiate(&mut self, id: impl Into<ComponentId>) -> Result<InstanceId> {
781        let id = id.into();
782        let entry = self
783            .components
784            .get_mut(&id)
785            .ok_or_else(|| anyhow!("component does not exist in the graph"))?;
786
787        let instance_id = InstanceId::next()?;
788
789        log::info!(
790            "instantiating WebAssembly component `{name}` ({id}) with instance identifier {instance_id}",
791            name = entry.component.name(),
792        );
793
794        self.instances.insert(
795            instance_id,
796            Instance {
797                component: id,
798                connected: Default::default(),
799            },
800        );
801
802        entry.instances.insert(instance_id);
803
804        Ok(instance_id)
805    }
806
807    /// Gets the component of the given instance.
808    pub fn get_component_of_instance(
809        &self,
810        id: impl Into<InstanceId>,
811    ) -> Option<(ComponentId, &Component<'_>)> {
812        let id = id.into();
813        let instance = self.instances.get(&id)?;
814
815        Some((
816            instance.component,
817            self.get_component(instance.component).unwrap(),
818        ))
819    }
820
821    /// Removes an instance from the graph.
822    ///
823    /// All connections relating to the instance will also be removed.
824    pub fn remove_instance(&mut self, id: impl Into<InstanceId>) {
825        let id = id.into();
826        if let Some(instance) = self.instances.swap_remove(&id) {
827            let entry = self.components.get_mut(&instance.component).unwrap();
828
829            log::info!(
830                "removing instance ({id}) of component `{name}` ({cid}) from the graph",
831                name = entry.component.name(),
832                cid = instance.component.0,
833            );
834
835            entry.instances.remove(&id);
836
837            // Remove any connected indexes from outward edges from this instance
838            for (_, target, map) in self.graph.edges_directed(id, EdgeDirection::Outgoing) {
839                let target = self.instances.get_mut(&target).unwrap();
840                for index in map.keys() {
841                    target.connected.swap_remove(index);
842                }
843            }
844
845            self.graph.remove_node(id);
846        }
847    }
848
849    /// Creates a connection (edge) between instances in the composition graph.
850    ///
851    /// A connection represents an instantiation argument.
852    ///
853    /// If `source_export` is `None`, the source instance itself
854    /// is used as the instantiation argument.
855    pub fn connect(
856        &mut self,
857        source: impl Into<InstanceId> + Copy,
858        source_export: Option<impl Into<ExportIndex> + Copy>,
859        target: impl Into<InstanceId> + Copy,
860        target_import: impl Into<ImportIndex> + Copy,
861    ) -> Result<()> {
862        self.validate_connection(source, source_export, target, target_import)?;
863
864        let source = source.into();
865        let source_export = source_export.map(Into::into);
866        let target = target.into();
867        let target_import = target_import.into();
868
869        match source_export {
870            Some(export) => log::info!(
871                "connecting export {export} of instance {source} to import `{target_import}` of instance {target}"
872            ),
873            None => log::info!(
874                "connecting instance {source} to import {target_import} of instance {target}"
875            ),
876        }
877
878        self.instances
879            .get_mut(&target)
880            .unwrap()
881            .connected
882            .insert(target_import);
883
884        if let Some(map) = self.graph.edge_weight_mut(source, target) {
885            assert!(map.insert(target_import, source_export).is_none());
886        } else {
887            let mut map = IndexMap::new();
888            map.insert(target_import, source_export);
889            self.graph.add_edge(source, target, map);
890        }
891
892        Ok(())
893    }
894
895    /// Disconnects a previous connection between instances.
896    ///
897    /// Requires that the source and target instances are valid.
898    ///
899    /// If the source and target are not connected via the target's import,
900    /// then this is a no-op.
901    pub fn disconnect(
902        &mut self,
903        source: impl Into<InstanceId>,
904        target: impl Into<InstanceId>,
905        target_import: impl Into<ImportIndex>,
906    ) -> Result<()> {
907        let source = source.into();
908        let target = target.into();
909        let target_import = target_import.into();
910
911        log::info!("disconnecting import {target_import} of instance {target}");
912
913        if !self.instances.contains_key(&source) {
914            bail!("the source instance does not exist in the graph");
915        }
916
917        let target_instance = self
918            .instances
919            .get_mut(&target)
920            .ok_or_else(|| anyhow!("the target instance does not exist in the graph"))?;
921
922        target_instance.connected.swap_remove(&target_import);
923
924        let remove_edge = if let Some(set) = self.graph.edge_weight_mut(source, target) {
925            set.swap_remove(&target_import);
926            set.is_empty()
927        } else {
928            false
929        };
930
931        if remove_edge {
932            self.graph.remove_edge(source, target);
933        }
934
935        Ok(())
936    }
937
938    /// Validates a connection between two instances in the graph.
939    ///
940    /// Use `None` for `source_export` to signify that the instance
941    /// itself should be the source for the connection.
942    ///
943    /// Returns `Err(_)` if the connection would not be valid.
944    pub fn validate_connection(
945        &self,
946        source: impl Into<InstanceId>,
947        source_export: Option<impl Into<ExportIndex>>,
948        target: impl Into<InstanceId>,
949        target_import: impl Into<ImportIndex>,
950    ) -> Result<()> {
951        let source = source.into();
952        let source_export = source_export.map(Into::into);
953        let target = target.into();
954        let target_import = target_import.into();
955
956        if source == target {
957            bail!("an instance cannot be connected to itself");
958        }
959
960        let source_instance = self
961            .instances
962            .get(&source)
963            .ok_or_else(|| anyhow!("the source instance does not exist in the graph"))?;
964
965        let source_component = &self.components[&source_instance.component].component;
966
967        let target_instance = self
968            .instances
969            .get(&target)
970            .ok_or_else(|| anyhow!("the target instance does not exist in the graph"))?;
971
972        let target_component = &self.components[&target_instance.component].component;
973        let (import_name, import_ty) = target_component
974            .import_entity_type(target_import)
975            .ok_or_else(|| anyhow!("the target import index is invalid"))?;
976
977        if target_instance.connected.contains(&target_import) {
978            bail!(
979                "{import_ty} import `{import_name}` is already connected",
980                import_ty = type_desc(import_ty)
981            );
982        }
983
984        if let Some(export_index) = source_export {
985            let (export_name, export_ty) = source_component
986                .export_entity_type(export_index)
987                .ok_or_else(|| anyhow!("the source export index is invalid"))?;
988
989            if !self.try_connection(
990                source_instance.component,
991                export_ty,
992                source_component.types(),
993                import_ty,
994                target_component.types(),
995            ) {
996                bail!(
997                    "source {export_ty} export `{export_name}` is not compatible with target \
998                         {import_ty} import `{import_name}`",
999                    export_ty = type_desc(export_ty),
1000                    import_ty = type_desc(import_ty),
1001                );
1002            }
1003        } else {
1004            let ty = match import_ty {
1005                ComponentEntityType::Instance(id) => id,
1006                _ => bail!(
1007                    "source instance is not compatible with target {import_ty} import `{import_name}`",
1008                    import_ty = type_desc(import_ty)
1009                ),
1010            };
1011
1012            if !source_component.is_instance_subtype_of(ty, target_component.types()) {
1013                bail!(
1014                    "source instance is not compatible with target {import_ty} import `{import_name}`",
1015                    import_ty = type_desc(import_ty)
1016                );
1017            }
1018        };
1019
1020        Ok(())
1021    }
1022
1023    /// Encodes the current composition graph as a WebAssembly component.
1024    pub fn encode(&self, options: EncodeOptions) -> Result<Vec<u8>> {
1025        let bytes = CompositionGraphEncoder::new(options, self).encode()?;
1026
1027        if options.validate {
1028            Validator::new()
1029                .validate_all(&bytes)
1030                .context("failed to validate encoded graph bytes")?;
1031        }
1032
1033        Ok(bytes)
1034    }
1035
1036    /// Gets the topological instantiation order based on the composition graph.
1037    ///
1038    /// If an instance is not in the returned set, it is considered to be
1039    /// "independent" (i.e it has no dependencies on other instances).
1040    pub(crate) fn instantiation_order(&self) -> Result<Vec<InstanceId>> {
1041        toposort(&self.graph, None).map_err(|e| {
1042            let id = e.node_id();
1043            let instance = &self.instances[&id];
1044            anyhow!(
1045                "an instantiation of component `{name}` and its dependencies form a cycle in the instantiation graph",
1046                name = self.components[&instance.component].component.name,
1047            )
1048        })
1049    }
1050}
1051
1052#[cfg(test)]
1053mod test {
1054    use super::*;
1055    use pretty_assertions::assert_eq;
1056
1057    #[test]
1058    fn it_rejects_modules() -> Result<()> {
1059        let mut validator = Validator::new();
1060        match Component::from_bytes(&mut validator, "a", b"(module)".as_ref()) {
1061            Ok(_) => panic!("expected a failure to parse"),
1062            Err(e) => assert_eq!(
1063                format!("{e:#}"),
1064                "failed to parse component: the given data is not a WebAssembly component"
1065            ),
1066        }
1067
1068        Ok(())
1069    }
1070
1071    #[test]
1072    fn it_rejects_invalid_components() -> Result<()> {
1073        let mut validator = Validator::new();
1074        match Component::from_bytes(
1075            &mut validator,
1076            "a",
1077            b"(component (export \"x\" (func 0)))".as_ref(),
1078        ) {
1079            Ok(_) => panic!("expected a failure to parse"),
1080            Err(e) => assert_eq!(
1081                format!("{e:#}"),
1082                "failed to parse component: unknown function 0: function index out of bounds (at offset 0xb)"
1083            ),
1084        }
1085
1086        Ok(())
1087    }
1088
1089    #[test]
1090    fn it_ensures_unique_component_names() -> Result<()> {
1091        let mut graph = CompositionGraph::new();
1092        let mut validator = Validator::new();
1093        graph.add_component(Component::from_bytes(
1094            &mut validator,
1095            "a",
1096            b"(component)".as_ref(),
1097        )?)?;
1098
1099        match graph.add_component(Component::from_bytes(
1100            &mut validator,
1101            "a",
1102            b"(component)".as_ref(),
1103        )?) {
1104            Ok(_) => panic!("expected a failure to add component"),
1105            Err(e) => assert_eq!(format!("{e:#}"), "a component with name `a` already exists"),
1106        }
1107
1108        Ok(())
1109    }
1110
1111    #[test]
1112    fn it_fails_to_instantiate_a_missing_component() -> Result<()> {
1113        let mut graph = CompositionGraph::new();
1114        match graph.instantiate(ComponentId(0)) {
1115            Ok(_) => panic!("expected a failure to instantiate"),
1116            Err(e) => assert_eq!(format!("{e:#}"), "component does not exist in the graph"),
1117        }
1118
1119        Ok(())
1120    }
1121
1122    #[test]
1123    fn it_instantiates_a_component() -> Result<()> {
1124        let mut graph = CompositionGraph::new();
1125        let mut validator = Validator::new();
1126        let id = graph.add_component(Component::from_bytes(
1127            &mut validator,
1128            "a",
1129            b"(component)".as_ref(),
1130        )?)?;
1131        let id = graph.instantiate(id)?;
1132        assert_eq!(graph.get_component_of_instance(id).unwrap().1.name(), "a");
1133        Ok(())
1134    }
1135
1136    #[test]
1137    fn it_cannot_get_a_component_of_missing_instance() -> Result<()> {
1138        let graph = CompositionGraph::new();
1139        assert!(graph.get_component_of_instance(InstanceId(0)).is_none());
1140        Ok(())
1141    }
1142
1143    #[test]
1144    fn it_gets_a_component() -> Result<()> {
1145        let mut graph = CompositionGraph::new();
1146        let mut validator = Validator::new();
1147        let id = graph.add_component(Component::from_bytes(
1148            &mut validator,
1149            "a",
1150            b"(component)".as_ref(),
1151        )?)?;
1152        assert_eq!(graph.get_component(id).unwrap().name(), "a");
1153        assert_eq!(graph.get_component_by_name("a").unwrap().1.name(), "a");
1154        Ok(())
1155    }
1156
1157    #[test]
1158    fn it_removes_a_component() -> Result<()> {
1159        let mut graph = CompositionGraph::new();
1160        let mut validator = Validator::new();
1161        let a = graph.add_component(Component::from_bytes(
1162            &mut validator,
1163            "a",
1164            b"(component (import \"x\" (func)))".as_ref(),
1165        )?)?;
1166        let b = graph.add_component(Component::from_bytes(
1167            &mut validator,
1168            "b",
1169            b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(),
1170        )?)?;
1171        let ai = graph.instantiate(a)?;
1172        let bi = graph.instantiate(b)?;
1173        graph.connect(bi, Some(0), ai, 0)?;
1174
1175        assert!(graph.get_component(a).is_some());
1176        assert!(graph.get_component(b).is_some());
1177        assert_eq!(graph.components.len(), 2);
1178        assert_eq!(graph.instances.len(), 2);
1179        assert_eq!(graph.graph.node_count(), 2);
1180        assert_eq!(graph.graph.edge_count(), 1);
1181
1182        graph.remove_component(b);
1183
1184        assert!(graph.get_component(a).is_some());
1185        assert!(graph.get_component(b).is_none());
1186        assert_eq!(graph.components.len(), 1);
1187        assert_eq!(graph.instances.len(), 1);
1188        assert_eq!(graph.graph.node_count(), 1);
1189        assert_eq!(graph.graph.edge_count(), 0);
1190        Ok(())
1191    }
1192
1193    #[test]
1194    fn it_removes_a_connection() -> Result<()> {
1195        let mut graph = CompositionGraph::new();
1196        let mut validator = Validator::new();
1197        let a = graph.add_component(Component::from_bytes(
1198            &mut validator,
1199            "a",
1200            b"(component (import \"x\" (func)))".as_ref(),
1201        )?)?;
1202        let b = graph.add_component(Component::from_bytes(
1203            &mut validator,
1204            "b",
1205            b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(),
1206        )?)?;
1207        let ai = graph.instantiate(a)?;
1208        let bi = graph.instantiate(b)?;
1209        graph.connect(bi, Some(0), ai, 0)?;
1210
1211        assert_eq!(graph.graph.node_count(), 2);
1212        assert_eq!(graph.graph.edge_count(), 1);
1213
1214        graph.disconnect(bi, ai, 0)?;
1215
1216        assert_eq!(graph.graph.node_count(), 2);
1217        assert_eq!(graph.graph.edge_count(), 0);
1218        Ok(())
1219    }
1220
1221    #[test]
1222    fn it_requires_source_to_disconnect() -> Result<()> {
1223        let mut graph = CompositionGraph::new();
1224        let mut validator = Validator::new();
1225        let a = graph.add_component(Component::from_bytes(
1226            &mut validator,
1227            "a",
1228            b"(component (import \"x\" (func)))".as_ref(),
1229        )?)?;
1230        let b = graph.add_component(Component::from_bytes(
1231            &mut validator,
1232            "b",
1233            b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(),
1234        )?)?;
1235        let ai = graph.instantiate(a)?;
1236        let bi = graph.instantiate(b)?;
1237        graph.connect(bi, Some(0), ai, 0)?;
1238
1239        match graph.disconnect(101, ai, 0) {
1240            Ok(_) => panic!("expected a failure to disconnect"),
1241            Err(e) => assert_eq!(
1242                format!("{e:#}"),
1243                "the source instance does not exist in the graph"
1244            ),
1245        }
1246
1247        Ok(())
1248    }
1249
1250    #[test]
1251    fn it_requires_a_target_to_disconnect() -> Result<()> {
1252        let mut graph = CompositionGraph::new();
1253        let mut validator = Validator::new();
1254        let a = graph.add_component(Component::from_bytes(
1255            &mut validator,
1256            "a",
1257            b"(component (import \"x\" (func)))".as_ref(),
1258        )?)?;
1259        let b = graph.add_component(Component::from_bytes(
1260            &mut validator,
1261            "b",
1262            b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(),
1263        )?)?;
1264        let ai = graph.instantiate(a)?;
1265        let bi = graph.instantiate(b)?;
1266        graph.connect(bi, Some(0), ai, 0)?;
1267
1268        match graph.disconnect(bi, 101, 0) {
1269            Ok(_) => panic!("expected a failure to disconnect"),
1270            Err(e) => assert_eq!(
1271                format!("{e:#}"),
1272                "the target instance does not exist in the graph"
1273            ),
1274        }
1275
1276        Ok(())
1277    }
1278
1279    #[test]
1280    fn it_validates_connections() -> Result<()> {
1281        let mut graph = CompositionGraph::new();
1282        let mut validator = Validator::new();
1283        let a = graph.add_component(Component::from_bytes(
1284            &mut validator,
1285            "a",
1286            b"(component (import \"i1\" (func)) (import \"i2\" (instance (export \"no\" (func)))))"
1287                .as_ref(),
1288        )?)?;
1289        let b = graph.add_component(Component::from_bytes(
1290            &mut validator,
1291            "b",
1292            b"(component (import \"i1\" (func)) (import \"i2\" (core module)) (export \"e1\" (func 0)) (export \"e2\" (core module 0)))".as_ref(),
1293        )?)?;
1294        let ai = graph.instantiate(a)?;
1295        let bi = graph.instantiate(b)?;
1296
1297        match graph.connect(ai, None::<ExportIndex>, ai, 0) {
1298            Ok(_) => panic!("expected a failure to connect"),
1299            Err(e) => assert_eq!(
1300                format!("{e:#}"),
1301                "an instance cannot be connected to itself"
1302            ),
1303        }
1304
1305        match graph.connect(ai, Some(0), bi, 0) {
1306            Ok(_) => panic!("expected a failure to connect"),
1307            Err(e) => assert_eq!(format!("{e:#}"), "the source export index is invalid"),
1308        }
1309
1310        match graph.connect(101, Some(0), ai, 0) {
1311            Ok(_) => panic!("expected a failure to connect"),
1312            Err(e) => assert_eq!(
1313                format!("{e:#}"),
1314                "the source instance does not exist in the graph"
1315            ),
1316        }
1317
1318        match graph.connect(bi, Some(0), 101, 0) {
1319            Ok(_) => panic!("expected a failure to connect"),
1320            Err(e) => assert_eq!(
1321                format!("{e:#}"),
1322                "the target instance does not exist in the graph"
1323            ),
1324        }
1325
1326        match graph.connect(bi, Some(101), ai, 0) {
1327            Ok(_) => panic!("expected a failure to connect"),
1328            Err(e) => assert_eq!(format!("{e:#}"), "the source export index is invalid"),
1329        }
1330
1331        match graph.connect(bi, Some(0), ai, 101) {
1332            Ok(_) => panic!("expected a failure to connect"),
1333            Err(e) => assert_eq!(format!("{e:#}"), "the target import index is invalid"),
1334        }
1335
1336        match graph.connect(bi, Some(1), ai, 0) {
1337            Ok(_) => panic!("expected a failure to connect"),
1338            Err(e) => assert_eq!(
1339                format!("{e:#}"),
1340                "source module export `e2` is not compatible with target function import `i1`"
1341            ),
1342        }
1343
1344        match graph.connect(bi, None::<ExportIndex>, ai, 0) {
1345            Ok(_) => panic!("expected a failure to connect"),
1346            Err(e) => assert_eq!(
1347                format!("{e:#}"),
1348                "source instance is not compatible with target function import `i1`"
1349            ),
1350        }
1351
1352        match graph.connect(bi, None::<ExportIndex>, ai, 1) {
1353            Ok(_) => panic!("expected a failure to connect"),
1354            Err(e) => assert_eq!(
1355                format!("{e:#}"),
1356                "source instance is not compatible with target instance import `i2`"
1357            ),
1358        }
1359
1360        Ok(())
1361    }
1362
1363    #[test]
1364    fn it_cannot_encode_a_cycle() -> Result<()> {
1365        let mut graph = CompositionGraph::new();
1366        let mut validator = Validator::new();
1367        let a = graph.add_component(Component::from_bytes(
1368            &mut validator,
1369            "a",
1370            b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(),
1371        )?)?;
1372        let b = graph.add_component(Component::from_bytes(
1373            &mut validator,
1374            "b",
1375            b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(),
1376        )?)?;
1377        let ai = graph.instantiate(a)?;
1378        let bi = graph.instantiate(b)?;
1379
1380        graph.connect(ai, Some(0), bi, 0)?;
1381        graph.connect(bi, Some(0), ai, 0)?;
1382
1383        match graph.encode(EncodeOptions {
1384            define_components: false,
1385            export: None,
1386            validate: true,
1387        }) {
1388            Ok(_) => panic!("graph should not encode"),
1389            Err(e) => assert_eq!(
1390                format!("{e:#}"),
1391                "an instantiation of component `b` and its dependencies form a cycle in the instantiation graph"
1392            ),
1393        }
1394
1395        Ok(())
1396    }
1397
1398    #[test]
1399    fn it_encodes_an_empty_component() -> Result<()> {
1400        let mut graph = CompositionGraph::new();
1401        let mut validator = Validator::new();
1402        graph.add_component(Component::from_bytes(
1403            &mut validator,
1404            "a",
1405            b"(component)".as_ref(),
1406        )?)?;
1407        graph.add_component(Component::from_bytes(
1408            &mut validator,
1409            "b",
1410            b"(component)".as_ref(),
1411        )?)?;
1412
1413        let encoded = graph.encode(EncodeOptions {
1414            define_components: false,
1415            export: None,
1416            validate: true,
1417        })?;
1418
1419        let wat = wasmprinter::print_bytes(encoded)?;
1420        assert_eq!(
1421            r#"(component)
1422"#,
1423            wat
1424        );
1425
1426        Ok(())
1427    }
1428
1429    #[test]
1430    fn it_encodes_component_imports() -> Result<()> {
1431        let mut graph = CompositionGraph::new();
1432        let mut validator = Validator::new();
1433        // Add a component that doesn't get instantiated (shouldn't be imported)
1434        graph.add_component(Component::from_bytes(
1435            &mut validator,
1436            "a",
1437            b"(component)".as_ref(),
1438        )?)?;
1439        let b = graph.add_component(Component::from_bytes(
1440            &mut validator,
1441            "b",
1442            b"(component)".as_ref(),
1443        )?)?;
1444        graph.instantiate(b)?;
1445
1446        let encoded = graph.encode(EncodeOptions {
1447            define_components: false,
1448            export: None,
1449            validate: true,
1450        })?;
1451
1452        let wat = wasmprinter::print_bytes(encoded)?.replace("\r\n", "\n");
1453        assert_eq!(
1454            r#"(component
1455  (type (;0;)
1456    (component)
1457  )
1458  (import "b" (component (;0;) (type 0)))
1459  (instance (;0;) (instantiate 0))
1460)
1461"#,
1462            wat
1463        );
1464
1465        Ok(())
1466    }
1467
1468    #[test]
1469    fn it_encodes_defined_components() -> Result<()> {
1470        let mut graph = CompositionGraph::new();
1471        let mut validator = Validator::new();
1472        // Add a component that doesn't get instantiated (shouldn't be imported)
1473        graph.add_component(Component::from_bytes(
1474            &mut validator,
1475            "a",
1476            b"(component)".as_ref(),
1477        )?)?;
1478        let b = graph.add_component(Component::from_bytes(
1479            &mut validator,
1480            "b",
1481            b"(component)".as_ref(),
1482        )?)?;
1483        graph.instantiate(b)?;
1484
1485        let encoded = graph.encode(EncodeOptions {
1486            define_components: true,
1487            export: None,
1488            validate: true,
1489        })?;
1490
1491        let wat = wasmprinter::print_bytes(encoded)?.replace("\r\n", "\n");
1492        assert_eq!(
1493            r#"(component
1494  (component (;0;))
1495  (instance (;0;) (instantiate 0))
1496)
1497"#,
1498            wat
1499        );
1500
1501        Ok(())
1502    }
1503
1504    #[test]
1505    fn it_encodes_a_simple_composition() -> Result<()> {
1506        let mut graph = CompositionGraph::new();
1507        let mut validator = Validator::new();
1508        let a = graph.add_component(Component::from_bytes(
1509            &mut validator,
1510            "a",
1511            b"(component
1512  (type (tuple u32 u32))
1513  (import \"i1\" (instance (export \"e1\" (func)) (export \"e3\" (func (param \"a\" u32)))))
1514  (import \"i2\" (func))
1515  (import \"i3\" (component))
1516  (import \"i4\" (core module))
1517  (import \"i5\" (type (eq 0)))
1518  (export \"e1\" (instance 0))
1519  (export \"e2\" (func 0))
1520  (export \"e3\" (component 0))
1521  (export \"e4\" (core module 0))
1522  (export \"e5\" (type 1))
1523)"
1524            .as_ref(),
1525        )?)?;
1526        let b = graph.add_component(Component::from_bytes(
1527            &mut validator,
1528            "b",
1529            b"(component
1530  (type (tuple u32 u32))
1531  (import \"i1\" (instance (export \"e2\" (func)) (export \"e3\" (func (param \"a\" u32)))))
1532  (import \"i2\" (func))
1533  (import \"i3\" (component))
1534  (import \"i4\" (core module))
1535  (import \"i5\" (type (eq 0)))
1536)"
1537            .as_ref(),
1538        )?)?;
1539
1540        let ai = graph.instantiate(a)?;
1541        let bi1 = graph.instantiate(b)?;
1542        let bi2 = graph.instantiate(b)?;
1543        let bi3 = graph.instantiate(b)?;
1544
1545        // Skip the instance arguments so a merged instance is imported
1546        for i in 1..=3 {
1547            graph.connect(ai, Some(i), bi1, i)?;
1548            graph.connect(ai, Some(i), bi2, i)?;
1549            graph.connect(ai, Some(i), bi3, i)?;
1550        }
1551
1552        let encoded = graph.encode(EncodeOptions {
1553            define_components: true,
1554            export: None,
1555            validate: true,
1556        })?;
1557
1558        let wat = wasmprinter::print_bytes(encoded)?.replace("\r\n", "\n");
1559        assert_eq!(
1560            r#"(component
1561  (type (;0;)
1562    (instance
1563      (type (;0;) (func))
1564      (export (;0;) "e1" (func (type 0)))
1565      (type (;1;) (func (param "a" u32)))
1566      (export (;1;) "e3" (func (type 1)))
1567      (type (;2;) (func))
1568      (export (;2;) "e2" (func (type 2)))
1569    )
1570  )
1571  (import "i1" (instance (;0;) (type 0)))
1572  (type (;1;) (func))
1573  (import "i2" (func (;0;) (type 1)))
1574  (type (;2;)
1575    (component)
1576  )
1577  (import "i3" (component (;0;) (type 2)))
1578  (core type (;0;)
1579    (module)
1580  )
1581  (import "i4" (core module (;0;) (type 0)))
1582  (type (;3;) (tuple u32 u32))
1583  (import "i5" (type (;4;) (eq 3)))
1584  (component (;1;)
1585    (type (;0;) (tuple u32 u32))
1586    (type (;1;)
1587      (instance
1588        (type (;0;) (func))
1589        (export (;0;) "e1" (func (type 0)))
1590        (type (;1;) (func (param "a" u32)))
1591        (export (;1;) "e3" (func (type 1)))
1592      )
1593    )
1594    (import "i1" (instance (;0;) (type 1)))
1595    (type (;2;) (func))
1596    (import "i2" (func (;0;) (type 2)))
1597    (type (;3;)
1598      (component)
1599    )
1600    (import "i3" (component (;0;) (type 3)))
1601    (core type (;0;)
1602      (module)
1603    )
1604    (import "i4" (core module (;0;) (type 0)))
1605    (import "i5" (type (;4;) (eq 0)))
1606    (export (;1;) "e1" (instance 0))
1607    (export (;1;) "e2" (func 0))
1608    (export (;1;) "e3" (component 0))
1609    (export (;1;) "e4" (core module 0))
1610    (export (;5;) "e5" (type 1))
1611  )
1612  (component (;2;)
1613    (type (;0;) (tuple u32 u32))
1614    (type (;1;)
1615      (instance
1616        (type (;0;) (func))
1617        (export (;0;) "e2" (func (type 0)))
1618        (type (;1;) (func (param "a" u32)))
1619        (export (;1;) "e3" (func (type 1)))
1620      )
1621    )
1622    (import "i1" (instance (;0;) (type 1)))
1623    (type (;2;) (func))
1624    (import "i2" (func (;0;) (type 2)))
1625    (type (;3;)
1626      (component)
1627    )
1628    (import "i3" (component (;0;) (type 3)))
1629    (core type (;0;)
1630      (module)
1631    )
1632    (import "i4" (core module (;0;) (type 0)))
1633    (import "i5" (type (;4;) (eq 0)))
1634  )
1635  (instance (;1;) (instantiate 1
1636      (with "i1" (instance 0))
1637      (with "i2" (func 0))
1638      (with "i3" (component 0))
1639      (with "i4" (core module 0))
1640      (with "i5" (type 4))
1641    )
1642  )
1643  (alias export 1 "e2" (func (;1;)))
1644  (alias export 1 "e3" (component (;3;)))
1645  (alias export 1 "e4" (core module (;1;)))
1646  (instance (;2;) (instantiate 2
1647      (with "i2" (func 1))
1648      (with "i3" (component 3))
1649      (with "i4" (core module 1))
1650      (with "i1" (instance 0))
1651      (with "i5" (type 4))
1652    )
1653  )
1654  (instance (;3;) (instantiate 2
1655      (with "i2" (func 1))
1656      (with "i3" (component 3))
1657      (with "i4" (core module 1))
1658      (with "i1" (instance 0))
1659      (with "i5" (type 4))
1660    )
1661  )
1662  (instance (;4;) (instantiate 2
1663      (with "i2" (func 1))
1664      (with "i3" (component 3))
1665      (with "i4" (core module 1))
1666      (with "i1" (instance 0))
1667      (with "i5" (type 4))
1668    )
1669  )
1670)
1671"#,
1672            wat
1673        );
1674
1675        Ok(())
1676    }
1677}