Skip to content

Commit 22f51bc

Browse files
authored
x64: Add conditional jumps to the new assembler (#11196)
While this doesn't remove any pseudo insts from ISLE (as was thought might be the case with `WinchJmpIf`) this does clean up some emission code to use some helpers and make it more clear what's happening.
1 parent c848860 commit 22f51bc

File tree

3 files changed

+191
-112
lines changed

3 files changed

+191
-112
lines changed
Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,66 @@
11
use crate::dsl::{Customization::*, Feature::*, Inst, Location::*};
2-
use crate::dsl::{fmt, inst, r, rex};
2+
use crate::dsl::{fmt, inst, r, rex, sxq};
33

44
#[rustfmt::skip] // Keeps instructions on a single line.
55
pub fn list() -> Vec<Inst> {
66
vec![
77
inst("jmpq", fmt("M", [r(rm64)]), rex([0xFF]).digit(4), _64b).custom(Display),
8+
9+
inst("jmp", fmt("D8", [r(sxq(imm8))]), rex([0xEB]).ib(), _64b | compat).custom(Display),
10+
inst("jmp", fmt("D32", [r(sxq(imm32))]), rex([0xE9]).id(), _64b | compat).custom(Display),
11+
12+
// Note that the Intel manual lists many mnemonics for this family of
13+
// instructions which are duplicates of other mnemonics. The order here
14+
// matches the order in the manual and comments are left when variants
15+
// are omitted due to the instructions being duplicates of another.
16+
inst("ja", fmt("D8", [r(sxq(imm8))]), rex([0x77]).ib(), _64b | compat).custom(Display),
17+
inst("ja", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x87]).id(), _64b | compat).custom(Display),
18+
inst("jae", fmt("D8", [r(sxq(imm8))]), rex([0x73]).ib(), _64b | compat).custom(Display),
19+
inst("jae", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x83]).id(), _64b | compat).custom(Display),
20+
inst("jb", fmt("D8", [r(sxq(imm8))]), rex([0x72]).ib(), _64b | compat).custom(Display),
21+
inst("jb", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x82]).id(), _64b | compat).custom(Display),
22+
inst("jbe", fmt("D8", [r(sxq(imm8))]), rex([0x76]).ib(), _64b | compat).custom(Display),
23+
inst("jbe", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x86]).id(), _64b | compat).custom(Display),
24+
// jc == jb
25+
// TODO: jcx
26+
// TODO: jecx
27+
// TODO: jrcx
28+
inst("je", fmt("D8", [r(sxq(imm8))]), rex([0x74]).ib(), _64b | compat).custom(Display),
29+
inst("je", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x84]).id(), _64b | compat).custom(Display),
30+
inst("jg", fmt("D8", [r(sxq(imm8))]), rex([0x7F]).ib(), _64b | compat).custom(Display),
31+
inst("jg", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x8F]).id(), _64b | compat).custom(Display),
32+
inst("jge", fmt("D8", [r(sxq(imm8))]), rex([0x7D]).ib(), _64b | compat).custom(Display),
33+
inst("jge", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x8D]).id(), _64b | compat).custom(Display),
34+
inst("jl", fmt("D8", [r(sxq(imm8))]), rex([0x7C]).ib(), _64b | compat).custom(Display),
35+
inst("jl", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x8C]).id(), _64b | compat).custom(Display),
36+
inst("jle", fmt("D8", [r(sxq(imm8))]), rex([0x7E]).ib(), _64b | compat).custom(Display),
37+
inst("jle", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x8E]).id(), _64b | compat).custom(Display),
38+
// jna == jbe
39+
// jnae == jb
40+
// jnb == jae
41+
// jnbe == ja
42+
// jnc == jae
43+
inst("jne", fmt("D8", [r(sxq(imm8))]), rex([0x75]).ib(), _64b | compat).custom(Display),
44+
inst("jne", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x85]).id(), _64b | compat).custom(Display),
45+
// jng == jle
46+
// jnge == jl
47+
// jnl == jge
48+
// jnle == jg
49+
inst("jno", fmt("D8", [r(sxq(imm8))]), rex([0x71]).ib(), _64b | compat).custom(Display),
50+
inst("jno", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x81]).id(), _64b | compat).custom(Display),
51+
inst("jnp", fmt("D8", [r(sxq(imm8))]), rex([0x7B]).ib(), _64b | compat).custom(Display),
52+
inst("jnp", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x8B]).id(), _64b | compat).custom(Display),
53+
inst("jns", fmt("D8", [r(sxq(imm8))]), rex([0x79]).ib(), _64b | compat).custom(Display),
54+
inst("jns", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x89]).id(), _64b | compat).custom(Display),
55+
// jnz == jne
56+
inst("jo", fmt("D8", [r(sxq(imm8))]), rex([0x70]).ib(), _64b | compat).custom(Display),
57+
inst("jo", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x80]).id(), _64b | compat).custom(Display),
58+
inst("jp", fmt("D8", [r(sxq(imm8))]), rex([0x7A]).ib(), _64b | compat).custom(Display),
59+
inst("jp", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x8A]).id(), _64b | compat).custom(Display),
60+
// jpe == jp
61+
// jpo == jnp
62+
inst("js", fmt("D8", [r(sxq(imm8))]), rex([0x78]).ib(), _64b | compat).custom(Display),
63+
inst("js", fmt("D32", [r(sxq(imm32))]), rex([0x0F, 0x88]).id(), _64b | compat).custom(Display),
64+
// jz == je
865
]
966
}

