summaryrefslogtreecommitdiff
path: root/test/ruby/test_shapes.rb
AgeCommit message (Collapse)Author
23 hoursReapply "Reserve 2 bits for expressing object layout (#17139)" (#17158)Aaron Patterson
This reverts commit ddb5055d961d970aded287cfebd07b78efee3ca7.
4 daysRevert "Reserve 2 bits for expressing object layout (#17139)"Aaron Patterson
This reverts commit 63d9f090b5d9461cf0b9446e0039d9c56156b826.
4 daysReserve 2 bits for expressing object layout (#17139)Aaron Patterson
* Reserve 2 bits for expressing object layout We would like to make instance variable reads in the JIT compiler faster (as well as simplify the JIT implementation). Currently, in order to read an instance variable, we have to: 1. Test for heap object 2. Load object to a 64 bit register 3. Mask the object header 4. Bit test against the masked header 5. JNE 6. Load field We would like to: 1. Test for heap object 2. Load object shape to a 32 bit register 3. Bit test against the shape 4. JNE 5. Load field The way we fetch instance variables is not consistent across objects. In order to realize our goal, we need to encode object layout inside the shape. If we encode object layout inside the shape, then the shape itself will guarantee that the access pattern generated by the JIT compiler is correct. We should encode the following load patterns into the shape tag bits. This way we can share shapes on transitions, but be able to differentiate the access patterns for the JIT compiler. In other words, two objects can have an `@a -> @b -> @c` transition and share the same shape, but the tag bits can differentiate the access pattern so that the JIT compiler can be confident that the machine code is correct. Here are the patterns: 1. Embedded/Extended T_OBJECT Instance Variables Objects with direct references to instance variables or via malloc buffer 2. Objects with fields_objects fields These are Data and TypedData objects. They have an associated axillary imemo/fields object that stores the instance variables. The access pattern is `object[2] + 2`. The fields object is the 3rd field, and the instance variables start at +2 inside the fields object. The fields object itself is a Ruby object, so it contains the usual header bits + class headers. 3. Non Boxable Classes / Modules This is similar to Objects with fields_objects, but the fields object is stored at a different offset. We’re differentiating this from boxable classes and modules because those are harder to support. 4. Other "Other" pattern is for objects that are rare, or have difficult-to-implement access patterns. This includes: * Boxable classes and modules * Structs (for now) * Objects that use the geniv table Proposed shape bit layout: ``` Current shape_id_t is 32 bits: 31 28 27 26 25 24 23 22 19 18 0 +-----------+--+--+--+--+--+------------+----------------------------+ | unused |L1|L0|OI|FR|CX| heap index | shape tree offset | +-----------+--+--+--+--+--+------------+----------------------------+ | | | | | | | | | | | | | +-- bits 0-18: SHAPE_ID_OFFSET_MASK | | | | | +--------------- bits 19-22: SHAPE_ID_HEAP_INDEX_MASK | | | | +------------------ bit 23: SHAPE_ID_FL_COMPLEX | | | +--------------------- bit 24: SHAPE_ID_FL_FROZEN | | +------------------------ bit 25: SHAPE_ID_FL_HAS_OBJECT_ID +--+--------------------------- bits 26-27: SHAPE_ID_LAYOUT_MASK ``` The important part about these layout patterns is that they do not reflect the _type_ of object, only how the object is laid out in memory. For example, we currently treat structs as "other", but we can refactor them to have the same layout as "Objects with fields_objects", and when we do that they should get a different bit in the shape header. This commit only reserves the two bits, it doesn't use them in the JIT compiler yet. Co-Authored-By: John Hawthorn <john@hawthorn.email> Co-Authored-By: Max Bernstein <tekknolagi@gmail.com> * Update gc.c Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com> * Update shape.h Co-authored-by: Jean Boussier <jean.boussier@gmail.com> * fix function name * Update shape.c Co-authored-by: Jean Boussier <jean.boussier@gmail.com> * fix function name * Revert "Update shape.c" This reverts commit 900711defc6c541a93f3393a350819ae88cf87f1. * add comment --------- Co-authored-by: John Hawthorn <john@hawthorn.email> Co-authored-by: Max Bernstein <tekknolagi@gmail.com> Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com> Co-authored-by: Jean Boussier <jean.boussier@gmail.com>
2026-05-06shapes: Rename `TOO_COMPLEX` in just `COMPLEX`Jean Boussier
The `too_` prefix wasn't consistently used and just make the thing longer for no benefit.
2026-05-06shape.c: transition to complex when `max_capacity` is reachedJean Boussier
Now that we have 1024B slots, we can store up to 126 fields inline. Objects larger than this are rare if not non-existent, hence we can get rid of the `malloc` path for imemo/fields and simply transition to `TOO_COMPLEX`. This additionally allows to shrink `attr_index_t` from 16 to 8B. Note: the ZJIT "ivar on extended" tests are renamed as "complex" because "extended" AKA malloc allocated imemo/fields no longer exists. They're now complex fields, AKA st tables. rb_class_allocate_instance: start as complex when over max_fields If `RCLASS_MAX_IV_COUNT` is over `max_fields`, allocating a large slot to end up transitioning to `TOO_COMPLEX` is wasteful. We might as well start as complex directly.
2026-05-05shape.c: finalize the "offset" renameJean Boussier
To better distinguish fully formed shape ids from "naked" offsets.
2026-05-04test_shapes.rb: replace hardcoded MANY_IVS constantJean Boussier
Since more size pools have been added, this constant was outdated.
2026-05-02Never increment `max_iv_count` on Object / BasicObject.Jean Boussier
Otherwise some code defining ivars on a naked object early during program boot can cause all objects to be larger than needed. This seem like it was always the intention, but wasn't quite properly prevented. Also stop updating `max_iv_count` during GC marking as it is redundant.
2026-01-09Use `assert_ruby_status` if no assertionNobuyoshi Nakada
2025-08-28rb_ivar_delete: allow complex transitionJean Boussier
`rb_ivar_delete` would force a new shape to be created. And in the case of a shape exhaustion, it wouldn't handle T_CLASS/T_MODULE correctly.
2025-08-21Remove unused SPECIAL_CONST_SHAPE_IDÉtienne Barrié
Its usage was removed in 306d50811dd060d876d1eb364a0d5e6106f5e4f1.
2025-08-19Fix special consts unit tests for i686 (#14271)Max Bernstein
32-bit platforms do not have flonum and something about the static symbol test was flaky.
2025-08-18Don't allow looking at the shape ID of immediates (#14266)Max Bernstein
It only makes sense for heap objects.
2025-08-13imemo_fields: store owner object in RBasic.klassJean Boussier
It is much more convenient than storing the klass, especially when dealing with `object_id` as it allows to update the id2ref table without having to dereference the owner, which may be garbage at that point.
2025-08-01Fix rb_shape_transition_object_id transition to TOO_COMPLEXJean Boussier
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-06-07Get rid of SHAPE_T_OBJECTJean Boussier
Now that we have the `heap_index` in shape flags we no longer need `T_OBJECT` shapes. Notes: Merged: https://github.com/ruby/ruby/pull/13556
2025-06-07Replicate `heap_index` in shape_id flags.Jean Boussier
This is preparation to getting rid of `T_OBJECT` transitions. By first only replicating the information it's easier to ensure consistency. Notes: Merged: https://github.com/ruby/ruby/pull/13556
2025-06-05Refactor raw accesses to rb_shape_t.capacityJean Boussier
Notes: Merged: https://github.com/ruby/ruby/pull/13524
2025-06-05Get rid of `rb_shape_t.flags`Jean Boussier
Now all flags are only in the `shape_id_t`, and can all be checked without needing to dereference a pointer. Notes: Merged: https://github.com/ruby/ruby/pull/13515
2025-06-04Get rid of frozen shapes.Jean Boussier
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. Notes: Merged: https://github.com/ruby/ruby/pull/13289
2025-05-31`Ractor::Port`Koichi Sasada
* Added `Ractor::Port` * `Ractor::Port#receive` (support multi-threads) * `Rcator::Port#close` * `Ractor::Port#closed?` * Added some methods * `Ractor#join` * `Ractor#value` * `Ractor#monitor` * `Ractor#unmonitor` * Removed some methods * `Ractor#take` * `Ractor.yield` * Change the spec * `Racotr.select` You can wait for multiple sequences of messages with `Ractor::Port`. ```ruby ports = 3.times.map{ Ractor::Port.new } ports.map.with_index do |port, ri| Ractor.new port,ri do |port, ri| 3.times{|i| port << "r#{ri}-#{i}"} end end p ports.each{|port| pp 3.times.map{port.receive}} ``` In this example, we use 3 ports, and 3 Ractors send messages to them respectively. We can receive a series of messages from each port. You can use `Ractor#value` to get the last value of a Ractor's block: ```ruby result = Ractor.new do heavy_task() end.value ``` You can wait for the termination of a Ractor with `Ractor#join` like this: ```ruby Ractor.new do some_task() end.join ``` `#value` and `#join` are similar to `Thread#value` and `Thread#join`. To implement `#join`, `Ractor#monitor` (and `Ractor#unmonitor`) is introduced. This commit changes `Ractor.select()` method. It now only accepts ports or Ractors, and returns when a port receives a message or a Ractor terminates. We removes `Ractor.yield` and `Ractor#take` because: * `Ractor::Port` supports most of similar use cases in a simpler manner. * Removing them significantly simplifies the code. We also change the internal thread scheduler code (thread_pthread.c): * During barrier synchronization, we keep the `ractor_sched` lock to avoid deadlocks. This lock is released by `rb_ractor_sched_barrier_end()` which is called at the end of operations that require the barrier. * fix potential deadlock issues by checking interrupts just before setting UBF. https://bugs.ruby-lang.org/issues/21262 Notes: Merged: https://github.com/ruby/ruby/pull/13445
2025-05-13variable.c: Refactor rb_obj_field_* to take shape_id_tJean Boussier
Notes: Merged: https://github.com/ruby/ruby/pull/13314
2025-05-08Fix `remove_instance_variable` on complex objectsJean Boussier
Introduced in: https://github.com/ruby/ruby/pull/13159 Now that there is no longer a unique TOO_COMPLEX shape with no children, checking `shape->type == TOO_COMPLEX` is incorrect. Notes: Merged: https://github.com/ruby/ruby/pull/13280
2025-05-08Move `object_id` in object fields.Jean Boussier
And get rid of the `obj_to_id_tbl` It's no longer needed, the `object_id` is now stored inline in the object alongside instance variables. We still need the inverse table in case `_id2ref` is invoked, but we lazily build it by walking the heap if that happens. The `object_id` concern is also no longer a GC implementation concern, but a generic implementation. Co-Authored-By: Matt Valentine-House <matt@eightbitraptor.com> Notes: Merged: https://github.com/ruby/ruby/pull/13159
2025-05-08Rename `ivptr` -> `fields`, `next_iv_index` -> `next_field_index`Jean Boussier
Ivars will longer be the only thing stored inline via shapes, so keeping the `iv_index` and `ivptr` names would be confusing. Instance variables won't be the only thing stored inline via shapes, so keeping the `ivptr` name would be confusing. `field` encompass anything that can be stored in a VALUE array. Similarly, `gen_ivtbl` becomes `gen_fields_tbl`. Notes: Merged: https://github.com/ruby/ruby/pull/13159
2025-02-13[Feature #21116] Extract RJIT as a third-party gemNobuyoshi Nakada
Notes: Merged: https://github.com/ruby/ruby/pull/12740
2024-04-11compile.c: use rb_enc_interned_str to reduce allocationsJean Boussier
The `rb_fstring(rb_enc_str_new())` pattern is inneficient because: - It passes a mutable string to `rb_fstring` so if it has to be interned it will first be duped. - It an equivalent interned string already exists, we allocated the string for nothing. With `rb_enc_interned_str` we either directly get the pre-existing string with 0 allocations, or efficiently directly intern the one we create without first duping it.
2024-03-13Make special const and too complex shapes before T_OBJECT shapesPeter Zhu
2024-03-13Don't create per size pool shapes for non-T_OBJECTPeter Zhu
2024-01-10Fix memory leak when duplicating too complex objectPeter Zhu
[Bug #20162] Creating a ST table then calling st_replace leaks memory because the st_replace overwrites the ST table without freeing any of the existing memory. This commit changes it to use st_copy instead. For example: RubyVM::Shape.exhaust_shapes o = Object.new o.instance_variable_set(:@a, 0) 10.times do 100_000.times { o.dup } puts `ps -o rss= -p #{$$}` end Before: 23264 33600 42672 52160 61600 71728 81056 90528 100560 109840 After: 14752 14816 15584 15584 15664 15664 15664 15664 15664 15664
2023-11-29Add missing assertion in test_use_all_shapes_then_freezePeter Zhu
2023-11-28Fix Ractor sharing for too complex ObjectsPeter Zhu
2023-11-28Fix Ractor sharing for too complex generic ivarsPeter Zhu
2023-11-24Switch shape test to use exhaust_shapesPeter Zhu
2023-11-23Add tests for compaction during evacuation of ivarsPeter Zhu
Extracted from PR #8932. Co-Authored-By: Jean Boussier <byroot@ruby-lang.org>
2023-11-22Speedup test_shape.rbJean Boussier
Many tests start by exhausting all shapes, which is a slow process. By exposing a method to directly move the bump allocator forward we cut test runtime in half. Before: ``` Finished tests in 1.544756s ``` After: ``` Finished tests in 0.759733s, ```
2023-11-21Fix memory leak when evacuating generic ivarsPeter Zhu
The lookup in the table is using the wrong key when converting generic instance variables to too complex, which means that it never looks up the entry which leaks memory when the entry is overwritten.
2023-11-20Fix crash when evacuating generic ivarPeter Zhu
When transitioning generic instance variable objects to too complex, we set the shape first before performing inserting the new gen_ivtbl. The st_insert for the new gen_ivtbl could allocate and cause a GC. If that happens, then it will crash because the object will have a too complex shape but not yet be backed by a st_table. This commit changes the order so that the insert happens first before the new shape is set. The following script reproduces the issue: ``` o = [] o.instance_variable_set(:@a, 1) i = 0 o = Object.new while RubyVM::Shape.shapes_available > 0 o.instance_variable_set(:"@i#{i}", 1) i += 1 end ary = 1_000.times.map { [] } GC.stress = true ary.each do |o| o.instance_variable_set(:@a, 1) o.instance_variable_set(:@b, 1) end ```
2023-11-20Fix crash when iterating over generic ivarsPeter Zhu
2023-11-17Fix corruption when out of shape during ivar removePeter Zhu
Reproduction script: ``` o = Object.new 10.times { |i| o.instance_variable_set(:"@a#{i}", i) } i = 0 a = Object.new while RubyVM::Shape.shapes_available > 2 a.instance_variable_set(:"@i#{i}", 1) i += 1 end o.remove_instance_variable(:@a0) puts o.instance_variable_get(:@a1) ``` Before this patch, it would incorrectly output `2` and now it correctly outputs `1`.
2023-11-13Don't overwrite shape capacity when removing ivarPeter Zhu
Other objects may be using the shape, so we can't change the capacity otherwise the other objects may have a buffer overflow.
2023-11-06generic_ivar_set: properly check for TOO_COMPLEX on capacity transitionJean Boussier
2023-11-03rb_ivar_defined: handle complex modulesJean Boussier
It was assuming only objects can be complex.
2023-11-02Fix vm_getivar to handle module with TOO_COMPLEX shapeJean Boussier
2023-11-02Make every initial size pool shape a root shapePeter Zhu
This commit makes every initial size pool shape a root shape and assigns it a capacity of 0.
2023-11-02Better handle running out of shapes in remove_shape_recursiveJean Boussier
2023-11-01Fix SystemStackError in test_run_out_of_shape_for_class_cvarPeter Zhu
2023-11-01Fix remove_class_variable for too complex classesPeter Zhu
2023-11-01remove_instance_variable: Handle running out of shapesJean Boussier
`remove_shape_recursive` wasn't considering that if we run out of shapes, it might have to transition to SHAPE_TOO_COMPLEX. When this happens, we now return with an error and the caller initiates the evacuation.
2023-11-01Fix removing non-existent ivar for too complexPeter Zhu