* 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>
Almost every objects are now WB protected, with just a few
exceptions, hence we can provide a much simpler interface.
It's also much easier to find the remaining unprotected objects.
[Bug #22013]
Up to a certain size, only `eql?` is used, but for larger arrays
a Hash is used an `#hash` becomes necessary.
We could consider always checking `#hash` for consistency, but
that would decrease performance.
At the very least we can clarify the documentation.
For now the provided size is just for GC statistics, but in the future
we may want to forward it to C23's `free_sized` and passing an incorrect
size to it is undefined behavior.
* Document Range#to_set
* Update Thread#raise and Fiber#raise signatures and docs
* Add reference to String#strip to character_selectors.rdoc
* Update *nil docs when calling methods
* Enhance Array#find and #rfind docs
* Add a notice to Kernel#raise about cause:
Implement Array#rfind, which is the same as find except from the
other side of the Array. Also implemented Array#find (as opposed to
the generic one on Enumerable because it is significantly faster
and to keep the implementations together.
[Feature #21678]
When all elements are strings, we never have to recalculate the length
of the array because there are no conversion methods that are called, so
the length will never change. This speeds up the fast path by ~10%.
```ruby
a = ["1"*10, "2"*10, "3"*10, "4"*10, "5"*10] * 10
10_000_000.times do
a.join
end
```
```
hyperfine --warmup 1 'ruby ../ruby2/test.rb' './exe/ruby ../ruby2/test.rb'
Benchmark 1: ruby ../ruby2/test.rb
Time (mean ± σ): 3.779 s ± 0.053 s [User: 3.754 s, System: 0.017 s]
Range (min … max): 3.715 s … 3.874 s 10 runs
Benchmark 2: ./exe/ruby ../ruby2/test.rb
Time (mean ± σ): 3.411 s ± 0.038 s [User: 3.387 s, System: 0.017 s]
Range (min … max): 3.360 s … 3.472 s 10 runs
Summary
./exe/ruby ../ruby2/test.rb ran
1.11 ± 0.02 times faster than ruby ../ruby2/test.rb
```
to adopt strict shareable rule.
* (basically) shareable objects only refer shareable objects
* (exception) shareable objects can refere unshareable objects
but should not leak reference to unshareable objects to Ruby world
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).
The inspect format was intentionally changed as an outcome of
[Bug #20433] [ruby-core:118668], but some documentation update
was missing, as [Bug #20962] pointed out. Update some output
examples that clearly use Hash#inspect.