diff --git a/cranelift/codegen/src/isa/pulley_shared/inst.isle b/cranelift/codegen/src/isa/pulley_shared/inst.isle index bdce4929d95e..a65592026303 100644 --- a/cranelift/codegen/src/isa/pulley_shared/inst.isle +++ b/cranelift/codegen/src/isa/pulley_shared/inst.isle @@ -21,6 +21,13 @@ ;; A pseudo-instruction to update unwind info. (Unwind (inst UnwindInst)) + ;; Implementation of `br_table`, uses `idx` to jump to one of `targets` or + ;; jumps to `default` is it's out-of-bounds. + (BrTable + (idx XReg) + (default MachLabel) + (targets BoxVecMachLabel)) + ;;;; Actual Instructions ;;;; ;; Raise a trap. @@ -547,6 +554,10 @@ (_ Unit (emit (MInst.BitcastIntFromFloat64 dst src)))) dst)) +(decl gen_br_table (XReg MachLabel BoxVecMachLabel) Unit) +(rule (gen_br_table idx default labels) + (emit (MInst.BrTable idx default labels))) + ;;;; Helpers for Emitting Calls ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (decl gen_call (SigRef ExternalName RelocDistance ValueSlice) InstOutput) diff --git a/cranelift/codegen/src/isa/pulley_shared/inst/emit.rs b/cranelift/codegen/src/isa/pulley_shared/inst/emit.rs index 3772c127955b..af593a2708e8 100644 --- a/cranelift/codegen/src/isa/pulley_shared/inst/emit.rs +++ b/cranelift/codegen/src/isa/pulley_shared/inst/emit.rs @@ -104,8 +104,8 @@ where // (with an `EmitIsland`). We check this in debug builds. This is `mut` // to allow disabling the check for `JTSequence`, which is always // emitted following an `EmitIsland`. - let start = sink.cur_offset(); - pulley_emit(self, sink, emit_info, state, start); + let mut start = sink.cur_offset(); + pulley_emit(self, sink, emit_info, state, &mut start); let end = sink.cur_offset(); assert!( @@ -124,9 +124,9 @@ where fn pulley_emit
(
inst: &Inst,
sink: &mut MachBuffer ,
- start_offset: u32,
+ start_offset: &mut u32,
) where
P: PulleyTargetKind,
{
@@ -218,8 +218,8 @@ fn pulley_emit (
Inst::IndirectCall { .. } => todo!(),
Inst::Jump { label } => {
- sink.use_label_at_offset(start_offset + 1, *label, LabelUse::Jump(1));
- sink.add_uncond_branch(start_offset, start_offset + 5, *label);
+ sink.use_label_at_offset(*start_offset + 1, *label, LabelUse::Jump(1));
+ sink.add_uncond_branch(*start_offset, *start_offset + 5, *label);
enc::jump(sink, 0x00000000);
}
@@ -229,7 +229,7 @@ fn pulley_emit (
not_taken,
} => {
// If taken.
- let taken_start = start_offset + 2;
+ let taken_start = *start_offset + 2;
let taken_end = taken_start + 4;
sink.use_label_at_offset(taken_start, *taken, LabelUse::Jump(2));
@@ -237,10 +237,10 @@ fn pulley_emit (
enc::br_if_not(&mut inverted, c, 0x00000000);
debug_assert_eq!(
inverted.len(),
- usize::try_from(taken_end - start_offset).unwrap()
+ usize::try_from(taken_end - *start_offset).unwrap()
);
- sink.add_cond_branch(start_offset, taken_end, *taken, &inverted);
+ sink.add_cond_branch(*start_offset, taken_end, *taken, &inverted);
enc::br_if(sink, c, 0x00000000);
debug_assert_eq!(sink.cur_offset(), taken_end);
@@ -261,7 +261,7 @@ fn pulley_emit (
} => {
br_if_cond_helper(
sink,
- start_offset,
+ *start_offset,
*src1,
*src2,
taken,
@@ -279,7 +279,7 @@ fn pulley_emit (
} => {
br_if_cond_helper(
sink,
- start_offset,
+ *start_offset,
*src1,
*src2,
taken,
@@ -297,7 +297,7 @@ fn pulley_emit (
} => {
br_if_cond_helper(
sink,
- start_offset,
+ *start_offset,
*src1,
*src2,
taken,
@@ -315,7 +315,7 @@ fn pulley_emit (
} => {
br_if_cond_helper(
sink,
- start_offset,
+ *start_offset,
*src1,
*src2,
taken,
@@ -333,7 +333,7 @@ fn pulley_emit (
} => {
br_if_cond_helper(
sink,
- start_offset,
+ *start_offset,
*src1,
*src2,
taken,
@@ -351,7 +351,7 @@ fn pulley_emit (
} => {
br_if_cond_helper(
sink,
- start_offset,
+ *start_offset,
*src1,
*src2,
taken,
@@ -484,6 +484,47 @@ fn pulley_emit (
Inst::BitcastIntFromFloat64 { dst, src } => enc::bitcast_int_from_float_64(sink, dst, src),
Inst::BitcastFloatFromInt32 { dst, src } => enc::bitcast_float_from_int_32(sink, dst, src),
Inst::BitcastFloatFromInt64 { dst, src } => enc::bitcast_float_from_int_64(sink, dst, src),
+
+ Inst::BrTable {
+ idx,
+ default,
+ targets,
+ } => {
+ // Encode the `br_table32` instruction directly which expects the
+ // next `amt` 4-byte integers to all be relative offsets. Each
+ // offset is the pc-relative offset of the branch destination.
+ //
+ // Pulley clamps the branch targets to the `amt` specified so the
+ // final branch target is the default jump target.
+ //
+ // Note that this instruction may have many branch targets so it
+ // manually checks to see if an island is needed. If so we emit a
+ // jump around the island before the `br_table32` itself gets
+ // emitted.
+ let amt = u32::try_from(targets.len() + 1).expect("too many branch targets");
+ let br_table_size = amt * 4 + 6;
+ if sink.island_needed(br_table_size) {
+ let label = sink.get_label();
+ );
crate::isle_prelude_caller_methods!(PulleyABICallSite );
- fn lower_br_table(&mut self, _index: Reg, _targets: &[MachLabel]) -> Unit {
- todo!()
- }
-
fn vreg_new(&mut self, r: Reg) -> VReg {
VReg::new(r).unwrap()
}
diff --git a/cranelift/filetests/filetests/isa/pulley32/br_table.clif b/cranelift/filetests/filetests/isa/pulley32/br_table.clif
new file mode 100644
index 000000000000..624b808192c1
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/pulley32/br_table.clif
@@ -0,0 +1,69 @@
+test compile precise-output
+target pulley32
+
+function %br_table(i32) -> i32 {
+block0(v0: i32):
+ br_table v0, block4, [block1, block2, block2, block3]
+
+block1:
+ v1 = iconst.i32 1
+ jump block5(v1)
+
+block2:
+ v2 = iconst.i32 2
+ jump block5(v2)
+
+block3:
+ v3 = iconst.i32 3
+ jump block5(v3)
+
+block4:
+ v4 = iconst.i32 4
+ jump block5(v4)
+
+block5(v5: i32):
+ v6 = iadd.i32 v0, v5
+ return v6
+}
+
+; VCode:
+; block0:
+; br_table x0 MachLabel(6) [MachLabel(5), MachLabel(1), MachLabel(2), MachLabel(3)]
+; block1:
+; jump label4
+; block2:
+; jump label4
+; block3:
+; x5 = xconst8 3
+; jump label7
+; block4:
+; x5 = xconst8 2
+; jump label7
+; block5:
+; x5 = xconst8 1
+; jump label7
+; block6:
+; x5 = xconst8 4
+; jump label7
+; block7:
+; x0 = xadd32 x0, x5
+; ret
+;
+; Disassembled:
+; br_table32 x0, 5
+; 0x29 // target = 0x2f
+; 0x1d // target = 0x27
+; 0x19 // target = 0x27
+; 0xd // target = 0x1f
+; 0x21 // target = 0x37
+; jump 0xd // target = 0x27
+; xconst8 x5, 3
+; jump 0x18 // target = 0x3a
+; xconst8 x5, 2
+; jump 0x10 // target = 0x3a
+; xconst8 x5, 1
+; jump 0x8 // target = 0x3a
+; xconst8 x5, 4
+; xadd32 x0, x0, x5
+; ret
+
diff --git a/cranelift/filetests/filetests/isa/pulley64/br_table.clif b/cranelift/filetests/filetests/isa/pulley64/br_table.clif
new file mode 100644
index 000000000000..8c334abc9be4
--- /dev/null
+++ b/cranelift/filetests/filetests/isa/pulley64/br_table.clif
@@ -0,0 +1,69 @@
+test compile precise-output
+target pulley64
+
+function %br_table(i32) -> i32 {
+block0(v0: i32):
+ br_table v0, block4, [block1, block2, block2, block3]
+
+block1:
+ v1 = iconst.i32 1
+ jump block5(v1)
+
+block2:
+ v2 = iconst.i32 2
+ jump block5(v2)
+
+block3:
+ v3 = iconst.i32 3
+ jump block5(v3)
+
+block4:
+ v4 = iconst.i32 4
+ jump block5(v4)
+
+block5(v5: i32):
+ v6 = iadd.i32 v0, v5
+ return v6
+}
+
+; VCode:
+; block0:
+; br_table x0 MachLabel(6) [MachLabel(5), MachLabel(1), MachLabel(2), MachLabel(3)]
+; block1:
+; jump label4
+; block2:
+; jump label4
+; block3:
+; x5 = xconst8 3
+; jump label7
+; block4:
+; x5 = xconst8 2
+; jump label7
+; block5:
+; x5 = xconst8 1
+; jump label7
+; block6:
+; x5 = xconst8 4
+; jump label7
+; block7:
+; x0 = xadd32 x0, x5
+; ret
+;
+; Disassembled:
+; br_table32 x0, 5
+; 0x29 // target = 0x2f
+; 0x1d // target = 0x27
+; 0x19 // target = 0x27
+; 0xd // target = 0x1f
+; 0x21 // target = 0x37
+; jump 0xd // target = 0x27
+; xconst8 x5, 3
+; jump 0x18 // target = 0x3a
+; xconst8 x5, 2
+; jump 0x10 // target = 0x3a
+; xconst8 x5, 1
+; jump 0x8 // target = 0x3a
+; xconst8 x5, 4
+; xadd32 x0, x0, x5
+; ret
+
diff --git a/pulley/fuzz/src/interp.rs b/pulley/fuzz/src/interp.rs
index 5fd70b082c16..e0bac2e104fd 100644
--- a/pulley/fuzz/src/interp.rs
+++ b/pulley/fuzz/src/interp.rs
@@ -116,6 +116,7 @@ fn op_is_safe_for_fuzzing(op: &Op) -> bool {
Op::XPop32(_) | Op::XPop64(_) => false,
Op::XPush32Many(_) | Op::XPush64Many(_) => false,
Op::XPop32Many(_) | Op::XPop64Many(_) => false,
+ Op::BrTable32(_) => false,
}
}
diff --git a/pulley/src/disas.rs b/pulley/src/disas.rs
index 248309cd45d1..b146773660df 100644
--- a/pulley/src/disas.rs
+++ b/pulley/src/disas.rs
@@ -88,6 +88,17 @@ impl<'a> Disassembler<'a> {
val.disas(self.start + self.start_offset, &mut self.temp);
}
}
+
+ fn disas_br_table32(&mut self, reg: XReg, amt: u32) {
+ self.disas_op("br_table32", &[®, &amt]);
+ for _ in 0..amt {
+ self.after_visit();
+ self.start = self.bytecode.position();
+ if let Ok(offset) = PcRelOffset::decode(self.bytecode()) {
+ offset.disas(self.start, &mut self.temp);
+ }
+ }
+ }
}
/// Anything inside an instruction that can be disassembled: registers,
@@ -206,11 +217,36 @@ macro_rules! impl_disas {
)*
) => {
$(
- fn $snake_name(&mut self $( $( , $field : $field_ty )* )? ) {
- self.disas_op(stringify!($snake_name), &[$($(&$field),*)?])
- }
+ impl_disas!(@one $snake_name = $name $( { $($field: $field_ty),* } )?);
)*
};
+
+ // Diassembling `br_table` is a bit special as it has trailing byte after
+ // the opcode of the branch table itself.
+ (
+ @one br_table32 = BrTable32 $( {
+ $(
+ $field:ident : $field_ty:ty
+ ),*
+ } )?
+ ) => {
+ fn br_table32(&mut self $( $( , $field : $field_ty )* )? ) {
+ self.disas_br_table32($($($field),*)?)
+ }
+ };
+
+ // All other opcodes other than `br_table` are handled in the same manner.
+ (
+ @one $snake_name:ident = $name:ident $( {
+ $(
+ $field:ident : $field_ty:ty
+ ),*
+ } )?
+ ) => {
+ fn $snake_name(&mut self $( $( , $field : $field_ty )* )? ) {
+ self.disas_op(stringify!($snake_name), &[$($(&$field),*)?])
+ }
+ };
}
impl<'a> OpVisitor for Disassembler<'a> {
diff --git a/pulley/src/interp.rs b/pulley/src/interp.rs
index c0817f454144..51200c79cc05 100644
--- a/pulley/src/interp.rs
+++ b/pulley/src/interp.rs
@@ -1161,6 +1161,15 @@ impl OpVisitor for Interpreter<'_> {
self.state[dst].set_f64(f64::from_ne_bytes(val.to_ne_bytes()));
ControlFlow::Continue(())
}
+
+ fn br_table32(&mut self, idx: XReg, amt: u32) -> ControlFlow