Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/cranelift/codegen/src/verifier/mod.rs
1693 views
1
//! A verifier for ensuring that functions are well formed.
2
//! It verifies:
3
//!
4
//! block integrity
5
//!
6
//! - All instructions reached from the `block_insts` iterator must belong to
7
//! the block as reported by `inst_block()`.
8
//! - Every block must end in a terminator instruction, and no other instruction
9
//! can be a terminator.
10
//! - Every value in the `block_params` iterator belongs to the block as reported by `value_block`.
11
//!
12
//! Instruction integrity
13
//!
14
//! - The instruction format must match the opcode.
15
//! - All result values must be created for multi-valued instructions.
16
//! - All referenced entities must exist. (Values, blocks, stack slots, ...)
17
//! - Instructions must not reference (eg. branch to) the entry block.
18
//!
19
//! SSA form
20
//!
21
//! - Values must be defined by an instruction that exists and that is inserted in
22
//! a block, or be an argument of an existing block.
23
//! - Values used by an instruction must dominate the instruction.
24
//!
25
//! Control flow graph and dominator tree integrity:
26
//!
27
//! - All predecessors in the CFG must be branches to the block.
28
//! - All branches to a block must be present in the CFG.
29
//! - A recomputed dominator tree is identical to the existing one.
30
//! - The entry block must not be a cold block.
31
//!
32
//! Type checking
33
//!
34
//! - Compare input and output values against the opcode's type constraints.
35
//! For polymorphic opcodes, determine the controlling type variable first.
36
//! - Branches and jumps must pass arguments to destination blocks that match the
37
//! expected types exactly. The number of arguments must match.
38
//! - All blocks in a jump table must take no arguments.
39
//! - Function calls are type checked against their signature.
40
//! - The entry block must take arguments that match the signature of the current
41
//! function.
42
//! - All return instructions must have return value operands matching the current
43
//! function signature.
44
//!
45
//! Global values
46
//!
47
//! - Detect cycles in global values.
48
//! - Detect use of 'vmctx' global value when no corresponding parameter is defined.
49
//!
50
//! Memory types
51
//!
52
//! - Ensure that struct fields are in offset order.
53
//! - Ensure that struct fields are completely within the overall
54
//! struct size, and do not overlap.
55
//!
56
//! TODO:
57
//! Ad hoc checking
58
//!
59
//! - Stack slot loads and stores must be in-bounds.
60
//! - Immediate constraints for certain opcodes, like `udiv_imm v3, 0`.
61
//! - `Insertlane` and `extractlane` instructions have immediate lane numbers that must be in
62
//! range for their polymorphic type.
63
//! - Swizzle and shuffle instructions take a variable number of lane arguments. The number
64
//! of arguments must match the destination type, and the lane indexes must be in range.
65
66
use crate::dbg::DisplayList;
67
use crate::dominator_tree::DominatorTree;
68
use crate::entity::SparseSet;
69
use crate::flowgraph::{BlockPredecessor, ControlFlowGraph};
70
use crate::ir::ExceptionTableItem;
71
use crate::ir::entities::AnyEntity;
72
use crate::ir::instructions::{CallInfo, InstructionFormat, ResolvedConstraint};
73
use crate::ir::{self, ArgumentExtension, BlockArg, ExceptionTable};
74
use crate::ir::{
75
ArgumentPurpose, Block, Constant, DynamicStackSlot, FuncRef, Function, GlobalValue, Inst,
76
JumpTable, MemFlags, MemoryTypeData, Opcode, SigRef, StackSlot, Type, Value, ValueDef,
77
ValueList, types,
78
};
79
use crate::isa::TargetIsa;
80
use crate::print_errors::pretty_verifier_error;
81
use crate::settings::FlagsOrIsa;
82
use crate::timing;
83
use alloc::collections::BTreeSet;
84
use alloc::string::{String, ToString};
85
use alloc::vec::Vec;
86
use core::fmt::{self, Display, Formatter};
87
88
/// A verifier error.
89
#[derive(Debug, PartialEq, Eq, Clone)]
90
pub struct VerifierError {
91
/// The entity causing the verifier error.
92
pub location: AnyEntity,
93
/// Optionally provide some context for the given location; e.g., for `inst42` provide
94
/// `Some("v3 = iconst.i32 0")` for more comprehensible errors.
95
pub context: Option<String>,
96
/// The error message.
97
pub message: String,
98
}
99
100
// This is manually implementing Error and Display instead of using thiserror to reduce the amount
101
// of dependencies used by Cranelift.
102
impl std::error::Error for VerifierError {}
103
104
impl Display for VerifierError {
105
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
106
match &self.context {
107
None => write!(f, "{}: {}", self.location, self.message),
108
Some(context) => write!(f, "{} ({}): {}", self.location, context, self.message),
109
}
110
}
111
}
112
113
/// Convenience converter for making error-reporting less verbose.
114
///
115
/// Converts a tuple of `(location, context, message)` to a `VerifierError`.
116
/// ```
117
/// use cranelift_codegen::verifier::VerifierErrors;
118
/// use cranelift_codegen::ir::Inst;
119
/// let mut errors = VerifierErrors::new();
120
/// errors.report((Inst::from_u32(42), "v3 = iadd v1, v2", "iadd cannot be used with values of this type"));
121
/// // note the double parenthenses to use this syntax
122
/// ```
123
impl<L, C, M> From<(L, C, M)> for VerifierError
124
where
125
L: Into<AnyEntity>,
126
C: Into<String>,
127
M: Into<String>,
128
{
129
fn from(items: (L, C, M)) -> Self {
130
let (location, context, message) = items;
131
Self {
132
location: location.into(),
133
context: Some(context.into()),
134
message: message.into(),
135
}
136
}
137
}
138
139
/// Convenience converter for making error-reporting less verbose.
140
///
141
/// Same as above but without `context`.
142
impl<L, M> From<(L, M)> for VerifierError
143
where
144
L: Into<AnyEntity>,
145
M: Into<String>,
146
{
147
fn from(items: (L, M)) -> Self {
148
let (location, message) = items;
149
Self {
150
location: location.into(),
151
context: None,
152
message: message.into(),
153
}
154
}
155
}
156
157
/// Result of a step in the verification process.
158
///
159
/// Functions that return `VerifierStepResult` should also take a
160
/// mutable reference to `VerifierErrors` as argument in order to report
161
/// errors.
162
///
163
/// Here, `Ok` represents a step that **did not lead to a fatal error**,
164
/// meaning that the verification process may continue. However, other (non-fatal)
165
/// errors might have been reported through the previously mentioned `VerifierErrors`
166
/// argument.
167
pub type VerifierStepResult = Result<(), ()>;
168
169
/// Result of a verification operation.
170
///
171
/// Unlike `VerifierStepResult` which may be `Ok` while still having reported
172
/// errors, this type always returns `Err` if an error (fatal or not) was reported.
173
pub type VerifierResult<T> = Result<T, VerifierErrors>;
174
175
/// List of verifier errors.
176
#[derive(Debug, Default, PartialEq, Eq, Clone)]
177
pub struct VerifierErrors(pub Vec<VerifierError>);
178
179
// This is manually implementing Error and Display instead of using thiserror to reduce the amount
180
// of dependencies used by Cranelift.
181
impl std::error::Error for VerifierErrors {}
182
183
impl VerifierErrors {
184
/// Return a new `VerifierErrors` struct.
185
#[inline]
186
pub fn new() -> Self {
187
Self(Vec::new())
188
}
189
190
/// Return whether no errors were reported.
191
#[inline]
192
pub fn is_empty(&self) -> bool {
193
self.0.is_empty()
194
}
195
196
/// Return whether one or more errors were reported.
197
#[inline]
198
pub fn has_error(&self) -> bool {
199
!self.0.is_empty()
200
}
201
202
/// Return a `VerifierStepResult` that is fatal if at least one error was reported,
203
/// and non-fatal otherwise.
204
#[inline]
205
pub fn as_result(&self) -> VerifierStepResult {
206
if self.is_empty() { Ok(()) } else { Err(()) }
207
}
208
209
/// Report an error, adding it to the list of errors.
210
pub fn report(&mut self, error: impl Into<VerifierError>) {
211
self.0.push(error.into());
212
}
213
214
/// Report a fatal error and return `Err`.
215
pub fn fatal(&mut self, error: impl Into<VerifierError>) -> VerifierStepResult {
216
self.report(error);
217
Err(())
218
}
219
220
/// Report a non-fatal error and return `Ok`.
221
pub fn nonfatal(&mut self, error: impl Into<VerifierError>) -> VerifierStepResult {
222
self.report(error);
223
Ok(())
224
}
225
}
226
227
impl From<Vec<VerifierError>> for VerifierErrors {
228
fn from(v: Vec<VerifierError>) -> Self {
229
Self(v)
230
}
231
}
232
233
impl From<VerifierErrors> for Vec<VerifierError> {
234
fn from(errors: VerifierErrors) -> Vec<VerifierError> {
235
errors.0
236
}
237
}
238
239
impl From<VerifierErrors> for VerifierResult<()> {
240
fn from(errors: VerifierErrors) -> VerifierResult<()> {
241
if errors.is_empty() {
242
Ok(())
243
} else {
244
Err(errors)
245
}
246
}
247
}
248
249
impl Display for VerifierErrors {
250
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
251
for err in &self.0 {
252
writeln!(f, "- {err}")?;
253
}
254
Ok(())
255
}
256
}
257
258
/// Verify `func`.
259
pub fn verify_function<'a, FOI: Into<FlagsOrIsa<'a>>>(
260
func: &Function,
261
fisa: FOI,
262
) -> VerifierResult<()> {
263
let _tt = timing::verifier();
264
let mut errors = VerifierErrors::default();
265
let verifier = Verifier::new(func, fisa.into());
266
let result = verifier.run(&mut errors);
267
if errors.is_empty() {
268
result.unwrap();
269
Ok(())
270
} else {
271
Err(errors)
272
}
273
}
274
275
/// Verify `func` after checking the integrity of associated context data structures `cfg` and
276
/// `domtree`.
277
pub fn verify_context<'a, FOI: Into<FlagsOrIsa<'a>>>(
278
func: &Function,
279
cfg: &ControlFlowGraph,
280
domtree: &DominatorTree,
281
fisa: FOI,
282
errors: &mut VerifierErrors,
283
) -> VerifierStepResult {
284
let _tt = timing::verifier();
285
let verifier = Verifier::new(func, fisa.into());
286
if cfg.is_valid() {
287
verifier.cfg_integrity(cfg, errors)?;
288
}
289
if domtree.is_valid() {
290
verifier.domtree_integrity(domtree, errors)?;
291
}
292
verifier.run(errors)
293
}
294
295
#[derive(Clone, Copy, Debug)]
296
enum BlockCallTargetType {
297
Normal,
298
ExNormalRet,
299
Exception,
300
}
301
302
struct Verifier<'a> {
303
func: &'a Function,
304
expected_cfg: ControlFlowGraph,
305
expected_domtree: DominatorTree,
306
isa: Option<&'a dyn TargetIsa>,
307
}
308
309
impl<'a> Verifier<'a> {
310
pub fn new(func: &'a Function, fisa: FlagsOrIsa<'a>) -> Self {
311
let expected_cfg = ControlFlowGraph::with_function(func);
312
let expected_domtree = DominatorTree::with_function(func, &expected_cfg);
313
Self {
314
func,
315
expected_cfg,
316
expected_domtree,
317
isa: fisa.isa,
318
}
319
}
320
321
/// Determine a contextual error string for an instruction.
322
#[inline]
323
fn context(&self, inst: Inst) -> String {
324
self.func.dfg.display_inst(inst).to_string()
325
}
326
327
// Check for:
328
// - cycles in the global value declarations.
329
// - use of 'vmctx' when no special parameter declares it.
330
fn verify_global_values(&self, errors: &mut VerifierErrors) -> VerifierStepResult {
331
let mut cycle_seen = false;
332
let mut seen = SparseSet::new();
333
334
'gvs: for gv in self.func.global_values.keys() {
335
seen.clear();
336
seen.insert(gv);
337
338
let mut cur = gv;
339
loop {
340
match self.func.global_values[cur] {
341
ir::GlobalValueData::Load { base, .. }
342
| ir::GlobalValueData::IAddImm { base, .. } => {
343
if seen.insert(base).is_some() {
344
if !cycle_seen {
345
errors.report((
346
gv,
347
format!("global value cycle: {}", DisplayList(seen.as_slice())),
348
));
349
// ensures we don't report the cycle multiple times
350
cycle_seen = true;
351
}
352
continue 'gvs;
353
}
354
355
cur = base;
356
}
357
_ => break,
358
}
359
}
360
361
match self.func.global_values[gv] {
362
ir::GlobalValueData::VMContext { .. } => {
363
if self
364
.func
365
.special_param(ir::ArgumentPurpose::VMContext)
366
.is_none()
367
{
368
errors.report((gv, format!("undeclared vmctx reference {gv}")));
369
}
370
}
371
ir::GlobalValueData::IAddImm {
372
base, global_type, ..
373
} => {
374
if !global_type.is_int() {
375
errors.report((
376
gv,
377
format!("iadd_imm global value with non-int type {global_type}"),
378
));
379
} else if let Some(isa) = self.isa {
380
let base_type = self.func.global_values[base].global_type(isa);
381
if global_type != base_type {
382
errors.report((
383
gv,
384
format!(
385
"iadd_imm type {global_type} differs from operand type {base_type}"
386
),
387
));
388
}
389
}
390
}
391
ir::GlobalValueData::Load { base, .. } => {
392
if let Some(isa) = self.isa {
393
let base_type = self.func.global_values[base].global_type(isa);
394
let pointer_type = isa.pointer_type();
395
if base_type != pointer_type {
396
errors.report((
397
gv,
398
format!(
399
"base {base} has type {base_type}, which is not the pointer type {pointer_type}"
400
),
401
));
402
}
403
}
404
}
405
_ => {}
406
}
407
}
408
409
// Invalid global values shouldn't stop us from verifying the rest of the function
410
Ok(())
411
}
412
413
fn verify_memory_types(&self, errors: &mut VerifierErrors) -> VerifierStepResult {
414
// Verify that all fields are statically-sized and lie within
415
// the struct, do not overlap, and are in offset order
416
for (mt, mt_data) in &self.func.memory_types {
417
match mt_data {
418
MemoryTypeData::Struct { size, fields } => {
419
let mut last_offset = 0;
420
for field in fields {
421
if field.offset < last_offset {
422
errors.report((
423
mt,
424
format!(
425
"memory type {} has a field at offset {}, which is out-of-order",
426
mt, field.offset
427
),
428
));
429
}
430
last_offset = match field.offset.checked_add(u64::from(field.ty.bytes())) {
431
Some(o) => o,
432
None => {
433
errors.report((
434
mt,
435
format!(
436
"memory type {} has a field at offset {} of size {}; offset plus size overflows a u64",
437
mt, field.offset, field.ty.bytes()),
438
));
439
break;
440
}
441
};
442
443
if last_offset > *size {
444
errors.report((
445
mt,
446
format!(
447
"memory type {} has a field at offset {} of size {} that overflows the struct size {}",
448
mt, field.offset, field.ty.bytes(), *size),
449
));
450
}
451
}
452
}
453
_ => {}
454
}
455
}
456
457
Ok(())
458
}
459
460
/// Check that the given block can be encoded as a BB, by checking that only
461
/// branching instructions are ending the block.
462
fn encodable_as_bb(&self, block: Block, errors: &mut VerifierErrors) -> VerifierStepResult {
463
match self.func.is_block_basic(block) {
464
Ok(()) => Ok(()),
465
Err((inst, message)) => errors.fatal((inst, self.context(inst), message)),
466
}
467
}
468
469
fn block_integrity(
470
&self,
471
block: Block,
472
inst: Inst,
473
errors: &mut VerifierErrors,
474
) -> VerifierStepResult {
475
let is_terminator = self.func.dfg.insts[inst].opcode().is_terminator();
476
let is_last_inst = self.func.layout.last_inst(block) == Some(inst);
477
478
if is_terminator && !is_last_inst {
479
// Terminating instructions only occur at the end of blocks.
480
return errors.fatal((
481
inst,
482
self.context(inst),
483
format!("a terminator instruction was encountered before the end of {block}"),
484
));
485
}
486
if is_last_inst && !is_terminator {
487
return errors.fatal((block, "block does not end in a terminator instruction"));
488
}
489
490
// Instructions belong to the correct block.
491
let inst_block = self.func.layout.inst_block(inst);
492
if inst_block != Some(block) {
493
return errors.fatal((
494
inst,
495
self.context(inst),
496
format!("should belong to {block} not {inst_block:?}"),
497
));
498
}
499
500
// Parameters belong to the correct block.
501
for &arg in self.func.dfg.block_params(block) {
502
match self.func.dfg.value_def(arg) {
503
ValueDef::Param(arg_block, _) => {
504
if block != arg_block {
505
return errors.fatal((arg, format!("does not belong to {block}")));
506
}
507
}
508
_ => {
509
return errors.fatal((arg, "expected an argument, found a result"));
510
}
511
}
512
}
513
514
Ok(())
515
}
516
517
fn instruction_integrity(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {
518
let inst_data = &self.func.dfg.insts[inst];
519
let dfg = &self.func.dfg;
520
521
// The instruction format matches the opcode
522
if inst_data.opcode().format() != InstructionFormat::from(inst_data) {
523
return errors.fatal((
524
inst,
525
self.context(inst),
526
"instruction opcode doesn't match instruction format",
527
));
528
}
529
530
let expected_num_results = dfg.num_expected_results_for_verifier(inst);
531
532
// All result values for multi-valued instructions are created
533
let got_results = dfg.inst_results(inst).len();
534
if got_results != expected_num_results {
535
return errors.fatal((
536
inst,
537
self.context(inst),
538
format!("expected {expected_num_results} result values, found {got_results}"),
539
));
540
}
541
542
self.verify_entity_references(inst, errors)
543
}
544
545
fn verify_entity_references(
546
&self,
547
inst: Inst,
548
errors: &mut VerifierErrors,
549
) -> VerifierStepResult {
550
use crate::ir::instructions::InstructionData::*;
551
552
for arg in self.func.dfg.inst_values(inst) {
553
self.verify_inst_arg(inst, arg, errors)?;
554
555
// All used values must be attached to something.
556
let original = self.func.dfg.resolve_aliases(arg);
557
if !self.func.dfg.value_is_attached(original) {
558
errors.report((
559
inst,
560
self.context(inst),
561
format!("argument {arg} -> {original} is not attached"),
562
));
563
}
564
}
565
566
for &res in self.func.dfg.inst_results(inst) {
567
self.verify_inst_result(inst, res, errors)?;
568
}
569
570
match self.func.dfg.insts[inst] {
571
MultiAry { ref args, .. } => {
572
self.verify_value_list(inst, args, errors)?;
573
}
574
Jump { destination, .. } => {
575
self.verify_block(inst, destination.block(&self.func.dfg.value_lists), errors)?;
576
}
577
Brif {
578
arg,
579
blocks: [block_then, block_else],
580
..
581
} => {
582
self.verify_value(inst, arg, errors)?;
583
self.verify_block(inst, block_then.block(&self.func.dfg.value_lists), errors)?;
584
self.verify_block(inst, block_else.block(&self.func.dfg.value_lists), errors)?;
585
}
586
BranchTable { table, .. } => {
587
self.verify_jump_table(inst, table, errors)?;
588
}
589
Call {
590
func_ref, ref args, ..
591
} => {
592
self.verify_func_ref(inst, func_ref, errors)?;
593
self.verify_value_list(inst, args, errors)?;
594
}
595
CallIndirect {
596
sig_ref, ref args, ..
597
} => {
598
self.verify_sig_ref(inst, sig_ref, errors)?;
599
self.verify_value_list(inst, args, errors)?;
600
}
601
TryCall {
602
func_ref,
603
ref args,
604
exception,
605
..
606
} => {
607
self.verify_func_ref(inst, func_ref, errors)?;
608
self.verify_value_list(inst, args, errors)?;
609
self.verify_exception_table(inst, exception, errors)?;
610
self.verify_exception_compatible_abi(inst, exception, errors)?;
611
}
612
TryCallIndirect {
613
ref args,
614
exception,
615
..
616
} => {
617
self.verify_value_list(inst, args, errors)?;
618
self.verify_exception_table(inst, exception, errors)?;
619
self.verify_exception_compatible_abi(inst, exception, errors)?;
620
}
621
FuncAddr { func_ref, .. } => {
622
self.verify_func_ref(inst, func_ref, errors)?;
623
}
624
StackLoad { stack_slot, .. } | StackStore { stack_slot, .. } => {
625
self.verify_stack_slot(inst, stack_slot, errors)?;
626
}
627
DynamicStackLoad {
628
dynamic_stack_slot, ..
629
}
630
| DynamicStackStore {
631
dynamic_stack_slot, ..
632
} => {
633
self.verify_dynamic_stack_slot(inst, dynamic_stack_slot, errors)?;
634
}
635
UnaryGlobalValue { global_value, .. } => {
636
self.verify_global_value(inst, global_value, errors)?;
637
}
638
NullAry {
639
opcode: Opcode::GetPinnedReg,
640
}
641
| Unary {
642
opcode: Opcode::SetPinnedReg,
643
..
644
} => {
645
if let Some(isa) = &self.isa {
646
if !isa.flags().enable_pinned_reg() {
647
return errors.fatal((
648
inst,
649
self.context(inst),
650
"GetPinnedReg/SetPinnedReg cannot be used without enable_pinned_reg",
651
));
652
}
653
} else {
654
return errors.fatal((
655
inst,
656
self.context(inst),
657
"GetPinnedReg/SetPinnedReg need an ISA!",
658
));
659
}
660
}
661
NullAry {
662
opcode: Opcode::GetFramePointer | Opcode::GetReturnAddress,
663
} => {
664
if let Some(isa) = &self.isa {
665
// Backends may already rely on this check implicitly, so do
666
// not relax it without verifying that it is safe to do so.
667
if !isa.flags().preserve_frame_pointers() {
668
return errors.fatal((
669
inst,
670
self.context(inst),
671
"`get_frame_pointer`/`get_return_address` cannot be used without \
672
enabling `preserve_frame_pointers`",
673
));
674
}
675
} else {
676
return errors.fatal((
677
inst,
678
self.context(inst),
679
"`get_frame_pointer`/`get_return_address` require an ISA!",
680
));
681
}
682
}
683
LoadNoOffset {
684
opcode: Opcode::Bitcast,
685
flags,
686
arg,
687
} => {
688
self.verify_bitcast(inst, flags, arg, errors)?;
689
}
690
LoadNoOffset { opcode, arg, .. } if opcode.can_load() => {
691
self.verify_is_address(inst, arg, errors)?;
692
}
693
Load { opcode, arg, .. } if opcode.can_load() => {
694
self.verify_is_address(inst, arg, errors)?;
695
}
696
AtomicCas {
697
opcode,
698
args: [p, _, _],
699
..
700
} if opcode.can_load() || opcode.can_store() => {
701
self.verify_is_address(inst, p, errors)?;
702
}
703
AtomicRmw {
704
opcode,
705
args: [p, _],
706
..
707
} if opcode.can_load() || opcode.can_store() => {
708
self.verify_is_address(inst, p, errors)?;
709
}
710
Store {
711
opcode,
712
args: [_, p],
713
..
714
} if opcode.can_store() => {
715
self.verify_is_address(inst, p, errors)?;
716
}
717
StoreNoOffset {
718
opcode,
719
args: [_, p],
720
..
721
} if opcode.can_store() => {
722
self.verify_is_address(inst, p, errors)?;
723
}
724
UnaryConst {
725
opcode: opcode @ (Opcode::Vconst | Opcode::F128const),
726
constant_handle,
727
..
728
} => {
729
self.verify_constant_size(inst, opcode, constant_handle, errors)?;
730
}
731
732
ExceptionHandlerAddress { block, imm, .. } => {
733
self.verify_block(inst, block, errors)?;
734
self.verify_try_call_handler_index(inst, block, imm.into(), errors)?;
735
}
736
737
// Exhaustive list so we can't forget to add new formats
738
AtomicCas { .. }
739
| AtomicRmw { .. }
740
| LoadNoOffset { .. }
741
| StoreNoOffset { .. }
742
| Unary { .. }
743
| UnaryConst { .. }
744
| UnaryImm { .. }
745
| UnaryIeee16 { .. }
746
| UnaryIeee32 { .. }
747
| UnaryIeee64 { .. }
748
| Binary { .. }
749
| BinaryImm8 { .. }
750
| BinaryImm64 { .. }
751
| Ternary { .. }
752
| TernaryImm8 { .. }
753
| Shuffle { .. }
754
| IntAddTrap { .. }
755
| IntCompare { .. }
756
| IntCompareImm { .. }
757
| FloatCompare { .. }
758
| Load { .. }
759
| Store { .. }
760
| Trap { .. }
761
| CondTrap { .. }
762
| NullAry { .. } => {}
763
}
764
765
Ok(())
766
}
767
768
fn verify_block(
769
&self,
770
loc: impl Into<AnyEntity>,
771
e: Block,
772
errors: &mut VerifierErrors,
773
) -> VerifierStepResult {
774
if !self.func.dfg.block_is_valid(e) || !self.func.layout.is_block_inserted(e) {
775
return errors.fatal((loc, format!("invalid block reference {e}")));
776
}
777
if let Some(entry_block) = self.func.layout.entry_block() {
778
if e == entry_block {
779
return errors.fatal((loc, format!("invalid reference to entry block {e}")));
780
}
781
}
782
Ok(())
783
}
784
785
fn verify_sig_ref(
786
&self,
787
inst: Inst,
788
s: SigRef,
789
errors: &mut VerifierErrors,
790
) -> VerifierStepResult {
791
if !self.func.dfg.signatures.is_valid(s) {
792
errors.fatal((
793
inst,
794
self.context(inst),
795
format!("invalid signature reference {s}"),
796
))
797
} else {
798
Ok(())
799
}
800
}
801
802
fn verify_func_ref(
803
&self,
804
inst: Inst,
805
f: FuncRef,
806
errors: &mut VerifierErrors,
807
) -> VerifierStepResult {
808
if !self.func.dfg.ext_funcs.is_valid(f) {
809
errors.nonfatal((
810
inst,
811
self.context(inst),
812
format!("invalid function reference {f}"),
813
))
814
} else {
815
Ok(())
816
}
817
}
818
819
fn verify_stack_slot(
820
&self,
821
inst: Inst,
822
ss: StackSlot,
823
errors: &mut VerifierErrors,
824
) -> VerifierStepResult {
825
if !self.func.sized_stack_slots.is_valid(ss) {
826
errors.nonfatal((inst, self.context(inst), format!("invalid stack slot {ss}")))
827
} else {
828
Ok(())
829
}
830
}
831
832
fn verify_dynamic_stack_slot(
833
&self,
834
inst: Inst,
835
ss: DynamicStackSlot,
836
errors: &mut VerifierErrors,
837
) -> VerifierStepResult {
838
if !self.func.dynamic_stack_slots.is_valid(ss) {
839
errors.nonfatal((
840
inst,
841
self.context(inst),
842
format!("invalid dynamic stack slot {ss}"),
843
))
844
} else {
845
Ok(())
846
}
847
}
848
849
fn verify_global_value(
850
&self,
851
inst: Inst,
852
gv: GlobalValue,
853
errors: &mut VerifierErrors,
854
) -> VerifierStepResult {
855
if !self.func.global_values.is_valid(gv) {
856
errors.nonfatal((
857
inst,
858
self.context(inst),
859
format!("invalid global value {gv}"),
860
))
861
} else {
862
Ok(())
863
}
864
}
865
866
fn verify_value_list(
867
&self,
868
inst: Inst,
869
l: &ValueList,
870
errors: &mut VerifierErrors,
871
) -> VerifierStepResult {
872
if !l.is_valid(&self.func.dfg.value_lists) {
873
errors.nonfatal((
874
inst,
875
self.context(inst),
876
format!("invalid value list reference {l:?}"),
877
))
878
} else {
879
Ok(())
880
}
881
}
882
883
fn verify_jump_table(
884
&self,
885
inst: Inst,
886
j: JumpTable,
887
errors: &mut VerifierErrors,
888
) -> VerifierStepResult {
889
if !self.func.stencil.dfg.jump_tables.is_valid(j) {
890
errors.nonfatal((
891
inst,
892
self.context(inst),
893
format!("invalid jump table reference {j}"),
894
))
895
} else {
896
let pool = &self.func.stencil.dfg.value_lists;
897
for block in self.func.stencil.dfg.jump_tables[j].all_branches() {
898
self.verify_block(inst, block.block(pool), errors)?;
899
}
900
Ok(())
901
}
902
}
903
904
fn verify_exception_table(
905
&self,
906
inst: Inst,
907
et: ExceptionTable,
908
errors: &mut VerifierErrors,
909
) -> VerifierStepResult {
910
// Verify that the exception table reference itself is valid.
911
if !self.func.stencil.dfg.exception_tables.is_valid(et) {
912
errors.nonfatal((
913
inst,
914
self.context(inst),
915
format!("invalid exception table reference {et}"),
916
))?;
917
}
918
919
let pool = &self.func.stencil.dfg.value_lists;
920
let exdata = &self.func.stencil.dfg.exception_tables[et];
921
922
// Verify that the exception table's signature reference
923
// is valid.
924
self.verify_sig_ref(inst, exdata.signature(), errors)?;
925
926
// Verify that the exception table's block references are valid.
927
for block in exdata.all_branches() {
928
self.verify_block(inst, block.block(pool), errors)?;
929
}
930
Ok(())
931
}
932
933
fn verify_exception_compatible_abi(
934
&self,
935
inst: Inst,
936
et: ExceptionTable,
937
errors: &mut VerifierErrors,
938
) -> VerifierStepResult {
939
let callee_sig_ref = self.func.dfg.exception_tables[et].signature();
940
let callee_sig = &self.func.dfg.signatures[callee_sig_ref];
941
let callee_call_conv = callee_sig.call_conv;
942
if !callee_call_conv.supports_exceptions() {
943
errors.nonfatal((
944
inst,
945
self.context(inst),
946
format!(
947
"calling convention `{callee_call_conv}` of callee does not support exceptions"
948
),
949
))?;
950
}
951
Ok(())
952
}
953
954
fn verify_value(
955
&self,
956
loc_inst: Inst,
957
v: Value,
958
errors: &mut VerifierErrors,
959
) -> VerifierStepResult {
960
let dfg = &self.func.dfg;
961
if !dfg.value_is_valid(v) {
962
errors.nonfatal((
963
loc_inst,
964
self.context(loc_inst),
965
format!("invalid value reference {v}"),
966
))
967
} else {
968
Ok(())
969
}
970
}
971
972
fn verify_inst_arg(
973
&self,
974
loc_inst: Inst,
975
v: Value,
976
errors: &mut VerifierErrors,
977
) -> VerifierStepResult {
978
self.verify_value(loc_inst, v, errors)?;
979
980
let dfg = &self.func.dfg;
981
let loc_block = self
982
.func
983
.layout
984
.inst_block(loc_inst)
985
.expect("Instruction not in layout.");
986
let is_reachable = self.expected_domtree.is_reachable(loc_block);
987
988
// SSA form
989
match dfg.value_def(v) {
990
ValueDef::Result(def_inst, _) => {
991
// Value is defined by an instruction that exists.
992
if !dfg.inst_is_valid(def_inst) {
993
return errors.fatal((
994
loc_inst,
995
self.context(loc_inst),
996
format!("{v} is defined by invalid instruction {def_inst}"),
997
));
998
}
999
// Defining instruction is inserted in a block.
1000
if self.func.layout.inst_block(def_inst) == None {
1001
return errors.fatal((
1002
loc_inst,
1003
self.context(loc_inst),
1004
format!("{v} is defined by {def_inst} which has no block"),
1005
));
1006
}
1007
// Defining instruction dominates the instruction that uses the value.
1008
if is_reachable {
1009
if !self
1010
.expected_domtree
1011
.dominates(def_inst, loc_inst, &self.func.layout)
1012
{
1013
return errors.fatal((
1014
loc_inst,
1015
self.context(loc_inst),
1016
format!("uses value {v} from non-dominating {def_inst}"),
1017
));
1018
}
1019
if def_inst == loc_inst {
1020
return errors.fatal((
1021
loc_inst,
1022
self.context(loc_inst),
1023
format!("uses value {v} from itself"),
1024
));
1025
}
1026
}
1027
}
1028
ValueDef::Param(block, _) => {
1029
// Value is defined by an existing block.
1030
if !dfg.block_is_valid(block) {
1031
return errors.fatal((
1032
loc_inst,
1033
self.context(loc_inst),
1034
format!("{v} is defined by invalid block {block}"),
1035
));
1036
}
1037
// Defining block is inserted in the layout
1038
if !self.func.layout.is_block_inserted(block) {
1039
return errors.fatal((
1040
loc_inst,
1041
self.context(loc_inst),
1042
format!("{v} is defined by {block} which is not in the layout"),
1043
));
1044
}
1045
let user_block = self.func.layout.inst_block(loc_inst).expect("Expected instruction to be in a block as we're traversing code already in layout");
1046
// The defining block dominates the instruction using this value.
1047
if is_reachable && !self.expected_domtree.block_dominates(block, user_block) {
1048
return errors.fatal((
1049
loc_inst,
1050
self.context(loc_inst),
1051
format!("uses value arg from non-dominating {block}"),
1052
));
1053
}
1054
}
1055
ValueDef::Union(_, _) => {
1056
// Nothing: union nodes themselves have no location,
1057
// so we cannot check any dominance properties.
1058
}
1059
}
1060
Ok(())
1061
}
1062
1063
fn verify_inst_result(
1064
&self,
1065
loc_inst: Inst,
1066
v: Value,
1067
errors: &mut VerifierErrors,
1068
) -> VerifierStepResult {
1069
self.verify_value(loc_inst, v, errors)?;
1070
1071
match self.func.dfg.value_def(v) {
1072
ValueDef::Result(def_inst, _) => {
1073
if def_inst != loc_inst {
1074
errors.fatal((
1075
loc_inst,
1076
self.context(loc_inst),
1077
format!("instruction result {v} is not defined by the instruction"),
1078
))
1079
} else {
1080
Ok(())
1081
}
1082
}
1083
ValueDef::Param(_, _) => errors.fatal((
1084
loc_inst,
1085
self.context(loc_inst),
1086
format!("instruction result {v} is not defined by the instruction"),
1087
)),
1088
ValueDef::Union(_, _) => errors.fatal((
1089
loc_inst,
1090
self.context(loc_inst),
1091
format!("instruction result {v} is a union node"),
1092
)),
1093
}
1094
}
1095
1096
fn verify_bitcast(
1097
&self,
1098
inst: Inst,
1099
flags: MemFlags,
1100
arg: Value,
1101
errors: &mut VerifierErrors,
1102
) -> VerifierStepResult {
1103
let typ = self.func.dfg.ctrl_typevar(inst);
1104
let value_type = self.func.dfg.value_type(arg);
1105
1106
if typ.bits() != value_type.bits() {
1107
errors.fatal((
1108
inst,
1109
format!(
1110
"The bitcast argument {} has a type of {} bits, which doesn't match an expected type of {} bits",
1111
arg,
1112
value_type.bits(),
1113
typ.bits()
1114
),
1115
))
1116
} else if flags != MemFlags::new()
1117
&& flags != MemFlags::new().with_endianness(ir::Endianness::Little)
1118
&& flags != MemFlags::new().with_endianness(ir::Endianness::Big)
1119
{
1120
errors.fatal((
1121
inst,
1122
"The bitcast instruction only accepts the `big` or `little` memory flags",
1123
))
1124
} else if flags == MemFlags::new() && typ.lane_count() != value_type.lane_count() {
1125
errors.fatal((
1126
inst,
1127
"Byte order specifier required for bitcast instruction changing lane count",
1128
))
1129
} else {
1130
Ok(())
1131
}
1132
}
1133
1134
fn verify_constant_size(
1135
&self,
1136
inst: Inst,
1137
opcode: Opcode,
1138
constant: Constant,
1139
errors: &mut VerifierErrors,
1140
) -> VerifierStepResult {
1141
let type_size = match opcode {
1142
Opcode::F128const => types::F128.bytes(),
1143
Opcode::Vconst => self.func.dfg.ctrl_typevar(inst).bytes(),
1144
_ => unreachable!("unexpected opcode {opcode:?}"),
1145
} as usize;
1146
let constant_size = self.func.dfg.constants.get(constant).len();
1147
if type_size != constant_size {
1148
errors.fatal((
1149
inst,
1150
format!(
1151
"The instruction expects {constant} to have a size of {type_size} bytes but it has {constant_size}"
1152
),
1153
))
1154
} else {
1155
Ok(())
1156
}
1157
}
1158
1159
fn verify_is_address(
1160
&self,
1161
loc_inst: Inst,
1162
v: Value,
1163
errors: &mut VerifierErrors,
1164
) -> VerifierStepResult {
1165
if let Some(isa) = self.isa {
1166
let pointer_width = isa.triple().pointer_width()?;
1167
let value_type = self.func.dfg.value_type(v);
1168
let expected_width = pointer_width.bits() as u32;
1169
let value_width = value_type.bits();
1170
if expected_width != value_width {
1171
errors.nonfatal((
1172
loc_inst,
1173
self.context(loc_inst),
1174
format!("invalid pointer width (got {value_width}, expected {expected_width}) encountered {v}"),
1175
))
1176
} else {
1177
Ok(())
1178
}
1179
} else {
1180
Ok(())
1181
}
1182
}
1183
1184
fn domtree_integrity(
1185
&self,
1186
domtree: &DominatorTree,
1187
errors: &mut VerifierErrors,
1188
) -> VerifierStepResult {
1189
// We consider two `DominatorTree`s to be equal if they return the same immediate
1190
// dominator for each block. Therefore the current domtree is valid if it matches the freshly
1191
// computed one.
1192
for block in self.func.layout.blocks() {
1193
let expected = self.expected_domtree.idom(block);
1194
let got = domtree.idom(block);
1195
if got != expected {
1196
return errors.fatal((
1197
block,
1198
format!("invalid domtree, expected idom({block}) = {expected:?}, got {got:?}"),
1199
));
1200
}
1201
}
1202
// We also verify if the postorder defined by `DominatorTree` is sane
1203
if domtree.cfg_postorder().len() != self.expected_domtree.cfg_postorder().len() {
1204
return errors.fatal((
1205
AnyEntity::Function,
1206
"incorrect number of Blocks in postorder traversal",
1207
));
1208
}
1209
for (index, (&test_block, &true_block)) in domtree
1210
.cfg_postorder()
1211
.iter()
1212
.zip(self.expected_domtree.cfg_postorder().iter())
1213
.enumerate()
1214
{
1215
if test_block != true_block {
1216
return errors.fatal((
1217
test_block,
1218
format!(
1219
"invalid domtree, postorder block number {index} should be {true_block}, got {test_block}"
1220
),
1221
));
1222
}
1223
}
1224
Ok(())
1225
}
1226
1227
fn typecheck_entry_block_params(&self, errors: &mut VerifierErrors) -> VerifierStepResult {
1228
if let Some(block) = self.func.layout.entry_block() {
1229
let expected_types = &self.func.signature.params;
1230
let block_param_count = self.func.dfg.num_block_params(block);
1231
1232
if block_param_count != expected_types.len() {
1233
return errors.fatal((
1234
block,
1235
format!(
1236
"entry block parameters ({}) must match function signature ({})",
1237
block_param_count,
1238
expected_types.len()
1239
),
1240
));
1241
}
1242
1243
for (i, &arg) in self.func.dfg.block_params(block).iter().enumerate() {
1244
let arg_type = self.func.dfg.value_type(arg);
1245
if arg_type != expected_types[i].value_type {
1246
errors.report((
1247
block,
1248
format!(
1249
"entry block parameter {} expected to have type {}, got {}",
1250
i, expected_types[i], arg_type
1251
),
1252
));
1253
}
1254
}
1255
}
1256
1257
errors.as_result()
1258
}
1259
1260
fn check_entry_not_cold(&self, errors: &mut VerifierErrors) -> VerifierStepResult {
1261
if let Some(entry_block) = self.func.layout.entry_block() {
1262
if self.func.layout.is_cold(entry_block) {
1263
return errors
1264
.fatal((entry_block, format!("entry block cannot be marked as cold")));
1265
}
1266
}
1267
errors.as_result()
1268
}
1269
1270
fn typecheck(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {
1271
let inst_data = &self.func.dfg.insts[inst];
1272
let constraints = inst_data.opcode().constraints();
1273
1274
let ctrl_type = if let Some(value_typeset) = constraints.ctrl_typeset() {
1275
// For polymorphic opcodes, determine the controlling type variable first.
1276
let ctrl_type = self.func.dfg.ctrl_typevar(inst);
1277
1278
if !value_typeset.contains(ctrl_type) {
1279
errors.report((
1280
inst,
1281
self.context(inst),
1282
format!(
1283
"has an invalid controlling type {ctrl_type} (allowed set is {value_typeset:?})"
1284
),
1285
));
1286
}
1287
1288
ctrl_type
1289
} else {
1290
// Non-polymorphic instructions don't check the controlling type variable, so `Option`
1291
// is unnecessary and we can just make it `INVALID`.
1292
types::INVALID
1293
};
1294
1295
// Typechecking instructions is never fatal
1296
let _ = self.typecheck_results(inst, ctrl_type, errors);
1297
let _ = self.typecheck_fixed_args(inst, ctrl_type, errors);
1298
let _ = self.typecheck_variable_args(inst, errors);
1299
let _ = self.typecheck_return(inst, errors);
1300
let _ = self.typecheck_special(inst, errors);
1301
1302
Ok(())
1303
}
1304
1305
fn typecheck_results(
1306
&self,
1307
inst: Inst,
1308
ctrl_type: Type,
1309
errors: &mut VerifierErrors,
1310
) -> VerifierStepResult {
1311
let mut i = 0;
1312
for &result in self.func.dfg.inst_results(inst) {
1313
let result_type = self.func.dfg.value_type(result);
1314
let expected_type = self.func.dfg.compute_result_type(inst, i, ctrl_type);
1315
if let Some(expected_type) = expected_type {
1316
if result_type != expected_type {
1317
errors.report((
1318
inst,
1319
self.context(inst),
1320
format!(
1321
"expected result {i} ({result}) to have type {expected_type}, found {result_type}"
1322
),
1323
));
1324
}
1325
} else {
1326
return errors.nonfatal((
1327
inst,
1328
self.context(inst),
1329
"has more result values than expected",
1330
));
1331
}
1332
i += 1;
1333
}
1334
1335
// There aren't any more result types left.
1336
if self.func.dfg.compute_result_type(inst, i, ctrl_type) != None {
1337
return errors.nonfatal((
1338
inst,
1339
self.context(inst),
1340
"has fewer result values than expected",
1341
));
1342
}
1343
Ok(())
1344
}
1345
1346
fn typecheck_fixed_args(
1347
&self,
1348
inst: Inst,
1349
ctrl_type: Type,
1350
errors: &mut VerifierErrors,
1351
) -> VerifierStepResult {
1352
let constraints = self.func.dfg.insts[inst].opcode().constraints();
1353
1354
for (i, &arg) in self.func.dfg.inst_fixed_args(inst).iter().enumerate() {
1355
let arg_type = self.func.dfg.value_type(arg);
1356
match constraints.value_argument_constraint(i, ctrl_type) {
1357
ResolvedConstraint::Bound(expected_type) => {
1358
if arg_type != expected_type {
1359
errors.report((
1360
inst,
1361
self.context(inst),
1362
format!(
1363
"arg {i} ({arg}) has type {arg_type}, expected {expected_type}"
1364
),
1365
));
1366
}
1367
}
1368
ResolvedConstraint::Free(type_set) => {
1369
if !type_set.contains(arg_type) {
1370
errors.report((
1371
inst,
1372
self.context(inst),
1373
format!(
1374
"arg {i} ({arg}) with type {arg_type} failed to satisfy type set {type_set:?}"
1375
),
1376
));
1377
}
1378
}
1379
}
1380
}
1381
Ok(())
1382
}
1383
1384
/// Typecheck both instructions that contain variable arguments like calls, and those that
1385
/// include references to basic blocks with their arguments.
1386
fn typecheck_variable_args(
1387
&self,
1388
inst: Inst,
1389
errors: &mut VerifierErrors,
1390
) -> VerifierStepResult {
1391
match &self.func.dfg.insts[inst] {
1392
ir::InstructionData::Jump { destination, .. } => {
1393
self.typecheck_block_call(inst, destination, BlockCallTargetType::Normal, errors)?;
1394
}
1395
ir::InstructionData::Brif {
1396
blocks: [block_then, block_else],
1397
..
1398
} => {
1399
self.typecheck_block_call(inst, block_then, BlockCallTargetType::Normal, errors)?;
1400
self.typecheck_block_call(inst, block_else, BlockCallTargetType::Normal, errors)?;
1401
}
1402
ir::InstructionData::BranchTable { table, .. } => {
1403
for block in self.func.stencil.dfg.jump_tables[*table].all_branches() {
1404
self.typecheck_block_call(inst, block, BlockCallTargetType::Normal, errors)?;
1405
}
1406
}
1407
ir::InstructionData::TryCall { exception, .. }
1408
| ir::InstructionData::TryCallIndirect { exception, .. } => {
1409
let exdata = &self.func.dfg.exception_tables[*exception];
1410
self.typecheck_block_call(
1411
inst,
1412
exdata.normal_return(),
1413
BlockCallTargetType::ExNormalRet,
1414
errors,
1415
)?;
1416
for item in exdata.items() {
1417
match item {
1418
ExceptionTableItem::Tag(_, block_call)
1419
| ExceptionTableItem::Default(block_call) => {
1420
self.typecheck_block_call(
1421
inst,
1422
&block_call,
1423
BlockCallTargetType::Exception,
1424
errors,
1425
)?;
1426
}
1427
ExceptionTableItem::Context(_) => {}
1428
}
1429
}
1430
}
1431
inst => debug_assert!(!inst.opcode().is_branch()),
1432
}
1433
1434
match self.func.dfg.insts[inst]
1435
.analyze_call(&self.func.dfg.value_lists, &self.func.dfg.exception_tables)
1436
{
1437
CallInfo::Direct(func_ref, args) => {
1438
let sig_ref = self.func.dfg.ext_funcs[func_ref].signature;
1439
let arg_types = self.func.dfg.signatures[sig_ref]
1440
.params
1441
.iter()
1442
.map(|a| a.value_type);
1443
self.typecheck_variable_args_iterator(inst, arg_types, args, errors)?;
1444
}
1445
CallInfo::DirectWithSig(func_ref, sig_ref, args) => {
1446
let expected_sig_ref = self.func.dfg.ext_funcs[func_ref].signature;
1447
let sigdata = &self.func.dfg.signatures;
1448
// Compare signatures by value, not by ID -- any
1449
// equivalent signature ID is acceptable.
1450
if sigdata[sig_ref] != sigdata[expected_sig_ref] {
1451
errors.nonfatal((
1452
inst,
1453
self.context(inst),
1454
format!(
1455
"exception table signature {sig_ref} did not match function {func_ref}'s signature {expected_sig_ref}"
1456
),
1457
))?;
1458
}
1459
let arg_types = self.func.dfg.signatures[sig_ref]
1460
.params
1461
.iter()
1462
.map(|a| a.value_type);
1463
self.typecheck_variable_args_iterator(inst, arg_types, args, errors)?;
1464
}
1465
CallInfo::Indirect(sig_ref, args) => {
1466
let arg_types = self.func.dfg.signatures[sig_ref]
1467
.params
1468
.iter()
1469
.map(|a| a.value_type);
1470
self.typecheck_variable_args_iterator(inst, arg_types, args, errors)?;
1471
}
1472
CallInfo::NotACall => {}
1473
}
1474
Ok(())
1475
}
1476
1477
fn typecheck_block_call(
1478
&self,
1479
inst: Inst,
1480
block: &ir::BlockCall,
1481
target_type: BlockCallTargetType,
1482
errors: &mut VerifierErrors,
1483
) -> VerifierStepResult {
1484
let pool = &self.func.dfg.value_lists;
1485
let block_params = self.func.dfg.block_params(block.block(pool));
1486
let args = block.args(pool);
1487
if args.len() != block_params.len() {
1488
return errors.nonfatal((
1489
inst,
1490
self.context(inst),
1491
format!(
1492
"mismatched argument count for `{}`: got {}, expected {}",
1493
self.func.dfg.display_inst(inst),
1494
args.len(),
1495
block_params.len(),
1496
),
1497
));
1498
}
1499
for (arg, param) in args.zip(block_params.iter()) {
1500
let Some(arg_ty) = self.block_call_arg_ty(arg, inst, target_type, errors)? else {
1501
continue;
1502
};
1503
let param_ty = self.func.dfg.value_type(*param);
1504
if arg_ty != param_ty {
1505
errors.nonfatal((
1506
inst,
1507
self.context(inst),
1508
format!("arg {arg} has type {arg_ty}, expected {param_ty}"),
1509
))?;
1510
}
1511
}
1512
Ok(())
1513
}
1514
1515
fn block_call_arg_ty(
1516
&self,
1517
arg: BlockArg,
1518
inst: Inst,
1519
target_type: BlockCallTargetType,
1520
errors: &mut VerifierErrors,
1521
) -> Result<Option<Type>, ()> {
1522
match arg {
1523
BlockArg::Value(v) => Ok(Some(self.func.dfg.value_type(v))),
1524
BlockArg::TryCallRet(_) | BlockArg::TryCallExn(_) => {
1525
// Get the invoked signature.
1526
let et = match self.func.dfg.insts[inst].exception_table() {
1527
Some(et) => et,
1528
None => {
1529
errors.fatal((
1530
inst,
1531
self.context(inst),
1532
format!(
1533
"`retN` block argument in block-call not on `try_call` instruction"
1534
),
1535
))?;
1536
unreachable!()
1537
}
1538
};
1539
let exdata = &self.func.dfg.exception_tables[et];
1540
let sig = &self.func.dfg.signatures[exdata.signature()];
1541
1542
match (arg, target_type) {
1543
(BlockArg::TryCallRet(i), BlockCallTargetType::ExNormalRet)
1544
if (i as usize) < sig.returns.len() =>
1545
{
1546
Ok(Some(sig.returns[i as usize].value_type))
1547
}
1548
(BlockArg::TryCallRet(_), BlockCallTargetType::ExNormalRet) => {
1549
errors.fatal((
1550
inst,
1551
self.context(inst),
1552
format!("out-of-bounds `retN` block argument"),
1553
))?;
1554
unreachable!()
1555
}
1556
(BlockArg::TryCallRet(_), _) => {
1557
errors.fatal((
1558
inst,
1559
self.context(inst),
1560
format!("`retN` block argument used outside normal-return target of `try_call`"),
1561
))?;
1562
unreachable!()
1563
}
1564
(BlockArg::TryCallExn(i), BlockCallTargetType::Exception) => {
1565
if let Some(isa) = self.isa {
1566
match sig
1567
.call_conv
1568
.exception_payload_types(isa.pointer_type())
1569
.get(i as usize)
1570
{
1571
Some(ty) => Ok(Some(*ty)),
1572
None => {
1573
errors.fatal((
1574
inst,
1575
self.context(inst),
1576
format!("out-of-bounds `exnN` block argument"),
1577
))?;
1578
unreachable!()
1579
}
1580
}
1581
} else {
1582
Ok(None)
1583
}
1584
}
1585
(BlockArg::TryCallExn(_), _) => {
1586
errors.fatal((
1587
inst,
1588
self.context(inst),
1589
format!("`exnN` block argument used outside normal-return target of `try_call`"),
1590
))?;
1591
unreachable!()
1592
}
1593
_ => unreachable!(),
1594
}
1595
}
1596
}
1597
}
1598
1599
fn typecheck_variable_args_iterator(
1600
&self,
1601
inst: Inst,
1602
iter: impl ExactSizeIterator<Item = Type>,
1603
variable_args: &[Value],
1604
errors: &mut VerifierErrors,
1605
) -> VerifierStepResult {
1606
let mut i = 0;
1607
1608
for expected_type in iter {
1609
if i >= variable_args.len() {
1610
// Result count mismatch handled below, we want the full argument count first though
1611
i += 1;
1612
continue;
1613
}
1614
let arg = variable_args[i];
1615
let arg_type = self.func.dfg.value_type(arg);
1616
if expected_type != arg_type {
1617
errors.report((
1618
inst,
1619
self.context(inst),
1620
format!(
1621
"arg {} ({}) has type {}, expected {}",
1622
i, variable_args[i], arg_type, expected_type
1623
),
1624
));
1625
}
1626
i += 1;
1627
}
1628
if i != variable_args.len() {
1629
return errors.nonfatal((
1630
inst,
1631
self.context(inst),
1632
format!(
1633
"mismatched argument count for `{}`: got {}, expected {}",
1634
self.func.dfg.display_inst(inst),
1635
variable_args.len(),
1636
i,
1637
),
1638
));
1639
}
1640
Ok(())
1641
}
1642
1643
fn typecheck_return(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {
1644
match self.func.dfg.insts[inst] {
1645
ir::InstructionData::MultiAry {
1646
opcode: Opcode::Return,
1647
args,
1648
} => {
1649
let types = args
1650
.as_slice(&self.func.dfg.value_lists)
1651
.iter()
1652
.map(|v| self.func.dfg.value_type(*v));
1653
self.typecheck_return_types(
1654
inst,
1655
types,
1656
errors,
1657
"arguments of return must match function signature",
1658
)?;
1659
}
1660
ir::InstructionData::Call {
1661
opcode: Opcode::ReturnCall,
1662
func_ref,
1663
..
1664
} => {
1665
let sig_ref = self.func.dfg.ext_funcs[func_ref].signature;
1666
self.typecheck_tail_call(inst, sig_ref, errors)?;
1667
}
1668
ir::InstructionData::CallIndirect {
1669
opcode: Opcode::ReturnCallIndirect,
1670
sig_ref,
1671
..
1672
} => {
1673
self.typecheck_tail_call(inst, sig_ref, errors)?;
1674
}
1675
inst => debug_assert!(!inst.opcode().is_return()),
1676
}
1677
Ok(())
1678
}
1679
1680
fn typecheck_tail_call(
1681
&self,
1682
inst: Inst,
1683
sig_ref: SigRef,
1684
errors: &mut VerifierErrors,
1685
) -> VerifierStepResult {
1686
let signature = &self.func.dfg.signatures[sig_ref];
1687
let cc = signature.call_conv;
1688
if !cc.supports_tail_calls() {
1689
errors.report((
1690
inst,
1691
self.context(inst),
1692
format!("calling convention `{cc}` does not support tail calls"),
1693
));
1694
}
1695
if cc != self.func.signature.call_conv {
1696
errors.report((
1697
inst,
1698
self.context(inst),
1699
"callee's calling convention must match caller",
1700
));
1701
}
1702
let types = signature.returns.iter().map(|param| param.value_type);
1703
self.typecheck_return_types(inst, types, errors, "results of callee must match caller")?;
1704
Ok(())
1705
}
1706
1707
fn typecheck_return_types(
1708
&self,
1709
inst: Inst,
1710
actual_types: impl ExactSizeIterator<Item = Type>,
1711
errors: &mut VerifierErrors,
1712
message: &str,
1713
) -> VerifierStepResult {
1714
let expected_types = &self.func.signature.returns;
1715
if actual_types.len() != expected_types.len() {
1716
return errors.nonfatal((inst, self.context(inst), message));
1717
}
1718
for (i, (actual_type, &expected_type)) in actual_types.zip(expected_types).enumerate() {
1719
if actual_type != expected_type.value_type {
1720
errors.report((
1721
inst,
1722
self.context(inst),
1723
format!(
1724
"result {i} has type {actual_type}, must match function signature of \
1725
{expected_type}"
1726
),
1727
));
1728
}
1729
}
1730
Ok(())
1731
}
1732
1733
// Check special-purpose type constraints that can't be expressed in the normal opcode
1734
// constraints.
1735
fn typecheck_special(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {
1736
match self.func.dfg.insts[inst] {
1737
ir::InstructionData::UnaryGlobalValue { global_value, .. } => {
1738
if let Some(isa) = self.isa {
1739
let inst_type = self.func.dfg.value_type(self.func.dfg.first_result(inst));
1740
let global_type = self.func.global_values[global_value].global_type(isa);
1741
if inst_type != global_type {
1742
return errors.nonfatal((
1743
inst, self.context(inst),
1744
format!(
1745
"global_value instruction with type {inst_type} references global value with type {global_type}"
1746
)),
1747
);
1748
}
1749
}
1750
}
1751
_ => {}
1752
}
1753
Ok(())
1754
}
1755
1756
fn cfg_integrity(
1757
&self,
1758
cfg: &ControlFlowGraph,
1759
errors: &mut VerifierErrors,
1760
) -> VerifierStepResult {
1761
let mut expected_succs = BTreeSet::<Block>::new();
1762
let mut got_succs = BTreeSet::<Block>::new();
1763
let mut expected_preds = BTreeSet::<Inst>::new();
1764
let mut got_preds = BTreeSet::<Inst>::new();
1765
1766
for block in self.func.layout.blocks() {
1767
expected_succs.extend(self.expected_cfg.succ_iter(block));
1768
got_succs.extend(cfg.succ_iter(block));
1769
1770
let missing_succs: Vec<Block> =
1771
expected_succs.difference(&got_succs).cloned().collect();
1772
if !missing_succs.is_empty() {
1773
errors.report((
1774
block,
1775
format!("cfg lacked the following successor(s) {missing_succs:?}"),
1776
));
1777
continue;
1778
}
1779
1780
let excess_succs: Vec<Block> = got_succs.difference(&expected_succs).cloned().collect();
1781
if !excess_succs.is_empty() {
1782
errors.report((
1783
block,
1784
format!("cfg had unexpected successor(s) {excess_succs:?}"),
1785
));
1786
continue;
1787
}
1788
1789
expected_preds.extend(
1790
self.expected_cfg
1791
.pred_iter(block)
1792
.map(|BlockPredecessor { inst, .. }| inst),
1793
);
1794
got_preds.extend(
1795
cfg.pred_iter(block)
1796
.map(|BlockPredecessor { inst, .. }| inst),
1797
);
1798
1799
let missing_preds: Vec<Inst> = expected_preds.difference(&got_preds).cloned().collect();
1800
if !missing_preds.is_empty() {
1801
errors.report((
1802
block,
1803
format!("cfg lacked the following predecessor(s) {missing_preds:?}"),
1804
));
1805
continue;
1806
}
1807
1808
let excess_preds: Vec<Inst> = got_preds.difference(&expected_preds).cloned().collect();
1809
if !excess_preds.is_empty() {
1810
errors.report((
1811
block,
1812
format!("cfg had unexpected predecessor(s) {excess_preds:?}"),
1813
));
1814
continue;
1815
}
1816
1817
expected_succs.clear();
1818
got_succs.clear();
1819
expected_preds.clear();
1820
got_preds.clear();
1821
}
1822
errors.as_result()
1823
}
1824
1825
fn immediate_constraints(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {
1826
let inst_data = &self.func.dfg.insts[inst];
1827
1828
match *inst_data {
1829
ir::InstructionData::Store { flags, .. } => {
1830
if flags.readonly() {
1831
errors.fatal((
1832
inst,
1833
self.context(inst),
1834
"A store instruction cannot have the `readonly` MemFlag",
1835
))
1836
} else {
1837
Ok(())
1838
}
1839
}
1840
ir::InstructionData::BinaryImm8 {
1841
opcode: ir::instructions::Opcode::Extractlane,
1842
imm: lane,
1843
arg,
1844
..
1845
}
1846
| ir::InstructionData::TernaryImm8 {
1847
opcode: ir::instructions::Opcode::Insertlane,
1848
imm: lane,
1849
args: [arg, _],
1850
..
1851
} => {
1852
// We must be specific about the opcodes above because other instructions are using
1853
// the same formats.
1854
let ty = self.func.dfg.value_type(arg);
1855
if lane as u32 >= ty.lane_count() {
1856
errors.fatal((
1857
inst,
1858
self.context(inst),
1859
format!("The lane {lane} does not index into the type {ty}",),
1860
))
1861
} else {
1862
Ok(())
1863
}
1864
}
1865
ir::InstructionData::Shuffle {
1866
opcode: ir::instructions::Opcode::Shuffle,
1867
imm,
1868
..
1869
} => {
1870
let imm = self.func.dfg.immediates.get(imm).unwrap().as_slice();
1871
if imm.len() != 16 {
1872
errors.fatal((
1873
inst,
1874
self.context(inst),
1875
format!("the shuffle immediate wasn't 16-bytes long"),
1876
))
1877
} else if let Some(i) = imm.iter().find(|i| **i >= 32) {
1878
errors.fatal((
1879
inst,
1880
self.context(inst),
1881
format!("shuffle immediate index {i} is larger than the maximum 31"),
1882
))
1883
} else {
1884
Ok(())
1885
}
1886
}
1887
_ => Ok(()),
1888
}
1889
}
1890
1891
fn iconst_bounds(&self, inst: Inst, errors: &mut VerifierErrors) -> VerifierStepResult {
1892
use crate::ir::instructions::InstructionData::UnaryImm;
1893
1894
let inst_data = &self.func.dfg.insts[inst];
1895
if let UnaryImm {
1896
opcode: Opcode::Iconst,
1897
imm,
1898
} = inst_data
1899
{
1900
let ctrl_typevar = self.func.dfg.ctrl_typevar(inst);
1901
let bounds_mask = match ctrl_typevar {
1902
types::I8 => u8::MAX.into(),
1903
types::I16 => u16::MAX.into(),
1904
types::I32 => u32::MAX.into(),
1905
types::I64 => u64::MAX,
1906
_ => unreachable!(),
1907
};
1908
1909
let value = imm.bits() as u64;
1910
if value & bounds_mask != value {
1911
errors.fatal((
1912
inst,
1913
self.context(inst),
1914
"constant immediate is out of bounds",
1915
))
1916
} else {
1917
Ok(())
1918
}
1919
} else {
1920
Ok(())
1921
}
1922
}
1923
1924
fn typecheck_function_signature(&self, errors: &mut VerifierErrors) -> VerifierStepResult {
1925
let params = self
1926
.func
1927
.signature
1928
.params
1929
.iter()
1930
.enumerate()
1931
.map(|p| (true, p));
1932
let returns = self
1933
.func
1934
.signature
1935
.returns
1936
.iter()
1937
.enumerate()
1938
.map(|p| (false, p));
1939
1940
for (is_argument, (i, param)) in params.chain(returns) {
1941
let is_return = !is_argument;
1942
let item = if is_argument {
1943
"Parameter"
1944
} else {
1945
"Return value"
1946
};
1947
1948
if param.value_type == types::INVALID {
1949
errors.report((
1950
AnyEntity::Function,
1951
format!("{item} at position {i} has an invalid type"),
1952
));
1953
}
1954
1955
if let ArgumentPurpose::StructArgument(_) = param.purpose {
1956
if is_return {
1957
errors.report((
1958
AnyEntity::Function,
1959
format!("{item} at position {i} can't be an struct argument"),
1960
))
1961
}
1962
}
1963
1964
let ty_allows_extension = param.value_type.is_int();
1965
let has_extension = param.extension != ArgumentExtension::None;
1966
if !ty_allows_extension && has_extension {
1967
errors.report((
1968
AnyEntity::Function,
1969
format!(
1970
"{} at position {} has invalid extension {:?}",
1971
item, i, param.extension
1972
),
1973
));
1974
}
1975
}
1976
1977
if errors.has_error() { Err(()) } else { Ok(()) }
1978
}
1979
1980
fn verify_try_call_handler_index(
1981
&self,
1982
inst: Inst,
1983
block: Block,
1984
index_imm: i64,
1985
errors: &mut VerifierErrors,
1986
) -> VerifierStepResult {
1987
if index_imm < 0 {
1988
return errors.fatal((
1989
inst,
1990
format!("exception handler index {index_imm} cannot be negative"),
1991
));
1992
}
1993
let Ok(index) = usize::try_from(index_imm) else {
1994
return errors.fatal((
1995
inst,
1996
format!("exception handler index {index_imm} is out-of-range"),
1997
));
1998
};
1999
let Some(terminator) = self.func.layout.last_inst(block) else {
2000
return errors.fatal((
2001
inst,
2002
format!("referenced block {block} does not have a terminator"),
2003
));
2004
};
2005
let Some(et) = self.func.dfg.insts[terminator].exception_table() else {
2006
return errors.fatal((
2007
inst,
2008
format!("referenced block {block} does not end in a try_call"),
2009
));
2010
};
2011
2012
let etd = &self.func.dfg.exception_tables[et];
2013
// The exception table's out-edges consist of all exceptional
2014
// edges first, followed by the normal return last. For N
2015
// out-edges, there are N-1 exception handlers that can be
2016
// selected.
2017
let num_exceptional_edges = etd.all_branches().len() - 1;
2018
if index >= num_exceptional_edges {
2019
return errors.fatal((
2020
inst,
2021
format!("exception handler index {index_imm} is out-of-range"),
2022
));
2023
}
2024
2025
Ok(())
2026
}
2027
2028
pub fn run(&self, errors: &mut VerifierErrors) -> VerifierStepResult {
2029
self.verify_global_values(errors)?;
2030
self.verify_memory_types(errors)?;
2031
self.typecheck_entry_block_params(errors)?;
2032
self.check_entry_not_cold(errors)?;
2033
self.typecheck_function_signature(errors)?;
2034
2035
for block in self.func.layout.blocks() {
2036
if self.func.layout.first_inst(block).is_none() {
2037
return errors.fatal((block, format!("{block} cannot be empty")));
2038
}
2039
for inst in self.func.layout.block_insts(block) {
2040
crate::trace!("verifying {inst:?}: {}", self.func.dfg.display_inst(inst));
2041
self.block_integrity(block, inst, errors)?;
2042
self.instruction_integrity(inst, errors)?;
2043
self.typecheck(inst, errors)?;
2044
self.immediate_constraints(inst, errors)?;
2045
self.iconst_bounds(inst, errors)?;
2046
}
2047
2048
self.encodable_as_bb(block, errors)?;
2049
}
2050
2051
if !errors.is_empty() {
2052
log::warn!(
2053
"Found verifier errors in function:\n{}",
2054
pretty_verifier_error(self.func, None, errors.clone())
2055
);
2056
}
2057
2058
Ok(())
2059
}
2060
}
2061
2062
#[cfg(test)]
2063
mod tests {
2064
use super::{Verifier, VerifierError, VerifierErrors};
2065
use crate::ir::instructions::{InstructionData, Opcode};
2066
use crate::ir::{AbiParam, Function, Type, types};
2067
use crate::settings;
2068
2069
macro_rules! assert_err_with_msg {
2070
($e:expr, $msg:expr) => {
2071
match $e.0.get(0) {
2072
None => panic!("Expected an error"),
2073
Some(&VerifierError { ref message, .. }) => {
2074
if !message.contains($msg) {
2075
#[cfg(feature = "std")]
2076
panic!("'{}' did not contain the substring '{}'", message, $msg);
2077
#[cfg(not(feature = "std"))]
2078
panic!("error message did not contain the expected substring");
2079
}
2080
}
2081
}
2082
};
2083
}
2084
2085
#[test]
2086
fn empty() {
2087
let func = Function::new();
2088
let flags = &settings::Flags::new(settings::builder());
2089
let verifier = Verifier::new(&func, flags.into());
2090
let mut errors = VerifierErrors::default();
2091
2092
assert_eq!(verifier.run(&mut errors), Ok(()));
2093
assert!(errors.0.is_empty());
2094
}
2095
2096
#[test]
2097
fn bad_instruction_format() {
2098
let mut func = Function::new();
2099
let block0 = func.dfg.make_block();
2100
func.layout.append_block(block0);
2101
let nullary_with_bad_opcode = func.dfg.make_inst(InstructionData::UnaryImm {
2102
opcode: Opcode::F32const,
2103
imm: 0.into(),
2104
});
2105
func.layout.append_inst(nullary_with_bad_opcode, block0);
2106
let destination = func.dfg.block_call(block0, &[]);
2107
func.stencil.layout.append_inst(
2108
func.stencil.dfg.make_inst(InstructionData::Jump {
2109
opcode: Opcode::Jump,
2110
destination,
2111
}),
2112
block0,
2113
);
2114
let flags = &settings::Flags::new(settings::builder());
2115
let verifier = Verifier::new(&func, flags.into());
2116
let mut errors = VerifierErrors::default();
2117
2118
let _ = verifier.run(&mut errors);
2119
2120
assert_err_with_msg!(errors, "instruction format");
2121
}
2122
2123
fn test_iconst_bounds(immediate: i64, ctrl_typevar: Type) -> VerifierErrors {
2124
let mut func = Function::new();
2125
let block0 = func.dfg.make_block();
2126
func.layout.append_block(block0);
2127
2128
let test_inst = func.dfg.make_inst(InstructionData::UnaryImm {
2129
opcode: Opcode::Iconst,
2130
imm: immediate.into(),
2131
});
2132
2133
let end_inst = func.dfg.make_inst(InstructionData::MultiAry {
2134
opcode: Opcode::Return,
2135
args: Default::default(),
2136
});
2137
2138
func.dfg.make_inst_results(test_inst, ctrl_typevar);
2139
func.layout.append_inst(test_inst, block0);
2140
func.layout.append_inst(end_inst, block0);
2141
2142
let flags = &settings::Flags::new(settings::builder());
2143
let verifier = Verifier::new(&func, flags.into());
2144
let mut errors = VerifierErrors::default();
2145
2146
let _ = verifier.run(&mut errors);
2147
errors
2148
}
2149
2150
fn test_iconst_bounds_err(immediate: i64, ctrl_typevar: Type) {
2151
assert_err_with_msg!(
2152
test_iconst_bounds(immediate, ctrl_typevar),
2153
"constant immediate is out of bounds"
2154
);
2155
}
2156
2157
fn test_iconst_bounds_ok(immediate: i64, ctrl_typevar: Type) {
2158
assert!(test_iconst_bounds(immediate, ctrl_typevar).is_empty());
2159
}
2160
2161
#[test]
2162
fn negative_iconst_8() {
2163
test_iconst_bounds_err(-10, types::I8);
2164
}
2165
2166
#[test]
2167
fn negative_iconst_32() {
2168
test_iconst_bounds_err(-1, types::I32);
2169
}
2170
2171
#[test]
2172
fn large_iconst_8() {
2173
test_iconst_bounds_err(1 + u8::MAX as i64, types::I8);
2174
}
2175
2176
#[test]
2177
fn large_iconst_16() {
2178
test_iconst_bounds_err(10 + u16::MAX as i64, types::I16);
2179
}
2180
2181
#[test]
2182
fn valid_iconst_8() {
2183
test_iconst_bounds_ok(10, types::I8);
2184
}
2185
2186
#[test]
2187
fn valid_iconst_32() {
2188
test_iconst_bounds_ok(u32::MAX as i64, types::I32);
2189
}
2190
2191
#[test]
2192
fn test_function_invalid_param() {
2193
let mut func = Function::new();
2194
func.signature.params.push(AbiParam::new(types::INVALID));
2195
2196
let mut errors = VerifierErrors::default();
2197
let flags = &settings::Flags::new(settings::builder());
2198
let verifier = Verifier::new(&func, flags.into());
2199
2200
let _ = verifier.typecheck_function_signature(&mut errors);
2201
assert_err_with_msg!(errors, "Parameter at position 0 has an invalid type");
2202
}
2203
2204
#[test]
2205
fn test_function_invalid_return_value() {
2206
let mut func = Function::new();
2207
func.signature.returns.push(AbiParam::new(types::INVALID));
2208
2209
let mut errors = VerifierErrors::default();
2210
let flags = &settings::Flags::new(settings::builder());
2211
let verifier = Verifier::new(&func, flags.into());
2212
2213
let _ = verifier.typecheck_function_signature(&mut errors);
2214
assert_err_with_msg!(errors, "Return value at position 0 has an invalid type");
2215
}
2216
2217
#[test]
2218
fn test_printing_contextual_errors() {
2219
// Build function.
2220
let mut func = Function::new();
2221
let block0 = func.dfg.make_block();
2222
func.layout.append_block(block0);
2223
2224
// Build instruction "f64const 0.0" (missing one required result)
2225
let inst = func.dfg.make_inst(InstructionData::UnaryIeee64 {
2226
opcode: Opcode::F64const,
2227
imm: 0.0.into(),
2228
});
2229
func.layout.append_inst(inst, block0);
2230
2231
// Setup verifier.
2232
let mut errors = VerifierErrors::default();
2233
let flags = &settings::Flags::new(settings::builder());
2234
let verifier = Verifier::new(&func, flags.into());
2235
2236
// Now the error message, when printed, should contain the instruction sequence causing the
2237
// error (i.e. f64const 0.0) and not only its entity value (i.e. inst0)
2238
let _ = verifier.typecheck_results(inst, types::I32, &mut errors);
2239
assert_eq!(
2240
format!("{}", errors.0[0]),
2241
"inst0 (f64const 0.0): has fewer result values than expected"
2242
)
2243
}
2244
2245
#[test]
2246
fn test_empty_block() {
2247
let mut func = Function::new();
2248
let block0 = func.dfg.make_block();
2249
func.layout.append_block(block0);
2250
2251
let flags = &settings::Flags::new(settings::builder());
2252
let verifier = Verifier::new(&func, flags.into());
2253
let mut errors = VerifierErrors::default();
2254
let _ = verifier.run(&mut errors);
2255
2256
assert_err_with_msg!(errors, "block0 cannot be empty");
2257
}
2258
}
2259
2260