Skip to content

Commit

Permalink
cranelift: Add Bswap instruction (bytecodealliance#1092)
Browse files Browse the repository at this point in the history
Adds Bswap to the Cranelift IR. Implements the Bswap instruction
in the x64 and aarch64 codegen backends. Cranelift users can now:
```
builder.ins().bswap(value)
```
to get a native byteswap instruction.

* x64: implements the 32- and 64-bit bswap instruction, following
the pattern set by similar unary instrutions (Neg and Not) - it
only operates on a dst register, but is parameterized with both
a src and dst which are expected to be the same register.

As x64 bswap instruction is only for 32- or 64-bit registers,
the 16-bit swap is implemented as a rotate left by 8.

Updated x64 RexFlags type to support emitting for single-operand
instructions like bswap

* aarch64: Bswap gets emitted as aarch64 rev16, rev32,
or rev64 instruction as appropriate.

* s390x: Bswap was already supported in backend, just had to add
a bit of plumbing

* For completeness, added bswap to the interpreter as well.

* added filetests and runtests for each ISA

* added bswap to fuzzgen, thanks to afonso360 for the code there

* 128-bit swaps are not yet implemented, that can be done later
  • Loading branch information
11evan committed Oct 31, 2022
1 parent 434fbf2 commit a284a24
Show file tree
Hide file tree
Showing 24 changed files with 455 additions and 0 deletions.
23 changes: 23 additions & 0 deletions cranelift/codegen/meta/src/shared/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,12 @@ pub(crate) fn define(
TypeSetBuilder::new().ints(Interval::All).build(),
);

let iSwappable = &TypeVar::new(
"iSwappable",
"A multi byte scalar integer type",
TypeSetBuilder::new().ints(16..128).build(),
);

let iAddr = &TypeVar::new(
"iAddr",
"An integer address type",
Expand Down Expand Up @@ -2699,6 +2705,23 @@ pub(crate) fn define(
.operands_out(vec![a]),
);

let x = &Operand::new("x", iSwappable);
let a = &Operand::new("a", iSwappable);

ig.push(
Inst::new(
"bswap",
r#"
Reverse the byte order of an integer.
Reverses the bytes in ``x``.
"#,
&formats.unary,
)
.operands_in(vec![x])
.operands_out(vec![a]),
);

let x = &Operand::new("x", Int);
let a = &Operand::new("a", Int);

Expand Down
15 changes: 15 additions & 0 deletions cranelift/codegen/src/isa/aarch64/inst.isle
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,10 @@
(RBit)
(Clz)
(Cls)
;; Byte reverse
(Rev16)
(Rev32)
(Rev64)
))

(type MemLabel extern (enum))
Expand Down Expand Up @@ -2579,6 +2583,17 @@
(decl a64_cls (Type Reg) Reg)
(rule (a64_cls ty x) (bit_rr (BitOp.Cls) ty x))

;; Helpers for generating `rev` instructions

(decl a64_rev16 (Type Reg) Reg)
(rule (a64_rev16 ty x) (bit_rr (BitOp.Rev16) ty x))

(decl a64_rev32 (Type Reg) Reg)
(rule (a64_rev32 ty x) (bit_rr (BitOp.Rev32) ty x))

(decl a64_rev64 (Type Reg) Reg)
(rule (a64_rev64 ty x) (bit_rr (BitOp.Rev64) ty x))

;; Helpers for generating `eon` instructions.

(decl eon (Type Reg Reg) Reg)
Expand Down
3 changes: 3 additions & 0 deletions cranelift/codegen/src/isa/aarch64/inst/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -934,6 +934,9 @@ impl MachInstEmit for Inst {
BitOp::RBit => (0b00000, 0b000000),
BitOp::Clz => (0b00000, 0b000100),
BitOp::Cls => (0b00000, 0b000101),
BitOp::Rev16 => (0b00000, 0b000001),
BitOp::Rev32 => (0b00000, 0b000010),
BitOp::Rev64 => (0b00000, 0b000011),
};
sink.put4(enc_bit_rr(size.sf_bit(), op1, op2, rn, rd))
}
Expand Down
55 changes: 55 additions & 0 deletions cranelift/codegen/src/isa/aarch64/inst/emit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1375,6 +1375,61 @@ fn test_aarch64_binemit() {
"cls x21, x16",
));

insns.push((
Inst::BitRR {
op: BitOp::Rev16,
size: OperandSize::Size64,
rd: writable_xreg(2),
rn: xreg(11),
},
"6205C0DA",
"rev16 x2, x11",
));

insns.push((
Inst::BitRR {
op: BitOp::Rev16,
size: OperandSize::Size32,
rd: writable_xreg(3),
rn: xreg(21),
},
"A306C05A",
"rev16 w3, w21",
));

insns.push((
Inst::BitRR {
op: BitOp::Rev32,
size: OperandSize::Size64,
rd: writable_xreg(2),
rn: xreg(11),
},
"6209C0DA",
"rev32 x2, x11",
));

