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, Validator, WasmFeatures,
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!("a dependency named `{component_name}` could not be found and instance imports are not allowed");
197 }
198
199 log::warn!("instance `{name}` will be imported because a dependency named `{component_name}` could not be found");
200 Ok(None)
201 }
202 }
203 }
204
205 fn find_compatible_instance(
211 &self,
212 instance: usize,
213 dependent: usize,
214 arg_name: &str,
215 ty: ComponentInstanceTypeId,
216 types: TypesRef,
217 ) -> Result<Option<ExportIndex>> {
218 let (instance_name, instance_id) = self.instances.get_index(instance).unwrap();
219 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
220
221 let (dependent_name, dependent_instance_id) = self.instances.get_index(dependent).unwrap();
222
223 if component.is_instance_subtype_of(ty, types) {
225 log::debug!("instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`");
227 return Ok(None);
228 }
229
230 log::debug!("searching for compatible export from instance `{instance_name}` for argument `{arg_name}` of instance `{dependent_name}`");
231
232 let export = component.find_compatible_export(ty, types, component_id, &self.graph).ok_or_else(|| {
233 anyhow!(
234 "component `{path}` is not compatible with import `{arg_name}` of component `{dependent_path}`",
235 path = component.path().unwrap().display(),
236 dependent_path = self.graph.get_component_of_instance(*dependent_instance_id).unwrap().1.path().unwrap().display(),
237 )
238 })?;
239
240 log::debug!(
241 "export `{export_name}` (export index {export}) from instance `{instance_name}` can be used for argument `{arg_name}` of instance `{dependent_name}`",
242 export = export.0,
243 export_name = component.exports.get_index(export.0).unwrap().0,
244 );
245
246 Ok(Some(export))
247 }
248
249 fn resolve_export_index(
253 &self,
254 export: &str,
255 instance: usize,
256 dependent_path: &Path,
257 arg_name: &str,
258 ty: ComponentInstanceTypeId,
259 types: TypesRef,
260 ) -> Result<ExportIndex> {
261 let (_, instance_id) = self.instances.get_index(instance).unwrap();
262 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
263
264 match component.export_by_name(export) {
265 Some((export_index, kind, index)) if kind == ComponentExternalKind::Instance => {
266 let export_ty = component.types.as_ref().component_instance_at(index);
267
268 if self.graph.try_connection(
269 component_id,
270 ComponentEntityType::Instance(export_ty),
271 component.types(),
272 ComponentEntityType::Instance(ty),
273 types,
274 ) {
275 Ok(export_index)
276 } else {
277 bail!(
278 "component `{path}` exports an instance named `{export}` \
279 but it is not compatible with import `{arg_name}` \
280 of component `{dependent_path}`",
281 path = component.path().unwrap().display(),
282 dependent_path = dependent_path.display(),
283 )
284 }
285 }
286 _ => bail!(
287 "component `{path}` does not export an instance named `{export}`",
288 path = component.path().unwrap().display(),
289 ),
290 }
291 }
292
293 fn resolve_import_ref(
295 &self,
296 r: InstanceImportRef,
297 ) -> (&Component, &str, ComponentInstanceTypeId) {
298 let component = self.graph.get_component(r.component).unwrap();
299 let (name, ty) = component.import(r.import).unwrap();
300 match ty {
301 ComponentTypeRef::Instance(index) => (
302 component,
303 name,
304 component
305 .types
306 .as_ref()
307 .component_any_type_at(index)
308 .unwrap_instance(),
309 ),
310 _ => unreachable!("should not have an instance import ref to a non-instance import"),
311 }
312 }
313
314 fn process_dependency(&mut self, dependency: Dependency) -> Result<Option<usize>> {
318 match dependency.kind {
319 DependencyKind::Instance { instance, export } => self.process_instance_dependency(
320 dependency.dependent,
321 dependency.import,
322 &instance,
323 export.as_deref(),
324 ),
325 DependencyKind::Definition { index, export } => {
326 let (component_id, instance_id) = &mut self.definitions[index];
328 let instance_id = *instance_id
329 .get_or_insert_with(|| self.graph.instantiate(*component_id).unwrap());
330
331 self.graph
332 .connect(
333 instance_id,
334 Some(export),
335 self.instances[dependency.dependent],
336 dependency.import.import,
337 )
338 .with_context(|| {
339 let name = self.instances.get_index(dependency.dependent).unwrap().0;
340 format!(
341 "failed to connect instance `{name}` to definition component `{path}`",
342 path = self
343 .graph
344 .get_component(*component_id)
345 .unwrap()
346 .path()
347 .unwrap()
348 .display(),
349 )
350 })?;
351
352 Ok(None)
354 }
355 }
356 }
357
358 fn process_instance_dependency(
359 &mut self,
360 dependent_index: usize,
361 import: InstanceImportRef,
362 instance: &str,
363 export: Option<&str>,
364 ) -> Result<Option<usize>> {
365 let name = self.config.dependency_name(instance);
366
367 log::info!(
368 "processing dependency `{name}` from instance `{dependent_name}` to instance `{instance}`",
369 dependent_name = self.instances.get_index(dependent_index).unwrap().0,
370 );
371
372 match self.instantiate(instance, name)? {
373 Some((instance, existing)) => {
374 let (dependent, import_name, import_type) = self.resolve_import_ref(import);
375
376 let export = match export {
377 Some(export) => Some(self.resolve_export_index(
378 export,
379 instance,
380 dependent.path().unwrap(),
381 import_name,
382 import_type,
383 dependent.types(),
384 )?),
385 None => self.find_compatible_instance(
386 instance,
387 dependent_index,
388 import_name,
389 import_type,
390 dependent.types(),
391 )?,
392 };
393
394 self.graph.connect(
396 self.instances[instance],
397 export,
398 self.instances[dependent_index],
399 import.import,
400 )?;
401
402 if existing {
403 return Ok(None);
404 }
405
406 Ok(Some(instance))
407 }
408 None => {
409 if let Some(export) = export {
410 bail!("an explicit export `{export}` cannot be specified for imported instance `{name}`");
411 }
412 Ok(None)
413 }
414 }
415 }
416
417 fn push_dependencies(&self, instance: usize, queue: &mut VecDeque<Dependency>) -> Result<()> {
419 let (instance_name, instance_id) = self.instances.get_index(instance).unwrap();
420 let instantiation = self.config.instantiations.get(instance_name);
421 let (component_id, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
422 let count = queue.len();
423
424 'outer: for (import, name, _) in component.imports() {
426 log::debug!("adding dependency for argument `{name}` (import index {import}) from instance `{instance_name}` to the queue", import = import.0);
427
428 for (index, (def_component_id, _)) in self.definitions.iter().enumerate() {
430 let def_component = self.graph.get_component(*def_component_id).unwrap();
431
432 match def_component.export_by_name(name) {
433 Some((export, ComponentExternalKind::Instance, _)) => {
434 log::debug!(
435 "found matching instance export `{name}` in definition component `{path}`",
436 path = def_component.path().unwrap().display()
437 );
438
439 queue.push_back(Dependency {
440 dependent: instance,
441 import: InstanceImportRef {
442 component: component_id,
443 import,
444 },
445 kind: DependencyKind::Definition { index, export },
446 });
447
448 continue 'outer;
449 }
450 _ => continue,
451 }
452 }
453
454 let arg = instantiation.and_then(|c| c.arguments.get(name));
455 queue.push_back(Dependency {
456 dependent: instance,
457 import: InstanceImportRef {
458 component: component_id,
459 import,
460 },
461 kind: DependencyKind::Instance {
462 instance: arg
463 .map(|arg| arg.instance.clone())
464 .unwrap_or_else(|| name.to_string()),
465 export: arg.and_then(|arg| arg.export.clone()),
466 },
467 });
468 }
469
470 if let Some(instantiation) = instantiation {
472 for arg in instantiation.arguments.keys() {
473 if !component.imports.contains_key(arg) {
474 bail!(
475 "component `{path}` has no import named `{arg}`",
476 path = component.path().unwrap().display()
477 );
478 }
479 }
480 }
481
482 if count == queue.len() && instance == 0 {
484 bail!(
485 "component `{path}` does not import any instances",
486 path = component.path().unwrap().display()
487 );
488 }
489
490 Ok(())
491 }
492
493 fn build(mut self) -> Result<(InstanceId, CompositionGraph<'a>)> {
495 let mut queue: VecDeque<Dependency> = VecDeque::new();
496
497 let (root_instance, existing) = self
499 .instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME)?
500 .unwrap();
501
502 assert!(!existing);
503
504 self.push_dependencies(0, &mut queue)?;
505
506 while let Some(dependency) = queue.pop_front() {
508 if let Some(instance) = self.process_dependency(dependency)? {
509 self.push_dependencies(instance, &mut queue)?;
510 }
511 }
512
513 Ok((self.instances[root_instance], self.graph))
514 }
515}
516
517pub struct ComponentComposer<'a> {
525 component: &'a Path,
526 config: &'a Config,
527}
528
529impl<'a> ComponentComposer<'a> {
530 pub fn new(component: &'a Path, config: &'a Config) -> Self {
536 Self { component, config }
537 }
538
539 pub fn compose(&self) -> Result<Vec<u8>> {
544 let (root_instance, graph) =
545 CompositionGraphBuilder::new(self.component, self.config)?.build()?;
546
547 if graph.instances.len() == 1 {
549 bail!(
550 "no dependencies of component `{path}` were found",
551 path = self.component.display()
552 );
553 }
554
555 CompositionGraphEncoder::new(
556 EncodeOptions {
557 define_components: !self.config.import_components,
558 export: Some(root_instance),
559 validate: false,
560 },
561 &graph,
562 )
563 .encode()
564 }
565}