Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1191,6 +1191,30 @@ def test(x) = [1,2,3][x]
}, call_threshold: 2, insns: [:opt_aref]
end

def test_empty_array_pop
assert_compiles 'nil', %q{
def test(arr) = arr.pop
test([])
test([])
}, call_threshold: 2
end

def test_array_pop_no_arg
assert_compiles '42', %q{
def test(arr) = arr.pop
test([32, 33, 42])
test([32, 33, 42])
}, call_threshold: 2
end

def test_array_pop_arg
assert_compiles '[33, 42]', %q{
def test(arr) = arr.pop(2)
test([32, 33, 42])
test([32, 33, 42])
}, call_threshold: 2
end

def test_new_range_inclusive
assert_compiles '1..5', %q{
def test(a, b) = a..b
Expand Down
7 changes: 7 additions & 0 deletions zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ def stats_string
buf = +"***ZJIT: Printing ZJIT statistics on exit***\n"
stats = self.stats

stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100

# Show counters independent from exit_* or dynamic_send_*
print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20)
# Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs
Expand Down Expand Up @@ -197,6 +199,9 @@ def stats_string
:vm_write_to_parent_iseq_local_count,
:vm_read_from_parent_iseq_local_count,

:guard_type_count,
:guard_type_exit_ratio,

:code_region_bytes,
:side_exit_count,
:total_insn_count,
Expand Down Expand Up @@ -232,6 +237,8 @@ def print_counters(keys, buf:, stats:, right_align: false, base: nil)
case key
when :ratio_in_zjit
value = '%0.1f%%' % value
when :guard_type_exit_ratio
value = '%0.1f%%' % value
when /_time_ns\z/
key = key.to_s.sub(/_time_ns\z/, '_time')
value = "#{number_with_delimiter(value / 10**6)}ms"
Expand Down
2 changes: 2 additions & 0 deletions zjit/bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ fn main() {
.allowlist_function("rb_ary_clear")
.allowlist_function("rb_ary_dup")
.allowlist_function("rb_ary_push")
.allowlist_function("rb_ary_pop")
.allowlist_function("rb_ary_unshift_m")
.allowlist_function("rb_ec_ary_new_from_values")
.allowlist_function("rb_ary_tmp_new_from_values")
Expand Down Expand Up @@ -331,6 +332,7 @@ fn main() {
.allowlist_function("rb_class_new_instance_pass_kw")
.allowlist_function("rb_obj_alloc")
.allowlist_function("rb_obj_info")
.allowlist_function("rb_obj_frozen_p")
// From include/ruby/debug.h
.allowlist_function("rb_profile_frames")
.allowlist_function("ruby_xfree")
Expand Down
4 changes: 2 additions & 2 deletions zjit/src/asm/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ fn write_rm_unary(cb: &mut CodeBlock, op_mem_reg_8: u8, op_mem_reg_pref: u8, op_

// Encode an add-like RM instruction with multiple possible encodings
fn write_rm_multi(cb: &mut CodeBlock, op_mem_reg8: u8, op_mem_reg_pref: u8, op_reg_mem8: u8, op_reg_mem_pref: u8, op_mem_imm8: u8, op_mem_imm_sml: u8, op_mem_imm_lrg: u8, op_ext_imm: Option<u8>, opnd0: X86Opnd, opnd1: X86Opnd) {
assert!(matches!(opnd0, X86Opnd::Reg(_) | X86Opnd::Mem(_)));
assert!(matches!(opnd0, X86Opnd::Reg(_) | X86Opnd::Mem(_)), "unexpected opnd0: {opnd0:?}, {opnd1:?}");

// Check the size of opnd0
let opnd_size = opnd0.num_bits();
Expand Down Expand Up @@ -1334,7 +1334,7 @@ pub fn test(cb: &mut CodeBlock, rm_opnd: X86Opnd, test_opnd: X86Opnd) {
write_rm(cb, rm_num_bits == 16, rm_num_bits == 64, test_opnd, rm_opnd, None, &[0x85]);
}
},
_ => unreachable!()
_ => unreachable!("unexpected operands for test: {rm_opnd:?}, {test_opnd:?}")
};
}

Expand Down
20 changes: 12 additions & 8 deletions zjit/src/backend/arm64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ impl Assembler {

/// Return an Assembler with scratch registers disabled in the backend, and a scratch register.
pub fn new_with_scratch_reg() -> (Self, Opnd) {
(Self::new_with_label_names(Vec::default(), 0, true), SCRATCH_OPND)
(Self::new_with_accept_scratch_reg(true), SCRATCH_OPND)
}

/// Return true if opnd contains a scratch reg
Expand Down Expand Up @@ -386,9 +386,9 @@ impl Assembler {
}
}

let mut asm_local = Assembler::new_with_asm(&self);
let live_ranges: Vec<LiveRange> = take(&mut self.live_ranges);
let mut iterator = self.insns.into_iter().enumerate().peekable();
let mut asm_local = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg);
let asm = &mut asm_local;

