use alloc::sync::Arc;
use miden_assembly_syntax::{
ast::{Attribute, AttributeSet, MetaExpr, Path, PathBuf, Visibility, types::FunctionType},
debuginfo::{SourceManager, SourceSpan, Spanned},
diagnostics::Report,
};
use miden_core::Word;
use super::{
GlobalItemIndex,
assembler::{MAX_PROC_LOCALS, error::AssemblerError},
mast_forest_builder::{MastNodeRef, MastNodeUse, SourceNodeRef},
};
pub struct ProcedureContext {
source_manager: Arc<dyn SourceManager>,
gid: GlobalItemIndex,
is_program_entrypoint: bool,
span: SourceSpan,
path: Arc<Path>,
signature: Option<Arc<FunctionType>>,
attributes: AttributeSet,
visibility: Visibility,
is_kernel: bool,
num_locals: u16,
}
impl ProcedureContext {
pub fn new(
gid: GlobalItemIndex,
is_program_entrypoint: bool,
path: Arc<Path>,
visibility: Visibility,
signature: Option<Arc<FunctionType>>,
is_kernel: bool,
source_manager: Arc<dyn SourceManager>,
) -> Self {
Self {
source_manager,
gid,
is_program_entrypoint,
span: SourceSpan::UNKNOWN,
path,
visibility,
signature,
attributes: Default::default(),
is_kernel,
num_locals: 0,
}
}
pub fn with_num_locals(mut self, num_locals: u16) -> Result<Self, Report> {
if num_locals > MAX_PROC_LOCALS {
let source_file = self.source_manager.get(self.span.source_id()).ok();
return Err(Report::new(AssemblerError::TooManyProcedureLocals {
span: self.span,
source_file,
max_locals: MAX_PROC_LOCALS,
num_locals,
}));
}
self.num_locals = num_locals;
Ok(self)
}
pub fn with_span(mut self, span: SourceSpan) -> Self {
self.span = span;
self
}
pub fn with_attributes(mut self, attributes: AttributeSet) -> Self {
self.attributes = attributes;
self
}
}
impl ProcedureContext {
pub fn id(&self) -> GlobalItemIndex {
self.gid
}
pub fn is_program_entrypoint(&self) -> bool {
self.is_program_entrypoint
}
pub fn path(&self) -> &Arc<Path> {
&self.path
}
pub fn signature(&self) -> Option<Arc<FunctionType>> {
self.signature.clone()
}
pub fn set_signature(&mut self, signature: Option<Arc<FunctionType>>) {
self.signature = signature;
}
pub fn num_locals(&self) -> u16 {
self.num_locals
}
pub fn module(&self) -> &Path {
self.path.parent().unwrap()
}
pub fn is_kernel(&self) -> bool {
self.is_kernel
}
#[inline(always)]
pub fn source_manager(&self) -> &dyn SourceManager {
self.source_manager.as_ref()
}
}
impl ProcedureContext {
pub(crate) fn into_procedure(self, mast_root: Word, body_node: MastNodeUse) -> Procedure {
let is_syscall = self.is_kernel && self.visibility.is_public();
Procedure::new(
self.path,
self.visibility,
self.signature,
self.attributes,
is_syscall,
self.num_locals as u32,
mast_root,
body_node,
)
.with_span(self.span)
}
}
impl Spanned for ProcedureContext {
fn span(&self) -> SourceSpan {
self.span
}
}
#[derive(Clone, Debug)]
pub struct Procedure {
span: SourceSpan,
path: Arc<Path>,
signature: Option<Arc<FunctionType>>,
attributes: AttributeSet,
visibility: Visibility,
is_syscall: bool,
num_locals: u32,
mast_root: Word,
body_node_ref: MastNodeRef,
body_source_ref: SourceNodeRef,
}
impl Procedure {
fn new(
path: Arc<Path>,
visibility: Visibility,
signature: Option<Arc<FunctionType>>,
attributes: AttributeSet,
is_syscall: bool,
num_locals: u32,
mast_root: Word,
body_node: MastNodeUse,
) -> Self {
Self {
span: SourceSpan::default(),
path,
visibility,
signature,
attributes,
is_syscall,
num_locals,
mast_root,
body_node_ref: body_node.node_ref(),
body_source_ref: body_node.source_ref(),
}
}
pub(crate) fn with_span(mut self, span: SourceSpan) -> Self {
self.span = span;
self
}
}
impl Procedure {
pub fn span(&self) -> &SourceSpan {
&self.span
}
pub fn path(&self) -> &Arc<Path> {
&self.path
}
#[inline(always)]
pub const fn is_syscall(&self) -> bool {
self.is_syscall
}
pub fn visibility(&self) -> Visibility {
self.visibility
}
pub fn module(&self) -> &Path {
self.path.parent().unwrap()
}
pub fn signature(&self) -> Option<Arc<FunctionType>> {
self.signature.clone()
}
pub fn attributes(&self) -> &AttributeSet {
&self.attributes
}
pub fn source_name_fully_qualified(
&self,
source_manager: &dyn SourceManager,
) -> Result<Option<PathBuf>, Report> {
let Some(attribute) = self.attributes.get("source_name") else {
return Ok(None);
};
if let Attribute::List(list) = attribute
&& let [MetaExpr::String(name)] = list.as_slice()
{
return Ok(Some(self.path.parent().unwrap().join(name)));
}
let span = attribute.span();
Err(Report::new(AssemblerError::InvalidSourceNameAttribute {
span,
source_file: source_manager.get(span.source_id()).ok(),
}))
}
pub fn num_locals(&self) -> u32 {
self.num_locals
}
pub fn mast_root(&self) -> Word {
self.mast_root
}
pub(crate) fn body_node_ref(&self) -> MastNodeRef {
self.body_node_ref
}
pub(crate) fn body_node_use(&self) -> MastNodeUse {
MastNodeUse::new(self.body_node_ref, self.body_source_ref)
}
pub(crate) fn body_source_ref(&self) -> SourceNodeRef {
self.body_source_ref
}
}
impl Spanned for Procedure {
fn span(&self) -> SourceSpan {
self.span
}
}
#[cfg(test)]
mod tests {
use alloc::{sync::Arc, vec};
use miden_assembly_syntax::{
PathBuf,
ast::{Attribute, Ident, MetaExpr},
debuginfo::{DefaultSourceManager, SourceLanguage, Uri},
};
use super::*;
fn procedure_with_attributes(attrs: vec::IntoIter<Attribute>) -> Procedure {
Procedure::new(
Arc::from(PathBuf::new("::test::module::foo").unwrap()),
Visibility::Private,
None,
AttributeSet::new(attrs),
false,
0,
Word::default(),
MastNodeUse::new(MastNodeRef::from(0), SourceNodeRef::from(0)),
)
}
#[test]
fn source_name_fully_qualified_is_none_without_attribute() {
let source_manager = DefaultSourceManager::default();
let procedure = procedure_with_attributes(vec![].into_iter());
assert_eq!(procedure.source_name_fully_qualified(&source_manager).unwrap(), None);
}
#[test]
fn source_name_fully_qualified_returns_quoted_string_joined_to_module_path() {
let source_manager = DefaultSourceManager::default();
let attribute = Attribute::from_iter(
Ident::new("source_name").unwrap(),
[MetaExpr::String(Ident::new("bar").unwrap())],
);
let procedure = procedure_with_attributes(vec![attribute].into_iter());
assert_eq!(
procedure.source_name_fully_qualified(&source_manager).unwrap(),
Some(PathBuf::new("::test::module::bar").unwrap()),
);
}
#[test]
fn malformed_source_name_attributes_are_rejected() {
let source_manager = DefaultSourceManager::default();
let file = source_manager.load(
SourceLanguage::Masm,
Uri::new("test.masm"),
"@source_name(unquoted)".into(),
);
let span = SourceSpan::new(file.id(), 0..19);
let malformed = vec![
Attribute::Marker(Ident::new("source_name").unwrap()),
Attribute::from_iter(
Ident::new("source_name").unwrap(),
[MetaExpr::Ident(Ident::new("unquoted").unwrap())],
),
Attribute::from_iter(
Ident::new("source_name").unwrap(),
[
MetaExpr::String(Ident::new("one").unwrap()),
MetaExpr::String(Ident::new("two").unwrap()),
],
),
Attribute::from_iter(
Ident::new("source_name").unwrap(),
[(Ident::new("value").unwrap(), MetaExpr::String(Ident::new("named").unwrap()))],
),
];
for attribute in malformed {
let procedure = procedure_with_attributes(vec![attribute.with_span(span)].into_iter());
let error = procedure.source_name_fully_qualified(&source_manager).unwrap_err();
match error.downcast_ref::<AssemblerError>() {
Some(AssemblerError::InvalidSourceNameAttribute { source_file, .. }) => {
assert_eq!(source_file.as_ref(), Some(&file));
},
unexpected => panic!("expected InvalidSourceNameAttribute, got {unexpected:?}"),
}
}
}
}