winch: Use cranelift-codegen x64 backend for emission. (#5581)
This change substitutes the string based emission mechanism with
cranelift-codegen's x64 backend.
This change _does not_:
* Introduce new functionality in terms of supported instructions.
* Change the semantics of the assembler/macroassembler in terms of the logic to
emit instructions.
The most notable differences between this change and the previous version are:
* Handling of shared flags and ISA-specific flags, which for now are left with
the default value.
* Simplification of instruction emission per operand size: previously the
assembler defined different methods depending on the operand size (e.g. `mov`
for 64 bits, and `movl` for 32 bits). This change updates such approach so that
each assembler method takes an operand size as a parameter, reducing duplication
and making the code more concise and easier to integrate with the x64's `Inst` enum.
* Introduction of a disassembler for testing purposes.
As of this change, Winch generates the following code for the following test
programs:
```wat
(module
(export "main" (func $main))
(func $main (result i32)
(i32.const 10)
(i32.const 20)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: b8 0a 00 00 00 mov eax, 0xa
9: 83 c0 14 add eax, 0x14
c: 5d pop rbp
d: c3 ret
```
```wat
(module
(export "main" (func $main))
(func $main (result i32)
(local $foo i32)
(local $bar i32)
(i32.const 10)
(local.set $foo)
(i32.const 20)
(local.set $bar)
(local.get $foo)
(local.get $bar)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: 48 83 ec 08 sub rsp, 8
8: 48 c7 04 24 00 00 00 00 mov qword ptr [rsp], 0
10: b8 0a 00 00 00 mov eax, 0xa
15: 89 44 24 04 mov dword ptr [rsp + 4], eax
19: b8 14 00 00 00 mov eax, 0x14
1e: 89 04 24 mov dword ptr [rsp], eax
21: 8b 04 24 mov eax, dword ptr [rsp]
24: 8b 4c 24 04 mov ecx, dword ptr [rsp + 4]
28: 01 c1 add ecx, eax
2a: 48 89 c8 mov rax, rcx
2d: 48 83 c4 08 add rsp, 8
31: 5d pop rbp
32: c3 ret
```
```wat
(module
(export "main" (func $main))
(func $main (param i32) (param i32) (result i32)
(local.get 0)
(local.get 1)
i32.add
))
```
```asm
0: 55 push rbp
1: 48 89 e5 mov rbp, rsp
4: 48 83 ec 08 sub rsp, 8
8: 89 7c 24 04 mov dword ptr [rsp + 4], edi
c: 89 34 24 mov dword ptr [rsp], esi
f: 8b 04 24 mov eax, dword ptr [rsp]
12: 8b 4c 24 04 mov ecx, dword ptr [rsp + 4]
16: 01 c1 add ecx, eax
18: 48 89 c8 mov rax, rcx
1b: 48 83 c4 08 add rsp, 8
1f: 5d pop rbp
20: c3 ret
```
This commit is contained in:
262
winch/codegen/src/isa/x64/asm.rs
Normal file
262
winch/codegen/src/isa/x64/asm.rs
Normal file
@@ -0,0 +1,262 @@
|
||||
//! Assembler library implementation for x64.
|
||||
|
||||
use crate::{abi::Address, isa::reg::Reg, masm::OperandSize};
|
||||
use cranelift_codegen::{
|
||||
isa::x64::{
|
||||
args::{
|
||||
self, AluRmiROpcode, Amode, ExtMode, FromWritableReg, Gpr, GprMem, GprMemImm, RegMem,
|
||||
RegMemImm, SyntheticAmode, WritableGpr,
|
||||
},
|
||||
settings as x64_settings, EmitInfo, EmitState, Inst,
|
||||
},
|
||||
settings, Final, MachBuffer, MachBufferFinalized, MachInstEmit, Writable,
|
||||
};
|
||||
|
||||
/// A x64 instruction operand.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub(crate) enum Operand {
|
||||
/// Register.
|
||||
Reg(Reg),
|
||||
/// Memory address.
|
||||
Mem(Address),
|
||||
/// Immediate.
|
||||
Imm(i32),
|
||||
}
|
||||
|
||||
// Conversions between winch-codegen x64 types and cranelift-codegen x64 types.
|
||||
|
||||
impl From<Reg> for RegMemImm {
|
||||
fn from(reg: Reg) -> Self {
|
||||
RegMemImm::reg(reg.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OperandSize> for args::OperandSize {
|
||||
fn from(size: OperandSize) -> Self {
|
||||
match size {
|
||||
OperandSize::S32 => Self::Size32,
|
||||
OperandSize::S64 => Self::Size64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Low level assembler implementation for x64.
|
||||
pub(crate) struct Assembler {
|
||||
/// The machine instruction buffer.
|
||||
buffer: MachBuffer<Inst>,
|
||||
/// Constant emission information.
|
||||
emit_info: EmitInfo,
|
||||
/// Emission state.
|
||||
emit_state: EmitState,
|
||||
}
|
||||
|
||||
impl Assembler {
|
||||
/// Create a new x64 assembler.
|
||||
pub fn new(shared_flags: settings::Flags, isa_flags: x64_settings::Flags) -> Self {
|
||||
Self {
|
||||
buffer: MachBuffer::<Inst>::new(),
|
||||
emit_state: Default::default(),
|
||||
emit_info: EmitInfo::new(shared_flags, isa_flags),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the emitted code.
|
||||
pub fn finalize(self) -> MachBufferFinalized<Final> {
|
||||
let stencil = self.buffer.finish();
|
||||
stencil.apply_base_srcloc(Default::default())
|
||||
}
|
||||
|
||||
fn emit(&mut self, inst: Inst) {
|
||||
inst.emit(&[], &mut self.buffer, &self.emit_info, &mut self.emit_state);
|
||||
}
|
||||
|
||||
/// Push register.
|
||||
pub fn push_r(&mut self, reg: Reg) {
|
||||
let src = GprMemImm::new(reg.into()).expect("valid gpr");
|
||||
self.emit(Inst::Push64 { src });
|
||||
}
|
||||
|
||||
/// Pop to register.
|
||||
pub fn pop_r(&mut self, dst: Reg) {
|
||||
let writable = Writable::from_reg(dst.into());
|
||||
let dst = WritableGpr::from_writable_reg(writable).expect("valid writable gpr");
|
||||
self.emit(Inst::Pop64 { dst });
|
||||
}
|
||||
|
||||
/// Return instruction.
|
||||
pub fn ret(&mut self) {
|
||||
self.emit(Inst::Ret { rets: vec![] });
|
||||
}
|
||||
|
||||
/// Move instruction variants.
|
||||
pub fn mov(&mut self, src: Operand, dst: Operand, size: OperandSize) {
|
||||
use self::Operand::*;
|
||||
|
||||
match &(src, dst) {
|
||||
(Reg(lhs), Reg(rhs)) => self.mov_rr(*lhs, *rhs, size),
|
||||
(Reg(lhs), Mem(addr)) => match addr {
|
||||
Address::Base { base, imm } => self.mov_rm(*lhs, *base, *imm, size),
|
||||
},
|
||||
(Imm(imm), Mem(addr)) => match addr {
|
||||
Address::Base { base, imm: disp } => self.mov_im(*imm as u64, *base, *disp, size),
|
||||
},
|
||||
(Imm(imm), Reg(reg)) => self.mov_ir(*imm as u64, *reg, size),
|
||||
(Mem(addr), Reg(reg)) => match addr {
|
||||
Address::Base { base, imm } => self.mov_mr(*base, *imm, *reg, size),
|
||||
},
|
||||
|
||||
_ => panic!(
|
||||
"Invalid operand combination for mov; src={:?}, dst={:?}",
|
||||
src, dst
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register-to-register move.
|
||||
pub fn mov_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
|
||||
let src = Gpr::new(src.into()).expect("valid gpr");
|
||||
let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
|
||||
.expect("valid writable gpr");
|
||||
|
||||
self.emit(Inst::MovRR {
|
||||
src,
|
||||
dst,
|
||||
size: size.into(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Register-to-memory move.
|
||||
pub fn mov_rm(&mut self, src: Reg, base: Reg, disp: u32, size: OperandSize) {
|
||||
let src = Gpr::new(src.into()).expect("valid gpr");
|
||||
let dst = Amode::imm_reg(disp, base.into());
|
||||
|
||||
self.emit(Inst::MovRM {
|
||||
size: size.into(),
|
||||
src,
|
||||
dst: SyntheticAmode::real(dst),
|
||||
});
|
||||
}
|
||||
|
||||
/// Immediate-to-memory move.
|
||||
pub fn mov_im(&mut self, src: u64, base: Reg, disp: u32, size: OperandSize) {
|
||||
let dst = Amode::imm_reg(disp, base.into());
|
||||
self.emit(Inst::MovImmM {
|
||||
size: size.into(),
|
||||
simm64: src,
|
||||
dst: SyntheticAmode::real(dst),
|
||||
});
|
||||
}
|
||||
|
||||
/// Immediate-to-register move.
|
||||
pub fn mov_ir(&mut self, imm: u64, dst: Reg, size: OperandSize) {
|
||||
let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
|
||||
.expect("valid writable gpr");
|
||||
|
||||
self.emit(Inst::Imm {
|
||||
dst_size: size.into(),
|
||||
simm64: imm,
|
||||
dst,
|
||||
});
|
||||
}
|
||||
|
||||
/// Memory-to-register load.
|
||||
pub fn mov_mr(&mut self, base: Reg, disp: u32, dst: Reg, size: OperandSize) {
|
||||
use OperandSize::S64;
|
||||
|
||||
let dst = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
|
||||
.expect("valid writable gpr");
|
||||
let amode = Amode::imm_reg(disp, base.into());
|
||||
let src = SyntheticAmode::real(amode);
|
||||
|
||||
if size == S64 {
|
||||
self.emit(Inst::Mov64MR { src, dst });
|
||||
} else {
|
||||
let reg_mem = RegMem::mem(src);
|
||||
self.emit(Inst::MovzxRmR {
|
||||
ext_mode: ExtMode::LQ,
|
||||
src: GprMem::new(reg_mem).expect("valid memory address"),
|
||||
dst,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Subtact immediate register.
|
||||
pub fn sub_ir(&mut self, imm: u32, dst: Reg, size: OperandSize) {
|
||||
let writable = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
|
||||
.expect("valid writable gpr");
|
||||
let src = Gpr::new(dst.into()).expect("valid gpr");
|
||||
|
||||
let imm = RegMemImm::imm(imm);
|
||||
|
||||
self.emit(Inst::AluRmiR {
|
||||
size: size.into(),
|
||||
op: AluRmiROpcode::Sub,
|
||||
src1: src,
|
||||
src2: GprMemImm::new(imm).expect("valid immediate"),
|
||||
dst: writable,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add instruction variants.
|
||||
pub fn add(&mut self, src: Operand, dst: Operand, size: OperandSize) {
|
||||
match &(src, dst) {
|
||||
(Operand::Imm(imm), Operand::Reg(dst)) => self.add_ir(*imm, *dst, size),
|
||||
(Operand::Reg(src), Operand::Reg(dst)) => self.add_rr(*src, *dst, size),
|
||||
_ => panic!(
|
||||
"Invalid operand combination for add; src = {:?} dst = {:?}",
|
||||
src, dst
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add immediate and register.
|
||||
pub fn add_ir(&mut self, imm: i32, dst: Reg, size: OperandSize) {
|
||||
let writable = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
|
||||
.expect("valid writable gpr");
|
||||
let src = Gpr::new(dst.into()).expect("valid gpr");
|
||||
|
||||
let imm = RegMemImm::imm(imm as u32);
|
||||
|
||||
self.emit(Inst::AluRmiR {
|
||||
size: size.into(),
|
||||
op: AluRmiROpcode::Add,
|
||||
src1: src,
|
||||
src2: GprMemImm::new(imm).expect("valid immediate"),
|
||||
dst: writable,
|
||||
});
|
||||
}
|
||||
|
||||
/// Add register and register.
|
||||
pub fn add_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
|
||||
let dest = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
|
||||
.expect("valid writable gpr");
|
||||
let src1 = Gpr::new(dst.into()).expect("valid gpr");
|
||||
|
||||
let src2 = RegMemImm::reg(src.into());
|
||||
|
||||
self.emit(Inst::AluRmiR {
|
||||
size: size.into(),
|
||||
op: AluRmiROpcode::Add,
|
||||
src1,
|
||||
src2: GprMemImm::new(src2).expect("valid gpr"),
|
||||
dst: dest,
|
||||
});
|
||||
}
|
||||
|
||||
/// Logical exclusive or with registers.
|
||||
pub fn xor_rr(&mut self, src: Reg, dst: Reg, size: OperandSize) {
|
||||
let dest = WritableGpr::from_writable_reg(Writable::from_reg(dst.into()))
|
||||
.expect("valid writable gpr");
|
||||
let src1 = Gpr::new(dst.into()).expect("valid gpr");
|
||||
|
||||
let src2 = RegMemImm::reg(src.into());
|
||||
|
||||
self.emit(Inst::AluRmiR {
|
||||
size: size.into(),
|
||||
op: AluRmiROpcode::Xor,
|
||||
src1,
|
||||
src2: GprMemImm::new(src2).expect("valid gpr"),
|
||||
dst: dest,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user