-
Notifications
You must be signed in to change notification settings - Fork 5.3k
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
Skip ancestry check and allow method caching for protected FCALLs #5643
Conversation
static inline bool | ||
vm_call_cacheable(const struct rb_callinfo *ci, const struct rb_callcache *cc) | ||
{ | ||
return (vm_ci_flag(ci) & VM_CALL_FCALL) || | ||
METHOD_ENTRY_VISI(vm_cc_cme(cc)) != METHOD_VISI_PROTECTED; | ||
} |
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.
Imo you should add some comments here to explain what this method checks. What does it mean for a call to be cacheable? Why is that so? Makes the code more approachable for people not familiar with the Ruby codebase. Not trying to be negative, it's just Ruby is a big language and this is a big codebase.
yjit_codegen.c
Outdated
@@ -4137,7 +4137,9 @@ gen_send_general(jitstate_t *jit, ctx_t *ctx, struct rb_call_data *cd, rb_iseq_t | |||
} | |||
break; | |||
case METHOD_VISI_PROTECTED: | |||
jit_protected_callee_ancestry_guard(jit, cb, cme, side_exit); | |||
if (!(vm_ci_flag(ci) & (VM_CALL_OPT_SEND | VM_CALL_FCALL))) { |
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.
Could also comment here as to what flags are being checked and why.
@noahgibbs if this PR is merged, we'll want to forward port this change into RustYJIT. Though we'd have to rebase on upstream CRuby first 🤔 |
Actually @jhawthorn, would you want to implement this change in RustYJIT? :) Could give you a quick tour of the codebase if you have time next week. |
# For 89d47d11e37efa0aaf767cafbd69e15bb0a42707, GH-5643
# The PR changes behavior for protected refinement methods.
#
# In a world without refinements, I think this is sound because FCALL
# means we did method lookup on `self` and the ancestry check
# and method lookup psudo algo both scan the same ancestors list in the same way.
# Finding the method implies ancestry.
class A
end
module RefProt
refine(A) { protected def foo = :refined }
end
class A
using RefProt
def foo
:normal
end
def call_foo
foo
end
end
A.new.call_foo Sorry. |
Thanks for the test Alan. I was worried there would be an edge case in this. I think we could easily test for a refinement, but that might make the added complexity not worth it. I'll re-evaluate (and see how often real code might hit this). If I do decide to move forward with this, of course I'm happy to implement in RustYJIT 🙂 |
89d47d1
to
7809e37
Compare
With the protected/refinement behaviour clarified in https://bugs.ruby-lang.org/issues/18806, and a fix pending in #5966 I think this can move forward. I've also added a commit applying the optimization to the Rust YJIT codegen. |
If we are making an FCALL, we know we are calling a method on self. This is the same check made for private method visibility, so it should also guarantee we can call a protected method.
7809e37
to
8983d1f
Compare
If we are making an FCALL, we know we are calling a method on self. This is the same check made for private method visibility, so I think it should also guarantee we can call the protected method. (Hopefully I haven't missed some case the check was needed)
For example in the following
foo
we should not have to check the ancestry of thebar
receiver, as we know it is self:This allows us to skip the ancestry check on the receiver (which was recently made faster, but still not as fast as skipping the check entirely 😁). And allows setting the fastpath to the inline method cache in the interpreter.
I don't think this pattern is particularly common, and probably most often found in code that should be using
private
, but there are some proper uses (when a method is sometimes called via fcall and sometimes on a different receiver). Fortunately I think this optimization is essentially free, as we were already checking flags everywhere this PR changes.Benchmark
Interpreter:
YJIT: