sway_core/asm_generation/fuel/programs/
abstract.rs

1use super::{AllocatedProgram, FnName, SelectorOpt};
2use crate::{
3    asm_generation::{
4        fuel::{
5            abstract_instruction_set::AbstractInstructionSet,
6            allocated_abstract_instruction_set::AllocatedAbstractInstructionSet,
7            compiler_constants,
8            data_section::{DataSection, Entry, EntryName},
9            globals_section::GlobalsSection,
10            register_sequencer::RegisterSequencer,
11        },
12        ProgramKind,
13    },
14    asm_lang::{
15        allocated_ops::{AllocatedOpcode, AllocatedRegister},
16        AllocatedAbstractOp, ConstantRegister, ControlFlowOp, Label, VirtualImmediate12,
17        VirtualImmediate18, VirtualImmediate24,
18    },
19    decl_engine::DeclRefFunction,
20};
21use either::Either;
22use sway_error::error::CompileError;
23use sway_features::ExperimentalFeatures;
24
25/// The entry point of an abstract program.
26pub(crate) struct AbstractEntry {
27    pub(crate) selector: SelectorOpt,
28    pub(crate) label: Label,
29    pub(crate) ops: AbstractInstructionSet,
30    pub(crate) name: FnName,
31    pub(crate) test_decl_ref: Option<DeclRefFunction>,
32}
33
34/// An [AbstractProgram] represents code generated by the compilation from IR, with virtual registers
35/// and abstract control flow.
36///
37/// Use `AbstractProgram::to_allocated_program()` to perform register allocation.
38///
39pub(crate) struct AbstractProgram {
40    kind: ProgramKind,
41    data_section: DataSection,
42    globals_section: GlobalsSection,
43    before_entries: AbstractInstructionSet,
44    entries: Vec<AbstractEntry>,
45    non_entries: Vec<AbstractInstructionSet>,
46    reg_seqr: RegisterSequencer,
47    experimental: ExperimentalFeatures,
48}
49
50impl AbstractProgram {
51    #[allow(clippy::too_many_arguments)]
52    pub(crate) fn new(
53        kind: ProgramKind,
54        data_section: DataSection,
55        globals_section: GlobalsSection,
56        before_entries: AbstractInstructionSet,
57        entries: Vec<AbstractEntry>,
58        non_entries: Vec<AbstractInstructionSet>,
59        reg_seqr: RegisterSequencer,
60        experimental: ExperimentalFeatures,
61    ) -> Self {
62        AbstractProgram {
63            kind,
64            data_section,
65            globals_section,
66            before_entries,
67            entries,
68            non_entries,
69            reg_seqr,
70            experimental,
71        }
72    }
73
74    /// True if the [AbstractProgram] does not contain any instructions, or entries, or data in the data section.
75    pub(crate) fn is_empty(&self) -> bool {
76        self.non_entries.is_empty()
77            && self.entries.is_empty()
78            && self.data_section.iter_all_entries().next().is_none()
79    }
80
81    /// Adds prologue, globals allocation, before entries, contract method switch, and allocates virtual register.
82    pub(crate) fn into_allocated_program(
83        mut self,
84        fallback_fn: Option<crate::asm_lang::Label>,
85    ) -> Result<AllocatedProgram, CompileError> {
86        let mut prologue = self.build_prologue();
87        self.append_globals_allocation(&mut prologue);
88        self.append_before_entries(&mut prologue)?;
89
90        match (self.experimental.new_encoding, self.kind) {
91            (true, ProgramKind::Contract) => {
92                self.append_jump_to_entry(&mut prologue);
93            }
94            (false, ProgramKind::Contract) => {
95                self.append_encoding_v0_contract_abi_switch(&mut prologue, fallback_fn);
96            }
97            _ => {}
98        }
99
100        // Keep track of the labels (and names) that represent program entry points.
101        let entries = self
102            .entries
103            .iter()
104            .map(|entry| {
105                (
106                    entry.selector,
107                    entry.label,
108                    entry.name.clone(),
109                    entry.test_decl_ref.clone(),
110                )
111            })
112            .collect();
113
114        // Gather all functions.
115        let all_functions = self
116            .entries
117            .into_iter()
118            .map(|entry| entry.ops)
119            .chain(self.non_entries);
120
121        // Optimize and then verify abstract functions.
122        let abstract_functions = all_functions
123            .map(|instruction_set| instruction_set.optimize(&self.data_section))
124            .map(AbstractInstructionSet::verify)
125            .collect::<Result<Vec<AbstractInstructionSet>, CompileError>>()?;
126
127        // Allocate the registers for each function.
128        let allocated_functions = abstract_functions
129            .into_iter()
130            .map(|abstract_instruction_set| {
131                let allocated = abstract_instruction_set.allocate_registers()?;
132                Ok(allocated.emit_pusha_popa())
133            })
134            .collect::<Result<Vec<AllocatedAbstractInstructionSet>, CompileError>>()?;
135
136        // Optimize allocated functions.
137        // TODO: Add verification. E.g., verify that the stack use for each function is balanced.
138        let functions = allocated_functions
139            .into_iter()
140            .map(|instruction_set| instruction_set.optimize())
141            .collect::<Vec<AllocatedAbstractInstructionSet>>();
142
143        Ok(AllocatedProgram {
144            kind: self.kind,
145            data_section: self.data_section,
146            prologue,
147            functions,
148            entries,
149        })
150    }
151
152    fn append_before_entries(
153        &self,
154        prologue: &mut AllocatedAbstractInstructionSet,
155    ) -> Result<(), CompileError> {
156        let before_entries = self.before_entries.clone().optimize(&self.data_section);
157        let before_entries = before_entries.verify()?;
158        let mut before_entries = before_entries.allocate_registers()?;
159        prologue.ops.append(&mut before_entries.ops);
160        Ok(())
161    }
162
163    /// Builds the asm preamble, which includes metadata and a jump past the metadata.
164    /// Right now, it looks like this:
165    ///
166    /// WORD OP
167    ///     1    MOV $scratch $pc
168    ///     -    JMPF $zero i10
169    ///     2    DATA_START (0-32) (in bytes, offset from $is)
170    ///     -    DATA_START (32-64)
171    ///     3    CONFIGURABLES_OFFSET (0-32)
172    ///     -    CONFIGURABLES_OFFSET (32-64)
173    ///     4    LW $ds $scratch 1
174    ///     -    ADD $ds $ds $scratch
175    ///     5    .program_start:
176    fn build_prologue(&mut self) -> AllocatedAbstractInstructionSet {
177        const _: () = assert!(
178            crate::PRELUDE_CONFIGURABLES_OFFSET_IN_BYTES == 16,
179            "Inconsistency in the assumption of prelude organisation"
180        );
181        const _: () = assert!(
182            crate::PRELUDE_CONFIGURABLES_SIZE_IN_BYTES == 8,
183            "Inconsistency in the assumption of prelude organisation"
184        );
185        const _: () = assert!(
186            crate::PRELUDE_SIZE_IN_BYTES == 32,
187            "Inconsistency in the assumption of prelude organisation"
188        );
189        let label = self.reg_seqr.get_label();
190        AllocatedAbstractInstructionSet {
191            ops: [
192                AllocatedAbstractOp {
193                    opcode: Either::Left(AllocatedOpcode::MOVE(
194                        AllocatedRegister::Constant(ConstantRegister::Scratch),
195                        AllocatedRegister::Constant(ConstantRegister::ProgramCounter),
196                    )),
197                    comment: String::new(),
198                    owning_span: None,
199                },
200                // word 1.5
201                AllocatedAbstractOp {
202                    opcode: Either::Right(ControlFlowOp::Jump(label)),
203                    comment: String::new(),
204                    owning_span: None,
205                },
206                // word 2 -- full word u64 placeholder
207                AllocatedAbstractOp {
208                    opcode: Either::Right(ControlFlowOp::DataSectionOffsetPlaceholder),
209                    comment: "data section offset".into(),
210                    owning_span: None,
211                },
212                // word 3 -- full word u64 placeholder
213                AllocatedAbstractOp {
214                    opcode: Either::Right(ControlFlowOp::ConfigurablesOffsetPlaceholder),
215                    comment: "configurables offset".into(),
216                    owning_span: None,
217                },
218                AllocatedAbstractOp {
219                    opcode: Either::Right(ControlFlowOp::Label(label)),
220                    comment: "end of configurables offset".into(),
221                    owning_span: None,
222                },
223                // word 4 -- load the data offset into $ds
224                AllocatedAbstractOp {
225                    opcode: Either::Left(AllocatedOpcode::LW(
226                        AllocatedRegister::Constant(ConstantRegister::DataSectionStart),
227                        AllocatedRegister::Constant(ConstantRegister::Scratch),
228                        VirtualImmediate12::new_unchecked(1, "1 doesn't fit in 12 bits"),
229                    )),
230                    comment: "".into(),
231                    owning_span: None,
232                },
233                // word 4.5 -- add $ds $ds $is
234                AllocatedAbstractOp {
235                    opcode: Either::Left(AllocatedOpcode::ADD(
236                        AllocatedRegister::Constant(ConstantRegister::DataSectionStart),
237                        AllocatedRegister::Constant(ConstantRegister::DataSectionStart),
238                        AllocatedRegister::Constant(ConstantRegister::Scratch),
239                    )),
240                    comment: "".into(),
241                    owning_span: None,
242                },
243            ]
244            .to_vec(),
245        }
246    }
247
248    // WHen the new encoding is used, jumps to the `__entry`  function
249    fn append_jump_to_entry(&mut self, asm: &mut AllocatedAbstractInstructionSet) {
250        let entry = self.entries.iter().find(|x| x.name == "__entry").unwrap();
251        asm.ops.push(AllocatedAbstractOp {
252            opcode: Either::Right(ControlFlowOp::Jump(entry.label)),
253            comment: "jump to ABI function selector".into(),
254            owning_span: None,
255        });
256    }
257
258    /// Builds the contract switch statement based on the first argument to a contract call: the
259    /// 'selector'.
260    /// See https://fuellabs.github.io/fuel-specs/master/vm#call-frames which
261    /// describes the first argument to be at word offset 73.
262    fn append_encoding_v0_contract_abi_switch(
263        &mut self,
264        asm: &mut AllocatedAbstractInstructionSet,
265        fallback_fn: Option<crate::asm_lang::Label>,
266    ) {
267        const SELECTOR_WORD_OFFSET: u64 = 73;
268        const INPUT_SELECTOR_REG: AllocatedRegister = AllocatedRegister::Allocated(0);
269        const PROG_SELECTOR_REG: AllocatedRegister = AllocatedRegister::Allocated(1);
270        const CMP_RESULT_REG: AllocatedRegister = AllocatedRegister::Allocated(2);
271
272        // Build the switch statement for selectors.
273        asm.ops.push(AllocatedAbstractOp {
274            opcode: Either::Right(ControlFlowOp::Comment),
275            comment: "[function selection]: begin contract function selector switch".into(),
276            owning_span: None,
277        });
278
279        // Load the selector from the call frame.
280        asm.ops.push(AllocatedAbstractOp {
281            opcode: Either::Left(AllocatedOpcode::LW(
282                INPUT_SELECTOR_REG,
283                AllocatedRegister::Constant(ConstantRegister::FramePointer),
284                VirtualImmediate12::new_unchecked(
285                    SELECTOR_WORD_OFFSET,
286                    "constant infallible value",
287                ),
288            )),
289            comment: "[function selection]: load input function selector".into(),
290            owning_span: None,
291        });
292
293        // Add a 'case' for each entry with a selector.
294        for entry in &self.entries {
295            let selector = match entry.selector {
296                Some(sel) => sel,
297                // Skip entries that don't have a selector - they're probably tests.
298                None => continue,
299            };
300
301            // Put the selector in the data section.
302            let data_label = self.data_section.insert_data_value(Entry::new_word(
303                u32::from_be_bytes(selector) as u64,
304                EntryName::NonConfigurable,
305                None,
306            ));
307
308            // Load the data into a register for comparison.
309            asm.ops.push(AllocatedAbstractOp {
310                opcode: Either::Left(AllocatedOpcode::LoadDataId(PROG_SELECTOR_REG, data_label)),
311                comment: format!(
312                    "[function selection]: load function {} selector for comparison",
313                    entry.name
314                ),
315                owning_span: None,
316            });
317
318            // Compare with the input selector.
319            asm.ops.push(AllocatedAbstractOp {
320                opcode: Either::Left(AllocatedOpcode::EQ(
321                    CMP_RESULT_REG,
322                    INPUT_SELECTOR_REG,
323                    PROG_SELECTOR_REG,
324                )),
325                comment: format!(
326                    "[function selection]: compare function {} selector with input selector",
327                    entry.name
328                ),
329                owning_span: None,
330            });
331
332            // Jump to the function label if the selector was equal.
333            asm.ops.push(AllocatedAbstractOp {
334                // If the comparison result is _not_ equal to 0, then it was indeed equal.
335                opcode: Either::Right(ControlFlowOp::JumpIfNotZero(CMP_RESULT_REG, entry.label)),
336                comment: "[function selection]: jump to selected contract function".into(),
337                owning_span: None,
338            });
339        }
340
341        if let Some(fallback_fn) = fallback_fn {
342            asm.ops.push(AllocatedAbstractOp {
343                opcode: Either::Right(ControlFlowOp::Call(fallback_fn)),
344                comment: "[function selection]: call contract fallback function".into(),
345                owning_span: None,
346            });
347        }
348
349        asm.ops.push(AllocatedAbstractOp {
350            opcode: Either::Left(AllocatedOpcode::MOVI(
351                AllocatedRegister::Constant(ConstantRegister::Scratch),
352                VirtualImmediate18::new_unchecked(
353                    compiler_constants::MISMATCHED_SELECTOR_REVERT_CODE.into(),
354                    "constant must fit in 18 bits",
355                ),
356            )),
357            comment: "[function selection]: load revert code for mismatched function selector"
358                .into(),
359            owning_span: None,
360        });
361        asm.ops.push(AllocatedAbstractOp {
362            opcode: Either::Left(AllocatedOpcode::RVRT(AllocatedRegister::Constant(
363                ConstantRegister::Scratch,
364            ))),
365            comment: "[function selection]: revert if no selectors have matched".into(),
366            owning_span: None,
367        });
368    }
369
370    fn append_globals_allocation(&self, asm: &mut AllocatedAbstractInstructionSet) {
371        let len_in_bytes = self.globals_section.len_in_bytes();
372        asm.ops.push(AllocatedAbstractOp {
373            opcode: Either::Left(AllocatedOpcode::CFEI(VirtualImmediate24::new_unchecked(
374                len_in_bytes,
375                "length (bytes) must fit in 24 bits",
376            ))),
377            comment: "allocate stack space for globals".into(),
378            owning_span: None,
379        });
380    }
381}
382
383impl std::fmt::Display for AbstractProgram {
384    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
385        writeln!(f, ";; Program kind: {:?}", self.kind)?;
386
387        writeln!(f, ";; --- Before Entries ---")?;
388        writeln!(f, "{}\n", self.before_entries)?;
389
390        writeln!(f, ";; --- Entries ---")?;
391        for entry in &self.entries {
392            writeln!(f, "{}\n", entry.ops)?;
393        }
394        writeln!(f, ";; --- Functions ---")?;
395        for function in &self.non_entries {
396            writeln!(f, "{function}\n")?;
397        }
398        writeln!(f, ";; --- Data ---")?;
399        write!(f, "{}", self.data_section)
400    }
401}