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