midenc_compile/stages/
link.rs

1use alloc::{borrow::ToOwned, collections::BTreeMap, string::ToString, sync::Arc, vec::Vec};
2
3use midenc_frontend_wasm::FrontendOutput;
4use midenc_hir::{BuilderExt, OpBuilder, SourceSpan, interner::Symbol};
5#[cfg(feature = "std")]
6use midenc_session::Path;
7use midenc_session::{
8    InputType, OutputMode, OutputType, ProjectType,
9    diagnostics::{Severity, Spanned},
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                        // In the meantime assume the caller is responsible for the ABI transformation
109                        // i.e. we're passing tx kernel function mocks in the integration tests.
110                        masm.push(module);
111                    }
112                }
113                ParseOutput::Library(lib) => {
114                    mast.push(lib);
115                }
116                ParseOutput::Package(package) => {
117                    packages.insert(Symbol::intern(&package.name), package);
118                }
119            }
120        }
121
122        // Parse and translate the component WebAssembly using the constructed World
123        let component_wasm =
124            component_wasm.ok_or_else(|| Report::msg("expected at least one wasm input"))?;
125        let FrontendOutput {
126            component,
127            account_component_metadata_bytes,
128        } = match component_wasm {
129            #[cfg(feature = "std")]
130            InputType::Real(path) => parse_hir_from_wasm_file(&path, world, context.clone())?,
131            #[cfg(not(feature = "std"))]
132            InputType::Real(_path) => unimplemented!(),
133            InputType::Stdin { name, input } => {
134                let config = wasm::WasmTranslationConfig {
135                    source_name: name.file_stem().unwrap().to_owned().into(),
136                    trim_path_prefixes: context.session().options.trim_path_prefixes.clone(),
137                    world: Some(world),
138                    ..Default::default()
139                };
140                parse_hir_from_wasm_bytes(&input, context.clone(), &config)?
141            }
142        };
143
144        let mut link_output = LinkOutput {
145            world,
146            component,
147            account_component_metadata_bytes,
148            masm,
149            mast: Vec::with_capacity(context.session().options.link_libraries.len()),
150            packages,
151        };
152
153        link_output.link_libraries_from(context.session())?;
154
155        // Emit HIR if requested
156        let session = context.session();
157        if session.should_emit(OutputType::Hir) {
158            use midenc_hir::{Op, OpPrinter, OpPrintingFlags};
159            let flags = OpPrintingFlags {
160                print_entry_block_headers: true,
161                print_intrinsic_attributes: false,
162                print_source_locations: session.options.print_hir_source_locations,
163            };
164            let op = link_output.component.borrow();
165            let hir_context = op.as_operation().context();
166            let doc = op.as_operation().print(&flags, hir_context);
167            let hir_str = doc.to_string();
168            session.emit(OutputMode::Text, &hir_str).into_diagnostic()?;
169        }
170
171        if context.session().parse_only() {
172            log::debug!("stopping compiler early (parse-only=true)");
173            return Err(CompilerStopped.into());
174        } else if context.session().analyze_only() {
175            log::debug!("stopping compiler early (analyze-only=true)");
176            return Err(CompilerStopped.into());
177        } else if context.session().options.link_only {
178            log::debug!("stopping compiler early (link-only=true)");
179            return Err(CompilerStopped.into());
180        }
181
182        Ok(link_output)
183    }
184}
185
186#[cfg(feature = "std")]
187fn parse_hir_from_wasm_file(
188    path: &Path,
189    world: builtin::WorldRef,
190    context: Rc<Context>,
191) -> CompilerResult<FrontendOutput> {
192    use std::io::Read;
193
194    log::debug!("parsing hir from wasm at {}", path.display());
195    let mut file = std::fs::File::open(path)
196        .into_diagnostic()
197        .wrap_err("could not open input for reading")?;
198    let mut bytes = Vec::with_capacity(1024);
199    file.read_to_end(&mut bytes).into_diagnostic()?;
200    let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned();
201
202    let config = wasm::WasmTranslationConfig {
203        source_name: file_name.into(),
204        trim_path_prefixes: context.session().options.trim_path_prefixes.clone(),
205        world: Some(world),
206        ..Default::default()
207    };
208    parse_hir_from_wasm_bytes(&bytes, context, &config)
209}
210
211fn parse_hir_from_wasm_bytes(
212    bytes: &[u8],
213    context: Rc<Context>,
214    config: &wasm::WasmTranslationConfig,
215) -> CompilerResult<FrontendOutput> {
216    let outpub = wasm::translate(bytes, config, context.clone())?;
217    log::debug!(
218        "parsed hir component from wasm bytes with first module name: {}",
219        outpub.component.borrow().id()
220    );
221
222    Ok(outpub)
223}