8000 ZJIT: Add newrange support by st0012 · Pull Request #13505 · ruby/ruby · GitHub
[go: up one dir, main page]

Skip to content

ZJIT: Add newrange support #13505

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 5, 2025
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
21 changes: 21 additions & 0 deletions test/ruby/test_zjit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,27 @@ def test = [1,2,3]
}
end

def test_new_range_inclusive
assert_compiles '1..5', %q{
def test(a, b) = a..b
test(1, 5)
}
end

def test_new_range_exclusive
assert_compiles '1...5', %q{
def test(a, b) = a...b
test(1, 5)
}
end

def test_new_range_with_literal
assert_compiles '3..10', %q{
def test(n) = n..10
test(3)
}
end

def test_if
assert_compiles '[0, nil]', %q{
def test(n)
Expand Down
1 change: 1 addition & 0 deletions zjit/bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ fn main() {
.allowlist_var("rb_cSymbol")
.allowlist_var("rb_cFloat")
.allowlist_var("rb_cNumeric")
.allowlist_var("rb_cRange")
.allowlist_var("rb_cString")
.allowlist_var("rb_cThread")
.allowlist_var("rb_cArray")
Expand Down
25 changes: 24 additions & 1 deletion zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::state::ZJITState;
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption};
use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP};
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo};
use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType};
use crate::hir::{Const, FrameState, Function, Insn, InsnId};
use crate::hir_type::{types::Fixnum, Type};
use crate::options::get_option;
Expand Down Expand Up @@ -251,6 +251,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
Insn::PutSelf => gen_putself(),
Insn::Const { val: Const::Value(val) } => gen_const(*val),
Insn::NewArray { elements, state } => gen_new_array(jit, asm, elements, &function.frame_state(*state)),
Insn::NewRange { low, high, flag, state } => gen_new_range(asm, opnd!(low), opnd!(high), *flag, &function.frame_state(*state)),
Insn::ArrayDup { val, state } => gen_array_dup(asm, opnd!(val), &function.frame_state(*state)),
Insn::Param { idx } => unreachable!("block.insns should not have Insn::Param({idx})"),
Insn::Snapshot { .. } => return Some(()), // we don't need to do anything for this instruction at the moment
Expand Down Expand Up @@ -552,6 +553,28 @@ fn gen_new_array(
new_array
}

/// Compile a new range instruction
fn gen_new_range(
asm: &mut Assembler,
low: lir::Opnd,
high: lir::Opnd,
flag: RangeType,
state: &FrameState,
) -> lir::Opnd {
asm_comment!(asm, "call rb_range_new");

// Save PC
gen_save_pc(asm, state);

// Call rb_range_new(low, high, flag)
let new_range = asm.ccall(
rb_range_new as *const u8,
vec![low, high, lir::Opnd::Imm(flag as i64)],
);

new_range
}

/// Compile code that exits from JIT code with a return value
fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> {
// Pop the current frame (ec->cfp++)
Expand Down
1 change: 1 addition & 0 deletions zjit/src/cruby_bindings.inc.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

127 changes: 127 additions & 0 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,50 @@ impl Const {
}
}

pub enum RangeType {
Inclusive = 0, // include the end value
Exclusive = 1, // exclude the end value
}

impl std::fmt::Display for RangeType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", match self {
RangeType::Inclusive => "NewRangeInclusive",
RangeType::Exclusive => "NewRangeExclusive",
})
}
}

impl std::fmt::Debug for RangeType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.to_string())
}
}

impl Clone for RangeType {
fn clone(&self) -> Self {
*self
}
}

impl Copy for RangeType {}

impl From<u32> for RangeType {
fn from(flag: u32) -> Self {
match flag {
0 => RangeType::Inclusive,
1 => RangeType::Exclusive,
_ => panic!("Invalid range flag: {}", flag),
}
}
}

impl From<RangeType> for u32 {
fn from(range_type: RangeType) -> Self {
range_type as u32
}
}

