Skip to content

Commit 68d3e70

Browse files
committed
cranelift/pulley: assert continuation has single predecessor (debug only)
Make the structural invariant explicit: the funcref-dispatch fusion absorbs the two `VMFuncRef` field loads from the continuation block, which is only sound when the continuation has exactly one predecessor (the brif we're replacing). Build a `ControlFlowGraph` in `pre_lower_pulley` and `debug_assert_eq!(n_preds, 1)` before sinking. Both the CFG construction and the assertion are gated on `#[cfg(debug_assertions)]` so they compile to nothing in release builds (no runtime overhead).
1 parent 80b51f7 commit 68d3e70

14 files changed

Lines changed: 622 additions & 402 deletions

cranelift/codegen/src/isa/pulley_shared/inst/emit.rs

Lines changed: 92 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -407,12 +407,20 @@ fn pulley_emit<P>(
407407
match size {
408408
OperandSize::Size32 => {
409409
enc::xband32_s8_br_if_not_x32(
410-
&mut inverted, dst_writable, src_reg, mask_imm, 0,
410+
&mut inverted,
411+
dst_writable,
412+
src_reg,
413+
mask_imm,
414+
0,
411415
);
412416
}
413417
OperandSize::Size64 => {
414418
enc::xband64_s8_br_if_not_x64(
415-
&mut inverted, dst_writable, src_reg, mask_imm, 0,
419+
&mut inverted,
420+
dst_writable,
421+
src_reg,
422+
mask_imm,
423+
0,
416424
);
417425
}
418426
}
@@ -422,12 +430,20 @@ fn pulley_emit<P>(
422430
match size {
423431
OperandSize::Size32 => {
424432
enc::xband32_s8_br_if_not_x32(
425-
&mut inverted, dst_writable, src_reg, mask_imm, inv_rel,
433+
&mut inverted,
434+
dst_writable,
435+
src_reg,
436+
mask_imm,
437+
inv_rel,
426438
);
427439
}
428440
OperandSize::Size64 => {
429441
enc::xband64_s8_br_if_not_x64(
430-
&mut inverted, dst_writable, src_reg, mask_imm, inv_rel,
442+
&mut inverted,
443+
dst_writable,
444+
src_reg,
445+
mask_imm,
446+
inv_rel,
431447
);
432448
}
433449
}
@@ -482,12 +498,24 @@ fn pulley_emit<P>(
482498
match size {
483499
OperandSize::Size32 => {
484500
enc::xfuncref_dispatch_not_x32(
485-
&mut inverted, dst_code_w, dst_vmctx_w, src_reg, oc, ov, 0,
501+
&mut inverted,
502+
dst_code_w,
503+
dst_vmctx_w,
504+
src_reg,
505+
oc,
506+
ov,
507+
0,
486508
);
487509
}
488510
OperandSize::Size64 => {
489511
enc::xfuncref_dispatch_not_x64(
490-
&mut inverted, dst_code_w, dst_vmctx_w, src_reg, oc, ov, 0,
512+
&mut inverted,
513+
dst_code_w,
514+
dst_vmctx_w,
515+
src_reg,
516+
oc,
517+
ov,
518+
0,
491519
);
492520
}
493521
}
@@ -497,12 +525,24 @@ fn pulley_emit<P>(
497525
match size {
498526
OperandSize::Size32 => {
499527
enc::xfuncref_dispatch_not_x32(
500-
&mut inverted, dst_code_w, dst_vmctx_w, src_reg, oc, ov, inv_rel,
528+
&mut inverted,
529+
dst_code_w,
530+
dst_vmctx_w,
531+
src_reg,
532+
oc,
533+
ov,
534+
inv_rel,
501535
);
502536
}
503537
OperandSize::Size64 => {
504538
enc::xfuncref_dispatch_not_x64(
505-
&mut inverted, dst_code_w, dst_vmctx_w, src_reg, oc, ov, inv_rel,
539+
&mut inverted,
540+
dst_code_w,
541+
dst_vmctx_w,
542+
src_reg,
543+
oc,
544+
ov,
545+
inv_rel,
506546
);
507547
}
508548
}
@@ -513,12 +553,12 @@ fn pulley_emit<P>(
513553
sink.use_label_at_offset(taken_end - 4, *taken, LabelUse::PcRel);
514554
sink.add_cond_branch(*start_offset, taken_end, *taken, &inverted);
515555
patch_pc_rel_offset(sink, |sink| match size {
516-
OperandSize::Size32 => enc::xfuncref_dispatch_x32(
517-
sink, dst_code_w, dst_vmctx_w, src_reg, oc, ov, 0,
518-
),
519-
OperandSize::Size64 => enc::xfuncref_dispatch_x64(
520-
sink, dst_code_w, dst_vmctx_w, src_reg, oc, ov, 0,
521-
),
556+
OperandSize::Size32 => {
557+
enc::xfuncref_dispatch_x32(sink, dst_code_w, dst_vmctx_w, src_reg, oc, ov, 0)
558+
}
559+
OperandSize::Size64 => {
560+
enc::xfuncref_dispatch_x64(sink, dst_code_w, dst_vmctx_w, src_reg, oc, ov, 0)
561+
}
522562
});
523563
debug_assert_eq!(sink.cur_offset(), taken_end);
524564

@@ -559,12 +599,26 @@ fn pulley_emit<P>(
559599
match size {
560600
OperandSize::Size32 => {
561601
enc::xband_funcref_dispatch_not_x32(
562-
&mut inverted, dm_w, dc_w, dv_w, src_reg, oc, ov, 0,
602+
&mut inverted,
603+
dm_w,
604+
dc_w,
605+
dv_w,
606+
src_reg,
607+
oc,
608+
ov,
609+
0,
563610
);
564611
}
565612
OperandSize::Size64 => {
566613
enc::xband_funcref_dispatch_not_x64(
567-
&mut inverted, dm_w, dc_w, dv_w, src_reg, oc, ov, 0,
614+
&mut inverted,
615+
dm_w,
616+
dc_w,
617+
dv_w,
618+
src_reg,
619+
oc,
620+
ov,
621+
0,
568622
);
569623
}
570624
}
@@ -574,12 +628,26 @@ fn pulley_emit<P>(
574628
match size {
575629
OperandSize::Size32 => {
576630
enc::xband_funcref_dispatch_not_x32(
577-
&mut inverted, dm_w, dc_w, dv_w, src_reg, oc, ov, inv_rel,
631+
&mut inverted,
632+
dm_w,
633+
dc_w,
634+
dv_w,
635+
src_reg,
636+
oc,
637+
ov,
638+
inv_rel,
578639
);
579640
}
580641
OperandSize::Size64 => {
581642
enc::xband_funcref_dispatch_not_x64(
582-
&mut inverted, dm_w, dc_w, dv_w, src_reg, oc, ov, inv_rel,
643+
&mut inverted,
644+
dm_w,
645+
dc_w,
646+
dv_w,
647+
src_reg,
648+
oc,
649+
ov,
650+
inv_rel,
583651
);
584652
}
585653
}
@@ -589,12 +657,12 @@ fn pulley_emit<P>(
589657
sink.use_label_at_offset(taken_end - 4, *taken, LabelUse::PcRel);
590658
sink.add_cond_branch(*start_offset, taken_end, *taken, &inverted);
591659
patch_pc_rel_offset(sink, |sink| match size {
592-
OperandSize::Size32 => enc::xband_funcref_dispatch_x32(
593-
sink, dm_w, dc_w, dv_w, src_reg, oc, ov, 0,
594-
),
595-
OperandSize::Size64 => enc::xband_funcref_dispatch_x64(
596-
sink, dm_w, dc_w, dv_w, src_reg, oc, ov, 0,
597-
),
660+
OperandSize::Size32 => {
661+
enc::xband_funcref_dispatch_x32(sink, dm_w, dc_w, dv_w, src_reg, oc, ov, 0)
662+
}
663+
OperandSize::Size64 => {
664+
enc::xband_funcref_dispatch_x64(sink, dm_w, dc_w, dv_w, src_reg, oc, ov, 0)
665+
}
598666
});
599667
debug_assert_eq!(sink.cur_offset(), taken_end);
600668

cranelift/codegen/src/isa/pulley_shared/lower.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,35 @@ where
460460
}
461461
}
462462
}
463+
464+
// Debug-only structural invariant: each continuation block we're about
465+
// to absorb loads from must have exactly one predecessor (the brif that
466+
// matched the pattern). If a second predecessor reaches the
467+
// continuation, its control-flow path would observe the absorbed
468+
// load destinations as uninitialized — exactly the bug the
469+
// trap-on-null handler fix defends against. Building
470+
// `ControlFlowGraph` is O(n) so we gate the whole thing on
471+
// `#[cfg(debug_assertions)]`; the assertion + CFG construction
472+
// compile to nothing in release builds (no runtime overhead).
473+
#[cfg(debug_assertions)]
474+
if !to_sink.is_empty() {
475+
let cfg = crate::flowgraph::ControlFlowGraph::with_function(ctx.f);
476+
for (l_code, _) in &to_sink {
477+
let cont = ctx
478+
.f
479+
.layout
480+
.inst_block(*l_code)
481+
.expect("absorbed load belongs to a block");
482+
let n_preds = cfg.pred_iter(cont).count();
483+
debug_assert_eq!(
484+
n_preds, 1,
485+
"pulley funcref-dispatch fusion absorbs continuation-block \
486+
loads, so the continuation must have exactly one predecessor \
487+
(the brif being lowered); found {n_preds} predecessors for {cont:?}"
488+
);
489+
}
490+
}
491+
463492
for (l_code, l_vmctx) in to_sink {
464493
ctx.sink_pure_inst(l_code);
465494
ctx.sink_pure_inst(l_vmctx);

pulley/src/interp.rs

Lines changed: 64 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2474,8 +2474,14 @@ impl OpVisitor for Interpreter<'_> {
24742474
// pointer, so the field loads are valid memory accesses.
24752475
let base = s as *const u8;
24762476
unsafe {
2477-
let code = base.byte_offset(offset_code as isize).cast::<i64>().read_unaligned();
2478-
let vmctx = base.byte_offset(offset_vmctx as isize).cast::<i64>().read_unaligned();
2477+
let code = base
2478+
.byte_offset(offset_code as isize)
2479+
.cast::<i64>()
2480+
.read_unaligned();
2481+
let vmctx = base
2482+
.byte_offset(offset_vmctx as isize)
2483+
.cast::<i64>()
2484+
.read_unaligned();
24792485
self.state[dst_code].set_i64(code);
24802486
self.state[dst_vmctx].set_i64(vmctx);
24812487
}
@@ -2504,8 +2510,14 @@ impl OpVisitor for Interpreter<'_> {
25042510
} else {
25052511
let base = s as *const u8;
25062512
unsafe {
2507-
let code = base.byte_offset(offset_code as isize).cast::<i64>().read_unaligned();
2508-
let vmctx = base.byte_offset(offset_vmctx as isize).cast::<i64>().read_unaligned();
2513+
let code = base
2514+
.byte_offset(offset_code as isize)
2515+
.cast::<i64>()
2516+
.read_unaligned();
2517+
let vmctx = base
2518+
.byte_offset(offset_vmctx as isize)
2519+
.cast::<i64>()
2520+
.read_unaligned();
25092521
self.state[dst_code].set_i64(code);
25102522
self.state[dst_vmctx].set_i64(vmctx);
25112523
}
@@ -2528,8 +2540,14 @@ impl OpVisitor for Interpreter<'_> {
25282540
} else {
25292541
let base = s as usize as *const u8;
25302542
unsafe {
2531-
let code = base.byte_offset(offset_code as isize).cast::<i32>().read_unaligned();
2532-
let vmctx = base.byte_offset(offset_vmctx as isize).cast::<i32>().read_unaligned();
2543+
let code = base
2544+
.byte_offset(offset_code as isize)
2545+
.cast::<i32>()
2546+
.read_unaligned();
2547+
let vmctx = base
2548+
.byte_offset(offset_vmctx as isize)
2549+
.cast::<i32>()
2550+
.read_unaligned();
25332551
self.state[dst_code].set_i32(code);
25342552
self.state[dst_vmctx].set_i32(vmctx);
25352553
}
@@ -2553,8 +2571,14 @@ impl OpVisitor for Interpreter<'_> {
25532571
} else {
25542572
let base = s as usize as *const u8;
25552573
unsafe {
2556-
let code = base.byte_offset(offset_code as isize).cast::<i32>().read_unaligned();
2557-
let vmctx = base.byte_offset(offset_vmctx as isize).cast::<i32>().read_unaligned();
2574+
let code = base
2575+
.byte_offset(offset_code as isize)
2576+
.cast::<i32>()
2577+
.read_unaligned();
2578+
let vmctx = base
2579+
.byte_offset(offset_vmctx as isize)
2580+
.cast::<i32>()
2581+
.read_unaligned();
25582582
self.state[dst_code].set_i32(code);
25592583
self.state[dst_vmctx].set_i32(vmctx);
25602584
}
@@ -2590,8 +2614,14 @@ impl OpVisitor for Interpreter<'_> {
25902614
if s != 0 {
25912615
let base = masked as *const u8;
25922616
unsafe {
2593-
let code = base.byte_offset(offset_code as isize).cast::<i64>().read_unaligned();
2594-
let vmctx = base.byte_offset(offset_vmctx as isize).cast::<i64>().read_unaligned();
2617+
let code = base
2618+
.byte_offset(offset_code as isize)
2619+
.cast::<i64>()
2620+
.read_unaligned();
2621+
let vmctx = base
2622+
.byte_offset(offset_vmctx as isize)
2623+
.cast::<i64>()
2624+
.read_unaligned();
25952625
self.state[dst_code].set_i64(code);
25962626
self.state[dst_vmctx].set_i64(vmctx);
25972627
}
@@ -2621,8 +2651,14 @@ impl OpVisitor for Interpreter<'_> {
26212651
} else {
26222652
let base = masked as *const u8;
26232653
unsafe {
2624-
let code = base.byte_offset(offset_code as isize).cast::<i64>().read_unaligned();
2625-
let vmctx = base.byte_offset(offset_vmctx as isize).cast::<i64>().read_unaligned();
2654+
let code = base
2655+
.byte_offset(offset_code as isize)
2656+
.cast::<i64>()
2657+
.read_unaligned();
2658+
let vmctx = base
2659+
.byte_offset(offset_vmctx as isize)
2660+
.cast::<i64>()
2661+
.read_unaligned();
26262662
self.state[dst_code].set_i64(code);
26272663
self.state[dst_vmctx].set_i64(vmctx);
26282664
}
@@ -2646,8 +2682,14 @@ impl OpVisitor for Interpreter<'_> {
26462682
if s != 0 {
26472683
let base = masked as usize as *const u8;
26482684
unsafe {
2649-
let code = base.byte_offset(offset_code as isize).cast::<i32>().read_unaligned();
2650-
let vmctx = base.byte_offset(offset_vmctx as isize).cast::<i32>().read_unaligned();
2685+
let code = base
2686+
.byte_offset(offset_code as isize)
2687+
.cast::<i32>()
2688+
.read_unaligned();
2689+
let vmctx = base
2690+
.byte_offset(offset_vmctx as isize)
2691+
.cast::<i32>()
2692+
.read_unaligned();
26512693
self.state[dst_code].set_i32(code);
26522694
self.state[dst_vmctx].set_i32(vmctx);
26532695
}
@@ -2676,8 +2718,14 @@ impl OpVisitor for Interpreter<'_> {
26762718
} else {
26772719
let base = masked as usize as *const u8;
26782720
unsafe {
2679-
let code = base.byte_offset(offset_code as isize).cast::<i32>().read_unaligned();
2680-
let vmctx = base.byte_offset(offset_vmctx as isize).cast::<i32>().read_unaligned();
2721+
let code = base
2722+
.byte_offset(offset_code as isize)
2723+
.cast::<i32>()
2724+
.read_unaligned();
2725+
let vmctx = base
2726+
.byte_offset(offset_vmctx as isize)
2727+
.cast::<i32>()
2728+
.read_unaligned();
26812729
self.state[dst_code].set_i32(code);
26822730
self.state[dst_vmctx].set_i32(vmctx);
26832731
}

tests/all/pulley.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,11 @@ fn fusion_call_indirect_with_host_swap() -> Result<()> {
729729
let bytes = wat::parse_str(wat)?;
730730

731731
for use_pulley in [true, false] {
732-
let cfg = if use_pulley { pulley_trap_safe_config() } else { Config::new() };
732+
let cfg = if use_pulley {
733+
pulley_trap_safe_config()
734+
} else {
735+
Config::new()
736+
};
733737
let engine = Engine::new(&cfg)?;
734738
let module = Module::new(&engine, &bytes)?;
735739
let mut store = Store::new(&engine, ());
@@ -782,7 +786,11 @@ fn fusion_call_indirect_imported_table() -> Result<()> {
782786
let bytes_b = wat::parse_str(wat_b)?;
783787

784788
for use_pulley in [true, false] {
785-
let cfg = if use_pulley { pulley_trap_safe_config() } else { Config::new() };
789+
let cfg = if use_pulley {
790+
pulley_trap_safe_config()
791+
} else {
792+
Config::new()
793+
};
786794
let engine = Engine::new(&cfg)?;
787795
let module_a = Module::new(&engine, &bytes_a)?;
788796
let module_b = Module::new(&engine, &bytes_b)?;

0 commit comments

Comments
 (0)