Commit graph

129 commits

Author SHA1 Message Date
Nobuyoshi Nakada
13e6fe9bbc
[DOC] Hide RubyVM::Shape that is for debug from RDoc totally 2024-12-24 21:31:52 +09:00
Kunshan Wang
8ae7c22972 Annotate anonymous mmap
Use PR_SET_VMA_ANON_NAME to set human-readable names for anonymous
virtual memory areas mapped by `mmap()` when compiled and run on Linux
5.17 or higher.  This makes it convenient for developers to debug mmap.
2024-11-21 13:48:05 -05:00
Matt Valentine-House
8e7df4b7c6 Rename size_pool -> heap
Now that we've inlined the eden_heap into the size_pool, we should
rename the size_pool to heap. So that Ruby contains multiple heaps, with
different sized objects.

The term heap as a collection of memory pages is more in memory
management nomenclature, whereas size_pool was a name chosen out of
necessity during the development of the Variable Width Allocation
features of Ruby.

The concept of size pools was introduced in order to facilitate
different sized objects (other than the default 40 bytes). They wrapped
the eden heap and the tomb heap, and some related state, and provided a
reasonably simple way of duplicating all related concerns, to provide
multiple pools that all shared the same structure but held different
objects.

Since then various changes have happend in Ruby's memory layout:

* The concept of tomb heaps has been replaced by a global free pages list,
  with each page having it's slot size reconfigured at the point when it
  is resurrected
* the eden heap has been inlined into the size pool itself, so that now
  the size pool directly controls the free_pages list, the sweeping
  page, the compaction cursor and the other state that was previously
  being managed by the eden heap.

Now that there is no need for a heap wrapper, we should refer to the
collection of pages containing Ruby objects as a heap again rather than
a size pool
2024-10-03 21:20:09 +01:00
Nobuyoshi Nakada
196d59f690
Parenthesize macro arguments 2024-08-16 15:43:43 +09:00
Nobuyoshi Nakada
129b4936bf
Simplify and clarify bitmask calculation 2024-08-16 15:43:43 +09:00
Raed Rizqie
018bd07f07
Fix some warnings
* Fix unused functions when no `mmap`.

  ```
  shape.c:285:1: warning: unused function 'redblack_insert' [-Wunused-function]
    285 | redblack_insert(redblack_node_t * tree, ID key, rb_shape_t * value)
        | ^~~~~~~~~~~~~~~
  ```

* Fix unknown warning group '-Wmaybe-uninitialized' with clang.

  ```
  thread_win32.c:596:1: warning: unknown warning group '-Wmaybe-uninitialized', ignored [-Wunknown-warning-option]
    596 | COMPILER_WARNING_IGNORED(-Wmaybe-uninitialized)
        | ^
  ```