/// Print adaptor for [`Const`]. See [`PtrPrintMap`].
struct ConstPrinter<'a> {
inner: &'a Const,
Expand Down Expand Up @@ -330,6 +374,7 @@ pub enum Insn {
NewArray { elements: Vec<InsnId>, state: InsnId },
/// NewHash contains a vec of (key, value) pairs
NewHash { elements: Vec<(InsnId,InsnId)>, state: InsnId },
NewRange { low: InsnId, high: InsnId, flag: RangeType, state: InsnId },
ArraySet { array: InsnId, idx: usize, val: InsnId },
ArrayDup { val: InsnId, state: InsnId },
ArrayMax { elements: Vec<InsnId>, state: InsnId },
Expand Down Expand Up @@ -439,6 +484,7 @@ impl Insn {
Insn::StringCopy { .. } => false,
Insn::NewArray { .. } => false,
Insn::NewHash { .. } => false,
Insn::NewRange { .. } => false,
Insn::ArrayDup { .. } => false,
Insn::HashDup { .. } => false,
Insn::Test { .. } => false,
Expand Down Expand Up @@ -490,6 +536,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
Ok(())
}
Insn::NewRange { low, high, flag, .. } => {
write!(f, "NewRange {low} {flag} {high}")
}
Insn::ArrayMax { elements, .. } => {
write!(f, "ArrayMax")?;
let mut prefix = " ";
Expand Down Expand Up @@ -912,6 +961,7 @@ impl Function {
}
NewHash { elements: found_elements, state: find!(state) }
}
&NewRange { low, high, flag, state } => NewRange { low: find!(low), high: find!(high), flag, state: find!(state) },
ArrayMax { elements, state } => ArrayMax { elements: find_vec!(*elements), state: find!(*state) },
&GetIvar { self_val, id, state } => GetIvar { self_val: find!(self_val), id, state },
&SetIvar { self_val, id, val, state } => SetIvar { self_val: find!(self_val), id, val, state },
Expand Down Expand Up @@ -972,6 +1022,7 @@ impl Function {
Insn::ArrayDup { .. } => types::ArrayExact,
Insn::NewHash { .. } => types::HashExact,
Insn::HashDup { .. } => types::HashExact,
Insn::NewRange { .. } => types::RangeExact,
Insn::CCall { return_type, .. } => *return_type,
Insn::GuardType { val, guard_type, .. } => self.type_of(*val).intersection(*guard_type),
Insn::GuardBitEquals { val, expected, .. } => self.type_of(*val).intersection(Type::from_value(*expected)),
Expand Down Expand Up @@ -1486,6 +1537,11 @@ impl Function {
}
worklist.push_back(state);
}
Insn::NewRange { low, high, state, .. } => {
worklist.push_back(low);
worklist.push_back(high);
worklist.push_back(state);
}
Insn::StringCopy { val }
| Insn::StringIntern { val }
| Insn::Return { val }
Expand Down Expand Up @@ -2342,6 +2398,14 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
let val = state.stack_pop()?;
fun.push_insn(block, Insn::SetIvar { self_val, id, val, state: exit_id });
}
YARVINSN_newrange => {
let flag = RangeType::from(get_arg(pc, 0).as_u32());
let high = state.stack_pop()?;
let low = state.stack_pop()?;
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
let insn_id = fun.push_insn(block, Insn::NewRange { low, high, flag, state: exit_id });
state.stack_push(insn_id);
}
_ => {
// Unknown opcode; side-exit into the interpreter
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
Expand Down Expand Up @@ -2735,6 +2799,52 @@ mod tests {
"#]]);
}

#[test]
fn test_new_range_inclusive_with_one_element() {
eval("def test(a) = (a..10)");
assert_method_hir_with_opcode("test", YARVINSN_newrange, expect![[r#"
fn test:
bb0(v0:BasicObject):
v2:Fixnum[10] = Const Value(10)
v4:RangeExact = NewRange v0 NewRangeInclusive v2
Return v4
"#]]);
}

#[test]
fn test_new_range_inclusive_with_two_elements() {
eval("def test(a, b) = (a..b)");
assert_method_hir_with_opcode("test", YARVINSN_newrange, expect![[r#"
fn test:
bb0(v0:BasicObject, v1:BasicObject):
v4:RangeExact = NewRange v0 NewRangeInclusive v1
Return v4
"#]]);
}

#[test]
fn test_new_range_exclusive_with_one_element() {
eval("def test(a) = (a...10)");
assert_method_hir_with_opcode("test", YARVINSN_newrange, expect![[r#"
fn test:
bb0(v0:BasicObject):
v2:Fixnum[10] = Const Value(10)
v4:RangeExact = NewRange v0 NewRangeExclusive v2
Return v4
"#]]);
}

#[test]
fn test_new_range_exclusive_with_two_elements() {
eval("def test(a, b) = (a...b)");
assert_method_hir_with_opcode("test", YARVINSN_newrange, expect![[r#"
fn test:
bb0(v0:BasicObject, v1:BasicObject):
v4:RangeExact = NewRange v0 NewRangeExclusive v1
Return v4
"#]]);
}

#[test]
fn test_array_dup() {
eval("def test = [1, 2, 3]");
Expand Down Expand Up @@ -4273,6 +4383,23 @@ mod opt_tests {
"#]]);
}

#[test]
fn test_eliminate_new_range() {
eval("
def test()
c = (1..2)
5
end
test; test
");
assert_optimized_method_hir("test", expect![[r#"
fn test:
bb0():
v3:Fixnum[5] = Const Value(5)
Return v3
"#]]);
}

#[test]
fn test_eliminate_new_array_with_elements() {
eval("
Expand Down
1 change: 1 addition & 0 deletions zjit/src/hir_type/gen_hir_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ def base_type name
base_type "String"
base_type "Array"
base_type "Hash"
base_type "Range"

(integer, integer_exact) = base_type "Integer"
# CRuby partitions Integer into immediate and non-immediate variants.
Expand Down
33 changes: 21 additions & 12 deletions zjit/src/hir_type/hir_type.inc.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading
0