cranelift/assembler-x64/src/custom.rs

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,7 @@ pub mod display {
195195

196196
pub fn callq_d(f: &mut fmt::Formatter, inst: &inst::callq_d) -> fmt::Result {
197197
let inst::callq_d { imm32 } = inst;
198-
let displacement = i64::from(imm32.value()) + 5;
199-
if displacement >= 0 && displacement < 10 {
200-
write!(f, "callq {displacement:}")
201-
} else {
202-
write!(f, "callq {displacement:#x}")
203-
}
198+
display_displacement(f, "callq", i64::from(imm32.value()) + 5)
204199
}
205200

206201
pub fn callq_m<R: Registers>(f: &mut fmt::Formatter, inst: &inst::callq_m<R>) -> fmt::Result {
@@ -585,6 +580,61 @@ pub mod display {
585580
let rm64 = rm64.to_string(Size::Quadword);
586581
write!(f, "jmpq *{rm64}")
587582
}
583+
584+
pub fn jmp_d8(f: &mut fmt::Formatter<'_>, jmp: &inst::jmp_d8) -> fmt::Result {
585+
let inst::jmp_d8 { imm8 } = jmp;
586+
display_displacement(f, "jmp", i64::from(imm8.value()) + 2)
587+
}
588+
589+
pub fn jmp_d32(f: &mut fmt::Formatter<'_>, jmp: &inst::jmp_d32) -> fmt::Result {
590+
let inst::jmp_d32 { imm32 } = jmp;
591+
display_displacement(f, "jmp", i64::from(imm32.value()) + 5)
592+
}
593+
594+
macro_rules! jcc {
595+
($($mnemonic:tt = $j8:ident / $j32:ident;)*) => ($(
596+
pub fn $j8(f: &mut fmt::Formatter<'_>, jmp: &inst::$j8) -> fmt::Result {
597+
let inst::$j8 { imm8 } = jmp;
598+
display_displacement(f, $mnemonic, i64::from(imm8.value()) + 2)
599+
}
600+
601+
pub fn $j32(f: &mut fmt::Formatter<'_>, jmp: &inst::$j32) -> fmt::Result {
602+
let inst::$j32 { imm32 } = jmp;
603+
display_displacement(f, $mnemonic, i64::from(imm32.value()) + 6)
604+
}
605+
)*)
606+
}
607+
608+
jcc! {
609+
"ja" = ja_d8 / ja_d32;
610+
"jae" = jae_d8 / jae_d32;
611+
"jb" = jb_d8 / jb_d32;
612+
"jbe" = jbe_d8 / jbe_d32;
613+
"je" = je_d8 / je_d32;
614+
"jg" = jg_d8 / jg_d32;
615+
"jge" = jge_d8 / jge_d32;
616+
"jl" = jl_d8 / jl_d32;
617+
"jle" = jle_d8 / jle_d32;
618+
"jne" = jne_d8 / jne_d32;
619+
"jno" = jno_d8 / jno_d32;
620+
"jnp" = jnp_d8 / jnp_d32;
621+
"jns" = jns_d8 / jns_d32;
622+
"jo" = jo_d8 / jo_d32;
623+
"jp" = jp_d8 / jp_d32;
624+
"js" = js_d8 / js_d32;
625+
}
626+
627+
fn display_displacement(
628+
f: &mut fmt::Formatter<'_>,
629+
mnemonic: &str,
630+
displacement: i64,
631+
) -> fmt::Result {
632+
if displacement >= 0 && displacement < 10 {
633+
write!(f, "{mnemonic} {displacement}")
634+
} else {
635+
write!(f, "{mnemonic} {displacement:#x}")
636+
}
637+
}
588638
}
589639

590640
pub mod visit {

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

Lines changed: 77 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,73 @@ fn one_way_jmp(sink: &mut MachBuffer<Inst>, cc: CC, label: MachLabel) {
3535
let cond_start = sink.cur_offset();
3636
let cond_disp_off = cond_start + 2;
3737
sink.use_label_at_offset(cond_disp_off, label, LabelUse::JmpRel32);
38-
sink.put1(0x0F);
39-
sink.put1(0x80 + cc.get_enc());
40-
sink.put4(0x0);
38+
emit_jcc_no_offset(sink, cc);
39+
debug_assert_eq!(sink.cur_offset(), cond_disp_off + 4);
40+
}
41+
42+
/// Like `one_way_jmp` above emitting a conditional jump, but also using
43+
/// `MachBuffer::add_cond_branch`.
44+
fn cond_jmp(sink: &mut MachBuffer<Inst>, cc: CC, label: MachLabel) {
45+
let cond_start = sink.cur_offset();
46+
let cond_disp_off = cond_start + 2;
47+
let cond_end = cond_start + 6;
48+
49+
sink.use_label_at_offset(cond_disp_off, label, LabelUse::JmpRel32);
50+
// FIXME: ideally this `inverted` calculation would go through the external
51+
// assembler, but for now it's left done manually.
52+
let inverted: [u8; 6] = [0x0F, 0x80 + (cc.invert().get_enc()), 0x00, 0x00, 0x00, 0x00];
53+
sink.add_cond_branch(cond_start, cond_end, label, &inverted[..]);
54+
55+
emit_jcc_no_offset(sink, cc);
56+
57+
debug_assert_eq!(sink.cur_offset(), cond_disp_off + 4);
58+
debug_assert_eq!(sink.cur_offset(), cond_end);
59+
}
60+
61+
fn emit_jcc_no_offset(sink: &mut MachBuffer<Inst>, cc: CC) {
62+
// Note that the disassembler matches Capstone which doesn't match the `CC`
63+
// enum directly as Intel has multiple mnemonics use the same encoding.
64+
let inst: AsmInst = match cc {
65+
CC::Z => asm::inst::je_d32::new(0).into(), // jz == je
66+
CC::NZ => asm::inst::jne_d32::new(0).into(), // jnz == jne
67+
CC::B => asm::inst::jb_d32::new(0).into(),
68+
CC::NB => asm::inst::jae_d32::new(0).into(), // jnb == jae
69+
CC::BE => asm::inst::jbe_d32::new(0).into(),
70+
CC::NBE => asm::inst::ja_d32::new(0).into(), // jnbe == ja
71+
CC::L => asm::inst::jl_d32::new(0).into(),
72+
CC::LE => asm::inst::jle_d32::new(0).into(),
73+
CC::NL => asm::inst::jge_d32::new(0).into(), // jnl == jge
74+
CC::NLE => asm::inst::jg_d32::new(0).into(), // jnle == jg
75+
CC::O => asm::inst::jo_d32::new(0).into(),
76+
CC::NO => asm::inst::jno_d32::new(0).into(),
77+
CC::P => asm::inst::jp_d32::new(0).into(),
78+
CC::NP => asm::inst::jnp_d32::new(0).into(),
79+
CC::S => asm::inst::js_d32::new(0).into(),
80+
CC::NS => asm::inst::jns_d32::new(0).into(),
81+
};
82+
inst.encode(&mut external::AsmCodeSink {
83+
sink,
84+
incoming_arg_offset: 0,
85+
slot_offset: 0,
86+
});
87+
}
88+
89+
/// Emits an unconditional branch.
90+
fn uncond_jmp(sink: &mut MachBuffer<Inst>, label: MachLabel) {
91+
let uncond_start = sink.cur_offset();
92+
let uncond_disp_off = uncond_start + 1;
93+
let uncond_end = uncond_start + 5;
94+
95+
sink.use_label_at_offset(uncond_disp_off, label, LabelUse::JmpRel32);
96+
sink.add_uncond_branch(uncond_start, uncond_end, label);
97+
98+
asm::inst::jmp_d32::new(0).encode(&mut external::AsmCodeSink {
99+
sink,
100+
incoming_arg_offset: 0,
101+
slot_offset: 0,
102+
});
103+
debug_assert_eq!(sink.cur_offset(), uncond_disp_off + 4);
104+
debug_assert_eq!(sink.cur_offset(), uncond_end);
41105
}
42106

43107
/// Emits a relocation, attaching the current source location as well.
@@ -427,11 +491,11 @@ pub(crate) fn emit(
427491
// Note: this is not `Inst::Jmp { .. }.emit(..)` because we have
428492
// different metadata in this case: we don't have a label for the
429493
// target, but rather a function relocation.
430-
sink.put1(0xE9);
494+
asm::inst::jmp_d32::new(0).emit(sink, info, state);
495+
let offset = sink.cur_offset();
431496
// The addend adjusts for the difference between the end of the instruction and the
432497
// beginning of the immediate field.
433-
emit_reloc(sink, Reloc::X86CallPCRel4, &call_info.dest, -4);
434-
sink.put4(0);
498+
sink.add_reloc_at_offset(offset - 4, Reloc::X86CallPCRel4, &call_info.dest, -4);
435499
sink.add_call_site(&[]);
436500
}
437501

@@ -595,62 +659,17 @@ pub(crate) fn emit(
595659
sink.bind_label(resume, state.ctrl_plane_mut());
596660
}
597661

598-
Inst::JmpKnown { dst } => {
599-
let br_start = sink.cur_offset();
600-
let br_disp_off = br_start + 1;
601-
let br_end = br_start + 5;
662+
Inst::JmpKnown { dst } => uncond_jmp(sink, *dst),
602663

603-
sink.use_label_at_offset(br_disp_off, *dst, LabelUse::JmpRel32);
604-
sink.add_uncond_branch(br_start, br_end, *dst);
605-
606-
sink.put1(0xE9);
607-
// Placeholder for the label value.
608-
sink.put4(0x0);
609-
}
610-
611-
Inst::WinchJmpIf { cc, taken } => {
612-
let cond_start = sink.cur_offset();
613-
let cond_disp_off = cond_start + 2;
614-
615-
sink.use_label_at_offset(cond_disp_off, *taken, LabelUse::JmpRel32);
616-
// Since this is not a terminator, don't enroll in the branch inversion mechanism.
617-
618-
sink.put1(0x0F);
619-
sink.put1(0x80 + cc.get_enc());
620-
// Placeholder for the label value.
621-
sink.put4(0x0);
622-
}
664+
Inst::WinchJmpIf { cc, taken } => one_way_jmp(sink, *cc, *taken),
623665

624666
Inst::JmpCond {
625667
cc,
626668
taken,
627669
not_taken,
628670
} => {
629-
// If taken.
630-
let cond_start = sink.cur_offset();
631-
let cond_disp_off = cond_start + 2;
632-
let cond_end = cond_start + 6;
633-
634-
sink.use_label_at_offset(cond_disp_off, *taken, LabelUse::JmpRel32);
635-
let inverted: [u8; 6] = [0x0F, 0x80 + (cc.invert().get_enc()), 0x00, 0x00, 0x00, 0x00];
636-
sink.add_cond_branch(cond_start, cond_end, *taken, &inverted[..]);
637-
638-
sink.put1(0x0F);
639-
sink.put1(0x80 + cc.get_enc());
640-
// Placeholder for the label value.
641-
sink.put4(0x0);
642-
643-
// If not taken.
644-
let uncond_start = sink.cur_offset();
645-
let uncond_disp_off = uncond_start + 1;
646-
let uncond_end = uncond_start + 5;
647-
648-
sink.use_label_at_offset(uncond_disp_off, *not_taken, LabelUse::JmpRel32);
649-
sink.add_uncond_branch(uncond_start, uncond_end, *not_taken);
650-
651-
sink.put1(0xE9);
652-
// Placeholder for the label value.
653-
sink.put4(0x0);
671+
cond_jmp(sink, *cc, *taken);
672+
uncond_jmp(sink, *not_taken);
654673
}
655674

656675
Inst::JmpCondOr {
@@ -671,56 +690,9 @@ pub(crate) fn emit(
671690
// not_taken and that one block is the fallthrough block,
672691
// all three branches can disappear.
673692

674-
// jcc1 taken
675-
let cond_1_start = sink.cur_offset();
676-
let cond_1_disp_off = cond_1_start + 2;
677-
let cond_1_end = cond_1_start + 6;
678-
679-
sink.use_label_at_offset(cond_1_disp_off, *taken, LabelUse::JmpRel32);
680-
let inverted: [u8; 6] = [
681-
0x0F,
682-
0x80 + (cc1.invert().get_enc()),
683-
0x00,
684-
0x00,
685-
0x00,
686-
0x00,
687-
];
688-
sink.add_cond_branch(cond_1_start, cond_1_end, *taken, &inverted[..]);
689-
690-
sink.put1(0x0F);
691-
sink.put1(0x80 + cc1.get_enc());
692-
sink.put4(0x0);
693-
694-
// jcc2 taken
695-
let cond_2_start = sink.cur_offset();
696-
let cond_2_disp_off = cond_2_start + 2;
697-
let cond_2_end = cond_2_start + 6;
698-
699-
sink.use_label_at_offset(cond_2_disp_off, *taken, LabelUse::JmpRel32);
700-
let inverted: [u8; 6] = [
701-
0x0F,
702-
0x80 + (cc2.invert().get_enc()),
703-
0x00,
704-
0x00,
705-
0x00,
706-
0x00,
707-
];
708-
sink.add_cond_branch(cond_2_start, cond_2_end, *taken, &inverted[..]);
709-
710-
sink.put1(0x0F);
711-
sink.put1(0x80 + cc2.get_enc());
712-
sink.put4(0x0);
713-
714-
// jmp not_taken
715-
let uncond_start = sink.cur_offset();
716-
let uncond_disp_off = uncond_start + 1;
717-
let uncond_end = uncond_start + 5;
718-
719-
sink.use_label_at_offset(uncond_disp_off, *not_taken, LabelUse::JmpRel32);
720-
sink.add_uncond_branch(uncond_start, uncond_end, *not_taken);
721-
722-
sink.put1(0xE9);
723-
sink.put4(0x0);
693+
cond_jmp(sink, *cc1, *taken);
694+
cond_jmp(sink, *cc2, *taken);
695+
uncond_jmp(sink, *not_taken);
724696
}
725697

726698
&Inst::JmpTableSeq {

0 commit comments

Comments
 (0)