mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
Load Param off of cfp->ep (https://github.com/Shopify/zjit/pull/31)
* Load Param off of cfp->ep * Test with --zjit-call-threshold=1 as well * Fix get_opnd's debug output * Return Mem operand from gen_param * Test both first and second calls * Spell out the namespace for Opnd returns * Update a comment about gen_param * Explain why we take a lock * Fix a typo
This commit is contained in:
parent
62adbdf247
commit
48fa16f644
Notes:
git
2025-04-18 13:48:23 +00:00
5 changed files with 118 additions and 64 deletions
14
.github/workflows/zjit-macos.yml
vendored
14
.github/workflows/zjit-macos.yml
vendored
|
@ -30,19 +30,20 @@ jobs:
|
|||
include:
|
||||
- test_task: 'zjit-test'
|
||||
configure: '--enable-zjit=dev'
|
||||
rust_version: 1.85.0
|
||||
|
||||
- test_task: 'btest'
|
||||
zjit_opts: '--zjit-call-threshold=1'
|
||||
configure: '--enable-zjit=dev'
|
||||
btests: '../src/bootstraptest/test_zjit.rb'
|
||||
|
||||
- test_task: 'btest'
|
||||
zjit_opts: '--zjit-call-threshold=2'
|
||||
configure: '--enable-zjit=dev'
|
||||
btests: '../src/bootstraptest/test_zjit.rb'
|
||||
rust_version: 1.85.0
|
||||
|
||||
# Test without ZJIT for now
|
||||
- test_task: 'check'
|
||||
# Test without ZJIT for now
|
||||
#zjit_opts: '--zjit-call-threshold=2'
|
||||
configure: '--enable-zjit'
|
||||
rust_version: 1.85.0
|
||||
|
||||
env:
|
||||
GITPULLOPTIONS: --no-tags origin ${{ github.ref }}
|
||||
|
@ -85,8 +86,7 @@ jobs:
|
|||
if: ${{ matrix.test_task == 'zjit-test' }}
|
||||
|
||||
- name: Install Rust # TODO(alan): remove when GitHub images catch up past 1.85.0
|
||||
if: ${{ matrix.rust_version }}
|
||||
run: rustup default ${{ matrix.rust_version }}
|
||||
run: rustup default 1.85.0
|
||||
|
||||
- name: Run configure
|
||||
run: ../src/configure -C --disable-install-doc ${{ matrix.configure }}
|
||||
|
|
15
.github/workflows/zjit-ubuntu.yml
vendored
15
.github/workflows/zjit-ubuntu.yml
vendored
|
@ -32,23 +32,23 @@ jobs:
|
|||
hint: 'To fix: use patch in logs'
|
||||
configure: '--enable-zjit=dev --with-gcc=clang-14'
|
||||
libclang_path: '/usr/lib/llvm-14/lib/libclang.so.1'
|
||||
rust_version: 1.85.0 # TODO(alan): remove when GitHub's images catch up
|
||||
|
||||
- test_task: 'zjit-test'
|
||||
configure: '--enable-zjit=dev'
|
||||
rust_version: 1.85.0
|
||||
|
||||
- test_task: 'btest'
|
||||
zjit_opts: '--zjit-call-threshold=1'
|
||||
configure: '--enable-zjit=dev'
|
||||
btests: '../src/bootstraptest/test_zjit.rb'
|
||||
|
||||
- test_task: 'btest'
|
||||
zjit_opts: '--zjit-call-threshold=2'
|
||||
configure: '--enable-zjit=dev'
|
||||
btests: '../src/bootstraptest/test_zjit.rb'
|
||||
rust_version: 1.85.0
|
||||
|
||||
# Test without ZJIT for now
|
||||
- test_task: 'check'
|
||||
# Test without ZJIT for now
|
||||
#zjit_opts: '--zjit-call-threshold=2'
|
||||
configure: '--enable-zjit'
|
||||
rust_version: 1.85.0
|
||||
|
||||
env:
|
||||
GITPULLOPTIONS: --no-tags origin ${{ github.ref }}
|
||||
|
@ -100,8 +100,7 @@ jobs:
|
|||
fetch-depth: 10
|
||||
|
||||
- name: Install Rust
|
||||
if: ${{ matrix.rust_version }}
|
||||
run: rustup default ${{ matrix.rust_version }}
|
||||
run: rustup default 1.85.0
|
||||
|
||||
- name: Install rustfmt
|
||||
if: ${{ matrix.test_task == 'zjit-bindgen' }}
|
||||
|
|
|
@ -15,3 +15,8 @@ assert_equal '3', %q{
|
|||
def test = 1 + 2
|
||||
test; test
|
||||
}
|
||||
|
||||
assert_equal '[6, 3]', %q{
|
||||
def test(a, b) = a + b
|
||||
[test(2, 4), test(1, 2)]
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::{
|
||||
asm::CodeBlock, backend::lir::{asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, EC, SP}, cruby::*, debug, hir::{Const, FrameState, Function, Insn, InsnId}, hir_type::{types::Fixnum, Type}, virtualmem::CodePtr
|
||||
asm::CodeBlock, backend::lir, backend::lir::{asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, EC, SP}, cruby::*, debug, hir::{Const, FrameState, Function, Insn, InsnId}, hir_type::{types::Fixnum, Type}, virtualmem::CodePtr
|
||||
};
|
||||
|
||||
/// Ephemeral code generation state
|
||||
struct JITState {
|
||||
/// Instruction sequence for the compiling method
|
||||
/// Instruction sequence for the method being compiled
|
||||
iseq: IseqPtr,
|
||||
|
||||
/// Low-level IR Operands indexed by High-level IR's Instruction ID
|
||||
|
@ -12,12 +12,22 @@ struct JITState {
|
|||
}
|
||||
|
||||
impl JITState {
|
||||
/// Create a new JITState instance
|
||||
fn new(iseq: IseqPtr, insn_len: usize) -> Self {
|
||||
JITState {
|
||||
iseq,
|
||||
opnds: vec![None; insn_len],
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieve the output of a given instruction that has been compiled
|
||||
fn get_opnd(&self, insn_id: InsnId) -> Option<lir::Opnd> {
|
||||
let opnd = self.opnds[insn_id.0];
|
||||
if opnd.is_none() {
|
||||
debug!("Failed to get_opnd({insn_id})");
|
||||
}
|
||||
opnd
|
||||
}
|
||||
}
|
||||
|
||||
/// Compile High-level IR into machine code
|
||||
|
@ -29,23 +39,10 @@ pub fn gen_function(cb: &mut CodeBlock, function: &Function, iseq: IseqPtr) -> O
|
|||
|
||||
// Compile each instruction in the IR
|
||||
for (insn_idx, insn) in function.insns.iter().enumerate() {
|
||||
let insn_id = InsnId(insn_idx);
|
||||
if !matches!(*insn, Insn::Snapshot { .. }) {
|
||||
asm_comment!(asm, "Insn: {:04} {:?}", insn_idx, insn);
|
||||
if gen_insn(&mut jit, &mut asm, function, InsnId(insn_idx), insn).is_none() {
|
||||
debug!("Failed to compile insn: {:04} {:?}", insn_idx, insn);
|
||||
return None;
|
||||
}
|
||||
match insn {
|
||||
Insn::Const { val: Const::Value(val) } => gen_const(&mut jit, insn_id, *val),
|
||||
Insn::Snapshot { .. } => {}, // we don't need to do anything for this instruction at the moment
|
||||
Insn::Return { val } => gen_return(&jit, &mut asm, *val)?,
|
||||
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(&mut jit, &mut asm, insn_id, *left, *right, function.frame_state(*state))?,
|
||||
Insn::GuardType { val, guard_type, state } => gen_guard_type(&mut jit, &mut asm, insn_id, *val, *guard_type, function.frame_state(*state))?,
|
||||
Insn::PatchPoint(_) => {}, // For now, rb_zjit_bop_redefined() panics. TODO: leave a patch point and fix rb_zjit_bop_redefined()
|
||||
_ => {
|
||||
debug!("ZJIT: gen_function: unexpected insn {:?}", insn);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
debug!("Compiled insn: {:04} {:?}", insn_idx, insn);
|
||||
}
|
||||
|
||||
// Generate code if everything can be compiled
|
||||
|
@ -55,6 +52,31 @@ pub fn gen_function(cb: &mut CodeBlock, function: &Function, iseq: IseqPtr) -> O
|
|||
start_ptr
|
||||
}
|
||||
|
||||
/// Compile an instruction
|
||||
fn gen_insn(jit: &mut JITState, asm: &mut Assembler, function: &Function, insn_id: InsnId, insn: &Insn) -> Option<()> {
|
||||
if !matches!(*insn, Insn::Snapshot { .. }) {
|
||||
asm_comment!(asm, "Insn: {:04} {:?}", insn_id.0, insn);
|
||||
}
|
||||
let out_opnd = match insn {
|
||||
Insn::Const { val: Const::Value(val) } => gen_const(*val),
|
||||
Insn::Param { idx } => gen_param(jit, asm, *idx)?,
|
||||
Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment
|
||||
Insn::Return { val } => return Some(gen_return(&jit, asm, *val)?),
|
||||
Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, *left, *right, function.frame_state(*state))?,
|
||||
Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, *val, *guard_type, function.frame_state(*state))?,
|
||||
Insn::PatchPoint(_) => return Some(()), // For now, rb_zjit_bop_redefined() panics. TODO: leave a patch point and fix rb_zjit_bop_redefined()
|
||||
_ => {
|
||||
debug!("ZJIT: gen_function: unexpected insn {:?}", insn);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
// If the instruction has an output, remember it in jit.opnds
|
||||
jit.opnds[insn_id.0] = Some(out_opnd);
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Compile an interpreter entry block to be inserted into an ISEQ
|
||||
fn gen_entry_prologue(jit: &JITState, asm: &mut Assembler) {
|
||||
asm_comment!(asm, "YJIT entry point: {}", iseq_get_location(jit.iseq, 0));
|
||||
|
@ -76,9 +98,25 @@ fn gen_entry_prologue(jit: &JITState, asm: &mut Assembler) {
|
|||
}
|
||||
|
||||
/// Compile a constant
|
||||
fn gen_const(jit: &mut JITState, insn_id: InsnId, val: VALUE) {
|
||||
// Just remember the constant value and generate nothing
|
||||
jit.opnds[insn_id.0] = Some(Opnd::Value(val));
|
||||
fn gen_const(val: VALUE) -> Opnd {
|
||||
// Just propagate the constant value and generate nothing
|
||||
Opnd::Value(val)
|
||||
}
|
||||
|
||||
/// Compile a method/block paramter read. For now, it only supports method parameters.
|
||||
fn gen_param(jit: &JITState, asm: &mut Assembler, local_idx: usize) -> Option<lir::Opnd> {
|
||||
// Get the EP of the current CFP
|
||||
// TODO: Use the SP register and invalidate on EP escape
|
||||
let ep_opnd = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_EP);
|
||||
let ep_reg = asm.load(ep_opnd);
|
||||
|
||||
// Load the local variable
|
||||
// val = *(vm_get_ep(GET_EP(), level) - idx);
|
||||
let ep_offset = local_idx_to_ep_offset(jit.iseq, local_idx);
|
||||
let offs = -(SIZEOF_VALUE_I32 * ep_offset);
|
||||
let local_opnd = Opnd::mem(64, ep_reg, offs);
|
||||
|
||||
Some(local_opnd)
|
||||
}
|
||||
|
||||
/// Compile code that exits from JIT code with a return value
|
||||
|
@ -104,9 +142,9 @@ fn gen_return(jit: &JITState, asm: &mut Assembler, val: InsnId) -> Option<()> {
|
|||
}
|
||||
|
||||
/// Compile Fixnum + Fixnum
|
||||
fn gen_fixnum_add(jit: &mut JITState, asm: &mut Assembler, insn_id: InsnId, left: InsnId, right: InsnId, state: &FrameState) -> Option<()> {
|
||||
let left_opnd = jit.opnds[left.0]?;
|
||||
let right_opnd = jit.opnds[right.0]?;
|
||||
fn gen_fixnum_add(jit: &mut JITState, asm: &mut Assembler, left: InsnId, right: InsnId, state: &FrameState) -> Option<lir::Opnd> {
|
||||
let left_opnd = jit.get_opnd(left)?;
|
||||
let right_opnd = jit.get_opnd(right)?;
|
||||
|
||||
// Load left into a register if left is a constant. The backend doesn't support sub(imm, imm).
|
||||
let left_reg = match left_opnd {
|
||||
|
@ -119,13 +157,12 @@ fn gen_fixnum_add(jit: &mut JITState, asm: &mut Assembler, insn_id: InsnId, left
|
|||
let out_val = asm.add(left_untag, right_opnd);
|
||||
asm.jo(Target::SideExit(state.clone()));
|
||||
|
||||
jit.opnds[insn_id.0] = Some(out_val);
|
||||
Some(())
|
||||
Some(out_val)
|
||||
}
|
||||
|
||||
/// Compile a type check with a side exit
|
||||
fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, insn_id: InsnId, val: InsnId, guard_type: Type, state: &FrameState) -> Option<()> {
|
||||
let opnd = jit.opnds[val.0]?;
|
||||
fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, val: InsnId, guard_type: Type, state: &FrameState) -> Option<lir::Opnd> {
|
||||
let opnd = jit.get_opnd(val)?;
|
||||
if guard_type.is_subtype(Fixnum) {
|
||||
// Load opnd into a register if opnd is a constant. The backend doesn't support test(imm, imm) yet.
|
||||
let opnd_reg = match opnd {
|
||||
|
@ -139,7 +176,13 @@ fn gen_guard_type(jit: &mut JITState, asm: &mut Assembler, insn_id: InsnId, val:
|
|||
} else {
|
||||
unimplemented!("unsupported type: {guard_type}");
|
||||
}
|
||||
|
||||
jit.opnds[insn_id.0] = Some(opnd);
|
||||
Some(())
|
||||
Some(opnd)
|
||||
}
|
||||
|
||||
/// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details.
|
||||
fn local_idx_to_ep_offset(iseq: IseqPtr, local_idx: usize) -> i32 {
|
||||
let local_table_size: i32 = unsafe { get_iseq_body_local_table_size(iseq) }
|
||||
.try_into()
|
||||
.unwrap();
|
||||
local_table_size - local_idx as i32 - 1 + VM_ENV_DATA_SIZE as i32
|
||||
}
|
||||
|
|
|
@ -89,23 +89,30 @@ fn rb_bug_panic_hook() {
|
|||
/// Generate JIT code for a given ISEQ, which takes EC and CFP as its arguments.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *const u8 {
|
||||
// TODO: acquire the VM barrier
|
||||
|
||||
// Compile ISEQ into High-level IR
|
||||
let ssa = match hir::iseq_to_hir(iseq) {
|
||||
Ok(ssa) => ssa,
|
||||
Err(err) => {
|
||||
debug!("ZJIT: iseq_to_hir: {:?}", err);
|
||||
return std::ptr::null();
|
||||
}
|
||||
};
|
||||
|
||||
// Compile High-level IR into machine code
|
||||
let cb = ZJITState::get_code_block();
|
||||
match gen_function(cb, &ssa, iseq) {
|
||||
Some(start_ptr) => start_ptr.raw_ptr(cb),
|
||||
|
||||
// Compilation failed, continue executing in the interpreter only
|
||||
None => std::ptr::null(),
|
||||
// Do not test the JIT code in HIR tests
|
||||
if cfg!(test) {
|
||||
return std::ptr::null();
|
||||
}
|
||||
|
||||
// Take a lock to avoid writing to ISEQ in parallel with Ractors.
|
||||
// with_vm_lock() does nothing if the program doesn't use Ractors.
|
||||
with_vm_lock(src_loc!(), || {
|
||||
// Compile ISEQ into High-level IR
|
||||
let ssa = match hir::iseq_to_hir(iseq) {
|
||||
Ok(ssa) => ssa,
|
||||
Err(err) => {
|
||||
debug!("ZJIT: iseq_to_hir: {:?}", err);
|
||||
return std::ptr::null();
|
||||
}
|
||||
};
|
||||
|
||||
// Compile High-level IR into machine code
|
||||
let cb = ZJITState::get_code_block();
|
||||
match gen_function(cb, &ssa, iseq) {
|
||||
Some(start_ptr) => start_ptr.raw_ptr(cb),
|
||||
|
||||
// Compilation failed, continue executing in the interpreter only
|
||||
None => std::ptr::null(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue