944 Commits
Author SHA1 Message Date
Peter Zhu 0b7838e23b Fix IO reading without GVL during GC compaction
[Bug #22467]

If we perform IO read without the GVL during GC compaction and the string
we are writing to is embedded, then it can get mprotect'd by the read
barrier in the GC, which can cause the IO read operation to fail.
2026-10-25 14:27:26 +09:00
Yusuke EndohandClaude Opus 5.5 053f8da910 Always use the no-inline rb_current_ec()
Under the M:N thread scheduler, a Ruby thread in a non-main Ractor may be
resumed on a different native thread after it blocks. The C compiler
assumes that the native thread never changes within a function, so it may
compute the address of ruby_current_ec once (`__tls_get_addr` in
libruby.so) and reuse it after the switch. The address then refers to the
TLS of the previous native thread, which leads to a SEGV or to using
another thread's ec.

    8.times.map do
      Ractor.new do
        50.times do
          ",,".split(/(,)/) { sleep 0.001 }
        end
        :ok
      end
    end.map(&:value)
    #=> [BUG] Segmentation fault at 0x0000000000000030 (with Clang 21)

arm64 and ppc64le already use the out-of-line rb_current_ec()
(RB_THREAD_CURRENT_EC_NOINLINE), which prevents the compiler from caching
the address. Use it on all platforms, and remove the macro.

[Bug #22453]

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:27:26 +09:00
Matt Valentine-House 37561263fc ZJIT: Make the write barrier marking flag per-objspace
`zjit_during_incremental_marking` was one process-wide global, written
by `gc_set_incremental_marking` on every transition of any objspace.
Since 24be314b45, two objspaces can run incremental marks at the same
time, so when one finished its mark it wrote zero while another was
still marking.

`gen_write_barrier` skips `rb_gc_writebarrier` when the receiver is young,
not shareable, and the word reads zero, so JIT code in the still-marking
objspace skipped the barrier.

A white object stored into a young black receiver was never marked, the
sweep freed it, and the next major crashed with

```
[BUG] try to mark T_NONE object (parent: T_ARRAY/Array).
```

This bug can be reproduced in a few seconds with

```
RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=0.5 ./ruby --zjit test.rb, where test.rb is:
```

```
Ractor.new {
  loop {
    GC.start(full_mark: true, immediate_mark: false, global: false)
    GC.start(global: false)
  }
}

loop {
  GC.start(full_mark: true, immediate_mark: false, global: false)
  Array.new(2_000) { [nil] }.each { |b|
    200.times { Object.new }
    sleep 0.0002
    b[0] = Object.new
  }
}
```

Limiting the oldobject limit factor ensures we keep running majors which
makes this bug easier to trigger, and the array is pre-sized because the
inlined ArrayAset side-exits on an empty array.

This commit moves zjit_incremental_marking into rb_objspace_t and makes
it a bool, so each Ractor can signal to zjit when it's incrementally
marking. Because this is now directly addressable per objspace we've
also removed the incremental marking flag.
2026-10-19 13:46:08 +01:00
Nobuyoshi Nakada 61261bf5c4 Add more assertions to RARRAY_AREF 2026-10-17 11:58:19 +09:00
Luke Gruber 4c2968348e Use mark context when marking execution_context 2026-10-16 12:33:52 -04:00
Luke Gruber b1d315127f Use mark ctx when marking internal ractor objects
When doing marking work with > 16 Ractors, we can see a difference by
not having to rederive the mark context when marking the Ractor object.

Benchmark: gc_balloon, 20 runs per number of ractors:

|      bench | ractors |      ctl (ms) |      exp (ms) | ctl/exp |
| ---------: | ------: | ------------: | ------------: | ------: |
| gc_balloon |       8 |  516.1 ± 2.8% |  517.3 ± 3.2% |   0.998 |
| gc_balloon |      16 |  913.4 ± 0.3% |  896.0 ± 0.3% |   1.019 |
| gc_balloon |      32 | 1818.1 ± 0.2% | 1792.1 ± 0.3% |   1.014 |

GC summary:

|      bench | ractors | mark/GC ratio | notes                     |
| ---------: | ------: | ------------: | :------------------------ |
| gc_balloon |       8 |         1.008 |                           |
| gc_balloon |      16 |         1.069 | 7% increase in mark perf  |
| gc_balloon |      32 |         1.059 | 6% increase in mark perf  |
2026-10-15 17:13:41 -04:00
Douglas Eichelberger 44f7c6b00d Join getaddrinfo and getnameinfo threads before fork
A getaddrinfo thread calls getaddrinfo(3) inside rb_thread_prevent_fork,
but on glibc its thread exit takes the resolver configuration lock again
after the thread has released the fork lock. A fork that lands then
leaves the child with that lock held, and every later name lookup in the
child blocks forever. getnameinfo(3) attaches the same resolver state,
even for a name in /etc/hosts, and its thread ran outside
rb_thread_prevent_fork altogether.

Make the lookup threads joinable and have fork join any that may still
be exiting. As a lookup thread finishes, still inside
rb_thread_prevent_fork, it swaps itself into a single slot with an
atomic exchange and joins the thread it displaced, which has usually
exited by then, and rb_thread_acquire_fork_lock joins whatever is left
in the slot. Once lookups settle, at most one finished thread is left
unjoined. The slot holds pthread_t as a pointer, with NULL for empty,
which assumes pthread_t is scalar and never 0.

getnameinfo threads now run within rb_thread_prevent_fork and register
as they exit, as getaddrinfo threads do, and where getnameinfo runs
without a helper thread it is called within rb_thread_prevent_fork too.
So fork also waits for a reverse lookup in progress, as it already did
for a forward one. raddrinfo_pthread_create now always creates joinable
threads, which drops its pthread_detach fallback for platforms without
pthread_attr_setdetachstate.

The slot is kept across fork, so every fork path that keeps running
Ruby code has to take the fork lock. On the BSDs, Process.daemon forks
with daemon(3) and did not take it, so the daemon would inherit the
registered thread, and its next fork would abort in pthread_join with
ESRCH. Take and release the fork lock around daemon(3) as rb_fork_ruby
does. daemon(3) can fail in the child as well as in the parent, so
compare pids to tell which process returned.

[Bug #22406]
2026-10-14 10:46:00 +02:00
Hiroshi SHIBATAandClaude Opus 5.5 c68782a239 Hold heredoc offsets in rb_len_t
Where rb_len_t is wider than long, a heredoc opener past column LONG_MAX made the long offset negative, and the identifier and the restored lexer position were read from before the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:29:43 +09:00
Jeremy Evans 35c32429e1 Add a minimal internal WeakMap C-API
This will be used to make the refinement iclasses cache weak, avoiding
a memory leak when creating and using an arbitrary number of
refinements.
2026-10-10 01:14:57 -07:00
Jeremy Evans 3a66844fa9 Allow super in module method called via refinement method super
This was previously prohibited as it wasn't handled correctly. The
reason it wasn't handled correctly is that at the point of the super
call, the refinement module didn't have the class super pointer
set correctly, so it couldn't determine the ancestor to start the
lookup for the super method to call.  Fix that by lazily creating a
refinement module iclass with the class super pointer set correctly.
This is cached in a hidden hash so it's only created the first time
it is needed (similar to how singleton classes are created).

Unfortunately, storing this hidden hash in an instance variable in
the iclass failed CI due to shape_id layout mismatch errors. Storing
the hidden hash in an instance variable of the class/module itself is
possible, but that doesn't work if the class/module is frozen. So the
only option is to store this in rb_classext_struct. I didn't want to
increase the size of rb_classext_struct, so this uses the classpath
member to store the hash. That member is currently only used for
classes/modules and not for iclasses, so it shouldn't cause any
problems. Ultimately, I think the classpath entry should be moved
into the as union as that would be easier to understand. However, I
did not do so in this commit in order to avoid unnecessary churn.

By lazily creating the iclass, we don't create unnecessary iclasses.
For example, if Enumerable is refined, this only generates refinement
module iclasses for the classes using Enumerable where the refinement
method is actually called.

While working on specs for this, I ran into an infinite loop because
current_method_entry didn't correctly handle bmethods.  So update
current_method_entry to handle that.

Implements [Feature #22309]
2026-10-10 01:14:57 -07:00
Hiroshi SHIBATAandClaude Opus 5.5 c9dd247de0 Use rb_len_t in the bulk Hash builders and the ZJIT helpers
The ZJIT bindings are regenerated with make zjit-bindgen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:45:25 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 cedc07cc8d Count Enumerable#count with an unsigned member of MEMO
Only imemo_count_up and imemo_count_value treat the count as unsigned, so keep cnt as rb_len_t for the other users of MEMO and give them their own member.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:45:25 +09:00
Hiroshi SHIBATAandClaude Opus 5.5 adf30005ea Use rb_len_t for the lengths left in long
These hold String and Array lengths or offsets into them, so they would truncate on mswin once rb_len_t widens. A build with rb_len_t set to long long reported them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 224dc5c110 Use rb_len_t for path lengths
The file, dir, load and ruby.c changes go together because rb_get_expanded_load_path() returns the maximum path length through a pointer. Lengths passed to the Windows string conversion helpers that take `long *` stay `long`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 cfabfcd520 Use rb_len_t for lengths and indices of Struct
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 2c00037d72 Use rb_len_t for symbol name lengths
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 8f88956c99 Use rb_len_t for match offsets and search positions in re.c
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 62f0eec147 Use rb_len_t for lengths and indices in array.c
ARY_MAX_SIZE follows rb_len_t, so the index errors report the value with PRIdLEN. The repeated_permutation exponent stays `long`, because rb_int_positive_pow() takes an `unsigned long` and the value is the user's Integer rather than an array length.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 ac09093b91 Use rb_len_t for element counts in enum.c
The MEMO counter becomes rb_len_t, which is wider than `unsigned long` once rb_len_t widens, so the Enumerable#count overflow builds the next value from RB_ULEN_MAX instead of assuming `unsigned long` words.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 d5ebdee531 Use rb_len_t for lengths and indices in string.c
RString capa becomes pointer-sized everywhere once rb_len_t widens, so fake strings can keep their precomputed hash on LLP64 as well.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Hiroshi SHIBATAandClaude Opus 5 cc3ba11012 Use rb_len_t in the C API for lengths and indices
Once rb_len_t widens this changes the `long *` output parameters of rb_range_beg_len(), rb_str_subpos(), rb_arithmetic_sequence_beg_len_step(), ruby_enc_find_basename() and ruby_enc_find_extname(), so extensions passing `long *` to them will need to be updated. The extension guides list these signatures verbatim and follow along.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-09 14:00:15 +09:00
Peter Zhu 8972135571 Include JIT call count in ObjectSpace dumps of iseq
This commit adds `jit_calls` and `jit_threshold_reached` to iseq dumps which
dumps the number of calls to the iseq and whether the iseq has been JIT
compiled or not, respectively.
2026-10-09 08:56:20 +09:00
Max Bernstein e4672be48d ZJIT: Inline write barrier check in LIR (#18641)
For `WriteBarrier a, b`, inline the fast-path of the write barrier that covers the majority of cases and doesn't require any actual barrier:

* `a` isn't old
* `a` isn't shareable
* not incremental marking

Removes the vast majority of barrier C calls.

Lobsters before:

```
Top-20 calls to C functions from JIT code (69.2% of total 50,814,557):
                  rb_hash_aref: 5,770,278 (11.4%)
  rb_vm_opt_send_without_block: 5,428,040 (10.7%)
            rb_gc_writebarrier: 3,304,955 ( 6.5%)
...
```

After: completely gone from top-20. Total C calls drop from ~50.8MM to ~47.6MM.

On a microbenchmark such as `setivar_object` or `setivar_young`, it takes per-iteration time from ~41ms to ~36ms (roughly -12%).

```c
void
rb_gc_impl_writebarrier(void *objspace_ptr, VALUE a, VALUE b)
{
    rb_objspace_t *objspace = objspace_ptr;

#if RGENGC_CHECK_MODE
    if (SPECIAL_CONST_P(a)) rb_bug("rb_gc_writebarrier: a is special const: %"PRIxVALUE, a);
    if (SPECIAL_CONST_P(b)) rb_bug("rb_gc_writebarrier: b is special const: %"PRIxVALUE, b);
#else
    RBIMPL_ASSERT_OR_ASSUME(!SPECIAL_CONST_P(a));
    RBIMPL_ASSERT_OR_ASSUME(!SPECIAL_CONST_P(b));
#endif

    GC_ASSERT(!during_gc);
    GC_ASSERT(RB_BUILTIN_TYPE(a) != T_NONE);
    GC_ASSERT(RB_BUILTIN_TYPE(a) != T_MOVED);
    GC_ASSERT(RB_BUILTIN_TYPE(a) != T_ZOMBIE);

    /* A shareable object now references an unshareable one: record b as a shref so its
     * owner's local GC roots it (the parent may live in another objspace, untraversed
     * there).  Only b's owner stores this, on its own page: a plain store suffices. */
    if (RB_UNLIKELY(RB_FL_TEST_RAW(a, RUBY_FL_SHAREABLE)) &&
            !RB_FL_TEST_RAW(b, RUBY_FL_SHAREABLE)) {
        struct heap_page *bpage = GET_HEAP_PAGE(b);
        if (!_MARKED_IN_BITMAP(bpage->shref_bits, bpage, b)) {
            _MARK_IN_BITMAP(bpage->shref_bits, bpage, b);
            bpage->flags.has_shref_objects = TRUE;
        }
    }

  retry:
    if (!is_incremental_marking(objspace)) {
        /* The generational barrier covers old->young edges within one objspace only; a
         * foreign a or b has age bits another objspace mutates, unsafe to read, so check
         * locality first when multi-Ractor (a foreign a is shareable and the shref above
         * already keeps b alive).  With a single Ractor nothing is foreign. */
        if ((rb_gc_multi_ractor_p() &&
                (GET_HEAP_OBJSPACE(a) != objspace || GET_HEAP_OBJSPACE(b) != objspace)) ||
                !RVALUE_OLD_P(objspace, a) || RVALUE_OLD_P(objspace, b)) {
            // do nothing
        }
        else {
            gc_writebarrier_generational(a, b, objspace);
        }
    }
    else {
        /* Slow path, no lock: incremental marking only runs while the process has a single
         * objspace, so the owning Ractor's GVL already serializes this barrier against its
         * own GC. */
        if (is_incremental_marking(objspace)) {
            gc_writebarrier_incremental(a, b, objspace);
        }
        else {
            goto retry;
        }
    }
    return;
}
```
2026-10-08 11:13:52 -04:00
Hiroshi SHIBATAandClaude Opus 5.5 957caeef23 Drop support for Darwin on PowerPC
The last Mac OS X release for PowerPC Macs is 10.5, and nothing else runs this code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 07:04:09 +09:00
Jean Boussier 9ce0df6719 Make ISeq binaries portable
* Make `__FILE__` a frozen core method that return the iseq path.
* Make `load_from_binary` accept `file, path` like compile.

[Feature #17593]
[Feature #22395]
2026-10-07 09:57:31 +02:00
Luke Gruber f0439cdfa7 Remove VM lock from Ractor.make_shareable()
Ractor.make_shareable could previously take the VM lock and then call
rb_funcall(id_freeze), which is not correct to do. Create a new
rb_objspace_reachable_objects_from_local() function that works the same as the
non-local version except doesn't lock. The callback given to it must skip foreign
objects (classes/modules, etc.) as running mark_children could traverse data
structures that could be mutated out from under it.

The reachable_objects_from_local callback must skip foreign objects altogether
instead of checking their shareability because foreign objects that aren't
shareable could be collected or have their page unmapped due to RLGC. Of course,
foreign edges should be shareable anyway. This was a bug that existed in the
locking version as well. Unfortunately, that means we can't check that T_DATA
objects don't refer to unshareables across object spaces while making them
shareable.

The alternative is to check and risk a crash, and raise if we see these illegal
edges.
2026-10-06 18:31:56 -04:00
Hiroshi SHIBATAandClaude Opus 5.5 3cf882a6d7 YJIT: Register unwind info for the JIT region on Windows
RtlUnwindEx(), which longjmp() and C++ exceptions use, needs unwind info for every frame, so raising through JIT frames skipped __finally blocks and C++ destructors. Exception dispatch also could not walk through JIT frames to reach the unhandled exception filter that the compaction read barrier relies on. Describe the entry frame, which now always has a frame pointer on Windows, for the whole region.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 06:11:37 +09:00
Luke Gruber df0fa43f53 Add marking context to speed up marking in multi-Ractor
This makes a bigger difference on arm64, where GET_EC() is not inlined.
Calling this when marking every reference is expensive. It also makes a
bigger difference with Ractors because in single-ractor, the main ractor
can be retrieved through a global variable instead of through the EC.

The context now holds the objspace, derived from the Ractor. The context is
passed to other internal marking functions as an argument so they don't need
to rederive it.

Custom ruby-bench GC benchmark: gc_balloon.
Arm64 MacOS, ruby_ctl = without this commit, ruby_exp = with it:

| ractors |     ctl (ms) |     exp (ms) | exp 1st itr | ctl/exp |
| ------: | -----------: | -----------: | ----------: | ------: |
|       0 | 332.3 ± 0.3% | 328.5 ± 0.3% |       1.011 |   1.011 |
|       1 | 373.4 ± 1.0% | 336.7 ± 2.0% |       1.101 |   1.109 |
|       2 | 375.4 ± 1.0% | 341.3 ± 0.3% |       1.115 |   1.100 |
|       4 | 385.5 ± 1.3% | 344.9 ± 0.5% |       1.134 |   1.118 |
|       6 | 386.4 ± 0.6% | 362.0 ± 1.0% |       1.073 |   1.067 |
|       8 | 461.8 ± 1.1% | 421.9 ± 1.0% |       1.091 |   1.095 |

The difference between the two can be accounted for in the marking time (not shown).

Linux x86, same gc_balloon benchmark:

| ractors |     ctl (ms) |     exp (ms) | exp 1st itr | ctl/exp |
| ------: | -----------: | -----------: | ----------: | ------: |
|       0 | 443.4 ± 7.7% | 435.6 ± 6.7% |       1.045 |   1.018 |
|       1 | 489.0 ± 2.8% | 457.0 ± 1.9% |       1.093 |   1.070 |
|       2 | 509.5 ± 0.9% | 470.2 ± 0.4% |       1.073 |   1.084 |
|       4 | 528.5 ± 1.4% | 496.5 ± 0.6% |       1.093 |   1.065 |
|       6 | 547.3 ± 1.8% | 509.0 ± 0.8% |       1.049 |   1.075 |
|       8 | 704.9 ± 7.1% | 645.1 ± 7.1% |       1.100 |   1.093 |

Single-ractor marking time isn't affected as much.

Linux x86, gcbench benchmark:

|   bench |     ctl (ms) |     exp (ms) | exp 1st itr | ctl/exp |
| ------: | -----------: | -----------: | ----------: | ------: |
| gcbench | 264.5 ± 0.8% | 262.7 ± 0.9% |       0.988 |   1.007 |
2026-10-05 15:32:58 -04:00
Luke Gruber 4eed7d64ea Don't call Ruby code in rb_method_definition_eq
During method aliasing, we check if the methods `a` and `b` are equal
before aliasing. If they are equal, we don't need to alias. That check
was running Ruby code with the VM lock held when the first method was a
bmethod, which is not good and can cause incorrect behavior. Now, we call
`rb_proc_eq` directly. This means an override of `Proc#==` will not be
called to check if 2 methods are equal and will instead use the definition
of Proc equality at VM startup. `Method#==` and `UnboundMethod==` will
also not call into `Proc#==` now for bmethods. This is probably better behavior
even without the VM lock fix because `Method#hash` never called `Proc#hash` when
the method was a bmethod, it used `rb_hash_proc` directly.

Fixes [Bug #22398]
2026-10-02 09:35:13 -04:00
Peter Zhu 96d57e2403 Make rb_str_no_gvl_safe_acquire public C API
This commit renames rb_str_tmp_frozen_no_embed_acquire to rb_str_no_gvl_safe_acquire
and makes it part of the public C API.

There is currently no way for C extensions that use no GVL operations to
safely read from a string because it has no way of guaranteeing that the
string is not embedded. Embedded strings are not safe for no GVL operations
because GC compaction can run, which will mprotect the memory region and
will cause any read to SEGV.

See https://github.com/ruby/ruby/pull/9817 for when rb_str_tmp_frozen_no_embed_acquire
was introduced.
2026-10-01 10:02:46 +09:00
Jean Boussier 5c128f545b Deprecate RHASH_TBL
[Feature #22232]

Guaranteeing that any hash can be converted into an st_table is
limiting the evolution of RHash.

None of the public usages of `RHASH_TBL` were justified, they all
had equivalent `rb_hash_*` public APIs to perform the same thing
more efficiently without disabling write barriers nor coercing the
hash into an st_table.
2026-09-30 11:51:14 +02:00
Randy Stauner b58bea071d Fix buffer size accounting for rb_str_tmp_frozen roots
capa does not include the terminator, so when a buffer moves
between strings with different terminator lengths the capacity has to
be converted or we will try to free the buffer with the wrong size:

    [BUG] buffer 0x00005030039ff768 freed with old_size=18, but was allocated with size=17
    [BUG] buffer 0x00005631adc48f48 freed with old_size=8268, but was allocated with size=8269

Three related fixes:

* `rb_str_tmp_frozen_release`: convert tmp's capacity to orig's
  terminator length when handing the buffer back.  This is the inverse
  of a calculation added to `rb_str_tmp_frozen_no_embed_acquire` in
  91619f0230.

* `heap_str_make_shared`: since bdf3032e35
  the root created by `rb_str_tmp_frozen_acquire` does not copy the
  encoding of the string it takes the buffer from, so the two can have
  different terminator lengths and the root's capacity has to be
  adjusted as well, otherwise the root frees fewer bytes than were
  allocated once its only child stops sharing the buffer.  termlen is
  passed in because the caller assigns the encoding of the root after
  this function returns.

* `rb_str_tmp_frozen_no_embed_acquire`: unset `STR_BORROWED` on the
  root.  `STR_SET_SHARED` sets `STR_BORROWED` on roots with
  `klass == 0` to mark the ones that may have other children, but this
  root only ever has orig as its child, so unset it like
  `heap_str_make_shared` does.  Any string that starts sharing the
  root later will set the flag again.  Without this
  `rb_str_tmp_frozen_release` never gives the buffer back, so strings
  written by `io_fwrite` stay shared and are copied on their next
  modification.

The release and make_shared adjustments go together
as they were previously canceling each other out.
2026-09-26 21:24:18 +02:00
Hiroshi SHIBATAandClaude Opus 5.5 0fb144a7d9 Remove unused MSC_VERSION_SINCE and MSC_VERSION_BEFORE
The last use went away in cdb9893c55.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 12:42:26 +09:00
Matt Valentine-House 2ad6b99a98 GC: scope rb_gc_register_address to the calling Ractor
The registered_globals list made every Ractor's GC scan every slot under
a shared lock. This commit splits it into per-Ractor lists.

We also maintain a global registry that references every Ractor that has
ever registered so that unregistering from a Ractor other than the one
that registered can look up the owning list.

When a Ractor is joined, the dead Ractor's slots are moved to the
joining Ractor's heap before the objspace merge happens.

This change tightens up the requirements for values stored in a
registered address.

Previously, Every Ractor's local GC scanned the shared list. So if a
slot held an unshareable object living in a different Ractor's heap,
that object was always kept alive regardless.

After this change, only the registering Ractor's GC scans its list. So if a
slot holds another Ractor's unshareable object, the owning Ractor's
local GC cannot see that reference, so it can collect the object while
the registered slot still points at it.

Storing such an object was already a Ractor containment violation. It
was just hidden by the shared scan.

[Feature #22277]
2026-09-25 19:24:10 +01:00
Luke Gruber 24be314b45 Enable incremental marking when the process has > 1 objspace
Previously, major GC incremental marking was disabled (even on the main Ractor)
when more than 1 Ractor was running. By changing the WB implementation, we now
have it back.
2026-09-24 13:54:45 -04:00
Shugo MaedaandClaude Fable 5.1 611bb146b0 [Feature #22304] Add rb_warn_to_remove_at and rb_warn_scheduled_deprecation
rb_warn_deprecated_to_remove_at warns only when Warning[:deprecated]
is enabled, so a deprecation scheduled to warn by default in a later
version needed a hand-written rb_warn and a separate commit to switch.

rb_warn_to_remove_at emits the same message with rb_warn and no
category.  rb_warn_scheduled_deprecation(X.Y, X.Z, ...) selects
between the two at compile time by RUBY_API_VERSION_CODE: gated by
Warning[:deprecated] before X.Y, shown by default from X.Y, and a
compile error in RUBY_DEBUG builds at X.Z, like the existing removal
check.  ruby/version.h is now included unconditionally and RUBY_VERSION_SINCE
and the version-string macros are always defined for that, while the
compile-time checks and RBIMPL_TODO stay RUBY_DEBUG only.  Both
functions are exported so that the new -test-/warning extension can
exercise both phases.

Use it for the two alias deprecations in rb_alias, which are scheduled
to warn by default in 4.2 and to be removed in 4.3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-24 20:33:14 +09:00
Jean Boussier 5a395d2ad6 marshal.c: embed compat_tbl in load and dump args 2026-09-24 13:31:56 +02:00
Hiroshi SHIBATAandClaude Opus 5 44fff96b0b Attach a box classext to its class before filling it in
rb_class_duplicate_classext builds the whole copy first and the caller only
then puts it in the class's table, so a GC in the middle collects the tables
it has made and marking the class afterwards walks into a freed object.  Put
the classext in the table first instead.  Marking it while it is still half
built is fine, since ZALLOC leaves every field zero.

    $ RUBY_BOX=1 ruby --disable-gems -e 'GC.stress = true; module Enumerable; def m; end; end'
    -e:1: [BUG] try to mark T_NONE object (obj: out-of-heap:0x..., parent: ... T_MODULE/Module Enumerable)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-24 16:29:51 +09:00
YO4andNobuyoshi Nakada b96ef541a1 Use of explicit parentheses
Apply suggestion from @nobu

Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
2026-09-24 12:33:21 +09:00
YO4 73481d73d2 mswin: enable flexible array support for msvc
msvc supports flexible array as it's language extensions.
The /Za msvc compiler option disables this and other extensions, but it is not necessary to consider it.
The Windows SDK headers contain anonymous unions/structs, and using /Za makes the SDK unusable.
2026-09-24 12:33:21 +09:00
YO4 98e99961ff mswin: avoid using a bitfield basen on enum type
Using the option (one of -std:[c11|c17|clatest]) to enable standard C support in msvc causes the following error:
[BUG] vm_call_method: unsupported method type (-7)
Simply disable it.
2026-09-24 12:33:21 +09:00
Nobuyoshi Nakada 74856fbfb6 Use identity sets to track unspecified keyword arguments
Keyword tracking only needs membership, so replace identity hashes
with identity sets to avoid storing unused values.  Add `rb_set_p`
for the type assertion in `vm_check_keyword`.

Add benchmarks for 31, 32 and 128 keyword parameters with sparse and
dense dynamic defaults, using both direct keywords and keyword splats.
The 31-parameter cases provide a bitmask-only control.

Local arm64 macOS measurements show about 16-26% higher throughput
with many omitted dynamic defaults at 32 and 128 parameters.  Smaller
differences remain noisy, including in the bitmask-only controls.

The tracking container is created at call time, leaving IBF and
`ISeq#to_a` unchanged.  Verify IBF-loaded calls with the Integer
refinement and colliding keyword indices from [Bug #22335].
2026-09-23 21:36:41 +09:00
Luke Gruber 7ba699ae26 Make fibers RUBY_TYPED_THREAD_SAFE_FREE
Now fibers can be collected by multiple ractors at once when those Ractors
are running local GCs. Fibers are created in the Ractor that owns them, so
if multiple Ractors have unmarked fibers during local GC sweeping, we can
now collect them concurrently instead of deferring their collection to a
postponed job. Each fiber pool is protected by its own native mutex.

In the future, maybe fiber pools will be ractor-local, but we have to
experiment with it first and see if it's enough of a performance win to
justify the change. We also want to minimize resource acquisition for each
Ractor as they're supposed to be lighweight.
2026-09-22 09:48:07 -04:00
Peter Zhu edce07a8c9 Fix use-after-free in String#% when array modified
If the array object passed into String#% is modified during conversion
(such as via to_s, to_i, to_f, etc.) then it can be a use-after-free if
the buffer of the array is freed.

This commit changes it to detect if the buffer has been changed and raises
an error when that happens.

The following script reproduces the crash:

    count = 1_000
    obj = Object.new
    ary = [obj, *Array.new(count - 1) { "x" }]
    obj.define_singleton_method(:to_s) do
      ary.replace([])
      "X"
    end
    str = "%s" * count
    puts (str % ary).length
2026-09-20 21:06:47 +09:00
Nobuyoshi Nakada 92cef2802c Mark rb_freeze_singleton_class as internal
`rb_freeze_singleton_class` is an implementation detail of
`RB_OBJ_FREEZE`, so deprecate direct calls from extension libraries
before removing its public declaration in Ruby 4.2.
2026-09-19 18:51:40 +09:00
John Hawthorn 1bd863dd23 Use Marshal compat from Ractor courier
For some types (probably most notably Set), Marshal loading is defined
with compatibility functions. We should use those when set.
2026-09-17 09:33:28 -07:00
John Hawthorn 73055acd57 Forbid setting ivars on Ractor shareable objects
Previously we had a check that objects which were shareable, but not
frozen on a non-main Ractor would raise an exception if instance
variables were read. This prevented a non-main Ractor reading
non-shareable objects.

This changes the behaviour to forbid all writes to shareable objects
(even when unfrozen). This is done by re-purposing the frozen flag on
the object shape to reflect (FL_FROZEN || FL_SHAREABLE).

With IV writes forbidden on all shareable objects whether or not they
are frozen (with the exception of classes and modules), we can also
remove the check against reading instance variables, since no unsafe
instance variables can be set.

[Bug #22291]
2026-09-16 12:26:11 -07:00
Peter Zhu 41cf062956 Use Set instead of Hash for Array#&
This commit changes Array#& to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a
    b = (5_000...15_000).to_a

    1_000.times { a & b }

Results:

    Benchmark 1: master
      Time (mean ± σ):     196.4 ms ±   9.1 ms    [User: 182.9 ms, System: 9.5 ms]
      Range (min … max):   186.3 ms … 214.7 ms    13 runs

    Benchmark 2: branch
      Time (mean ± σ):     163.4 ms ±   3.3 ms    [User: 152.7 ms, System: 7.8 ms]
      Range (min … max):   157.7 ms … 169.9 ms    17 runs

    Summary
      branch ran
        1.20 ± 0.06 times faster than master
2026-09-13 11:52:30 +09:00
Peter Zhu 078196c12d Use Set instead of Hash for Array#|
This commit changes Array#| to use a temporary Set instead of Hash for
performance and memory savings. We can see in the following benchmark:

    a = (0...10_000).to_a
    b = (5_000...15_000).to_a

    1_000.times { a | b }

Results:

    Benchmark 1: master
      Time (mean ± σ):     237.5 ms ±   2.0 ms    [User: 223.4 ms, System: 10.9 ms]
      Range (min … max):   234.6 ms … 240.7 ms    12 runs

    Benchmark 2: branch
      Time (mean ± σ):     165.2 ms ±   3.8 ms    [User: 151.2 ms, System: 10.8 ms]
      Range (min … max):   159.8 ms … 174.2 ms    18 runs

    Summary
      branch ran
        1.44 ± 0.04 times faster than master
2026-09-13 11:52:30 +09:00
Samuel Williams aaf83bcebe Scope fiber_interrupt with an operation proxy. (#18793)
[Bug #21918]
2026-09-13 14:48:19 +12:00