sway_ir/analysis/
memory_utils.rs

1//! An analysis to compute symbols that escape out from a function.
2//! This could be into another function, or via `ptr_to_int` etc.
3//! Any transformations involving such symbols are unsafe.
4
5use indexmap::IndexSet;
6use rustc_hash::FxHashSet;
7use sway_types::{FxIndexMap, FxIndexSet};
8
9use crate::{
10    AnalysisResult, AnalysisResultT, AnalysisResults, BlockArgument, Context, FuelVmInstruction,
11    Function, InstOp, Instruction, IrError, LocalVar, Pass, PassMutability, ScopedPass, Type,
12    Value, ValueDatum,
13};
14
15pub const ESCAPED_SYMBOLS_NAME: &str = "escaped-symbols";
16
17pub fn create_escaped_symbols_pass() -> Pass {
18    Pass {
19        name: ESCAPED_SYMBOLS_NAME,
20        descr: "Symbols that escape or cannot be analyzed",
21        deps: vec![],
22        runner: ScopedPass::FunctionPass(PassMutability::Analysis(compute_escaped_symbols_pass)),
23    }
24}
25
26#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash)]
27pub enum Symbol {
28    Local(LocalVar),
29    Arg(BlockArgument),
30}
31
32impl Symbol {
33    pub fn get_type(&self, context: &Context) -> Type {
34        match self {
35            Symbol::Local(l) => l.get_type(context),
36            Symbol::Arg(ba) => ba.ty,
37        }
38    }
39
40    pub fn _get_name(&self, context: &Context, function: Function) -> String {
41        match self {
42            Symbol::Local(l) => function.lookup_local_name(context, l).unwrap().clone(),
43            Symbol::Arg(ba) => format!("{}[{}]", ba.block.get_label(context), ba.idx),
44        }
45    }
46}
47
48/// Get [Symbol]s, both [Symbol::Local]s and [Symbol::Arg]s, reachable
49/// from the `val` via chain of [InstOp::GetElemPtr] (GEP) instructions.
50/// A `val` can, via GEP instructions, refer indirectly to none, or one
51/// or more symbols.
52///
53/// If the `val` is not a pointer, an empty set is returned.
54///
55/// Note that this function does not return [Symbol]s potentially reachable
56/// via referencing (`&`), dereferencing (`*`), and raw pointers (`__addr_of`)
57/// and is thus suitable for all IR analysis and manipulation that deals
58/// strictly with GEP access.
59///
60/// To acquire all [Symbol]s reachable from the `val`, use [get_referred_symbols] instead.
61pub fn get_gep_referred_symbols(context: &Context, val: Value) -> FxIndexSet<Symbol> {
62    match get_symbols(context, val, true) {
63        ReferredSymbols::Complete(symbols) => symbols,
64        _ => unreachable!(
65            "In the case of GEP access, the set of returned symbols is always complete."
66        ),
67    }
68}
69
70/// Provides [Symbol]s, both [Symbol::Local]s and [Symbol::Arg]s, reachable
71/// from a certain [Value] via chain of [InstOp::GetElemPtr] (GEP) instructions
72/// or via [InstOp::IntToPtr] and [InstOp::PtrToInt] instruction patterns
73/// specific to references, both referencing (`&`) and dereferencing (`*`),
74/// and raw pointers, via `__addr_of`.
75pub enum ReferredSymbols {
76    /// Guarantees that all [Symbol]s reachable from the particular [Value]
77    /// are collected, thus, that there are no escapes or pointer accesses
78    /// in the scope that _might_ result in symbols indirectly related to
79    /// the [Value] but not reachable only via GEP, or references, or
80    /// raw pointers only.
81    Complete(FxIndexSet<Symbol>),
82    /// Denotes that there _might_ be [Symbol]s out of returned ones that
83    /// are related to the particular [Value], but not reachable only via GEP,
84    /// or references, or raw pointers.
85    Incomplete(FxIndexSet<Symbol>),
86}
87
88impl ReferredSymbols {
89    pub fn new(is_complete: bool, symbols: FxIndexSet<Symbol>) -> Self {
90        if is_complete {
91            Self::Complete(symbols)
92        } else {
93            Self::Incomplete(symbols)
94        }
95    }
96
97    /// Returns the referred [Symbol]s and the information if they are
98    /// complete (true) or incomplete (false).
99    pub fn consume(self) -> (bool, FxIndexSet<Symbol>) {
100        let is_complete = matches!(self, ReferredSymbols::Complete(_));
101        let syms = match self {
102            ReferredSymbols::Complete(syms) | ReferredSymbols::Incomplete(syms) => syms,
103        };
104
105        (is_complete, syms)
106    }
107}
108
109/// Get [Symbol]s, both [Symbol::Local]s and [Symbol::Arg]s, reachable
110/// from the `val` via chain of [InstOp::GetElemPtr] (GEP) instructions
111/// or via [InstOp::IntToPtr] and [InstOp::PtrToInt] instruction patterns
112/// specific to references, both referencing (`&`) and dereferencing (`*`),
113/// and raw pointers, via `__addr_of`.
114/// A `val` can, via these instructions, refer indirectly to none, or one
115/// or more symbols.
116///
117/// Note that *this function does not perform any escape analysis*. E.g., if a
118/// local symbol gets passed by `raw_ptr` or `&T` to a function and returned
119/// back from the function via the same `raw_ptr` or `&T` the value returned
120/// from the function will not be tracked back to the original symbol and the
121/// symbol will not be collected as referred.
122///
123/// This means that, even if the result contains [Symbol]s, it _might_ be that
124/// there are still other [Symbol]s in scope related to the `val`. E.g., in case
125/// of branching, where the first branch directly returns `& local_var_a`
126/// and the second branch, indirectly over a function call as explained above,
127/// `& local_var_b`, only the `local_var_a` will be returned as a result.
128///
129/// Therefore, the function returns the [ReferredSymbols] enum to denote
130/// if the returned set of symbols is guaranteed to be complete, or if it is
131/// incomplete.
132///
133/// If the `val` is not a pointer, an empty set is returned and marked as
134/// [ReferredSymbols::Complete].
135pub fn get_referred_symbols(context: &Context, val: Value) -> ReferredSymbols {
136    get_symbols(context, val, false)
137}
138
139/// Get [Symbol]s, both [Symbol::Local]s and [Symbol::Arg]s, reachable
140/// from the `val`.
141///
142/// If `gep_only` is `true` only the [Symbol]s reachable via GEP instructions
143/// are returned. Otherwise, the result also contains [Symbol]s reachable
144/// via referencing (`&`) and dereferencing (`*`).
145///
146/// If the `val` is not a pointer, an empty set is returned and marked as
147/// [ReferredSymbols::Complete].
148fn get_symbols(context: &Context, val: Value, gep_only: bool) -> ReferredSymbols {
149    // The input to this recursive function is always a pointer.
150    // The function tracks backwards where the pointer is coming from.
151    fn get_symbols_rec(
152        context: &Context,
153        symbols: &mut FxIndexSet<Symbol>,
154        visited: &mut FxHashSet<Value>,
155        ptr: Value,
156        gep_only: bool,
157        is_complete: &mut bool,
158    ) {
159        fn get_argument_symbols(
160            context: &Context,
161            symbols: &mut FxIndexSet<Symbol>,
162            visited: &mut FxHashSet<Value>,
163            arg: BlockArgument,
164            gep_only: bool,
165            is_complete: &mut bool,
166        ) {
167            if arg.block.get_label(context) == "entry" {
168                symbols.insert(Symbol::Arg(arg));
169            } else {
170                arg.block
171                    .pred_iter(context)
172                    .map(|pred| arg.get_val_coming_from(context, pred).unwrap())
173                    .for_each(|v| {
174                        get_symbols_rec(context, symbols, visited, v, gep_only, is_complete)
175                    })
176            }
177        }
178
179        fn get_symbols_from_u64_address_argument(
180            context: &Context,
181            symbols: &mut FxIndexSet<Symbol>,
182            visited: &mut FxHashSet<Value>,
183            u64_address_arg: BlockArgument,
184            is_complete: &mut bool,
185        ) {
186            if u64_address_arg.block.get_label(context) == "entry" {
187                // The u64 address is coming from a function argument.
188                // Same as in the case of a pointer coming from a function argument,
189                // we collect it.
190                symbols.insert(Symbol::Arg(u64_address_arg));
191            } else {
192                u64_address_arg
193                    .block
194                    .pred_iter(context)
195                    .map(|pred| u64_address_arg.get_val_coming_from(context, pred).unwrap())
196                    .for_each(|v| {
197                        get_symbols_from_u64_address_rec(context, symbols, visited, v, is_complete)
198                    })
199            }
200        }
201
202        // The input to this recursive function is always a `u64` holding an address.
203        // The below chain of instructions are specific to patterns where pointers
204        // are obtained from `u64` addresses and vice versa. This includes:
205        //  - referencing and dereferencing
206        //  - raw pointers (`__addr_of`)
207        //  - GTF intrinsic
208        fn get_symbols_from_u64_address_rec(
209            context: &Context,
210            symbols: &mut FxIndexSet<Symbol>,
211            visited: &mut FxHashSet<Value>,
212            u64_address: Value,
213            is_complete: &mut bool,
214        ) {
215            match context.values[u64_address.0].value {
216                // Follow the sources of the address, and for every source address,
217                // recursively come back to this function.
218                ValueDatum::Argument(arg) => get_symbols_from_u64_address_argument(
219                    context,
220                    symbols,
221                    visited,
222                    arg,
223                    is_complete,
224                ),
225                // 1. Patterns related to references and raw pointers.
226                ValueDatum::Instruction(Instruction {
227                    // The address is coming from a `raw_pointer` or `&T` variable.
228                    op: InstOp::Load(_loaded_from),
229                    ..
230                }) => {
231                    // TODO: https://github.com/FuelLabs/sway/issues/6065
232                    //       We want to track sources of loaded addresses.
233                    //       Currently we don't and simply mark the result as incomplete.
234                    *is_complete = false;
235                }
236                ValueDatum::Instruction(Instruction {
237                    op: InstOp::PtrToInt(ptr_value, _),
238                    ..
239                }) => get_symbols_rec(context, symbols, visited, ptr_value, false, is_complete),
240                // 2. The address is coming from a GTF instruction.
241                ValueDatum::Instruction(Instruction {
242                    // There cannot be a symbol behind it, and so the returned set is complete.
243                    op: InstOp::FuelVm(FuelVmInstruction::Gtf { .. }),
244                    ..
245                }) => (),
246                // In other cases, e.g., getting the integer address from an unsafe pointer
247                // arithmetic, or as a function result, etc. we bail out and mark the
248                // collection as not being guaranteed to be a complete set of all referred symbols.
249                _ => {
250                    *is_complete = false;
251                }
252            }
253        }
254
255        if visited.contains(&ptr) {
256            return;
257        }
258        visited.insert(ptr);
259        match context.values[ptr.0].value {
260            ValueDatum::Instruction(Instruction {
261                op: InstOp::GetLocal(local),
262                ..
263            }) => {
264                symbols.insert(Symbol::Local(local));
265            }
266            ValueDatum::Instruction(Instruction {
267                op: InstOp::GetElemPtr { base, .. },
268                ..
269            }) => get_symbols_rec(context, symbols, visited, base, gep_only, is_complete),
270            ValueDatum::Instruction(Instruction {
271                op: InstOp::IntToPtr(u64_address, _),
272                ..
273            }) if !gep_only => get_symbols_from_u64_address_rec(
274                context,
275                symbols,
276                visited,
277                u64_address,
278                is_complete,
279            ),
280            // We've reached a configurable at the top of the chain.
281            // There cannot be a symbol behind it, and so the returned set is complete.
282            ValueDatum::Instruction(Instruction {
283                op: InstOp::GetConfig(_, _),
284                ..
285            }) if !gep_only => (),
286            // We've reached a global at the top of the chain.
287            // There cannot be a symbol behind it, and so the returned set is complete.
288            ValueDatum::Instruction(Instruction {
289                op: InstOp::GetGlobal(_),
290                ..
291            }) if !gep_only => (),
292            // We've reached a storage key at the top of the chain.
293            // There cannot be a symbol behind it, and so the returned set is complete.
294            ValueDatum::Instruction(Instruction {
295                op: InstOp::GetStorageKey(_),
296                ..
297            }) if !gep_only => (),
298            // Note that in this case, the pointer itself is coming from a `Load`,
299            // and not an address. So, we just continue following the pointer.
300            ValueDatum::Instruction(Instruction {
301                op: InstOp::Load(loaded_from),
302                ..
303            }) if !gep_only => get_symbols_rec(
304                context,
305                symbols,
306                visited,
307                loaded_from,
308                gep_only,
309                is_complete,
310            ),
311            ValueDatum::Instruction(Instruction {
312                op: InstOp::CastPtr(ptr_to_cast, _),
313                ..
314            }) if !gep_only => get_symbols_rec(
315                context,
316                symbols,
317                visited,
318                ptr_to_cast,
319                gep_only,
320                is_complete,
321            ),
322            ValueDatum::Argument(arg) => {
323                get_argument_symbols(context, symbols, visited, arg, gep_only, is_complete)
324            }
325            // We've reached a constant at the top of the chain.
326            // There cannot be a symbol behind it, and so the returned set is complete.
327            ValueDatum::Constant(_) if !gep_only => (),
328            _ if !gep_only => {
329                // In other cases, e.g., getting the pointer from an ASM block,
330                // or as a function result, etc., we cannot track the value up the chain
331                // and cannot guarantee that the value is not coming from some of the symbols.
332                // So, we bail out and mark the collection as not being guaranteed to be
333                // a complete set of all referred symbols.
334                *is_complete = false;
335            }
336            // In the case of GEP only access, the returned set is always complete.
337            _ => (),
338        }
339    }
340
341    if !val.get_type(context).is_some_and(|t| t.is_ptr(context)) {
342        return ReferredSymbols::new(true, IndexSet::default());
343    }
344
345    let mut visited = FxHashSet::default();
346    let mut symbols = IndexSet::default();
347    let mut is_complete = true;
348
349    get_symbols_rec(
350        context,
351        &mut symbols,
352        &mut visited,
353        val,
354        gep_only,
355        &mut is_complete,
356    );
357
358    ReferredSymbols::new(is_complete, symbols)
359}
360
361pub fn get_gep_symbol(context: &Context, val: Value) -> Option<Symbol> {
362    let syms = get_gep_referred_symbols(context, val);
363    (syms.len() == 1)
364        .then(|| syms.iter().next().cloned())
365        .flatten()
366}
367
368/// Return [Symbol] referred by `val` if there is _exactly one_ symbol referred,
369/// or `None` if there are no [Symbol]s referred or if there is more than one
370/// referred.
371pub fn get_referred_symbol(context: &Context, val: Value) -> Option<Symbol> {
372    let syms = get_referred_symbols(context, val);
373    match syms {
374        ReferredSymbols::Complete(syms) => (syms.len() == 1)
375            .then(|| syms.iter().next().cloned())
376            .flatten(),
377        // It might be that we have more than one referred symbol here.
378        ReferredSymbols::Incomplete(_) => None,
379    }
380}
381
382pub enum EscapedSymbols {
383    /// Guarantees that all escaping [Symbol]s are collected.
384    Complete(FxHashSet<Symbol>),
385    /// Denotes that there _might_ be additional escaping [Symbol]s
386    /// out of the collected ones.
387    Incomplete(FxHashSet<Symbol>),
388}
389
390impl AnalysisResultT for EscapedSymbols {}
391
392pub fn compute_escaped_symbols_pass(
393    context: &Context,
394    _analyses: &AnalysisResults,
395    function: Function,
396) -> Result<AnalysisResult, IrError> {
397    Ok(Box::new(compute_escaped_symbols(context, &function)))
398}
399
400fn compute_escaped_symbols(context: &Context, function: &Function) -> EscapedSymbols {
401    let add_from_val = |result: &mut FxHashSet<Symbol>, val: &Value, is_complete: &mut bool| {
402        let (complete, syms) = get_referred_symbols(context, *val).consume();
403
404        *is_complete &= complete;
405
406        syms.iter().for_each(|s| {
407            result.insert(*s);
408        });
409    };
410
411    let mut result = FxHashSet::default();
412    let mut is_complete = true;
413
414    for (_block, inst) in function.instruction_iter(context) {
415        match &inst.get_instruction(context).unwrap().op {
416            InstOp::AsmBlock(_, args) => {
417                for arg_init in args.iter().filter_map(|arg| arg.initializer) {
418                    add_from_val(&mut result, &arg_init, &mut is_complete)
419                }
420            }
421            InstOp::UnaryOp { .. } => (),
422            InstOp::BinaryOp { .. } => (),
423            InstOp::BitCast(_, _) => (),
424            InstOp::Branch(_) => (),
425            InstOp::Call(callee, args) => args
426                .iter()
427                .enumerate()
428                .filter(|(arg_idx, _arg)| {
429                    // Immutable arguments are not considered as escaping symbols.
430                    !callee.is_arg_immutable(context, *arg_idx)
431                })
432                .for_each(|(_, v)| add_from_val(&mut result, v, &mut is_complete)),
433            InstOp::CastPtr(ptr, _) => add_from_val(&mut result, ptr, &mut is_complete),
434            InstOp::Cmp(_, _, _) => (),
435            InstOp::ConditionalBranch { .. } => (),
436            InstOp::ContractCall { params, .. } => {
437                add_from_val(&mut result, params, &mut is_complete)
438            }
439            InstOp::FuelVm(_) => (),
440            InstOp::GetLocal(_) => (),
441            InstOp::GetGlobal(_) => (),
442            InstOp::GetConfig(_, _) => (),
443            InstOp::GetStorageKey(_) => (),
444            InstOp::GetElemPtr { .. } => (),
445            InstOp::IntToPtr(_, _) => (),
446            InstOp::Load(_) => (),
447            InstOp::MemCopyBytes { .. } => (),
448            InstOp::MemCopyVal { .. } => (),
449            InstOp::MemClearVal { .. } => (),
450            InstOp::Nop => (),
451            InstOp::PtrToInt(v, _) => add_from_val(&mut result, v, &mut is_complete),
452            InstOp::Ret(_, _) => (),
453            InstOp::Store { stored_val, .. } => {
454                add_from_val(&mut result, stored_val, &mut is_complete)
455            }
456        }
457    }
458
459    if is_complete {
460        EscapedSymbols::Complete(result)
461    } else {
462        EscapedSymbols::Incomplete(result)
463    }
464}
465
466/// Pointers that may possibly be loaded from the instruction `inst`.
467pub fn get_loaded_ptr_values(context: &Context, inst: Value) -> Vec<Value> {
468    match &inst.get_instruction(context).unwrap().op {
469        InstOp::UnaryOp { .. }
470        | InstOp::BinaryOp { .. }
471        | InstOp::BitCast(_, _)
472        | InstOp::Branch(_)
473        | InstOp::ConditionalBranch { .. }
474        | InstOp::Cmp(_, _, _)
475        | InstOp::Nop
476        | InstOp::CastPtr(_, _)
477        | InstOp::GetLocal(_)
478        | InstOp::GetGlobal(_)
479        | InstOp::GetConfig(_, _)
480        | InstOp::GetStorageKey(_)
481        | InstOp::GetElemPtr { .. }
482        | InstOp::IntToPtr(_, _) => vec![],
483        InstOp::PtrToInt(src_val_ptr, _) => vec![*src_val_ptr],
484        InstOp::ContractCall {
485            params,
486            coins,
487            asset_id,
488            ..
489        } => vec![*params, *coins, *asset_id],
490        InstOp::Call(_, args) => args.clone(),
491        InstOp::AsmBlock(_, args) => args.iter().filter_map(|val| val.initializer).collect(),
492        InstOp::MemClearVal { .. } => vec![],
493        InstOp::MemCopyBytes { src_val_ptr, .. }
494        | InstOp::MemCopyVal { src_val_ptr, .. }
495        | InstOp::Ret(src_val_ptr, _)
496        | InstOp::Load(src_val_ptr)
497        | InstOp::FuelVm(FuelVmInstruction::Log {
498            log_val: src_val_ptr,
499            ..
500        })
501        | InstOp::FuelVm(FuelVmInstruction::StateLoadWord(src_val_ptr))
502        | InstOp::FuelVm(FuelVmInstruction::StateStoreWord {
503            key: src_val_ptr, ..
504        })
505        | InstOp::FuelVm(FuelVmInstruction::StateLoadQuadWord {
506            key: src_val_ptr, ..
507        })
508        | InstOp::FuelVm(FuelVmInstruction::StateClear {
509            key: src_val_ptr, ..
510        }) => vec![*src_val_ptr],
511        InstOp::FuelVm(FuelVmInstruction::StateStoreQuadWord {
512            stored_val: memopd1,
513            key: memopd2,
514            ..
515        })
516        | InstOp::FuelVm(FuelVmInstruction::Smo {
517            recipient: memopd1,
518            message: memopd2,
519            ..
520        }) => vec![*memopd1, *memopd2],
521        InstOp::Store { dst_val_ptr: _, .. } => vec![],
522        InstOp::FuelVm(FuelVmInstruction::Gtf { .. })
523        | InstOp::FuelVm(FuelVmInstruction::ReadRegister(_))
524        | InstOp::FuelVm(FuelVmInstruction::Revert(_) | FuelVmInstruction::JmpMem) => vec![],
525        InstOp::FuelVm(FuelVmInstruction::WideUnaryOp { arg, .. }) => vec![*arg],
526        InstOp::FuelVm(FuelVmInstruction::WideBinaryOp { arg1, arg2, .. })
527        | InstOp::FuelVm(FuelVmInstruction::WideCmpOp { arg1, arg2, .. }) => {
528            vec![*arg1, *arg2]
529        }
530        InstOp::FuelVm(FuelVmInstruction::WideModularOp {
531            arg1, arg2, arg3, ..
532        }) => vec![*arg1, *arg2, *arg3],
533        InstOp::FuelVm(FuelVmInstruction::Retd { ptr, .. }) => vec![*ptr],
534    }
535}
536
537/// [Symbol]s that may possibly, directly or indirectly, be loaded from the instruction `inst`.
538pub fn get_loaded_symbols(context: &Context, inst: Value) -> ReferredSymbols {
539    let mut res = IndexSet::default();
540    let mut is_complete = true;
541    for val in get_loaded_ptr_values(context, inst) {
542        let (complete, syms) = get_referred_symbols(context, val).consume();
543
544        is_complete &= complete;
545
546        for sym in syms {
547            res.insert(sym);
548        }
549    }
550
551    ReferredSymbols::new(is_complete, res)
552}
553
554/// Pointers that may possibly be stored to the instruction `inst`.
555pub fn get_stored_ptr_values(context: &Context, inst: Value) -> Vec<Value> {
556    match &inst.get_instruction(context).unwrap().op {
557        InstOp::UnaryOp { .. }
558        | InstOp::BinaryOp { .. }
559        | InstOp::BitCast(_, _)
560        | InstOp::Branch(_)
561        | InstOp::ConditionalBranch { .. }
562        | InstOp::Cmp(_, _, _)
563        | InstOp::Nop
564        | InstOp::PtrToInt(_, _)
565        | InstOp::Ret(_, _)
566        | InstOp::CastPtr(_, _)
567        | InstOp::GetLocal(_)
568        | InstOp::GetGlobal(_)
569        | InstOp::GetConfig(_, _)
570        | InstOp::GetStorageKey(_)
571        | InstOp::GetElemPtr { .. }
572        | InstOp::IntToPtr(_, _) => vec![],
573        InstOp::ContractCall { params, .. } => vec![*params],
574        InstOp::Call(_, args) => args.clone(),
575        InstOp::AsmBlock(_, args) => args.iter().filter_map(|val| val.initializer).collect(),
576        InstOp::MemCopyBytes { dst_val_ptr, .. }
577        | InstOp::MemCopyVal { dst_val_ptr, .. }
578        | InstOp::MemClearVal { dst_val_ptr }
579        | InstOp::Store { dst_val_ptr, .. } => vec![*dst_val_ptr],
580        InstOp::Load(_) => vec![],
581        InstOp::FuelVm(vmop) => match vmop {
582            FuelVmInstruction::Gtf { .. }
583            | FuelVmInstruction::Log { .. }
584            | FuelVmInstruction::ReadRegister(_)
585            | FuelVmInstruction::Revert(_)
586            | FuelVmInstruction::JmpMem
587            | FuelVmInstruction::Smo { .. }
588            | FuelVmInstruction::StateClear { .. } => vec![],
589            FuelVmInstruction::StateLoadQuadWord { load_val, .. } => vec![*load_val],
590            FuelVmInstruction::StateLoadWord(_) | FuelVmInstruction::StateStoreWord { .. } => {
591                vec![]
592            }
593            FuelVmInstruction::StateStoreQuadWord { stored_val: _, .. } => vec![],
594            FuelVmInstruction::WideUnaryOp { result, .. }
595            | FuelVmInstruction::WideBinaryOp { result, .. }
596            | FuelVmInstruction::WideModularOp { result, .. } => vec![*result],
597            FuelVmInstruction::WideCmpOp { .. } => vec![],
598            _ => vec![],
599        },
600    }
601}
602
603/// [Symbol]s that may possibly, directly or indirectly, be stored to the instruction `inst`.
604pub fn get_stored_symbols(context: &Context, inst: Value) -> ReferredSymbols {
605    let mut res = IndexSet::default();
606    let mut is_complete = true;
607    for val in get_stored_ptr_values(context, inst) {
608        let (complete, syms) = get_referred_symbols(context, val).consume();
609
610        is_complete &= complete;
611
612        for sym in syms {
613            res.insert(sym);
614        }
615    }
616
617    ReferredSymbols::new(is_complete, res)
618}
619
620/// Combine a series of GEPs into one.
621pub fn combine_indices(context: &Context, val: Value) -> Option<Vec<Value>> {
622    match &context.values[val.0].value {
623        ValueDatum::Instruction(Instruction {
624            op: InstOp::GetLocal(_),
625            ..
626        }) => Some(vec![]),
627        ValueDatum::Instruction(Instruction {
628            op:
629                InstOp::GetElemPtr {
630                    base,
631                    elem_ptr_ty: _,
632                    indices,
633                },
634            ..
635        }) => {
636            let mut base_indices = combine_indices(context, *base)?;
637            base_indices.append(&mut indices.clone());
638            Some(base_indices)
639        }
640        ValueDatum::Argument(_) => Some(vec![]),
641        _ => None,
642    }
643}
644
645/// Given a memory pointer instruction, compute the offset of indexed element,
646/// for each symbol that it may alias to.
647/// If for any symbol we can't compute it, return None.
648pub fn get_memory_offsets(context: &Context, val: Value) -> Option<FxIndexMap<Symbol, u64>> {
649    let syms = get_gep_referred_symbols(context, val);
650
651    let mut res: FxIndexMap<Symbol, u64> = FxIndexMap::default();
652    for sym in syms {
653        let offset = sym
654            .get_type(context)
655            .get_pointee_type(context)?
656            .get_value_indexed_offset(context, &combine_indices(context, val)?)?;
657        res.insert(sym, offset);
658    }
659    Some(res)
660}
661
662/// Can memory ranges [val1, val1+len1] and [val2, val2+len2] overlap?
663/// Conservatively returns true if cannot statically determine.
664pub fn may_alias(context: &Context, val1: Value, len1: u64, val2: Value, len2: u64) -> bool {
665    let (Some(mem_offsets_1), Some(mem_offsets_2)) = (
666        get_memory_offsets(context, val1),
667        get_memory_offsets(context, val2),
668    ) else {
669        return true;
670    };
671
672    for (sym1, off1) in mem_offsets_1 {
673        if let Some(off2) = mem_offsets_2.get(&sym1) {
674            // does off1 + len1 overlap with off2 + len2?
675            if (off1 <= *off2 && (off1 + len1 > *off2)) || (*off2 <= off1 && (*off2 + len2 > off1))
676            {
677                return true;
678            }
679        }
680    }
681    false
682}
683
684/// Are memory ranges [val1, val1+len1] and [val2, val2+len2] exactly the same?
685/// Conservatively returns false if cannot statically determine.
686pub fn must_alias(context: &Context, val1: Value, len1: u64, val2: Value, len2: u64) -> bool {
687    let (Some(mem_offsets_1), Some(mem_offsets_2)) = (
688        get_memory_offsets(context, val1),
689        get_memory_offsets(context, val2),
690    ) else {
691        return false;
692    };
693
694    if mem_offsets_1.len() != 1 || mem_offsets_2.len() != 1 {
695        return false;
696    }
697
698    let (sym1, off1) = mem_offsets_1.iter().next().unwrap();
699    let (sym2, off2) = mem_offsets_2.iter().next().unwrap();
700
701    // does off1 + len1 overlap with off2 + len2?
702    sym1 == sym2 && off1 == off2 && len1 == len2
703}
704
705/// For a pointer argument `ptr_val`, what's the size of its pointee.
706pub fn pointee_size(context: &Context, ptr_val: Value) -> u64 {
707    ptr_val
708        .get_type(context)
709        .unwrap()
710        .get_pointee_type(context)
711        .expect("Expected arg to be a pointer")
712        .size(context)
713        .in_bytes()
714}