diff options
author | Maxime Chevalier-Boisvert <maxime.chevalierboisvert@shopify.com> | 2022-05-17 17:31:36 -0400 |
---|---|---|
committer | Takashi Kokubun <takashikkbn@gmail.com> | 2022-08-29 08:46:53 -0700 |
commit | e9cc17dcc9a365d59330b8c37baeafed5d75a519 (patch) | |
tree | 91ebfe56849c527d98df38e7361e7e6d41ba9124 | |
parent | 18dc379aca69fd9dc72debae3fd504399799e86f (diff) |
Start work on platform-specific codegen
-rw-r--r-- | yjit/src/asm/x86_64/mod.rs | 51 | ||||
-rw-r--r-- | yjit/src/backend/ir.rs (renamed from yjit/src/ir.rs) | 138 | ||||
-rw-r--r-- | yjit/src/backend/mod.rs | 3 | ||||
-rw-r--r-- | yjit/src/backend/x86_64/mod.rs | 55 | ||||
-rw-r--r-- | yjit/src/codegen.rs | 1 | ||||
-rw-r--r-- | yjit/src/lib.rs | 2 |
6 files changed, 161 insertions, 89 deletions
diff --git a/yjit/src/asm/x86_64/mod.rs b/yjit/src/asm/x86_64/mod.rs index b4ef2e4bf9..0a930ecf60 100644 --- a/yjit/src/asm/x86_64/mod.rs +++ b/yjit/src/asm/x86_64/mod.rs @@ -34,7 +34,7 @@ pub enum RegType IP, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct X86Reg { // Size in bits @@ -157,22 +157,39 @@ const RBP_REG_NO: u8 = 5; const R12_REG_NO: u8 = 12; const R13_REG_NO: u8 = 13; -pub const RAX: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: RAX_REG_NO }); -pub const RCX: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 1 }); -pub const RDX: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 2 }); -pub const RBX: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 3 }); -pub const RSP: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: RSP_REG_NO }); -pub const RBP: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: RBP_REG_NO }); -pub const RSI: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 6 }); -pub const RDI: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 7 }); -pub const R8: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 8 }); -pub const R9: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 9 }); -pub const R10: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 10 }); -pub const R11: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 11 }); -pub const R12: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: R12_REG_NO }); -pub const R13: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: R13_REG_NO }); -pub const R14: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 14 }); -pub const R15: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 15 }); +pub const RAX_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: RAX_REG_NO }; +pub const RCX_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 1 }; +pub const RDX_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 2 }; +pub const RBX_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 3 }; +pub const RSP_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: RSP_REG_NO }; +pub const RBP_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: RBP_REG_NO }; +pub const RSI_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 6 }; +pub const RDI_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 7 }; +pub const R8_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 8 }; +pub const R9_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 9 }; +pub const R10_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 10 }; +pub const R11_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 11 }; +pub const R12_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: R12_REG_NO }; +pub const R13_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: R13_REG_NO }; +pub const R14_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 14 }; +pub const R15_REG: X86Reg = X86Reg { num_bits: 64, reg_type: RegType::GP, reg_no: 15 }; + +pub const RAX: X86Opnd = X86Opnd::Reg(RAX_REG); +pub const RCX: X86Opnd = X86Opnd::Reg(RCX_REG); +pub const RDX: X86Opnd = X86Opnd::Reg(RDX_REG); +pub const RBX: X86Opnd = X86Opnd::Reg(RBX_REG); +pub const RSP: X86Opnd = X86Opnd::Reg(RSP_REG); +pub const RBP: X86Opnd = X86Opnd::Reg(RBP_REG); +pub const RSI: X86Opnd = X86Opnd::Reg(RSI_REG); +pub const RDI: X86Opnd = X86Opnd::Reg(RDI_REG); +pub const R8: X86Opnd = X86Opnd::Reg(R8_REG); +pub const R9: X86Opnd = X86Opnd::Reg(R9_REG); +pub const R10: X86Opnd = X86Opnd::Reg(R10_REG); +pub const R11: X86Opnd = X86Opnd::Reg(R11_REG); +pub const R12: X86Opnd = X86Opnd::Reg(R12_REG); +pub const R13: X86Opnd = X86Opnd::Reg(R13_REG); +pub const R14: X86Opnd = X86Opnd::Reg(R14_REG); +pub const R15: X86Opnd = X86Opnd::Reg(R15_REG); // 32-bit GP registers pub const EAX: X86Opnd = X86Opnd::Reg(X86Reg { num_bits: 32, reg_type: RegType::GP, reg_no: 0 }); diff --git a/yjit/src/ir.rs b/yjit/src/backend/ir.rs index 9a4fc559de..9cff4aeac9 100644 --- a/yjit/src/ir.rs +++ b/yjit/src/backend/ir.rs @@ -5,17 +5,22 @@ use std::convert::From; use crate::cruby::{VALUE}; use crate::virtualmem::{CodePtr}; +use crate::asm::{CodeBlock}; use crate::asm::x86_64::{X86Opnd, X86Imm, X86UImm, X86Reg, X86Mem, RegType}; use crate::core::{Context, Type, TempMapping}; +#[cfg(target_arch = "x86_64")] +use crate::backend::x86_64::*; + +//#[cfg(target_arch = "aarch64")] +//use crate::backend:aarch64::* + /// Instruction opcodes #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Op { - // Add a comment into the IR at the point that this instruction is added. It - // won't have any impact on that actual compiled code, but it will impact - // the output of ir_print_insns. Accepts as its only operand an EIR_IMM - // operand (typically generated by ir_str_ptr). + // Add a comment into the IR at the point that this instruction is added. + // It won't have any impact on that actual compiled code. Comment, // Add a label into the IR at the point that this instruction is added. @@ -51,6 +56,9 @@ pub enum Op // A low-level instruction that loads a value into a register. Load, + // Low-level instruction to store a value to memory. + Store, + // A low-level mov instruction. It accepts two operands. Mov, @@ -170,32 +178,18 @@ pub enum Op */ } -// Register value used by IR operands -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct Reg -{ - // Register number/index - reg_no: u8, - - // Size in bits - num_bits: u8, - - // Special register flag EC/CFP/SP/SELF - special: bool, -} - // Memory location #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Mem { // Base register - base: Reg, + pub(super) base: Reg, // Offset relative to the base pointer - disp: i32, + pub(super) disp: i32, // Size in bits - num_bits: u8, + pub(super) num_bits: u8, } /// Operand to an IR instruction @@ -222,7 +216,7 @@ impl Opnd pub fn mem(num_bits: u8, base: Opnd, disp: i32) -> Self { match base { Opnd::Reg(base_reg) => { - assert!(base_reg.num_bits == 64 && !base_reg.special); + assert!(base_reg.num_bits == 64); Opnd::Mem(Mem { num_bits: num_bits, base: base_reg, @@ -234,12 +228,6 @@ impl Opnd } } -// Special register constants -pub const EC : Opnd = Opnd::Reg(Reg { reg_no: 0, num_bits: 64, special: true }); -pub const CFP : Opnd = Opnd::Reg(Reg { reg_no: 1, num_bits: 64, special: true }); -pub const SP : Opnd = Opnd::Reg(Reg { reg_no: 2, num_bits: 64, special: true }); -pub const SELF : Opnd = Opnd::Reg(Reg { reg_no: 3, num_bits: 64, special: true }); - /// Method to convert from an X86Opnd to an IR Opnd impl From<X86Opnd> for Opnd { fn from(opnd: X86Opnd) -> Self { @@ -249,17 +237,13 @@ impl From<X86Opnd> for Opnd { X86Opnd::Imm(X86Imm{ value, .. }) => Opnd::Imm(value), // General-purpose register - X86Opnd::Reg(X86Reg{ num_bits, reg_no, reg_type: RegType::GP }) => { - Opnd::Reg(Reg { - reg_no, - num_bits, - special: false, - }) + X86Opnd::Reg(reg) => { + Opnd::Reg(reg) } // Memory operand with displacement X86Opnd::Mem(X86Mem{ num_bits, base_reg_no, disp, idx_reg_no: None, scale_exp: 0 }) => { - let base_reg = Reg { num_bits: 64, reg_no: base_reg_no, special: false }; + let base_reg = Reg { num_bits: 64, reg_no: base_reg_no, reg_type: RegType::GP }; Opnd::Mem(Mem { base: base_reg, @@ -273,15 +257,10 @@ impl From<X86Opnd> for Opnd { } } - - - - - /// Branch target (something that we can jump to) /// for branch instructions #[derive(Clone, PartialEq, Eq, Debug)] -enum Target +pub enum Target { CodePtr(CodePtr), // Pointer to a piece of code (e.g. side-exit) LabelName(String), // A label without an index in the output @@ -293,30 +272,30 @@ enum Target pub struct Insn { // Opcode for the instruction - op: Op, + pub(super) op: Op, // Optional string for comments and labels - text: Option<String>, + pub(super) text: Option<String>, // List of input operands/values - opnds: Vec<Opnd>, + pub(super) opnds: Vec<Opnd>, // Output operand for this instruction - out: Opnd, + pub(super) out: Opnd, // List of branch targets (branch instructions only) - target: Option<Target>, + pub(super) target: Option<Target>, // Position in the generated machine code // Useful for comments and for patching jumps - pos: Option<CodePtr>, + pub(super) pos: Option<CodePtr>, } /// Object into which we assemble instructions to be /// optimized and lowered -struct Assembler +pub struct Assembler { - insns: Vec<Insn>, + pub(super) insns: Vec<Insn>, /// Parallel vec with insns /// Index of the last insn using the output of this insn @@ -450,7 +429,7 @@ impl Assembler Op::Add | Op::Sub | Op::Mov => { match opnds.as_slice() { [Opnd::Mem(_), Opnd::Mem(_)] => { - let output = asm.push_insn(Op::Load, vec![opnds[0]], None); + let output = asm.load(opnds[0]); asm.push_insn(op, vec![output, opnds[1]], None); }, _ => { @@ -534,19 +513,18 @@ impl Assembler } // Optimize and compile the stored instructions - fn compile(self, regs: Vec<Reg>) -> Assembler + fn compile(self, cb: &mut CodeBlock) { - self.split_insns().alloc_regs(regs) + // NOTE: for arm we're going to want to split loads but also stores + // This can be done in a platform-agnostic way, but the set of passes + // we run will be slightly different. - // TODO: splitting pass, split_insns() + let scratch_regs = Self::get_scrach_regs(); - // Peephole optimizations - // Register allocation - // Generic lowering pass - // Platform-specific lowering - - // Question: should this method return machine code? - // How do we go from lowered/optimized insn to an array of bytes? + self + .split_insns() + .alloc_regs(scratch_regs) + .target_emit(cb) } } @@ -564,6 +542,18 @@ impl Assembler } } +macro_rules! def_push_1_opnd { + ($op_name:ident, $opcode:expr) => { + impl Assembler + { + fn $op_name(&mut self, opnd0: Opnd) -> Opnd + { + self.push_insn($opcode, vec![opnd0], None) + } + } + }; +} + macro_rules! def_push_2_opnd { ($op_name:ident, $opcode:expr) => { impl Assembler @@ -591,6 +581,7 @@ macro_rules! def_push_2_opnd_no_out { def_push_2_opnd!(add, Op::Add); def_push_2_opnd!(sub, Op::Sub); def_push_2_opnd!(and, Op::And); +def_push_1_opnd!(load, Op::Load); def_push_2_opnd_no_out!(mov, Op::Mov); def_push_2_opnd_no_out!(cmp, Op::Cmp); def_push_2_opnd_no_out!(test, Op::Test); @@ -660,12 +651,11 @@ mod tests { fn test_split_insns() { let mut asm = Assembler::new(); - let reg1 = Reg { reg_no: 0, num_bits: 64, special: false }; - let reg2 = Reg { reg_no: 1, num_bits: 64, special: false }; + let regs = Assembler::get_scrach_regs(); asm.add( - Opnd::mem(64, Opnd::Reg(reg1), 0), - Opnd::mem(64, Opnd::Reg(reg2), 0) + Opnd::mem(64, Opnd::Reg(regs[0]), 0), + Opnd::mem(64, Opnd::Reg(regs[1]), 0) ); let result = asm.split_insns(); @@ -698,14 +688,22 @@ mod tests { asm.add(out3, Opnd::UImm(6)); // Here we're going to allocate the registers. - let reg1 = Reg { reg_no: 0, num_bits: 64, special: false }; - let reg2 = Reg { reg_no: 1, num_bits: 64, special: false }; - let result = asm.alloc_regs(vec![reg1, reg2]); + let result = asm.alloc_regs(Assembler::get_scrach_regs()); // Now we're going to verify that the out field has been appropriately // updated for each of the instructions that needs it. - assert_eq!(result.insns[0].out, Opnd::Reg(reg1)); - assert_eq!(result.insns[2].out, Opnd::Reg(reg2)); - assert_eq!(result.insns[5].out, Opnd::Reg(reg1)); + let regs = Assembler::get_scrach_regs(); + assert_eq!(result.insns[0].out, Opnd::Reg(regs[0])); + assert_eq!(result.insns[2].out, Opnd::Reg(regs[1])); + assert_eq!(result.insns[5].out, Opnd::Reg(regs[0])); + } + + #[test] + fn test_compile() + { + // TODO: test full compile pipeline + + + } } diff --git a/yjit/src/backend/mod.rs b/yjit/src/backend/mod.rs new file mode 100644 index 0000000000..a83fe4f69e --- /dev/null +++ b/yjit/src/backend/mod.rs @@ -0,0 +1,3 @@ +pub mod x86_64; + +pub mod ir;
\ No newline at end of file diff --git a/yjit/src/backend/x86_64/mod.rs b/yjit/src/backend/x86_64/mod.rs new file mode 100644 index 0000000000..257373e86f --- /dev/null +++ b/yjit/src/backend/x86_64/mod.rs @@ -0,0 +1,55 @@ +#![allow(dead_code)] +#![allow(unused_variables)] +#![allow(unused_imports)] + +use crate::asm::{CodeBlock}; +use crate::asm::x86_64::*; +use crate::backend::ir::*; + +// Use the x86 register type for this platform +pub type Reg = X86Reg; + +// Callee-saved registers +pub const CFP: Opnd = Opnd::Reg(R13_REG); +pub const EC: Opnd = Opnd::Reg(R12_REG); +pub const SP: Opnd = Opnd::Reg(RBX_REG); + +impl Assembler +{ + // Get the list of registers from which we can allocate on this platform + pub fn get_scrach_regs() -> Vec<Reg> + { + vec![ + RAX_REG, + RCX_REG, + ] + } + + // Emit platform-specific machine code + pub fn target_emit(&self, cb: &mut CodeBlock) + { + + + + for insn in &self.insns { + + + // For each instruction, either handle it here or allow the map_insn + // callback to handle it. + match insn.op { + Op::Comment => { + }, + Op::Label => { + }, + _ => { + } + }; + + + } + + + + + } +} diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 119477f505..67d3ecd573 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -33,7 +33,6 @@ pub const REG0: X86Opnd = RAX; pub const REG0_32: X86Opnd = EAX; pub const REG0_8: X86Opnd = AL; pub const REG1: X86Opnd = RCX; -// pub const REG1_32: X86Opnd = ECX; // A block that can be invalidated needs space to write a jump. // We'll reserve a minimum size for any block that could diff --git a/yjit/src/lib.rs b/yjit/src/lib.rs index 019189e8e8..752b7872c1 100644 --- a/yjit/src/lib.rs +++ b/yjit/src/lib.rs @@ -4,7 +4,7 @@ #![allow(clippy::identity_op)] // Sometimes we do it for style mod asm; -mod ir; +mod backend; mod codegen; mod core; mod cruby; |