Skip to content

Conversation

altxtech
Copy link
Contributor

@altxtech altxtech commented Aug 29, 2025

Context

Closes Shopify#710

Changes

HIR

  • Added a new instruction NewRangeFixnum that mirrors NewRange but is specialized for Fixnum endpoints.
  • Marked as leaf and effect-free (unlike NewRange, which may call <=>).

Codegen

  • Added gen_new_range_fixnum, wired to the new instruction.
  • Uses gen_prepare_call_with_gc(asm, state) since range allocation may trigger GC, but does not call Ruby methods.
  • Emits a direct call to rb_range_new.

Optimization

  • Added optimize_ranges pass: conservatively rewrites NewRange into NewRangeFixnum when one or both ends are statically Fixnum literals (with guards added for the dynamic side if needed).
  • This covers the simple literal/guardable cases that can be proven cheaply.

At this point I haven’t added rewrites for parameter–parameter ranges.
From looking at how opt_plus works, it seems to rely on profiling info, but I don’t fully understand whether that same info is available for newrange.
Since I’m still new to this codebase (and to low-level Rust in general), I decided to keep the optimization conservative for now and get maintainer feedback before broadening it.

@altxtech
Copy link
Contributor Author

@tekknolagi

Could you take a look at this draft PR? Want to make sure this is the right direction.

@tekknolagi
Copy link
Contributor

I'm on vacation for the next two weeks but someone else on the team can give it a look!

@tekknolagi
Copy link
Contributor

cc @ruby/jit

@altxtech altxtech marked this pull request as ready for review August 30, 2025 19:01
@matzbot matzbot requested a review from a team August 30, 2025 19:01
zjit/src/hir.rs Outdated
worklist.push_back(high);
worklist.push_back(state);
}
&Insn::NewRangeFixnum { low, high, state, .. } => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be stacked with NewRange like

            &Insn::NewRangeFixnum { low, high, state, .. } |
            &Insn::NewRange { low, high, state, .. } => {
                worklist.push_back(low);
                worklist.push_back(high);
                worklist.push_back(state);
            }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

zjit/src/hir.rs Outdated

#[test]
fn test_optimize_range_fixnum_inclusive_literals() {
eval(r#"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think none of the new tests new r# as there's no character needs escaping.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will check

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

def test(a)
(a...10)
end
test(2); test(3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add these cases to test/ruby/test_zjit.rb too as integration tests? Remember to add call_threshold: 2 option so we actually run the optimzied HIR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests added

@altxtech altxtech requested a review from st0012 September 1, 2025 21:04
@k0kubun k0kubun enabled auto-merge (squash) September 2, 2025 17:10
@k0kubun k0kubun merged commit c026627 into ruby:master Sep 2, 2025
86 checks passed
@altxtech altxtech deleted the altxtech/new-range-fixnum-instruction branch September 2, 2025 17:45
fn test_optimize_range_fixnum_exclusive_literals() {
eval("
def test()
(1...2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may have noticed that this doesn't produce a New range instruction. For that you probably need to use a local variable (makes it harder for the bytecode compiler to reason about, but the JIT sees through it). If you have time, please rewrite these tests.

Copy link
Contributor Author

@altxtech altxtech Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will handle this later this week, along with you other comment.

self.insn_types[repl.0] = self.infer_type(repl);
} else if low_is_fix {
// Only left is fixnum => guard right
let high_fix = self.coerce_to_fixnum(block, high, state);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some code above to do this for you: see arguments_likely_fixnums and coerce something or other

Copy link
Contributor Author

@altxtech altxtech Sep 19, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't found arguments_likely_fixnum to be applicable here, unless we change how this optimization works.

Profiling info is not enabled for NewRange, so this optimization fires if either end of the range is a fixnum, without looking at profiling info. For example:

def test(a)
  (1..a)
end

Is a case where the optimization fires even if we don't have profiling info for a, because the start of the range is a Fixnum literal.

We could enable profiling for NewRange and just use arguments_likely_fixnum as you said. In effect, this would make this more similar to other Fixnum operations.

I submitted a draft PR #14607. Would like you to take a look before marking it ready for review.. Decided to just mark it as ready.

tekknolagi pushed a commit that referenced this pull request Sep 22, 2025
## Context

#14409

#14409 (comment)
>You may have noticed that this doesn't produce a New range instruction. For that you probably need to use a local variable (makes it harder for the bytecode compiler to reason about, but the JIT sees through it). If you have time, please rewrite these tests.

#14409 (comment)
>There's some code above to do this for you: see arguments_likely_fixnums and coerce something or other

## Changes

### Refactor tests

Modify tests force the usage of `NewRangeFixnum` instruction and make tests names more consistent.  

### Refactor optimize ranges

Didn't found `arguments_likely_fixnums` to be applicable unless we enable profiling for `NewRange` and change the criteria of when the optimization fires. But there were other ways to clean up the code.  

Simplify the logic and move it to `type_specialize`. Deleted the standalone `optimize_ranges` function.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ZJIT: Make HIR NewRangeFixnum instruction
4 participants