summaryrefslogtreecommitdiff
path: root/shape.c
AgeCommit message (Collapse)Author
2023-05-20`rb_bug` prints a newline after the messageNobuyoshi Nakada
2023-05-03Make the maximum shapes variation warning non-verboseJean Boussier
[Feature #19538] Since that category is not enabled by default, making it a verbose warning is redundant. Enabling performance warning should work with the default verbosity level. Notes: Merged: https://github.com/ruby/ruby/pull/7733
2023-04-18Return NULL to indicate the next shape isn't foundAaron Patterson
During compaction we must fix up shapes on objects who were extended but then became embedded. `rb_shape_traverse_from_new_root` is supposed to walk shape trees looking for a matching shape. When a shape has a "single child" we weren't returning NULL when the edge names didn't match. In the case of a single outgoing edge, this patch returns NULL when the child edge name doesn't match (similar to the case when a shape has a hash of outgoing edges) Notes: Merged: https://github.com/ruby/ruby/pull/7735
2023-04-16Move shape ID to flags for classes on 32 bitPeter Zhu
Moves shape ID to FL_USER4 to FL_USER19 for the shape ID on 32 bit systems. This makes the rb_classext_struct smaller so that it can be embedded. Notes: Merged: https://github.com/ruby/ruby/pull/7719
2023-04-13Emit a performance warning when a class reached max variationsJean Boussier
[Feature #19538] This new `peformance` warning category is disabled by default. It needs to be specifically enabled via `-W:performance` or `Warning[:performance] = true` Notes: Merged: https://github.com/ruby/ruby/pull/7708
2023-04-06[Feature #19474] Refactor NEWOBJ macrosMatt Valentine-House
NEWOBJ_OF is now our canonical newobj macro. It takes an optional ec Notes: Merged: https://github.com/ruby/ruby/pull/7393
2023-04-06Pull the shape tree out of the vm objectMatt Valentine-House
Notes: Merged: https://github.com/ruby/ruby/pull/7393
2023-03-22Lazily allocate id tables for childrenAaron Patterson
This patch lazily allocates id tables for shape children. If a shape has only one single child, it tags the child with a bit. When we read children, if the id table has the bit set, we know it's a single child. If we need to add more children, then we create a new table and evacuate the child to the new table. Co-Authored-By: Matt Valentine-House <matt@eightbitraptor.com> Notes: Merged: https://github.com/ruby/ruby/pull/7512
2023-03-22pull child allocation in to a different functionAaron Patterson
Notes: Merged: https://github.com/ruby/ruby/pull/7512
2023-03-22combine allocation functionsAaron Patterson
Notes: Merged: https://github.com/ruby/ruby/pull/7512
2023-03-22Make shape functions staticAaron Patterson
These functions don't need to be in the header file, we can declare them as static. Notes: Merged: https://github.com/ruby/ruby/pull/7512
2023-03-22Fix shape allocation limitsAaron Patterson
We can only allocate enough shapes to fit in the shape buffer. MAX_SHAPE_ID was based on the theoretical maximum number of shapes we could have, not on the amount of memory we can actually consume. This commit changes the MAX_SHAPE_ID to be based on the amount of memory we're allowed to consume. Co-Authored-By: Jemma Issroff <jemmaissroff@gmail.com> Notes: Merged: https://github.com/ruby/ruby/pull/7556
2023-03-18Fix frozen status loss when moving objectsPeter Zhu
[Bug #19536] When objects are moved between size pools, their frozen status is lost in the shape. This will cause the frozen check to be bypassed when there is an inline cache. For example, the following script should raise a FrozenError, but doesn't on Ruby 3.2 and master. class A def add_ivars @a = @b = @c = @d = 1 end def set_a @a = 10 end end a = A.new a.add_ivars a.freeze b = A.new b.add_ivars b.set_a # Set the inline cache in set_a GC.verify_compaction_references(expand_heap: true, toward: :empty) a.set_a Notes: Merged: https://github.com/ruby/ruby/pull/7553
2023-03-10Revert "Allow classes and modules to become too complex"Aaron Patterson
This reverts commit 69465df4242f3b2d8e55fbe18d7c45b47b40a626.
2023-03-09Allow classes and modules to become too complexHParker
This makes the behavior of classes and modules when there are too many instance variables match the behavior of objects with too many instance variables. Notes: Merged: https://github.com/ruby/ruby/pull/7349
2023-03-06Resurrect symbols used by ObjectSpaceTakashi Kokubun
Notes: Merged: https://github.com/ruby/ruby/pull/7459
2023-03-06Stop exporting symbols for MJITTakashi Kokubun
Notes: Merged: https://github.com/ruby/ruby/pull/7459
2023-02-15Handle all non-object type objectsHaldun Bayhantopcu
Notes: Merged: https://github.com/ruby/ruby/pull/7314
2023-02-15Fix removing ivars from clases and modules.Haldun Bayhantopcu
Co-authored-by: Adam Hess <hparker@github.com> Notes: Merged: https://github.com/ruby/ruby/pull/7314
2023-02-09Merge gc.h and internal/gc.hMatt Valentine-House
[Feature #19425] Notes: Merged: https://github.com/ruby/ruby/pull/7273
2023-02-06Limit maximum number of IVs on a shape on T_OBJECTSJemma Issroff
Create SHAPE_MAX_NUM_IVS (currently 50) and limit all shapes of T_OBJECTS to that number of IVs. When a shape with a T_OBJECT has more than 50 IVs, fall back to the obj_too_complex shape which uses hash lookup for ivs. Note that a previous version of this commit 78fcc9847a9db6d42c8c263154ec05903a370b6b was reverted in 88f2b94065be3fcd6769a3f132cfee8ecfb663b8 because it did not account for non-T_OBJECTS Notes: Merged: https://github.com/ruby/ruby/pull/7188
2023-01-30Remove dead code in shapes.c and shapes.hPeter Zhu
Notes: Merged: https://github.com/ruby/ruby/pull/7200
2023-01-26Revert "Limit maximum number of IVs on a shape"Aaron Patterson
This reverts commit 78fcc9847a9db6d42c8c263154ec05903a370b6b.
2023-01-25Limit maximum number of IVs on a shapeJemma Issroff
Create SHAPE_MAX_NUM_IVS (currently 50) and limit all shapes to that number of IVs. When a shape has more than 50 IVs, fallback to the obj_too_complex shape which uses hash lookup for ivs. Notes: Merged: https://github.com/ruby/ruby/pull/7183
2023-01-05Fix undefined behavior in shape.cPeter Zhu
Under strict aliasing, writing to the memory location of a different type is not allowed and will result in undefined behavior. This was happening in shape.c due to `rb_id_table_lookup` writing to the memory location of `VALUE *` that was casted from a `rb_shape_t **`. This was causing test failures when compiled with LTO. Fixes [Bug #19248] Co-Authored-By: Alan Wu <alanwu@ruby-lang.org> Notes: Merged: https://github.com/ruby/ruby/pull/7067
2022-12-22Hide RubyVM::Shape's interface as much as possible [ci skip]Takashi Kokubun
RubyVM::Shape is usually not available (you need SHAPE_DEBUG macro, which is not defined by default). So it seems confusing to leave RubyVM::Shape in the document. This hides only method definitions because, well, I can't find a way to hide things defined by rb_define_const or rb_struct_define_under. I gave up making the C-based documentation right. You should define things in Ruby instead.
2022-12-16Clean up Ruby Shape APIJemma Issroff
Make printing shapes better, use a struct instead of specific methods for each field on a shape. Co-Authored-By: Aaron Patterson <tenderlove@ruby-lang.org> Notes: Merged: https://github.com/ruby/ruby/pull/6942
2022-12-15Fix Object Movement allocation in GCMatt Valentine-House
When moving Objects between size pools we have to assign a new shape. This happened during updating references - we tried to create a new shape tree that mirrored the existing tree, but based on the root shape of the new size pool. This causes allocations to happen if the new tree doesn't already exist, potentially triggering a GC, during GC. This commit changes object movement to look for a pre-existing new tree during object movement, and if that tree does not exist, we don't move the object to the new pool. This allows us to remove the shape allocation from update references. Co-Authored-By: Peter Zhu <peter@peterzhu.ca> Notes: Merged: https://github.com/ruby/ruby/pull/6938
2022-12-15Transition complex objects to "too complex" shapeJemma Issroff
When an object becomes "too complex" (in other words it has too many variations in the shape tree), we transition it to use a "too complex" shape and use a hash for storing instance variables. Without this patch, there were rare cases where shape tree growth could "explode" and cause performance degradation on what would otherwise have been cached fast paths. This patch puts a limit on shape tree growth, and gracefully degrades in the rare case where there could be a factorial growth in the shape tree. For example: ```ruby class NG; end HUGE_NUMBER.times do NG.new.instance_variable_set(:"@unique_ivar_#{_1}", 1) end ``` We consider objects to be "too complex" when the object's class has more than SHAPE_MAX_VARIATIONS (currently 8) leaf nodes in the shape tree and the object introduces a new variation (a new leaf node) associated with that class. For example, new variations on instances of the following class would be considered "too complex" because those instances create more than 8 leaves in the shape tree: ```ruby class Foo; end 9.times { Foo.new.instance_variable_set(":@uniq_#{_1}", 1) } ``` However, the following class is *not* too complex because it only has one leaf in the shape tree: ```ruby class Foo def initialize @a = @b = @c = @d = @e = @f = @g = @h = @i = nil end end 9.times { Foo.new } `` This case is rare, so we don't expect this change to impact performance of most applications, but it needs to be handled. Co-Authored-By: Aaron Patterson <tenderlove@ruby-lang.org> Notes: Merged: https://github.com/ruby/ruby/pull/6931
2022-12-15Add variation_count on classesJemma Issroff
Count how many "variations" each class creates. A "variation" is a a unique ordering of instance variables on a particular class. This can also be thought of as a branch in the shape tree. For example, the following Foo class will have 2 variations: ```ruby class Foo ; end Foo.new.instance_variable_set(:@a, 1) # case 1: creates one variation Foo.new.instance_variable_set(:@b, 1) # case 2: creates another variation foo = Foo.new foo.instance_variable_set(:@a, 1) # does not create a new variation foo.instance_variable_set(:@b, 1) # does not create a new variation (a continuation of the variation in case 1) ``` We will use this number to limit the amount of shapes that a class can create and fallback to using a hash iv lookup. Co-Authored-By: Aaron Patterson <tenderlove@ruby-lang.org> Notes: Merged: https://github.com/ruby/ruby/pull/6931
2022-12-15Revert "Fix Object Movement allocation in GC"Peter Zhu
This reverts commit 9c54466e299aa91af225bc2d92a3d7755730948f. We're seeing crashes in Shopify CI after this commit.
2022-12-15Fix Object Movement allocation in GCMatt Valentine-House
When moving Objects between size pools we have to assign a new shape. This happened during updating references - we tried to create a new shape tree that mirrored the existing tree, but based on the root shape of the new size pool. This causes allocations to happen if the new tree doesn't already exist, potentially triggering a GC, during GC. This commit changes object movement to look for a pre-existing new tree during object movement, and if that tree does not exist, we don't move the object to the new pool. This allows us to remove the shape allocation from update references. Co-Authored-By: Peter Zhu <peter@peterzhu.ca> Notes: Merged: https://github.com/ruby/ruby/pull/6926
2022-12-14Remove dead code in get_next_shape_internalPeter Zhu
If the rb_id_table_lookup fails, then res is not updated so it cannot be any value other than null. Notes: Merged: https://github.com/ruby/ruby/pull/6928
2022-12-10Update shape capacity when removing ivar and rewriting shape transitionsJemma Issroff
Since edc7af48acd12666a2945f30901d16b62a39f474, we now no longer have undef ivar transitions. Instead, we rebuild the shapes table. When we do this, we need to ensure that we retain our capacities on shapes. Notes: Merged: https://github.com/ruby/ruby/pull/6894
2022-12-08ObjectSpace.dump_all: dump shapes as wellJean Boussier
I see several arguments in doing so. First they use a non trivial amount of memory, so for various memory profiling/mapping tools it is relevant to have visibility of the space occupied by shapes. Then, some pathological code can create a tons of shape, so it is valuable to have a way to have a way to observe shapes without having to compile Ruby with `SHAPE_DEBUG=1`. And additionally it's likely much faster to dump then this way than to use `RubyVM::Shape`. There are however a few open questions: - Shapes can't respect the `since:` argument. Not sure what to do when it is provided. Would probably make sense to not dump them. - Maybe it would make more sense to have a separate `ObjectSpace.dump_shapes`? - Maybe instead `dump_all` should take a `shapes: false` argument? Additionally, `ObjectSpace.dump_shapes` is added for the use case of debugging the evolution of the shape tree. Notes: Merged: https://github.com/ruby/ruby/pull/6868
2022-12-07Stop transitioning to UNDEF when undefining an instance variableAaron Patterson
Cases like this: ```ruby obj = Object.new loop do obj.instance_variable_set(:@foo, 1) obj.remove_instance_variable(:@foo) end ``` can cause us to use many more shapes than we want (and even run out). This commit changes the code such that when an instance variable is removed, we'll walk up the shape tree, find the shape, then rebuild any child nodes that happened to be below the "targetted for removal" IV. This also requires moving any instance variables so that indexes derived from the shape tree will work correctly. Co-Authored-By: Jemma Issroff <jemmaissroff@gmail.com> Co-authored-by: John Hawthorn <jhawthorn@github.com> Notes: Merged: https://github.com/ruby/ruby/pull/6866
2022-12-05YJIT: Extract SHAPE_ID_NUM_BITS into a constant (#6863)Jemma Issroff
Notes: Merged-By: k0kubun <takashikkbn@gmail.com>
2022-12-02Remove unused rb_shape_flag_shift and rb_shape_flag_maskJemma Issroff
Notes: Merged: https://github.com/ruby/ruby/pull/6767
2022-12-02Extracted rb_shape_id_offsetJemma Issroff
Notes: Merged: https://github.com/ruby/ruby/pull/6767
2022-12-02implement IV writesAaron Patterson
Notes: Merged: https://github.com/ruby/ruby/pull/6767
2022-12-01Add a macro for SHAPE_DEBUGJohn Hawthorn
Like before, default to VM_CHECK_MODE > 0, but this allows just enabling shape debug helpers without the rest of VM_CHECK_MODE. Notes: Merged: https://github.com/ruby/ruby/pull/6800
2022-11-21Speed up shape transitionsPeter Zhu
This commit significantly speeds up shape transitions as it changes get_next_shape_internal to not perform a lookup (and instead require the caller to perform the lookup). This avoids double lookups during shape transitions. There is a significant (~2x) speedup in the following micro-benchmark: puts(Benchmark.measure do o = Object.new 100_000.times do |i| o.instance_variable_set(:"@a#{i}", 0) end end) Before: 22.393194 0.201639 22.594833 ( 22.684237) After: 11.323086 0.022284 11.345370 ( 11.389346) Notes: Merged: https://github.com/ruby/ruby/pull/6751
2022-11-1832 bit comparison on shape idAaron Patterson
This commit changes the shape id comparisons to use a 32 bit comparison rather than 64 bit. That means we don't need to load the shape id to a register on x86 machines. Given the following program: ```ruby class Foo def initialize @foo = 1 @bar = 1 end def read [@foo, @bar] end end foo = Foo.new foo.read foo.read foo.read foo.read foo.read puts RubyVM::YJIT.disasm(Foo.instance_method(:read)) ``` The machine code we generated _before_ this change is like this: ``` == BLOCK 1/4, ISEQ RANGE [0,3), 65 bytes ====================== # getinstancevariable 0x559a18623023: mov rax, qword ptr [r13 + 0x18] # guard object is heap 0x559a18623027: test al, 7 0x559a1862302a: jne 0x559a1862502d 0x559a18623030: cmp rax, 4 0x559a18623034: jbe 0x559a1862502d # guard shape, embedded, and T_OBJECT 0x559a1862303a: mov rcx, qword ptr [rax] 0x559a1862303d: movabs r11, 0xffff00000000201f 0x559a18623047: and rcx, r11 0x559a1862304a: movabs r11, 0xb000000002001 0x559a18623054: cmp rcx, r11 0x559a18623057: jne 0x559a18625046 0x559a1862305d: mov rax, qword ptr [rax + 0x18] 0x559a18623061: mov qword ptr [rbx], rax == BLOCK 2/4, ISEQ RANGE [3,6), 0 bytes ======================= == BLOCK 3/4, ISEQ RANGE [3,6), 47 bytes ====================== # gen_direct_jmp: fallthrough # getinstancevariable # regenerate_branch # getinstancevariable # regenerate_branch 0x559a18623064: mov rax, qword ptr [r13 + 0x18] # guard shape, embedded, and T_OBJECT 0x559a18623068: mov rcx, qword ptr [rax] 0x559a1862306b: movabs r11, 0xffff00000000201f 0x559a18623075: and rcx, r11 0x559a18623078: movabs r11, 0xb000000002001 0x559a18623082: cmp rcx, r11 0x559a18623085: jne 0x559a18625099 0x559a1862308b: mov rax, qword ptr [rax + 0x20] 0x559a1862308f: mov qword ptr [rbx + 8], rax ``` After this change, it's like this: ``` == BLOCK 1/4, ISEQ RANGE [0,3), 41 bytes ====================== # getinstancevariable 0x5560c986d023: mov rax, qword ptr [r13 + 0x18] # guard object is heap 0x5560c986d027: test al, 7 0x5560c986d02a: jne 0x5560c986f02d 0x5560c986d030: cmp rax, 4 0x5560c986d034: jbe 0x5560c986f02d # guard shape 0x5560c986d03a: cmp word ptr [rax + 6], 0x19 0x5560c986d03f: jne 0x5560c986f046 0x5560c986d045: mov rax, qword ptr [rax + 0x10] 0x5560c986d049: mov qword ptr [rbx], rax == BLOCK 2/4, ISEQ RANGE [3,6), 0 bytes ======================= == BLOCK 3/4, ISEQ RANGE [3,6), 23 bytes ====================== # gen_direct_jmp: fallthrough # getinstancevariable # regenerate_branch # getinstancevariable # regenerate_branch 0x5560c986d04c: mov rax, qword ptr [r13 + 0x18] # guard shape 0x5560c986d050: cmp word ptr [rax + 6], 0x19 0x5560c986d055: jne 0x5560c986f099 0x5560c986d05b: mov rax, qword ptr [rax + 0x18] 0x5560c986d05f: mov qword ptr [rbx + 8], rax ``` The first ivar read is a bit more complex, but the second ivar read is much simpler. I think eventually we could teach the context about the shape, then emit only one shape guard. Notes: Merged: https://github.com/ruby/ruby/pull/6737
2022-11-18rename SHAPE_BITS to SHAPE_ID_NUM_BITSAaron Patterson
Notes: Merged: https://github.com/ruby/ruby/pull/6737
2022-11-18Differentiate T_OBJECT shapes from other objectsAaron Patterson
We would like to differentiate types of objects via their shape. This commit adds a special T_OBJECT shape when we allocate an instance of T_OBJECT. This allows us to avoid testing whether an object is an instance of a T_OBJECT or not, we can just check the shape. Notes: Merged: https://github.com/ruby/ruby/pull/6758
2022-11-17Fix indentation of switch statement in shape.cPeter Zhu
2022-11-15Fix buffer overrun in ivars when rebuilding shapesPeter Zhu
In rb_shape_rebuild_shape, we need to increase the capacity when capacity == next_iv_index since the next ivar will be writing at index next_iv_index. This bug can be reproduced when assertions are turned on and you run the following code: class Foo def initialize @a1 = 1 @a2 = 1 @a3 = 1 @a4 = 1 @a5 = 1 @a6 = 1 @a7 = 1 end def add_ivars @a8 = 1 @a9 = 1 end end class Bar < Foo end foo = Foo.new foo.add_ivars bar = Bar.new GC.start bar.add_ivars bar.clone You will get the following crash: Assertion Failed: object.c:301:rb_obj_copy_ivar:src_num_ivs <= shape_to_set_on_dest->capacity Notes: Merged: https://github.com/ruby/ruby/pull/6725
2022-11-14Remove unused function rb_shape_transition_shapePeter Zhu
Notes: Merged: https://github.com/ruby/ruby/pull/6717
2022-11-10Extract `rb_shape_get_parent` helperJemma Issroff
Extract an `rb_shape_get_parent` method instead of continually calling `rb_shape_get_shape_by_id(shape->parent_id)` Notes: Merged: https://github.com/ruby/ruby/pull/6708
2022-11-10Transition shape when object's capacity changesJemma Issroff
This commit adds a `capacity` field to shapes, and adds shape transitions whenever an object's capacity changes. Objects which are allocated out of a bigger size pool will also make a transition from the root shape to the shape with the correct capacity for their size pool when they are allocated. This commit will allow us to remove numiv from objects completely, and will also mean we can guarantee that if two objects share shapes, their IVs are in the same positions (an embedded and extended object cannot share shapes). This will enable us to implement ivar sets in YJIT using object shapes. Co-Authored-By: Aaron Patterson <tenderlove@ruby-lang.org> Notes: Merged: https://github.com/ruby/ruby/pull/6699