Skip to main content

sway_ir/
function.rs

1//! A typical function data type.
2//!
3//! [`Function`] is named, takes zero or more arguments and has an optional return value.  It
4//! contains a collection of [`Block`]s.
5//!
6//! It also maintains a collection of local values which can be typically regarded as variables
7//! existing in the function scope.
8
9use std::collections::{BTreeMap, HashMap};
10use std::fmt::Write;
11
12use rustc_hash::{FxHashMap, FxHashSet};
13
14use crate::{
15    block::{Block, BlockIterator, Label},
16    context::Context,
17    error::IrError,
18    irtype::Type,
19    metadata::MetadataIndex,
20    module::Module,
21    value::{Value, ValueDatum},
22    variable::{LocalVar, LocalVarContent},
23    BlockArgument, BranchToWithArgs,
24};
25use crate::{Constant, InstOp};
26
27/// A wrapper around an [ECS](https://github.com/orlp/slotmap) handle into the
28/// [`Context`].
29#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
30pub struct Function(pub slotmap::DefaultKey);
31
32#[doc(hidden)]
33pub struct FunctionContent {
34    pub name: String,
35    /// Display string representing the function in the ABI errors
36    /// related context (in "errorCodes" and "panickingCalls" sections).
37    // TODO: Explore how and if we should lazy evaluate `abi_errors_display`,
38    //       only for functions that are actually used in ABI errors context.
39    //       Having it precomputed for every function is a simple design.
40    //       Lazy evaluation might be much more complex to implement and
41    //       a premature optimization, considering that even for large
42    //       project we compile <1500 functions.
43    pub abi_errors_display: String,
44    pub arguments: Vec<(String, Value)>,
45    pub return_type: Type,
46    pub blocks: Vec<Block>,
47    pub module: Module,
48    pub is_public: bool,
49    pub is_entry: bool,
50    /// True if the function was an entry, before getting wrapped
51    /// by the `__entry` function. E.g, a script `main` function.
52    pub is_original_entry: bool,
53    pub is_fallback: bool,
54    pub selector: Option<[u8; 4]>,
55    pub metadata: Option<MetadataIndex>,
56
57    pub local_storage: BTreeMap<String, LocalVar>, // BTree rather than Hash for deterministic ordering.
58
59    next_label_idx: u64,
60}
61
62impl Function {
63    /// Return a new [`Function`] handle.
64    ///
65    /// Creates a [`Function`] in the `context` within `module` and returns a handle.
66    ///
67    /// `name`, `args`, `return_type` and `is_public` are the usual suspects.  `selector` is a
68    /// special value used for Sway contract calls; much like `name` is unique and not particularly
69    /// used elsewhere in the IR.
70    #[allow(clippy::too_many_arguments)]
71    pub fn new(
72        context: &mut Context,
73        module: Module,
74        name: String,
75        abi_errors_display: String,
76        args: Vec<(String, Type, Option<MetadataIndex>)>,
77        return_type: Type,
78        selector: Option<[u8; 4]>,
79        is_public: bool,
80        is_entry: bool,
81        is_original_entry: bool,
82        is_fallback: bool,
83        metadata: Option<MetadataIndex>,
84    ) -> Function {
85        let content = FunctionContent {
86            name,
87            abi_errors_display,
88            // Arguments to a function are the arguments to its entry block.
89            // We set it up after creating the entry block below.
90            arguments: Vec::new(),
91            return_type,
92            blocks: Vec::new(),
93            module,
94            is_public,
95            is_entry,
96            is_original_entry,
97            is_fallback,
98            selector,
99            metadata,
100            local_storage: BTreeMap::new(),
101            next_label_idx: 0,
102        };
103        let func = Function(context.functions.insert(content));
104
105        context.modules[module.0].functions.push(func);
106
107        let entry_block = Block::new(context, func, Some("entry".to_owned()));
108        context
109            .functions
110            .get_mut(func.0)
111            .unwrap()
112            .blocks
113            .push(entry_block);
114
115        // Setup the arguments.
116        let arguments: Vec<_> = args
117            .into_iter()
118            .enumerate()
119            .map(|(idx, (name, ty, arg_metadata))| {
120                (
121                    name,
122                    Value::new_argument(
123                        context,
124                        BlockArgument {
125                            block: entry_block,
126                            idx,
127                            ty,
128                            is_immutable: false,
129                        },
130                    )
131                    .add_metadatum(context, arg_metadata),
132                )
133            })
134            .collect();
135        context
136            .functions
137            .get_mut(func.0)
138            .unwrap()
139            .arguments
140            .clone_from(&arguments);
141        let (_, arg_vals): (Vec<_>, Vec<_>) = arguments.iter().cloned().unzip();
142        context.blocks.get_mut(entry_block.0).unwrap().args = arg_vals;
143
144        func
145    }
146
147    /// WARNING: This function iterate over all instructions
148    pub fn is_leaf_fn(&self, context: &Context) -> bool {
149        let any_call = self
150            .instruction_iter(context)
151            .filter_map(|(_, i)| i.get_instruction(context).map(|i| i.is_call()))
152            .any(|x| x);
153        !any_call
154    }
155
156    /// Create and append a new [`Block`] to this function.
157    pub fn create_block(&self, context: &mut Context, label: Option<Label>) -> Block {
158        let block = Block::new(context, *self, label);
159        let func = context.functions.get_mut(self.0).unwrap();
160        func.blocks.push(block);
161        block
162    }
163
164    /// Create and insert a new [`Block`] into this function.
165    ///
166    /// The new block is inserted before `other`.
167    pub fn create_block_before(
168        &self,
169        context: &mut Context,
170        other: &Block,
171        label: Option<Label>,
172    ) -> Result<Block, IrError> {
173        let block_idx = context.functions[self.0]
174            .blocks
175            .iter()
176            .position(|block| block == other)
177            .ok_or_else(|| {
178                let label = &context.blocks[other.0].label;
179                IrError::MissingBlock(label.clone())
180            })?;
181
182        let new_block = Block::new(context, *self, label);
183        context.functions[self.0]
184            .blocks
185            .insert(block_idx, new_block);
186        Ok(new_block)
187    }
188
189    /// Create and insert a new [`Block`] into this function.
190    ///
191    /// The new block is inserted after `other`.
192    pub fn create_block_after(
193        &self,
194        context: &mut Context,
195        other: &Block,
196        label: Option<Label>,
197    ) -> Result<Block, IrError> {
198        // We need to create the new block first (even though we may not use it on Err below) since
199        // we can't borrow context mutably twice.
200        let new_block = Block::new(context, *self, label);
201        let func = context.functions.get_mut(self.0).unwrap();
202        func.blocks
203            .iter()
204            .position(|block| block == other)
205            .map(|idx| {
206                func.blocks.insert(idx + 1, new_block);
207                new_block
208            })
209            .ok_or_else(|| {
210                let label = &context.blocks[other.0].label;
211                IrError::MissingBlock(label.clone())
212            })
213    }
214
215    /// Remove a [`Block`] from this function.
216    ///
217    /// > Care must be taken to ensure the block has no predecessors otherwise the function will be
218    /// > made invalid.
219    pub fn remove_block(&self, context: &mut Context, block: &Block) -> Result<(), IrError> {
220        let label = block.get_label(context);
221        let func = context.functions.get_mut(self.0).unwrap();
222        let block_idx = func
223            .blocks
224            .iter()
225            .position(|b| b == block)
226            .ok_or(IrError::RemoveMissingBlock(label))?;
227        func.blocks.remove(block_idx);
228        Ok(())
229    }
230
231    /// Remove instructions from function that satisfy a given predicate.
232    pub fn remove_instructions<T: Fn(Value) -> bool>(&self, context: &mut Context, pred: T) {
233        for block in context.functions[self.0].blocks.clone() {
234            block.remove_instructions(context, &pred);
235        }
236    }
237
238    /// Get a new unique block label.
239    ///
240    /// If `hint` is `None` then the label will be in the form `"blockN"` where N is an
241    /// incrementing decimal.
242    ///
243    /// Otherwise if the hint is already unique to this function it will be returned.  If not
244    /// already unique it will have N appended to it until it is unique.
245    pub fn get_unique_label(&self, context: &mut Context, hint: Option<String>) -> String {
246        match hint {
247            Some(hint) => {
248                if context.functions[self.0]
249                    .blocks
250                    .iter()
251                    .any(|block| context.blocks[block.0].label == hint)
252                {
253                    let idx = self.get_next_label_idx(context);
254                    self.get_unique_label(context, Some(format!("{hint}{idx}")))
255                } else {
256                    hint
257                }
258            }
259            None => {
260                let idx = self.get_next_label_idx(context);
261                self.get_unique_label(context, Some(format!("block{idx}")))
262            }
263        }
264    }
265
266    fn get_next_label_idx(&self, context: &mut Context) -> u64 {
267        let func = context.functions.get_mut(self.0).unwrap();
268        let idx = func.next_label_idx;
269        func.next_label_idx += 1;
270        idx
271    }
272
273    /// Return the number of blocks in this function.
274    pub fn num_blocks(&self, context: &Context) -> usize {
275        context.functions[self.0].blocks.len()
276    }
277
278    /// Return the number of instructions in this function.
279    ///
280    /// The [crate::InstOp::AsmBlock] is counted as a single instruction,
281    /// regardless of the number of [crate::asm::AsmInstruction]s in the ASM block.
282    /// E.g., even if the ASM block is empty and contains no instructions, it
283    /// will still be counted as a single instruction.
284    ///
285    /// If you want to count every ASM instruction as an instruction, use
286    /// `num_instructions_incl_asm_instructions` instead.
287    pub fn num_instructions(&self, context: &Context) -> usize {
288        self.block_iter(context)
289            .map(|block| block.num_instructions(context))
290            .sum()
291    }
292
293    /// Return the number of instructions in this function, including
294    /// the [crate::asm::AsmInstruction]s found in [crate::InstOp::AsmBlock]s.
295    ///
296    /// Every [crate::asm::AsmInstruction] encountered in any of the ASM blocks
297    /// will be counted as an instruction. The [crate::InstOp::AsmBlock] itself
298    /// is not counted but rather replaced with the number of ASM instructions
299    /// found in the block. In other words, empty ASM blocks do not count as
300    /// instructions.
301    ///
302    /// If you want to count [crate::InstOp::AsmBlock]s as single instructions, use
303    /// `num_instructions` instead.
304    pub fn num_instructions_incl_asm_instructions(&self, context: &Context) -> usize {
305        self.instruction_iter(context).fold(0, |num, (_, value)| {
306            match &value
307                .get_instruction(context)
308                .expect("We are iterating through the instructions.")
309                .op
310            {
311                InstOp::AsmBlock(asm, _) => num + asm.body.len(),
312                _ => num + 1,
313            }
314        })
315    }
316
317    /// Return the function name.
318    pub fn get_name<'a>(&self, context: &'a Context) -> &'a str {
319        &context.functions[self.0].name
320    }
321
322    /// Return the display string representing the function in the ABI errors
323    /// related context, in the "errorCodes" and "panickingCalls" sections.
324    pub fn get_abi_errors_display(&self, context: &Context) -> String {
325        context.functions[self.0].abi_errors_display.clone()
326    }
327
328    /// Return the module that this function belongs to.
329    pub fn get_module(&self, context: &Context) -> Module {
330        context.functions[self.0].module
331    }
332
333    /// Return the function entry (i.e., the first) block.
334    pub fn get_entry_block(&self, context: &Context) -> Block {
335        context.functions[self.0].blocks[0]
336    }
337
338    /// Return the attached metadata.
339    pub fn get_metadata(&self, context: &Context) -> Option<MetadataIndex> {
340        context.functions[self.0].metadata
341    }
342
343    /// Whether this function has a valid selector.
344    pub fn has_selector(&self, context: &Context) -> bool {
345        context.functions[self.0].selector.is_some()
346    }
347
348    /// Return the function selector, if it has one.
349    pub fn get_selector(&self, context: &Context) -> Option<[u8; 4]> {
350        context.functions[self.0].selector
351    }
352
353    /// Whether or not the function is a program entry point, i.e. `main`, `#[test]` fns or abi
354    /// methods.
355    pub fn is_entry(&self, context: &Context) -> bool {
356        context.functions[self.0].is_entry
357    }
358
359    /// Whether or not the function was a program entry point, i.e. `main`, `#[test]` fns or abi
360    /// methods, before it got wrapped within the `__entry` function.
361    pub fn is_original_entry(&self, context: &Context) -> bool {
362        context.functions[self.0].is_original_entry
363    }
364
365    /// Whether or not this function is a contract fallback function
366    pub fn is_fallback(&self, context: &Context) -> bool {
367        context.functions[self.0].is_fallback
368    }
369
370    // Get the function return type.
371    pub fn get_return_type(&self, context: &Context) -> Type {
372        context.functions[self.0].return_type
373    }
374
375    // Set a new function return type.
376    pub fn set_return_type(&self, context: &mut Context, new_ret_type: Type) {
377        context.functions.get_mut(self.0).unwrap().return_type = new_ret_type
378    }
379
380    /// Get the number of args.
381    pub fn num_args(&self, context: &Context) -> usize {
382        context.functions[self.0].arguments.len()
383    }
384
385    /// Get an arg value by name, if found.
386    pub fn get_arg(&self, context: &Context, name: &str) -> Option<Value> {
387        context.functions[self.0]
388            .arguments
389            .iter()
390            .find_map(|(arg_name, val)| (arg_name == name).then_some(val))
391            .copied()
392    }
393
394    /// Append an extra argument to the function signature.
395    ///
396    /// NOTE: `arg` must be a `BlockArgument` value with the correct index otherwise `add_arg` will
397    /// panic.
398    pub fn add_arg<S: Into<String>>(&self, context: &mut Context, name: S, arg: Value) {
399        match context.values[arg.0].value {
400            ValueDatum::Argument(BlockArgument { idx, .. })
401                if idx == context.functions[self.0].arguments.len() =>
402            {
403                context.functions[self.0].arguments.push((name.into(), arg));
404            }
405            _ => panic!("Inconsistent function argument being added"),
406        }
407    }
408
409    /// Find the name of an arg by value.
410    pub fn lookup_arg_name<'a>(&self, context: &'a Context, value: &Value) -> Option<&'a String> {
411        context.functions[self.0]
412            .arguments
413            .iter()
414            .find_map(|(name, arg_val)| (arg_val == value).then_some(name))
415    }
416
417    /// Return an iterator for each of the function arguments.
418    pub fn args_iter<'a>(&self, context: &'a Context) -> impl Iterator<Item = &'a (String, Value)> {
419        context.functions[self.0].arguments.iter()
420    }
421
422    /// Is argument `i` marked immutable?
423    pub fn is_arg_immutable(&self, context: &Context, i: usize) -> bool {
424        if let Some((_, val)) = context.functions[self.0].arguments.get(i) {
425            if let ValueDatum::Argument(arg) = &context.values[val.0].value {
426                return arg.is_immutable;
427            }
428        }
429        false
430    }
431
432    /// Get a pointer to a local value by name, if found.
433    pub fn get_local_var(&self, context: &Context, name: &str) -> Option<LocalVar> {
434        context.functions[self.0].local_storage.get(name).copied()
435    }
436
437    /// Find the name of a local value by pointer.
438    pub fn lookup_local_name<'a>(
439        &self,
440        context: &'a Context,
441        var: &LocalVar,
442    ) -> Option<&'a String> {
443        context.functions[self.0]
444            .local_storage
445            .iter()
446            .find_map(|(name, local_var)| if local_var == var { Some(name) } else { None })
447    }
448
449    /// Add a value to the function local storage.
450    ///
451    /// The name must be unique to this function else an error is returned.
452    pub fn new_local_var(
453        &self,
454        context: &mut Context,
455        name: String,
456        local_type: Type,
457        initializer: Option<Constant>,
458        mutable: bool,
459    ) -> Result<LocalVar, IrError> {
460        let var = LocalVar::new(context, local_type, initializer, mutable);
461        let func = context.functions.get_mut(self.0).unwrap();
462        func.local_storage
463            .insert(name.clone(), var)
464            .map(|_| Err(IrError::FunctionLocalClobbered(func.name.clone(), name)))
465            .unwrap_or(Ok(var))
466    }
467
468    /// Add a value to the function local storage, by forcing the name to be unique if needed.
469    ///
470    /// Will use the provided name as a hint and rename to guarantee insertion.
471    pub fn new_unique_local_var(
472        &self,
473        context: &mut Context,
474        name: String,
475        local_type: Type,
476        initializer: Option<Constant>,
477        mutable: bool,
478    ) -> LocalVar {
479        let func = &context.functions[self.0];
480        let new_name = if func.local_storage.contains_key(&name) {
481            // Assuming that we'll eventually find a unique name by appending numbers to the old
482            // one...
483            (0..)
484                .find_map(|n| {
485                    let candidate = format!("{name}{n}");
486                    if func.local_storage.contains_key(&candidate) {
487                        None
488                    } else {
489                        Some(candidate)
490                    }
491                })
492                .unwrap()
493        } else {
494            name
495        };
496        self.new_local_var(context, new_name, local_type, initializer, mutable)
497            .unwrap()
498    }
499
500    /// Return an iterator to all of the values in this function's local storage.
501    pub fn locals_iter<'a>(
502        &self,
503        context: &'a Context,
504    ) -> impl Iterator<Item = (&'a String, &'a LocalVar)> {
505        context.functions[self.0].local_storage.iter()
506    }
507
508    /// Remove given list of locals
509    pub fn remove_locals(&self, context: &mut Context, removals: &Vec<String>) {
510        for remove in removals {
511            if let Some(local) = context.functions[self.0].local_storage.remove(remove) {
512                context.local_vars.remove(local.0);
513            }
514        }
515    }
516
517    /// Merge values from another [`Function`] into this one.
518    ///
519    /// The names of the merged values are guaranteed to be unique via the use of
520    /// [`Function::new_unique_local_var`].
521    ///
522    /// Returns a map from the original pointers to the newly merged pointers.
523    pub fn merge_locals_from(
524        &self,
525        context: &mut Context,
526        other: Function,
527    ) -> HashMap<LocalVar, LocalVar> {
528        let mut var_map = HashMap::new();
529        let old_vars: Vec<(String, LocalVar, LocalVarContent)> = context.functions[other.0]
530            .local_storage
531            .iter()
532            .map(|(name, var)| (name.clone(), *var, context.local_vars[var.0].clone()))
533            .collect();
534        for (name, old_var, old_var_content) in old_vars {
535            let old_ty = old_var_content
536                .ptr_ty
537                .get_pointee_type(context)
538                .expect("LocalVar types are always pointers.");
539            let new_var = self.new_unique_local_var(
540                context,
541                name.clone(),
542                old_ty,
543                old_var_content.initializer,
544                old_var_content.mutable,
545            );
546            var_map.insert(old_var, new_var);
547        }
548        var_map
549    }
550
551    /// Return an iterator to each block in this function.
552    pub fn block_iter(&self, context: &Context) -> BlockIterator {
553        BlockIterator::new(context, self)
554    }
555
556    /// Return an iterator to each instruction in each block in this function.
557    ///
558    /// This is a convenience method for when all instructions in a function need to be inspected.
559    /// The instruction value is returned from the iterator along with the block it belongs to.
560    pub fn instruction_iter<'a>(
561        &self,
562        context: &'a Context,
563    ) -> impl Iterator<Item = (Block, Value)> + 'a {
564        context.functions[self.0]
565            .blocks
566            .iter()
567            .flat_map(move |block| {
568                block
569                    .instruction_iter(context)
570                    .map(move |ins_val| (*block, ins_val))
571            })
572    }
573
574    /// Return a reverse iterator to each instruction in each block in this function.
575    ///
576    /// Blocks and their instructions are both traversed in reverse order.
577    ///
578    /// This is a convenience method for when all instructions in a function need to be inspected
579    /// in reverse order.
580    /// The instruction value is returned from the iterator along with the block it belongs to.
581    pub fn instruction_iter_rev<'a>(
582        &self,
583        context: &'a Context,
584    ) -> impl Iterator<Item = (Block, Value)> + 'a {
585        context.functions[self.0]
586            .blocks
587            .iter()
588            .rev()
589            .flat_map(move |block| {
590                block
591                    .instruction_iter(context)
592                    .rev()
593                    .map(move |ins_val| (*block, ins_val))
594            })
595    }
596
597    /// Replace a value with another within this function.
598    ///
599    /// This is a convenience method which iterates over this function's blocks and calls
600    /// [`Block::replace_values`] in turn.
601    ///
602    /// `starting_block` is an optimisation for when the first possible reference to `old_val` is
603    /// known.
604    pub fn replace_values(
605        &self,
606        context: &mut Context,
607        replace_map: &FxHashMap<Value, Value>,
608        starting_block: Option<Block>,
609    ) {
610        let mut block_iter = self.block_iter(context).peekable();
611
612        if let Some(ref starting_block) = starting_block {
613            // Skip blocks until we hit the starting block.
614            while block_iter
615                .next_if(|block| block != starting_block)
616                .is_some()
617            {}
618        }
619
620        for block in block_iter {
621            block.replace_values(context, replace_map);
622        }
623    }
624
625    pub fn replace_value(
626        &self,
627        context: &mut Context,
628        old_val: Value,
629        new_val: Value,
630        starting_block: Option<Block>,
631    ) {
632        let mut map = FxHashMap::<Value, Value>::default();
633        map.insert(old_val, new_val);
634        self.replace_values(context, &map, starting_block);
635    }
636
637    /// A graphviz dot graph of the control-flow-graph.
638    pub fn dot_cfg(&self, context: &Context) -> String {
639        let mut worklist = Vec::<Block>::new();
640        let mut visited = FxHashSet::<Block>::default();
641        let entry = self.get_entry_block(context);
642        let mut res = format!("digraph {} {{\n", self.get_name(context));
643
644        worklist.push(entry);
645        while let Some(n) = worklist.pop() {
646            visited.insert(n);
647            for BranchToWithArgs { block: n_succ, .. } in n.successors(context) {
648                let _ = writeln!(
649                    res,
650                    "\t{} -> {}\n",
651                    n.get_label(context),
652                    n_succ.get_label(context)
653                );
654                if !visited.contains(&n_succ) {
655                    worklist.push(n_succ);
656                }
657            }
658        }
659
660        res += "}\n";
661        res
662    }
663}
664
665/// An iterator over each [`Function`] in a [`Module`].
666pub struct FunctionIterator {
667    functions: Vec<slotmap::DefaultKey>,
668    next: usize,
669}
670
671impl FunctionIterator {
672    /// Return a new iterator for the functions in `module`.
673    pub fn new(context: &Context, module: &Module) -> FunctionIterator {
674        // Copy all the current modules indices, so they may be modified in the context during
675        // iteration.
676        FunctionIterator {
677            functions: context.modules[module.0]
678                .functions
679                .iter()
680                .map(|func| func.0)
681                .collect(),
682            next: 0,
683        }
684    }
685}
686
687impl Iterator for FunctionIterator {
688    type Item = Function;
689
690    fn next(&mut self) -> Option<Function> {
691        if self.next < self.functions.len() {
692            let idx = self.next;
693            self.next += 1;
694            Some(Function(self.functions[idx]))
695        } else {
696            None
697        }
698    }
699}