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