10000 ZJIT: Support invokebuiltin opcodes by composerinteralia · Pull Request #13632 · ruby/ruby · GitHub
[go: up one dir, main page]

Skip to content

ZJIT: Support invokebuiltin opcodes #13632

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

composerinteralia
Copy link
Contributor
  • invokebuiltin
  • invokebuiltin_delegate
  • invokebuiltin_delegate_leave

These instructions all call out to a C function, passing EC, self, and some number of arguments. invokebuiltin gets the arguments from the stack, whereas the _delegate instructions use a subset of the locals.

opt_invokebuiltin_delegate_leave has a fast path for leave, but I'm not sure we need to do anything special for that here (FWIW YJIT appears to treat the two delegate instructions the same).

* `invokebuiltin`
* `invokebuiltin_delegate`
* `invokebuiltin_delegate_leave`

These instructions all call out to a C function, passing EC, self, and
some number of arguments. `invokebuiltin` gets the arguments from the
stack, whereas the `_delegate` instructions use a subset of the locals.

`opt_invokebuiltin_delegate_leave` has a fast path for `leave`, but I'm
not sure we need to do anything special for that here (FWIW YJIT appears
to treat the two delegate instructions the same).
@matzbot matzbot requested a review from a team June 17, 2025 01:35
@@ -76,6 +76,14 @@ def test3 = baz(4, 1)
}
end

def test_invokebuiltin_delegate
Copy link
Contributor Author
@composerinteralia composerinteralia Jun 17, 2025

Choose a reason for hiding this comment

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

I haven't really figured out how to test this, so let me know if you have ideas. This test kinda works, but it's indirect—there's no invokebuiltin_delegate directly in the code here, but Kernel#clone gets compiled along with test and that does have the instruction.

I couldn't get a good test for invokebuiltin. All of the common ones I could find (Hash.new, Dir.glob) use default args which we don't support yet. I was testing locally with GC.start, but there's not much to assert on for that one.

Copy link
Member

Choose a reason for hiding this comment

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

I suggest testing the no args code path of Integer#times for now:

== disasm: #<ISeq:times@<internal:numeric>:250 (250,2)-(261,5)> INLINE_BLOCK
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] i@0
0000 putnil                                                           ( 252)[LiCa]
0001 defined                                yield, false, true
0005 branchif                               11
0007 opt_invokebuiltin_delegate             <builtin!_bi253/0>, 0     ( 253)[Li]
0010 leave                                  [Re]
0011 putobject_INT2FIX_0_                                             ( 255)[Li]
0012 setlocal_WC_0                          i@0
<snip>

You're running into a limitation of assert_compiles() here in that there is no way to test that an indirectly compiled method contains a certain YARV instruction. Probably still fine for now since the builtin instructions are the only ones we can't generate in normal user mode ruby.

Copy link
Member

Choose a reason for hiding this comment

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

Related, you should be able to test the HIR directly for kernel methods in hir.rs, since it lets you target the method to assert HIR for more precisely:

    /// Get the ISeq of a specified method
    pub fn get_method_iseq(name: &str) -> *const rb_iseq_t {
        let wrapped_iseq = eval(&format!("RubyVM::InstructionSequence.of(method(:{}))", name));
        unsafe { rb_iseqw_to_iseq(wrapped_iseq) }
    }

Just call assert_method_hir_with_opcode() with say, "loop", which should find Kernel#loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice. Thank you for that. I'll add some tests there.

@@ -632,6 +635,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
}
Ok(())
}
Insn::InvokeBuiltin { bf, args, .. } => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

An example of what this looks like for GC.start

fn start:
bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject, v3:BasicObject, v4:BasicObject):
  v6:FalseClassExact = Const Value(false)
  v8:BasicObject = InvokeBuiltin gc_start_internal, v0, v1, v2, v3, v6
  Return v8

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.

2 participants
0