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    /// Create and append a new [`Block`] to this function.
148    pub fn create_block(&self, context: &mut Context, label: Option<Label>) -> Block {
149        let block = Block::new(context, *self, label);
150        let func = context.functions.get_mut(self.0).unwrap();
151        func.blocks.push(block);
152        block
153    }
154
155    /// Create and insert a new [`Block`] into this function.
156    ///
157    /// The new block is inserted before `other`.
158    pub fn create_block_before(
159        &self,
160        context: &mut Context,
161        other: &Block,
162        label: Option<Label>,
163    ) -> Result<Block, IrError> {
164        let block_idx = context.functions[self.0]
165            .blocks
166            .iter()
167            .position(|block| block == other)
168            .ok_or_else(|| {
169                let label = &context.blocks[other.0].label;
170                IrError::MissingBlock(label.clone())
171            })?;
172
173        let new_block = Block::new(context, *self, label);
174        context.functions[self.0]
175            .blocks
176            .insert(block_idx, new_block);
177        Ok(new_block)
178    }
179
180    /// Create and insert a new [`Block`] into this function.
181    ///
182    /// The new block is inserted after `other`.
183    pub fn create_block_after(
184        &self,
185        context: &mut Context,
186        other: &Block,
187        label: Option<Label>,
188    ) -> Result<Block, IrError> {
189        // We need to create the new block first (even though we may not use it on Err below) since
190        // we can't borrow context mutably twice.
191        let new_block = Block::new(context, *self, label);
192        let func = context.functions.get_mut(self.0).unwrap();
193        func.blocks
194            .iter()
195            .position(|block| block == other)
196            .map(|idx| {
197                func.blocks.insert(idx + 1, new_block);
198                new_block
199            })
200            .ok_or_else(|| {
201                let label = &context.blocks[other.0].label;
202                IrError::MissingBlock(label.clone())
203            })
204    }
205
206    /// Remove a [`Block`] from this function.
207    ///
208    /// > Care must be taken to ensure the block has no predecessors otherwise the function will be
209    /// > made invalid.
210    pub fn remove_block(&self, context: &mut Context, block: &Block) -> Result<(), IrError> {
211        let label = block.get_label(context);
212        let func = context.functions.get_mut(self.0).unwrap();
213        let block_idx = func
214            .blocks
215            .iter()
216            .position(|b| b == block)
217            .ok_or(IrError::RemoveMissingBlock(label))?;
218        func.blocks.remove(block_idx);
219        Ok(())
220    }
221
222    /// Remove instructions from function that satisfy a given predicate.
223    pub fn remove_instructions<T: Fn(Value) -> bool>(&self, context: &mut Context, pred: T) {
224        for block in context.functions[self.0].blocks.clone() {
225            block.remove_instructions(context, &pred);
226        }
227    }
228
229    /// Get a new unique block label.
230    ///
231    /// If `hint` is `None` then the label will be in the form `"blockN"` where N is an
232    /// incrementing decimal.
233    ///
234    /// Otherwise if the hint is already unique to this function it will be returned.  If not
235    /// already unique it will have N appended to it until it is unique.
236    pub fn get_unique_label(&self, context: &mut Context, hint: Option<String>) -> String {
237        match hint {
238            Some(hint) => {
239                if context.functions[self.0]
240                    .blocks
241                    .iter()
242                    .any(|block| context.blocks[block.0].label == hint)
243                {
244                    let idx = self.get_next_label_idx(context);
245                    self.get_unique_label(context, Some(format!("{hint}{idx}")))
246                } else {
247                    hint
248                }
249            }
250            None => {
251                let idx = self.get_next_label_idx(context);
252                self.get_unique_label(context, Some(format!("block{idx}")))
253            }
254        }
255    }
256
257    fn get_next_label_idx(&self, context: &mut Context) -> u64 {
258        let func = context.functions.get_mut(self.0).unwrap();
259        let idx = func.next_label_idx;
260        func.next_label_idx += 1;
261        idx
262    }
263
264    /// Return the number of blocks in this function.
265    pub fn num_blocks(&self, context: &Context) -> usize {
266        context.functions[self.0].blocks.len()
267    }
268
269    /// Return the number of instructions in this function.
270    ///
271    /// The [crate::InstOp::AsmBlock] is counted as a single instruction,
272    /// regardless of the number of [crate::asm::AsmInstruction]s in the ASM block.
273    /// E.g., even if the ASM block is empty and contains no instructions, it
274    /// will still be counted as a single instruction.
275    ///
276    /// If you want to count every ASM instruction as an instruction, use
277    /// `num_instructions_incl_asm_instructions` instead.
278    pub fn num_instructions(&self, context: &Context) -> usize {
279        self.block_iter(context)
280            .map(|block| block.num_instructions(context))
281            .sum()
282    }
283
284    /// Return the number of instructions in this function, including
285    /// the [crate::asm::AsmInstruction]s found in [crate::InstOp::AsmBlock]s.
286    ///
287    /// Every [crate::asm::AsmInstruction] encountered in any of the ASM blocks
288    /// will be counted as an instruction. The [crate::InstOp::AsmBlock] itself
289    /// is not counted but rather replaced with the number of ASM instructions
290    /// found in the block. In other words, empty ASM blocks do not count as
291    /// instructions.
292    ///
293    /// If you want to count [crate::InstOp::AsmBlock]s as single instructions, use
294    /// `num_instructions` instead.
295    pub fn num_instructions_incl_asm_instructions(&self, context: &Context) -> usize {
296        self.instruction_iter(context).fold(0, |num, (_, value)| {
297            match &value
298                .get_instruction(context)
299                .expect("We are iterating through the instructions.")
300                .op
301            {
302                InstOp::AsmBlock(asm, _) => num + asm.body.len(),
303                _ => num + 1,
304            }
305        })
306    }
307
308    /// Return the function name.
309    pub fn get_name<'a>(&self, context: &'a Context) -> &'a str {
310        &context.functions[self.0].name
311    }
312
313    /// Return the display string representing the function in the ABI errors
314    /// related context, in the "errorCodes" and "panickingCalls" sections.
315    pub fn get_abi_errors_display(&self, context: &Context) -> String {
316        context.functions[self.0].abi_errors_display.clone()
317    }
318
319    /// Return the module that this function belongs to.
320    pub fn get_module(&self, context: &Context) -> Module {
321        context.functions[self.0].module
322    }
323
324    /// Return the function entry (i.e., the first) block.
325    pub fn get_entry_block(&self, context: &Context) -> Block {
326        context.functions[self.0].blocks[0]
327    }
328
329    /// Return the attached metadata.
330    pub fn get_metadata(&self, context: &Context) -> Option<MetadataIndex> {
331        context.functions[self.0].metadata
332    }
333
334    /// Whether this function has a valid selector.
335    pub fn has_selector(&self, context: &Context) -> bool {
336        context.functions[self.0].selector.is_some()
337    }
338
339    /// Return the function selector, if it has one.
340    pub fn get_selector(&self, context: &Context) -> Option<[u8; 4]> {
341        context.functions[self.0].selector
342    }
343
344    /// Whether or not the function is a program entry point, i.e. `main`, `#[test]` fns or abi
345    /// methods.
346    pub fn is_entry(&self, context: &Context) -> bool {
347        context.functions[self.0].is_entry
348    }
349
350    /// Whether or not the function was a program entry point, i.e. `main`, `#[test]` fns or abi
351    /// methods, before it got wrapped within the `__entry` function.
352    pub fn is_original_entry(&self, context: &Context) -> bool {
353        context.functions[self.0].is_original_entry
354    }
355
356    /// Whether or not this function is a contract fallback function
357    pub fn is_fallback(&self, context: &Context) -> bool {
358        context.functions[self.0].is_fallback
359    }
360
361    // Get the function return type.
362    pub fn get_return_type(&self, context: &Context) -> Type {
363        context.functions[self.0].return_type
364    }
365
366    // Set a new function return type.
367    pub fn set_return_type(&self, context: &mut Context, new_ret_type: Type) {
368        context.functions.get_mut(self.0).unwrap().return_type = new_ret_type
369    }
370
371    /// Get the number of args.
372    pub fn num_args(&self, context: &Context) -> usize {
373        context.functions[self.0].arguments.len()
374    }
375
376    /// Get an arg value by name, if found.
377    pub fn get_arg(&self, context: &Context, name: &str) -> Option<Value> {
378        context.functions[self.0]
379            .arguments
380            .iter()
381            .find_map(|(arg_name, val)| (arg_name == name).then_some(val))
382            .copied()
383    }
384
385    /// Append an extra argument to the function signature.
386    ///
387    /// NOTE: `arg` must be a `BlockArgument` value with the correct index otherwise `add_arg` will
388    /// panic.
389    pub fn add_arg<S: Into<String>>(&self, context: &mut Context, name: S, arg: Value) {
390        match context.values[arg.0].value {
391            ValueDatum::Argument(BlockArgument { idx, .. })
392                if idx == context.functions[self.0].arguments.len() =>
393            {
394                context.functions[self.0].arguments.push((name.into(), arg));
395            }
396            _ => panic!("Inconsistent function argument being added"),
397        }
398    }
399
400    /// Find the name of an arg by value.
401    pub fn lookup_arg_name<'a>(&self, context: &'a Context, value: &Value) -> Option<&'a String> {
402        context.functions[self.0]
403            .arguments
404            .iter()
405            .find_map(|(name, arg_val)| (arg_val == value).then_some(name))
406    }
407
408    /// Return an iterator for each of the function arguments.
409    pub fn args_iter<'a>(&self, context: &'a Context) -> impl Iterator<Item = &'a (String, Value)> {
410        context.functions[self.0].arguments.iter()
411    }
412
413    /// Is argument `i` marked immutable?
414    pub fn is_arg_immutable(&self, context: &Context, i: usize) -> bool {
415        if let Some((_, val)) = context.functions[self.0].arguments.get(i) {
416            if let ValueDatum::Argument(arg) = &context.values[val.0].value {
417                return arg.is_immutable;
418            }
419        }
420        false
421    }
422
423    /// Get a pointer to a local value by name, if found.
424    pub fn get_local_var(&self, context: &Context, name: &str) -> Option<LocalVar> {
425        context.functions[self.0].local_storage.get(name).copied()
426    }
427
428    /// Find the name of a local value by pointer.
429    pub fn lookup_local_name<'a>(
430        &self,
431        context: &'a Context,
432        var: &LocalVar,
433    ) -> Option<&'a String> {
434        context.functions[self.0]
435            .local_storage
436            .iter()
437            .find_map(|(name, local_var)| if local_var == var { Some(name) } else { None })
438    }
439
440    /// Add a value to the function local storage.
441    ///
442    /// The name must be unique to this function else an error is returned.
443    pub fn new_local_var(
444        &self,
445        context: &mut Context,
446        name: String,
447        local_type: Type,
448        initializer: Option<Constant>,
449        mutable: bool,
450    ) -> Result<LocalVar, IrError> {
451        let var = LocalVar::new(context, local_type, initializer, mutable);
452        let func = context.functions.get_mut(self.0).unwrap();
453        func.local_storage
454            .insert(name.clone(), var)
455            .map(|_| Err(IrError::FunctionLocalClobbered(func.name.clone(), name)))
456            .unwrap_or(Ok(var))
457    }
458
459    /// Add a value to the function local storage, by forcing the name to be unique if needed.
460    ///
461    /// Will use the provided name as a hint and rename to guarantee insertion.
462    pub fn new_unique_local_var(
463        &self,
464        context: &mut Context,
465        name: String,
466        local_type: Type,
467        initializer: Option<Constant>,
468        mutable: bool,
469    ) -> LocalVar {
470        let func = &context.functions[self.0];
471        let new_name = if func.local_storage.contains_key(&name) {
472            // Assuming that we'll eventually find a unique name by appending numbers to the old
473            // one...
474            (0..)
475                .find_map(|n| {
476                    let candidate = format!("{name}{n}");
477                    if func.local_storage.contains_key(&candidate) {
478                        None
479                    } else {
480                        Some(candidate)
481                    }
482                })
483                .unwrap()
484        } else {
485            name
486        };
487        self.new_local_var(context, new_name, local_type, initializer, mutable)
488            .unwrap()
489    }
490
491    /// Return an iterator to all of the values in this function's local storage.
492    pub fn locals_iter<'a>(
493        &self,
494        context: &'a Context,
495    ) -> impl Iterator<Item = (&'a String, &'a LocalVar)> {
496        context.functions[self.0].local_storage.iter()
497    }
498
499    /// Remove given list of locals
500    pub fn remove_locals(&self, context: &mut Context, removals: &Vec<String>) {
501        for remove in removals {
502            if let Some(local) = context.functions[self.0].local_storage.remove(remove) {
503                context.local_vars.remove(local.0);
504            }
505        }
506    }
507
508    /// Merge values from another [`Function`] into this one.
509    ///
510    /// The names of the merged values are guaranteed to be unique via the use of
511    /// [`Function::new_unique_local_var`].
512    ///
513    /// Returns a map from the original pointers to the newly merged pointers.
514    pub fn merge_locals_from(
515        &self,
516        context: &mut Context,
517        other: Function,
518    ) -> HashMap<LocalVar, LocalVar> {
519        let mut var_map = HashMap::new();
520        let old_vars: Vec<(String, LocalVar, LocalVarContent)> = context.functions[other.0]
521            .local_storage
522            .iter()
523            .map(|(name, var)| (name.clone(), *var, context.local_vars[var.0].clone()))
524            .collect();
525        for (name, old_var, old_var_content) in old_vars {
526            let old_ty = old_var_content
527                .ptr_ty
528                .get_pointee_type(context)
529                .expect("LocalVar types are always pointers.");
530            let new_var = self.new_unique_local_var(
531                context,
532                name.clone(),
533                old_ty,
534                old_var_content.initializer,
535                old_var_content.mutable,
536            );
537            var_map.insert(old_var, new_var);
538        }
539        var_map
540    }
541
542    /// Return an iterator to each block in this function.
543    pub fn block_iter(&self, context: &Context) -> BlockIterator {
544        BlockIterator::new(context, self)
545    }
546
547    /// Return an iterator to each instruction in each block in this function.
548    ///
549    /// This is a convenience method for when all instructions in a function need to be inspected.
550    /// The instruction value is returned from the iterator along with the block it belongs to.
551    pub fn instruction_iter<'a>(
552        &self,
553        context: &'a Context,
554    ) -> impl Iterator<Item = (Block, Value)> + 'a {
555        context.functions[self.0]
556            .blocks
557            .iter()
558            .flat_map(move |block| {
559                block
560                    .instruction_iter(context)
561                    .map(move |ins_val| (*block, ins_val))
562            })
563    }
564
565    /// Replace a value with another within this function.
566    ///
567    /// This is a convenience method which iterates over this function's blocks and calls
568    /// [`Block::replace_values`] in turn.
569    ///
570    /// `starting_block` is an optimisation for when the first possible reference to `old_val` is
571    /// known.
572    pub fn replace_values(
573        &self,
574        context: &mut Context,
575        replace_map: &FxHashMap<Value, Value>,
576        starting_block: Option<Block>,
577    ) {
578        let mut block_iter = self.block_iter(context).peekable();
579
580        if let Some(ref starting_block) = starting_block {
581            // Skip blocks until we hit the starting block.
582            while block_iter
583                .next_if(|block| block != starting_block)
584                .is_some()
585            {}
586        }
587
588        for block in block_iter {
589            block.replace_values(context, replace_map);
590        }
591    }
592
593    pub fn replace_value(
594        &self,
595        context: &mut Context,
596        old_val: Value,
597        new_val: Value,
598        starting_block: Option<Block>,
599    ) {
600        let mut map = FxHashMap::<Value, Value>::default();
601        map.insert(old_val, new_val);
602        self.replace_values(context, &map, starting_block);
603    }
604
605    /// A graphviz dot graph of the control-flow-graph.
606    pub fn dot_cfg(&self, context: &Context) -> String {
607        let mut worklist = Vec::<Block>::new();
608        let mut visited = FxHashSet::<Block>::default();
609        let entry = self.get_entry_block(context);
610        let mut res = format!("digraph {} {{\n", self.get_name(context));
611
612        worklist.push(entry);
613        while let Some(n) = worklist.pop() {
614            visited.insert(n);
615            for BranchToWithArgs { block: n_succ, .. } in n.successors(context) {
616                let _ = writeln!(
617                    res,
618                    "\t{} -> {}\n",
619                    n.get_label(context),
620                    n_succ.get_label(context)
621                );
622                if !visited.contains(&n_succ) {
623                    worklist.push(n_succ);
624                }
625            }
626        }
627
628        res += "}\n";
629        res
630    }
631}
632
633/// An iterator over each [`Function`] in a [`Module`].
634pub struct FunctionIterator {
635    functions: Vec<slotmap::DefaultKey>,
636    next: usize,
637}
638
639impl FunctionIterator {
640    /// Return a new iterator for the functions in `module`.
641    pub fn new(context: &Context, module: &Module) -> FunctionIterator {
642        // Copy all the current modules indices, so they may be modified in the context during
643        // iteration.
644        FunctionIterator {
645            functions: context.modules[module.0]
646                .functions
647                .iter()
648                .map(|func| func.0)
649                .collect(),
650            next: 0,
651        }
652    }
653}
654
655impl Iterator for FunctionIterator {
656    type Item = Function;
657
658    fn next(&mut self) -> Option<Function> {
659        if self.next < self.functions.len() {
660            let idx = self.next;
661            self.next += 1;
662            Some(Function(self.functions[idx]))
663        } else {
664            None
665        }
666    }
667}