midenc_compile/stages/
link.rs

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