1use crate::{
4 config::Config,
5 encoding::CompositionGraphEncoder,
6 graph::{
7 Component, ComponentId, CompositionGraph, EncodeOptions, ExportIndex, ImportIndex,
8 InstanceId,
9 },
10};
11use anyhow::{anyhow, bail, Context, Result};
12use indexmap::IndexMap;
13use std::{collections::VecDeque, ffi::OsStr, path::Path};
14use wasmparser::{
15 component_types::{ComponentEntityType, ComponentInstanceTypeId},
16 types::TypesRef,
17 ComponentExternalKind, ComponentTypeRef,
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}
73
74impl<'a> CompositionGraphBuilder<'a> {
75 fn new(root_path: &Path, config: &'a Config) -> Result<Self> {
76 let mut graph = CompositionGraph::new();
77 graph.add_component(Component::from_file(ROOT_COMPONENT_NAME, root_path)?)?;
78
79 let definitions = config
80 .definitions
81 .iter()
82 .map(|path| {
83 let name = path.file_stem().and_then(OsStr::to_str).with_context(|| {
84 format!(
85 "invalid definition component path `{path}`",
86 path = path.display()
87 )
88 })?;
89
90 let component = Component::from_file(name, config.dir.join(path))?;
91
92 Ok((graph.add_component(component)?, None))
93 })
94 .collect::<Result<_>>()?;
95
96 Ok(Self {
97 config,
98 graph,
99 instances: Default::default(),
100 definitions,
101 })
102 }
103
104 fn add_component(&mut self, name: &str) -> Result<Option<ComponentId>> {
109 if let Some((id, _)) = self.graph.get_component_by_name(name) {
110 return Ok(Some(id));
111 }
112
113 match self.find_component(name)? {
114 Some(component) => Ok(Some(self.graph.add_component(component)?)),
115 None => Ok(None),
116 }
117 }
118
119 fn find_component(&self, name: &str) -> Result<Option<Component<'a>>> {
121 if let Some(dep) = self.config.dependencies.get(name) {
123 log::debug!(
124 "component with name `{name}` has an explicit path of `{path}`",
125 path = dep.path.display()
126 );
127 return Ok(Some(Component::from_file(
128 name,
129 self.config.dir.join(&dep.path),
130 )?));
131 }
132
133 log::info!("searching for a component with name `{name}`");
135 for dir in std::iter::once(&self.config.dir).chain(self.config.search_paths.iter()) {
136 if let Some(component) = Self::parse_component(dir, name)? {
137 return Ok(Some(component));
138 }
139 }
140
141 Ok(None)
142 }
143
144 fn parse_component(dir: &Path, name: &str) -> Result<Option<Component<'a>>> {
148 let mut path = dir.join(name);
149
150 for ext in ["wasm", "wat"] {
151 path.set_extension(ext);
152 if !path.is_file() {
153 log::info!("component `{path}` does not exist", path = path.display());
154 continue;
155 }
156
157 return Ok(Some(Component::from_file(name, &path)?));
158 }
159
160 Ok(None)
161 }
162
163 fn instantiate(&mut self, name: &str, component_name: &str) -> Result<Option<(usize, bool)>> {
169 if let Some(index) = self.instances.get_index_of(name) {
170 return Ok(Some((index, true)));
171 }
172
173 match self.add_component(component_name)? {
174 Some(component_id) => {
175 let (index, prev) = self
176 .instances
177 .insert_full(name.to_string(), self.graph.instantiate(component_id)?);
178 assert!(prev.is_none());
179 Ok(Some((index, false)))
180 }
181 None => {
182 if self.config.disallow_imports {
183 bail!("a dependency named `{component_name}` could not be found and instance imports are not allowed");
184 }
185
186 log::warn!("instance `{name}` will be imported because a dependency named `{component_name}` could not be found");
187 Ok(None)
188 }
189 }
190 }
191
192 fn find_compatible_instance(
198 &self,
199 instance: usize,
200 dependent: usize,
201 arg_name: &str,
202 ty: ComponentInstanceTypeId,
203 types: TypesRef,
204 ) -> Result<Option<ExportIndex>> {
205 let (instance_name, instance_id) = self.instances.get_index(instance).unwrap();
206 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
207
208 let (dependent_name, dependent_instance_id) = self.instances.get_index(dependent).unwrap();
209
210 if component.is_instance_subtype_of(ty, types) {
212 log::debug!("instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`");
214 return Ok(None);
215 }
216
217 log::debug!("searching for compatible export from instance `{instance_name}` for argument `{arg_name}` of instance `{dependent_name}`");
218
219 let export = component.find_compatible_export(ty, types, component_id, &self.graph).ok_or_else(|| {
220 anyhow!(
221 "component `{path}` is not compatible with import `{arg_name}` of component `{dependent_path}`",
222 path = component.path().unwrap().display(),
223 dependent_path = self.graph.get_component_of_instance(*dependent_instance_id).unwrap().1.path().unwrap().display(),
224 )
225 })?;
226
227 log::debug!(
228 "export `{export_name}` (export index {export}) from instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`",
229 export = export.0,
230 export_name = component.exports.get_index(export.0).unwrap().0,
231 );
232
233 Ok(Some(export))
234 }
235
236 fn resolve_export_index(
240 &self,
241 export: &str,
242 instance: usize,
243 dependent_path: &Path,
244 arg_name: &str,
245 ty: ComponentInstanceTypeId,
246 types: TypesRef,
247 ) -> Result<ExportIndex> {
248 let (_, instance_id) = self.instances.get_index(instance).unwrap();
249 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
250
251 match component.export_by_name(export) {
252 Some((export_index, kind, index)) if kind == ComponentExternalKind::Instance => {
253 let export_ty = component.types.as_ref().component_instance_at(index);
254
255 if self.graph.try_connection(
256 component_id,
257 ComponentEntityType::Instance(export_ty),
258 component.types(),
259 ComponentEntityType::Instance(ty),
260 types,
261 ) {
262 Ok(export_index)
263 } else {
264 bail!(
265 "component `{path}` exports an instance named `{export}` \
266 but it is not compatible with import `{arg_name}` \
267 of component `{dependent_path}`",
268 path = component.path().unwrap().display(),
269 dependent_path = dependent_path.display(),
270 )
271 }
272 }
273 _ => bail!(
274 "component `{path}` does not export an instance named `{export}`",
275 path = component.path().unwrap().display(),
276 ),
277 }
278 }
279
280 fn resolve_import_ref(
282 &self,
283 r: InstanceImportRef,
284 ) -> (&Component, &str, ComponentInstanceTypeId) {
285 let component = self.graph.get_component(r.component).unwrap();
286 let (name, ty) = component.import(r.import).unwrap();
287 match ty {
288 ComponentTypeRef::Instance(index) => (
289 component,
290 name,
291 component
292 .types
293 .as_ref()
294 .component_any_type_at(index)
295 .unwrap_instance(),
296 ),
297 _ => unreachable!("should not have an instance import ref to a non-instance import"),
298 }
299 }
300
301 fn process_dependency(&mut self, dependency: Dependency) -> Result<Option<usize>> {
305 match dependency.kind {
306 DependencyKind::Instance { instance, export } => self.process_instance_dependency(
307 dependency.dependent,
308 dependency.import,
309 &instance,
310 export.as_deref(),
311 ),
312 DependencyKind::Definition { index, export } => {
313 let (component_id, instance_id) = &mut self.definitions[index];
315 let instance_id = *instance_id
316 .get_or_insert_with(|| self.graph.instantiate(*component_id).unwrap());
317
318 self.graph
319 .connect(
320 instance_id,
321 Some(export),
322 self.instances[dependency.dependent],
323 dependency.import.import,
324 )
325 .with_context(|| {
326 let name = self.instances.get_index(dependency.dependent).unwrap().0;
327 format!(
328 "failed to connect instance `{name}` to definition component `{path}`",
329 path = self
330 .graph
331 .get_component(*component_id)
332 .unwrap()
333 .path()
334 .unwrap()
335 .display(),
336 )
337 })?;
338
339 Ok(None)
341 }
342 }
343 }
344
345 fn process_instance_dependency(
346 &mut self,
347 dependent_index: usize,
348 import: InstanceImportRef,
349 instance: &str,
350 export: Option<&str>,
351 ) -> Result<Option<usize>> {
352 let name = self.config.dependency_name(instance);
353
354 log::info!(
355 "processing dependency `{name}` from instance `{dependent_name}` to instance `{instance}`",
356 dependent_name = self.instances.get_index(dependent_index).unwrap().0,
357 );
358
359 match self.instantiate(instance, name)? {
360 Some((instance, existing)) => {
361 let (dependent, import_name, import_type) = self.resolve_import_ref(import);
362
363 let export = match export {
364 Some(export) => Some(self.resolve_export_index(
365 export,
366 instance,
367 dependent.path().unwrap(),
368 import_name,
369 import_type,
370 dependent.types(),
371 )?),
372 None => self.find_compatible_instance(
373 instance,
374 dependent_index,
375 import_name,
376 import_type,
377 dependent.types(),
378 )?,
379 };
380
381 self.graph.connect(
383 self.instances[instance],
384 export,
385 self.instances[dependent_index],
386 import.import,
387 )?;
388
389 if existing {
390 return Ok(None);
391 }
392
393 Ok(Some(instance))
394 }
395 None => {
396 if let Some(export) = export {
397 bail!("an explicit export `{export}` cannot be specified for imported instance `{name}`");
398 }
399 Ok(None)
400 }
401 }
402 }
403
404 fn push_dependencies(&self, instance: usize, queue: &mut VecDeque<Dependency>) -> Result<()> {
406 let (instance_name, instance_id) = self.instances.get_index(instance).unwrap();
407 let instantiation = self.config.instantiations.get(instance_name);
408 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
409 let count = queue.len();
410
411 'outer: for (import, name, _) in component.imports() {
413 log::debug!("adding dependency for argument `{name}` (import index {import}) from instance `{instance_name}` to the queue", import = import.0);
414
415 for (index, (def_component_id, _)) in self.definitions.iter().enumerate() {
417 let def_component = self.graph.get_component(*def_component_id).unwrap();
418
419 match def_component.export_by_name(name) {
420 Some((export, ComponentExternalKind::Instance, _)) => {
421 log::debug!(
422 "found matching instance export `{name}` in definition component `{path}`",
423 path = def_component.path().unwrap().display()
424 );
425
426 queue.push_back(Dependency {
427 dependent: instance,
428 import: InstanceImportRef {
429 component: component_id,
430 import,
431 },
432 kind: DependencyKind::Definition { index, export },
433 });
434
435 continue 'outer;
436 }
437 _ => continue,
438 }
439 }
440
441 let arg = instantiation.and_then(|c| c.arguments.get(name));
442 queue.push_back(Dependency {
443 dependent: instance,
444 import: InstanceImportRef {
445 component: component_id,
446 import,
447 },
448 kind: DependencyKind::Instance {
449 instance: arg
450 .map(|arg| arg.instance.clone())
451 .unwrap_or_else(|| name.to_string()),
452 export: arg.and_then(|arg| arg.export.clone()),
453 },
454 });
455 }
456
457 if let Some(instantiation) = instantiation {
459 for arg in instantiation.arguments.keys() {
460 if !component.imports.contains_key(arg) {
461 bail!(
462 "component `{path}` has no import named `{arg}`",
463 path = component.path().unwrap().display()
464 );
465 }
466 }
467 }
468
469 if count == queue.len() && instance == 0 {
471 bail!(
472 "component `{path}` does not import any instances",
473 path = component.path().unwrap().display()
474 );
475 }
476
477 Ok(())
478 }
479
480 fn build(mut self) -> Result<(InstanceId, CompositionGraph<'a>)> {
482 let mut queue: VecDeque<Dependency> = VecDeque::new();
483
484 let (root_instance, existing) = self
486 .instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME)?
487 .unwrap();
488
489 assert!(!existing);
490
491 self.push_dependencies(0, &mut queue)?;
492
493 while let Some(dependency) = queue.pop_front() {
495 if let Some(instance) = self.process_dependency(dependency)? {
496 self.push_dependencies(instance, &mut queue)?;
497 }
498 }
499
500 Ok((self.instances[root_instance], self.graph))
501 }
502}
503
504pub struct ComponentComposer<'a> {
512 component: &'a Path,
513 config: &'a Config,
514}
515
516impl<'a> ComponentComposer<'a> {
517 pub fn new(component: &'a Path, config: &'a Config) -> Self {
523 Self { component, config }
524 }
525
526 pub fn compose(&self) -> Result<Vec<u8>> {
531 let (root_instance, graph) =
532 CompositionGraphBuilder::new(self.component, self.config)?.build()?;
533
534 if graph.instances.len() == 1 {
536 bail!(
537 "no dependencies of component `{path}` were found",
538 path = self.component.display()
539 );
540 }
541
542 CompositionGraphEncoder::new(
543 EncodeOptions {
544 define_components: !self.config.import_components,
545 export: Some(root_instance),
546 validate: false,
547 },
548 &graph,
549 )
550 .encode()
551 }
552}