use alloc::{collections::BTreeSet, sync::Arc};
use miden_assembly::{PathBuf as LibraryPath, ast::InvocationTarget};
use miden_assembly_syntax::parser::WordValue;
use midenc_hir::{
CallConv, FunctionIdent, Op, SourceSpan, Span, Symbol, TraceTarget, ValueRef,
diagnostics::IntoDiagnostic, dialects::builtin, pass::AnalysisManager,
};
use midenc_hir_analysis::analyses::LivenessAnalysis;
use midenc_session::{
TargetEnv,
diagnostics::{Report, Spanned, WrapErr},
};
use smallvec::SmallVec;
use crate::{
TraceEvent,
artifact::MasmComponent,
emitter::BlockEmitter,
linker::{LinkInfo, Linker},
masm,
};
pub trait ToMasmComponent {
fn to_masm_component(&self, analysis_manager: AnalysisManager)
-> Result<MasmComponent, Report>;
}
impl ToMasmComponent for builtin::Component {
fn to_masm_component(
&self,
analysis_manager: AnalysisManager,
) -> Result<MasmComponent, Report> {
let context = self.as_operation().context_rc();
let link_info = Linker::default().link(self).map_err(Report::msg)?;
let component_path = link_info.component().to_library_path();
let entrypoint = match context.session().options.entrypoint.as_deref() {
Some(entry) => {
let entry_id = entry.parse::<FunctionIdent>().map_err(|_| {
Report::msg(format!("invalid entrypoint identifier: '{entry}'"))
})?;
let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts(
Span::new(entry_id.function.span, entry_id.function.as_str().into()),
));
let is_wrapper = link_info.component().is_synthetic_wrapper();
let path = if is_wrapper {
let mut path = component_path.clone();
path.push(entry_id.module.as_str());
path
} else {
LibraryPath::new(entry_id.module.as_str()).into_diagnostic()?
};
let qualified = masm::QualifiedProcedureName::new(path.as_path(), name);
Some(masm::InvocationTarget::Path(Span::new(
entry_id.function.span,
qualified.into_inner(),
)))
}
None => None,
};
let requires_init = link_info.has_globals() || link_info.has_data_segments();
let init = if requires_init {
let name = masm::ProcedureName::new("init").unwrap();
let qualified = masm::QualifiedProcedureName::new(component_path.as_path(), name);
Some(masm::InvocationTarget::Path(Span::new(
SourceSpan::default(),
qualified.into_inner(),
)))
} else {
None
};
let id = link_info.component().clone();
let modules =
vec![Arc::new(masm::Module::new(masm::ModuleKind::Library, id.to_library_path()))];
let rodata = data_segments_to_rodata(&link_info)?;
let kernel = if matches!(context.session().options.target, TargetEnv::Rollup { .. }) {
Some(miden_protocol::transaction::TransactionKernel::kernel())
} else {
None
};
let heap_base = core::cmp::max(
link_info.reserved_memory_bytes(),
link_info.globals_layout().next_page_boundary() as usize,
);
let heap_base = u32::try_from(heap_base)
.expect("unable to allocate dynamic heap: global table too large");
let stack_pointer = link_info.globals_layout().stack_pointer_offset();
let mut masm_component = MasmComponent {
id,
init,
entrypoint,
kernel,
rodata,
heap_base,
stack_pointer,
modules,
};
let builder = MasmComponentBuilder {
analysis_manager,
component: &mut masm_component,
link_info: &link_info,
source_manager: context.session().source_manager.clone(),
init_body: Default::default(),
invoked_from_init: Default::default(),
};
builder.build(self)?;
Ok(masm_component)
}
}
fn data_segments_to_rodata(link_info: &LinkInfo) -> Result<Vec<crate::Rodata>, Report> {
use midenc_hir::constants::ConstantData;
use crate::data_segments::{ResolvedDataSegment, merge_data_segments};
let mut resolved = SmallVec::<[ResolvedDataSegment; 2]>::new();
for sref in link_info.segment_layout().iter() {
let s = sref.borrow();
resolved.push(ResolvedDataSegment {
offset: *s.offset(),
data: s.initializer().as_slice().to_vec(),
readonly: *s.readonly(),
});
}
Ok(match merge_data_segments(resolved).map_err(Report::msg)? {
None => alloc::vec::Vec::new(),
Some(merged) => {
let data = alloc::sync::Arc::new(ConstantData::from(merged.data));
let felts = crate::Rodata::bytes_to_elements(data.as_slice());
let digest = miden_core::crypto::hash::Rpo256::hash_elements(&felts);
alloc::vec![crate::Rodata {
component: link_info.component().clone(),
digest,
start: super::NativePtr::from_ptr(merged.offset),
data,
}]
}
})
}
struct MasmComponentBuilder<'a> {
component: &'a mut MasmComponent,
analysis_manager: AnalysisManager,
link_info: &'a LinkInfo,
source_manager: Arc<dyn midenc_session::SourceManager + Send + Sync>,
init_body: Vec<masm::Op>,
invoked_from_init: BTreeSet<masm::Invoke>,
}
impl MasmComponentBuilder<'_> {
pub fn build(mut self, component: &builtin::Component) -> Result<(), Report> {
use masm::{Instruction as Inst, InvocationTarget, Op};
if self.component.init.is_some() {
let span = component.span();
let heap_base = self.component.heap_base;
self.init_body.push(masm::Op::Inst(Span::new(
span,
Inst::Push(masm::Immediate::Value(Span::unknown(heap_base.into()))),
)));
let heap_init = {
let name = masm::ProcedureName::new("heap_init").unwrap();
let module = masm::LibraryPath::new("::intrinsics::mem").unwrap();
let qualified = masm::QualifiedProcedureName::new(module.as_path(), name);
InvocationTarget::Path(Span::new(span, qualified.into_inner()))
};
self.init_body.push(Op::Inst(Span::new(
span,
Inst::Trace(TraceEvent::FrameStart.as_u32().into()),
)));
self.init_body.push(Op::Inst(Span::new(span, Inst::Exec(heap_init))));
self.init_body
.push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into()))));
self.emit_data_segment_initialization();
}
let region = component.body();
let block = region.entry();
for op in block.body() {
if let Some(module) = op.downcast_ref::<builtin::Module>() {
self.define_module(module)?;
} else if let Some(interface) = op.downcast_ref::<builtin::Interface>() {
self.define_interface(interface)?;
} else if let Some(function) = op.downcast_ref::<builtin::Function>() {
self.define_function(function)?;
} else {
panic!(
"invalid component-level operation: '{}' is not supported in a component body",
op.name()
)
}
}
if self.component.init.is_some() {
let module =
Arc::get_mut(&mut self.component.modules[0]).expect("expected unique reference");
let init_name = masm::ProcedureName::new("init").unwrap();
let init_body = core::mem::take(&mut self.init_body);
let init = masm::Procedure::new(
Default::default(),
masm::Visibility::Private,
init_name,
0,
masm::Block::new(component.span(), init_body),
);
module
.define_procedure(init, self.source_manager.clone())
.into_diagnostic()
.wrap_err("failed to define component `init` procedure")?;
} else {
assert!(
self.init_body.is_empty(),
"the need for an 'init' function was not expected, but code was generated for one"
);
}
Ok(())
}
fn define_interface(&mut self, interface: &builtin::Interface) -> Result<(), Report> {
let component_path = self.component.id.to_library_path();
let mut interface_path = component_path;
interface_path.push(interface.name().as_str());
let mut masm_module =
Box::new(masm::Module::new(masm::ModuleKind::Library, interface_path));
let builder = MasmModuleBuilder {
module: &mut masm_module,
analysis_manager: self
.analysis_manager
.nest(interface.as_operation().as_operation_ref()),
link_info: self.link_info,
source_manager: self.source_manager.clone(),
init_body: &mut self.init_body,
invoked_from_init: &mut self.invoked_from_init,
};
builder.build_from_interface(interface)?;
self.component.modules.push(Arc::from(masm_module));
Ok(())
}
fn define_module(&mut self, module: &builtin::Module) -> Result<(), Report> {
let component_path = self.component.id.to_library_path();
let mut module_path = component_path;
module_path.push(module.name().as_str());
let mut masm_module = Box::new(masm::Module::new(masm::ModuleKind::Library, module_path));
let builder = MasmModuleBuilder {
module: &mut masm_module,
analysis_manager: self.analysis_manager.nest(module.as_operation_ref()),
link_info: self.link_info,
source_manager: self.source_manager.clone(),
init_body: &mut self.init_body,
invoked_from_init: &mut self.invoked_from_init,
};
builder.build(module)?;
self.component.modules.push(Arc::from(masm_module));
Ok(())
}
fn define_function(&mut self, function: &builtin::Function) -> Result<(), Report> {
let builder = MasmFunctionBuilder::new(function)?;
let procedure = builder.build(
function,
self.analysis_manager.nest(function.as_operation_ref()),
self.link_info,
)?;
let module =
Arc::get_mut(&mut self.component.modules[0]).expect("expected unique reference");
let expected_path_len = if module.path().is_absolute() { 2 } else { 1 };
assert_eq!(
module.path().len(),
expected_path_len,
"expected top-level namespace module, but one has not been defined (in '{}' of '{}')",
module.path(),
function.path()
);
module
.define_procedure(procedure, self.source_manager.clone())
.into_diagnostic()
.wrap_err("failed to define MASM procedure")?;
Ok(())
}
fn emit_data_segment_initialization(&mut self) {
use masm::{Instruction as Inst, InvocationTarget, Op};
let span = SourceSpan::default();
let pipe_preimage_to_memory = {
let name = masm::ProcedureName::new("pipe_preimage_to_memory").unwrap();
let module = masm::LibraryPath::new("::miden::core::mem").unwrap();
let qualified = masm::QualifiedProcedureName::new(module.as_path(), name);
InvocationTarget::Path(Span::new(span, qualified.into_inner()))
};
for rodata in self.component.rodata.iter() {
let word = rodata.digest.as_elements();
let word_value = [word[0], word[1], word[2], word[3]];
self.init_body.push(Op::Inst(Span::new(
span,
Inst::Push(masm::Immediate::Value(Span::unknown(WordValue(word_value).into()))),
)));
self.init_body
.push(Op::Inst(Span::new(span, Inst::SysEvent(masm::SystemEventNode::PushMapVal))));
assert!(rodata.start.is_word_aligned(), "rodata segments must be word-aligned");
self.init_body.push(Op::Inst(Span::new(
span,
Inst::Push(masm::Immediate::Value(Span::unknown(rodata.start.addr.into()))),
)));
self.init_body.push(Op::Inst(Span::new(
span,
Inst::Push(masm::Immediate::Value(Span::unknown(
(rodata.size_in_words() as u32).into(),
))),
)));
self.init_body.push(Op::Inst(Span::new(
span,
Inst::Trace(TraceEvent::FrameStart.as_u32().into()),
)));
self.init_body
.push(Op::Inst(Span::new(span, Inst::Exec(pipe_preimage_to_memory.clone()))));
self.init_body
.push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into()))));
self.init_body.push(Op::Inst(Span::new(span, Inst::Drop)));
}
}
}
struct MasmModuleBuilder<'a> {
module: &'a mut masm::Module,
analysis_manager: AnalysisManager,
link_info: &'a LinkInfo,
source_manager: Arc<dyn midenc_session::SourceManager + Send + Sync>,
init_body: &'a mut Vec<masm::Op>,
invoked_from_init: &'a mut BTreeSet<masm::Invoke>,
}
impl MasmModuleBuilder<'_> {
pub fn build(mut self, module: &builtin::Module) -> Result<(), Report> {
let region = module.body();
let block = region.entry();
for op in block.body() {
if let Some(function) = op.downcast_ref::<builtin::Function>() {
self.define_function(function)?;
} else if let Some(gv) = op.downcast_ref::<builtin::GlobalVariable>() {
self.emit_global_variable_initializer(gv)?;
} else if op.is::<builtin::Segment>() {
continue;
} else {
panic!(
"invalid module-level operation: '{}' is not legal in a MASM module body",
op.name()
)
}
}
Ok(())
}
pub fn build_from_interface(mut self, interface: &builtin::Interface) -> Result<(), Report> {
let region = interface.body();
let block = region.entry();
for op in block.body() {
if let Some(function) = op.downcast_ref::<builtin::Function>() {
self.define_function(function)?;
} else {
panic!(
"invalid interface-level operation: '{}' is not legal in a MASM module body",
op.name()
)
}
}
Ok(())
}
fn define_function(&mut self, function: &builtin::Function) -> Result<(), Report> {
let builder = MasmFunctionBuilder::new(function)?;
let procedure = builder.build(
function,
self.analysis_manager.nest(function.as_operation_ref()),
self.link_info,
)?;
self.module
.define_procedure(procedure, self.source_manager.clone())
.map_err(|e| Report::msg(e.to_string()))?;
Ok(())
}
fn emit_global_variable_initializer(
&mut self,
gv: &builtin::GlobalVariable,
) -> Result<(), Report> {
if gv.is_declaration() {
return Ok(());
}
let analysis_manager = self.analysis_manager.nest(gv.as_operation_ref());
let liveness = analysis_manager.get_analysis::<LivenessAnalysis>()?;
let initializer_region = gv.region(0);
let initializer_block = initializer_region.entry();
let mut block_emitter = BlockEmitter {
liveness: &liveness,
link_info: self.link_info,
invoked: self.invoked_from_init,
target: Default::default(),
stack: Default::default(),
trace_target: TraceTarget::category("codegen")
.with_relevant_symbol(gv.name().as_symbol()),
};
block_emitter.emit_inline(&initializer_block);
assert_eq!(block_emitter.stack.len(), 1, "expected only global variable value on stack");
let return_ty = block_emitter.stack.peek().unwrap().ty();
assert_eq!(
&return_ty,
gv.ty(),
"expected initializer to return value of same type as declaration"
);
let computed_addr = self
.link_info
.globals_layout()
.get_computed_addr(gv.as_global_var_ref())
.expect("undefined global variable");
block_emitter.emitter().store_imm(computed_addr, gv.span());
let mut body = core::mem::take(&mut block_emitter.target);
self.init_body.append(&mut body);
Ok(())
}
}
struct MasmFunctionBuilder {
span: midenc_hir::SourceSpan,
name: masm::ProcedureName,
signature: masm::FunctionType,
visibility: masm::Visibility,
num_locals: u16,
}
impl MasmFunctionBuilder {
pub fn new(function: &builtin::Function) -> Result<Self, Report> {
use midenc_hir::{Symbol, Visibility};
let name = function.name();
let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts(Span::new(
name.span,
name.as_ref().into(),
)));
let visibility = match function.visibility() {
Visibility::Public => masm::Visibility::Public,
Visibility::Internal => masm::Visibility::Public,
Visibility::Private => masm::Visibility::Private,
};
let locals_required = function.locals().iter().map(|ty| ty.size_in_felts()).sum::<usize>();
let num_locals = u16::try_from(locals_required).map_err(|_| {
let context = function.as_operation().context();
context
.diagnostics()
.diagnostic(miden_assembly::diagnostics::Severity::Error)
.with_message("cannot emit masm for function")
.with_primary_label(
function.span(),
"local storage exceeds procedure limit: no more than u16::MAX elements are \
supported",
)
.into_report()
})?;
let sig = function.signature();
let args = sig.params.iter().map(|param| masm::TypeExpr::from(param.ty.clone())).collect();
let results = sig
.results
.iter()
.map(|result| masm::TypeExpr::from(result.ty.clone()))
.collect();
let signature = masm::FunctionType::new(sig.cc, args, results);
Ok(Self {
span: function.span(),
name,
signature,
visibility,
num_locals,
})
}
pub fn build(
self,
function: &builtin::Function,
analysis_manager: AnalysisManager,
link_info: &LinkInfo,
) -> Result<masm::Procedure, Report> {
use alloc::collections::BTreeSet;
use midenc_hir_analysis::analyses::LivenessAnalysis;
let demangled_symbol_name = midenc_hir::demangle::demangle(function.name());
let trace_target = TraceTarget::category("codegen")
.with_relevant_symbol(midenc_hir::SymbolName::intern(demangled_symbol_name));
log::trace!(target: &trace_target, "lowering {}", function.as_operation());
let liveness = analysis_manager.get_analysis::<LivenessAnalysis>()?;
let mut invoked = BTreeSet::default();
let entry = function.entry_block();
let mut stack = crate::OperandStack::default();
{
let entry_block = entry.borrow();
for arg in entry_block.arguments().iter().rev().copied() {
stack.push(arg as ValueRef);
}
}
let mut emitter = BlockEmitter {
liveness: &liveness,
link_info,
invoked: &mut invoked,
target: Default::default(),
stack,
trace_target,
};
if function.signature().cc == CallConv::CanonLift
&& (link_info.has_globals() || link_info.has_data_segments())
{
let component_path = link_info.component().to_library_path();
let init = {
let name = masm::ProcedureName::new("init").unwrap();
let qualified = masm::QualifiedProcedureName::new(component_path.as_path(), name);
InvocationTarget::Path(Span::new(SourceSpan::default(), qualified.into_inner()))
};
let span = SourceSpan::default();
emitter
.target
.push(masm::Op::Inst(Span::new(span, masm::Instruction::Exec(init))));
}
let mut body = emitter.emit(&entry.borrow());
if function.signature().cc == CallConv::CanonLift {
let truncate_stack = {
let name = masm::ProcedureName::new("truncate_stack").unwrap();
let module = masm::LibraryPath::new("::miden::core::sys").unwrap();
let qualified = masm::QualifiedProcedureName::new(module.as_path(), name);
InvocationTarget::Path(Span::new(SourceSpan::default(), qualified.into_inner()))
};
let span = SourceSpan::default();
body.push(masm::Op::Inst(Span::new(span, masm::Instruction::Exec(truncate_stack))));
}
let Self {
span,
name,
signature,
visibility,
num_locals,
} = self;
let mut procedure = masm::Procedure::new(span, visibility, name, num_locals, body);
procedure.set_signature(signature);
procedure.extend_invoked(invoked);
Ok(procedure)
}
}