Bug #21362
openNamespace: Inline method caches poisoned with builtins
Description
File.write("/tmp/ntest.rb", <<~'RUBY')
class Integer
def succ = self + 2
end
module Test
def self.run = 10.times.to_a
end
RUBY
module Test
def self.run = 10.times.to_a
end
ns = Namespace.new
ns.require("/tmp/ntest.rb")
p namespaced: ns::Test.run
p main: Test.run
RUBY_NAMESPACE=1 ruby test_namespace_succ.rb
ruby: warning: Namespace is experimental, and the behavior may change in the future!
See doc/namespace.md for known issues, etc.
{namespaced: [0, 2, 4, 6, 8]}
{main: [0, 2, 4, 6, 8]}
What's happening here is that we have a number of "builtin" Ruby files which are loaded during the VM boot. The instruction sequences and method definitions from these files end up as part of the "root" namespace. However this iseq can still include inline caches, and in this example we see the cache poisoned by being run in a namespace with succ redefined. I was surprised by this, I expected it to call using the "root" namespace.
It's definitely a strange edge-case to redefine succ
, but the namespace semantics here are important because how this is defined impacts how useful builtins can be:
-
If builtins inherit their namespace from the caller, then they cannot use inline caches (or inline caches must always check for namespace validity) and also cannot be JIT compiled (unless the JIT also checks for namespace validity, and given the complexity of rb_current_namespace, it would be very undesirable). This is very bad as converting C code to builtin Ruby has been important for getting better performance.
-
If builtins make calls using the "root" namespace, inline caching and the JIT should work (actually there may be advantages to both since the namespace is mostly immutable). However this will limit what we are able to move from C to Ruby, as it wouldn't be possible to have the same semantics as C methods (where the caller's namespace is used).