Skip to main content

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.name.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.name.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_item_for_export(name)?.ty,
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_item_for_import(name)?.ty,
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.ty, 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: 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.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.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_item_for_import(import_name)
595                    .unwrap()
596                    .ty;
597
598                if let ComponentEntityType::Instance(instance_id) = ty {
599                    for (export_name, ty) in &component.types[instance_id].exports {
600                        // TODO: support nested instances
601                        if let ComponentEntityType::Type {
602                            referenced: ComponentAnyTypeId::Resource(resource_id),
603                            ..
604                        } = ty.ty
605                        {
606                            let set = resource_imports
607                                .entry(vec![import_name.to_string(), export_name.to_string()])
608                                .or_default();
609
610                            if let Some(pair) = resource_mapping.map.get(&resource_id.resource()) {
611                                set.insert(*pair);
612                            }
613                            set.insert((*component_id, resource_id.resource()));
614                        }
615                    }
616                }
617            }
618        }
619
620        for resources in resource_imports.values() {
621            match &resources.iter().copied().collect::<Vec<_>>()[..] {
622                [] => unreachable!(),
623                [_] => {}
624                [first, rest @ ..] => {
625                    resource_mapping.map.insert(first.1, *first);
626                    for resource in rest {
627                        resource_mapping.map.insert(resource.1, *first);
628                    }
629                }
630            }
631        }
632    }
633
634    /// Attempt to connect the specified import to the specified export.
635    ///
636    /// This will attempt to match up any resource types by name by name and
637    /// optimistically produce a remapping that sets identically-named pairs
638    /// equal to each other, provided that remapping does not contradict any
639    /// previous remappings.  If the import is not a subtype of the export
640    /// (either because a consistent remapping could not be created or because
641    /// the instances were incompatible for other reasons), we discard the
642    /// remapping changes and return `false`.  Otherwise, we store the remapping
643    /// changes and return `true`.
644    ///
645    /// Note that although this method takes a shared reference, it uses
646    /// internal mutability to update the remapping.
647    pub(crate) fn try_connection(
648        &self,
649        export_component: ComponentId,
650        mut export_type: ComponentEntityType,
651        export_types: TypesRef,
652        mut import_type: ComponentEntityType,
653        import_types: TypesRef,
654    ) -> bool {
655        let resource_mapping = self.resource_mapping.borrow().clone();
656
657        if let Some(resource_mapping) = resource_mapping.add_pairs(
658            export_component,
659            export_type,
660            export_types,
661            import_type,
662            import_types,
663        ) {
664            let remapping = &mut resource_mapping.remapping();
665            let mut context = SubtypeCx::new_with_refs(export_types, import_types);
666
667            context
668                .a
669                .remap_component_entity(&mut export_type, remapping);
670            remapping.reset_type_cache();
671
672            context
673                .b
674                .remap_component_entity(&mut import_type, remapping);
675            remapping.reset_type_cache();
676
677            let v = context.component_entity_type(&export_type, &import_type, 0);
678            if v.is_ok() {
679                *self.resource_mapping.borrow_mut() = resource_mapping;
680                true
681            } else {
682                false
683            }
684        } else {
685            false
686        }
687    }
688
689    pub(crate) fn remapping_map<'b>(
690        &'b self,
691    ) -> HashMap<ResourceId, (&'b Component<'a>, ResourceId)> {
692        let mut map = HashMap::new();
693        for (old, (component, new)) in &self.resource_mapping.borrow().map {
694            if old != new {
695                let component = &self.components.get(component).unwrap().component;
696                map.insert(*old, (component, *new));
697            }
698        }
699        map
700    }
701
702    /// Adds a new component to the graph.
703    ///
704    /// The component name must be unique.
705    pub fn add_component(&mut self, component: Component<'a>) -> Result<ComponentId> {
706        let id = match self.names.entry(component.name.clone()) {
707            Entry::Occupied(e) => {
708                bail!(
709                    "a component with name `{name}` already exists",
710                    name = e.key()
711                )
712            }
713            Entry::Vacant(e) => *e.insert(ComponentId::next()?),
714        };
715
716        log::info!(
717            "adding WebAssembly component `{name}` ({id}) to the graph",
718            name = component.name(),
719        );
720
721        let entry = ComponentEntry {
722            component,
723            instances: HashSet::new(),
724        };
725
726        assert!(self.components.insert(id, entry).is_none());
727
728        if self.components.len() > 1 {
729            self.unify_imported_resources();
730        }
731
732        Ok(id)
733    }
734
735    /// Gets a component from the graph.
736    pub fn get_component(&self, id: impl Into<ComponentId>) -> Option<&Component<'a>> {
737        self.components.get(&id.into()).map(|e| &e.component)
738    }
739
740    /// Gets a component from the graph by name.
741    pub fn get_component_by_name(&self, name: &str) -> Option<(ComponentId, &Component<'a>)> {
742        let id = self.names.get(name)?;
743        let entry = &self.components[id];
744        Some((*id, &entry.component))
745    }
746
747    /// Removes a component from the graph.
748    ///
749    /// All instances and connections relating to the component
750    /// will also be removed.
751    pub fn remove_component(&mut self, id: impl Into<ComponentId>) {
752        let id = id.into();
753        if let Some(entry) = self.components.swap_remove(&id) {
754            log::info!(
755                "removing WebAssembly component `{name}` ({id}) from the graph",
756                name = entry.component.name(),
757            );
758
759            assert!(self.names.remove(&entry.component.name).is_some());
760
761            for instance_id in entry.instances.iter().copied() {
762                self.instances.swap_remove(&instance_id);
763
764                // Remove any connected indexes from outward edges from the instance being removed
765                for (_, target_id, map) in self
766                    .graph
767                    .edges_directed(instance_id, EdgeDirection::Outgoing)
768                {
769                    let target = self.instances.get_mut(&target_id).unwrap();
770                    for index in map.keys() {
771                        target.connected.swap_remove(index);
772                    }
773                }
774
775                self.graph.remove_node(instance_id);
776            }
777        }
778    }
779
780    /// Creates a new instance of a component in the composition graph.
781    pub fn instantiate(&mut self, id: impl Into<ComponentId>) -> Result<InstanceId> {
782        let id = id.into();
783        let entry = self
784            .components
785            .get_mut(&id)
786            .ok_or_else(|| anyhow!("component does not exist in the graph"))?;
787
788        let instance_id = InstanceId::next()?;
789
790        log::info!(
791            "instantiating WebAssembly component `{name}` ({id}) with instance identifier {instance_id}",
792            name = entry.component.name(),
793        );
794
795        self.instances.insert(
796            instance_id,
797            Instance {
798                component: id,
799                connected: Default::default(),
800            },
801        );
802
803        entry.instances.insert(instance_id);
804
805        Ok(instance_id)
806    }
807
808    /// Gets the component of the given instance.
809    pub fn get_component_of_instance(
810        &self,
811        id: impl Into<InstanceId>,
812    ) -> Option<(ComponentId, &Component<'_>)> {
813        let id = id.into();
814        let instance = self.instances.get(&id)?;
815
816        Some((
817            instance.component,
818            self.get_component(instance.component).unwrap(),
819        ))
820    }
821
822    /// Removes an instance from the graph.
823    ///
824    /// All connections relating to the instance will also be removed.
825    pub fn remove_instance(&mut self, id: impl Into<InstanceId>) {
826        let id = id.into();
827        if let Some(instance) = self.instances.swap_remove(&id) {
828            let entry = self.components.get_mut(&instance.component).unwrap();
829
830            log::info!(
831                "removing instance ({id}) of component `{name}` ({cid}) from the graph",
832                name = entry.component.name(),
833                cid = instance.component.0,
834            );
835
836            entry.instances.remove(&id);
837
838            // Remove any connected indexes from outward edges from this instance
839            for (_, target, map) in self.graph.edges_directed(id, EdgeDirection::Outgoing) {
840                let target = self.instances.get_mut(&target).unwrap();
841                for index in map.keys() {
842                    target.connected.swap_remove(index);
843                }
844            }
845
846            self.graph.remove_node(id);
847        }
848    }
849
850    /// Creates a connection (edge) between instances in the composition graph.
851    ///
852    /// A connection represents an instantiation argument.
853    ///
854    /// If `source_export` is `None`, the source instance itself
855    /// is used as the instantiation argument.
856    pub fn connect(
857        &mut self,
858        source: impl Into<InstanceId> + Copy,
859        source_export: Option<impl Into<ExportIndex> + Copy>,
860        target: impl Into<InstanceId> + Copy,
861        target_import: impl Into<ImportIndex> + Copy,
862    ) -> Result<()> {
863        self.validate_connection(source, source_export, target, target_import)?;
864
865        let source = source.into();
866        let source_export = source_export.map(Into::into);
867        let target = target.into();
868        let target_import = target_import.into();
869
870        match source_export {
871            Some(export) => log::info!(
872                "connecting export {export} of instance {source} to import `{target_import}` of instance {target}"
873            ),
874            None => log::info!(
875                "connecting instance {source} to import {target_import} of instance {target}"
876            ),
877        }
878
879        self.instances
880            .get_mut(&target)
881            .unwrap()
882            .connected
883            .insert(target_import);
884
885        if let Some(map) = self.graph.edge_weight_mut(source, target) {
886            assert!(map.insert(target_import, source_export).is_none());
887        } else {
888            let mut map = IndexMap::new();
889            map.insert(target_import, source_export);
890            self.graph.add_edge(source, target, map);
891        }
892
893        Ok(())
894    }
895
896    /// Disconnects a previous connection between instances.
897    ///
898    /// Requires that the source and target instances are valid.
899    ///
900    /// If the source and target are not connected via the target's import,
901    /// then this is a no-op.
902    pub fn disconnect(
903        &mut self,
904        source: impl Into<InstanceId>,
905        target: impl Into<InstanceId>,
906        target_import: impl Into<ImportIndex>,
907    ) -> Result<()> {
908        let source = source.into();
909        let target = target.into();
910        let target_import = target_import.into();
911
912        log::info!("disconnecting import {target_import} of instance {target}");
913
914        if !self.instances.contains_key(&source) {
915            bail!("the source instance does not exist in the graph");
916        }
917
918        let target_instance = self
919            .instances
920            .get_mut(&target)
921            .ok_or_else(|| anyhow!("the target instance does not exist in the graph"))?;
922
923        target_instance.connected.swap_remove(&target_import);
924
925        let remove_edge = if let Some(set) = self.graph.edge_weight_mut(source, target) {
926            set.swap_remove(&target_import);
927            set.is_empty()
928        } else {
929            false
930        };
931
932        if remove_edge {
933            self.graph.remove_edge(source, target);
934        }
935
936        Ok(())
937    }
938
939    /// Validates a connection between two instances in the graph.
940    ///
941    /// Use `None` for `source_export` to signify that the instance
942    /// itself should be the source for the connection.
943    ///
944    /// Returns `Err(_)` if the connection would not be valid.
945    pub fn validate_connection(
946        &self,
947        source: impl Into<InstanceId>,
948        source_export: Option<impl Into<ExportIndex>>,
949        target: impl Into<InstanceId>,
950        target_import: impl Into<ImportIndex>,
951    ) -> Result<()> {
952        let source = source.into();
953        let source_export = source_export.map(Into::into);
954        let target = target.into();
955        let target_import = target_import.into();
956
957        if source == target {
958            bail!("an instance cannot be connected to itself");
959        }
960
961        let source_instance = self
962            .instances
963            .get(&source)
964            .ok_or_else(|| anyhow!("the source instance does not exist in the graph"))?;
965
966        let source_component = &self.components[&source_instance.component].component;
967
968        let target_instance = self
969            .instances
970            .get(&target)
971            .ok_or_else(|| anyhow!("the target instance does not exist in the graph"))?;
972
973        let target_component = &self.components[&target_instance.component].component;
974        let (import_name, import_ty) = target_component
975            .import_entity_type(target_import)
976            .ok_or_else(|| anyhow!("the target import index is invalid"))?;
977
978        if target_instance.connected.contains(&target_import) {
979            bail!(
980                "{import_ty} import `{import_name}` is already connected",
981                import_ty = type_desc(import_ty)
982            );
983        }
984
985        if let Some(export_index) = source_export {
986            let (export_name, export_ty) = source_component
987                .export_entity_type(export_index)
988                .ok_or_else(|| anyhow!("the source export index is invalid"))?;
989
990            if !self.try_connection(
991                source_instance.component,
992                export_ty,
993                source_component.types(),
994                import_ty,
995                target_component.types(),
996            ) {
997                bail!(
998                    "source {export_ty} export `{export_name}` is not compatible with target \
999                         {import_ty} import `{import_name}`",
1000                    export_ty = type_desc(export_ty),
1001                    import_ty = type_desc(import_ty),
1002                );
1003            }
1004        } else {
1005            let ty = match import_ty {
1006                ComponentEntityType::Instance(id) => id,
1007                _ => bail!(
1008                    "source instance is not compatible with target {import_ty} import `{import_name}`",
1009                    import_ty = type_desc(import_ty)
1010                ),
1011            };
1012
1013            if !source_component.is_instance_subtype_of(ty, target_component.types()) {
1014                bail!(
1015                    "source instance is not compatible with target {import_ty} import `{import_name}`",
1016                    import_ty = type_desc(import_ty)
1017                );
1018            }
1019        };
1020
1021        Ok(())
1022    }
1023
1024    /// Encodes the current composition graph as a WebAssembly component.
1025    pub fn encode(&self, options: EncodeOptions) -> Result<Vec<u8>> {
1026        let bytes = CompositionGraphEncoder::new(options, self).encode()?;
1027
1028        if options.validate {
1029            Validator::new()
1030                .validate_all(&bytes)
1031                .context("failed to validate encoded graph bytes")?;
1032        }
1033
1034        Ok(bytes)
1035    }
1036
1037    /// Gets the topological instantiation order based on the composition graph.
1038    ///
1039    /// If an instance is not in the returned set, it is considered to be
1040    /// "independent" (i.e it has no dependencies on other instances).
1041    pub(crate) fn instantiation_order(&self) -> Result<Vec<InstanceId>> {
1042        toposort(&self.graph, None).map_err(|e| {
1043            let id = e.node_id();
1044            let instance = &self.instances[&id];
1045            anyhow!(
1046                "an instantiation of component `{name}` and its dependencies form a cycle in the instantiation graph",
1047                name = self.components[&instance.component].component.name,
1048            )
1049        })
1050    }
1051}
1052
1053#[cfg(test)]
1054mod test {
1055    use super::*;
1056    use pretty_assertions::assert_eq;
1057
1058    #[test]
1059    fn it_rejects_modules() -> Result<()> {
1060        let mut validator = Validator::new();
1061        match Component::from_bytes(&mut validator, "a", b"(module)".as_ref()) {
1062            Ok(_) => panic!("expected a failure to parse"),
1063            Err(e) => assert_eq!(
1064                format!("{e:#}"),
1065                "failed to parse component: the given data is not a WebAssembly component"
1066            ),
1067        }
1068
1069        Ok(())
1070    }
1071
1072    #[test]
1073    fn it_rejects_invalid_components() -> Result<()> {
1074        let mut validator = Validator::new();
1075        match Component::from_bytes(
1076            &mut validator,
1077            "a",
1078            b"(component (export \"x\" (func 0)))".as_ref(),
1079        ) {
1080            Ok(_) => panic!("expected a failure to parse"),
1081            Err(e) => assert_eq!(
1082                format!("{e:#}"),
1083                "failed to parse component: unknown function 0: function index out of bounds (at offset 0xb)"
1084            ),
1085        }
1086
1087        Ok(())
1088    }
1089
1090    #[test]
1091    fn it_ensures_unique_component_names() -> Result<()> {
1092        let mut graph = CompositionGraph::new();
1093        let mut validator = Validator::new();
1094        graph.add_component(Component::from_bytes(
1095            &mut validator,
1096            "a",
1097            b"(component)".as_ref(),
1098        )?)?;
1099
1100        match graph.add_component(Component::from_bytes(
1101            &mut validator,
1102            "a",
1103            b"(component)".as_ref(),
1104        )?) {
1105            Ok(_) => panic!("expected a failure to add component"),
1106            Err(e) => assert_eq!(format!("{e:#}"), "a component with name `a` already exists"),
1107        }
1108
1109        Ok(())
1110    }
1111
1112    #[test]
1113    fn it_fails_to_instantiate_a_missing_component() -> Result<()> {
1114        let mut graph = CompositionGraph::new();
1115        match graph.instantiate(ComponentId(0)) {
1116            Ok(_) => panic!("expected a failure to instantiate"),
1117            Err(e) => assert_eq!(format!("{e:#}"), "component does not exist in the graph"),
1118        }
1119
1120        Ok(())
1121    }
1122
1123    #[test]
1124    fn it_instantiates_a_component() -> Result<()> {
1125        let mut graph = CompositionGraph::new();
1126        let mut validator = Validator::new();
1127        let id = graph.add_component(Component::from_bytes(
1128            &mut validator,
1129            "a",
1130            b"(component)".as_ref(),
1131        )?)?;
1132        let id = graph.instantiate(id)?;
1133        assert_eq!(graph.get_component_of_instance(id).unwrap().1.name(), "a");
1134        Ok(())
1135    }
1136
1137    #[test]
1138    fn it_cannot_get_a_component_of_missing_instance() -> Result<()> {
1139        let graph = CompositionGraph::new();
1140        assert!(graph.get_component_of_instance(InstanceId(0)).is_none());
1141        Ok(())
1142    }
1143
1144    #[test]
1145    fn it_gets_a_component() -> Result<()> {
1146        let mut graph = CompositionGraph::new();
1147        let mut validator = Validator::new();
1148        let id = graph.add_component(Component::from_bytes(
1149            &mut validator,
1150            "a",
1151            b"(component)".as_ref(),
1152        )?)?;
1153        assert_eq!(graph.get_component(id).unwrap().name(), "a");
1154        assert_eq!(graph.get_component_by_name("a").unwrap().1.name(), "a");
1155        Ok(())
1156    }
1157
1158    #[test]
1159    fn it_removes_a_component() -> Result<()> {
1160        let mut graph = CompositionGraph::new();
1161        let mut validator = Validator::new();
1162        let a = graph.add_component(Component::from_bytes(
1163            &mut validator,
1164            "a",
1165            b"(component (import \"x\" (func)))".as_ref(),
1166        )?)?;
1167        let b = graph.add_component(Component::from_bytes(
1168            &mut validator,
1169            "b",
1170            b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(),
1171        )?)?;
1172        let ai = graph.instantiate(a)?;
1173        let bi = graph.instantiate(b)?;
1174        graph.connect(bi, Some(0), ai, 0)?;
1175
1176        assert!(graph.get_component(a).is_some());
1177        assert!(graph.get_component(b).is_some());
1178        assert_eq!(graph.components.len(), 2);
1179        assert_eq!(graph.instances.len(), 2);
1180        assert_eq!(graph.graph.node_count(), 2);
1181        assert_eq!(graph.graph.edge_count(), 1);
1182
1183        graph.remove_component(b);
1184
1185        assert!(graph.get_component(a).is_some());
1186        assert!(graph.get_component(b).is_none());
1187        assert_eq!(graph.components.len(), 1);
1188        assert_eq!(graph.instances.len(), 1);
1189        assert_eq!(graph.graph.node_count(), 1);
1190        assert_eq!(graph.graph.edge_count(), 0);
1191        Ok(())
1192    }
1193
1194    #[test]
1195    fn it_removes_a_connection() -> Result<()> {
1196        let mut graph = CompositionGraph::new();
1197        let mut validator = Validator::new();
1198        let a = graph.add_component(Component::from_bytes(
1199            &mut validator,
1200            "a",
1201            b"(component (import \"x\" (func)))".as_ref(),
1202        )?)?;
1203        let b = graph.add_component(Component::from_bytes(
1204            &mut validator,
1205            "b",
1206            b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(),
1207        )?)?;
1208        let ai = graph.instantiate(a)?;
1209        let bi = graph.instantiate(b)?;
1210        graph.connect(bi, Some(0), ai, 0)?;
1211
1212        assert_eq!(graph.graph.node_count(), 2);
1213        assert_eq!(graph.graph.edge_count(), 1);
1214
1215        graph.disconnect(bi, ai, 0)?;
1216
1217        assert_eq!(graph.graph.node_count(), 2);
1218        assert_eq!(graph.graph.edge_count(), 0);
1219        Ok(())
1220    }
1221
1222    #[test]
1223    fn it_requires_source_to_disconnect() -> Result<()> {
1224        let mut graph = CompositionGraph::new();
1225        let mut validator = Validator::new();
1226        let a = graph.add_component(Component::from_bytes(
1227            &mut validator,
1228            "a",
1229            b"(component (import \"x\" (func)))".as_ref(),
1230        )?)?;
1231        let b = graph.add_component(Component::from_bytes(
1232            &mut validator,
1233            "b",
1234            b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(),
1235        )?)?;
1236        let ai = graph.instantiate(a)?;
1237        let bi = graph.instantiate(b)?;
1238        graph.connect(bi, Some(0), ai, 0)?;
1239
1240        match graph.disconnect(101, ai, 0) {
1241            Ok(_) => panic!("expected a failure to disconnect"),
1242            Err(e) => assert_eq!(
1243                format!("{e:#}"),
1244                "the source instance does not exist in the graph"
1245            ),
1246        }
1247
1248        Ok(())
1249    }
1250
1251    #[test]
1252    fn it_requires_a_target_to_disconnect() -> Result<()> {
1253        let mut graph = CompositionGraph::new();
1254        let mut validator = Validator::new();
1255        let a = graph.add_component(Component::from_bytes(
1256            &mut validator,
1257            "a",
1258            b"(component (import \"x\" (func)))".as_ref(),
1259        )?)?;
1260        let b = graph.add_component(Component::from_bytes(
1261            &mut validator,
1262            "b",
1263            b"(component (import \"x\" (func)) (export \"y\" (func 0)))".as_ref(),
1264        )?)?;
1265        let ai = graph.instantiate(a)?;
1266        let bi = graph.instantiate(b)?;
1267        graph.connect(bi, Some(0), ai, 0)?;
1268
1269        match graph.disconnect(bi, 101, 0) {
1270            Ok(_) => panic!("expected a failure to disconnect"),
1271            Err(e) => assert_eq!(
1272                format!("{e:#}"),
1273                "the target instance does not exist in the graph"
1274            ),
1275        }
1276
1277        Ok(())
1278    }
1279
1280    #[test]
1281    fn it_validates_connections() -> Result<()> {
1282        let mut graph = CompositionGraph::new();
1283        let mut validator = Validator::new();
1284        let a = graph.add_component(Component::from_bytes(
1285            &mut validator,
1286            "a",
1287            b"(component (import \"i1\" (func)) (import \"i2\" (instance (export \"no\" (func)))))"
1288                .as_ref(),
1289        )?)?;
1290        let b = graph.add_component(Component::from_bytes(
1291            &mut validator,
1292            "b",
1293            b"(component (import \"i1\" (func)) (import \"i2\" (core module)) (export \"e1\" (func 0)) (export \"e2\" (core module 0)))".as_ref(),
1294        )?)?;
1295        let ai = graph.instantiate(a)?;
1296        let bi = graph.instantiate(b)?;
1297
1298        match graph.connect(ai, None::<ExportIndex>, ai, 0) {
1299            Ok(_) => panic!("expected a failure to connect"),
1300            Err(e) => assert_eq!(
1301                format!("{e:#}"),
1302                "an instance cannot be connected to itself"
1303            ),
1304        }
1305
1306        match graph.connect(ai, Some(0), bi, 0) {
1307            Ok(_) => panic!("expected a failure to connect"),
1308            Err(e) => assert_eq!(format!("{e:#}"), "the source export index is invalid"),
1309        }
1310
1311        match graph.connect(101, Some(0), ai, 0) {
1312            Ok(_) => panic!("expected a failure to connect"),
1313            Err(e) => assert_eq!(
1314                format!("{e:#}"),
1315                "the source instance does not exist in the graph"
1316            ),
1317        }
1318
1319        match graph.connect(bi, Some(0), 101, 0) {
1320            Ok(_) => panic!("expected a failure to connect"),
1321            Err(e) => assert_eq!(
1322                format!("{e:#}"),
1323                "the target instance does not exist in the graph"
1324            ),
1325        }
1326
1327        match graph.connect(bi, Some(101), ai, 0) {
1328            Ok(_) => panic!("expected a failure to connect"),
1329            Err(e) => assert_eq!(format!("{e:#}"), "the source export index is invalid"),
1330        }
1331
1332        match graph.connect(bi, Some(0), ai, 101) {
1333            Ok(_) => panic!("expected a failure to connect"),
1334            Err(e) => assert_eq!(format!("{e:#}"), "the target import index is invalid"),
1335        }
1336
1337        match graph.connect(bi, Some(1), ai, 0) {
1338            Ok(_) => panic!("expected a failure to connect"),
1339            Err(e) => assert_eq!(
1340                format!("{e:#}"),
1341                "source module export `e2` is not compatible with target function import `i1`"
1342            ),
1343        }
1344
1345        match graph.connect(bi, None::<ExportIndex>, ai, 0) {
1346            Ok(_) => panic!("expected a failure to connect"),
1347            Err(e) => assert_eq!(
1348                format!("{e:#}"),
1349                "source instance is not compatible with target function import `i1`"
1350            ),
1351        }
1352
1353        match graph.connect(bi, None::<ExportIndex>, ai, 1) {
1354            Ok(_) => panic!("expected a failure to connect"),
1355            Err(e) => assert_eq!(
1356                format!("{e:#}"),
1357                "source instance is not compatible with target instance import `i2`"
1358            ),
1359        }
1360
1361        Ok(())
1362    }
1363
1364    #[test]
1365    fn it_cannot_encode_a_cycle() -> Result<()> {
1366        let mut graph = CompositionGraph::new();
1367        let mut validator = Validator::new();
1368        let a = graph.add_component(Component::from_bytes(
1369            &mut validator,
1370            "a",
1371            b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(),
1372        )?)?;
1373        let b = graph.add_component(Component::from_bytes(
1374            &mut validator,
1375            "b",
1376            b"(component (import \"i1\" (func)) (export \"e1\" (func 0)))".as_ref(),
1377        )?)?;
1378        let ai = graph.instantiate(a)?;
1379        let bi = graph.instantiate(b)?;
1380
1381        graph.connect(ai, Some(0), bi, 0)?;
1382        graph.connect(bi, Some(0), ai, 0)?;
1383
1384        match graph.encode(EncodeOptions {
1385            define_components: false,
1386            export: None,
1387            validate: true,
1388        }) {
1389            Ok(_) => panic!("graph should not encode"),
1390            Err(e) => assert_eq!(
1391                format!("{e:#}"),
1392                "an instantiation of component `b` and its dependencies form a cycle in the instantiation graph"
1393            ),
1394        }
1395
1396        Ok(())
1397    }
1398
1399    #[test]
1400    fn it_encodes_an_empty_component() -> Result<()> {
1401        let mut graph = CompositionGraph::new();
1402        let mut validator = Validator::new();
1403        graph.add_component(Component::from_bytes(
1404            &mut validator,
1405            "a",
1406            b"(component)".as_ref(),
1407        )?)?;
1408        graph.add_component(Component::from_bytes(
1409            &mut validator,
1410            "b",
1411            b"(component)".as_ref(),
1412        )?)?;
1413
1414        let encoded = graph.encode(EncodeOptions {
1415            define_components: false,
1416            export: None,
1417            validate: true,
1418        })?;
1419
1420        let wat = wasmprinter::print_bytes(encoded)?;
1421        assert_eq!(
1422            r#"(component)
1423"#,
1424            wat
1425        );
1426
1427        Ok(())
1428    }
1429
1430    #[test]
1431    fn it_encodes_component_imports() -> Result<()> {
1432        let mut graph = CompositionGraph::new();
1433        let mut validator = Validator::new();
1434        // Add a component that doesn't get instantiated (shouldn't be imported)
1435        graph.add_component(Component::from_bytes(
1436            &mut validator,
1437            "a",
1438            b"(component)".as_ref(),
1439        )?)?;
1440        let b = graph.add_component(Component::from_bytes(
1441            &mut validator,
1442            "b",
1443            b"(component)".as_ref(),
1444        )?)?;
1445        graph.instantiate(b)?;
1446
1447        let encoded = graph.encode(EncodeOptions {
1448            define_components: false,
1449            export: None,
1450            validate: true,
1451        })?;
1452
1453        let wat = wasmprinter::print_bytes(encoded)?.replace("\r\n", "\n");
1454        assert_eq!(
1455            r#"(component
1456  (type (;0;)
1457    (component)
1458  )
1459  (import "b" (component (;0;) (type 0)))
1460  (instance (;0;) (instantiate 0))
1461)
1462"#,
1463            wat
1464        );
1465
1466        Ok(())
1467    }
1468
1469    #[test]
1470    fn it_encodes_defined_components() -> Result<()> {
1471        let mut graph = CompositionGraph::new();
1472        let mut validator = Validator::new();
1473        // Add a component that doesn't get instantiated (shouldn't be imported)
1474        graph.add_component(Component::from_bytes(
1475            &mut validator,
1476            "a",
1477            b"(component)".as_ref(),
1478        )?)?;
1479        let b = graph.add_component(Component::from_bytes(
1480            &mut validator,
1481            "b",
1482            b"(component)".as_ref(),
1483        )?)?;
1484        graph.instantiate(b)?;
1485
1486        let encoded = graph.encode(EncodeOptions {
1487            define_components: true,
1488            export: None,
1489            validate: true,
1490        })?;
1491
1492        let wat = wasmprinter::print_bytes(encoded)?.replace("\r\n", "\n");
1493        assert_eq!(
1494            r#"(component
1495  (component (;0;))
1496  (instance (;0;) (instantiate 0))
1497)
1498"#,
1499            wat
1500        );
1501
1502        Ok(())
1503    }
1504
1505    #[test]
1506    fn it_encodes_a_simple_composition() -> Result<()> {
1507        let mut graph = CompositionGraph::new();
1508        let mut validator = Validator::new();
1509        let a = graph.add_component(Component::from_bytes(
1510            &mut validator,
1511            "a",
1512            b"(component
1513  (type (tuple u32 u32))
1514  (import \"i1\" (instance (export \"e1\" (func)) (export \"e3\" (func (param \"a\" u32)))))
1515  (import \"i2\" (func))
1516  (import \"i3\" (component))
1517  (import \"i4\" (core module))
1518  (import \"i5\" (type (eq 0)))
1519  (export \"e1\" (instance 0))
1520  (export \"e2\" (func 0))
1521  (export \"e3\" (component 0))
1522  (export \"e4\" (core module 0))
1523  (export \"e5\" (type 1))
1524)"
1525            .as_ref(),
1526        )?)?;
1527        let b = graph.add_component(Component::from_bytes(
1528            &mut validator,
1529            "b",
1530            b"(component
1531  (type (tuple u32 u32))
1532  (import \"i1\" (instance (export \"e2\" (func)) (export \"e3\" (func (param \"a\" u32)))))
1533  (import \"i2\" (func))
1534  (import \"i3\" (component))
1535  (import \"i4\" (core module))
1536  (import \"i5\" (type (eq 0)))
1537)"
1538            .as_ref(),
1539        )?)?;
1540
1541        let ai = graph.instantiate(a)?;
1542        let bi1 = graph.instantiate(b)?;
1543        let bi2 = graph.instantiate(b)?;
1544        let bi3 = graph.instantiate(b)?;
1545
1546        // Skip the instance arguments so a merged instance is imported
1547        for i in 1..=3 {
1548            graph.connect(ai, Some(i), bi1, i)?;
1549            graph.connect(ai, Some(i), bi2, i)?;
1550            graph.connect(ai, Some(i), bi3, i)?;
1551        }
1552
1553        let encoded = graph.encode(EncodeOptions {
1554            define_components: true,
1555            export: None,
1556            validate: true,
1557        })?;
1558
1559        let wat = wasmprinter::print_bytes(encoded)?.replace("\r\n", "\n");
1560        assert_eq!(
1561            r#"(component
1562  (type (;0;)
1563    (instance
1564      (type (;0;) (func))
1565      (export (;0;) "e1" (func (type 0)))
1566      (type (;1;) (func (param "a" u32)))
1567      (export (;1;) "e3" (func (type 1)))
1568      (type (;2;) (func))
1569      (export (;2;) "e2" (func (type 2)))
1570    )
1571  )
1572  (import "i1" (instance (;0;) (type 0)))
1573  (type (;1;) (func))
1574  (import "i2" (func (;0;) (type 1)))
1575  (type (;2;)
1576    (component)
1577  )
1578  (import "i3" (component (;0;) (type 2)))
1579  (core type (;0;)
1580    (module)
1581  )
1582  (import "i4" (core module (;0;) (type 0)))
1583  (type (;3;) (tuple u32 u32))
1584  (import "i5" (type (;4;) (eq 3)))
1585  (component (;1;)
1586    (type (;0;) (tuple u32 u32))
1587    (type (;1;)
1588      (instance
1589        (type (;0;) (func))
1590        (export (;0;) "e1" (func (type 0)))
1591        (type (;1;) (func (param "a" u32)))
1592        (export (;1;) "e3" (func (type 1)))
1593      )
1594    )
1595    (import "i1" (instance (;0;) (type 1)))
1596    (type (;2;) (func))
1597    (import "i2" (func (;0;) (type 2)))
1598    (type (;3;)
1599      (component)
1600    )
1601    (import "i3" (component (;0;) (type 3)))
1602    (core type (;0;)
1603      (module)
1604    )
1605    (import "i4" (core module (;0;) (type 0)))
1606    (import "i5" (type (;4;) (eq 0)))
1607    (export (;1;) "e1" (instance 0))
1608    (export (;1;) "e2" (func 0))
1609    (export (;1;) "e3" (component 0))
1610    (export (;1;) "e4" (core module 0))
1611    (export (;5;) "e5" (type 1))
1612  )
1613  (component (;2;)
1614    (type (;0;) (tuple u32 u32))
1615    (type (;1;)
1616      (instance
1617        (type (;0;) (func))
1618        (export (;0;) "e2" (func (type 0)))
1619        (type (;1;) (func (param "a" u32)))
1620        (export (;1;) "e3" (func (type 1)))
1621      )
1622    )
1623    (import "i1" (instance (;0;) (type 1)))
1624    (type (;2;) (func))
1625    (import "i2" (func (;0;) (type 2)))
1626    (type (;3;)
1627      (component)
1628    )
1629    (import "i3" (component (;0;) (type 3)))
1630    (core type (;0;)
1631      (module)
1632    )
1633    (import "i4" (core module (;0;) (type 0)))
1634    (import "i5" (type (;4;) (eq 0)))
1635  )
1636  (instance (;1;) (instantiate 1
1637      (with "i1" (instance 0))
1638      (with "i2" (func 0))
1639      (with "i3" (component 0))
1640      (with "i4" (core module 0))
1641      (with "i5" (type 4))
1642    )
1643  )
1644  (alias export 1 "e2" (func (;1;)))
1645  (alias export 1 "e3" (component (;3;)))
1646  (alias export 1 "e4" (core module (;1;)))
1647  (instance (;2;) (instantiate 2
1648      (with "i2" (func 1))
1649      (with "i3" (component 3))
1650      (with "i4" (core module 1))
1651      (with "i1" (instance 0))
1652      (with "i5" (type 4))
1653    )
1654  )
1655  (instance (;3;) (instantiate 2
1656      (with "i2" (func 1))
1657      (with "i3" (component 3))
1658      (with "i4" (core module 1))
1659      (with "i1" (instance 0))
1660      (with "i5" (type 4))
1661    )
1662  )
1663  (instance (;4;) (instantiate 2
1664      (with "i2" (func 1))
1665      (with "i3" (component 3))
1666      (with "i4" (core module 1))
1667      (with "i1" (instance 0))
1668      (with "i5" (type 4))
1669    )
1670  )
1671)
1672"#,
1673            wat
1674        );
1675
1676        Ok(())
1677    }
1678}