sway_ir/optimize/
misc_demotion.rs

1use std::ops::Not;
2
3/// Miscellaneous value demotion.
4///
5/// This pass demotes miscellaneous 'by-value' types to 'by-reference' pointer types, based on
6/// target specific parameters.
7///
8/// Current special cases are:
9/// - log arguments: These can be any type and should be demoted to pointers if possible.
10/// - Fuel ASM block arguments: These are assumed to be pointers for 'by-reference' values.
11/// - Fuel ASM block return values: These are also assumed to be pointers for 'by-reference'
12///   values.
13/// - Fuel Wide binary operators: Demote binary operands bigger than 64 bits.
14use crate::{
15    asm::AsmArg, AnalysisResults, BinaryOpKind, Constant, ConstantContent, Context,
16    FuelVmInstruction, Function, InstOp, InstructionInserter, IrError, Pass, PassMutability,
17    Predicate, ScopedPass, Type, UnaryOpKind, Value,
18};
19
20use rustc_hash::FxHashMap;
21
22pub const MISC_DEMOTION_NAME: &str = "misc-demotion";
23
24pub fn create_misc_demotion_pass() -> Pass {
25    Pass {
26        name: MISC_DEMOTION_NAME,
27        descr: "Miscellaneous by-value demotions to by-reference",
28        deps: Vec::new(),
29        runner: ScopedPass::FunctionPass(PassMutability::Transform(misc_demotion)),
30    }
31}
32
33pub fn misc_demotion(
34    context: &mut Context,
35    _: &AnalysisResults,
36    function: Function,
37) -> Result<bool, IrError> {
38    let log_res = log_demotion(context, function)?;
39    let asm_arg_res = asm_block_arg_demotion(context, function)?;
40    let asm_ret_res = asm_block_ret_demotion(context, function)?;
41    let addrof_res = ptr_to_int_demotion(context, function)?;
42
43    let wide_binary_op_res = wide_binary_op_demotion(context, function)?;
44    let wide_shifts_op_res = wide_shift_op_demotion(context, function)?;
45    let wide_cmp_res = wide_cmp_demotion(context, function)?;
46    let wide_unary_op_res = wide_unary_op_demotion(context, function)?;
47
48    Ok(log_res
49        || asm_arg_res
50        || asm_ret_res
51        || addrof_res
52        || wide_unary_op_res
53        || wide_binary_op_res
54        || wide_shifts_op_res
55        || wide_cmp_res)
56}
57
58fn log_demotion(context: &mut Context, function: Function) -> Result<bool, IrError> {
59    // Find all log instructions.
60    let candidates = function
61        .instruction_iter(context)
62        .filter_map(|(block, instr_val)| {
63            instr_val.get_instruction(context).and_then(|instr| {
64                // Is the instruction a Log?
65                if let InstOp::FuelVm(FuelVmInstruction::Log {
66                    log_val,
67                    log_ty,
68                    log_id,
69                }) = instr.op
70                {
71                    super::target_fuel::is_demotable_type(context, &log_ty)
72                        .then_some((block, instr_val, log_val, log_ty, log_id))
73                } else {
74                    None
75                }
76            })
77        })
78        .collect::<Vec<_>>();
79
80    if candidates.is_empty() {
81        return Ok(false);
82    }
83
84    // Take the logged value, store it in a temporary local, and replace it with its pointer in the
85    // log instruction.
86    for (block, log_instr_val, logged_val, logged_ty, log_id_val) in candidates {
87        // Create a variable for the arg, a get_local for it and a store.
88        let loc_var =
89            function.new_unique_local_var(context, "__log_arg".to_owned(), logged_ty, None, false);
90        let get_loc_val = Value::new_instruction(context, block, InstOp::GetLocal(loc_var));
91        let store_val = Value::new_instruction(
92            context,
93            block,
94            InstOp::Store {
95                dst_val_ptr: get_loc_val,
96                stored_val: logged_val,
97            },
98        );
99
100        // We need to replace the log instruction because we're changing the type to a pointer.
101        let ptr_ty = Type::new_ptr(context, logged_ty);
102        let new_log_instr_val = Value::new_instruction(
103            context,
104            block,
105            InstOp::FuelVm(FuelVmInstruction::Log {
106                log_val: get_loc_val,
107                log_ty: ptr_ty,
108                log_id: log_id_val,
109            }),
110        );
111
112        // NOTE: We don't need to replace the uses of the old log instruction as it doesn't return a
113        // value.  (It's a 'statement' rather than an 'expression'.)
114        block
115            .replace_instruction(context, log_instr_val, new_log_instr_val, false)
116            .unwrap();
117
118        // Put these two _before_ it.
119        let mut inserter = InstructionInserter::new(
120            context,
121            block,
122            crate::InsertionPosition::Before(new_log_instr_val),
123        );
124        inserter.insert_slice(&[get_loc_val, store_val]);
125    }
126
127    Ok(true)
128}
129
130fn asm_block_arg_demotion(context: &mut Context, function: Function) -> Result<bool, IrError> {
131    // Gather the ASM blocks with reference type args.
132    let candidates = function
133        .instruction_iter(context)
134        .filter_map(|(block, instr_val)| {
135            instr_val.get_instruction(context).and_then(|instr| {
136                // Is the instruction an ASM block?
137                if let InstOp::AsmBlock(_asm_block, args) = &instr.op {
138                    let ref_args = args
139                        .iter()
140                        .filter_map(
141                            |AsmArg {
142                                 name: _,
143                                 initializer,
144                             }| {
145                                initializer.and_then(|init_val| {
146                                    init_val.get_type(context).and_then(|ty| {
147                                        super::target_fuel::is_demotable_type(context, &ty)
148                                            .then_some((init_val, ty))
149                                    })
150                                })
151                            },
152                        )
153                        .collect::<Vec<_>>();
154
155                    (!ref_args.is_empty()).then_some((block, instr_val, ref_args))
156                } else {
157                    None
158                }
159            })
160        })
161        .collect::<Vec<_>>();
162
163    if candidates.is_empty() {
164        return Ok(false);
165    }
166
167    for (block, asm_block_instr_val, ref_args) in candidates {
168        let mut replace_map = FxHashMap::default();
169        let mut temporaries = Vec::new();
170
171        for (ref_arg_val, ref_arg_ty) in ref_args {
172            // Create temporaries for each of the by-reference args.
173            let loc_var = function.new_unique_local_var(
174                context,
175                "__asm_arg".to_owned(),
176                ref_arg_ty,
177                None,
178                false,
179            );
180
181            // Create `get_local`s and `store`s for each one.
182            let get_loc_val = Value::new_instruction(context, block, InstOp::GetLocal(loc_var));
183            let store_val = Value::new_instruction(
184                context,
185                block,
186                InstOp::Store {
187                    dst_val_ptr: get_loc_val,
188                    stored_val: ref_arg_val,
189                },
190            );
191
192            replace_map.insert(ref_arg_val, get_loc_val);
193            temporaries.push(get_loc_val);
194            temporaries.push(store_val);
195        }
196
197        // Insert the temporaries into the block.
198        let mut inserter = InstructionInserter::new(
199            context,
200            block,
201            crate::InsertionPosition::Before(asm_block_instr_val),
202        );
203        inserter.insert_slice(&temporaries);
204
205        // Replace the args with the `get_local`s in the ASM block.
206        asm_block_instr_val.replace_instruction_values(context, &replace_map);
207    }
208
209    Ok(true)
210}
211
212fn asm_block_ret_demotion(context: &mut Context, function: Function) -> Result<bool, IrError> {
213    // Gather the ASM blocks which return a reference type.
214    let candidates = function
215        .instruction_iter(context)
216        .filter_map(|(block, instr_val)| {
217            instr_val.get_instruction(context).and_then(|instr| {
218                // Is the instruction an ASM block?
219                if let InstOp::AsmBlock(asm_block, args) = &instr.op {
220                    let ret_ty = asm_block.return_type;
221                    super::target_fuel::is_demotable_type(context, &ret_ty).then_some((
222                        block,
223                        instr_val,
224                        asm_block.clone(),
225                        args.clone(),
226                        ret_ty,
227                    ))
228                } else {
229                    None
230                }
231            })
232        })
233        .collect::<Vec<_>>();
234
235    if candidates.is_empty() {
236        return Ok(false);
237    }
238
239    let mut replace_map = FxHashMap::default();
240    for (block, asm_block_instr_val, mut asm_block, asm_args, ret_ty) in candidates {
241        // Change the ASM block return type to be a pointer.
242        let ret_ptr_ty = Type::new_ptr(context, ret_ty);
243        asm_block.return_type = ret_ptr_ty;
244        let new_asm_block =
245            Value::new_instruction(context, block, InstOp::AsmBlock(asm_block, asm_args));
246
247        // Insert a load after the block.
248        let load_val = Value::new_instruction(context, block, InstOp::Load(new_asm_block));
249        block
250            .replace_instruction(context, asm_block_instr_val, new_asm_block, false)
251            .unwrap();
252        let mut inserter = InstructionInserter::new(
253            context,
254            block,
255            crate::InsertionPosition::After(new_asm_block),
256        );
257        inserter.insert(load_val);
258
259        // Replace uses of the old ASM block with the new load.
260        replace_map.insert(asm_block_instr_val, load_val);
261    }
262    function.replace_values(context, &replace_map, None);
263
264    Ok(true)
265}
266
267fn ptr_to_int_demotion(context: &mut Context, function: Function) -> Result<bool, IrError> {
268    // Find all ptr_to_int instructions, which are generated by the __addr_of() intrinsic.
269    let candidates = function
270        .instruction_iter(context)
271        .filter_map(|(block, instr_val)| {
272            instr_val.get_instruction(context).and_then(|instr| {
273                // Is the instruction a PtrToInt?
274                if let InstOp::PtrToInt(ptr_val, _int_ty) = instr.op {
275                    ptr_val.get_type(context).and_then(|ptr_ty| {
276                        super::target_fuel::is_demotable_type(context, &ptr_ty)
277                            .then_some((block, instr_val, ptr_val, ptr_ty))
278                    })
279                } else {
280                    None
281                }
282            })
283        })
284        .collect::<Vec<_>>();
285
286    if candidates.is_empty() {
287        return Ok(false);
288    }
289
290    for (block, ptr_to_int_instr_val, ptr_val, ptr_ty) in candidates {
291        // If the ptr_val is a load from a memory location, we can just refer to that.
292        if let Some(instr) = ptr_val.get_instruction(context) {
293            if let Some(loaded_val) = match instr.op {
294                InstOp::Load(loaded_val) => Some(loaded_val),
295                _ => None,
296            } {
297                ptr_to_int_instr_val.replace_instruction_value(context, ptr_val, loaded_val);
298                continue;
299            }
300        }
301
302        // Take the ptr_to_int value, store it in a temporary local, and replace it with its pointer in
303        // the ptr_to_int instruction.
304
305        // Create a variable for the arg, a get_local for it and a store.
306        let loc_var = function.new_unique_local_var(
307            context,
308            "__ptr_to_int_arg".to_owned(),
309            ptr_ty,
310            None,
311            false,
312        );
313        let get_loc_val = Value::new_instruction(context, block, InstOp::GetLocal(loc_var));
314        let store_val = Value::new_instruction(
315            context,
316            block,
317            InstOp::Store {
318                dst_val_ptr: get_loc_val,
319                stored_val: ptr_val,
320            },
321        );
322
323        // Put these two _before_ ptr_to_int_instr_val.
324        let mut inserter = InstructionInserter::new(
325            context,
326            block,
327            crate::InsertionPosition::Before(ptr_to_int_instr_val),
328        );
329        inserter.insert_slice(&[get_loc_val, store_val]);
330
331        // Replace the argument to ptr_to_int.
332        ptr_to_int_instr_val.replace_instruction_value(context, ptr_val, get_loc_val);
333    }
334
335    Ok(true)
336}
337
338/// Find all binary operations on types bigger than 64 bits
339/// and demote them to fuel specific `wide binary ops`, that
340/// work only on pointers
341fn wide_binary_op_demotion(context: &mut Context, function: Function) -> Result<bool, IrError> {
342    // Find all intrinsics on wide operators
343    let candidates = function
344        .instruction_iter(context)
345        .filter_map(|(block, instr_val)| {
346            use BinaryOpKind as B;
347            let InstOp::BinaryOp {
348                op: B::Add | B::Sub | B::Mul | B::Div | B::Mod | B::And | B::Or | B::Xor,
349                arg1,
350                arg2,
351            } = instr_val.get_instruction(context)?.op
352            else {
353                return None;
354            };
355
356            let arg1_type = arg1.get_type(context);
357            let arg2_type = arg2.get_type(context);
358
359            match (arg1_type, arg2_type) {
360                (Some(arg1_type), Some(arg2_type))
361                    if arg1_type.is_uint_of(context, 256) && arg2_type.is_uint_of(context, 256) =>
362                {
363                    Some((block, instr_val))
364                }
365                (Some(arg1_type), Some(arg2_type))
366                    if arg1_type.is_b256(context) && arg2_type.is_b256(context) =>
367                {
368                    Some((block, instr_val))
369                }
370                _ => None,
371            }
372        })
373        .collect::<Vec<_>>();
374
375    if candidates.is_empty() {
376        return Ok(false);
377    }
378
379    // Now create a local for the result
380    // get ptr to each arg
381    // and store the result after
382    for (block, binary_op_instr_val) in candidates {
383        let InstOp::BinaryOp { op, arg1, arg2 } = binary_op_instr_val
384            .get_instruction(context)
385            .cloned()
386            .unwrap()
387            .op
388        else {
389            continue;
390        };
391
392        let binary_op_metadata = binary_op_instr_val.get_metadata(context);
393
394        let arg1_ty = arg1.get_type(context).unwrap();
395        let arg1_metadata = arg1.get_metadata(context);
396        let arg2_ty = arg2.get_type(context).unwrap();
397        let arg2_metadata = arg2.get_metadata(context);
398
399        let operand_ty = arg1.get_type(context).unwrap();
400
401        let result_local = function.new_unique_local_var(
402            context,
403            "__wide_result".to_owned(),
404            operand_ty,
405            None,
406            true,
407        );
408        let get_result_local =
409            Value::new_instruction(context, block, InstOp::GetLocal(result_local))
410                .add_metadatum(context, binary_op_metadata);
411        let load_result_local =
412            Value::new_instruction(context, block, InstOp::Load(get_result_local))
413                .add_metadatum(context, binary_op_metadata);
414
415        // If arg1 is not a pointer, store it to a local
416        let lhs_store = if !arg1_ty.is_ptr(context) {
417            let lhs_local = function.new_unique_local_var(
418                context,
419                "__wide_lhs".to_owned(),
420                operand_ty,
421                None,
422                false,
423            );
424            let get_lhs_local = Value::new_instruction(context, block, InstOp::GetLocal(lhs_local))
425                .add_metadatum(context, arg1_metadata);
426            let store_lhs_local = Value::new_instruction(
427                context,
428                block,
429                InstOp::Store {
430                    dst_val_ptr: get_lhs_local,
431                    stored_val: arg1,
432                },
433            )
434            .add_metadatum(context, arg1_metadata);
435            Some((get_lhs_local, store_lhs_local))
436        } else {
437            None
438        };
439
440        let (arg1_needs_insert, get_arg1) = if let Some((lhs_local, _)) = &lhs_store {
441            (false, *lhs_local)
442        } else {
443            (true, arg1)
444        };
445
446        // If arg2 is not a pointer, store it to a local
447        let rhs_store = if !arg2_ty.is_ptr(context) {
448            let rhs_local = function.new_unique_local_var(
449                context,
450                "__wide_rhs".to_owned(),
451                operand_ty,
452                None,
453                false,
454            );
455            let get_rhs_local = Value::new_instruction(context, block, InstOp::GetLocal(rhs_local))
456                .add_metadatum(context, arg2_metadata);
457            let store_lhs_local = Value::new_instruction(
458                context,
459                block,
460                InstOp::Store {
461                    dst_val_ptr: get_rhs_local,
462                    stored_val: arg2,
463                },
464            )
465            .add_metadatum(context, arg2_metadata);
466            Some((get_rhs_local, store_lhs_local))
467        } else {
468            None
469        };
470
471        let (arg2_needs_insert, get_arg2) = if let Some((rhs_local, _)) = &rhs_store {
472            (false, *rhs_local)
473        } else {
474            (true, arg2)
475        };
476
477        // For MOD we need a local zero as RHS of the add operation
478        let (wide_op, get_local_zero) = match op {
479            BinaryOpKind::Mod => {
480                let initializer = ConstantContent::new_uint(context, 256, 0);
481                let initializer = Constant::unique(context, initializer);
482                let local_zero = function.new_unique_local_var(
483                    context,
484                    "__wide_zero".to_owned(),
485                    operand_ty,
486                    Some(initializer),
487                    true,
488                );
489                let get_local_zero =
490                    Value::new_instruction(context, block, InstOp::GetLocal(local_zero))
491                        .add_metadatum(context, binary_op_metadata);
492
493                (
494                    Value::new_instruction(
495                        context,
496                        block,
497                        InstOp::FuelVm(FuelVmInstruction::WideModularOp {
498                            op,
499                            result: get_result_local,
500                            arg1: get_arg1,
501                            arg2: get_local_zero,
502                            arg3: get_arg2,
503                        }),
504                    )
505                    .add_metadatum(context, binary_op_metadata),
506                    Some(get_local_zero),
507                )
508            }
509            _ => (
510                Value::new_instruction(
511                    context,
512                    block,
513                    InstOp::FuelVm(FuelVmInstruction::WideBinaryOp {
514                        op,
515                        arg1: get_arg1,
516                        arg2: get_arg2,
517                        result: get_result_local,
518                    }),
519                )
520                .add_metadatum(context, binary_op_metadata),
521                None,
522            ),
523        };
524
525        // Assert all operands are pointers
526        assert!(get_arg1.get_type(context).unwrap().is_ptr(context));
527        assert!(get_arg2.get_type(context).unwrap().is_ptr(context));
528        assert!(get_result_local.get_type(context).unwrap().is_ptr(context));
529        if let Some(get_local_zero) = &get_local_zero {
530            assert!(get_local_zero.get_type(context).unwrap().is_ptr(context));
531        }
532
533        block
534            .replace_instruction(context, binary_op_instr_val, load_result_local, true)
535            .unwrap();
536
537        let mut additional_instrs = Vec::new();
538
539        // lhs
540        if let Some((get_lhs_local, store_lhs_local)) = lhs_store {
541            additional_instrs.push(get_lhs_local);
542            additional_instrs.push(store_lhs_local);
543        }
544        // Only for MOD
545        if let Some(get_local_zero) = get_local_zero {
546            additional_instrs.push(get_local_zero);
547        }
548
549        //rhs
550        if let Some((get_rhs_local, store_rhs_local)) = rhs_store {
551            additional_instrs.push(get_rhs_local);
552            additional_instrs.push(store_rhs_local);
553        }
554        if arg1_needs_insert {
555            additional_instrs.push(get_arg1);
556        }
557
558        if arg2_needs_insert {
559            additional_instrs.push(get_arg2);
560        }
561
562        additional_instrs.push(get_result_local);
563        additional_instrs.push(wide_op);
564
565        let mut inserter = InstructionInserter::new(
566            context,
567            block,
568            crate::InsertionPosition::Before(load_result_local),
569        );
570        inserter.insert_slice(&additional_instrs);
571    }
572
573    Ok(true)
574}
575
576/// Find all cmp operations on types bigger than 64 bits
577/// and demote them to fuel specific `wide cmp ops`, that
578/// work only on pointers
579fn wide_cmp_demotion(context: &mut Context, function: Function) -> Result<bool, IrError> {
580    // Find all cmp on wide operators
581    let candidates = function
582        .instruction_iter(context)
583        .filter_map(|(block, instr_val)| {
584            let InstOp::Cmp(
585                Predicate::Equal | Predicate::LessThan | Predicate::GreaterThan,
586                arg1,
587                arg2,
588            ) = instr_val.get_instruction(context)?.op
589            else {
590                return None;
591            };
592
593            let arg1_type = arg1.get_type(context);
594            let arg2_type = arg2.get_type(context);
595
596            match (arg1_type, arg2_type) {
597                (Some(arg1_type), Some(arg2_type))
598                    if arg1_type.is_uint_of(context, 256) && arg2_type.is_uint_of(context, 256) =>
599                {
600                    Some((block, instr_val))
601                }
602                (Some(arg1_type), Some(arg2_type))
603                    if arg1_type.is_b256(context) && arg2_type.is_b256(context) =>
604                {
605                    Some((block, instr_val))
606                }
607                _ => None,
608            }
609        })
610        .collect::<Vec<_>>();
611
612    if candidates.is_empty() {
613        return Ok(false);
614    }
615
616    // Get ptr to each arg
617    for (block, cmp_instr_val) in candidates {
618        let InstOp::Cmp(op, arg1, arg2) =
619            cmp_instr_val.get_instruction(context).cloned().unwrap().op
620        else {
621            continue;
622        };
623
624        let cmp_op_metadata = cmp_instr_val.get_metadata(context);
625
626        let arg1_ty = arg1.get_type(context).unwrap();
627        let arg1_metadata = arg1.get_metadata(context);
628        let arg2_ty = arg2.get_type(context).unwrap();
629        let arg2_metadata = arg2.get_metadata(context);
630
631        // If arg1 is not a pointer, store it to a local
632        let lhs_store = arg1_ty.is_ptr(context).not().then(|| {
633            let lhs_local = function.new_unique_local_var(
634                context,
635                "__wide_lhs".to_owned(),
636                arg1_ty,
637                None,
638                false,
639            );
640            let get_lhs_local = Value::new_instruction(context, block, InstOp::GetLocal(lhs_local))
641                .add_metadatum(context, arg1_metadata);
642            let store_lhs_local = Value::new_instruction(
643                context,
644                block,
645                InstOp::Store {
646                    dst_val_ptr: get_lhs_local,
647                    stored_val: arg1,
648                },
649            )
650            .add_metadatum(context, arg1_metadata);
651            (get_lhs_local, store_lhs_local)
652        });
653
654        let (arg1_needs_insert, get_arg1) = if let Some((lhs_local, _)) = &lhs_store {
655            (false, *lhs_local)
656        } else {
657            (true, arg1)
658        };
659
660        // If arg2 is not a pointer, store it to a local
661        let rhs_store = arg2_ty.is_ptr(context).not().then(|| {
662            let rhs_local = function.new_unique_local_var(
663                context,
664                "__wide_rhs".to_owned(),
665                arg1_ty,
666                None,
667                false,
668            );
669            let get_rhs_local = Value::new_instruction(context, block, InstOp::GetLocal(rhs_local))
670                .add_metadatum(context, arg2_metadata);
671            let store_lhs_local = Value::new_instruction(
672                context,
673                block,
674                InstOp::Store {
675                    dst_val_ptr: get_rhs_local,
676                    stored_val: arg2,
677                },
678            )
679            .add_metadatum(context, arg2_metadata);
680            (get_rhs_local, store_lhs_local)
681        });
682
683        let (arg2_needs_insert, get_arg2) = if let Some((rhs_local, _)) = &rhs_store {
684            (false, *rhs_local)
685        } else {
686            (true, arg2)
687        };
688
689        // Assert all operands are pointers
690        assert!(get_arg1.get_type(context).unwrap().is_ptr(context));
691        assert!(get_arg2.get_type(context).unwrap().is_ptr(context));
692
693        let wide_op = Value::new_instruction(
694            context,
695            block,
696            InstOp::FuelVm(FuelVmInstruction::WideCmpOp {
697                op,
698                arg1: get_arg1,
699                arg2: get_arg2,
700            }),
701        )
702        .add_metadatum(context, cmp_op_metadata);
703
704        block
705            .replace_instruction(context, cmp_instr_val, wide_op, true)
706            .unwrap();
707
708        let mut additional_instrs = Vec::new();
709
710        // lhs
711        if let Some((get_lhs_local, store_lhs_local)) = lhs_store {
712            additional_instrs.push(get_lhs_local);
713            additional_instrs.push(store_lhs_local);
714        }
715
716        //rhs
717        if let Some((get_rhs_local, store_rhs_local)) = rhs_store {
718            additional_instrs.push(get_rhs_local);
719            additional_instrs.push(store_rhs_local);
720        }
721
722        if arg1_needs_insert {
723            additional_instrs.push(get_arg1);
724        }
725
726        if arg2_needs_insert {
727            additional_instrs.push(get_arg2);
728        }
729
730        let mut inserter =
731            InstructionInserter::new(context, block, crate::InsertionPosition::Before(wide_op));
732        inserter.insert_slice(&additional_instrs);
733    }
734
735    Ok(true)
736}
737
738/// Find all unary operations on types bigger than 64 bits
739/// and demote them to fuel specific `wide ops`, that
740/// work only on pointers
741fn wide_unary_op_demotion(context: &mut Context, function: Function) -> Result<bool, IrError> {
742    // Find all intrinsics on wide operators
743    let candidates = function
744        .instruction_iter(context)
745        .filter_map(|(block, instr_val)| {
746            let InstOp::UnaryOp {
747                op: UnaryOpKind::Not,
748                arg,
749            } = instr_val.get_instruction(context)?.op
750            else {
751                return None;
752            };
753
754            match arg.get_type(context) {
755                Some(t) if t.is_uint_of(context, 256) || t.is_b256(context) => {
756                    Some((block, instr_val))
757                }
758                _ => None,
759            }
760        })
761        .collect::<Vec<_>>();
762
763    if candidates.is_empty() {
764        return Ok(false);
765    }
766
767    // Now create a local for the result
768    // get ptr to each arg
769    // and store the result after
770    for (block, binary_op_instr_val) in candidates {
771        let InstOp::UnaryOp { arg, .. } = binary_op_instr_val
772            .get_instruction(context)
773            .cloned()
774            .unwrap()
775            .op
776        else {
777            continue;
778        };
779
780        let unary_op_metadata = binary_op_instr_val.get_metadata(context);
781
782        let arg_ty = arg.get_type(context).unwrap();
783        let arg_metadata = arg.get_metadata(context);
784
785        let result_local =
786            function.new_unique_local_var(context, "__wide_result".to_owned(), arg_ty, None, true);
787        let get_result_local =
788            Value::new_instruction(context, block, InstOp::GetLocal(result_local))
789                .add_metadatum(context, unary_op_metadata);
790        let load_result_local =
791            Value::new_instruction(context, block, InstOp::Load(get_result_local))
792                .add_metadatum(context, unary_op_metadata);
793
794        // If arg1 is not a pointer, store it to a local
795        let lhs_store = arg_ty.is_ptr(context).not().then(|| {
796            let lhs_local = function.new_unique_local_var(
797                context,
798                "__wide_lhs".to_owned(),
799                arg_ty,
800                None,
801                false,
802            );
803            let get_lhs_local = Value::new_instruction(context, block, InstOp::GetLocal(lhs_local))
804                .add_metadatum(context, arg_metadata);
805            let store_lhs_local = Value::new_instruction(
806                context,
807                block,
808                InstOp::Store {
809                    dst_val_ptr: get_lhs_local,
810                    stored_val: arg,
811                },
812            )
813            .add_metadatum(context, arg_metadata);
814            (get_lhs_local, store_lhs_local)
815        });
816
817        let (arg1_needs_insert, get_arg) = if let Some((lhs_local, _)) = &lhs_store {
818            (false, *lhs_local)
819        } else {
820            (true, arg)
821        };
822
823        // Assert all operands are pointers
824        assert!(get_arg.get_type(context).unwrap().is_ptr(context));
825        assert!(get_result_local.get_type(context).unwrap().is_ptr(context));
826
827        let wide_op = Value::new_instruction(
828            context,
829            block,
830            InstOp::FuelVm(FuelVmInstruction::WideUnaryOp {
831                op: UnaryOpKind::Not,
832                arg: get_arg,
833                result: get_result_local,
834            }),
835        )
836        .add_metadatum(context, unary_op_metadata);
837
838        block
839            .replace_instruction(context, binary_op_instr_val, load_result_local, true)
840            .unwrap();
841
842        let mut additional_instrs = Vec::new();
843
844        // lhs
845        if let Some((get_lhs_local, store_lhs_local)) = lhs_store {
846            additional_instrs.push(get_lhs_local);
847            additional_instrs.push(store_lhs_local);
848        }
849
850        if arg1_needs_insert {
851            additional_instrs.push(get_arg);
852        }
853
854        additional_instrs.push(get_result_local);
855        additional_instrs.push(wide_op);
856
857        let mut inserter = InstructionInserter::new(
858            context,
859            block,
860            crate::InsertionPosition::Before(load_result_local),
861        );
862        inserter.insert_slice(&additional_instrs);
863    }
864
865    Ok(true)
866}
867
868/// Find all shift operations on types bigger than 64 bits
869/// and demote them to fuel specific `wide binary ops`, that
870/// work only on pointers
871fn wide_shift_op_demotion(context: &mut Context, function: Function) -> Result<bool, IrError> {
872    // Find all intrinsics on wide operators
873    let candidates = function
874        .instruction_iter(context)
875        .filter_map(|(block, instr_val)| {
876            let instr = instr_val.get_instruction(context)?;
877            let InstOp::BinaryOp {
878                op: BinaryOpKind::Lsh | BinaryOpKind::Rsh,
879                arg1,
880                arg2,
881            } = instr.op
882            else {
883                return None;
884            };
885
886            let arg1_type = arg1.get_type(context);
887            let arg2_type = arg2.get_type(context);
888
889            match (arg1_type, arg2_type) {
890                (Some(arg1_type), Some(arg2_type))
891                    if arg1_type.is_uint_of(context, 256) && arg2_type.is_uint64(context) =>
892                {
893                    Some((block, instr_val))
894                }
895                (Some(arg1_type), Some(arg2_type))
896                    if arg1_type.is_b256(context) && arg2_type.is_uint64(context) =>
897                {
898                    Some((block, instr_val))
899                }
900                _ => None,
901            }
902        })
903        .collect::<Vec<_>>();
904
905    if candidates.is_empty() {
906        return Ok(false);
907    }
908
909    // Now create a local for the result
910    // get ptr to each arg
911    // and store the result after
912    for (block, binary_op_instr_val) in candidates {
913        let InstOp::BinaryOp { op, arg1, arg2 } = binary_op_instr_val
914            .get_instruction(context)
915            .cloned()
916            .unwrap()
917            .op
918        else {
919            continue;
920        };
921
922        let binary_op_metadata = binary_op_instr_val.get_metadata(context);
923
924        let arg1_ty = arg1.get_type(context).unwrap();
925        let arg1_metadata = arg1.get_metadata(context);
926
927        let arg2_ty = arg2.get_type(context).unwrap();
928
929        let operand_ty = arg1.get_type(context).unwrap();
930
931        let result_local = function.new_unique_local_var(
932            context,
933            "__wide_result".to_owned(),
934            operand_ty,
935            None,
936            true,
937        );
938        let get_result_local =
939            Value::new_instruction(context, block, InstOp::GetLocal(result_local))
940                .add_metadatum(context, binary_op_metadata);
941        let load_result_local =
942            Value::new_instruction(context, block, InstOp::Load(get_result_local))
943                .add_metadatum(context, binary_op_metadata);
944
945        // If arg1 is not a pointer, store it to a local
946        let lhs_store = if !arg1_ty.is_ptr(context) {
947            let lhs_local = function.new_unique_local_var(
948                context,
949                "__wide_lhs".to_owned(),
950                operand_ty,
951                None,
952                false,
953            );
954            let get_lhs_local = Value::new_instruction(context, block, InstOp::GetLocal(lhs_local))
955                .add_metadatum(context, arg1_metadata);
956            let store_lhs_local = Value::new_instruction(
957                context,
958                block,
959                InstOp::Store {
960                    dst_val_ptr: get_lhs_local,
961                    stored_val: arg1,
962                },
963            )
964            .add_metadatum(context, arg1_metadata);
965            Some((get_lhs_local, store_lhs_local))
966        } else {
967            None
968        };
969
970        let (arg1_needs_insert, get_arg1) = if let Some((lhs_local, _)) = &lhs_store {
971            (false, *lhs_local)
972        } else {
973            (true, arg1)
974        };
975
976        // Assert result and lhs are pointers
977        // Assert rhs is u64
978        assert!(get_arg1.get_type(context).unwrap().is_ptr(context));
979        assert!(get_result_local.get_type(context).unwrap().is_ptr(context));
980        assert!(arg2_ty.is_uint64(context));
981
982        let wide_op = Value::new_instruction(
983            context,
984            block,
985            InstOp::FuelVm(FuelVmInstruction::WideBinaryOp {
986                op,
987                arg1: get_arg1,
988                arg2,
989                result: get_result_local,
990            }),
991        )
992        .add_metadatum(context, binary_op_metadata);
993
994        block
995            .replace_instruction(context, binary_op_instr_val, load_result_local, true)
996            .unwrap();
997
998        let mut additional_instrs = Vec::new();
999
1000        // lhs
1001        if let Some((get_lhs_local, store_lhs_local)) = lhs_store {
1002            additional_instrs.push(get_lhs_local);
1003            additional_instrs.push(store_lhs_local);
1004        }
1005
1006        if arg1_needs_insert {
1007            additional_instrs.push(get_arg1);
1008        }
1009
1010        additional_instrs.push(get_result_local);
1011        additional_instrs.push(wide_op);
1012
1013        let mut inserter = InstructionInserter::new(
1014            context,
1015            block,
1016            crate::InsertionPosition::Before(load_result_local),
1017        );
1018        inserter.insert_slice(&additional_instrs);
1019    }
1020
1021    Ok(true)
1022}