1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
use smallvec::SmallVec;
use vm_core::mast::MastNodeId;

use super::{Assembler, BasicBlockBuilder, Operation};
use crate::{
    assembler::{mast_forest_builder::MastForestBuilder, ProcedureContext},
    ast::{InvocationTarget, InvokeKind},
    AssemblyError, RpoDigest, SourceSpan, Spanned,
};

/// Procedure Invocation
impl Assembler {
    pub(super) fn invoke(
        &self,
        kind: InvokeKind,
        callee: &InvocationTarget,
        proc_ctx: &mut ProcedureContext,
        mast_forest_builder: &mut MastForestBuilder,
    ) -> Result<Option<MastNodeId>, AssemblyError> {
        let span = callee.span();
        let digest = self.resolve_target(kind, callee, proc_ctx, mast_forest_builder)?;
        self.invoke_mast_root(kind, span, digest, proc_ctx, mast_forest_builder)
    }

    fn invoke_mast_root(
        &self,
        kind: InvokeKind,
        span: SourceSpan,
        mast_root: RpoDigest,
        proc_ctx: &mut ProcedureContext,
        mast_forest_builder: &mut MastForestBuilder,
    ) -> Result<Option<MastNodeId>, AssemblyError> {
        // Get the procedure from the assembler
        let current_source_file = self.source_manager.get(span.source_id()).ok();

        // If the procedure is cached, register the call to ensure the callset
        // is updated correctly.
        match mast_forest_builder.find_procedure(&mast_root) {
            Some(proc) if matches!(kind, InvokeKind::SysCall) => {
                // Verify if this is a syscall, that the callee is a kernel procedure
                //
                // NOTE: The assembler is expected to know the full set of all kernel
                // procedures at this point, so if we can't identify the callee as a
                // kernel procedure, it is a definite error.
                if !proc.visibility().is_syscall() {
                    return Err(AssemblyError::InvalidSysCallTarget {
                        span,
                        source_file: current_source_file,
                        callee: proc.fully_qualified_name().clone(),
                    });
                }
                let maybe_kernel_path = proc.path();
                self.module_graph
                    .find_module(maybe_kernel_path)
                    .ok_or_else(|| AssemblyError::InvalidSysCallTarget {
                        span,
                        source_file: current_source_file.clone(),
                        callee: proc.fully_qualified_name().clone(),
                    })
                    .and_then(|module| {
                        // Note: this module is guaranteed to be of AST variant, since we have the
                        // AST of a procedure contained in it (i.e. `proc`). Hence, it must be that
                        // the entire module is in AST representation as well.
                        if module.unwrap_ast().is_kernel() {
                            Ok(())
                        } else {
                            Err(AssemblyError::InvalidSysCallTarget {
                                span,
                                source_file: current_source_file.clone(),
                                callee: proc.fully_qualified_name().clone(),
                            })
                        }
                    })?;
                proc_ctx.register_external_call(&proc, false)?;
            },
            Some(proc) => proc_ctx.register_external_call(&proc, false)?,
            None => (),
        }

        let mast_root_node_id = {
            match kind {
                InvokeKind::Exec | InvokeKind::ProcRef => {
                    // Note that here we rely on the fact that we topologically sorted the
                    // procedures, such that when we assemble a procedure, all
                    // procedures that it calls will have been assembled, and
                    // hence be present in the `MastForest`.
                    match mast_forest_builder.find_procedure_node_id(mast_root) {
                        Some(root) => root,
                        None => {
                            // If the MAST root called isn't known to us, make it an external
                            // reference.
                            mast_forest_builder.ensure_external(mast_root)?
                        },
                    }
                },
                InvokeKind::Call => {
                    let callee_id = match mast_forest_builder.find_procedure_node_id(mast_root) {
                        Some(callee_id) => callee_id,
                        None => {
                            // If the MAST root called isn't known to us, make it an external
                            // reference.
                            mast_forest_builder.ensure_external(mast_root)?
                        },
                    };

                    mast_forest_builder.ensure_call(callee_id)?
                },
                InvokeKind::SysCall => {
                    let callee_id = match mast_forest_builder.find_procedure_node_id(mast_root) {
                        Some(callee_id) => callee_id,
                        None => {
                            // If the MAST root called isn't known to us, make it an external
                            // reference.
                            mast_forest_builder.ensure_external(mast_root)?
                        },
                    };

                    mast_forest_builder.ensure_syscall(callee_id)?
                },
            }
        };

        Ok(Some(mast_root_node_id))
    }

    /// Creates a new DYN block for the dynamic code execution and return.
    pub(super) fn dynexec(
        &self,
        mast_forest_builder: &mut MastForestBuilder,
    ) -> Result<Option<MastNodeId>, AssemblyError> {
        let dyn_node_id = mast_forest_builder.ensure_dyn()?;

        Ok(Some(dyn_node_id))
    }

    /// Creates a new CALL block whose target is DYN.
    pub(super) fn dyncall(
        &self,
        mast_forest_builder: &mut MastForestBuilder,
    ) -> Result<Option<MastNodeId>, AssemblyError> {
        let dyn_call_node_id = {
            let dyn_node_id = mast_forest_builder.ensure_dyn()?;
            mast_forest_builder.ensure_call(dyn_node_id)?
        };

        Ok(Some(dyn_call_node_id))
    }

    pub(super) fn procref(
        &self,
        callee: &InvocationTarget,
        proc_ctx: &mut ProcedureContext,
        span_builder: &mut BasicBlockBuilder,
        mast_forest_builder: &MastForestBuilder,
    ) -> Result<(), AssemblyError> {
        let digest =
            self.resolve_target(InvokeKind::ProcRef, callee, proc_ctx, mast_forest_builder)?;
        self.procref_mast_root(digest, proc_ctx, span_builder, mast_forest_builder)
    }

    fn procref_mast_root(
        &self,
        mast_root: RpoDigest,
        proc_ctx: &mut ProcedureContext,
        span_builder: &mut BasicBlockBuilder,
        mast_forest_builder: &MastForestBuilder,
    ) -> Result<(), AssemblyError> {
        // Add the root to the callset to be able to use dynamic instructions
        // with the referenced procedure later

        if let Some(proc) = mast_forest_builder.find_procedure(&mast_root) {
            proc_ctx.register_external_call(&proc, false)?;
        }

        // Create an array with `Push` operations containing root elements
        let ops = mast_root
            .iter()
            .map(|elem| Operation::Push(*elem))
            .collect::<SmallVec<[_; 4]>>();
        span_builder.push_ops(ops);
        Ok(())
    }
}