Skip to main content

miden_assembly/
procedure.rs

1use alloc::sync::Arc;
2
3use miden_assembly_syntax::{
4    ast::{Path, Visibility, types::FunctionType},
5    debuginfo::{SourceManager, SourceSpan, Spanned},
6    diagnostics::Report,
7};
8use miden_core::Word;
9
10use super::{
11    GlobalItemIndex,
12    assembler::{MAX_PROC_LOCALS, error::AssemblerError},
13    mast_forest_builder::MastNodeRef,
14};
15
16// PROCEDURE CONTEXT
17// ================================================================================================
18
19/// Information about a procedure currently being compiled.
20pub struct ProcedureContext {
21    source_manager: Arc<dyn SourceManager>,
22    gid: GlobalItemIndex,
23    is_program_entrypoint: bool,
24    span: SourceSpan,
25    path: Arc<Path>,
26    signature: Option<Arc<FunctionType>>,
27    visibility: Visibility,
28    is_kernel: bool,
29    num_locals: u16,
30}
31
32// ------------------------------------------------------------------------------------------------
33/// Constructors
34impl ProcedureContext {
35    pub fn new(
36        gid: GlobalItemIndex,
37        is_program_entrypoint: bool,
38        path: Arc<Path>,
39        visibility: Visibility,
40        signature: Option<Arc<FunctionType>>,
41        is_kernel: bool,
42        source_manager: Arc<dyn SourceManager>,
43    ) -> Self {
44        Self {
45            source_manager,
46            gid,
47            is_program_entrypoint,
48            span: SourceSpan::UNKNOWN,
49            path,
50            visibility,
51            signature,
52            is_kernel,
53            num_locals: 0,
54        }
55    }
56
57    /// Sets the number of locals to allocate for the procedure.
58    ///
59    /// Returns an error if `num_locals` exceeds `MAX_PROC_LOCALS`, the largest count that
60    /// stays representable in a `u16` once rounded up to a word boundary during frame-pointer
61    /// codegen. The text parser enforces this on `@locals(..)`, but procedures built directly
62    /// via the AST bypass the parser. So the limit is enforced here for all callers.
63    ///
64    /// Call [`Self::with_span`] first so the error can point at the procedure definition.
65    pub fn with_num_locals(mut self, num_locals: u16) -> Result<Self, Report> {
66        if num_locals > MAX_PROC_LOCALS {
67            let source_file = self.source_manager.get(self.span.source_id()).ok();
68            return Err(Report::new(AssemblerError::TooManyProcedureLocals {
69                span: self.span,
70                source_file,
71                max_locals: MAX_PROC_LOCALS,
72                num_locals,
73            }));
74        }
75        self.num_locals = num_locals;
76        Ok(self)
77    }
78
79    pub fn with_span(mut self, span: SourceSpan) -> Self {
80        self.span = span;
81        self
82    }
83}
84
85// ------------------------------------------------------------------------------------------------
86/// Public accessors
87impl ProcedureContext {
88    pub fn id(&self) -> GlobalItemIndex {
89        self.gid
90    }
91
92    pub fn is_program_entrypoint(&self) -> bool {
93        self.is_program_entrypoint
94    }
95
96    pub fn path(&self) -> &Arc<Path> {
97        &self.path
98    }
99
100    pub fn signature(&self) -> Option<Arc<FunctionType>> {
101        self.signature.clone()
102    }
103
104    pub fn set_signature(&mut self, signature: Option<Arc<FunctionType>>) {
105        self.signature = signature;
106    }
107
108    pub fn num_locals(&self) -> u16 {
109        self.num_locals
110    }
111
112    pub fn module(&self) -> &Path {
113        self.path.parent().unwrap()
114    }
115
116    /// Returns true if the procedure is being assembled for a kernel.
117    pub fn is_kernel(&self) -> bool {
118        self.is_kernel
119    }
120
121    #[inline(always)]
122    pub fn source_manager(&self) -> &dyn SourceManager {
123        self.source_manager.as_ref()
124    }
125}
126
127// ------------------------------------------------------------------------------------------------
128/// State mutators
129impl ProcedureContext {
130    /// Transforms this procedure context into a [Procedure].
131    ///
132    /// The passed-in `mast_root` defines the MAST root of the procedure's body while `body_node`
133    /// specifies the assembly-time reference to the procedure's body node.
134    ///
135    /// <div class="warning">
136    /// `mast_root` and `body_node` must be consistent. That is, `body_node` must resolve to a MAST
137    /// node whose digest equals `mast_root`.
138    /// </div>
139    pub(crate) fn into_procedure(self, mast_root: Word, body_node_ref: MastNodeRef) -> Procedure {
140        let is_syscall = self.is_kernel && self.visibility.is_public();
141        Procedure::new(
142            self.path,
143            self.visibility,
144            self.signature,
145            is_syscall,
146            self.num_locals as u32,
147            mast_root,
148            body_node_ref,
149        )
150        .with_span(self.span)
151    }
152}
153
154impl Spanned for ProcedureContext {
155    fn span(&self) -> SourceSpan {
156        self.span
157    }
158}
159
160// PROCEDURE
161// ================================================================================================
162
163/// A compiled Miden Assembly procedure, consisting of MAST info and basic metadata.
164///
165/// Procedure metadata includes:
166///
167/// - Fully-qualified path of the procedure in Miden Assembly (if known).
168/// - Number of procedure locals to allocate.
169/// - The visibility of the procedure (e.g. public/private/syscall)
170/// - The set of MAST roots invoked by this procedure.
171/// - The original source span and file of the procedure (if available).
172#[derive(Clone, Debug)]
173pub struct Procedure {
174    span: SourceSpan,
175    path: Arc<Path>,
176    signature: Option<Arc<FunctionType>>,
177    visibility: Visibility,
178    is_syscall: bool,
179    num_locals: u32,
180    /// The MAST root of the procedure.
181    mast_root: Word,
182    /// The assembly-time node reference which resolves to the above MAST root.
183    body_node_ref: MastNodeRef,
184}
185
186// ------------------------------------------------------------------------------------------------
187/// Constructors
188impl Procedure {
189    fn new(
190        path: Arc<Path>,
191        visibility: Visibility,
192        signature: Option<Arc<FunctionType>>,
193        is_syscall: bool,
194        num_locals: u32,
195        mast_root: Word,
196        body_node_ref: MastNodeRef,
197    ) -> Self {
198        Self {
199            span: SourceSpan::default(),
200            path,
201            visibility,
202            signature,
203            is_syscall,
204            num_locals,
205            mast_root,
206            body_node_ref,
207        }
208    }
209
210    pub(crate) fn with_span(mut self, span: SourceSpan) -> Self {
211        self.span = span;
212        self
213    }
214}
215
216// ------------------------------------------------------------------------------------------------
217/// Public accessors
218impl Procedure {
219    /// Returns source span of this procedure.
220    pub fn span(&self) -> &SourceSpan {
221        &self.span
222    }
223
224    /// Returns a reference to the fully-qualified name of this procedure
225    pub fn path(&self) -> &Arc<Path> {
226        &self.path
227    }
228
229    /// Returns true if this procedure is a syscallable procedure
230    #[inline(always)]
231    pub const fn is_syscall(&self) -> bool {
232        self.is_syscall
233    }
234
235    /// Returns the visibility of this procedure as expressed in the original source code
236    pub fn visibility(&self) -> Visibility {
237        self.visibility
238    }
239
240    /// Returns a reference to the fully-qualified module path of this procedure
241    pub fn module(&self) -> &Path {
242        self.path.parent().unwrap()
243    }
244
245    /// Returns a reference to the type signature of this procedure
246    pub fn signature(&self) -> Option<Arc<FunctionType>> {
247        self.signature.clone()
248    }
249
250    /// Returns the number of memory locals reserved by the procedure.
251    pub fn num_locals(&self) -> u32 {
252        self.num_locals
253    }
254
255    /// Returns the root of this procedure's MAST.
256    pub fn mast_root(&self) -> Word {
257        self.mast_root
258    }
259
260    /// Returns the assembly-time node reference of this procedure.
261    pub(crate) fn body_node_ref(&self) -> MastNodeRef {
262        self.body_node_ref
263    }
264}
265
266impl Spanned for Procedure {
267    fn span(&self) -> SourceSpan {
268        self.span
269    }
270}