Co-authored-by: Nobuyoshi Nakada <nobu.nakada@gmail.com>
2024-08-16 14:51:21 +09:00
Jean Boussier
f7b53a75b6 Do not emit shape transition warnings when YJIT is compiling
[Bug #20522]

If `Warning.warn` is redefined in Ruby, emitting a warning would invoke
Ruby code, which can't safely be done when YJIT is compiling.
2024-06-04 19:21:01 +02:00
Nobuyoshi Nakada
d224bfdc32
redblack_cache_ancestors is enabled only when mmap is available 2024-05-09 10:11:18 +09:00
Peter Zhu
49753cd082 Use xcalloc for allocating shape tree
The GC is initialized by this point, so we can use xcalloc instead of
ruby_mimcalloc.
2024-04-25 17:11:34 -04:00
Peter Zhu
214811974b Add ruby_mimcalloc
Many places call ruby_mimmalloc then MEMZERO. This can be reduced by
using ruby_mimcalloc instead.
2024-04-24 15:30:43 -04:00
Aaron Patterson
9579cf45d5 If we have a shape cache we should use it
If there is a shape cache, then we should believe the results instead of
doing a linear search for non-existent items

This fixes a case where checking the index of an undefined ivar would
result in an O(n) search. Now we get O(log n).

Benchmark is as follows:

```ruby
N = ARGV[0].to_i

class ManyIVs
  class_eval "def initialize;" +
    N.times.map { "@a#{_1} = #{_1}" }.join("\n") +
    "end"

  def check
    defined?(@not)
  end
end

class Subclass < ManyIVs
  def initialize
    super
    @foo = 123
  end
end

def t
  s = Process.clock_gettime Process::CLOCK_MONOTONIC
  yield
  Process.clock_gettime(Process::CLOCK_MONOTONIC) - s
end

def test
  a = ManyIVs.new
  b = Subclass.new
  t { 200000.times { a.check; b.check } }
end

puts "#{N},#{test}"
```

On the master branch:

```
$ for i in (seq 1 3 32); ./miniruby test.rb $i; end
1,0.015619999991031364
4,0.013061000005109236
7,0.013365999999223277
10,0.015474999992875382
13,0.017674999980954453
16,0.020055999979376793
19,0.02260500000556931
22,0.0254080000158865
25,0.02806599999894388
28,0.031244999991031364
31,0.034568000002764165
```

On this branch:

```
$ for i in (seq 1 3 32); ./miniruby test.rb $i; end
1,0.015848999988520518
4,0.013225000002421439
7,0.013049000001046807
10,0.010697999998228624
13,0.010902000009082258
16,0.011448000004747882
19,0.01151199999731034
22,0.011539999977685511
25,0.01173300002119504
28,0.011900000012246892
31,0.012278999987756833
```
2024-03-30 19:24:36 -07:00
Peter Zhu
42bfbe9aa6 Remove unused size_pool_edge_names 2024-03-13 14:50:44 -04:00
Peter Zhu
c6089b5654 Don't allow SHAPE_T_OBJECT in rb_shape_alloc_new_child 2024-03-13 09:55:52 -04:00
Peter Zhu
3896f9940e Make special const and too complex shapes before T_OBJECT shapes 2024-03-13 09:55:52 -04:00
Peter Zhu
6b0434c0f7 Don't create per size pool shapes for non-T_OBJECT 2024-03-13 09:55:52 -04:00
Peter Zhu
6ad347a105 Don't directly read the SIZE_POOL_COUNT in shapes
This removes the assumption about SIZE_POOL_COUNT for shapes.
2024-03-13 09:55:52 -04:00
Peter Zhu
df5b8ea4db Remove unneeded RUBY_FUNC_EXPORTED 2024-02-23 10:24:21 -05:00
Aaron Patterson
8b8dcc7af1 Handle mmap failures for redblack tree cache
The redblack tree cache is totally optional, so if we can't allocate
room for the cache, then just pretend as if the cache is full if mmap
fails
2024-01-12 09:31:36 -08:00
Jean Boussier
7c2d819862 Fix a grammar issue in the shape performance warning message 2023-12-20 09:28:45 +01:00
Jean Boussier
f6ad49b87c Use #initialize instead of initialize in shape perf warning
This is more consistent with other messages.
2023-12-18 13:42:02 +01:00
Nobuyoshi Nakada
40fc9b070c
[DOC] No document for internal or debug methods 2023-12-18 20:17:45 +09:00
Jean Boussier
ba1d1522d3 Make the SHAPE_TOO_COMPLEX performance warning more actionable
As suggested by Mame, we should try to help users fix the issues
without having to lookup the meaning of the warning.
2023-12-18 10:33:18 +01:00
Peter Zhu
12e3b07455 Re-embed when removing Object instance variables
Objects with the same shape must always have the same "embeddedness"
(either embedded or heap allocated) because YJIT assumes so. However,
using remove_instance_variable, it's possible that some objects are
embedded and some are heap allocated because it does not re-embed heap
allocated objects.

This commit changes remove_instance_variable to re-embed Object
instance variables when it becomes small enough.
2023-12-06 11:34:07 -05:00
Peter Zhu
4a7151a8e4 Deduplicate assertions in redblack_balance
The bug in i686 was fixed in commit
71babe5536.
2023-12-06 10:16:58 -05:00
Peter Zhu
56eccb350b Fix alphabetical order of include in shape.c 2023-12-05 16:25:34 -05:00
Peter Zhu
43ef0da0fb Add assertions for shape cache grandchild nodes 2023-12-01 09:56:32 -05:00
Peter Zhu
4541e192d9 Add assertions in redblack_balance
These assertions check that binary search tree invariants are held for
the new tree.
2023-11-30 16:48:51 -05:00
Peter Zhu
a1647c460f Rename variables redblack_balance
It's too difficult for me to keep track that y is the new node, x is the
new left node, z is the new right node, a is the new left left node,
b is the new left right node, c is the new right left node, and d is
the new right right node. This commit refactors the variable names to be
more descriptive.
2023-11-30 15:41:08 -05:00
Peter Zhu
57cb47bfe2 Assert that the left and right nodes are correct 2023-11-29 10:30:00 -05:00
Peter Zhu
cb70994b0e Assert node inserted into red-black tree exists 2023-11-28 13:37:38 -05:00
Peter Zhu
4d71f70fd1 Add assertions to check created red-black tree 2023-11-27 14:05:25 -05:00
Peter Zhu
872922b03d Fix indentation in comment in shape.c 2023-11-27 14:04:56 -05:00
Peter Zhu
b93a1bb40b Verify correctness of shape cache
This commit adds assertions to verify that the shape cache is correct
compared to the shape tree.
2023-11-25 09:32:36 -05:00
Peter Zhu
564ef66e26 Verify that duplicate shape is not created
This adds an assertion that the instance variable does not already exist
in the shape tree when creating a new shape.
2023-11-25 09:32:36 -05:00
Alan Wu
341321f115 Fix off-by-one with RubyVM::Shape.exhaust_shapes
Previously, the method left one shape available (MAX_SHAPE_ID) when
called without arguments.
2023-11-22 12:17:58 -05:00
Jean Boussier
2d7fb9c2fa Speedup test_shape.rb
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-22 10:12:07 +01:00
Peter Zhu
3dd77bc056 Fix corruption when out of shape during ivar remove
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-17 13:08:43 -08:00
Jean Boussier
94c9f16663 Refactor rb_obj_evacuate_ivs_to_hash_table
That function is a bit too low level to called from multiple
places. It's always used in tandem with `rb_shape_set_too_complex`
and both have to know how the object is laid out to update the
`iv_ptr`.

So instead we can provide two higher level function:

  - `rb_obj_copy_ivs_to_hash_table` to prepare a `st_table` from an
    arbitrary oject.
  - `rb_obj_convert_to_too_complex` to assign the new `st_table`
    to the old object, and safely free the old `iv_ptr`.

Unfortunately both can't be combined into one, because `rb_obj_copy_ivar`
need `rb_obj_copy_ivs_to_hash_table` to copy from one object
to another.
2023-11-17 09:19:21 +01:00
Peter Zhu
fabf5bead7 Don't overwrite shape capacity when removing ivar
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-13 18:26:36 -05:00
Peter Zhu
68869e9bd9 Revert "Revert "Remove SHAPE_CAPACITY_CHANGE shapes""
This reverts commit 5f3fb4f4e3.
2023-11-13 18:26:36 -05:00
Peter Zhu
5f3fb4f4e3 Revert "Remove SHAPE_CAPACITY_CHANGE shapes"
This reverts commit f6910a6112.

We're seeing crashes in the test suite of Shopify's core monolith after
this change.
2023-11-10 11:27:49 -05:00
Peter Zhu
f6910a6112 Remove SHAPE_CAPACITY_CHANGE shapes
We don't need to create a shape to transition capacity as we can
transition the capacity when the capacity of the SHAPE_IVAR changes.
2023-11-09 09:25:02 -05:00
Jean Boussier
d898e8d6f8 Refactor rb_shape_transition_shape_capa out
Right now the `rb_shape_get_next` shape caller need to
first check if there is capacity left, and if not call
`rb_shape_transition_shape_capa` before it can call `rb_shape_get_next`.

And on each of these it needs to checks if we got a TOO_COMPLEX
back.

All this logic is duplicated in the interpreter, YJIT and RJIT.

Instead we can have `rb_shape_get_next` do the capacity transition
when needed. The caller can compare the old and new shapes capacity
to know if resizing is needed. It also can check for TOO_COMPLEX
only once.
2023-11-08 11:02:55 +01:00
Jean Boussier
b92b9e1e9e vm_getivar: assume the cached shape_id like have a common ancestor
When an inline cache misses, it is very likely that the stale shape_id
and the current instance shape_id have a close common ancestor.

For example if the instance variable is sometimes frozen sometimes
not, one of the two shape will be the direct parent of the other.

Another pattern that commonly cause IC misses is "memoization",
in such case the object will have a "base common shape" and then
a number of close descendants.

In addition, when we find a common ancestor, we store it in the
inline cache instead of the current shape. This help prevent the
cache from flip-flopping, ensuring the next lookup will be marginally
faster and more generally avoid writing in memory too much.

However, now that shapes have an ancestors index, we only check
for a few ancestors before falling back to use the index.

So overall this change speeds up what is assumed to be the more common
case, but makes what is assumed to be the less common case a bit slower.

```
compare-ruby: ruby 3.3.0dev (2023-10-26T05:30:17Z master 701ca070b4) [arm64-darwin22]
built-ruby: ruby 3.3.0dev (2023-10-26T09:25:09Z shapes_double_sear.. a723a85235) [arm64-darwin22]
warming up......

|                                     |compare-ruby|built-ruby|
|:------------------------------------|-----------:|---------:|
|vm_ivar_stable_shape                 |     11.672M|   11.679M|
|                                     |           -|     1.00x|
|vm_ivar_memoize_unstable_shape       |      7.551M|   10.506M|
|                                     |           -|     1.39x|
|vm_ivar_memoize_unstable_shape_miss  |     11.591M|   11.624M|
|                                     |           -|     1.00x|
|vm_ivar_unstable_undef               |      9.037M|    7.981M|
|                                     |       1.13x|         -|
|vm_ivar_divergent_shape              |      8.034M|    6.657M|
|                                     |       1.21x|         -|
|vm_ivar_divergent_shape_imbalanced   |     10.471M|    9.231M|
|                                     |       1.13x|         -|
```

Co-Authored-By: John Hawthorn <john@hawthorn.email>
2023-11-03 12:47:43 +01:00
Peter Zhu
38ba040d8b Make every initial size pool shape a root shape
This commit makes every initial size pool shape a root shape and assigns
it a capacity of 0.
2023-11-02 13:42:11 -04:00
Jean Boussier
33795931a0 Better handle running out of shapes in remove_shape_recursive 2023-11-02 12:00:42 +01:00
Jean Boussier
b77148ae9f remove_instance_variable: Handle running out of shapes
`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-01 15:21:55 +01:00
Peter Zhu
e2d950733e Add ST table to gen_ivtbl for complex shapes
On 32-bit systems, we must store the shape ID in the gen_ivtbl to not
lose the shape. If we directly store the ST table into the generic
ivar table, then we lose the shape. This makes it impossible to
determine the shape of the object and whether it is too complex or not.
2023-10-31 12:07:54 -04:00
Jean Boussier
4aacc559d9 Handle running out of shapes in Object#dup
There is a handful of call sites where we may transition to
OBJ_TOO_COMPLEX_SHAPE if we just ran out of shapes, but that
weren't handling it properly.
2023-10-31 12:07:54 -04:00
Jean Boussier
4aee6931c3 Make get_next_shape_internal idempotent
Since the check for MAX_SHAPE_ID was done before even checking
if the transition we're looking for even exists, as soon as the
max shape is reached, get_next_shape_internal would always return
`TOO_COMPLEX` regardless of whether the transition we're looking
for already exist or not.

In addition to entirely de-optimize all newly created objects, it
also made an assertion fail in `vm_setivar`:

```
vm_setivar:rb_shape_get_next_iv_shape(rb_shape_get_shape_by_id(source_shape_id), id) == dest_shape
```
2023-10-27 21:09:03 +02:00