Skip to content

[pull] master from ruby:master#920

Merged
pull[bot] merged 34 commits intoturkdevops:masterfrom
ruby:master
Apr 9, 2026
Merged

[pull] master from ruby:master#920
pull[bot] merged 34 commits intoturkdevops:masterfrom
ruby:master

Conversation

@pull
Copy link
Copy Markdown

@pull pull bot commented Apr 9, 2026

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

byroot and others added 18 commits April 8, 2026 21:00
There is only two rare cases where they need to be sweeped:

  - If they are so large that they're heap allocated.
  - If they have an object_id (and id2ref_tbl exist but
    we can't check that here)
These were introduced in 165e10b
when these hashes were allocated with `rb_hash_new()`.

But since 4fb1438 they are allocated
with the right size, making the rehashing pure waste.
When we allocate a RHash using `rb_hash_new_capa()`, if `capa` is
larger than `8` it's directly allocated as an `st_stable` in a `80B`
slot.

However if the requested size if lesser or equal to 8, we allocate
it as an `ar_table` in a `160B` slot.

Since most hashes are allocated as mutable, we have to be able to
accomodate as much as 8 AR_TABLE entries regardless.

However there are case where we know the Hash won't ever be resized,
that notably the case of all the "literal" hashes allocated by the
compiler.

These are immediately frozen and hidden upon being constructed, hence
we can know for sure they won't ever be resized. This allows us to
allocate the smaller ones in smaller slots.

```
size: 0, slot_size: 32
size: 1, slot_size: 48
size: 2, slot_size: 64
size: 3, slot_size: 80
size: 4, slot_size: 96
size: 5, slot_size: 112
size: 6, slot_size: 128
size: 7, slot_size: 144
size: 8, slot_size: 160
```

```ruby
require "objspace"

p ObjectSpace.memsize_of({}.freeze) # => 40
p ObjectSpace.memsize_of({a: 1}.freeze) # => 80
p ObjectSpace.memsize_of({a: 1, b: 2}.freeze) # => 80
p ObjectSpace.memsize_of({a: 1, b: 2, c: 3}.freeze) # => 80
p ObjectSpace.memsize_of({a: 1, b: 2, c: 3, d: 4}.freeze) # => 160
p ObjectSpace.memsize_of({a: 1, b: 2, c: 3, d: 4, e: 5, }.freeze) # => 160
p ObjectSpace.memsize_of({a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}.freeze) # => 160
p ObjectSpace.memsize_of({a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7}.freeze) # => 160
p ObjectSpace.memsize_of({a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8}.freeze) # => 160
```
This otherwise causes a crash on e.g. #15220:

    thread '<unnamed>' (18071) panicked at zjit/src/codegen.rs:2511:21:
    internal error: entered unreachable code: with_num_bits should not be used for: Value(VALUE(19239180))
    stack backtrace:
       0: __rustc::rust_begin_unwind
                 at /rustc/e408947bfd200af42db322daf0fadfe7e26d3bd1/library/std/src/panicking.rs:689:5
       1: core::panicking::panic_fmt
                 at /rustc/e408947bfd200af42db322daf0fadfe7e26d3bd1/library/core/src/panicking.rs:80:14
       2: zjit::backend::lir::Opnd::with_num_bits
                 at /home/runner/work/ruby/ruby/src/zjit/src/backend/lir.rs:457:18
       3: zjit::codegen::gen_guard_type
                 at /home/runner/work/ruby/ruby/src/zjit/src/codegen.rs:2511:21
       4: zjit::codegen::gen_insn
                 at /home/runner/work/ruby/ruby/src/zjit/src/codegen.rs:690:55
ArrayPush calls out to the fast-path, not checking for frozen-ness. In
debug mode, this leads to crashes. In release mode, silent erroneous
modifications.
Bumps the github-actions group with 1 update in the / directory: [taiki-e/install-action](https://github.com/taiki-e/install-action).


Updates `taiki-e/install-action` from 2.75.0 to 2.75.1
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](taiki-e/install-action@cf39a74...80e6af7)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-version: 2.75.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Apple clang aborts if cpp output is closed in middle, and leaves the
preprocessed source and reproduction shell script.
When checking for suffixes, do not flush the numeric literal token
even if no suffix is found.
Wait for terminated threads to finish.
@pull pull bot locked and limited conversation to collaborators Apr 9, 2026
@pull pull bot added the ⤵️ pull label Apr 9, 2026
nobu and others added 10 commits April 9, 2026 17:34
It may be set to "false" if usable compiler is not found.
With smaller pool sizes (e.g. 32 bytes), an RObject with 0 embedded
fields would request a size smaller than the minimum meaningful object.
Ensure at least 1 field worth of space so the embedded size is always
large enough for a valid RObject.
When pool slot sizes can be smaller than sizeof(struct RBasic) (e.g. a
32-byte pool on 64-bit where RBasic is 16 bytes), the capacity
calculation would underflow. Guard against this by setting capacity to
0 for pools too small to hold fields.
Replace the RVALUE_SLOT_SIZE-multiplier based pool sizes with explicit
power-of-two (and near-power-of-two) slot sizes. On 64-bit this gives
12 heaps (32, 40, 64, 80, 96, 128, 160, 256, 512, 640, 768, 1024)
instead of 5, providing finer granularity and less internal
fragmentation. On 32-bit the layout is 5 heaps (32, 64, 128, 256, 512).
Add GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] to store the usable size
(excluding debug overhead) of the smallest pool that can hold a standard
RVALUE.
Add a 7/8 multiplier to the min_free_slots checks in
gc_sweep_finish_heap and gc_marks_finish, allowing heaps to be up to
~12.5% below the free slots target without triggering a major GC or
forced growth.

With 12 heaps instead of 5, each heap independently hitting the exact
threshold would cause excessive memory growth. The slack prevents
cascading growth decisions while still ensuring heaps stay close to
their target occupancy.
We implemented some bit twiddling logic with an unsigned int to have a
neat way of tracking which heaps were currently sweeping, but we
actually don't need to care which heap is sweeping right now, just
whether some are or not, so we can replace this with a counter.
This is because when `RVALUE_OVERHEAD` is positive, ie. when
`RACTOR_CHECK_MODE` is enabled and we need to store the pointer to the
owning ractor, we need to make sure there is enough space to store it.

With the previous size pools the smallest size pool was 40 bytes, this
gets expanded to 48 bytes when debug mode is on in order to make space
for this extra pointer.

because rb_obj_embedded_size(0) returns just the header with no field
space it wants to be allocated in the 40 byte slot, this gives 16 bytes
which is enough for RBasic only, but because this slot is 48 bytes in
debug mode, we get the extra space for the pointer.

When the smallest slot is 32 bytes it becomes 40 bytes in debug mode,
this causes objects with no ivars to to get allocated in this pool
because according to this calc it fits. but this doesn't leave the extra
word for the ractor pointer.

So in debug mode, we'll clamp this to 1 so that there's always enough
space for 1 extra field to force allocation into the 40/48 byte pool.
This isn't a 0 terminated list anymore because we iterate over
heaps_count directly. So we don't need to allocate an extra byte for the
sentinel
@pull pull bot merged commit c919778 into turkdevops:master Apr 9, 2026
3 of 4 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants