Skip to content
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

__callee__ behaves differently from MRI #2305

Closed
guai opened this issue Dec 11, 2014 · 12 comments · Fixed by #7702
Closed

__callee__ behaves differently from MRI #2305

guai opened this issue Dec 11, 2014 · 12 comments · Fixed by #7702

Comments

@guai
Copy link

guai commented Dec 11, 2014

jruby 1.7.13 and 1.7.16.1 both in --2.0 mode

class Foo
    def foo
        {__method__ => __callee__}
    end
    alias bar foo
end
raise 'fail' if Foo.new.foo == Foo.new.bar

They must not be equal.

@BanzaiMan
Copy link
Member

Don't use 1.7.x in 2.0 mode. It's not supported.

But it still fails on the master branch.

@headius
Copy link
Member

headius commented Dec 12, 2014

Can I ask why you need this? In JRuby, the "callee" name used in the caller code is not tracked anywhere; the alias is just a mechanism for finding the :foo method under the :bar name. In order to support using the alias name for callee instead of the real name of the method, we would have to start storing -- on every call -- the actual name used for that call, adding significant overhead to the system and slowing down every method, regardless of whether you use callee or not.

@guai
Copy link
Author

guai commented Dec 30, 2014

Metaprogramming of course. Something like this:

class Wrapper
    def initialize target
        @target = target
    end
    def __value
        @target[__callee__]
    end
end

class FoobarWrapper < Wrapper
    alias foobar __value
end

puts FoobarWrapper.new(foobar: 1).foobar

Wrap java collection for example. I thought it will be faster then method_missing. But now after I saw sources I'm not sure...

@pbl-pw
Copy link

pbl-pw commented Sep 23, 2016

Same problem. Metaprogramming too.
*jruby 9.1.5.0 (2.3.1) 2016-09-23 fffffff Java HotSpot(TM) Client VM 25.101-b13 on 1.8.0_101-b13 +jit [mswin32-x86]

@headius
Copy link
Member

headius commented Sep 23, 2016

There might be some way to support this in IR (if we can pass the aliased name through into all bodies as well as the actual method name) but this is a really troublesome feature to support. We don't save the actual called name, and I'm not sure I agree that we should.

@guai Your example, if it worked on JRuby, would be almost unusably slow. __method__ and __callee__ are implemented using caller logic, which is very expensive. Neither of them are particularly fast on MRI either, but they're orders of magnitude slower than that on JRuby.

@headius
Copy link
Member

headius commented Sep 23, 2016

One possible way to implement this: when a method has been aliased, generate a new jitted body that has both the alias name and the original name in its signature. We can mine that out of caller backtrace then. It still will be really slow due to the need to generate a backtrace.

@headius
Copy link
Member

headius commented Sep 23, 2016

Another possible way to implement this: pass the original method (AliasMethod in this case) through into the wrapped method so it always knows what method it was called as. It can report callee properly then without too much tricker. This approach will be a bit more complicated since we'd need the method to be available for evals and blocks inside a given method too.

@headius
Copy link
Member

headius commented Sep 23, 2016

cc @subbuss @enebo for more ideas.

@enebo
Copy link
Member

enebo commented Sep 24, 2016

@headius I had worked on trying to pass the callsite through the call locally on a branch in one effort to make profiler easier. This would then be a trivial feature. Perhaps this is one more reason to pass callsite through call paths.

@headius
Copy link
Member

headius commented Sep 26, 2016

@enebo That would indeed solve this case. And since CallSite is a pretty good place to store static call info, we might be able to use it for other purposes too.

@headius
Copy link
Member

headius commented Jul 9, 2020

This is still an outstanding issue with no easy fix. The problem is largely that when we have an alias, we do not pass the called name along at all:

