1use crate::{
4 config::Config,
5 encoding::CompositionGraphEncoder,
6 graph::{
7 Component, ComponentId, CompositionGraph, EncodeOptions, ExportIndex, ImportIndex,
8 InstanceId,
9 },
10};
11use anyhow::{Context, Result, anyhow, bail};
12use indexmap::IndexMap;
13use std::{collections::VecDeque, ffi::OsStr, path::Path};
14use wasmparser::{
15 ComponentExternalKind, ComponentTypeRef, Validator, WasmFeatures,
16 component_types::{ComponentEntityType, ComponentInstanceTypeId},
17 types::TypesRef,
18};
19
20pub const ROOT_COMPONENT_NAME: &str = "root";
22
23#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
25pub(crate) struct InstanceImportRef {
26 pub(crate) component: ComponentId,
28 pub(crate) import: ImportIndex,
30}
31
32enum DependencyKind {
34 Instance {
36 instance: String,
38 export: Option<String>,
40 },
41
42 Definition {
44 index: usize,
46 export: ExportIndex,
48 },
49}
50
51struct Dependency {
53 dependent: usize,
55 import: InstanceImportRef,
57 kind: DependencyKind,
59}
60
61struct CompositionGraphBuilder<'a> {
64 config: &'a Config,
66 graph: CompositionGraph<'a>,
68 instances: IndexMap<String, InstanceId>,
70 definitions: Vec<(ComponentId, Option<InstanceId>)>,
72 validator: Validator,
74}
75
76impl<'a> CompositionGraphBuilder<'a> {
77 fn new(root_path: &Path, config: &'a Config) -> Result<Self> {
78 let mut graph = CompositionGraph::new();
79 let mut validator = Validator::new_with_features(WasmFeatures::all());
80 graph.add_component(Component::from_file(
81 &mut validator,
82 ROOT_COMPONENT_NAME,
83 root_path,
84 )?)?;
85
86 let definitions = config
87 .definitions
88 .iter()
89 .map(|path| {
90 let name = path.file_stem().and_then(OsStr::to_str).with_context(|| {
91 format!(
92 "invalid definition component path `{path}`",
93 path = path.display()
94 )
95 })?;
96
97 let component = Component::from_file(&mut validator, name, config.dir.join(path))?;
98
99 Ok((graph.add_component(component)?, None))
100 })
101 .collect::<Result<_>>()?;
102
103 Ok(Self {
104 config,
105 graph,
106 instances: Default::default(),
107 definitions,
108 validator,
109 })
110 }
111
112 fn add_component(&mut self, name: &str) -> Result<Option<ComponentId>> {
117 if let Some((id, _)) = self.graph.get_component_by_name(name) {
118 return Ok(Some(id));
119 }
120
121 match self.find_component(name)? {
122 Some(component) => Ok(Some(self.graph.add_component(component)?)),
123 None => Ok(None),
124 }
125 }
126
127 fn find_component(&mut self, name: &str) -> Result<Option<Component<'a>>> {
129 if let Some(dep) = self.config.dependencies.get(name) {
131 log::debug!(
132 "component with name `{name}` has an explicit path of `{path}`",
133 path = dep.path.display()
134 );
135 return Ok(Some(Component::from_file(
136 &mut self.validator,
137 name,
138 self.config.dir.join(&dep.path),
139 )?));
140 }
141
142 log::info!("searching for a component with name `{name}`");
144 for dir in std::iter::once(&self.config.dir).chain(self.config.search_paths.iter()) {
145 if let Some(component) = Self::parse_component(&mut self.validator, dir, name)? {
146 return Ok(Some(component));
147 }
148 }
149
150 Ok(None)
151 }
152
153 fn parse_component(
157 validator: &mut Validator,
158 dir: &Path,
159 name: &str,
160 ) -> Result<Option<Component<'a>>> {
161 let mut path = dir.join(name);
162
163 for ext in ["wasm", "wat"] {
164 path.set_extension(ext);
165 if !path.is_file() {
166 log::info!("component `{path}` does not exist", path = path.display());
167 continue;
168 }
169
170 return Ok(Some(Component::from_file(validator, name, &path)?));
171 }
172
173 Ok(None)
174 }
175
176 fn instantiate(&mut self, name: &str, component_name: &str) -> Result<Option<(usize, bool)>> {
182 if let Some(index) = self.instances.get_index_of(name) {
183 return Ok(Some((index, true)));
184 }
185
186 match self.add_component(component_name)? {
187 Some(component_id) => {
188 let (index, prev) = self
189 .instances
190 .insert_full(name.to_string(), self.graph.instantiate(component_id)?);
191 assert!(prev.is_none());
192 Ok(Some((index, false)))
193 }
194 None => {
195 if self.config.disallow_imports {
196 bail!(
197 "a dependency named `{component_name}` could not be found and instance imports are not allowed"
198 );
199 }
200
201 log::warn!(
202 "instance `{name}` will be imported because a dependency named `{component_name}` could not be found"
203 );
204 Ok(None)
205 }
206 }
207 }
208
209 fn find_compatible_instance(
215 &self,
216 instance: usize,
217 dependent: usize,
218 arg_name: &str,
219 ty: ComponentInstanceTypeId,
220 types: TypesRef,
221 ) -> Result<Option<ExportIndex>> {
222 let (instance_name, instance_id) = self.instances.get_index(instance).unwrap();
223 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
224
225 let (dependent_name, dependent_instance_id) = self.instances.get_index(dependent).unwrap();
226
227 if component.is_instance_subtype_of(ty, types) {
229 log::debug!(
231 "instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`"
232 );
233 return Ok(None);
234 }
235
236 log::debug!(
237 "searching for compatible export from instance `{instance_name}` for argument `{arg_name}` of instance `{dependent_name}`"
238 );
239
240 let export = component.find_compatible_export(ty, types, component_id, &self.graph).ok_or_else(|| {
241 anyhow!(
242 "component `{path}` is not compatible with import `{arg_name}` of component `{dependent_path}`",
243 path = component.path().unwrap().display(),
244 dependent_path = self.graph.get_component_of_instance(*dependent_instance_id).unwrap().1.path().unwrap().display(),
245 )
246 })?;
247
248 log::debug!(
249 "export `{export_name}` (export index {export}) from instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`",
250 export = export.0,
251 export_name = component.exports.get_index(export.0).unwrap().0,
252 );
253
254 Ok(Some(export))
255 }
256
257 fn resolve_export_index(
261 &self,
262 export: &str,
263 instance: usize,
264 dependent_path: &Path,
265 arg_name: &str,
266 ty: ComponentInstanceTypeId,
267 types: TypesRef,
268 ) -> Result<ExportIndex> {
269 let (_, instance_id) = self.instances.get_index(instance).unwrap();
270 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
271
272 match component.export_by_name(export) {
273 Some((export_index, kind, index)) if kind == ComponentExternalKind::Instance => {
274 let export_ty = component.types.as_ref().component_instance_at(index);
275
276 if self.graph.try_connection(
277 component_id,
278 ComponentEntityType::Instance(export_ty),
279 component.types(),
280 ComponentEntityType::Instance(ty),
281 types,
282 ) {
283 Ok(export_index)
284 } else {
285 bail!(
286 "component `{path}` exports an instance named `{export}` \
287 but it is not compatible with import `{arg_name}` \
288 of component `{dependent_path}`",
289 path = component.path().unwrap().display(),
290 dependent_path = dependent_path.display(),
291 )
292 }
293 }
294 _ => bail!(
295 "component `{path}` does not export an instance named `{export}`",
296 path = component.path().unwrap().display(),
297 ),
298 }
299 }
300
301 fn resolve_import_ref(
303 &self,
304 r: InstanceImportRef,
305 ) -> (&Component<'_>, &str, ComponentInstanceTypeId) {
306 let component = self.graph.get_component(r.component).unwrap();
307 let (name, ty) = component.import(r.import).unwrap();
308 match ty {
309 ComponentTypeRef::Instance(index) => (
310 component,
311 name,
312 component
313 .types
314 .as_ref()
315 .component_any_type_at(index)
316 .unwrap_instance(),
317 ),
318 _ => unreachable!("should not have an instance import ref to a non-instance import"),
319 }
320 }
321
322 fn process_dependency(&mut self, dependency: Dependency) -> Result<Option<usize>> {
326 match dependency.kind {
327 DependencyKind::Instance { instance, export } => self.process_instance_dependency(
328 dependency.dependent,
329 dependency.import,
330 &instance,
331 export.as_deref(),
332 ),
333 DependencyKind::Definition { index, export } => {
334 let (component_id, instance_id) = &mut self.definitions[index];
336 let instance_id = *instance_id
337 .get_or_insert_with(|| self.graph.instantiate(*component_id).unwrap());
338
339 self.graph
340 .connect(
341 instance_id,
342 Some(export),
343 self.instances[dependency.dependent],
344 dependency.import.import,
345 )
346 .with_context(|| {
347 let name = self.instances.get_index(dependency.dependent).unwrap().0;
348 format!(
349 "failed to connect instance `{name}` to definition component `{path}`",
350 path = self
351 .graph
352 .get_component(*component_id)
353 .unwrap()
354 .path()
355 .unwrap()
356 .display(),
357 )
358 })?;
359
360 Ok(None)
362 }
363 }
364 }
365
366 fn process_instance_dependency(
367 &mut self,
368 dependent_index: usize,
369 import: InstanceImportRef,
370 instance: &str,
371 export: Option<&str>,
372 ) -> Result<Option<usize>> {
373 let name = self.config.dependency_name(instance);
374
375 log::info!(
376 "processing dependency `{name}` from instance `{dependent_name}` to instance `{instance}`",
377 dependent_name = self.instances.get_index(dependent_index).unwrap().0,
378 );
379
380 match self.instantiate(instance, name)? {
381 Some((instance, existing)) => {
382 let (dependent, import_name, import_type) = self.resolve_import_ref(import);
383
384 let export = match export {
385 Some(export) => Some(self.resolve_export_index(
386 export,
387 instance,
388 dependent.path().unwrap(),
389 import_name,
390 import_type,
391 dependent.types(),
392 )?),
393 None => self.find_compatible_instance(
394 instance,
395 dependent_index,
396 import_name,
397 import_type,
398 dependent.types(),
399 )?,
400 };
401
402 self.graph.connect(
404 self.instances[instance],
405 export,
406 self.instances[dependent_index],
407 import.import,
408 )?;
409
410 if existing {
411 return Ok(None);
412 }
413
414 Ok(Some(instance))
415 }
416 None => {
417 if let Some(export) = export {
418 bail!(
419 "an explicit export `{export}` cannot be specified for imported instance `{name}`"
420 );
421 }
422 Ok(None)
423 }
424 }
425 }
426
427 fn push_dependencies(&self, instance: usize, queue: &mut VecDeque<Dependency>) -> Result<()> {
429 let (instance_name, instance_id) = self.instances.get_index(instance).unwrap();
430 let instantiation = self.config.instantiations.get(instance_name);
431 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
432 let count = queue.len();
433
434 'outer: for (import, name, _) in component.imports() {
436 log::debug!(
437 "adding dependency for argument `{name}` (import index {import}) from instance `{instance_name}` to the queue",
438 import = import.0
439 );
440
441 for (index, (def_component_id, _)) in self.definitions.iter().enumerate() {
443 let def_component = self.graph.get_component(*def_component_id).unwrap();
444
445 match def_component.export_by_name(name) {
446 Some((export, ComponentExternalKind::Instance, _)) => {
447 log::debug!(
448 "found matching instance export `{name}` in definition component `{path}`",
449 path = def_component.path().unwrap().display()
450 );
451
452 queue.push_back(Dependency {
453 dependent: instance,
454 import: InstanceImportRef {
455 component: component_id,
456 import,
457 },
458 kind: DependencyKind::Definition { index, export },
459 });
460
461 continue 'outer;
462 }
463 _ => continue,
464 }
465 }
466
467 let arg = instantiation.and_then(|c| c.arguments.get(name));
468 queue.push_back(Dependency {
469 dependent: instance,
470 import: InstanceImportRef {
471 component: component_id,
472 import,
473 },
474 kind: DependencyKind::Instance {
475 instance: arg
476 .map(|arg| arg.instance.clone())
477 .unwrap_or_else(|| name.to_string()),
478 export: arg.and_then(|arg| arg.export.clone()),
479 },
480 });
481 }
482
483 if let Some(instantiation) = instantiation {
485 for arg in instantiation.arguments.keys() {
486 if !component.imports.contains_key(arg) {
487 bail!(
488 "component `{path}` has no import named `{arg}`",
489 path = component.path().unwrap().display()
490 );
491 }
492 }
493 }
494
495 if count == queue.len() && instance == 0 {
497 bail!(
498 "component `{path}` does not import any instances",
499 path = component.path().unwrap().display()
500 );
501 }
502
503 Ok(())
504 }
505
506 fn build(mut self) -> Result<(InstanceId, CompositionGraph<'a>)> {
508 let mut queue: VecDeque<Dependency> = VecDeque::new();
509
510 let (root_instance, existing) = self
512 .instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME)?
513 .unwrap();
514
515 assert!(!existing);
516
517 self.push_dependencies(0, &mut queue)?;
518
519 while let Some(dependency) = queue.pop_front() {
521 if let Some(instance) = self.process_dependency(dependency)? {
522 self.push_dependencies(instance, &mut queue)?;
523 }
524 }
525
526 Ok((self.instances[root_instance], self.graph))
527 }
528}
529
530pub struct ComponentComposer<'a> {
538 component: &'a Path,
539 config: &'a Config,
540}
541
542impl<'a> ComponentComposer<'a> {
543 pub fn new(component: &'a Path, config: &'a Config) -> Self {
549 Self { component, config }
550 }
551
552 pub fn compose(&self) -> Result<Vec<u8>> {
557 let (root_instance, graph) =
558 CompositionGraphBuilder::new(self.component, self.config)?.build()?;
559
560 if graph.instances.len() == 1 {
562 bail!(
563 "no dependencies of component `{path}` were found",
564 path = self.component.display()
565 );
566 }
567
568 CompositionGraphEncoder::new(
569 EncodeOptions {
570 define_components: !self.config.import_components,
571 export: Some(root_instance),
572 validate: false,
573 },
574 &graph,
575 )
576 .encode()
577 }
578}