-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
base: master
Are you sure you want to change the base?
ZJIT: Support invokebuiltin opcodes #13632
Conversation
* `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).
@@ -76,6 +76,14 @@ def test3 = baz(4, 1) | |||
} | |||
end | |||
|
|||
def test_invokebuiltin_delegate |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, .. } => { |
There was a problem hiding this comment.
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
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 forleave
, 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).