551 Commits

Author SHA1 Message Date
alpaca-tc
c8ddc0a843 Optimize callcache invalidation for refinements
Fixes [Bug #21201]

This change addresses a performance regression where defining methods
inside `refine` blocks caused severe slowdowns. The issue was due to
`rb_clear_all_refinement_method_cache()` triggering a full object
space scan via `rb_objspace_each_objects` to find and invalidate
affected callcaches, which is very inefficient.

To fix this, I introduce `vm->cc_refinement_table` to track
callcaches related to refinements. This allows us to invalidate
only the necessary callcaches without scanning the entire heap,
resulting in significant performance improvement.
2025-06-09 12:33:35 +09:00
git
aa00a2d07b * remove trailing spaces. [ci skip] 2025-05-26 02:44:58 +00:00
Nobuyoshi Nakada
aad9fa2853
Use RB_VM_LOCKING 2025-05-25 15:22:43 +09:00
Satoshi Tagomori
382645d440 namespace on read 2025-05-11 23:32:50 +09:00
Alan Wu
dd0e0eb4ac Delete always true assert [ci skip] 2025-05-02 21:06:11 +09:00
Jean Boussier
c0417bd094 Use set_table to track const caches
Now that we have a `set_table` implementation, we can
use it to track const caches and save some memory.

We could even save some more memory if `numtable` didn't
store a copy of the `hash` and instead recomputed it every
time, but this is a quick win.
2025-04-26 12:10:32 +02:00
Nobuyoshi Nakada
c218862d3c
Fix style [ci skip] 2025-04-19 22:02:10 +09:00
John Hawthorn
bfe6068417 Use atomic for method reference count [Bug #20934]
This changes reference_count on rb_method_definition_struct into an
atomic.

Ractors can create additional references as part of `bind_call` or
(presumably) similar. Because this can be done inside Ractors, we should
use a lock or atomics so that we don't race and avoid incrementing.

Co-authored-by: wanabe <s.wanabe@gmail.com>
2025-03-20 13:09:40 -07:00
Nobuyoshi Nakada
4a67ef09cc
[Feature #21116] Extract RJIT as a third-party gem 2025-02-13 18:01:03 +09:00
Fabio Sangiovanni
f0dc9dcdc7 rb_alias: improve "undefined method" error message by invoking
`rb_print_undef` with `target_klass` as argument.
2025-01-31 00:20:47 +09:00
Alan Wu
ce849d565b
ruby2_keywords warnings: Quote non-UTF8 method names fully
It used to quote only part of the method name because NUL byte in
the method terminates the C string:

```
(irb)> "abcdef".encode("UTF-16LE").bytes
=> [97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]
```

```
expected: /abcdef/
actual: warning: Skipping set of ruby2_keywords flag for a (method not defined in Ruby)\n".
```
2024-12-19 10:32:14 -05:00
Peter Zhu
a58675386c Prefix asan_poison_object with rb 2024-12-19 09:14:34 -05:00
John Hawthorn
f1dda5ed01 Warn when redefining __id__ as well as object_id
[Feature #20912]
2024-11-29 20:41:00 -08:00
Matt Valentine-House
551be8219e Place all non-default GC API behind USE_SHARED_GC
So that it doesn't get included in the generated binaries for builds
that don't support loading shared GC modules

Co-Authored-By: Peter Zhu <peter@peterzhu.ca>
2024-11-25 13:05:23 +00:00
Peter Zhu
56ecc243e2 [Bug #20868] Fix Method#hash to not change after compaction
The hash value of a Method must remain constant after a compaction, otherwise
it may not work as the key in a hash table.

For example:

    def a; end

    # Need this method here because otherwise the iseq may be on the C stack
    # which would get pinned and not move during compaction
    def get_hash
      method(:a).hash
    end

    puts get_hash # => 2993401401091578131

    GC.verify_compaction_references(expand_heap: true, toward: :empty)

    puts get_hash # => -2162775864511574135
2024-11-06 10:34:20 -05:00
Takashi Kokubun
478e0fc710
YJIT: Replace Array#each only when YJIT is enabled (#11955)
* YJIT: Replace Array#each only when YJIT is enabled

* Add comments about BUILTIN_ATTR_C_TRACE

* Make Ruby Array#each available with --yjit as well

* Fix all paths that expect a C location

* Use method_basic_definition_p to detect patches

* Copy a comment about C_TRACE flag to compilers

* Rephrase a comment about add_yjit_hook

* Give METHOD_ENTRY_BASIC flag to Array#each

* Add --yjit-c-builtin option

* Allow inconsistent source_location in test-spec

* Refactor a check of BUILTIN_ATTR_C_TRACE

* Set METHOD_ENTRY_BASIC without touching vm->running
2024-11-04 11:14:28 -05:00
Koichi Sasada
783dde2159 alias should not set defined_class for Modules
`me->defined_class` should be 0 for method entries of
Modules.

This patch checks this condition
and fix https://github.com/ruby/ruby/pull/11965#issuecomment-2448291790
2024-11-01 11:50:00 +09:00
Nobuyoshi Nakada
abfefd8e0c
Define VM_ASSERT_TYPE macros 2024-10-31 22:12:16 +09:00
Nobuyoshi Nakada
eb1e1d99b7
Detail the failing assertion [ci skip] 2024-10-30 14:48:21 +09:00
Nobuyoshi Nakada
b4eb7e2281
Refine assertion failure message 2024-10-11 16:52:01 +09:00
Thomas Marshall
5792bd7149 Emit warning for other method redefinition types
This commit ensures warnings about `object_id` and `__send__` method
redefinitions are emitted for other method types such as aliases, procs,
and attr readers—anything except C functions.
2024-10-10 12:53:11 +09:00
Koichi Sasada
a838f980f5 me->defined_class should be T_CLASS/T_ICLASS
`me->defined_class` will be used for ancestor searching
so that it should be T_CLASS or T_ICLASS.

This patch will resoleve https://github.com/ruby/ruby/pull/11715
2024-10-10 06:31:57 +09:00
Peter Zhu
b10500b72b Assume VM is locked in rb_vm_ci_free
The GC always locks the VM, so we don't need to lock it in rb_vm_ci_free.
2024-09-12 10:15:25 -04:00
Aaron Patterson
cdf33ed5f3 Optimized forwarding callers and callees
This patch optimizes forwarding callers and callees. It only optimizes methods that only take `...` as their parameter, and then pass `...` to other calls.

Calls it optimizes look like this:

```ruby
def bar(a) = a
def foo(...) = bar(...) # optimized
foo(123)
```

```ruby
def bar(a) = a
def foo(...) = bar(1, 2, ...) # optimized
foo(123)
```

```ruby
def bar(*a) = a

def foo(...)
  list = [1, 2]
  bar(*list, ...) # optimized
end
foo(123)
```

All variants of the above but using `super` are also optimized, including a bare super like this:

```ruby
def foo(...)
  super
end
```

This patch eliminates intermediate allocations made when calling methods that accept `...`.
We can observe allocation elimination like this:

```ruby
def m
  x = GC.stat(:total_allocated_objects)
  yield
  GC.stat(:total_allocated_objects) - x
end

def bar(a) = a
def foo(...) = bar(...)

def test
  m { foo(123) }
end

test
p test # allocates 1 object on master, but 0 objects with this patch
```

```ruby
def bar(a, b:) = a + b
def foo(...) = bar(...)

def test
  m { foo(1, b: 2) }
end

test
p test # allocates 2 objects on master, but 0 objects with this patch
```

How does it work?
-----------------

This patch works by using a dynamic stack size when passing forwarded parameters to callees.
The caller's info object (known as the "CI") contains the stack size of the
parameters, so we pass the CI object itself as a parameter to the callee.
When forwarding parameters, the forwarding ISeq uses the caller's CI to determine how much stack to copy, then copies the caller's stack before calling the callee.
The CI at the forwarded call site is adjusted using information from the caller's CI.

I think this description is kind of confusing, so let's walk through an example with code.

```ruby
def delegatee(a, b) = a + b

def delegator(...)
  delegatee(...)  # CI2 (FORWARDING)
end

def caller
  delegator(1, 2) # CI1 (argc: 2)
end
```

Before we call the delegator method, the stack looks like this:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
              4|   #                                   |
              5|   delegatee(...)  # CI2 (FORWARDING)  |
              6| end                                   |
              7|                                       |
              8| def caller                            |
          ->  9|   delegator(1, 2) # CI1 (argc: 2)     |
             10| end                                   |
```

The ISeq for `delegator` is tagged as "forwardable", so when `caller` calls in
to `delegator`, it writes `CI1` on to the stack as a local variable for the
`delegator` method.  The `delegator` method has a special local called `...`
that holds the caller's CI object.

Here is the ISeq disasm fo `delegator`:

```
== disasm: #<ISeq:delegator@-e:1 (1,0)-(1,39)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] "..."@0
0000 putself                                                          (   1)[LiCa]
0001 getlocal_WC_0                          "..."@0
0003 send                                   <calldata!mid:delegatee, argc:0, FCALL|FORWARDING>, nil
0006 leave                                  [Re]
```

The local called `...` will contain the caller's CI: CI1.

Here is the stack when we enter `delegator`:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
           -> 4|   #                                   | CI1 (argc: 2)
              5|   delegatee(...)  # CI2 (FORWARDING)  | cref_or_me
              6| end                                   | specval
              7|                                       | type
              8| def caller                            |
              9|   delegator(1, 2) # CI1 (argc: 2)     |
             10| end                                   |
```

The CI at `delegatee` on line 5 is tagged as "FORWARDING", so it knows to
memcopy the caller's stack before calling `delegatee`.  In this case, it will
memcopy self, 1, and 2 to the stack before calling `delegatee`.  It knows how much
memory to copy from the caller because `CI1` contains stack size information
(argc: 2).

Before executing the `send` instruction, we push `...` on the stack.  The
`send` instruction pops `...`, and because it is tagged with `FORWARDING`, it
knows to memcopy (using the information in the CI it just popped):

```
== disasm: #<ISeq:delegator@-e:1 (1,0)-(1,39)>
local table (size: 1, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] "..."@0
0000 putself                                                          (   1)[LiCa]
0001 getlocal_WC_0                          "..."@0
0003 send                                   <calldata!mid:delegatee, argc:0, FCALL|FORWARDING>, nil
0006 leave                                  [Re]
```

Instruction 001 puts the caller's CI on the stack.  `send` is tagged with
FORWARDING, so it reads the CI and _copies_ the callers stack to this stack:

```
Executing Line | Code                                  | Stack
---------------+---------------------------------------+--------
              1| def delegatee(a, b) = a + b           | self
              2|                                       | 1
              3| def delegator(...)                    | 2
              4|   #                                   | CI1 (argc: 2)
           -> 5|   delegatee(...)  # CI2 (FORWARDING)  | cref_or_me
              6| end                                   | specval
              7|                                       | type
              8| def caller                            | self
              9|   delegator(1, 2) # CI1 (argc: 2)     | 1
             10| end                                   | 2
```

The "FORWARDING" call site combines information from CI1 with CI2 in order
to support passing other values in addition to the `...` value, as well as
perfectly forward splat args, kwargs, etc.

Since we're able to copy the stack from `caller` in to `delegator`'s stack, we
can avoid allocating objects.

I want to do this to eliminate object allocations for delegate methods.
My long term goal is to implement `Class#new` in Ruby and it uses `...`.

I was able to implement `Class#new` in Ruby
[here](https://github.com/ruby/ruby/pull/9289).
If we adopt the technique in this patch, then we can optimize allocating
objects that take keyword parameters for `initialize`.

For example, this code will allocate 2 objects: one for `SomeObject`, and one
for the kwargs:

```ruby
SomeObject.new(foo: 1)
```

If we combine this technique, plus implement `Class#new` in Ruby, then we can
reduce allocations for this common operation.

Co-Authored-By: John Hawthorn <john@hawthorn.email>
Co-Authored-By: Alan Wu <XrXr@users.noreply.github.com>
2024-06-18 09:28:25 -07:00
Jeremy Evans
ad29527920 Fix Module#define_method to change visibility when passed existing method body
Fixes [Bug #19749]
2024-06-06 15:02:04 -07:00
Aaron Patterson
0434dfb76b We don't need to check if the ci is markable anymore
It doesn't matter if CI's are stack allocated or not.
2024-04-24 15:09:06 -07:00
Jean Boussier
fff2284871 Consolitate redefined the method warning
Currently redefining a method doesn't emit one but two warnings.
One at the location of the new method, and one at the location of
the old method.

I believe this is not ideal because when collecting warnings via
a custom `Warning.warn`, it has to be pieced together. It's even
more tricky because the second part may or may not be emitted
depending on whether the original method has an associated ISeq.

I think it's much better to emit a single warning with all the
information in one go.
2024-04-23 08:59:23 +02:00
Aaron Patterson
147ca9585e Implement equality for CI comparison when CC searching
When we're searching for CCs, compare the argc and flags for CI rather
than comparing pointers.  This means we don't need to store a reference
to the CI, and it also naturally "de-duplicates" CC objects.

We can observe the effect with the following code:

```ruby
require "objspace"

hash = {}

p ObjectSpace.memsize_of(Hash)

eval ("a".."zzz").map { |key|
  "hash.merge(:#{key} => 1)"
}.join("; ")

p ObjectSpace.memsize_of(Hash)
```

On master:

```
$ ruby -v test.rb
ruby 3.4.0dev (2024-04-15T16:21:41Z master d019b3baec) [arm64-darwin23]
test.rb:3: warning: assigned but unused variable - hash
3424
527736
```

On this branch:

```
$ make runruby
compiling vm.c
linking miniruby
builtin_binary.inc updated
compiling builtin.c
linking static-library libruby.3.4-static.a
ln -sf ../../rbconfig.rb .ext/arm64-darwin23/rbconfig.rb
linking ruby
ld: warning: ignoring duplicate libraries: '-ldl', '-lobjc', '-lpthread'
RUBY_ON_BUG='gdb -x ./.gdbinit -p' ./miniruby -I./lib -I. -I.ext/common  ./tool/runruby.rb --extout=.ext  -- --disable-gems  ./test.rb
2240
2368
```

Co-authored-by: John Hawthorn <jhawthorn@github.com>
2024-04-18 09:06:33 -07:00
Nobuyoshi Nakada
58918788ab [Bug #20342] Consider wrapped load in main methods 2024-04-05 01:33:08 +09:00
John Hawthorn
18ee7c9a10 Clear all refined CCs on reopening refinement mod
In cfd7729ce7a31c8b6ec5dd0e99c67b2932de4732 we started using inline
caches for refinements. However, we weren't clearing inline caches when
defined on a reopened refinement module.

Fixes [Bug #20246]
2024-03-07 10:06:48 -08:00
Jean Boussier
b4a69351ec Move FL_SINGLETON to FL_USER1
This frees FL_USER0 on both T_MODULE and T_CLASS.

Note: prior to this, FL_SINGLETON was never set on T_MODULE,
so checking for `FL_SINGLETON` without first checking that
`FL_TYPE` was `T_CLASS` was valid. That's no longer the case.
2024-03-06 13:11:41 -05:00
Peter Zhu
330830dd1a Add IMEMO_NEW
Rather than exposing that an imemo has a flag and four fields, this
changes the implementation to only expose one field (the klass) and
fills the rest with 0. The type will have to fill in the values themselves.
2024-02-21 11:33:05 -05:00
John Hawthorn
1c97abaaba De-dup identical callinfo objects
Previously every call to vm_ci_new (when the CI was not packable) would
result in a different callinfo being returned this meant that every
kwarg callsite had its own CI.

When calling, different CIs result in different CCs. These CIs and CCs
both end up persisted on the T_CLASS inside cc_tbl. So in an eval loop
this resulted in a memory leak of both types of object. This also likely
resulted in extra memory used, and extra time searching, in non-eval
cases.

For simplicity in this commit I always allocate a CI object inside
rb_vm_ci_lookup, but ideally we would lazily allocate it only when
needed. I hope to do that as a follow up in the future.
2024-02-20 18:55:00 -08:00
Yusuke Endoh
25d74b9527 Do not include a backtick in error messages and backtraces
[Feature #16495]
2024-02-15 18:42:31 +09:00
Peter Zhu
ae8db4b65a Remove unused function rb_cc_table_free 2024-02-14 15:52:15 -05:00
Nobuyoshi Nakada
84d8dbe7a5 Enable redefinition check for rbinc methods 2024-02-12 11:51:06 -08:00
Takashi Kokubun
e4d3e652ff
YJIT: Prefer an overloaded cme if available (#9913)
YJIT: Prefer an overloaded cme if applicable
2024-02-12 12:56:44 -05:00
Nobuyoshi Nakada
f3cc1f9a70
Show actual imemo type when unexpected type 2024-02-08 18:08:42 +09:00
Alan Wu
fcabe2df39
Remove written-but-never-read me->def.body.refined.owner
This also removes aliasing rule violations; the anonymous structs were
distinct types from `rb_method_refined_t`.
2023-11-29 01:41:40 +00:00
Alan Wu
cd4207869f Fix cache incoherency for ME resolved through VM_METHOD_TYPE_REFINED
Previously, we didn't invalidate the method entry wrapped by
VM_METHOD_TYPE_REFINED method entries which could cause calls to
land in the wrong method like it did in the included test.

Do the invalidation, and adjust rb_method_entry_clone() to accommodate
this new invalidation vector.

Fix: cfd7729ce7a31c8b6ec5dd0e99c67b2932de4732
See-also: e201b81f79828c30500947fe8c8ea3c515e3d112
2023-11-28 13:03:04 -05:00
Alan Wu
24fe22a5da
Fix ordering for auto compaction in get_overloaded_cme()
Found through GC.stress + GC.auto_compact crashes in GH-8932.
Previously, the compaction run within `rb_method_entry_alloc()` could
move the `def->body.iseq.cref` and `iseqptr` set up before the call and
leave the `def` pointing to moved addresses. Nothing was marking `def`
during that GC run.

Low probability reproducer:

    GC.stress = true
    GC.auto_compact = true
    arr = []
    alloc = 1000.times.map { [] }
    alloc = nil
    a = arr.first
    GC.start
2023-11-17 17:57:25 -05:00
Takashi Kokubun
6beb09c2c9
YJIT: Add RubyVM::YJIT.enable (#8705) 2023-10-19 10:54:35 -07:00
Adam Hess
8b236e0c66 [Bug #19896]
fix memory leak in vm_method

This introduces a unified reference_count to clarify who is referencing a method.
This also allows us to treat the refinement method as the def owner since it counts itself as a reference

Co-authored-by: Peter Zhu <peter@peterzhu.ca>
2023-09-22 09:44:58 -04:00
Peter Zhu
96c5a4be7b Fix memory leak in complemented method entries
[Bug #19894]

When a copy of a complemented method entry is created, there are two
issues:

1. IMEMO_FL_USER3 is not copied, so the complemented status is not
   copied over.
2. In rb_method_entry_clone we increment both alias_count and
   complemented_count. However, when we free the method entry in
   rb_method_definition_release, we only decrement one of the two
   counters, resulting in the rb_method_definition_t being leaked.

Co-authored-by: Adam Hess <adamhess1991@gmail.com>
2023-09-20 10:19:24 -04:00
Peter Zhu
38e98cbb6a Fix typo in "refinements" 2023-09-19 19:34:50 -04:00
Peter Zhu
901d0b4125 Remove dead function Init_Method
Init_Method no longer has any code, so we can remove it.
2023-09-19 14:25:01 -04:00
Koichi Sasada
cfd7729ce7 use inline cache for refinements
From Ruby 3.0, refined method invocations are slow because
resolved methods are not cached by inline cache because of
conservertive strategy. However, `using` clears all caches
so that it seems safe to cache resolved method entries.

This patch caches resolved method entries in inline cache
and clear all of inline method caches when `using` is called.

fix [Bug #18572]

```ruby
 # without refinements

class C
  def foo = :C
end

N = 1_000_000

obj = C.new
require 'benchmark'
Benchmark.bm{|x|
  x.report{N.times{
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
  }}
}

_END__
              user     system      total        real
master    0.362859   0.002544   0.365403 (  0.365424)
modified  0.357251   0.000000   0.357251 (  0.357258)
```

```ruby
 # with refinment but without using

class C
  def foo = :C
end

module R
  refine C do
    def foo = :R
  end
end

N = 1_000_000

obj = C.new
require 'benchmark'
Benchmark.bm{|x|
  x.report{N.times{
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
  }}
}
__END__
               user     system      total        real
master     0.957182   0.000000   0.957182 (  0.957212)
modified   0.359228   0.000000   0.359228 (  0.359238)
```

```ruby
 # with using

class C
  def foo = :C
end

module R
  refine C do
    def foo = :R
  end
end

N = 1_000_000

using R

obj = C.new
require 'benchmark'
Benchmark.bm{|x|
  x.report{N.times{
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
    obj.foo; obj.foo; obj.foo; obj.foo; obj.foo;
  }}
}
2023-07-31 17:13:43 +09:00
Koichi Sasada
280419d0e0 calling->cd instead of calling->ci
`struct rb_calling_info::cd` is introduced and `rb_calling_info::ci`
is replaced with it to manipulate the inline cache of iseq while
method invocation process. So that `ci` can be acessed with
`calling->cd->ci`. It adds one indirection but it can be justified
by the following points:

1) `vm_search_method_fastpath()` doesn't need `ci` and also
`vm_call_iseq_setup_normal()` doesn't need `ci`. It means
reducing `cd->ci` access in `vm_sendish()` can make it faster.

2) most of method types need to access `ci` once in theory
so that 1 additional indirection doesn't matter.
2023-07-31 17:13:43 +09:00
Nobuyoshi Nakada
8d242a33af
rb_bug prints a newline after the message 2023-05-20 21:43:30 +09:00
Takashi Kokubun
f5909ac6d9 RJIT: Stop allowing leaked globals rjit_* 2023-03-08 23:24:38 -08:00