insns.push((
Inst::BitRR {
op: BitOp::Rev32,
size: OperandSize::Size32,
rd: writable_xreg(3),
rn: xreg(21),
},
"A30AC05A",
"rev32 w3, w21",
));

insns.push((
Inst::BitRR {
op: BitOp::Rev64,
size: OperandSize::Size64,
rd: writable_xreg(1),
rn: xreg(10),
},
"410DC0DA",
"rev64 x1, x10",
));

insns.push((
Inst::ULoad8 {
rd: writable_xreg(1),
Expand Down
3 changes: 3 additions & 0 deletions cranelift/codegen/src/isa/aarch64/inst/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ impl BitOp {
BitOp::RBit => "rbit",
BitOp::Clz => "clz",
BitOp::Cls => "cls",
BitOp::Rev16 => "rev16",
BitOp::Rev32 => "rev32",
BitOp::Rev64 => "rev64",
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions cranelift/codegen/src/isa/aarch64/lower.isle
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,17 @@
(rule -1 (lower (has_type ty (cls x)))
(a64_cls ty x))

;;;; Rules for `bswap` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(rule (lower (has_type $I16 (bswap x)))
(a64_rev16 $I16 x))

(rule (lower (has_type $I32 (bswap x)))
(a64_rev32 $I32 x))

(rule (lower (has_type $I64 (bswap x)))
(a64_rev64 $I64 x))

;;;; Rules for `bmask` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Bmask tests the value against zero, and uses `csetm` to assert the result.
Expand Down
2 changes: 2 additions & 0 deletions cranelift/codegen/src/isa/aarch64/lower_inst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ pub(crate) fn lower_insn_to_regs(

Opcode::Bitrev | Opcode::Clz | Opcode::Cls | Opcode::Ctz => implemented_in_isle(ctx),

Opcode::Bswap => implemented_in_isle(ctx),

Opcode::Popcnt => implemented_in_isle(ctx),

Opcode::Load
Expand Down
12 changes: 12 additions & 0 deletions cranelift/codegen/src/isa/s390x/lower.isle
Original file line number Diff line number Diff line change
Expand Up @@ -1188,6 +1188,18 @@
7 6 5 4 3 2 1 0))))


;;;; Rules for `bswap` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(rule (lower (has_type $I16 (bswap x)))
(lshr_imm $I32 (bswap_reg $I32 x) 16))

(rule (lower (has_type $I32 (bswap x)))
(bswap_reg $I32 x))

(rule (lower (has_type $I64 (bswap x)))
(bswap_reg $I64 x))


;;;; Rules for `clz` ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; The FLOGR hardware instruction always operates on the full 64-bit register.
Expand Down
1 change: 1 addition & 0 deletions cranelift/codegen/src/isa/s390x/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ impl LowerBackend for S390xBackend {
| Opcode::Vselect
| Opcode::Bmask
| Opcode::Bitrev
| Opcode::Bswap
| Opcode::Clz
| Opcode::Cls
| Opcode::Ctz
Expand Down
15 changes: 15 additions & 0 deletions cranelift/codegen/src/isa/x64/encoding/rex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,21 @@ impl RexFlags {
(self.0 & 2) != 0
}

#[inline(always)]
pub(crate) fn emit_one_op(&self, sink: &mut MachBuffer<Inst>, enc_e: u8) {
// Register Operand coded in Opcode Byte
// REX.R and REX.X unused
// REX.B == 1 accesses r8-r15
let w = if self.must_clear_w() { 0 } else { 1 };
let r = 0;
let x = 0;
let b = (enc_e >> 3) & 1;
let rex = 0x40 | (w << 3) | (r << 2) | (x << 1) | b;
if rex != 0x40 || self.must_always_emit() {
sink.put1(rex);
}
}

#[inline(always)]
pub(crate) fn emit_two_op(&self, sink: &mut MachBuffer<Inst>, enc_g: u8, enc_e: u8) {
let w = if self.must_clear_w() { 0 } else { 1 };
Expand Down
15 changes: 15 additions & 0 deletions cranelift/codegen/src/isa/x64/inst.isle
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,11 @@
(Setcc (cc CC)
(dst WritableGpr))

;; Swaps byte order in register
(Bswap (size OperandSize) ;; 4 or 8
(src Gpr)
(dst WritableGpr))

;; =========================================
;; Conditional moves.

Expand Down Expand Up @@ -1959,6 +1964,16 @@
(rule (x64_sar ty src1 src2)
(shift_r ty (ShiftKind.ShiftRightArithmetic) src1 src2))

;; Helper for creating byteswap instructions.
;; In x64, 32- and 64-bit registers use BSWAP instruction, and
;; for 16-bit registers one must instead use xchg or rol/ror
(decl x64_bswap (Type Gpr) Gpr)
(rule (x64_bswap ty src)
(let ((dst WritableGpr (temp_writable_gpr))
(size OperandSize (operand_size_of_type_32_64 ty))
(_ Unit (emit (MInst.Bswap size src dst))))
dst))

;; Helper for creating `MInst.CmpRmiR` instructions.
(decl cmp_rmi_r (OperandSize CmpOpcode GprMemImm Gpr) ProducesFlags)
(rule (cmp_rmi_r size opcode src1 src2)
Expand Down
15 changes: 15 additions & 0 deletions cranelift/codegen/src/isa/x64/inst/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,21 @@ pub(crate) fn emit(
);
}

Inst::Bswap { size, src, dst } => {
let src = allocs.next(src.to_reg());
let dst = allocs.next(dst.to_reg().to_reg());
debug_assert_eq!(src, dst);
let enc_reg = int_reg_enc(dst);

// BSWAP reg32 is (REX.W==0) 0F C8
// BSWAP reg64 is (REX.W==1) 0F C8
let rex_flags = RexFlags::from(*size);
rex_flags.emit_one_op(sink, enc_reg);

sink.put1(0x0F);
sink.put1(0xC8 | (enc_reg & 7));
}

Inst::Cmove {
size,
cc,
Expand Down
56 changes: 56 additions & 0 deletions cranelift/codegen/src/isa/x64/inst/emit_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ impl Inst {
Inst::Setcc { cc, dst }
}

fn bswap(size: OperandSize, dst: Writable<Reg>) -> Inst {
debug_assert!(dst.to_reg().class() == RegClass::Int);
let src = Gpr::new(dst.to_reg()).unwrap();
let dst = WritableGpr::from_writable_reg(dst).unwrap();
Inst::Bswap { size, src, dst }
}

fn xmm_rm_r_imm(
op: SseOpcode,
src: RegMem,
Expand Down Expand Up @@ -3505,6 +3512,55 @@ fn test_x64_emit() {
insns.push((Inst::setcc(CC::LE, w_r14), "410F9EC6", "setle %r14b"));
insns.push((Inst::setcc(CC::P, w_r9), "410F9AC1", "setp %r9b"));
insns.push((Inst::setcc(CC::NP, w_r8), "410F9BC0", "setnp %r8b"));

// ========================================================
// Bswap
insns.push((
Inst::bswap(OperandSize::Size64, w_rax),
"480FC8",
"bswapq %rax, %rax",
));
insns.push((
Inst::bswap(OperandSize::Size64, w_r8),
"490FC8",
"bswapq %r8, %r8",
));
insns.push((
Inst::bswap(OperandSize::Size32, w_rax),
"0FC8",
"bswapl %eax, %eax",
));
insns.push((
Inst::bswap(OperandSize::Size64, w_rcx),
"480FC9",
"bswapq %rcx, %rcx",
));
insns.push((
Inst::bswap(OperandSize::Size32, w_rcx),
"0FC9",
"bswapl %ecx, %ecx",
));
insns.push((
Inst::bswap(OperandSize::Size64, w_r11),
"490FCB",
"bswapq %r11, %r11",
));
insns.push((
Inst::bswap(OperandSize::Size32, w_r11),
"410FCB",
"bswapl %r11d, %r11d",
));
insns.push((
Inst::bswap(OperandSize::Size64, w_r14),
"490FCE",
"bswapq %r14, %r14",
));
insns.push((
Inst::bswap(OperandSize::Size32, w_r14),
"410FCE",
"bswapl %r14d, %r14d",
));

// ========================================================
// Cmove
insns.push((
Expand Down
16 changes: 16 additions & 0 deletions cranelift/codegen/src/isa/x64/inst/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ impl Inst {
Inst::AluRmiR { .. }
| Inst::AluRM { .. }
| Inst::AtomicRmwSeq { .. }
| Inst::Bswap { .. }
| Inst::CallKnown { .. }
| Inst::CallUnknown { .. }
| Inst::CheckedDivOrRemSeq { .. }
Expand Down Expand Up @@ -1373,6 +1374,17 @@ impl PrettyPrint for Inst {
format!("{} {}", ljustify2("set".to_string(), cc.to_string()), dst)
}

Inst::Bswap { size, src, dst } => {
let src = pretty_print_reg(src.to_reg(), size.to_bytes(), allocs);
let dst = pretty_print_reg(dst.to_reg().to_reg(), size.to_bytes(), allocs);
format!(
"{} {}, {}",
ljustify2("bswap".to_string(), suffix_bwlq(*size)),
src,
dst
)
}

Inst::Cmove {
size,
cc,
Expand Down Expand Up @@ -1953,6 +1965,10 @@ fn x64_get_operands<F: Fn(VReg) -> VReg>(inst: &Inst, collector: &mut OperandCol
Inst::Setcc { dst, .. } => {
collector.reg_def(dst.to_writable_reg());
}
Inst::Bswap { src, dst, .. } => {
collector.reg_use(src.to_reg());
collector.reg_reuse_def(dst.to_writable_reg(), 0);
}
Inst::Cmove {
consequent,
alternative,
Expand Down
Loading

0 comments on commit a284a24

Please sign in to comment.