midenc_hir/
function.rs

1use cranelift_entity::entity_impl;
2use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink};
3
4use self::formatter::PrettyPrint;
5use crate::{
6    diagnostics::{miette, Diagnostic, Spanned},
7    *,
8};
9
10/// This error is raised when two function declarations conflict with the same symbol name
11#[derive(Debug, thiserror::Error, Diagnostic)]
12#[error("item named '{}' has already been declared, or cannot be merged", .0)]
13#[diagnostic()]
14pub struct SymbolConflictError(pub FunctionIdent);
15
16/// A handle that refers to an [ExternalFunction]
17#[derive(Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct FuncRef(u32);
19entity_impl!(FuncRef, "fn");
20
21/// Represents the calling convention of a function.
22///
23/// Calling conventions are part of a program's ABI (Application Binary Interface), and
24/// they define things such how arguments are passed to a function, how results are returned,
25/// etc. In essence, the contract between caller and callee is described by the calling convention
26/// of a function.
27///
28/// Importantly, it is perfectly normal to mix calling conventions. For example, the public
29/// API for a C library will use whatever calling convention is used by C on the target
30/// platform (for Miden, that would be `SystemV`). However, internally that library may use
31/// the `Fast` calling convention to allow the compiler to optimize more effectively calls
32/// from the public API to private functions. In short, choose a calling convention that is
33/// well-suited for a given function, to the extent that other constraints don't impose a choice
34/// on you.
35#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
36#[cfg_attr(
37    feature = "serde",
38    derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
39)]
40#[repr(u8)]
41pub enum CallConv {
42    /// This calling convention is what I like to call "chef's choice" - the
43    /// compiler chooses it's own convention that optimizes for call performance.
44    ///
45    /// As a result of this, it is not permitted to use this convention in externally
46    /// linked functions, as the convention is unstable, and the compiler can't ensure
47    /// that the caller in another translation unit will use the correct convention.
48    Fast,
49    /// The standard calling convention used for C on most platforms
50    #[default]
51    SystemV,
52    /// A function which is using the WebAssembly Component Model "Canonical ABI".
53    Wasm,
54    /// A function with this calling convention must be called using
55    /// the `syscall` instruction. Attempts to call it with any other
56    /// call instruction will cause a validation error. The one exception
57    /// to this rule is when calling another function with the `Kernel`
58    /// convention that is defined in the same module, which can use the
59    /// standard `call` instruction.
60    ///
61    /// Kernel functions may only be defined in a kernel [Module].
62    ///
63    /// In all other respects, this calling convention is the same as `SystemV`
64    Kernel,
65}
66impl fmt::Display for CallConv {
67    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68        match self {
69            Self::Fast => f.write_str("fast"),
70            Self::SystemV => f.write_str("C"),
71            Self::Wasm => f.write_str("wasm"),
72            Self::Kernel => f.write_str("kernel"),
73        }
74    }
75}
76
77/// Represents whether an argument or return value has a special purpose in
78/// the calling convention of a function.
79#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
80#[cfg_attr(
81    feature = "serde",
82    derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
83)]
84#[repr(u8)]
85pub enum ArgumentPurpose {
86    /// No special purpose, the argument is passed/returned by value
87    #[default]
88    Default,
89    /// Used for platforms where the calling convention expects return values of
90    /// a certain size to be written to a pointer passed in by the caller.
91    StructReturn,
92}
93impl fmt::Display for ArgumentPurpose {
94    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
95        match self {
96            Self::Default => f.write_str("default"),
97            Self::StructReturn => f.write_str("sret"),
98        }
99    }
100}
101
102/// Represents how to extend a small integer value to native machine integer width.
103///
104/// For Miden, native integrals are unsigned 64-bit field elements, but it is typically
105/// going to be the case that we are targeting the subset of Miden Assembly where integrals
106/// are unsigned 32-bit integers with a standard twos-complement binary representation.
107///
108/// It is for the latter scenario that argument extension is really relevant.
109#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
110#[cfg_attr(
111    feature = "serde",
112    derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
113)]
114#[repr(u8)]
115pub enum ArgumentExtension {
116    /// Do not perform any extension, high bits have undefined contents
117    #[default]
118    None,
119    /// Zero-extend the value
120    Zext,
121    /// Sign-extend the value
122    Sext,
123}
124impl fmt::Display for ArgumentExtension {
125    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
126        match self {
127            Self::None => f.write_str("none"),
128            Self::Zext => f.write_str("zext"),
129            Self::Sext => f.write_str("sext"),
130        }
131    }
132}
133
134/// Describes a function parameter or result.
135#[derive(Debug, Clone, PartialEq, Eq)]
136#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
137pub struct AbiParam {
138    /// The type associated with this value
139    pub ty: Type,
140    /// The special purpose, if any, of this parameter or result
141    pub purpose: ArgumentPurpose,
142    /// The desired approach to extending the size of this value to
143    /// a larger bit width, if applicable.
144    pub extension: ArgumentExtension,
145}
146impl AbiParam {
147    pub fn new(ty: Type) -> Self {
148        Self {
149            ty,
150            purpose: ArgumentPurpose::default(),
151            extension: ArgumentExtension::default(),
152        }
153    }
154
155    pub fn sret(ty: Type) -> Self {
156        assert!(ty.is_pointer(), "sret parameters must be pointers");
157        Self {
158            ty,
159            purpose: ArgumentPurpose::StructReturn,
160            extension: ArgumentExtension::default(),
161        }
162    }
163}
164impl formatter::PrettyPrint for AbiParam {
165    fn render(&self) -> formatter::Document {
166        use crate::formatter::*;
167
168        let mut doc = const_text("(") + const_text("param") + const_text(" ");
169        if !matches!(self.purpose, ArgumentPurpose::Default) {
170            doc += const_text("(") + display(self.purpose) + const_text(")") + const_text(" ");
171        }
172        if !matches!(self.extension, ArgumentExtension::None) {
173            doc += const_text("(") + display(self.extension) + const_text(")") + const_text(" ");
174        }
175        doc + text(format!("{}", &self.ty)) + const_text(")")
176    }
177}
178
179/// A [Signature] represents the type, ABI, and linkage of a function.
180///
181/// A function signature provides us with all of the necessary detail to correctly
182/// validate and emit code for a function, whether from the perspective of a caller,
183/// or the callee.
184#[derive(Debug, Clone)]
185#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
186pub struct Signature {
187    /// The arguments expected by this function
188    pub params: Vec<AbiParam>,
189    /// The results returned by this function
190    pub results: Vec<AbiParam>,
191    /// The calling convention that applies to this function
192    pub cc: CallConv,
193    /// The linkage that should be used for this function
194    pub linkage: Linkage,
195}
196impl Signature {
197    /// Create a new signature with the given parameter and result types,
198    /// for a public function using the `SystemV` calling convention
199    pub fn new<P: IntoIterator<Item = AbiParam>, R: IntoIterator<Item = AbiParam>>(
200        params: P,
201        results: R,
202    ) -> Self {
203        Self {
204            params: params.into_iter().collect(),
205            results: results.into_iter().collect(),
206            cc: CallConv::SystemV,
207            linkage: Linkage::External,
208        }
209    }
210
211    /// Returns true if this function is externally visible
212    pub fn is_public(&self) -> bool {
213        matches!(self.linkage, Linkage::External)
214    }
215
216    /// Returns true if this function is only visible within it's containing module
217    pub fn is_private(&self) -> bool {
218        matches!(self.linkage, Linkage::Internal)
219    }
220
221    /// Returns true if this function is a kernel function
222    pub fn is_kernel(&self) -> bool {
223        matches!(self.cc, CallConv::Kernel)
224    }
225
226    /// Returns the number of arguments expected by this function
227    pub fn arity(&self) -> usize {
228        self.params().len()
229    }
230
231    /// Returns a slice containing the parameters for this function
232    pub fn params(&self) -> &[AbiParam] {
233        self.params.as_slice()
234    }
235
236    /// Returns the parameter at `index`, if present
237    #[inline]
238    pub fn param(&self, index: usize) -> Option<&AbiParam> {
239        self.params.get(index)
240    }
241
242    /// Returns a slice containing the results of this function
243    pub fn results(&self) -> &[AbiParam] {
244        match self.results.as_slice() {
245            [AbiParam { ty: Type::Unit, .. }] => &[],
246            [AbiParam {
247                ty: Type::Never, ..
248            }] => &[],
249            results => results,
250        }
251    }
252}
253impl Eq for Signature {}
254impl PartialEq for Signature {
255    fn eq(&self, other: &Self) -> bool {
256        self.linkage == other.linkage
257            && self.cc == other.cc
258            && self.params.len() == other.params.len()
259            && self.results.len() == other.results.len()
260    }
261}
262impl formatter::PrettyPrint for Signature {
263    fn render(&self) -> formatter::Document {
264        use crate::formatter::*;
265
266        let cc = if matches!(self.cc, CallConv::SystemV) {
267            None
268        } else {
269            Some(
270                const_text("(")
271                    + const_text("cc")
272                    + const_text(" ")
273                    + display(self.cc)
274                    + const_text(")"),
275            )
276        };
277
278        let params = self.params.iter().fold(cc.unwrap_or(Document::Empty), |acc, param| {
279            if acc.is_empty() {
280                param.render()
281            } else {
282                acc + const_text(" ") + param.render()
283            }
284        });
285
286        if self.results.is_empty() {
287            params
288        } else {
289            let open = const_text("(") + const_text("result");
290            let results = self
291                .results
292                .iter()
293                .fold(open, |acc, e| acc + const_text(" ") + text(format!("{}", &e.ty)))
294                + const_text(")");
295            if matches!(params, Document::Empty) {
296                results
297            } else {
298                params + const_text(" ") + results
299            }
300        }
301    }
302}
303
304/// An [ExternalFunction] represents a function whose name and signature are known,
305/// but which may or may not be compiled as part of the current translation unit.
306///
307/// When building a [Function], we use [ExternalFunction] to represent references to
308/// other functions in the program which are called from its body. One "imports" a
309/// function to make it callable.
310///
311/// At link time, we make sure all external function references are either defined in
312/// the current program, or are well-known functions that are provided as part of a kernel
313/// or standard library in the Miden VM.
314#[derive(Debug, Clone, PartialEq, Eq)]
315pub struct ExternalFunction {
316    pub id: FunctionIdent,
317    pub signature: Signature,
318}
319impl Ord for ExternalFunction {
320    #[inline]
321    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
322        self.id.cmp(&other.id)
323    }
324}
325impl PartialOrd for ExternalFunction {
326    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
327        Some(self.cmp(other))
328    }
329}
330impl formatter::PrettyPrint for ExternalFunction {
331    fn render(&self) -> formatter::Document {
332        use crate::formatter::*;
333
334        let header = const_text("(")
335            + const_text("func")
336            + const_text(" ")
337            + const_text("(")
338            + const_text("import")
339            + const_text(" ")
340            + display(self.id.module)
341            + const_text(" ")
342            + display(self.id.function)
343            + const_text(")");
344        let signature = (const_text(" ") + self.signature.render() + const_text(")"))
345            | indent(6, nl() + self.signature.render() + const_text(")"));
346
347        header + signature
348    }
349}
350
351intrusive_adapter!(pub FunctionListAdapter = Box<Function>: Function { link: LinkedListLink });
352
353/// A type alias for `LinkedList<FunctionListAdapter>`
354pub type FunctionList = LinkedList<FunctionListAdapter>;
355
356/// [Function] corresponds to a function definition, in single-static assignment (SSA) form.
357///
358/// * Functions may have zero or more parameters, and produce zero or more results.
359/// * Functions are namespaced in [Module]s. You may define a function separately from a module,
360///   to aid in parallelizing compilation, but functions must be attached to a module prior to code
361///   generation. Furthermore, in order to reference other functions, you must do so using their
362///   fully-qualified names.
363/// * Functions consist of one or more basic blocks, where the entry block is predefined based
364///   on the function signature.
365/// * Basic blocks consist of a sequence of [Instruction] without any control flow (excluding
366///   calls), terminating with a control flow instruction. Our SSA representation uses block
367///   arguments rather than phi nodes to represent join points in the control flow graph.
368/// * Instructions consume zero or more arguments, and produce zero or more results. Results
369///   produced by an instruction constitute definitions of those values. A value may only ever have
370///   a single definition, e.g. you can't reassign a value after it is introduced by an instruction.
371///
372/// References to functions and global variables from a [Function] are not fully validated until
373/// link-time/code generation.
374#[derive(Spanned, AnalysisKey)]
375pub struct Function {
376    link: LinkedListLink,
377    #[span]
378    #[analysis_key]
379    pub id: FunctionIdent,
380    pub signature: Signature,
381    pub dfg: DataFlowGraph,
382}
383impl Function {
384    /// Create a new [Function] with the given name, signature, and source location.
385    ///
386    /// The resulting function will be given default internal linkage, i.e. it will only
387    /// be visible within it's containing [Module].
388    pub fn new(id: FunctionIdent, signature: Signature) -> Self {
389        let mut dfg = DataFlowGraph::default();
390        let entry = dfg.entry_block();
391        for param in signature.params() {
392            dfg.append_block_param(entry, param.ty.clone(), id.span());
393        }
394        dfg.imports.insert(
395            id,
396            ExternalFunction {
397                id,
398                signature: signature.clone(),
399            },
400        );
401        Self {
402            link: Default::default(),
403            id,
404            signature,
405            dfg,
406        }
407    }
408
409    /// This function is like [Function::new], except it does not initialize the
410    /// function entry block using the provided [Signature]. Instead, it is expected
411    /// that the caller does this manually.
412    ///
413    /// This is primarily intended for use by the IR parser.
414    pub(crate) fn new_uninit(id: FunctionIdent, signature: Signature) -> Self {
415        let mut dfg = DataFlowGraph::new_uninit();
416        dfg.imports.insert(
417            id,
418            ExternalFunction {
419                id,
420                signature: signature.clone(),
421            },
422        );
423        Self {
424            link: Default::default(),
425            id,
426            signature,
427            dfg,
428        }
429    }
430
431    /// Returns true if this function has yet to be attached to a [Module]
432    pub fn is_detached(&self) -> bool {
433        !self.link.is_linked()
434    }
435
436    /// Returns true if this function is a kernel function
437    pub fn is_kernel(&self) -> bool {
438        self.signature.is_kernel()
439    }
440
441    /// Returns true if this function has external linkage
442    pub fn is_public(&self) -> bool {
443        self.signature.is_public()
444    }
445
446    /// Return the [Signature] for this function
447    #[inline]
448    pub fn signature(&self) -> &Signature {
449        &self.signature
450    }
451
452    /// Return the [Signature] for this function
453    #[inline]
454    pub fn signature_mut(&mut self) -> &mut Signature {
455        &mut self.signature
456    }
457
458    /// Return the number of parameters this function expects
459    pub fn arity(&self) -> usize {
460        self.signature.arity()
461    }
462
463    /// Return the [Linkage] type for this function
464    pub fn linkage(&self) -> Linkage {
465        self.signature.linkage
466    }
467
468    /// Set the linkage type for this function
469    pub fn set_linkage(&mut self, linkage: Linkage) {
470        self.signature.linkage = linkage;
471    }
472
473    /// Return the [CallConv] type for this function
474    pub fn calling_convention(&self) -> CallConv {
475        self.signature.cc
476    }
477
478    /// Set the linkage type for this function
479    pub fn set_calling_convention(&mut self, cc: CallConv) {
480        self.signature.cc = cc;
481    }
482
483    /// Return true if this function has attribute `name`
484    pub fn has_attribute<Q>(&self, name: &Q) -> bool
485    where
486        Q: Ord + ?Sized,
487        Symbol: std::borrow::Borrow<Q>,
488    {
489        self.dfg.has_attribute(name)
490    }
491
492    /// Iterate over all of the external functions imported by this function
493    pub fn imports<'a, 'b: 'a>(&'b self) -> impl Iterator<Item = &'a ExternalFunction> + 'a {
494        self.dfg.imports().filter(|ext| ext.id != self.id)
495    }
496
497    pub fn builder(&mut self) -> FunctionBuilder {
498        FunctionBuilder::new(self)
499    }
500
501    pub fn cfg_printer(&self) -> impl fmt::Display + '_ {
502        CfgPrinter { function: self }
503    }
504}
505impl fmt::Debug for Function {
506    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
507        f.debug_struct("Function")
508            .field("id", &self.id)
509            .field("signature", &self.signature)
510            .finish()
511    }
512}
513impl formatter::PrettyPrint for Function {
514    fn render(&self) -> formatter::Document {
515        use crate::formatter::*;
516
517        let name = if self.is_public() {
518            const_text("(")
519                + const_text("export")
520                + const_text(" ")
521                + self.id.function.render()
522                + const_text(")")
523        } else {
524            self.id.function.render()
525        };
526        let header = const_text("(") + const_text("func") + const_text(" ") + name;
527
528        let signature =
529            (const_text(" ") + self.signature.render()) | indent(6, nl() + self.signature.render());
530
531        let body = self.dfg.blocks().fold(nl(), |acc, (_, block_data)| {
532            let open = const_text("(")
533                + const_text("block")
534                + const_text(" ")
535                + display(block_data.id.as_u32());
536            let params = block_data
537                .params(&self.dfg.value_lists)
538                .iter()
539                .map(|value| {
540                    let ty = self.dfg.value_type(*value);
541                    const_text("(")
542                        + const_text("param")
543                        + const_text(" ")
544                        + display(*value)
545                        + const_text(" ")
546                        + text(format!("{ty}"))
547                        + const_text(")")
548                })
549                .collect::<Vec<_>>();
550
551            let params_singleline = params
552                .iter()
553                .cloned()
554                .reduce(|acc, e| acc + const_text(" ") + e)
555                .map(|params| const_text(" ") + params)
556                .unwrap_or(Document::Empty);
557            let params_multiline = params
558                .into_iter()
559                .reduce(|acc, e| acc + nl() + e)
560                .map(|doc| indent(8, nl() + doc))
561                .unwrap_or(Document::Empty);
562            let header = open + (params_singleline | params_multiline);
563
564            let body = indent(
565                4,
566                block_data
567                    .insts()
568                    .map(|inst| {
569                        let inst_printer = crate::instruction::InstPrettyPrinter {
570                            current_function: self.id,
571                            id: inst,
572                            dfg: &self.dfg,
573                        };
574                        inst_printer.render()
575                    })
576                    .reduce(|acc, doc| acc + nl() + doc)
577                    .map(|body| nl() + body)
578                    .unwrap_or_default(),
579            );
580
581            if matches!(acc, Document::Newline) {
582                indent(4, acc + header + body + const_text(")"))
583            } else {
584                acc + nl() + indent(4, nl() + header + body + const_text(")"))
585            }
586        });
587
588        header + signature + body + nl() + const_text(")")
589    }
590}
591impl fmt::Display for Function {
592    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
593        self.pretty_print(f)
594    }
595}
596impl Eq for Function {}
597impl PartialEq for Function {
598    fn eq(&self, other: &Self) -> bool {
599        let is_eq = self.id == other.id && self.signature == other.signature;
600        if !is_eq {
601            return false;
602        }
603
604        // We expect the entry block to be the same
605        if self.dfg.entry != other.dfg.entry {
606            return false;
607        }
608
609        // We expect the blocks to be laid out in the same order, and to have the same parameter
610        // lists
611        for (block_id, block) in self.dfg.blocks() {
612            if let Some(other_block) = other.dfg.blocks.get(block_id) {
613                if block.params.as_slice(&self.dfg.value_lists)
614                    != other_block.params.as_slice(&other.dfg.value_lists)
615                {
616                    return false;
617                }
618                // We expect the instructions in each block to be the same
619                if !block
620                    .insts
621                    .iter()
622                    .map(|i| InstructionWithValueListPool {
623                        inst: i,
624                        value_lists: &self.dfg.value_lists,
625                    })
626                    .eq(other_block.insts.iter().map(|i| InstructionWithValueListPool {
627                        inst: i,
628                        value_lists: &other.dfg.value_lists,
629                    }))
630                {
631                    return false;
632                }
633            } else {
634                return false;
635            }
636        }
637
638        // We expect both functions to have the same imports
639        self.dfg.imports == other.dfg.imports
640    }
641}
642impl midenc_session::Emit for Function {
643    fn name(&self) -> Option<crate::Symbol> {
644        Some(self.id.function.as_symbol())
645    }
646
647    fn output_type(&self, _mode: midenc_session::OutputMode) -> midenc_session::OutputType {
648        midenc_session::OutputType::Hir
649    }
650
651    fn write_to<W: std::io::Write>(
652        &self,
653        mut writer: W,
654        mode: midenc_session::OutputMode,
655        _session: &midenc_session::Session,
656    ) -> std::io::Result<()> {
657        assert_eq!(
658            mode,
659            midenc_session::OutputMode::Text,
660            "binary mode is not supported for HIR functions"
661        );
662        writer.write_fmt(format_args!("{}\n", self))
663    }
664}
665
666struct CfgPrinter<'a> {
667    function: &'a Function,
668}
669impl<'a> fmt::Display for CfgPrinter<'a> {
670    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
671        use std::collections::{BTreeSet, VecDeque};
672
673        f.write_str("flowchart TB\n")?;
674
675        let mut block_q = VecDeque::from([self.function.dfg.entry_block()]);
676        let mut visited = BTreeSet::<Block>::default();
677        while let Some(block_id) = block_q.pop_front() {
678            if !visited.insert(block_id) {
679                continue;
680            }
681            if let Some(last_inst) = self.function.dfg.last_inst(block_id) {
682                match self.function.dfg.analyze_branch(last_inst) {
683                    BranchInfo::NotABranch => {
684                        // Must be a return or unreachable, print opcode for readability
685                        let opcode = self.function.dfg.inst(last_inst).opcode();
686                        writeln!(f, "    {block_id} --> {opcode}")?;
687                    }
688                    BranchInfo::SingleDest(info) => {
689                        assert!(
690                            self.function.dfg.is_block_linked(info.destination),
691                            "reference to detached block in attached block {}",
692                            info.destination
693                        );
694                        writeln!(f, "    {block_id} --> {}", info.destination)?;
695                        block_q.push_back(info.destination);
696                    }
697                    BranchInfo::MultiDest(ref infos) => {
698                        for info in infos {
699                            assert!(
700                                self.function.dfg.is_block_linked(info.destination),
701                                "reference to detached block in attached block {}",
702                                info.destination
703                            );
704                            writeln!(f, "    {block_id} --> {}", info.destination)?;
705                            block_q.push_back(info.destination);
706                        }
707                    }
708                }
709            }
710        }
711
712        Ok(())
713    }
714}