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, LogEventData, Module, Pass, PassMutability, ScopedPass,
20    StorageKey, 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<(), IrError> {
50        for (module, _) in &self.modules {
51            let module = Module(module);
52            self.verify_module(module)?;
53        }
54        Ok(())
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                        log_data,
298                    } => self.verify_log(log_val, log_ty, log_id, log_data)?,
299                    FuelVmInstruction::ReadRegister(_) => (),
300                    FuelVmInstruction::JmpMem => (),
301                    FuelVmInstruction::Revert(val) => self.verify_revert(val)?,
302                    FuelVmInstruction::Smo {
303                        recipient,
304                        message,
305                        message_size,
306                        coins,
307                    } => self.verify_smo(recipient, message, message_size, coins)?,
308                    FuelVmInstruction::StateClear {
309                        key,
310                        number_of_slots,
311                    } => self.verify_state_clear(key, number_of_slots)?,
312                    FuelVmInstruction::StateLoadWord(key) => self.verify_state_load_word(key)?,
313                    FuelVmInstruction::StateLoadQuadWord {
314                        load_val: dst_val,
315                        key,
316                        number_of_slots,
317                    }
318                    | FuelVmInstruction::StateStoreQuadWord {
319                        stored_val: dst_val,
320                        key,
321                        number_of_slots,
322                    } => self.verify_state_access_quad(dst_val, key, number_of_slots)?,
323                    FuelVmInstruction::StateStoreWord {
324                        stored_val: dst_val,
325                        key,
326                    } => self.verify_state_store_word(dst_val, key)?,
327                    FuelVmInstruction::WideUnaryOp { op, result, arg } => {
328                        self.verify_wide_unary_op(op, result, arg)?
329                    }
330                    FuelVmInstruction::WideBinaryOp {
331                        op,
332                        result,
333                        arg1,
334                        arg2,
335                    } => self.verify_wide_binary_op(op, result, arg1, arg2)?,
336                    FuelVmInstruction::WideModularOp {
337                        op,
338                        result,
339                        arg1,
340                        arg2,
341                        arg3,
342                    } => self.verify_wide_modular_op(op, result, arg1, arg2, arg3)?,
343                    FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => {
344                        self.verify_wide_cmp(op, arg1, arg2)?
345                    }
346                    FuelVmInstruction::Retd { .. } => (),
347                },
348                InstOp::GetElemPtr {
349                    base,
350                    elem_ptr_ty,
351                    indices,
352                } => self.verify_get_elem_ptr(&ins, base, elem_ptr_ty, indices)?,
353                InstOp::GetLocal(local_var) => self.verify_get_local(local_var)?,
354                InstOp::GetGlobal(global_var) => self.verify_get_global(global_var)?,
355                InstOp::GetConfig(_, name) => self.verify_get_config(self.cur_module, name)?,
356                InstOp::GetStorageKey(storage_key) => self.verify_get_storage_key(storage_key)?,
357                InstOp::IntToPtr(value, ty) => self.verify_int_to_ptr(value, ty)?,
358                InstOp::Load(ptr) => self.verify_load(ptr)?,
359                InstOp::MemCopyBytes {
360                    dst_val_ptr,
361                    src_val_ptr,
362                    byte_len,
363                } => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr, byte_len)?,
364                InstOp::MemCopyVal {
365                    dst_val_ptr,
366                    src_val_ptr,
367                } => self.verify_mem_copy_val(dst_val_ptr, src_val_ptr)?,
368                InstOp::MemClearVal { dst_val_ptr } => self.verify_mem_clear_val(dst_val_ptr)?,
369                InstOp::Nop => (),
370                InstOp::PtrToInt(val, ty) => self.verify_ptr_to_int(val, ty)?,
371                InstOp::Ret(val, ty) => self.verify_ret(val, ty)?,
372                InstOp::Store {
373                    dst_val_ptr,
374                    stored_val,
375                } => self.verify_store(&ins, dst_val_ptr, stored_val)?,
376            };
377
378            // Verify the instruction metadata too.
379            self.context.verify_metadata(value_content.metadata)?;
380        }
381
382        Ok(())
383    }
384
385    fn verify_bitcast(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
386        // The bitsize of bools and unit is 1 which obviously won't match a typical uint.  LLVM
387        // would use `trunc` or `zext` to make types match sizes before casting.  Until we have
388        // similar we'll just make sure the sizes are <= 64 bits.
389        let val_ty = value
390            .get_type(self.context)
391            .ok_or(IrError::VerifyBitcastUnknownSourceType)?;
392        if self.type_bit_size(&val_ty).is_some_and(|sz| sz > 64)
393            || self.type_bit_size(ty).is_some_and(|sz| sz > 64)
394        {
395            Err(IrError::VerifyBitcastBetweenInvalidTypes(
396                val_ty.as_string(self.context),
397                ty.as_string(self.context),
398            ))
399        } else {
400            Ok(())
401        }
402    }
403
404    fn verify_unary_op(&self, op: &UnaryOpKind, arg: &Value) -> Result<(), IrError> {
405        let arg_ty = arg
406            .get_type(self.context)
407            .ok_or(IrError::VerifyUnaryOpIncorrectArgType)?;
408        match op {
409            UnaryOpKind::Not => {
410                if !arg_ty.is_uint(self.context) && !arg_ty.is_b256(self.context) {
411                    return Err(IrError::VerifyUnaryOpIncorrectArgType);
412                }
413            }
414        }
415
416        Ok(())
417    }
418
419    fn verify_wide_cmp(&self, _: &Predicate, arg1: &Value, arg2: &Value) -> Result<(), IrError> {
420        let arg1_ty = arg1
421            .get_type(self.context)
422            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
423        let arg2_ty = arg2
424            .get_type(self.context)
425            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
426
427        if arg1_ty.is_ptr(self.context) && arg2_ty.is_ptr(self.context) {
428            Ok(())
429        } else {
430            Err(IrError::VerifyBinaryOpIncorrectArgType)
431        }
432    }
433
434    fn verify_wide_modular_op(
435        &self,
436        _op: &BinaryOpKind,
437        result: &Value,
438        arg1: &Value,
439        arg2: &Value,
440        arg3: &Value,
441    ) -> Result<(), IrError> {
442        let result_ty = result
443            .get_type(self.context)
444            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
445        let arg1_ty = arg1
446            .get_type(self.context)
447            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
448        let arg2_ty = arg2
449            .get_type(self.context)
450            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
451        let arg3_ty = arg3
452            .get_type(self.context)
453            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
454
455        if !arg1_ty.is_ptr(self.context)
456            || !arg2_ty.is_ptr(self.context)
457            || !arg3_ty.is_ptr(self.context)
458            || !result_ty.is_ptr(self.context)
459        {
460            return Err(IrError::VerifyBinaryOpIncorrectArgType);
461        }
462
463        Ok(())
464    }
465
466    fn verify_wide_binary_op(
467        &self,
468        op: &BinaryOpKind,
469        result: &Value,
470        arg1: &Value,
471        arg2: &Value,
472    ) -> Result<(), IrError> {
473        let result_ty = result
474            .get_type(self.context)
475            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
476        let arg1_ty = arg1
477            .get_type(self.context)
478            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
479        let arg2_ty = arg2
480            .get_type(self.context)
481            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
482
483        match op {
484            // Shifts rhs are 64 bits
485            BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
486                if !arg1_ty.is_ptr(self.context)
487                    || !arg2_ty.is_uint64(self.context)
488                    || !result_ty.is_ptr(self.context)
489                {
490                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
491                }
492            }
493            BinaryOpKind::Add
494            | BinaryOpKind::Sub
495            | BinaryOpKind::Mul
496            | BinaryOpKind::Div
497            | BinaryOpKind::And
498            | BinaryOpKind::Or
499            | BinaryOpKind::Xor
500            | BinaryOpKind::Mod => {
501                if !arg1_ty.is_ptr(self.context)
502                    || !arg2_ty.is_ptr(self.context)
503                    || !result_ty.is_ptr(self.context)
504                {
505                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
506                }
507            }
508        }
509
510        Ok(())
511    }
512
513    fn verify_wide_unary_op(
514        &self,
515        _op: &UnaryOpKind,
516        result: &Value,
517        arg: &Value,
518    ) -> Result<(), IrError> {
519        let result_ty = result
520            .get_type(self.context)
521            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
522        let arg_ty = arg
523            .get_type(self.context)
524            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
525
526        if !arg_ty.is_ptr(self.context) || !result_ty.is_ptr(self.context) {
527            return Err(IrError::VerifyBinaryOpIncorrectArgType);
528        }
529
530        Ok(())
531    }
532
533    fn verify_binary_op(
534        &self,
535        op: &BinaryOpKind,
536        arg1: &Value,
537        arg2: &Value,
538    ) -> Result<(), IrError> {
539        let arg1_ty = arg1
540            .get_type(self.context)
541            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
542        let arg2_ty = arg2
543            .get_type(self.context)
544            .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?;
545
546        match op {
547            // Shifts can have the rhs with different type
548            BinaryOpKind::Lsh | BinaryOpKind::Rsh => {
549                let is_lhs_ok = arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context);
550                if !is_lhs_ok || !arg2_ty.is_uint(self.context) {
551                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
552                }
553            }
554            BinaryOpKind::Add | BinaryOpKind::Sub => {
555                if !(arg1_ty.eq(self.context, &arg2_ty) && arg1_ty.is_uint(self.context)
556                    || arg1_ty.is_ptr(self.context) && arg2_ty.is_uint64(self.context))
557                {
558                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
559                }
560            }
561            BinaryOpKind::Mul | BinaryOpKind::Div | BinaryOpKind::Mod => {
562                if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) {
563                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
564                }
565            }
566            BinaryOpKind::And | BinaryOpKind::Or | BinaryOpKind::Xor => {
567                if !arg1_ty.eq(self.context, &arg2_ty)
568                    || !(arg1_ty.is_uint(self.context) || arg1_ty.is_b256(self.context))
569                {
570                    return Err(IrError::VerifyBinaryOpIncorrectArgType);
571                }
572            }
573        }
574
575        Ok(())
576    }
577
578    fn verify_br(&self, dest_block: &BranchToWithArgs) -> Result<(), IrError> {
579        if !self
580            .cur_function
581            .block_iter(self.context)
582            .contains(&dest_block.block)
583        {
584            Err(IrError::VerifyBranchToMissingBlock(
585                self.context.blocks[dest_block.block.0].label.clone(),
586            ))
587        } else {
588            self.verify_dest_args(dest_block)
589        }
590    }
591
592    fn verify_call(&self, callee: &Function, args: &[Value]) -> Result<(), IrError> {
593        let callee_content = &self.context.functions[callee.0];
594        if !self.cur_module.function_iter(self.context).contains(callee) {
595            return Err(IrError::VerifyCallToMissingFunction(
596                callee_content.name.clone(),
597            ));
598        }
599
600        let callee_arg_types = callee_content
601            .arguments
602            .iter()
603            .map(|(_, arg_val)| {
604                if let ValueDatum::Argument(BlockArgument { ty, .. }) =
605                    &self.context.values[arg_val.0].value
606                {
607                    Ok(*ty)
608                } else {
609                    Err(IrError::VerifyArgumentValueIsNotArgument(
610                        callee_content.name.clone(),
611                    ))
612                }
613            })
614            .collect::<Result<Vec<Type>, IrError>>()?;
615
616        for (opt_caller_arg_type, callee_arg_type) in args
617            .iter()
618            .map(|val| val.get_type(self.context))
619            .zip(callee_arg_types.iter())
620        {
621            if opt_caller_arg_type.is_none() {
622                return Err(IrError::VerifyUntypedValuePassedToFunction);
623            }
624
625            let caller_arg_type = opt_caller_arg_type.as_ref().unwrap();
626            if !caller_arg_type.eq(self.context, callee_arg_type) {
627                return Err(IrError::VerifyCallArgTypeMismatch(
628                    callee_content.name.clone(),
629                    caller_arg_type.as_string(self.context),
630                    callee_arg_type.as_string(self.context),
631                ));
632            }
633        }
634
635        Ok(())
636    }
637
638    fn verify_cast_ptr(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
639        if !(val
640            .get_type(self.context)
641            .is_some_and(|ty| ty.is_ptr(self.context)))
642        {
643            let ty = val
644                .get_type(self.context)
645                .map(|ty| ty.as_string(self.context))
646                .unwrap_or("Unknown".into());
647            return Err(IrError::VerifyPtrCastFromNonPointer(ty));
648        }
649
650        if !ty.is_ptr(self.context) {
651            Err(IrError::VerifyPtrCastToNonPointer(
652                ty.as_string(self.context),
653            ))
654        } else {
655            Ok(())
656        }
657    }
658
659    fn verify_dest_args(&self, dest: &BranchToWithArgs) -> Result<(), IrError> {
660        if dest.block.num_args(self.context) != dest.args.len() {
661            return Err(IrError::VerifyBranchParamsMismatch);
662        }
663        for (arg_idx, dest_param) in dest.block.arg_iter(self.context).enumerate() {
664            match dest.args.get(arg_idx) {
665                Some(actual)
666                    if dest_param
667                        .get_type(self.context)
668                        .unwrap()
669                        .eq(self.context, &actual.get_type(self.context).unwrap()) => {}
670                _ =>
671                // TODO: https://github.com/FuelLabs/sway/pull/2880
672                {
673                    // return Err(IrError::VerifyBranchParamsMismatch)
674                }
675            }
676        }
677        Ok(())
678    }
679
680    fn verify_cbr(
681        &self,
682        cond_val: &Value,
683        true_block: &BranchToWithArgs,
684        false_block: &BranchToWithArgs,
685    ) -> Result<(), IrError> {
686        if !cond_val
687            .get_type(self.context)
688            .is(Type::is_bool, self.context)
689        {
690            Err(IrError::VerifyConditionExprNotABool)
691        } else if !self
692            .cur_function
693            .block_iter(self.context)
694            .contains(&true_block.block)
695        {
696            Err(IrError::VerifyBranchToMissingBlock(
697                self.context.blocks[true_block.block.0].label.clone(),
698            ))
699        } else if !self
700            .cur_function
701            .block_iter(self.context)
702            .contains(&false_block.block)
703        {
704            Err(IrError::VerifyBranchToMissingBlock(
705                self.context.blocks[false_block.block.0].label.clone(),
706            ))
707        } else {
708            self.verify_dest_args(true_block)
709                .and_then(|()| self.verify_dest_args(false_block))
710        }
711    }
712
713    fn verify_cmp(
714        &self,
715        _pred: &Predicate,
716        lhs_value: &Value,
717        rhs_value: &Value,
718    ) -> Result<(), IrError> {
719        // Comparisons must be between integers or equivalent pointers at this stage.
720        match (
721            lhs_value.get_type(self.context),
722            rhs_value.get_type(self.context),
723        ) {
724            (Some(lhs_ty), Some(rhs_ty)) => {
725                if !lhs_ty.eq(self.context, &rhs_ty) {
726                    Err(IrError::VerifyCmpTypeMismatch(
727                        lhs_ty.as_string(self.context),
728                        rhs_ty.as_string(self.context),
729                    ))
730                } else if lhs_ty.is_bool(self.context)
731                    || lhs_ty.is_uint(self.context)
732                    || lhs_ty.is_ptr(self.context)
733                    || lhs_ty.is_b256(self.context)
734                {
735                    Ok(())
736                } else {
737                    Err(IrError::VerifyCmpBadTypes(
738                        lhs_ty.as_string(self.context),
739                        rhs_ty.as_string(self.context),
740                    ))
741                }
742            }
743            _otherwise => Err(IrError::VerifyCmpUnknownTypes),
744        }
745    }
746
747    fn verify_contract_call(
748        &self,
749        params: &Value,
750        coins: &Value,
751        asset_id: &Value,
752        gas: &Value,
753    ) -> Result<(), IrError> {
754        if !self.context.experimental.new_encoding {
755            // - The params must be a struct with the B256 address, u64 selector and u64 address to
756            //   user args.
757            // - The coins and gas must be u64s.
758            // - The asset_id must be a B256
759            let fields = params
760                .get_type(self.context)
761                .and_then(|ty| ty.get_pointee_type(self.context))
762                .map_or_else(std::vec::Vec::new, |ty| ty.get_field_types(self.context));
763            if fields.len() != 3
764                || !fields[0].is_b256(self.context)
765                || !fields[1].is_uint64(self.context)
766                || !fields[2].is_uint64(self.context)
767            {
768                Err(IrError::VerifyContractCallBadTypes("params".to_owned()))
769            } else {
770                Ok(())
771            }
772            .and_then(|_| {
773                if coins
774                    .get_type(self.context)
775                    .is(Type::is_uint64, self.context)
776                {
777                    Ok(())
778                } else {
779                    Err(IrError::VerifyContractCallBadTypes("coins".to_owned()))
780                }
781            })
782            .and_then(|_| {
783                if asset_id
784                    .get_type(self.context)
785                    .and_then(|ty| ty.get_pointee_type(self.context))
786                    .is(Type::is_b256, self.context)
787                {
788                    Ok(())
789                } else {
790                    Err(IrError::VerifyContractCallBadTypes("asset_id".to_owned()))
791                }
792            })
793            .and_then(|_| {
794                if gas.get_type(self.context).is(Type::is_uint64, self.context) {
795                    Ok(())
796                } else {
797                    Err(IrError::VerifyContractCallBadTypes("gas".to_owned()))
798                }
799            })
800        } else {
801            Ok(())
802        }
803    }
804
805    fn verify_get_elem_ptr(
806        &self,
807        ins: &Value,
808        base: &Value,
809        elem_ptr_ty: &Type,
810        indices: &[Value],
811    ) -> Result<(), IrError> {
812        let base_ty =
813            self.get_ptr_type(base, |s| IrError::VerifyGepFromNonPointer(s, Some(*ins)))?;
814        if !base_ty.is_aggregate(self.context) {
815            return Err(IrError::VerifyGepOnNonAggregate);
816        }
817
818        let Some(elem_inner_ty) = elem_ptr_ty.get_pointee_type(self.context) else {
819            return Err(IrError::VerifyGepElementTypeNonPointer);
820        };
821
822        if indices.is_empty() {
823            return Err(IrError::VerifyGepInconsistentTypes(
824                "Empty Indices".into(),
825                Some(*base),
826            ));
827        }
828
829        let index_ty = base_ty.get_value_indexed_type(self.context, indices);
830
831        if self.opt_ty_not_eq(&Some(elem_inner_ty), &index_ty) {
832            return Err(IrError::VerifyGepInconsistentTypes(
833                format!(
834                    "Element type \"{}\" versus index type {:?}",
835                    elem_inner_ty.as_string(self.context),
836                    index_ty.map(|x| x.as_string(self.context))
837                ),
838                Some(*ins),
839            ));
840        }
841
842        Ok(())
843    }
844
845    fn verify_get_local(&self, local_var: &LocalVar) -> Result<(), IrError> {
846        if !self.context.functions[self.cur_function.0]
847            .local_storage
848            .values()
849            .any(|var| var == local_var)
850        {
851            Err(IrError::VerifyGetNonExistentLocalVarPointer)
852        } else {
853            Ok(())
854        }
855    }
856
857    fn verify_get_global(&self, global_var: &GlobalVar) -> Result<(), IrError> {
858        if !self.context.modules[self.cur_module.0]
859            .global_variables
860            .values()
861            .any(|var| var == global_var)
862        {
863            Err(IrError::VerifyGetNonExistentGlobalVarPointer)
864        } else {
865            Ok(())
866        }
867    }
868
869    fn verify_get_config(&self, module: Module, name: &str) -> Result<(), IrError> {
870        if !self.context.modules[module.0].configs.contains_key(name) {
871            Err(IrError::VerifyGetNonExistentConfigPointer)
872        } else {
873            Ok(())
874        }
875    }
876
877    fn verify_get_storage_key(&self, storage_key: &StorageKey) -> Result<(), IrError> {
878        if !self.context.modules[self.cur_module.0]
879            .storage_keys
880            .values()
881            .any(|key| key == storage_key)
882        {
883            Err(IrError::VerifyGetNonExistentStorageKeyPointer)
884        } else {
885            Ok(())
886        }
887    }
888
889    fn verify_gtf(&self, index: &Value, _tx_field_id: &u64) -> Result<(), IrError> {
890        // We should perhaps verify that _tx_field_id fits in a twelve bit immediate
891        if !index.get_type(self.context).is(Type::is_uint, self.context) {
892            Err(IrError::VerifyInvalidGtfIndexType)
893        } else {
894            Ok(())
895        }
896    }
897
898    fn verify_int_to_ptr(&self, value: &Value, ty: &Type) -> Result<(), IrError> {
899        // We want the source value to be an integer and the destination type to be a pointer.
900        let val_ty = value
901            .get_type(self.context)
902            .ok_or(IrError::VerifyIntToPtrUnknownSourceType)?;
903        if !val_ty.is_uint(self.context) {
904            return Err(IrError::VerifyIntToPtrFromNonIntegerType(
905                val_ty.as_string(self.context),
906            ));
907        }
908        if !ty.is_ptr(self.context) {
909            return Err(IrError::VerifyIntToPtrToNonPointer(
910                ty.as_string(self.context),
911            ));
912        }
913
914        Ok(())
915    }
916
917    fn verify_load(&self, src_val: &Value) -> Result<(), IrError> {
918        // Just confirm `src_val` is a pointer.
919        self.get_ptr_type(src_val, IrError::VerifyLoadFromNonPointer)
920            .map(|_| ())
921    }
922
923    fn verify_log(
924        &self,
925        log_val: &Value,
926        log_ty: &Type,
927        log_id: &Value,
928        log_data: &Option<LogEventData>,
929    ) -> Result<(), IrError> {
930        if !log_id
931            .get_type(self.context)
932            .is(Type::is_uint64, self.context)
933        {
934            return Err(IrError::VerifyLogId);
935        }
936
937        if self.opt_ty_not_eq(&log_val.get_type(self.context), &Some(*log_ty)) {
938            return Err(IrError::VerifyLogMismatchedTypes);
939        }
940
941        if let Some(log_data) = log_data {
942            if log_data.version() != LogEventData::CURRENT_VERSION {
943                return Err(IrError::VerifyLogEventDataVersion(log_data.version()));
944            }
945
946            if !log_data.is_event() {
947                return Err(IrError::VerifyLogEventDataInvalid(
948                    "log metadata must describe an event".into(),
949                ));
950            }
951
952            if log_data.is_indexed() {
953                if log_data.num_elements() == 0 || log_data.event_type_size() == 0 {
954                    return Err(IrError::VerifyLogEventDataInvalid(
955                        "indexed event metadata requires non-zero element count and size".into(),
956                    ));
957                }
958            } else if log_data.num_elements() != 0 || log_data.event_type_size() != 0 {
959                return Err(IrError::VerifyLogEventDataInvalid(
960                    "non-indexed event metadata must not include element size or count".into(),
961                ));
962            }
963        }
964
965        Ok(())
966    }
967
968    fn verify_mem_copy_bytes(
969        &self,
970        dst_val_ptr: &Value,
971        src_val_ptr: &Value,
972        _byte_len: &u64,
973    ) -> Result<(), IrError> {
974        // Just confirm both values are pointers.
975        self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
976            .and_then(|_| self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer))
977            .map(|_| ())
978    }
979
980    fn verify_mem_copy_val(&self, dst_val_ptr: &Value, src_val_ptr: &Value) -> Result<(), IrError> {
981        // Check both types are pointers and to the same type.
982        self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer)
983            .and_then(|dst_ty| {
984                self.get_ptr_type(src_val_ptr, IrError::VerifyMemcopyNonPointer)
985                    .map(|src_ty| (dst_ty, src_ty))
986            })
987            .and_then(|(dst_ty, src_ty)| {
988                dst_ty
989                    .eq(self.context, &src_ty)
990                    .then_some(())
991                    .ok_or_else(|| {
992                        IrError::VerifyMemcopyMismatchedTypes(
993                            dst_ty.as_string(self.context),
994                            src_ty.as_string(self.context),
995                        )
996                    })
997            })
998    }
999
1000    // dst_val_ptr must be a a pointer.
1001    fn verify_mem_clear_val(&self, dst_val_ptr: &Value) -> Result<(), IrError> {
1002        let _ = self.get_ptr_type(dst_val_ptr, IrError::VerifyMemClearValNonPointer)?;
1003        Ok(())
1004    }
1005
1006    fn verify_ptr_to_int(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
1007        // XXX Casting pointers to integers is a low level operation which needs to be verified in
1008        // the target specific verifier.  e.g., for Fuel it is assumed that b256s are 'reference
1009        // types' and you can to a ptr_to_int on them, but for target agnostic IR this isn't true.
1010        if !(val
1011            .get_type(self.context)
1012            .is_some_and(|ty| ty.is_ptr(self.context)))
1013        {
1014            let ty = val
1015                .get_type(self.context)
1016                .map(|ty| ty.as_string(self.context))
1017                .unwrap_or("Unknown".into());
1018            return Err(IrError::VerifyPtrCastFromNonPointer(ty));
1019        }
1020        if !ty.is_uint(self.context) {
1021            Err(IrError::VerifyPtrToIntToNonInteger(
1022                ty.as_string(self.context),
1023            ))
1024        } else {
1025            Ok(())
1026        }
1027    }
1028
1029    fn verify_ret(&self, val: &Value, ty: &Type) -> Result<(), IrError> {
1030        if !self
1031            .cur_function
1032            .get_return_type(self.context)
1033            .eq(self.context, ty)
1034            || self.opt_ty_not_eq(&val.get_type(self.context), &Some(*ty))
1035        {
1036            Err(IrError::VerifyReturnMismatchedTypes(
1037                self.cur_function.get_name(self.context).to_string(),
1038            ))
1039        } else {
1040            Ok(())
1041        }
1042    }
1043
1044    fn verify_revert(&self, val: &Value) -> Result<(), IrError> {
1045        if !val.get_type(self.context).is(Type::is_uint64, self.context) {
1046            Err(IrError::VerifyRevertCodeBadType)
1047        } else {
1048            Ok(())
1049        }
1050    }
1051
1052    fn verify_smo(
1053        &self,
1054        recipient: &Value,
1055        message: &Value,
1056        message_size: &Value,
1057        coins: &Value,
1058    ) -> Result<(), IrError> {
1059        // Check that the first operand is a `b256` representing the recipient address.
1060        let recipient = self.get_ptr_type(recipient, IrError::VerifySmoRecipientNonPointer)?;
1061        if !recipient.is_b256(self.context) {
1062            return Err(IrError::VerifySmoRecipientBadType);
1063        }
1064
1065        // Check that the second operand is a struct with two fields
1066        let struct_ty = self.get_ptr_type(message, IrError::VerifySmoMessageNonPointer)?;
1067
1068        if !struct_ty.is_struct(self.context) {
1069            return Err(IrError::VerifySmoBadMessageType);
1070        }
1071        let fields = struct_ty.get_field_types(self.context);
1072        if fields.len() != 2 {
1073            return Err(IrError::VerifySmoBadMessageType);
1074        }
1075
1076        // Check that the second operand is a `u64` representing the message size.
1077        if !message_size
1078            .get_type(self.context)
1079            .is(Type::is_uint64, self.context)
1080        {
1081            return Err(IrError::VerifySmoMessageSize);
1082        }
1083
1084        // Check that the third operand is a `u64` representing the amount of coins being sent.
1085        if !coins
1086            .get_type(self.context)
1087            .is(Type::is_uint64, self.context)
1088        {
1089            return Err(IrError::VerifySmoCoins);
1090        }
1091
1092        Ok(())
1093    }
1094
1095    fn verify_state_clear(&self, key: &Value, number_of_slots: &Value) -> Result<(), IrError> {
1096        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1097        if !key_type.is_b256(self.context) {
1098            Err(IrError::VerifyStateKeyBadType)
1099        } else if !number_of_slots
1100            .get_type(self.context)
1101            .is(Type::is_uint, self.context)
1102        {
1103            Err(IrError::VerifyStateAccessNumOfSlots)
1104        } else {
1105            Ok(())
1106        }
1107    }
1108
1109    fn verify_state_access_quad(
1110        &self,
1111        dst_val: &Value,
1112        key: &Value,
1113        number_of_slots: &Value,
1114    ) -> Result<(), IrError> {
1115        let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStateAccessQuadNonPointer)?;
1116        if !dst_ty.is_b256(self.context) {
1117            return Err(IrError::VerifyStateDestBadType(
1118                dst_ty.as_string(self.context),
1119            ));
1120        }
1121        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1122        if !key_type.is_b256(self.context) {
1123            return Err(IrError::VerifyStateKeyBadType);
1124        }
1125        if !number_of_slots
1126            .get_type(self.context)
1127            .is(Type::is_uint, self.context)
1128        {
1129            return Err(IrError::VerifyStateAccessNumOfSlots);
1130        }
1131        Ok(())
1132    }
1133
1134    fn verify_state_load_word(&self, key: &Value) -> Result<(), IrError> {
1135        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1136        if !key_type.is_b256(self.context) {
1137            Err(IrError::VerifyStateKeyBadType)
1138        } else {
1139            Ok(())
1140        }
1141    }
1142
1143    fn verify_state_store_word(&self, dst_val: &Value, key: &Value) -> Result<(), IrError> {
1144        let key_type = self.get_ptr_type(key, IrError::VerifyStateKeyNonPointer)?;
1145        if !key_type.is_b256(self.context) {
1146            Err(IrError::VerifyStateKeyBadType)
1147        } else if !dst_val
1148            .get_type(self.context)
1149            .is(Type::is_uint, self.context)
1150        {
1151            Err(IrError::VerifyStateDestBadType(
1152                Type::get_uint64(self.context).as_string(self.context),
1153            ))
1154        } else {
1155            Ok(())
1156        }
1157    }
1158
1159    fn verify_store(
1160        &self,
1161        ins: &Value,
1162        dst_val: &Value,
1163        stored_val: &Value,
1164    ) -> Result<(), IrError> {
1165        let dst_ty = self.get_ptr_type(dst_val, IrError::VerifyStoreToNonPointer)?;
1166        let stored_ty = stored_val.get_type(self.context);
1167        if self.opt_ty_not_eq(&Some(dst_ty), &stored_ty) {
1168            Err(IrError::VerifyStoreMismatchedTypes(Some(*ins)))
1169        } else {
1170            Ok(())
1171        }
1172    }
1173
1174    //----------------------------------------------------------------------------------------------
1175
1176    // This is a really common operation above... calling `Value::get_type()` and then failing when
1177    // two don't match.
1178    fn opt_ty_not_eq(&self, l_ty: &Option<Type>, r_ty: &Option<Type>) -> bool {
1179        l_ty.is_none() || r_ty.is_none() || !l_ty.unwrap().eq(self.context, r_ty.as_ref().unwrap())
1180    }
1181
1182    fn get_ptr_type<F: FnOnce(String) -> IrError>(
1183        &self,
1184        val: &Value,
1185        errfn: F,
1186    ) -> Result<Type, IrError> {
1187        val.get_type(self.context)
1188            .ok_or_else(|| "unknown".to_owned())
1189            .and_then(|ptr_ty| {
1190                ptr_ty
1191                    .get_pointee_type(self.context)
1192                    .ok_or_else(|| ptr_ty.as_string(self.context))
1193            })
1194            .map_err(errfn)
1195    }
1196
1197    // Get the bit size for fixed atomic types, or None for other types.
1198    fn type_bit_size(&self, ty: &Type) -> Option<usize> {
1199        // Typically we don't want to make assumptions about the size of types in the IR.  This is
1200        // here until we reintroduce pointers and don't need to care about type sizes (and whether
1201        // they'd fit in a 64 bit register).
1202        if ty.is_unit(self.context) || ty.is_bool(self.context) {
1203            Some(1)
1204        } else if ty.is_uint(self.context) {
1205            Some(ty.get_uint_width(self.context).unwrap() as usize)
1206        } else if ty.is_b256(self.context) {
1207            Some(256)
1208        } else {
1209            None
1210        }
1211    }
1212}