while let Some((index, mut insn)) = iterator.next() {
Expand Down Expand Up @@ -691,9 +691,10 @@ impl Assembler {
/// VRegs, most splits should happen in [`Self::arm64_split`]. However, some instructions
/// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so this
/// splits them and uses scratch registers for it.
fn arm64_split_with_scratch_reg(mut self) -> Assembler {
fn arm64_split_with_scratch_reg(self) -> Assembler {
let mut asm = Assembler::new_with_asm(&self);
asm.accept_scratch_reg = true;
let iterator = self.insns.into_iter().enumerate().peekable();
let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true);

for (_, mut insn) in iterator {
match &mut insn {
Expand Down Expand Up @@ -1664,7 +1665,7 @@ mod tests {
fn test_emit_frame() {
let (mut asm, mut cb) = setup_asm();

asm.frame_setup(&[], 0);
asm.frame_setup(&[]);
asm.frame_teardown(&[]);
asm.compile_with_num_regs(&mut cb, 0);

Expand All @@ -1683,7 +1684,8 @@ mod tests {
// Test 3 preserved regs (odd), odd slot_count
let cb1 = {
let (mut asm, mut cb) = setup_asm();
asm.frame_setup(THREE_REGS, 3);
asm.stack_base_idx = 3;
asm.frame_setup(THREE_REGS);
asm.frame_teardown(THREE_REGS);
asm.compile_with_num_regs(&mut cb, 0);
cb
Expand All @@ -1692,7 +1694,8 @@ mod tests {
// Test 3 preserved regs (odd), even slot_count
let cb2 = {
let (mut asm, mut cb) = setup_asm();
asm.frame_setup(THREE_REGS, 4);
asm.stack_base_idx = 4;
asm.frame_setup(THREE_REGS);
asm.frame_teardown(THREE_REGS);
asm.compile_with_num_regs(&mut cb, 0);
cb
Expand All @@ -1702,7 +1705,8 @@ mod tests {
let cb3 = {
static FOUR_REGS: &[Opnd] = &[Opnd::Reg(X19_REG), Opnd::Reg(X20_REG), Opnd::Reg(X21_REG), Opnd::Reg(X22_REG)];
let (mut asm, mut cb) = setup_asm();
asm.frame_setup(FOUR_REGS, 3);
asm.stack_base_idx = 3;
asm.frame_setup(FOUR_REGS);
asm.frame_teardown(FOUR_REGS);
asm.compile_with_num_regs(&mut cb, 0);
cb
Expand Down
56 changes: 41 additions & 15 deletions zjit/src/backend/lir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1034,6 +1034,9 @@ impl fmt::Debug for Insn {

// Print list of operands
let mut opnd_iter = self.opnd_iter();
if let Insn::FrameSetup { slot_count, .. } = self {
write!(fmt, "{slot_count}")?;
}
if let Some(first_opnd) = opnd_iter.next() {
write!(fmt, "{first_opnd:?}")?;
}
Expand Down Expand Up @@ -1176,32 +1179,54 @@ pub struct Assembler {
/// On `compile`, it also disables the backend's use of them.
pub(super) accept_scratch_reg: bool,

/// The Assembler can use NATIVE_BASE_PTR + stack_base_idx as the
/// first stack slot in case it needs to allocate memory. This is
/// equal to the number of spilled basic block arguments.
pub(super) stack_base_idx: usize,

/// If Some, the next ccall should verify its leafness
leaf_ccall_stack_size: Option<usize>
}

impl Assembler
{
/// Create an Assembler
/// Create an Assembler with defaults
pub fn new() -> Self {
Self::new_with_label_names(Vec::default(), 0, false)
}

/// Create an Assembler with parameters that are populated by another Assembler instance.
/// This API is used for copying an Assembler for the next compiler pass.
pub fn new_with_label_names(label_names: Vec<String>, num_vregs: usize, accept_scratch_reg: bool) -> Self {
let mut live_ranges = Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY);
live_ranges.resize(num_vregs, LiveRange { start: None, end: None });

Self {
insns: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY),
live_ranges,
label_names,
accept_scratch_reg,
live_ranges: Vec::with_capacity(ASSEMBLER_INSNS_CAPACITY),
label_names: Vec::default(),
accept_scratch_reg: false,
stack_base_idx: 0,
leaf_ccall_stack_size: None,
}
}

/// Create an Assembler, reserving a specified number of stack slots
pub fn new_with_stack_slots(stack_base_idx: usize) -> Self {
Self { stack_base_idx, ..Self::new() }
}

/// Create an Assembler that allows the use of scratch registers.
/// This should be called only through [`Self::new_with_scratch_reg`].
pub(super) fn new_with_accept_scratch_reg(accept_scratch_reg: bool) -> Self {
Self { accept_scratch_reg, ..Self::new() }
}

/// Create an Assembler with parameters of another Assembler and empty instructions.
/// Compiler passes build a next Assembler with this API and insert new instructions to it.
pub(super) fn new_with_asm(old_asm: &Assembler) -> Self {
let mut asm = Self {
label_names: old_asm.label_names.clone(),
accept_scratch_reg: old_asm.accept_scratch_reg,
stack_base_idx: old_asm.stack_base_idx,
..Self::new()
};
// Bump the initial VReg index to allow the use of the VRegs for the old Assembler
asm.live_ranges.resize(old_asm.live_ranges.len(), LiveRange { start: None, end: None });
asm
}

pub fn expect_leaf_ccall(&mut self, stack_size: usize) {
self.leaf_ccall_stack_size = Some(stack_size);
}
Expand Down Expand Up @@ -1357,9 +1382,9 @@ impl Assembler
let mut saved_regs: Vec<(Reg, usize)> = vec![];

// live_ranges is indexed by original `index` given by the iterator.
let mut asm = Assembler::new_with_asm(&self);
let live_ranges: Vec<LiveRange> = take(&mut self.live_ranges);
let mut iterator = self.insns.into_iter().enumerate().peekable();
let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg);

while let Some((index, mut insn)) = iterator.next() {
let before_ccall = match (&insn, iterator.peek().map(|(_, insn)| insn)) {
Expand Down Expand Up @@ -1831,7 +1856,8 @@ impl Assembler {
out
}

pub fn frame_setup(&mut self, preserved_regs: &'static [Opnd], slot_count: usize) {
pub fn frame_setup(&mut self, preserved_regs: &'static [Opnd]) {
let slot_count = self.stack_base_idx;
self.push_insn(Insn::FrameSetup { preserved: preserved_regs, slot_count });
}

Expand Down
67 changes: 38 additions & 29 deletions zjit/src/backend/x86_64/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl From<Opnd> for X86Opnd {
"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."
),

_ => panic!("unsupported x86 operand type")
_ => panic!("unsupported x86 operand type: {opnd:?}")
}
}
}
Expand Down Expand Up @@ -102,7 +102,7 @@ const SCRATCH_OPND: Opnd = Opnd::Reg(R11_REG);
impl Assembler {
/// Return an Assembler with scratch registers disabled in the backend, and a scratch register.
pub fn new_with_scratch_reg() -> (Self, Opnd) {
(Self::new_with_label_names(Vec::default(), 0, true), SCRATCH_OPND)
(Self::new_with_accept_scratch_reg(true), SCRATCH_OPND)
}

/// Return true if opnd contains a scratch reg
Expand Down Expand Up @@ -137,9 +137,9 @@ impl Assembler {
/// Split IR instructions for the x86 platform
fn x86_split(mut self) -> Assembler
{
let mut asm = Assembler::new_with_asm(&self);
let live_ranges: Vec<LiveRange> = take(&mut self.live_ranges);
let mut iterator = self.insns.into_iter().enumerate().peekable();
let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), live_ranges.len(), self.accept_scratch_reg);

while let Some((index, mut insn)) = iterator.next() {
let is_load = matches!(insn, Insn::Load { .. } | Insn::LoadInto { .. });
Expand Down Expand Up @@ -390,7 +390,7 @@ impl Assembler {
/// for VRegs, most splits should happen in [`Self::x86_split`]. However, some instructions
/// need to be split with registers after `alloc_regs`, e.g. for `compile_side_exits`, so
/// this splits them and uses scratch registers for it.
pub fn x86_split_with_scratch_reg(mut self) -> Assembler {
pub fn x86_split_with_scratch_reg(self) -> Assembler {
/// For some instructions, we want to be able to lower a 64-bit operand
/// without requiring more registers to be available in the register
/// allocator. So we just use the SCRATCH_OPND register temporarily to hold
Expand Down Expand Up @@ -419,8 +419,9 @@ impl Assembler {
}
}

let mut asm = Assembler::new_with_asm(&self);
asm.accept_scratch_reg = true;
let mut iterator = self.insns.into_iter().enumerate().peekable();
let mut asm = Assembler::new_with_label_names(take(&mut self.label_names), self.live_ranges.len(), true);

while let Some((_, mut insn)) = iterator.next() {
match &mut insn {
Expand Down Expand Up @@ -1551,38 +1552,46 @@ mod tests {
}

#[test]
fn frame_setup_teardown() {
fn frame_setup_teardown_preserved_regs() {
let (mut asm, mut cb) = setup_asm();
asm.frame_setup(JIT_PRESERVED_REGS, 0);
asm.frame_setup(JIT_PRESERVED_REGS);
asm.frame_teardown(JIT_PRESERVED_REGS);

asm.cret(C_RET_OPND);
asm.compile_with_num_regs(&mut cb, 0);

asm.frame_setup(&[], 5);
asm.frame_teardown(&[]);
assert_disasm_snapshot!(cb.disasm(), @r"
0x0: push rbp
0x1: mov rbp, rsp
0x4: push r13
0x6: push rbx
0x7: push r12
0x9: sub rsp, 8
0xd: mov r13, qword ptr [rbp - 8]
0x11: mov rbx, qword ptr [rbp - 0x10]
0x15: mov r12, qword ptr [rbp - 0x18]
0x19: mov rsp, rbp
0x1c: pop rbp
0x1d: ret
");
assert_snapshot!(cb.hexdump(), @"554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3");
}

#[test]
fn frame_setup_teardown_stack_base_idx() {
let (mut asm, mut cb) = setup_asm();
asm.stack_base_idx = 5;
asm.frame_setup(&[]);
asm.frame_teardown(&[]);
asm.compile_with_num_regs(&mut cb, 0);

assert_disasm_snapshot!(cb.disasm(), @"
0x0: push rbp
0x1: mov rbp, rsp
0x4: push r13
0x6: push rbx
0x7: push r12
0x9: sub rsp, 8
0xd: mov r13, qword ptr [rbp - 8]
0x11: mov rbx, qword ptr [rbp - 0x10]
0x15: mov r12, qword ptr [rbp - 0x18]
0x19: mov rsp, rbp
0x1c: pop rbp
0x1d: ret
0x1e: push rbp
0x1f: mov rbp, rsp
0x22: sub rsp, 0x30
0x26: mov rsp, rbp
0x29: pop rbp
assert_disasm_snapshot!(cb.disasm(), @r"
0x0: push rbp
0x1: mov rbp, rsp
0x4: sub rsp, 0x30
0x8: mov rsp, rbp
0xb: pop rbp
");
assert_snapshot!(cb.hexdump(), @"554889e541555341544883ec084c8b6df8488b5df04c8b65e84889ec5dc3554889e54883ec304889ec5d");
assert_snapshot!(cb.hexdump(), @"554889e54883ec304889ec5d");
}

#[test]
Expand Down
Loading