Skip to content

YJIT: Fix String#setbyte crashing for converted arguments #10575

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 2 commits into from
Apr 22, 2024

Conversation

XrXr
Copy link
Member

@XrXr XrXr commented Apr 19, 2024

Previously, passing objects that respond to #to_int to String#setbyte
resulted in a crash when compiled by YJIT. This was due to the lazily
pushed frame from rb_yjit_lazy_push_frame() lingering and not being
popped by an exception as expected.

The fix is to ensure that ec->cfp is restored to before the lazy frame
push in case the method call for conversion succeeds. Right now, this is
only for conversion to integers.

Found running ruby/spec.

@matzbot matzbot requested a review from a team April 19, 2024 00:13
@k0kubun
Copy link
Member

k0kubun commented Apr 19, 2024

This was due to the lazily pushed frame from rb_yjit_lazy_push_frame() lingering and not being popped by an exception as expected.

The added test doesn't raise an exception, does it?

In the original PR #10080, the intention was to leave the un-popped frame during the C call and let JIT code resurrect the correct frame using the CFP register. But I guess ec->cfp could be used again before JIT code fixes it on leave?

A potentially more efficient way to fix this might be to just pop a frame after a lazy frame push. I used that trick in #10165 (see WITH_YJIT_LAZY_FRAME) while I ended up not merging it. Popping it adds overhead, but if the slow path is never called on our benchmarks, maybe we can do that for setbyte? Then we won't have the overhead of guard_two_fixnums.

@maximecb
Copy link
Contributor

Does this mean we need to be more careful about lazy push frame usage?

pop a frame after a lazy frame push.

It would be nice to have a more efficient solution. How would this work? Would only the C code have to care about this?

@XrXr
Copy link
Member Author

XrXr commented Apr 19, 2024

But I guess ec->cfp could be used again before JIT code fixes it on leave?

Yes. YJIT ends up looking it at in a branch stub 😅

How would this work? Would only the C code have to care about this?

Yes. Basically we need to pop the lazy frame after the function finishes making a call so there is no lingering frame whether the callee raises or not. I'll try poking at that later today.

Previously, passing objects that respond to #to_int to `String#setbyte`
resulted in a crash when compiled by YJIT. This was due to the lazily
pushed frame from rb_yjit_lazy_push_frame() lingering and not being
popped by an exception as expected.

The fix is to ensure that `ec->cfp` is restored to before the lazy frame
push in case the method call for conversion succeeds. Right now, this is
only for conversion to integers.

Found running `ruby/spec`.
@XrXr XrXr force-pushed the setbyte-bad-lazy branch from 4f9d531 to aa1de5a Compare April 19, 2024 19:28
@XrXr
Copy link
Member Author

XrXr commented Apr 19, 2024

Okay, restoring the CFP for the non-rising path seems to work (at least locally). Moving forward, we just need to be careful when lazily pushing frames in the C side.

object.c Outdated
@@ -3236,15 +3236,22 @@ ALWAYS_INLINE(static VALUE rb_to_integer_with_id_exception(VALUE val, const char
static inline VALUE
rb_to_integer_with_id_exception(VALUE val, const char *method, ID mid, int raise)
{
// when !raise, we need to pop the lazily pushed frame.
Copy link
Member

Choose a reason for hiding this comment

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

It seems to be popped on L3254 when raise too. But is the case unreachable because try_to_int raises?

Copy link
Member Author

Choose a reason for hiding this comment

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

I edited the comment to clarify that it's not really related to the parameter, but whether an exception is raised (we need to pop if the function returns normally)

Copy link
Member

@k0kubun k0kubun left a comment

Choose a reason for hiding this comment

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

Thank you!

We just need to make sure `ec->cfp` is always preserved and this can
convert without rising when `raise` is true.
@XrXr XrXr enabled auto-merge (squash) April 22, 2024 18:01
@XrXr XrXr merged commit aeb08bc into ruby:master Apr 22, 2024
100 checks passed
@XrXr XrXr deleted the setbyte-bad-lazy branch April 22, 2024 18:33
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.

3 participants