midenc_compile/stages/
link.rs1use 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 pub world: builtin::WorldRef,
18 pub component: builtin::ComponentRef,
20 pub account_component_metadata_bytes: Option<Vec<u8>>,
22 pub masm: Vec<Arc<miden_assembly::ast::Module>>,
25 pub mast: Vec<Arc<miden_assembly::Library>>,
29 pub packages: BTreeMap<Symbol, Arc<miden_mast_package::Package>>,
31}
32
33impl LinkOutput {
34 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
52pub 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 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 let mut masm = Vec::default();
73 let mut mast = Vec::default();
74 let mut packages = BTreeMap::default();
75
76 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 masm.push(module);
104 } else {
105 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 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 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}