ruby-changes:73231
From: Maxime <ko1@a...>
Date: Tue, 30 Aug 2022 01:03:28 +0900 (JST)
Subject: [ruby-changes:73231] 45da697450 (master): Push first pass at SSA IR sketch
https://git.ruby-lang.org/ruby.git/commit/?id=45da697450 From 45da6974500070872a2b20fafe2b50bc1dce1052 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert <maxime.chevalierboisvert@s...> Date: Mon, 1 Aug 2022 16:12:51 -0400 Subject: Push first pass at SSA IR sketch --- yjit/src/backend/ir.rs | 1 - yjit/src/backend/ir_ssa.rs | 1165 ++++++++++++++++++++++++++++++++++++++++++++ yjit/src/backend/mod.rs | 1 + yjit/src/lib.rs | 3 - 4 files changed, 1166 insertions(+), 4 deletions(-) create mode 100644 yjit/src/backend/ir_ssa.rs diff --git a/yjit/src/backend/ir.rs b/yjit/src/backend/ir.rs index ce82161693..45d4378eb4 100644 --- a/yjit/src/backend/ir.rs +++ b/yjit/src/backend/ir.rs @@ -8,7 +8,6 @@ use crate::cruby::{VALUE}; https://github.com/ruby/ruby/blob/trunk/yjit/src/backend/ir.rs#L8 use crate::virtualmem::{CodePtr}; use crate::asm::{CodeBlock, uimm_num_bits, imm_num_bits}; use crate::core::{Context, Type, TempMapping}; -use crate::codegen::{JITState}; #[cfg(target_arch = "x86_64")] use crate::backend::x86_64::*; diff --git a/yjit/src/backend/ir_ssa.rs b/yjit/src/backend/ir_ssa.rs new file mode 100644 index 0000000000..49974b90b7 --- /dev/null +++ b/yjit/src/backend/ir_ssa.rs @@ -0,0 +1,1165 @@ https://github.com/ruby/ruby/blob/trunk/yjit/src/backend/ir_ssa.rs#L1 +#![allow(dead_code)] +#![allow(unused_variables)] +#![allow(unused_imports)] + +use std::fmt; +use std::convert::From; +use crate::cruby::{VALUE}; +use crate::virtualmem::{CodePtr}; +use crate::asm::{CodeBlock, uimm_num_bits, imm_num_bits}; +use crate::core::{Context, Type, TempMapping}; + +/* +#[cfg(target_arch = "x86_64")] +use crate::backend::x86_64::*; + +#[cfg(target_arch = "aarch64")] +use crate::backend::arm64::*; + + +pub const EC: Opnd = _EC; +pub const CFP: Opnd = _CFP; +pub const SP: Opnd = _SP; + +pub const C_ARG_OPNDS: [Opnd; 6] = _C_ARG_OPNDS; +pub const C_RET_OPND: Opnd = _C_RET_OPND; +*/ + + + +// Dummy reg struct +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct Reg +{ + reg_no: u8, + num_bits: u8, +} + + + + + + + +/// 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. + Comment, + + // Add a label into the IR at the point that this instruction is added. + Label, + + // Mark a position in the generated code + PosMarker, + + // Bake a string directly into the instruction stream. + BakeString, + + // Add two operands together, and return the result as a new operand. This + // operand can then be used as the operand on another instruction. It + // accepts two operands, which can be of any type + // + // Under the hood when allocating registers, the IR will determine the most + // efficient way to get these values into memory. For example, if both + // operands are immediates, then it will load the first one into a register + // first with a mov instruction and then add them together. If one of them + // is a register, however, it will just perform a single add instruction. + Add, + + // This is the same as the OP_ADD instruction, except for subtraction. + Sub, + + // This is the same as the OP_ADD instruction, except that it performs the + // binary AND operation. + And, + + // Perform the NOT operation on an individual operand, and return the result + // as a new operand. This operand can then be used as the operand on another + // instruction. + Not, + + // + // Low-level instructions + // + + // A low-level instruction that loads a value into a register. + Load, + + // A low-level instruction that loads a value into a register and + // sign-extends it to a 64-bit value. + LoadSExt, + + // Low-level instruction to store a value to memory. + Store, + + // Load effective address + Lea, + + // Load effective address relative to the current instruction pointer. It + // accepts a single signed immediate operand. + LeaLabel, + + // A low-level mov instruction. It accepts two operands. + Mov, + + // Bitwise AND test instruction + Test, + + // Compare two operands + Cmp, + + // Unconditional jump to a branch target + Jmp, + + // Unconditional jump which takes a reg/mem address operand + JmpOpnd, + + // Low-level conditional jump instructions + Jbe, + Je, + Jne, + Jz, + Jnz, + Jo, + + // Conditional select instructions + CSelZ, + CSelNZ, + CSelE, + CSelNE, + CSelL, + CSelLE, + CSelG, + CSelGE, + + // Push and pop registers to/from the C stack + CPush, + CPop, + CPopInto, + + // Push and pop all of the caller-save registers and the flags to/from the C + // stack + CPushAll, + CPopAll, + + // C function call with N arguments (variadic) + CCall, + + // C function return + CRet, + + // Atomically increment a counter + // Input: memory operand, increment value + // Produces no output + IncrCounter, + + // Trigger a debugger breakpoint + Breakpoint, + + /// Set up the frame stack as necessary per the architecture. + FrameSetup, + + /// Tear down the frame stack as necessary per the architecture. + FrameTeardown, + + /// Take a specific register. Signal the register allocator to not use it. + LiveReg, +} + +/// Instruction idx in an assembler +/// This is used like a pointer +type InsnIdx = u32; + +/// Instruction operand index +type OpndIdx = u32; + +// Memory operand base +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum MemBase +{ + Reg(u8), + InsnOut(InsnIdx), +} + +// Memory location +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Mem +{ + // Base register number or instruction index + pub(super) base: MemBase, + + // Offset relative to the base pointer + pub(super) disp: i32, + + // Size in bits + pub(super) num_bits: u8, +} + +impl fmt::Debug for Mem { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "Mem{}[{:?}", self.num_bits, self.base)?; + if self.disp != 0 { + let sign = if self.disp > 0 { '+' } else { '-' }; + write!(fmt, " {sign} {}", self.disp)?; + } + + write!(fmt, "]") + } +} + +/// Operand to an IR instruction +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Opnd +{ + None, // For insns with no output + + // Immediate Ruby value, may be GC'd, movable + Value(VALUE), + + // Output of a preceding instruction in this block + InsnOut{ idx: InsnIdx, num_bits: u8 }, + + // Low-level operands, for lowering + Imm(i64), // Raw signed immediate + UImm(u64), // Raw unsigned immediate + Mem(Mem), // Memory location + Reg(Reg), // Machine register +} + +impl fmt::Debug for Opnd { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + use Opnd::*; + match self { + Self::None => write!(fmt, "None"), + Value(val) => write!(fmt, "Value({val:?})"), + InsnOut { idx, num_bits } => write!(fmt, "Out{num_bits}({idx})"), + Imm(signed) => write!(fmt, "{signed:x}_i64"), + UImm(unsigned) => write!(fmt, "{unsigned:x}_u64"), + // Say Mem and Reg only once + Mem(mem) => write!(fmt, "{mem:?}"), + Reg(reg) => write!(fmt, "{reg:?}"), + } + } +} + +impl Opnd +{ + /// Convenience constructor for memory operands + pub fn mem(num_bits: u8, base: Opnd, disp: i32) -> Self { + match base { + Opnd::Reg(base_reg) => { + assert!(base_reg.num_bits == 64); + Opnd::Mem(Mem { + base: MemBase::Reg(base_reg.reg_no), + disp: disp, + num_bits: num_bits, + }) + }, + + Opnd::InsnOut{idx, num_bits } => { + assert!(num_bits == 64); + Opnd::Mem(Mem { + base: MemBase::InsnOut(idx), + disp: disp, + num_bits: num_bits, + }) + }, + + _ => unreachable!("memory operand with non-register base") + } + } + + /// Constructor for constant pointer operand + pub fn const_ptr(ptr: *const u8) -> Self { + Opnd::UImm(ptr as u64) + } + + pub fn is_some(&self) -> bool { + match *self { + Opnd::None => false, + _ => true, + } + } + + /// Unwrap a register operand + pub fn unwrap_reg(&self) -> Reg { + match self { + Opnd::Reg(reg) => *reg, + _ => unreachable!("trying to unwrap {:?} into reg", self) + } + } + + /// Get the size in bits for register/memory operands + pub fn rm_num_bits(&self) -> u8 { + match *self { + Opnd::Reg(reg) => reg.num_bits, + Opnd::Mem(mem) => mem.num_bits, + Opnd::InsnOut{ num_bits, .. } => num_bits, + _ => unreachable!() + } + } +} + +impl From<usize> for Opnd { + fn from(value: usize) -> Self { + Opnd::UImm(value.try_into().unwrap()) + } +} + +impl From<u64> for Opnd { + fn from(value: u64) -> Self { + Opnd::UImm(value.try_into().unwrap()) + } +} + +impl From<i64> for Opnd { + fn from(value: i64) -> Self { + Opnd::Imm(value) + } +} + +impl From<i32> for Opnd { + fn from(value: i32) -> Self { + Opnd::Imm(value.try_into().unwrap()) + } +} + +impl From<u32> for Opnd { + fn from(value: u32) -> Self { + Opnd::UImm(value as u64) + } +} + +impl From<VALUE> for Opnd { + fn from(value: VALUE) -> Self { + let VALUE(uimm) = value; + Opnd::UImm(uimm as u64) + } +} + +/// Branch target (something that we can jump to) +/// for branch instructions +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Target +{ (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/