Commit graph

213 commits

Author SHA1 Message Date
Jean Boussier
559d9e1f67 Convert VM/shape_tree to use rb_gc_mark_and_move
The `p->field = rb_gc_location(p->field)` isn't ideal because it means all
references are rewritten on compaction, regardless of whether the referenced
object has moved. This isn't good for caches nor for Copy-on-Write.

`rb_gc_mark_and_move` avoid needless writes, and most of the time allow to
have a single function for both marking and updating references.
2025-08-07 21:00:00 +02:00
Jean Boussier
1064c63643 Fix rb_shape_transition_object_id transition to TOO_COMPLEX
If `get_next_shape_internal` fail to return a shape, we must
transitiont to a complex shape. `shape_transition_object_id`
mistakenly didn't.

Co-Authored-By: Peter Zhu <peter@peterzhu.ca>
2025-08-01 12:39:14 +02:00
John Hawthorn
54f28c1db9 Avoid concurrently overflowing of shape_id
Previously it was possible for two atomic increments of next_shape_id
running concurrently to overflow MAX_SHAPE_ID. For this reason we need
to do the test atomically with the allocation in shape_alloc returning
NULL.

This avoids overflowing next_shape_id by repeatedly attempting a CAS.
Alternatively we could have allowed incrementing past MAX_SHAPE_ID and
then clamping in rb_shapes_count, but this seems simpler.
2025-07-09 10:38:04 -07:00
John Hawthorn
cfc006d410 Always use atomics to get the shape count
When sharing between threads we need both atomic reads and writes. We
probably didn't need to use this in some cases (where we weren't running
in multi-ractor mode) but I think it's best to be consistent.
2025-07-09 10:38:04 -07:00
John Hawthorn
5dfd86cf3f Fix off-by-one in shape_tree_mark/shape_tree_compact
This was using < so subtract one from the last shape id would have us
miss the last inserted shape. I think this is unlikely to have caused
issues because I don't think the newest shape will ever have edges.

We do need to use `- 1` because otherwise RSHAPE wraps around and
returns the root shape.
2025-07-09 10:38:04 -07:00
John Hawthorn
12b0ce3875 Remove unused src param from rb_shape_copy_fields 2025-07-04 14:54:49 -07:00
Jean Boussier
242343ff80 variable.c: Refactor generic_field_set / generic_ivar_set
These two functions are very similar, they can share most of their
logic.
2025-06-26 16:25:57 +02:00
Peter Zhu
aed7a95f9d Move RUBY_ATOMIC_VALUE_LOAD to ruby_atomic.h
Deduplicates RUBY_ATOMIC_VALUE_LOAD by moving it to ruby_atomic.h.
2025-06-25 13:04:25 -04:00
Jean Boussier
b284987651 Cleanup and document shape_id_t layout 2025-06-24 16:19:16 +01:00
Jean Boussier
45a2c95d0f Reduce exposure of FL_FREEZE
The `FL_FREEZE` flag is redundant with `SHAPE_ID_FL_FROZEN`, so
ideally it should be eliminated in favor of the later.

Doing so would eliminate the risk of desync between the two, but
also solve the problem of the frozen status being global in namespace
context (See Bug #21330).
2025-06-24 11:29:39 +01:00
Jean Boussier
fb68721f63 Rename imemo_class_fields -> imemo_fields 2025-06-17 15:28:05 +02:00
ydah
2d96400c26 Fix typo in error message for shape_id verification 2025-06-15 22:35:50 +09:00
Jean Boussier
15084fbc3c Get rid of FL_EXIVAR
Now that the shape_id gives us all the same information, it's no
longer needed.
2025-06-13 23:50:30 +02:00
Jean Boussier
6dbe24fe56 Use the shape_id rather than FL_EXIVAR
We still keep setting `FL_EXIVAR` so that `rb_shape_verify_consistency`
can detect discrepancies.
2025-06-13 23:50:30 +02:00
Jean Boussier
b51078f82e Enforce consistency between shape_id and FL_EXIVAR
The FL_EXIVAR is a bit redundant with the shape_id.
Now that the `shape_id` is embedded in all objects on all archs,
we can cheaply check if an object has any fields with a simple
bitmask.
2025-06-13 23:50:30 +02:00
Nobuyoshi Nakada
f2d7c6afee Suppress unused-variable warning 2025-06-13 22:24:09 +02:00
Jean Boussier
a99d941cac Add SHAPE_ID_HAS_IVAR_MASK for quick ivar check
This allow checking if an object has ivars with just a shape_id
mask.
2025-06-13 19:46:29 +02:00
Nobuyoshi Nakada
1d11e1be13
Suppress unused-variable warning 2025-06-13 21:09:20 +09:00
Jean Boussier
071aa02a4a shape.c: cleanup unused IDs
id_frozen and id_t_object are no longer used.
id_object_id no longer need to be exposed.
2025-06-13 12:03:22 +02:00
Jean Boussier
7c22330cd2 Allocate rb_shape_tree statically
There is no point allocating it during init, it adds
a useless indirection.
2025-06-12 17:08:22 +02:00
Jean Boussier
de4b910381 Get rid of GET_SHAPE_TREE()
It's a useless indirection.
2025-06-12 17:08:22 +02:00
Jean Boussier
e070d93573 Get rid of rb_shape_lookup 2025-06-12 17:08:22 +02:00
Jean Boussier
0292b702c4 shape.h: make RSHAPE static inline
Since the shape_tree_ptr is `extern` it should be possible to
fully inline `RSHAPE`.
2025-06-12 17:08:22 +02:00
Jean Boussier
8b5ac5abf2 Fix class instance variable inside namespaces
Now that classes fields are delegated to an object with its own
shape_id, we no longer need to mark all classes as TOO_COMPLEX.
2025-06-12 13:43:29 +02:00
Jean Boussier
3abdd4241f Turn rb_classext_t.fields into a T_IMEMO/class_fields
This behave almost exactly as a T_OBJECT, the layout is entirely
compatible.

This aims to solve two problems.

First, it solves the problem of namspaced classes having
a single `shape_id`. Now each namespaced classext
has an object that can hold the namespace specific
shape.

Second, it open the door to later make class instance variable
writes atomics, hence be able to read class variables
without locking the VM.
In the future, in multi-ractor mode, we can do the write
on a copy of the `fields_obj` and then atomically swap it.

Considerations:

  - Right now the `RClass` shape_id is always synchronized,
    but with namespace we should likely mark classes that have
    multiple namespace with a specific shape flag.
2025-06-12 07:58:16 +02:00
Jean Boussier
95201299fd Refactor the last references to rb_shape_t
The type isn't opaque because Ruby isn't often compiled with LTO,
so for optimization purpose it's better to allow as much inlining
as possible.

However ideally only `shape.c` and `shape.h` should deal with
the actual struct, and everything else should just deal with opaque
`shape_id_t`.
2025-06-11 16:38:38 +02:00
Jean Boussier
c2f2ac7db3 shape.c: Fix rb_bug call to use correct format for size_t 2025-06-11 10:10:04 +02:00
Étienne Barrié
c54e96d651 Fix RubyVM::Shape.transition_tree 2025-06-10 19:37:03 +02:00
Jean Boussier
7d8695e02f Stop pinning shape edges
Now that `rb_shape_traverse_from_new_root` has been eliminated there's
no longer any reason to pin these objects, because we no longer
need to traverse shapes downward during compaction.
2025-06-07 18:30:44 +02:00
Jean Boussier
a640723d31 Simplify rb_gc_rebuild_shape
Now that there no longer multiple shape roots, all we need to do
when moving an object from one slot to the other is to update the
`heap_index` part of the shape_id.

Since this never need to create a shape transition, it will always
work and never result in a complex shape.
2025-06-07 18:30:44 +02:00
Jean Boussier
191f6e3b87 Get rid of rb_shape_t.heap_id 2025-06-07 18:30:44 +02:00
Jean Boussier
6eb0cd8df7 Get rid of SHAPE_T_OBJECT
Now that we have the `heap_index` in shape flags we no longer
need `T_OBJECT` shapes.
2025-06-07 18:30:44 +02:00
Jean Boussier
2de67d424f shape.c: assert we're not returning INVALID_SHAPE_ID. 2025-06-07 18:30:44 +02:00
Jean Boussier
8c4e368dcf shape.c: ensure heap_index is consistent for complex shapes 2025-06-07 18:30:44 +02:00
Jean Boussier
689ec51146 Replicate heap_index in shape_id flags.
This is preparation to getting rid of `T_OBJECT` transitions.
By first only replicating the information it's easier to ensure
consistency.
2025-06-07 18:30:44 +02:00
Jean Boussier
90ba2f4e1c Add missing lock around redblack_cache_ancestors
This used to be protected because all shape code was
under a lock, but now that the shape tree is lock-free
we still need to lock around the red-black cache.

Co-Authored-By: Luke Gruber <luke.gruber@shopify.com>
2025-06-06 23:07:22 +02:00
Jean Boussier
2b810ac595 shape.c: match capacity growth with T_OBJECT embedded sizes
This helps with getting with of `SHAPE_T_OBJECT`, by ensuring
that transitions will have capacities that match the next embed size.
2025-06-06 13:37:03 +02:00
Jean Boussier
3883c38979 shape.c: Fix improperly named routine
Meant to be `transition_complex` not `transition_frozen`.
2025-06-06 11:43:51 +02:00
Jean Boussier
4e39580992 Refactor raw accesses to rb_shape_t.capacity 2025-06-05 22:06:15 +02:00
Étienne Barrié
2f80117ce4 Fix comment about debugging shapes
This method was moved to RubyVM::Shape in 913979bede.
2025-06-05 18:02:45 +02:00
Jean Boussier
772fc1f187 Get rid of rb_shape_t.flags
Now all flags are only in the `shape_id_t`, and can all be checked
without needing to dereference a pointer.
2025-06-05 07:44:44 +02:00
Jean Boussier
675f33508c Get rid of TOO_COMPLEX shape type
Instead it's now a `shape_id` flag.

This allows to check if an object is complex without having
to chase the `rb_shape_t` pointer.
2025-06-04 13:13:50 +02:00
Jean Boussier
6b8dcb7c8f shape.c: fix off by one error in shape_tree_mark 2025-06-04 07:59:20 +02:00
Jean Boussier
625d6a9cbb Get rid of frozen shapes.
Instead `shape_id_t` higher bits contain flags, and the first one
tells whether the shape is frozen.

This has multiple benefits:
  - Can check if a shape is frozen with a single bit check instead of
    dereferencing a pointer.
  - Guarantees it is always possible to transition to frozen.
  - This allow reclaiming `FL_FREEZE` (not done yet).

The downside is you have to be careful to preserve these flags
when transitioning.
2025-06-04 07:59:20 +02:00
Jean Boussier
db2cfebff1 Pin shape->edges 2025-06-02 17:49:53 +02:00
Jean Boussier
e9fd44dd72 shape.c: Implement a lock-free version of get_next_shape_internal
Whenever we run into an inline cache miss when we try to set
an ivar, we may need to take the global lock, just to be able to
lookup inside `shape->edges`.

To solve that, when we're in multi-ractor mode, we can treat
the `shape->edges` as immutable. When we need to add a new
edge, we first copy the table, and then replace it with
CAS.

This increases memory allocations, however we expect that
creating new transitions becomes increasingly rare over time.

```ruby
class A
  def initialize(bool)
    @a = 1
    if bool
      @b = 2
    else
      @c = 3
    end
  end

  def test
    @d = 4
  end
end

def bench(iterations)
  i = iterations
  while i > 0
    A.new(true).test
    A.new(false).test
    i -= 1
  end
end

if ARGV.first == "ractor"
  ractors = 8.times.map do
    Ractor.new do
      bench(20_000_000 / 8)
    end
  end
  ractors.each(&:take)
else
  bench(20_000_000)
end
```

The above benchmark takes 27 seconds in Ractor mode on Ruby 3.4,
and only 1.7s with this branch.

Co-Authored-By: Étienne Barrié <etienne.barrie@gmail.com>
2025-06-02 17:49:53 +02:00
Jean Boussier
6c4ae85211 Rename rb_shape_frozen_shape_p -> shape_frozen_p 2025-05-27 15:34:02 +02:00
Jean Boussier
ccf2b7c5b8 Refactor rb_shape_too_complex_p to take a shape_id_t. 2025-05-27 15:34:02 +02:00
Jean Boussier
a1f72d23a9 Refactor rb_shape_has_object_id
Now takes a `shape_id_t` and the version that takes a `rb_shape_t *`
is private.
2025-05-27 15:34:02 +02:00
Jean Boussier
a80a5000ab Refactor rb_obj_shape out.
It still exists but only in `shape.c`.
2025-05-27 15:34:02 +02:00