public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule klazz, String unused) {

Because of this, it doesn't even make it as far as the interpreter/bytecode or into the frame.

Fixing this would at least require we pass through the called name along with the "super" name (the actual name of the target method) or else acquire the super name some other way that allows us to pass the called name through.

headius added a commit to headius/jruby that referenced this issue Mar 2, 2023
JRuby only pushes the actual method name on the stack, since up
until recently it was the only name needed. However the when the
`__callee__` method was added, the aliased name also became
necessary.

Passing another argument along the dynamic call stack would take
considerable work, which might happen along with making the call
path flexible enough to handle allocation-free keyword arguments.
For now, however this hack may be good enough.

The logic here encodes the aliased name and the original name in
the same string passed along the call stack. Since this name is
used in very few places – making super calls, setting up the
interpreter backtrace, and the `__method__` and `__callee__`
methods – only those locations need to be modified to receive the
encoded string.

The format of the encoded string uses a leading null character,
followed by the new name, another null character, and the old
name. This allows an easy check for encoding using charAt(0), and
the individual names can then be pulled out easily. The null
character is also very unlikely to show up in any real method
identifier (and may actually be illegal).

Because the two name are encoded into a single string, it is
necessary to allocate a new string to extract either. However, the
places where these names are used already have significant
overhead, this allocation may not be a big concern.

This change is also hopefully temporary until we can better thread
this data through the dynamic call stack.

Fixes jruby#2305
headius added a commit to headius/jruby that referenced this issue Mar 2, 2023
JRuby only pushes the actual method name on the stack, since up
until recently it was the only name needed. However the when the
`__callee__` method was added, the aliased name also became
necessary.

Passing another argument along the dynamic call stack would take
considerable work, which might happen along with making the call
path flexible enough to handle allocation-free keyword arguments.
For now, however this hack may be good enough.

The logic here encodes the aliased name and the original name in
the same string passed along the call stack. Since this name is
used in very few places – making super calls, setting up the
interpreter backtrace, and the `__method__` and `__callee__`
methods – only those locations need to be modified to receive the
encoded string.

The format of the encoded string uses a leading null character,
followed by the new name, another null character, and the old
name. This allows an easy check for encoding using charAt(0), and
the individual names can then be pulled out easily. The null
character is also very unlikely to show up in any real method
identifier (and may actually be illegal).

Because the two name are encoded into a single string, it is
necessary to allocate a new string to extract either. However, the
places where these names are used already have significant
overhead, this allocation may not be a big concern.

This change is also hopefully temporary until we can better thread
this data through the dynamic call stack.

Fixes jruby#2305
headius added a commit to headius/jruby that referenced this issue Mar 2, 2023
JRuby only pushes the actual method name on the stack, since up
until recently it was the only name needed. However the when the
`__callee__` method was added, the aliased name also became
necessary.

Passing another argument along the dynamic call stack would take
considerable work, which might happen along with making the call
path flexible enough to handle allocation-free keyword arguments.
For now, however this hack may be good enough.

The logic here encodes the aliased name and the original name in
the same string passed along the call stack. Since this name is
used in very few places – making super calls, setting up the
interpreter backtrace, and the `__method__` and `__callee__`
methods – only those locations need to be modified to receive the
encoded string.

The format of the encoded string uses a leading null character,
followed by the new name, another null character, and the old
name. This allows an easy check for encoding using charAt(0), and
the individual names can then be pulled out easily. The null
character is also very unlikely to show up in any real method
identifier (and may actually be illegal).

Because the two name are encoded into a single string, it is
necessary to allocate a new string to extract either. However, the
places where these names are used already have significant
overhead, this allocation may not be a big concern.

This change is also hopefully temporary until we can better thread
this data through the dynamic call stack.

Fixes jruby#2305
headius added a commit to headius/jruby that referenced this issue Mar 2, 2023
JRuby only pushes the actual method name on the stack, since up
until recently it was the only name needed. However the when the
`__callee__` method was added, the aliased name also became
necessary.

Passing another argument along the dynamic call stack would take
considerable work, which might happen along with making the call
path flexible enough to handle allocation-free keyword arguments.
For now, however this hack may be good enough.

The logic here encodes the aliased name and the original name in
the same string passed along the call stack. Since this name is
used in very few places – making super calls, setting up the
interpreter backtrace, and the `__method__` and `__callee__`
methods – only those locations need to be modified to receive the
encoded string.

The format of the encoded string uses a leading null character,
followed by the new name, another null character, and the old
name. This allows an easy check for encoding using charAt(0), and
the individual names can then be pulled out easily. The null
character is also very unlikely to show up in any real method
identifier (and may actually be illegal).

Because the two name are encoded into a single string, it is
necessary to allocate a new string to extract either. However, the
places where these names are used already have significant
overhead, this allocation may not be a big concern.

This change is also hopefully temporary until we can better thread
this data through the dynamic call stack.

Fixes jruby#2305
@headius
Copy link
Member

headius commented Mar 2, 2023

I have pushed a short-term fix in #7702 that encodes both the aliased name and original name in the name passed through the call stack. Anywhere that consumes either the "super name" or the "callee name" must decode the composite name in oder to get those others out. It appears to be working properly, but will likely need more bake time before release.

@headius headius added this to the JRuby 9.4.2.0 milestone Mar 2, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants