Skip to main content

miden_assembly_syntax/ast/procedure/
procedure.rs

1use alloc::{collections::BTreeSet, string::String};
2use core::fmt;
3
4use miden_debug_types::{SourceSpan, Span, Spanned};
5
6use super::ProcedureName;
7use crate::ast::{Attribute, AttributeSet, Block, DocString, FunctionType, Invoke, Visibility};
8
9// PROCEDURE
10// ================================================================================================
11
12/// Represents a concrete procedure definition in Miden Assembly syntax
13#[derive(Clone)]
14pub struct Procedure {
15    /// The source span of the full procedure body
16    span: SourceSpan,
17    /// The documentation attached to this procedure
18    docs: Option<DocString>,
19    /// The attributes attached to this procedure
20    attrs: AttributeSet,
21    /// The local name of this procedure
22    name: ProcedureName,
23    /// The visibility of this procedure (i.e. whether it is exported or not)
24    visibility: Visibility,
25    /// A flag which indicates that this procedure is only syscall-able
26    syscall: bool,
27    /// The type signature of this procedure, if known
28    ty: Option<FunctionType>,
29    /// The number of locals to allocate for this procedure
30    num_locals: u16,
31    /// The body of the procedure
32    body: Block,
33    /// The set of callees for any call-like instruction in the procedure body.
34    pub(crate) invoked: BTreeSet<Invoke>,
35}
36
37/// Construction
38impl Procedure {
39    /// Creates a new [Procedure] from the given source span, visibility, name, number of locals,
40    /// and code block.
41    pub fn new(
42        span: SourceSpan,
43        visibility: Visibility,
44        name: ProcedureName,
45        num_locals: u16,
46        body: Block,
47    ) -> Self {
48        Self {
49            span,
50            docs: None,
51            attrs: Default::default(),
52            name,
53            visibility,
54            syscall: false,
55            ty: None,
56            num_locals,
57            invoked: Default::default(),
58            body,
59        }
60    }
61
62    /// Same as [Self::new], but marks the procedure as being only visible to `syscall`
63    pub fn new_syscall(
64        span: SourceSpan,
65        name: ProcedureName,
66        num_locals: u16,
67        body: Block,
68    ) -> Self {
69        Self {
70            span,
71            docs: None,
72            attrs: Default::default(),
73            name,
74            visibility: Visibility::Public,
75            syscall: true,
76            ty: None,
77            num_locals,
78            invoked: Default::default(),
79            body,
80        }
81    }
82
83    /// Specify the type signature of this procedure
84    pub fn with_signature(mut self, ty: FunctionType) -> Self {
85        self.ty = Some(ty);
86        self
87    }
88
89    /// Adds documentation to this procedure definition
90    pub fn with_docs(mut self, docs: Option<Span<String>>) -> Self {
91        self.docs = docs.map(DocString::new);
92        self
93    }
94
95    /// Adds attributes to this procedure definition
96    pub fn with_attributes<I>(mut self, attrs: I) -> Self
97    where
98        I: IntoIterator<Item = Attribute>,
99    {
100        self.attrs.extend(attrs);
101        self
102    }
103
104    /// Override the visibility of this procedure.
105    pub fn set_visibility(&mut self, visibility: Visibility) {
106        self.visibility = visibility;
107    }
108
109    /// Override the syscall-only flag of this procedure.
110    pub fn set_syscall(&mut self, yes: bool) {
111        self.syscall = yes;
112    }
113
114    /// Override the type signature of this procedure.
115    pub fn set_signature(&mut self, signature: FunctionType) {
116        self.ty = Some(signature);
117    }
118
119    /// Override the number of locals allocated by this procedure.
120    ///
121    /// # Panics
122    ///
123    /// Panics if `num_locals` is non-zero and this procedure is the program entrypoint (the
124    /// `begin`..`end` block of an executable module). The entrypoint executes on a fresh frame and
125    /// cannot allocate locals; producing one with locals is an unrecoverable bug in the AST
126    /// producer.
127    pub fn set_num_locals(&mut self, num_locals: u16) {
128        assert!(
129            num_locals == 0 || !self.is_entrypoint(),
130            "program entrypoint cannot have locals"
131        );
132        self.num_locals = num_locals;
133    }
134}
135
136/// Metadata
137impl Procedure {
138    /// Returns the name of this procedure within its containing module.
139    pub fn name(&self) -> &ProcedureName {
140        &self.name
141    }
142
143    /// Returns the visibility of this procedure
144    pub fn visibility(&self) -> Visibility {
145        self.visibility
146    }
147
148    /// Returns whether or not this procedure requires `syscall` to invoke
149    pub fn is_syscall(&self) -> bool {
150        self.syscall
151    }
152
153    /// Get the type signature of this procedure, if known
154    pub fn signature(&self) -> Option<&FunctionType> {
155        self.ty.as_ref()
156    }
157
158    /// Get the type signature of this procedure mutably, if known
159    pub fn signature_mut(&mut self) -> Option<&mut FunctionType> {
160        self.ty.as_mut()
161    }
162
163    /// Returns the number of locals allocated by this procedure.
164    pub fn num_locals(&self) -> u16 {
165        self.num_locals
166    }
167
168    /// Returns true if this procedure corresponds to the `begin`..`end` block of an executable
169    /// module.
170    pub fn is_entrypoint(&self) -> bool {
171        self.name.is_main()
172    }
173
174    /// Returns the documentation for this procedure, if present.
175    pub fn docs(&self) -> Option<Span<&str>> {
176        self.docs.as_ref().map(|docstring| docstring.as_spanned_str())
177    }
178
179    /// Get the attributes attached to this procedure
180    #[inline]
181    pub fn attributes(&self) -> &AttributeSet {
182        &self.attrs
183    }
184
185    /// Get the attributes attached to this procedure, mutably
186    #[inline]
187    pub fn attributes_mut(&mut self) -> &mut AttributeSet {
188        &mut self.attrs
189    }
190
191    /// Returns true if this procedure has an attribute named `name`
192    #[inline]
193    pub fn has_attribute(&self, name: impl AsRef<str>) -> bool {
194        self.attrs.has(name)
195    }
196
197    /// Returns the attribute named `name`, if present
198    #[inline]
199    pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<&Attribute> {
200        self.attrs.get(name)
201    }
202
203    /// Returns a reference to the [Block] containing the body of this procedure.
204    pub fn body(&self) -> &Block {
205        &self.body
206    }
207
208    /// Returns a mutable reference to the [Block] containing the body of this procedure.
209    pub fn body_mut(&mut self) -> &mut Block {
210        &mut self.body
211    }
212
213    /// Returns an iterator over the operations of the top-level [Block] of this procedure.
214    pub fn iter(&self) -> core::slice::Iter<'_, crate::ast::Op> {
215        self.body.iter()
216    }
217
218    /// Returns an iterator over the set of invocation targets of this procedure, i.e. the callees
219    /// of any call instructions in the body of this procedure.
220    pub fn invoked<'a, 'b: 'a>(&'b self) -> impl Iterator<Item = &'a Invoke> + 'a {
221        if self.invoked.is_empty() {
222            InvokedIter::Empty
223        } else {
224            InvokedIter::NonEmpty(self.invoked.iter())
225        }
226    }
227
228    /// Extends the set of procedures known to be invoked by this procedure.
229    ///
230    /// This is for internal use only, and is called during semantic analysis once we've identified
231    /// the set of invoked procedures for a given definition.
232    pub fn extend_invoked<I>(&mut self, iter: I)
233    where
234        I: IntoIterator<Item = Invoke>,
235    {
236        self.invoked.extend(iter);
237    }
238}
239
240#[doc(hidden)]
241pub(crate) enum InvokedIter<'a, I: Iterator<Item = &'a Invoke> + 'a> {
242    Empty,
243    NonEmpty(I),
244}
245
246impl<'a, I> Iterator for InvokedIter<'a, I>
247where
248    I: Iterator<Item = &'a Invoke> + 'a,
249{
250    type Item = <I as Iterator>::Item;
251
252    fn next(&mut self) -> Option<Self::Item> {
253        match self {
254            Self::Empty => None,
255            Self::NonEmpty(iter) => {
256                let result = iter.next();
257                if result.is_none() {
258                    *self = Self::Empty;
259                }
260                result
261            },
262        }
263    }
264}
265
266impl Spanned for Procedure {
267    fn span(&self) -> SourceSpan {
268        self.span
269    }
270}
271
272impl crate::prettier::PrettyPrint for Procedure {
273    fn render(&self) -> crate::prettier::Document {
274        use crate::prettier::*;
275
276        let mut doc = self.docs.as_ref().map(PrettyPrint::render).unwrap_or(Document::Empty);
277
278        if !self.attrs.is_empty() {
279            doc += self
280                .attrs
281                .iter()
282                .map(PrettyPrint::render)
283                .reduce(|acc, attr| acc + nl() + attr)
284                .unwrap_or(Document::Empty);
285        }
286
287        if self.is_entrypoint() {
288            doc += const_text("begin");
289        } else {
290            if self.num_locals > 0 {
291                doc += text(format!("@locals(\"{}\")", self.num_locals)) + nl();
292            }
293            match self.signature() {
294                Some(sig) if sig.cc != crate::ast::types::CallConv::Fast => {
295                    doc += text(format!("@callconv(\"{}\")", sig.cc)) + nl();
296                },
297                _ => (),
298            }
299            if self.visibility.is_public() {
300                doc += display(self.visibility) + const_text(" ");
301            }
302            doc += const_text("proc") + const_text(" ") + display(self.name.as_ident());
303            if let Some(sig) = self.signature() {
304                doc += sig.render();
305            }
306        }
307
308        doc + self.body.render() + const_text("end") + nl()
309    }
310}
311
312impl fmt::Debug for Procedure {
313    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
314        f.debug_struct("Procedure")
315            .field("docs", &self.docs)
316            .field("attrs", &self.attrs)
317            .field("name", &self.name)
318            .field("visibility", &self.visibility)
319            .field("syscall", &self.syscall)
320            .field("num_locals", &self.num_locals)
321            .field("ty", &self.ty)
322            .field("body", &self.body)
323            .field("invoked", &self.invoked)
324            .finish()
325    }
326}
327
328impl Eq for Procedure {}
329
330impl PartialEq for Procedure {
331    fn eq(&self, other: &Self) -> bool {
332        self.name == other.name
333            && self.visibility == other.visibility
334            && self.syscall == other.syscall
335            && self.num_locals == other.num_locals
336            && self.ty == other.ty
337            && self.body == other.body
338            && self.attrs == other.attrs
339            && self.docs == other.docs
340    }
341}