use alloc::{borrow::ToOwned, collections::BTreeMap, string::ToString, sync::Arc, vec::Vec};
use midenc_frontend_wasm::FrontendOutput;
use midenc_hir::{BuilderExt, OpBuilder, SourceSpan, interner::Symbol, print::AsmPrinter};
#[cfg(feature = "std")]
use midenc_session::Path;
use midenc_session::{
InputType, OutputMode, OutputType, ProjectType,
diagnostics::{Severity, Spanned},
};
use super::*;
#[derive(Clone)]
pub struct LinkOutput {
pub world: builtin::WorldRef,
pub component: builtin::ComponentRef,
pub account_component_metadata_bytes: Option<Vec<u8>>,
pub masm: Vec<Arc<miden_assembly::ast::Module>>,
pub mast: Vec<Arc<miden_assembly::Library>>,
pub packages: BTreeMap<Symbol, Arc<miden_mast_package::Package>>,
}
impl LinkOutput {
pub fn link_libraries_from(&mut self, session: &Session) -> Result<(), Report> {
assert!(self.mast.is_empty(), "link libraries already loaded!");
for link_lib in session.options.link_libraries.iter() {
log::debug!(
"registering link library '{}' ({}, from {:#?}) with linker",
link_lib.name,
link_lib.kind,
link_lib.path.as_ref()
);
let lib = link_lib.load(session).map(Arc::new)?;
self.mast.push(lib);
}
Ok(())
}
}
pub struct LinkStage;
impl Stage for LinkStage {
type Input = Vec<ParseOutput>;
type Output = LinkOutput;
fn run(&mut self, inputs: Self::Input, context: Rc<Context>) -> CompilerResult<Self::Output> {
let world = {
let mut builder = OpBuilder::new(context.clone());
let world_builder = builder.create::<builtin::World, ()>(SourceSpan::default());
world_builder()?
};
let mut masm = Vec::default();
let mut mast = Vec::default();
let mut packages = BTreeMap::default();
let mut component_wasm = None;
for input in inputs {
match input {
ParseOutput::Wasm(wasm) => {
if component_wasm.is_some() {
return Err(Report::msg(
"only a single wasm input can be provided at a time",
));
}
component_wasm = Some(wasm);
}
ParseOutput::Module(module) => {
if matches!(context.session().options.project_type, ProjectType::Library if module.is_executable())
{
return Err(context
.diagnostics()
.diagnostic(Severity::Error)
.with_message("invalid input")
.with_primary_label(
module.span(),
"cannot pass executable modules as input when compiling a library",
)
.into_report());
} else if module.is_executable() {
masm.push(module);
} else {
masm.push(module);
}
}
ParseOutput::Library(lib) => {
mast.push(lib);
}
ParseOutput::Package(package) => {
packages.insert(Symbol::intern(&package.name), package);
}
}
}
let component_wasm =
component_wasm.ok_or_else(|| Report::msg("expected at least one wasm input"))?;
let FrontendOutput {
component,
account_component_metadata_bytes,
} = match component_wasm {
#[cfg(feature = "std")]
InputType::Real(path) => parse_hir_from_wasm_file(&path, world, context.clone())?,
#[cfg(not(feature = "std"))]
InputType::Real(_path) => unimplemented!(),
InputType::Stdin { name, input } => {
let config = wasm::WasmTranslationConfig {
source_name: name.file_stem().unwrap().to_owned().into(),
trim_path_prefixes: context.session().options.trim_path_prefixes.clone(),
world: Some(world),
..Default::default()
};
parse_hir_from_wasm_bytes(&input, context.clone(), &config)?
}
};
let mut link_output = LinkOutput {
world,
component,
account_component_metadata_bytes,
masm,
mast: Vec::with_capacity(context.session().options.link_libraries.len()),
packages,
};
link_output.link_libraries_from(context.session())?;
let session = context.session();
if session.should_emit(OutputType::Hir) {
use midenc_hir::OpPrintingFlags;
let flags = OpPrintingFlags::from(&context.session().options);
let op = link_output.component.borrow();
let mut printer = AsmPrinter::new(context.clone(), &flags);
printer.print_operation(op);
let hir_str = printer.finish().to_string();
session.emit(OutputMode::Text, &hir_str).into_diagnostic()?;
}
if context.session().parse_only() {
log::debug!("stopping compiler early (parse-only=true)");
return Err(CompilerStopped.into());
} else if context.session().analyze_only() {
log::debug!("stopping compiler early (analyze-only=true)");
return Err(CompilerStopped.into());
} else if context.session().options.link_only {
log::debug!("stopping compiler early (link-only=true)");
return Err(CompilerStopped.into());
}
Ok(link_output)
}
}
#[cfg(feature = "std")]
fn parse_hir_from_wasm_file(
path: &Path,
world: builtin::WorldRef,
context: Rc<Context>,
) -> CompilerResult<FrontendOutput> {
use std::io::Read;
log::debug!("parsing hir from wasm at {}", path.display());
let mut file = std::fs::File::open(path)
.into_diagnostic()
.wrap_err("could not open input for reading")?;
let mut bytes = Vec::with_capacity(1024);
file.read_to_end(&mut bytes).into_diagnostic()?;
let file_name = path.file_stem().unwrap().to_str().unwrap().to_owned();
let config = wasm::WasmTranslationConfig {
source_name: file_name.into(),
trim_path_prefixes: context.session().options.trim_path_prefixes.clone(),
world: Some(world),
..Default::default()
};
parse_hir_from_wasm_bytes(&bytes, context, &config)
}
fn parse_hir_from_wasm_bytes(
bytes: &[u8],
context: Rc<Context>,
config: &wasm::WasmTranslationConfig,
) -> CompilerResult<FrontendOutput> {
let outpub = wasm::translate(bytes, config, context.clone())?;
log::debug!(
"parsed hir component from wasm bytes with first module name: {}",
outpub.component.borrow().id()
);
Ok(outpub)
}