sway_ir/
verify.rs

1//! Code to validate the IR in a [`Context`].
2//!
3//! During creation, deserialization and optimization the IR should be verified to be in a
4//! consistent valid state, using the functions in this module.
5
6use itertools::Itertools;
7
8use crate::{
9    context::Context,
10    error::IrError,
11    function::Function,
12    instruction::{FuelVmInstruction, InstOp, Predicate},
13    irtype::Type,
14    metadata::{MetadataIndex, Metadatum},
15    printer,
16    value::{Value, ValueDatum},
17    variable::LocalVar,
18    AnalysisResult, AnalysisResultT, AnalysisResults, BinaryOpKind, Block, BlockArgument,
19    BranchToWithArgs, Doc, GlobalVar, Module, Pass, PassMutability, ScopedPass, StorageKey,
20    TypeOption, UnaryOpKind,
21};
22
23pub struct ModuleVerifierResult;
24impl AnalysisResultT for ModuleVerifierResult {}
25
26/// Verify module
27pub fn module_verifier(
28    context: &Context,
29    _analyses: &AnalysisResults,
30    module: Module,
31) -> Result<AnalysisResult, IrError> {
32    context.verify_module(module)?;
33    Ok(Box::new(ModuleVerifierResult))
34}
35
36pub const MODULE_VERIFIER_NAME: &str = "module-verifier";
37
38pub fn create_module_verifier_pass() -> Pass {
39    Pass {
40        name: MODULE_VERIFIER_NAME,
41        descr: "Verify module",
42        deps: vec![],
43        runner: ScopedPass::ModulePass(PassMutability::Analysis(module_verifier)),
44    }
45}
46
47impl Context<'_> {
48    /// Verify the contents of this [`Context`] is valid.
49    pub fn verify(self) -> Result<Self, IrError> {
50        for (module, _) in &self.modules {
51            let module = Module(module);
52            self.verify_module(module)?;
53        }
54        Ok(self)
55    }
56
57    fn verify_module(&self, module: Module) -> Result<(), IrError> {
58        for function in module.function_iter(self) {
59            self.verify_function(module, function)?;
60        }
61
62        // Check that globals have initializers if they are not mutable.
63        for global in &self.modules[module.0].global_variables {
64            if !global.1.is_mutable(self) && global.1.get_initializer(self).is_none() {
65                let global_name = module.lookup_global_variable_name(self, global.1);
66                return Err(IrError::VerifyGlobalMissingInitializer(
67                    global_name.unwrap_or_else(|| "<unknown>".to_owned()),
68                ));
69            }
70        }
71        Ok(())
72    }
73
74    fn verify_function(&self, cur_module: Module, function: Function) -> Result<(), IrError> {
75        if function.get_module(self) != cur_module {
76            return Err(IrError::InconsistentParent(
77                function.get_name(self).into(),
78                format!("Module_Index_{:?}", cur_module.0),
79                format!("Module_Index_{:?}", function.get_module(self).0),
80            ));
81        }
82
83        let entry_block = function.get_entry_block(self);
84
85        if entry_block.num_predecessors(self) != 0 {
86            return Err(IrError::VerifyEntryBlockHasPredecessors(
87                function.get_name(self).to_string(),
88                entry_block
89                    .pred_iter(self)
90                    .map(|block| block.get_label(self))
91                    .collect(),
92            ));
93        }
94
95        // Ensure that the entry block arguments are same as function arguments.
96        if function.num_args(self) != entry_block.num_args(self) {
97            return Err(IrError::VerifyBlockArgMalformed);
98        }
99        for ((_, func_arg), block_arg) in function.args_iter(self).zip(entry_block.arg_iter(self)) {
100            if func_arg != block_arg {
101                return Err(IrError::VerifyBlockArgMalformed);
102            }
103        }
104
105        // Check that locals have initializers if they aren't mutable.
106        // TODO: This check is disabled because we incorrect create
107        //       immutable locals without initializers at many places.
108        // for local in &self.functions[function.0].local_storage {
109        //     if !local.1.is_mutable(self) && local.1.get_initializer(self).is_none() {
110        //         return Err(IrError::VerifyLocalMissingInitializer(
111        //             local.0.to_string(),
112        //             function.get_name(self).to_string(),
113        //         ));
114        //     }
115        // }
116
117        for block in function.block_iter(self) {
118            self.verify_block(cur_module, function, block)?;
119        }
120        self.verify_metadata(function.get_metadata(self))?;
121        Ok(())
122    }
123
124    fn verify_block(
125        &self,
126        cur_module: Module,
127        cur_function: Function,
128        cur_block: Block,
129    ) -> Result<(), IrError> {
130        if cur_block.get_function(self) != cur_function {
131            return Err(IrError::InconsistentParent(
132                cur_block.get_label(self),
133                cur_function.get_name(self).into(),
134                cur_block.get_function(self).get_name(self).into(),
135            ));
136        }
137
138        if cur_block.num_instructions(self) <= 1 && cur_block.num_predecessors(self) == 0 {
139            // Empty unreferenced blocks are a harmless artefact.
140            return Ok(());
141        }
142
143        for (arg_idx, arg_val) in cur_block.arg_iter(self).enumerate() {
144            match self.values[arg_val.0].value {
145                ValueDatum::Argument(BlockArgument { idx, .. }) if idx == arg_idx => (),
146                _ => return Err(IrError::VerifyBlockArgMalformed),
147            }
148        }
149
150        let r = InstructionVerifier {
151            context: self,
152            cur_module,
153            cur_function,
154            cur_block,
155        }
156        .verify_instructions();
157
158        // Help to understand the verification failure
159        // If the error knows the problematic value, prints everything with the error highlighted,
160        // if not, print only the block to help pinpoint the issue
161        if let Err(error) = &r {
162            println!(
163                "Verification failed at {}::{}",
164                cur_function.get_name(self),
165                cur_block.get_label(self)
166            );
167
168            let block = if let Some(problematic_value) = error.get_problematic_value() {
169                printer::context_print(self, &|current_value: &Value, doc: Doc| {
170                    if *current_value == *problematic_value {
171                        doc.append(Doc::text_line(format!("\x1b[0;31m^ {error}\x1b[0m")))
172                    } else {
173                        doc
174                    }
175                })
176            } else {
177                printer::block_print(self, cur_function, cur_block, &|_, doc| doc)
178            };
179
180            println!("{block}");
181        }
182
183        r?;
184
185        let (last_is_term, num_terms) =
186            cur_block
187                .instruction_iter(self)
188                .fold((false, 0), |(_, n), ins| {
189                    if ins.is_terminator(self) {
190                        (true, n + 1)
191                    } else {
192                        (false, n)
193                    }
194                });
195        if !last_is_term {
196            Err(IrError::MissingTerminator(
197                cur_block.get_label(self).clone(),
198            ))
199        } else if num_terms != 1 {
200            Err(IrError::MisplacedTerminator(
201                cur_block.get_label(self).clone(),
202            ))
203        } else {
204            Ok(())
205        }
206    }
207
208    fn verify_metadata(&self, md_idx: Option<MetadataIndex>) -> Result<(), IrError> {
209        // For now we check only that struct tags are valid identifiers.
210        if let Some(md_idx) = md_idx {
211            match &self.metadata[md_idx.0] {
212                Metadatum::List(md_idcs) => {
213                    for md_idx in md_idcs {
214                        self.verify_metadata(Some(*md_idx))?;
215                    }
216                }
217                Metadatum::Struct(tag, ..) => {
218                    // We could import Regex to match it, but it's a simple identifier style pattern:
219                    // alpha start char, alphanumeric for the rest, or underscore anywhere.
220                    if tag.is_empty() {
221                        return Err(IrError::InvalidMetadatum(
222                            "Struct has empty tag.".to_owned(),
223                        ));
224                    }
225                    let mut chs = tag.chars();
226                    let ch0 = chs.next().unwrap();
227                    if !(ch0.is_ascii_alphabetic() || ch0 == '_')
228                        || chs.any(|ch| !(ch.is_ascii_alphanumeric() || ch == '_'))
229                    {
230                        return Err(IrError::InvalidMetadatum(format!(
231                            "Invalid struct tag: '{tag}'."
232                        )));
233                    }
234                }
235                _otherwise => (),
236            }
237        }
238        Ok(())
239    }
240}
241
242struct InstructionVerifier<'a, 'eng> {
243    context: &'a Context<'eng>,
244    cur_module: Module,
245    cur_function: Function,
246    cur_block: Block,
247}
248
249impl InstructionVerifier<'_, '_> {
250    fn verify_instructions(&self) -> Result<(), IrError> {
251        for ins in self.cur_block.instruction_iter(self.context) {
252            let value_content = &self.context.values[ins.0];
253            let ValueDatum::Instruction(instruction) = &value_content.value else {
254                unreachable!("The value must be an instruction, because it is retrieved via block instruction iterator.")
255            };
256
257            if instruction.parent != self.cur_block {
258                return Err(IrError::InconsistentParent(
259                    format!("Instr_{:?}", ins.0),
260                    self.cur_block.get_label(self.context),
261                    instruction.parent.get_label(self.context),
262                ));
263            }
264
265            match &instruction.op {
266                InstOp::AsmBlock(..) => (),
267                InstOp::BitCast(value, ty) => self.verify_bitcast(value, ty)?,
268                InstOp::UnaryOp { op, arg } => self.verify_unary_op(op, arg)?,
269                InstOp::BinaryOp { op, arg1, arg2 } => self.verify_binary_op(op, arg1, arg2)?,
270                InstOp::Branch(block) => self.verify_br(block)?,
271                InstOp::Call(func, args) => self.verify_call(func, args)?,
272                InstOp::CastPtr(val, ty) => self.verify_cast_ptr(val, ty)?,
273                InstOp::Cmp(pred, lhs_value, rhs_value) => {
274                    self.verify_cmp(pred, lhs_value, rhs_value)?
275                }
276                InstOp::ConditionalBranch {
277                    cond_value,
278                    true_block,
279                    false_block,
280                } => self.verify_cbr(cond_value, true_block, false_block)?,
281                InstOp::ContractCall {
282                    params,
283                    coins,
284                    asset_id,
285                    gas,
286                    ..
287                } => self.verify_contract_call(params, coins, asset_id, gas)?,
288                // XXX move the fuelvm verification into a module
289                InstOp::FuelVm(fuel_vm_instr) => match fuel_vm_instr {
290                    FuelVmInstruction::Gtf { index, tx_field_id } => {
291                        self.verify_gtf(index, tx_field_id)?
292                    }
293                    FuelVmInstruction::Log {
294                        log_val,
295                        log_ty,
296                        log_id,
297                    } => self.verify_log(log_val, log_ty, log_id)?,
298                    FuelVmInstruction::ReadRegister(_) => (),
299                    FuelVmInstruction::JmpMem => (),
300                    FuelVmInstruction::Revert(val) => self.verify_revert(val)?,
301                    FuelVmInstruction::Smo {
302                        recipient,
303                        message,
304                        message_size,
305                        coins,
306                    } => self.verify_smo(recipient, message, message_size, coins)?,
307                    FuelVmInstruction::StateClear {
308                        key,
309                        number_of_slots,
310                    } => self.verify_state_clear(key, number_of_slots)?,
311                    FuelVmInstruction::StateLoadWord(key) => self.verify_state_load_word(key)?,
312                    FuelVmInstruction::StateLoadQuadWord {
313                        load_val: dst_val,
314                        key,
315                        number_of_slots,
316                    }
317                    | FuelVmInstruction::StateStoreQuadWord {
318                        stored_val: dst_val,
319                        key,
320                        number_of_slots,
321                    } => self.verify_state_access_quad(dst_val, key, number_of_slots)?,
322                    FuelVmInstruction::StateStoreWord {
323                        stored_val: dst_val,
324                        key,
325                    } => self.verify_state_store_word(dst_val, key)?,
326                    FuelVmInstruction::WideUnaryOp { op, result, arg } => {
327                        self.verify_wide_unary_op(op, result, arg)?
328                    }
329                    FuelVmInstruction::WideBinaryOp {
330                        op,
331                        result,
332                        arg1,
333                        arg2,
334                    } => self.verify_wide_binary_op(op, result, arg1, arg2)?,
335                    FuelVmInstruction::WideModularOp {
336                        op,
337                        result,
338                        arg1,
339                        arg2,
340                        arg3,
341                    } => self.verify_wide_modular_op(op, result, arg1, arg2, arg3)?,
342                    FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => {
343                        self.verify_wide_cmp(op, arg1, arg2)?
344                    }
345                    FuelVmInstruction::Retd { .. } => (),
346                },
347                InstOp::GetElemPtr {
348                    base,
349                    elem_ptr_ty,
350                    indices,
351                } => self.verify_get_elem_ptr(&ins, base, elem_ptr_ty, indices)?,
352                InstOp::GetLocal(local_var) => self.verify_get_local(local_var)?,
353                InstOp::GetGlobal(global_var) => self.verify_get_global(global_var)?,
354                InstOp::GetConfig(_, name) => self.verify_get_config(self.cur_module, name)?,
355                InstOp::GetStorageKey(storage_key) => self.verify_get_storage_key(storage_key)?,
356                InstOp::IntToPtr(value, ty) => self.verify_int_to_ptr(value, ty)?,
357                InstOp::Load(ptr) => self.verify_load(ptr)?,
358                InstOp::MemCopyBytes {
359                    dst_val_ptr,
360                    src_val_ptr,
361                    byte_len,
362                } => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr, byte_len)?,
363                InstOp::MemCopyVal {
364                    dst_val_ptr,
365                    src_val_ptr,
366                } => self.verify_mem_copy_val(dst_val_ptr, src_val_ptr)?,
367                InstOp::MemClearVal { dst_val_ptr } => self.verify_mem_clear_val(dst_val_ptr)?,
368                InstOp::Nop => (),
369                InstOp::PtrToInt(val, ty) => self.verify_ptr_to_int(val, ty)?,
370                InstOp::Ret(val, ty) => self.verify_ret(val, ty)?,
371                InstOp::Store {
372                    dst_val_ptr,
373                    stored_val,
374                } => self.verify_store(&ins, dst_val_ptr, stored_val)?,
375            };
376
377            // Verify the instruction metadata too.
378            self.context.verify_metadata(value_content.metadata)?;
379        }
380
381        Ok(())
382    }
383
384    fn verify_bitcast(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
385        // The bitsize of bools and unit is 1 which obviously won't match a typical uint.  LLVM
386        // would use `trunc` or `zext` to make types match sizes before casting.  Until we have
387        // similar we'll just make sure the sizes are <= 64 bits.
388        let val_ty = value
389            .get_type(self.context)
390            .ok_or(IrError::VerifyBitcastUnknownSourceType)?;
391        if self.type_bit_size(&val_ty).is_some_and(|sz| sz > 64)
392            || self.type_bit_size(ty).is_some_and(|sz| sz > 64)
393        {
394            Err(IrError::VerifyBitcastBetweenInvalidTypes(
395                val_ty.as_string(self.context),
396                ty.as_string(self.context),
397            ))
398        } else {
399            Ok(())
400        }
401    }
402
403    fn verify_unary_op(&self, op: &UnaryOpKind, arg: &Value) -> Result<(), IrError> {
404        let arg_ty = arg
405            .get_type(self.context)
406            .ok_or(IrError::VerifyUnaryOpIncorrectArgType)?;
407        match op {
408            UnaryOpKind::Not => {
409                if !arg_ty.is_uint(self.context) && !arg_ty.is_b256(self.context) {
410                    return Err(IrError::VerifyUnaryOpIncorrectArgType);
411                }
412            }
413        }
414
415        Ok(())
416    }
417
418    fn verify_wide_cmp(&self, _: &Predicate, arg1: &Value, arg2: &Value) -> Result<(), IrError> {
419        let arg1_ty = arg1
420            .get_type(self.context)
421            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
422        let arg2_ty = arg2
423            .get_type(self.context)
424            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
425
426        if arg1_ty.is_ptr(self.context) && arg2_ty.is_ptr(self.context) {
427            Ok(())
428        } else {
429            Err(IrError::VerifyBinaryOpIncorrectArgType)
430        }
431    }
432
433    fn verify_wide_modular_op(
434        &self,
435        _op: &BinaryOpKind,
436        result: &Value,
437        arg1: &Value,
438        arg2: &Value,
439        arg3: &Value,
440    ) -> Result<(), IrError> {
441        let result_ty = result
442            .get_type(self.context)
443            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
444        let arg1_ty = arg1
445            .get_type(self.context)
446            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
447        let arg2_ty = arg2
448            .get_type(self.context)
449            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
450        let arg3_ty = arg3
451            .get_type(self.context)
452            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
453
454        if !arg1_ty.is_ptr(self.context)
455            || !arg2_ty.is_ptr(self.context)
456            || !arg3_ty.is_ptr(self.context)
457            || !result_ty.is_ptr(self.context)
458        {
459            return Err(IrError::VerifyBinaryOpIncorrectArgType);
460        }
461
462        Ok(())
463    }
464
465    fn verify_wide_binary_op(
466        &self,
467        op: &BinaryOpKind,
468        result: &Value,
469        arg1: &Value,
470        arg2: &Value,
471    ) -> Result<(), IrError> {
472        let result_ty = result
473            .get_type(self.context)
474            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
475        let arg1_ty = arg1
476            .get_type(self.context)
477            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
478        let arg2_ty = arg2
479            .get_type(self.context)
480            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
481
482        match op {
483            // Shifts rhs are 64 bits
484            BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
485                if !arg1_ty.is_ptr(self.context)
486                    || !arg2_ty.is_uint64(self.context)
487                    || !result_ty.is_ptr(self.context)
488                {
489                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
490                }
491            }
492            BinaryOpKind::Add
493            | BinaryOpKind::Sub
494            | BinaryOpKind::Mul
495            | BinaryOpKind::Div
496            | BinaryOpKind::And
497            | BinaryOpKind::Or
498            | BinaryOpKind::Xor
499            | BinaryOpKind::Mod => {
500                if !arg1_ty.is_ptr(self.context)
501                    || !arg2_ty.is_ptr(self.context)
502                    || !result_ty.is_ptr(self.context)
503                {
504                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
505                }
506            }
507        }
508
509        Ok(())
510    }
511
512    fn verify_wide_unary_op(
513        &self,
514        _op: &UnaryOpKind,
515        result: &Value,
516        arg: &Value,
517    ) -> Result<(), IrError> {
518        let result_ty = result
519            .get_type(self.context)
520            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
521        let arg_ty = arg
522            .get_type(self.context)
523            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
524
525        if !arg_ty.is_ptr(self.context) || !result_ty.is_ptr(self.context) {
526            return Err(IrError::VerifyBinaryOpIncorrectArgType);
527        }
528
529        Ok(())
530    }
531
532    fn verify_binary_op(
533        &self,
534        op: &BinaryOpKind,
535        arg1: &Value,
536        arg2: &Value,
537    ) -> Result<(), IrError> {
538        let arg1_ty = arg1
539            .get_type(self.context)
540            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
541        let arg2_ty = arg2
542            .get_type(self.context)
543            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
544
545        match op {
546            // Shifts can have the rhs with different type
547            BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
548                let is_lhs_ok = arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context);
549                if !is_lhs_ok || !arg2_ty.is_uint(self.context) {
550                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
551                }
552            }
553            BinaryOpKind::Add | BinaryOpKind::Sub => {
554                if !(arg1_ty.eq(self.context, &arg2_ty) && arg1_ty.is_uint(self.context)
555                    || arg1_ty.is_ptr(self.context) && arg2_ty.is_uint64(self.context))
556                {
557                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
558                }
559            }
560            BinaryOpKind::Mul | BinaryOpKind::Div | BinaryOpKind::Mod => {
561                if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) {
562                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
563                }
564            }
565            BinaryOpKind::And | BinaryOpKind::Or | BinaryOpKind::Xor => {
566                if !arg1_ty.eq(self.context, &arg2_ty)
567                    || !(arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context))
568                {
569                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
570                }
571            }
572        }
573
574        Ok(())
575    }
576
577    fn verify_br(&self, dest_block: &BranchToWithArgs) -> Result<(), IrError> {
578        if !self
579            .cur_function
580            .block_iter(self.context)
581            .contains(&dest_block.block)
582        {
583            Err(IrError::VerifyBranchToMissingBlock(
584                self.context.blocks[dest_block.block.0].label.clone(),
585            ))
586        } else {
587            self.verify_dest_args(dest_block)
588        }
589    }
590
591    fn verify_call(&self, callee: &Function, args: &[Value]) -> Result<(), IrError> {
592        let callee_content = &self.context.functions[callee.0];
593        if !self.cur_module.function_iter(self.context).contains(callee) {
594            return Err(IrError::VerifyCallToMissingFunction(
595                callee_content.name.clone(),
596            ));
597        }
598
599        let callee_arg_types = callee_content
600            .arguments
601            .iter()
602            .map(|(_, arg_val)| {
603                if let ValueDatum::Argument(BlockArgument { ty, .. }) =
604                    &self.context.values[arg_val.0].value
605                {
606                    Ok(*ty)
607                } else {
608                    Err(IrError::VerifyArgumentValueIsNotArgument(
609                        callee_content.name.clone(),
610                    ))
611                }
612            })
613            .collect::<Result<Vec<Type>, IrError>>()?;
614
615        for (opt_caller_arg_type, callee_arg_type) in args
616            .iter()
617            .map(|val| val.get_type(self.context))
618            .zip(callee_arg_types.iter())
619        {
620            if opt_caller_arg_type.is_none() {
621                return Err(IrError::VerifyUntypedValuePassedToFunction);
622            }
623
624            let caller_arg_type = opt_caller_arg_type.as_ref().unwrap();
625            if !caller_arg_type.eq(self.context, callee_arg_type) {
626                return Err(IrError::VerifyCallArgTypeMismatch(
627                    callee_content.name.clone(),
628                    caller_arg_type.as_string(self.context),
629                    callee_arg_type.as_string(self.context),
630                ));
631            }
632        }
633
634        Ok(())
635    }
636
637    fn verify_cast_ptr(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
638        if !(val
639            .get_type(self.context)
640            .is_some_and(|ty| ty.is_ptr(self.context)))
641        {
642            let ty = val
643                .get_type(self.context)
644                .map(|ty| ty.as_string(self.context))
645                .unwrap_or("Unknown".into());
646            return Err(IrError::VerifyPtrCastFromNonPointer(ty));
647        }
648
649        if !ty.is_ptr(self.context) {
650            Err(IrError::VerifyPtrCastToNonPointer(
651                ty.as_string(self.context),
652            ))
653        } else {
654            Ok(())
655        }
656    }
657
658    fn verify_dest_args(&self, dest: &BranchToWithArgs) -> Result<(), IrError> {
659        if dest.block.num_args(self.context) != dest.args.len() {
660            return Err(IrError::VerifyBranchParamsMismatch);
661        }
662        for (arg_idx, dest_param) in dest.block.arg_iter(self.context).enumerate() {
663            match dest.args.get(arg_idx) {
664                Some(actual)
665                    if dest_param
666                        .get_type(self.context)
667                        .unwrap()
668                        .eq(self.context, &actual.get_type(self.context).unwrap()) => {}
669                _ =>
670                // TODO: https://github.com/FuelLabs/sway/pull/2880
671                {
672                    // return Err(IrError::VerifyBranchParamsMismatch)
673                }
674            }
675        }
676        Ok(())
677    }
678
679    fn verify_cbr(
680        &self,
681        cond_val: &Value,
682        true_block: &BranchToWithArgs,
683        false_block: &BranchToWithArgs,
684    ) -> Result<(), IrError> {
685        if !cond_val
686            .get_type(self.context)
687            .is(Type::is_bool, self.context)
688        {
689            Err(IrError::VerifyConditionExprNotABool)
690        } else if !self
691            .cur_function
692            .block_iter(self.context)
693            .contains(&true_block.block)
694        {
695            Err(IrError::VerifyBranchToMissingBlock(
696                self.context.blocks[true_block.block.0].label.clone(),
697            ))
698        } else if !self
699            .cur_function
700            .block_iter(self.context)
701            .contains(&false_block.block)
702        {
703            Err(IrError::VerifyBranchToMissingBlock(
704                self.context.blocks[false_block.block.0].label.clone(),
705            ))
706        } else {
707            self.verify_dest_args(true_block)
708                .and_then(|()| self.verify_dest_args(false_block))
709        }
710    }
711
712    fn verify_cmp(
713        &self,
714        _pred: &Predicate,
715        lhs_value: &Value,
716        rhs_value: &Value,
717    ) -> Result<(), IrError> {
718        // Comparisons must be between integers or equivalent pointers at this stage.
719        match (
720            lhs_value.get_type(self.context),
721            rhs_value.get_type(self.context),
722        ) {
723            (Some(lhs_ty), Some(rhs_ty)) => {
724                if !lhs_ty.eq(self.context, &rhs_ty) {
725                    Err(IrError::VerifyCmpTypeMismatch(
726                        lhs_ty.as_string(self.context),
727                        rhs_ty.as_string(self.context),
728                    ))
729                } else if lhs_ty.is_bool(self.context)
730                    || lhs_ty.is_uint(self.context)
731                    || lhs_ty.is_ptr(self.context)
732                    || lhs_ty.is_b256(self.context)
733                {
734                    Ok(())
735                } else {
736                    Err(IrError::VerifyCmpBadTypes(
737                        lhs_ty.as_string(self.context),
738                        rhs_ty.as_string(self.context),
739                    ))
740                }
741            }
742            _otherwise => Err(IrError::VerifyCmpUnknownTypes),
743        }
744    }
745
746    fn verify_contract_call(
747        &self,
748        params: &Value,
749        coins: &Value,
750        asset_id: &Value,
751        gas: &Value,
752    ) -> Result<(), IrError> {
753        if !self.context.experimental.new_encoding {
754            // - The params must be a struct with the B256 address, u64 selector and u64 address to
755            //   user args.
756            // - The coins and gas must be u64s.
757            // - The asset_id must be a B256
758            let fields = params
759                .get_type(self.context)
760                .and_then(|ty| ty.get_pointee_type(self.context))
761                .map_or_else(std::vec::Vec::new, |ty| ty.get_field_types(self.context));
762            if fields.len() != 3
763                || !fields[0].is_b256(self.context)
764                || !fields[1].is_uint64(self.context)
765                || !fields[2].is_uint64(self.context)
766            {
767                Err(IrError::VerifyContractCallBadTypes("params".to_owned()))
768            } else {
769                Ok(())
770            }
771            .and_then(|_| {
772                if coins
773                    .get_type(self.context)
774                    .is(Type::is_uint64, self.context)
775                {
776                    Ok(())
777                } else {
778                    Err(IrError::VerifyContractCallBadTypes("coins".to_owned()))
779                }
780            })
781            .and_then(|_| {
782                if asset_id
783                    .get_type(self.context)
784                    .and_then(|ty| ty.get_pointee_type(self.context))
785                    .is(Type::is_b256, self.context)
786                {
787                    Ok(())
788                } else {
789                    Err(IrError::VerifyContractCallBadTypes("asset_id".to_owned()))
790                }
791            })
792            .and_then(|_| {
793                if gas.get_type(self.context).is(Type::is_uint64, self.context) {
794                    Ok(())
795                } else {
796                    Err(IrError::VerifyContractCallBadTypes("gas".to_owned()))
797                }
798            })
799        } else {
800            Ok(())
801        }
802    }
803
804    fn verify_get_elem_ptr(
805        &self,
806        ins: &Value,
807        base: &Value,
808        elem_ptr_ty: &Type,
809        indices: &[Value],
810    ) -> Result<(), IrError> {
811        let base_ty =
812            self.get_ptr_type(base, |s| IrError::VerifyGepFromNonPointer(s, Some(*ins)))?;
813        if !base_ty.is_aggregate(self.context) {
814            return Err(IrError::VerifyGepOnNonAggregate);
815        }
816
817        let Some(elem_inner_ty) = elem_ptr_ty.get_pointee_type(self.context) else {
818            return Err(IrError::VerifyGepElementTypeNonPointer);
819        };
820
821        if indices.is_empty() {
822            return Err(IrError::VerifyGepInconsistentTypes(
823                "Empty Indices".into(),
824                Some(*base),
825            ));
826        }
827
828        let index_ty = base_ty.get_value_indexed_type(self.context, indices);
829
830        if self.opt_ty_not_eq(&Some(elem_inner_ty), &index_ty) {
831            return Err(IrError::VerifyGepInconsistentTypes(
832                format!(
833                    "Element type \"{}\" versus index type {:?}",
834                    elem_inner_ty.as_string(self.context),
835                    index_ty.map(|x| x.as_string(self.context))
836                ),
837                Some(*ins),
838            ));
839        }
840
841        Ok(())
842    }
843
844    fn verify_get_local(&self, local_var: &LocalVar) -> Result<(), IrError> {
845        if !self.context.functions[self.cur_function.0]
846            .local_storage
847            .values()
848            .any(|var| var == local_var)
849        {
850            Err(IrError::VerifyGetNonExistentLocalVarPointer)
851        } else {
852            Ok(())
853        }
854    }
855
856    fn verify_get_global(&self, global_var: &GlobalVar) -> Result<(), IrError> {
857        if !self.context.modules[self.cur_module.0]
858            .global_variables
859            .values()
860            .any(|var| var == global_var)
861        {
862            Err(IrError::VerifyGetNonExistentGlobalVarPointer)
863        } else {
864            Ok(())
865        }
866    }
867
868    fn verify_get_config(&self, module: Module, name: &str) -> Result<(), IrError> {
869        if !self.context.modules[module.0].configs.contains_key(name) {
870            Err(IrError::VerifyGetNonExistentConfigPointer)
871        } else {
872            Ok(())
873        }
874    }
875
876    fn verify_get_storage_key(&self, storage_key: &StorageKey) -> Result<(), IrError> {
877        if !self.context.modules[self.cur_module.0]
878            .storage_keys
879            .values()
880            .any(|key| key == storage_key)
881        {
882            Err(IrError::VerifyGetNonExistentStorageKeyPointer)
883        } else {
884            Ok(())
885        }
886    }
887
888    fn verify_gtf(&self, index: &Value, _tx_field_id: &u64) -> Result<(), IrError> {
889        // We should perhaps verify that _tx_field_id fits in a twelve bit immediate
890        if !index.get_type(self.context).is(Type::is_uint, self.context) {
891            Err(IrError::VerifyInvalidGtfIndexType)
892        } else {
893            Ok(())
894        }
895    }
896
897    fn verify_int_to_ptr(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
898        // We want the source value to be an integer and the destination type to be a pointer.
899        let val_ty = value
900            .get_type(self.context)
901            .ok_or(IrError::VerifyIntToPtrUnknownSourceType)?;
902        if !val_ty.is_uint(self.context) {
903            return Err(IrError::VerifyIntToPtrFromNonIntegerType(
904                val_ty.as_string(self.context),
905            ));
906        }
907        if !ty.is_ptr(self.context) {
908            return Err(IrError::VerifyIntToPtrToNonPointer(
909                ty.as_string(self.context),
910            ));
911        }
912
913        Ok(())
914    }
915
916    fn verify_load(&self, src_val: &Value) -> Result<(), IrError> {
917        // Just confirm `src_val` is a pointer.
918        self.get_ptr_type(src_val, IrError::VerifyLoadFromNonPointer)
919            .map(|_| ())
920    }
921
922    fn verify_log(&self, log_val: &Value, log_ty: &Type, log_id: &Value) -> Result<(), IrError> {
923        if !log_id
924            .get_type(self.context)
925            .is(Type::is_uint64, self.context)
926        {
927            return Err(IrError::VerifyLogId);
928        }
929
930        if self.opt_ty_not_eq(&log_val.get_type(self.context), &Some(*log_ty)) {
931            return Err(IrError::VerifyLogMismatchedTypes);
932        }
933
934        Ok(())
935    }
936
937    fn verify_mem_copy_bytes(
938        &self,
939        dst_val_ptr: &Value,
940        src_val_ptr: &Value,
941        _byte_len: &u64,
942    ) -> Result<(), IrError> {
943        // Just confirm both values are pointers.
944        self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
945            .and_then(|_| self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer))
946            .map(|_| ())
947    }
948
949    fn verify_mem_copy_val(&self, dst_val_ptr: &Value, src_val_ptr: &Value) -> Result<(), IrError> {
950        // Check both types are pointers and to the same type.
951        self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
952            .and_then(|dst_ty| {
953                self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer)
954                    .map(|src_ty| (dst_ty, src_ty))
955            })
956            .and_then(|(dst_ty, src_ty)| {
957                dst_ty
958                    .eq(self.context, &src_ty)
959                    .then_some(())
960                    .ok_or_else(|| {
961                        IrError::VerifyMemcopyMismatchedTypes(
962                            dst_ty.as_string(self.context),
963                            src_ty.as_string(self.context),
964                        )
965                    })
966            })
967    }
968
969    // dst_val_ptr must be a a pointer.
970    fn verify_mem_clear_val(&self, dst_val_ptr: &Value) -> Result<(), IrError> {
971        let _ = self.get_ptr_type(dst_val_ptr, IrError::VerifyMemClearValNonPointer)?;
972        Ok(())
973    }
974
975    fn verify_ptr_to_int(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
976        // XXX Casting pointers to integers is a low level operation which needs to be verified in
977        // the target specific verifier.  e.g., for Fuel it is assumed that b256s are 'reference
978        // types' and you can to a ptr_to_int on them, but for target agnostic IR this isn't true.
979        if !(val
980            .get_type(self.context)
981            .is_some_and(|ty| ty.is_ptr(self.context)))
982        {
983            let ty = val
984                .get_type(self.context)
985                .map(|ty| ty.as_string(self.context))
986                .unwrap_or("Unknown".into());
987            return Err(IrError::VerifyPtrCastFromNonPointer(ty));
988        }
989        if !ty.is_uint(self.context) {
990            Err(IrError::VerifyPtrToIntToNonInteger(
991                ty.as_string(self.context),
992            ))
993        } else {
994            Ok(())
995        }
996    }
997
998    fn verify_ret(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
999        if !self
1000            .cur_function
1001            .get_return_type(self.context)
1002            .eq(self.context, ty)
1003            || self.opt_ty_not_eq(&val.get_type(self.context), &Some(*ty))
1004        {
1005            Err(IrError::VerifyReturnMismatchedTypes(
1006                self.cur_function.get_name(self.context).to_string(),
1007            ))
1008        } else {
1009            Ok(())
1010        }
1011    }
1012
1013    fn verify_revert(&self, val: &Value) -> Result<(), IrError> {
1014        if !val.get_type(self.context).is(Type::is_uint64, self.context) {
1015            Err(IrError::VerifyRevertCodeBadType)
1016        } else {
1017            Ok(())
1018        }
1019    }
1020
1021    fn verify_smo(
1022        &self,
1023        recipient: &Value,
1024        message: &Value,
1025        message_size: &Value,
1026        coins: &Value,
1027    ) -> Result<(), IrError> {
1028        // Check that the first operand is a `b256` representing the recipient address.
1029        let recipient = self.get_ptr_type(recipient, IrError::VerifySmoRecipientNonPointer)?;
1030        if !recipient.is_b256(self.context) {
1031            return Err(IrError::VerifySmoRecipientBadType);
1032        }
1033
1034        // Check that the second operand is a struct with two fields
1035        let struct_ty = self.get_ptr_type(message, IrError::VerifySmoMessageNonPointer)?;
1036
1037        if !struct_ty.is_struct(self.context) {
1038            return Err(IrError::VerifySmoBadMessageType);
1039        }
1040        let fields = struct_ty.get_field_types(self.context);
1041        if fields.len() != 2 {
1042            return Err(IrError::VerifySmoBadMessageType);
1043        }
1044
1045        // Check that the second operand is a `u64` representing the message size.
1046        if !message_size
1047            .get_type(self.context)
1048            .is(Type::is_uint64, self.context)
1049        {
1050            return Err(IrError::VerifySmoMessageSize);
1051        }
1052
1053        // Check that the third operand is a `u64` representing the amount of coins being sent.
1054        if !coins
1055            .get_type(self.context)
1056            .is(Type::is_uint64, self.context)
1057        {
1058            return Err(IrError::VerifySmoCoins);
1059        }
1060
1061        Ok(())
1062    }
1063
1064    fn verify_state_clear(&self, key: &Value, number_of_slots: &Value) -> Result<(), IrError> {
1065        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1066        if !key_type.is_b256(self.context) {
1067            Err(IrError::VerifyStateKeyBadType)
1068        } else if !number_of_slots
1069            .get_type(self.context)
1070            .is(Type::is_uint, self.context)
1071        {
1072            Err(IrError::VerifyStateAccessNumOfSlots)
1073        } else {
1074            Ok(())
1075        }
1076    }
1077
1078    fn verify_state_access_quad(
1079        &self,
1080        dst_val: &Value,
1081        key: &Value,
1082        number_of_slots: &Value,
1083    ) -> Result<(), IrError> {
1084        let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStateAccessQuadNonPointer)?;
1085        if !dst_ty.is_b256(self.context) {
1086            return Err(IrError::VerifyStateDestBadType(
1087                dst_ty.as_string(self.context),
1088            ));
1089        }
1090        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1091        if !key_type.is_b256(self.context) {
1092            return Err(IrError::VerifyStateKeyBadType);
1093        }
1094        if !number_of_slots
1095            .get_type(self.context)
1096            .is(Type::is_uint, self.context)
1097        {
1098            return Err(IrError::VerifyStateAccessNumOfSlots);
1099        }
1100        Ok(())
1101    }
1102
1103    fn verify_state_load_word(&self, key: &Value) -> Result<(), IrError> {
1104        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1105        if !key_type.is_b256(self.context) {
1106            Err(IrError::VerifyStateKeyBadType)
1107        } else {
1108            Ok(())
1109        }
1110    }
1111
1112    fn verify_state_store_word(&self, dst_val: &Value, key: &Value) -> Result<(), IrError> {
1113        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1114        if !key_type.is_b256(self.context) {
1115            Err(IrError::VerifyStateKeyBadType)
1116        } else if !dst_val
1117            .get_type(self.context)
1118            .is(Type::is_uint, self.context)
1119        {
1120            Err(IrError::VerifyStateDestBadType(
1121                Type::get_uint64(self.context).as_string(self.context),
1122            ))
1123        } else {
1124            Ok(())
1125        }
1126    }
1127
1128    fn verify_store(
1129        &self,
1130        ins: &Value,
1131        dst_val: &Value,
1132        stored_val: &Value,
1133    ) -> Result<(), IrError> {
1134        let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStoreToNonPointer)?;
1135        let stored_ty = stored_val.get_type(self.context);
1136        if self.opt_ty_not_eq(&Some(dst_ty), &stored_ty) {
1137            Err(IrError::VerifyStoreMismatchedTypes(Some(*ins)))
1138        } else {
1139            Ok(())
1140        }
1141    }
1142
1143    //----------------------------------------------------------------------------------------------
1144
1145    // This is a really common operation above... calling `Value::get_type()` and then failing when
1146    // two don't match.
1147    fn opt_ty_not_eq(&self, l_ty: &Option<Type>, r_ty: &Option<Type>) -> bool {
1148        l_ty.is_none() || r_ty.is_none() || !l_ty.unwrap().eq(self.context, r_ty.as_ref().unwrap())
1149    }
1150
1151    fn get_ptr_type<F: FnOnce(String) -> IrError>(
1152        &self,
1153        val: &Value,
1154        errfn: F,
1155    ) -> Result<Type, IrError> {
1156        val.get_type(self.context)
1157            .ok_or_else(|| "unknown".to_owned())
1158            .and_then(|ptr_ty| {
1159                ptr_ty
1160                    .get_pointee_type(self.context)
1161                    .ok_or_else(|| ptr_ty.as_string(self.context))
1162            })
1163            .map_err(errfn)
1164    }
1165
1166    // Get the bit size for fixed atomic types, or None for other types.
1167    fn type_bit_size(&self, ty: &Type) -> Option<usize> {
1168        // Typically we don't want to make assumptions about the size of types in the IR.  This is
1169        // here until we reintroduce pointers and don't need to care about type sizes (and whether
1170        // they'd fit in a 64 bit register).
1171        if ty.is_unit(self.context) || ty.is_bool(self.context) {
1172            Some(1)
1173        } else if ty.is_uint(self.context) {
1174            Some(ty.get_uint_width(self.context).unwrap() as usize)
1175        } else if ty.is_b256(self.context) {
1176            Some(256)
1177        } else {
1178            None
1179        }
1180    }
1181}