midenc_compile/stages/
link.rs

1use alloc::{borrow::ToOwned, collections::BTreeMap, sync::Arc, vec::Vec};
2
3use midenc_hir::{interner::Symbol, BuilderExt, OpBuilder, SourceSpan};
4#[cfg(feature = "std")]
5use midenc_session::Path;
6use midenc_session::{
7    diagnostics::{Severity, Spanned},
8    InputType, ProjectType,
9};
10
11use super::*;
12
13#[derive(Clone)]
14pub struct LinkOutput {
15    /// The IR world in which all components/modules are represented as declarations or definitions.
16    pub world: builtin::WorldRef,
17    /// The IR component which is the primary input being compiled
18    pub component: builtin::ComponentRef,
19    /// The set of Miden Assembly sources to be provided to the assembler to satisfy link-time
20    /// dependencies
21    pub masm: Vec<Arc<miden_assembly::ast::Module>>,
22    /// The set of MAST libraries to be provided to the assembler to satisfy link-time dependencies
23    ///
24    /// These are either given via `-l`, or as inputs
25    pub mast: Vec<Arc<miden_assembly::Library>>,
26    /// The set of link libraries provided to the compiler as MAST packages
27    pub packages: BTreeMap<Symbol, Arc<miden_mast_package::Package>>,
28}
29
30impl LinkOutput {
31    // Load link libraries from the given [midenc_session::Session]
32    pub fn link_libraries_from(&mut self, session: &Session) -> Result<(), Report> {
33        assert!(self.mast.is_empty(), "link libraries already loaded!");
34        for link_lib in session.options.link_libraries.iter() {
35            log::debug!(
36                "registering link library '{}' ({}, from {:#?}) with linker",
37                link_lib.name,
38                link_lib.kind,
39                link_lib.path.as_ref()
40            );
41            let lib = link_lib.load(session).map(Arc::new)?;
42            self.mast.push(lib);
43        }
44
45        Ok(())
46    }
47}
48
49/// This stage gathers together the parsed inputs, constructs a [World] representing all of the
50/// parsed non-Wasm inputs and specified link libraries, and then parses the Wasm input(s) in the
51/// context of that world. If successful, there are no undefined symbols present in the program.
52///
53/// This stage also ensures that any builtins/intrinsics are represented in the IR.
54pub struct LinkStage;
55
56impl Stage for LinkStage {
57    type Input = Vec<ParseOutput>;
58    type Output = LinkOutput;
59
60    fn run(&mut self, inputs: Self::Input, context: Rc<Context>) -> CompilerResult<Self::Output> {
61        // Construct an empty world
62        let world = {
63            let mut builder = OpBuilder::new(context.clone());
64            let world_builder = builder.create::<builtin::World, ()>(SourceSpan::default());
65            world_builder()?
66        };
67
68        // Construct the empty linker outputs
69        let mut masm = Vec::default();
70        let mut mast = Vec::default();
71        let mut packages = BTreeMap::default();
72
73        // Visit each input, validate it, and update the linker outputs accordingly
74        let mut component_wasm = None;
75        for input in inputs {
76            match input {
77                ParseOutput::Wasm(wasm) => {
78                    if component_wasm.is_some() {
79                        return Err(Report::msg(
80                            "only a single wasm input can be provided at a time",
81                        ));
82                    }
83                    component_wasm = Some(wasm);
84                }
85                ParseOutput::Module(module) => {
86                    if matches!(context.session().options.project_type, ProjectType::Library if module.is_executable())
87                    {
88                        return Err(context
89                            .diagnostics()
90                            .diagnostic(Severity::Error)
91                            .with_message("invalid input")
92                            .with_primary_label(
93                                module.span(),
94                                "cannot pass executable modules as input when compiling a library",
95                            )
96                            .into_report());
97                    } else if module.is_executable() {
98                        // If a module is executable, we do not need to represent it in the world
99                        // as it is by definition unreachable from any symbols outside of itself.
100                        masm.push(module);
101                    } else {
102                        // We represent library modules in the world so that the symbols are
103                        // resolvable.
104                        todo!("need type information for masm procedures")
105                    }
106                }
107                ParseOutput::Library(lib) => {
108                    mast.push(lib);
109                }
110                ParseOutput::Package(package) => {
111                    packages.insert(Symbol::intern(&package.name), package);
112                }
113            }
114        }
115
116        // Parse and translate the component WebAssembly using the constructed World
117        let component_wasm =
118            component_wasm.ok_or_else(|| Report::msg("expected at least one wasm input"))?;
119        let component = match component_wasm {
120            #[cfg(feature = "std")]
121            InputType::Real(path) => parse_hir_from_wasm_file(&path, world, context.clone())?,
122            #[cfg(not(feature = "std"))]
123            InputType::Real(_path) => unimplemented!(),
124            InputType::Stdin { name, input } => {
125                let config = wasm::WasmTranslationConfig {
126                    source_name: name.file_stem().unwrap().to_owned().into(),
127                    world: Some(world),
128                    ..Default::default()
129                };
130                parse_hir_from_wasm_bytes(&input, context.clone(), &config)?
131            }
132        };
133
134        let mut link_output = LinkOutput {
135            world,
136            component,
137            masm,
138            mast: Vec::with_capacity(context.session().options.link_libraries.len()),
139            packages,
140        };
141
142        link_output.link_libraries_from(context.session())?;
143
144        if context.session().parse_only() {
145            log::debug!("stopping compiler early (parse-only=true)");
146            return Err(CompilerStopped.into());
147        } else if context.session().analyze_only() {
148            log::debug!("stopping compiler early (analyze-only=true)");
149            return Err(CompilerStopped.into());
150        } else if context.session().options.link_only {
151            log::debug!("stopping compiler early (link-only=true)");
152            return Err(CompilerStopped.into());
153        }
154
155        Ok(link_output)
156    }
157}
158
159#[cfg(feature = "std")]
160fn parse_hir_from_wasm_file(
161    path: &Path,
162    world: builtin::WorldRef,
163    context: Rc<Context>,
164) -> CompilerResult<builtin::ComponentRef> {
165    use std::io::Read;
166
167    log::debug!("parsing hir from wasm at {}", path.display());
168    let mut file = std::fs::File::open(path)
169        .into_diagnostic()
170        .wrap_err("could not open input for reading")?;
171    let mut bytes = Vec::with_capacity(1024);
172    file.read_to_end(&mut bytes).into_diagnostic()?;
173    let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned();
174    let config = wasm::WasmTranslationConfig {
175        source_name: file_name.into(),
176        world: Some(world),
177        ..Default::default()
178    };
179    parse_hir_from_wasm_bytes(&bytes, context, &config)
180}
181
182fn parse_hir_from_wasm_bytes(
183    bytes: &[u8],
184    context: Rc<Context>,
185    config: &wasm::WasmTranslationConfig,
186) -> CompilerResult<builtin::ComponentRef> {
187    let component = wasm::translate(bytes, config, context.clone())?;
188    log::debug!(
189        "parsed hir component from wasm bytes with first module name: {}",
190        component.borrow().id()
191    );
192
193    Ok(component)
194}