YJIT: Introduce Opnd::Stack (#7352)

This commit is contained in:
Takashi Kokubun 2023-02-22 13:22:41 -08:00 committed by GitHub
parent 4f48debdcf
commit e9e4e1cb46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
Notes: git 2023-02-22 21:23:02 +00:00
Merged-By: maximecb <maximecb@ruby-lang.org>
5 changed files with 80 additions and 50 deletions

View File

@ -54,6 +54,7 @@ impl From<Opnd> for A64Opnd {
}, },
Opnd::InsnOut { .. } => panic!("attempted to lower an Opnd::InsnOut"), Opnd::InsnOut { .. } => panic!("attempted to lower an Opnd::InsnOut"),
Opnd::Value(_) => panic!("attempted to lower an Opnd::Value"), Opnd::Value(_) => panic!("attempted to lower an Opnd::Value"),
Opnd::Stack { .. } => panic!("attempted to lower an Opnd::Stack"),
Opnd::None => panic!( Opnd::None => panic!(
"Attempted to lower an Opnd::None. This often happens when an out operand was not allocated for an instruction because the output of the instruction was not used. Please ensure you are using the output." "Attempted to lower an Opnd::None. This often happens when an out operand was not allocated for an instruction because the output of the instruction was not used. Please ensure you are using the output."
), ),
@ -252,7 +253,7 @@ impl Assembler
/// do follow that encoding, and if they don't then we load them first. /// do follow that encoding, and if they don't then we load them first.
fn split_bitmask_immediate(asm: &mut Assembler, opnd: Opnd, dest_num_bits: u8) -> Opnd { fn split_bitmask_immediate(asm: &mut Assembler, opnd: Opnd, dest_num_bits: u8) -> Opnd {
match opnd { match opnd {
Opnd::Reg(_) | Opnd::InsnOut { .. } => opnd, Opnd::Reg(_) | Opnd::InsnOut { .. } | Opnd::Stack { .. } => opnd,
Opnd::Mem(_) => split_load_operand(asm, opnd), Opnd::Mem(_) => split_load_operand(asm, opnd),
Opnd::Imm(imm) => { Opnd::Imm(imm) => {
if imm == 0 { if imm == 0 {
@ -295,7 +296,7 @@ impl Assembler
asm.load(opnd) asm.load(opnd)
} }
}, },
Opnd::None | Opnd::Value(_) => unreachable!() Opnd::None | Opnd::Value(_) | Opnd::Stack { .. } => unreachable!()
} }
} }
@ -863,6 +864,9 @@ impl Assembler
let ptr_offset: u32 = (cb.get_write_pos() as u32) - (SIZEOF_VALUE as u32); let ptr_offset: u32 = (cb.get_write_pos() as u32) - (SIZEOF_VALUE as u32);
insn_gc_offsets.push(ptr_offset); insn_gc_offsets.push(ptr_offset);
}, },
Opnd::Stack { .. } => {
unreachable!("Stack operand was not lowered before arm64_emit");
}
Opnd::None => { Opnd::None => {
unreachable!("Attempted to load from None operand"); unreachable!("Attempted to load from None operand");
} }
@ -1072,7 +1076,7 @@ impl Assembler
/// Optimize and compile the stored instructions /// Optimize and compile the stored instructions
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Vec<u32> pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Vec<u32>
{ {
let mut asm = self.arm64_split().alloc_regs(regs); let mut asm = self.lower_stack().arm64_split().alloc_regs(regs);
// Create label instances in the code block // Create label instances in the code block
for (idx, name) in asm.label_names.iter().enumerate() { for (idx, name) in asm.label_names.iter().enumerate() {

View File

@ -7,7 +7,7 @@ use std::fmt;
use std::convert::From; use std::convert::From;
use std::io::Write; use std::io::Write;
use std::mem::take; use std::mem::take;
use crate::cruby::{VALUE}; use crate::cruby::{VALUE, SIZEOF_VALUE_I32};
use crate::virtualmem::{CodePtr}; use crate::virtualmem::{CodePtr};
use crate::asm::{CodeBlock, uimm_num_bits, imm_num_bits}; use crate::asm::{CodeBlock, uimm_num_bits, imm_num_bits};
use crate::core::{Context, Type, TempMapping}; use crate::core::{Context, Type, TempMapping};
@ -72,6 +72,9 @@ pub enum Opnd
// Output of a preceding instruction in this block // Output of a preceding instruction in this block
InsnOut{ idx: usize, num_bits: u8 }, InsnOut{ idx: usize, num_bits: u8 },
// Pointer to a slot on the VM stack
Stack { idx: i32, sp_offset: i16, num_bits: u8 },
// Low-level operands, for lowering // Low-level operands, for lowering
Imm(i64), // Raw signed immediate Imm(i64), // Raw signed immediate
UImm(u64), // Raw unsigned immediate UImm(u64), // Raw unsigned immediate
@ -85,6 +88,7 @@ impl fmt::Debug for Opnd {
match self { match self {
Self::None => write!(fmt, "None"), Self::None => write!(fmt, "None"),
Value(val) => write!(fmt, "Value({val:?})"), Value(val) => write!(fmt, "Value({val:?})"),
Stack { idx, sp_offset, .. } => write!(fmt, "SP[{}]", *sp_offset as i32 - idx - 1),
InsnOut { idx, num_bits } => write!(fmt, "Out{num_bits}({idx})"), InsnOut { idx, num_bits } => write!(fmt, "Out{num_bits}({idx})"),
Imm(signed) => write!(fmt, "{signed:x}_i64"), Imm(signed) => write!(fmt, "{signed:x}_i64"),
UImm(unsigned) => write!(fmt, "{unsigned:x}_u64"), UImm(unsigned) => write!(fmt, "{unsigned:x}_u64"),
@ -158,6 +162,7 @@ impl Opnd
Opnd::Reg(reg) => Some(Opnd::Reg(reg.with_num_bits(num_bits))), Opnd::Reg(reg) => Some(Opnd::Reg(reg.with_num_bits(num_bits))),
Opnd::Mem(Mem { base, disp, .. }) => Some(Opnd::Mem(Mem { base, disp, num_bits })), Opnd::Mem(Mem { base, disp, .. }) => Some(Opnd::Mem(Mem { base, disp, num_bits })),
Opnd::InsnOut { idx, .. } => Some(Opnd::InsnOut { idx, num_bits }), Opnd::InsnOut { idx, .. } => Some(Opnd::InsnOut { idx, num_bits }),
Opnd::Stack { idx, sp_offset, .. } => Some(Opnd::Stack { idx, sp_offset, num_bits }),
_ => None, _ => None,
} }
} }
@ -914,6 +919,25 @@ impl Assembler
Target::Label(label_idx) Target::Label(label_idx)
} }
/// Convert Stack operands to memory operands
pub fn lower_stack(mut self) -> Assembler
{
let mut asm = Assembler::new_with_label_names(take(&mut self.label_names));
let mut iterator = self.into_draining_iter();
while let Some((index, mut insn)) = iterator.next_unmapped() {
let mut opnd_iter = insn.opnd_iter_mut();
while let Some(opnd) = opnd_iter.next() {
if let Opnd::Stack { idx, sp_offset, num_bits } = *opnd {
*opnd = Opnd::mem(num_bits, SP, (sp_offset as i32 - idx - 1) * SIZEOF_VALUE_I32);
}
}
asm.push_insn(insn);
}
asm
}
/// Sets the out field on the various instructions that require allocated /// Sets the out field on the various instructions that require allocated
/// registers because their output is used as the operand on a subsequent /// registers because their output is used as the operand on a subsequent
/// instruction. This is our implementation of the linear scan algorithm. /// instruction. This is our implementation of the linear scan algorithm.

View File

@ -701,7 +701,7 @@ impl Assembler
/// Optimize and compile the stored instructions /// Optimize and compile the stored instructions
pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Vec<u32> pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec<Reg>) -> Vec<u32>
{ {
let mut asm = self.x86_split().alloc_regs(regs); let mut asm = self.lower_stack().x86_split().alloc_regs(regs);
// Create label instances in the code block // Create label instances in the code block
for (idx, name) in asm.label_names.iter().enumerate() { for (idx, name) in asm.label_names.iter().enumerate() {

View File

@ -880,9 +880,8 @@ fn gen_dup(
asm: &mut Assembler, asm: &mut Assembler,
_ocb: &mut OutlinedCb, _ocb: &mut OutlinedCb,
) -> CodegenStatus { ) -> CodegenStatus {
let dup_val = ctx.stack_opnd(0);
let dup_val = ctx.stack_pop(0); let (mapping, tmp_type) = ctx.get_opnd_mapping(dup_val.into());
let (mapping, tmp_type) = ctx.get_opnd_mapping(StackOpnd(0));
let loc0 = ctx.stack_push_mapping((mapping, tmp_type)); let loc0 = ctx.stack_push_mapping((mapping, tmp_type));
asm.mov(loc0, dup_val); asm.mov(loc0, dup_val);
@ -907,8 +906,8 @@ fn gen_dupn(
let opnd1: Opnd = ctx.stack_opnd(1); let opnd1: Opnd = ctx.stack_opnd(1);
let opnd0: Opnd = ctx.stack_opnd(0); let opnd0: Opnd = ctx.stack_opnd(0);
let mapping1 = ctx.get_opnd_mapping(StackOpnd(1)); let mapping1 = ctx.get_opnd_mapping(opnd1.into());
let mapping0 = ctx.get_opnd_mapping(StackOpnd(0)); let mapping0 = ctx.get_opnd_mapping(opnd0.into());
let dst1: Opnd = ctx.stack_push_mapping(mapping1); let dst1: Opnd = ctx.stack_push_mapping(mapping1);
asm.mov(dst1, opnd1); asm.mov(dst1, opnd1);
@ -940,16 +939,16 @@ fn stack_swap(
let stack0_mem = ctx.stack_opnd(offset0 as i32); let stack0_mem = ctx.stack_opnd(offset0 as i32);
let stack1_mem = ctx.stack_opnd(offset1 as i32); let stack1_mem = ctx.stack_opnd(offset1 as i32);
let mapping0 = ctx.get_opnd_mapping(StackOpnd(offset0)); let mapping0 = ctx.get_opnd_mapping(stack0_mem.into());
let mapping1 = ctx.get_opnd_mapping(StackOpnd(offset1)); let mapping1 = ctx.get_opnd_mapping(stack1_mem.into());
let stack0_reg = asm.load(stack0_mem); let stack0_reg = asm.load(stack0_mem);
let stack1_reg = asm.load(stack1_mem); let stack1_reg = asm.load(stack1_mem);
asm.mov(stack0_mem, stack1_reg); asm.mov(stack0_mem, stack1_reg);
asm.mov(stack1_mem, stack0_reg); asm.mov(stack1_mem, stack0_reg);
ctx.set_opnd_mapping(StackOpnd(offset0), mapping1); ctx.set_opnd_mapping(stack0_mem.into(), mapping1);
ctx.set_opnd_mapping(StackOpnd(offset1), mapping0); ctx.set_opnd_mapping(stack1_mem.into(), mapping0);
} }
fn gen_putnil( fn gen_putnil(
@ -1043,15 +1042,15 @@ fn gen_setn(
) -> CodegenStatus { ) -> CodegenStatus {
let n = jit.get_arg(0).as_usize(); let n = jit.get_arg(0).as_usize();
let top_val = ctx.stack_pop(0); let top_val = ctx.stack_opnd(0);
let dst_opnd = ctx.stack_opnd(n.try_into().unwrap()); let dst_opnd = ctx.stack_opnd(n.try_into().unwrap());
asm.mov( asm.mov(
dst_opnd, dst_opnd,
top_val top_val
); );
let mapping = ctx.get_opnd_mapping(StackOpnd(0)); let mapping = ctx.get_opnd_mapping(top_val.into());
ctx.set_opnd_mapping(StackOpnd(n.try_into().unwrap()), mapping); ctx.set_opnd_mapping(dst_opnd.into(), mapping);
KeepCompiling KeepCompiling
} }
@ -1066,7 +1065,7 @@ fn gen_topn(
let n = jit.get_arg(0).as_usize(); let n = jit.get_arg(0).as_usize();
let top_n_val = ctx.stack_opnd(n.try_into().unwrap()); let top_n_val = ctx.stack_opnd(n.try_into().unwrap());
let mapping = ctx.get_opnd_mapping(StackOpnd(n.try_into().unwrap())); let mapping = ctx.get_opnd_mapping(top_n_val.into());
let loc0 = ctx.stack_push_mapping(mapping); let loc0 = ctx.stack_push_mapping(mapping);
asm.mov(loc0, top_n_val); asm.mov(loc0, top_n_val);
@ -2481,9 +2480,13 @@ fn guard_two_fixnums(
ocb: &mut OutlinedCb, ocb: &mut OutlinedCb,
side_exit: Target side_exit: Target
) { ) {
// Get stack operands without popping them
let arg1 = ctx.stack_opnd(0);
let arg0 = ctx.stack_opnd(1);
// Get the stack operand types // Get the stack operand types
let arg1_type = ctx.get_opnd_type(StackOpnd(0)); let arg1_type = ctx.get_opnd_type(arg1.into());
let arg0_type = ctx.get_opnd_type(StackOpnd(1)); let arg0_type = ctx.get_opnd_type(arg0.into());
if arg0_type.is_heap() || arg1_type.is_heap() { if arg0_type.is_heap() || arg1_type.is_heap() {
asm.comment("arg is heap object"); asm.comment("arg is heap object");
@ -2508,10 +2511,6 @@ fn guard_two_fixnums(
assert!(arg0_type == Type::Fixnum || arg0_type.is_unknown()); assert!(arg0_type == Type::Fixnum || arg0_type.is_unknown());
assert!(arg1_type == Type::Fixnum || arg1_type.is_unknown()); assert!(arg1_type == Type::Fixnum || arg1_type.is_unknown());
// Get stack operands without popping them
let arg1 = ctx.stack_opnd(0);
let arg0 = ctx.stack_opnd(1);
// If not fixnums at run-time, fall back // If not fixnums at run-time, fall back
if arg0_type != Type::Fixnum { if arg0_type != Type::Fixnum {
asm.comment("guard arg0 fixnum"); asm.comment("guard arg0 fixnum");
@ -2543,8 +2542,8 @@ fn guard_two_fixnums(
} }
// Set stack types in context // Set stack types in context
ctx.upgrade_opnd_type(StackOpnd(0), Type::Fixnum); ctx.upgrade_opnd_type(arg1.into(), Type::Fixnum);
ctx.upgrade_opnd_type(StackOpnd(1), Type::Fixnum); ctx.upgrade_opnd_type(arg0.into(), Type::Fixnum);
} }
// Conditional move operation used by comparison operators // Conditional move operation used by comparison operators
@ -2697,7 +2696,7 @@ fn gen_equality_specialized(
ocb, ocb,
unsafe { rb_cString }, unsafe { rb_cString },
a_opnd, a_opnd,
StackOpnd(1), a_opnd.into(),
comptime_a, comptime_a,
SEND_MAX_DEPTH, SEND_MAX_DEPTH,
side_exit, side_exit,
@ -2711,7 +2710,7 @@ fn gen_equality_specialized(
asm.je(equal); asm.je(equal);
// Otherwise guard that b is a T_STRING (from type info) or String (from runtime guard) // Otherwise guard that b is a T_STRING (from type info) or String (from runtime guard)
let btype = ctx.get_opnd_type(StackOpnd(0)); let btype = ctx.get_opnd_type(b_opnd.into());
if btype.known_value_type() != Some(RUBY_T_STRING) { if btype.known_value_type() != Some(RUBY_T_STRING) {
// Note: any T_STRING is valid here, but we check for a ::String for simplicity // Note: any T_STRING is valid here, but we check for a ::String for simplicity
// To pass a mutable static variable (rb_cString) requires an unsafe block // To pass a mutable static variable (rb_cString) requires an unsafe block
@ -2722,7 +2721,7 @@ fn gen_equality_specialized(
ocb, ocb,
unsafe { rb_cString }, unsafe { rb_cString },
b_opnd, b_opnd,
StackOpnd(0), b_opnd.into(),
comptime_b, comptime_b,
SEND_MAX_DEPTH, SEND_MAX_DEPTH,
side_exit, side_exit,
@ -2833,7 +2832,7 @@ fn gen_opt_aref(
ocb, ocb,
unsafe { rb_cArray }, unsafe { rb_cArray },
recv_opnd, recv_opnd,
StackOpnd(1), recv_opnd.into(),
comptime_recv, comptime_recv,
OPT_AREF_MAX_CHAIN_DEPTH, OPT_AREF_MAX_CHAIN_DEPTH,
side_exit, side_exit,
@ -2876,7 +2875,7 @@ fn gen_opt_aref(
ocb, ocb,
unsafe { rb_cHash }, unsafe { rb_cHash },
recv_opnd, recv_opnd,
StackOpnd(1), recv_opnd.into(),
comptime_recv, comptime_recv,
OPT_AREF_MAX_CHAIN_DEPTH, OPT_AREF_MAX_CHAIN_DEPTH,
side_exit, side_exit,
@ -2937,7 +2936,7 @@ fn gen_opt_aset(
ocb, ocb,
unsafe { rb_cArray }, unsafe { rb_cArray },
recv, recv,
StackOpnd(2), recv.into(),
comptime_recv, comptime_recv,
SEND_MAX_DEPTH, SEND_MAX_DEPTH,
side_exit, side_exit,
@ -2951,7 +2950,7 @@ fn gen_opt_aset(
ocb, ocb,
unsafe { rb_cInteger }, unsafe { rb_cInteger },
key, key,
StackOpnd(1), key.into(),
comptime_key, comptime_key,
SEND_MAX_DEPTH, SEND_MAX_DEPTH,
side_exit, side_exit,
@ -2989,7 +2988,7 @@ fn gen_opt_aset(
ocb, ocb,
unsafe { rb_cHash }, unsafe { rb_cHash },
recv, recv,
StackOpnd(2), recv.into(),
comptime_recv, comptime_recv,
SEND_MAX_DEPTH, SEND_MAX_DEPTH,
side_exit, side_exit,
@ -5791,8 +5790,8 @@ fn gen_send_iseq(
// side exits, so you still need to allow side exits here if block_arg0_splat is true. // side exits, so you still need to allow side exits here if block_arg0_splat is true.
// Note that you can't have side exits after this arg0 splat. // Note that you can't have side exits after this arg0 splat.
if block_arg0_splat { if block_arg0_splat {
let arg0_type = ctx.get_opnd_type(StackOpnd(0));
let arg0_opnd = ctx.stack_opnd(0); let arg0_opnd = ctx.stack_opnd(0);
let arg0_type = ctx.get_opnd_type(arg0_opnd.into());
// Only handle the case that you don't need to_ary conversion // Only handle the case that you don't need to_ary conversion
let not_array_exit = counted_exit!(ocb, side_exit, invokeblock_iseq_arg0_not_array); let not_array_exit = counted_exit!(ocb, side_exit, invokeblock_iseq_arg0_not_array);
@ -6123,7 +6122,7 @@ fn gen_send_general(
// Points to the receiver operand on the stack // Points to the receiver operand on the stack
let recv = ctx.stack_opnd(recv_idx); let recv = ctx.stack_opnd(recv_idx);
let recv_opnd = StackOpnd(recv_idx.try_into().unwrap()); let recv_opnd: YARVOpnd = recv.into();
// Log the name of the method we're calling to // Log the name of the method we're calling to
#[cfg(feature = "disasm")] #[cfg(feature = "disasm")]
@ -6387,14 +6386,15 @@ fn gen_send_general(
} }
}; };
let name_opnd = ctx.stack_opnd(argc);
jit_guard_known_klass( jit_guard_known_klass(
jit, jit,
ctx, ctx,
asm, asm,
ocb, ocb,
known_class, known_class,
ctx.stack_opnd(argc), name_opnd,
StackOpnd(argc as u16), name_opnd.into(),
compile_time_name, compile_time_name,
2, // We have string or symbol, so max depth is 2 2, // We have string or symbol, so max depth is 2
type_mismatch_exit type_mismatch_exit
@ -6402,7 +6402,7 @@ fn gen_send_general(
// Need to do this here so we don't have too many live // Need to do this here so we don't have too many live
// values for the register allocator. // values for the register allocator.
let name_opnd = asm.load(ctx.stack_opnd(argc)); let name_opnd = asm.load(name_opnd);
let symbol_id_opnd = asm.ccall(rb_get_symbol_id as *const u8, vec![name_opnd]); let symbol_id_opnd = asm.ccall(rb_get_symbol_id as *const u8, vec![name_opnd]);
@ -7016,7 +7016,7 @@ fn gen_objtostring(
ocb, ocb,
comptime_recv.class_of(), comptime_recv.class_of(),
recv, recv,
StackOpnd(0), recv.into(),
comptime_recv, comptime_recv,
SEND_MAX_DEPTH, SEND_MAX_DEPTH,
side_exit, side_exit,

View File

@ -294,6 +294,15 @@ pub enum YARVOpnd {
StackOpnd(u16), StackOpnd(u16),
} }
impl From<Opnd> for YARVOpnd {
fn from(value: Opnd) -> Self {
match value {
Opnd::Stack { idx, .. } => StackOpnd(idx as u16),
_ => unreachable!("{:?} cannot be converted to YARVOpnd", value)
}
}
}
/// Code generation context /// Code generation context
/// Contains information we can use to specialize/optimize code /// Contains information we can use to specialize/optimize code
/// There are a lot of context objects so we try to keep the size small. /// There are a lot of context objects so we try to keep the size small.
@ -1176,9 +1185,7 @@ impl Context {
self.stack_size += 1; self.stack_size += 1;
self.sp_offset += 1; self.sp_offset += 1;
// SP points just above the topmost value return self.stack_opnd(0);
let offset = ((self.sp_offset as i32) - 1) * (SIZEOF_VALUE as i32);
return Opnd::mem(64, SP, offset);
} }
/// Push one new value on the temp stack /// Push one new value on the temp stack
@ -1206,9 +1213,7 @@ impl Context {
pub fn stack_pop(&mut self, n: usize) -> Opnd { pub fn stack_pop(&mut self, n: usize) -> Opnd {
assert!(n <= self.stack_size.into()); assert!(n <= self.stack_size.into());
// SP points just above the topmost value let top = self.stack_opnd(0);
let offset = ((self.sp_offset as i32) - 1) * (SIZEOF_VALUE as i32);
let top = Opnd::mem(64, SP, offset);
// Clear the types of the popped values // Clear the types of the popped values
for i in 0..n { for i in 0..n {
@ -1243,10 +1248,7 @@ impl Context {
/// Get an operand pointing to a slot on the temp stack /// Get an operand pointing to a slot on the temp stack
pub fn stack_opnd(&self, idx: i32) -> Opnd { pub fn stack_opnd(&self, idx: i32) -> Opnd {
// SP points just above the topmost value Opnd::Stack { idx, sp_offset: self.sp_offset, num_bits: 64 }
let offset = ((self.sp_offset as i32) - 1 - idx) * (SIZEOF_VALUE as i32);
let opnd = Opnd::mem(64, SP, offset);
return opnd;
} }
/// Get the type of an instruction operand /// Get the type of an instruction operand