Commit graph

137 commits

Author SHA1 Message Date
Yusuke Endoh
5cee3329df Skip test affected by TracePoint-dependent allocation_class_path
These assertions fail when TracePoint is enabled due to differing
allocation context. Commented out for now until behavior is fixed.

See [Bug #21298]
2025-05-01 17:21:36 +09:00
Aaron Patterson
8ac8225c50 Inline Class#new.
This commit inlines instructions for Class#new.  To make this work, we
added a new YARV instructions, `opt_new`.  `opt_new` checks whether or
not the `new` method is the default allocator method.  If it is, it
allocates the object, and pushes the instance on the stack.  If not, the
instruction jumps to the "slow path" method call instructions.

Old instructions:

```
> ruby --dump=insns -e'Object.new'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
0000 opt_getconstant_path                   <ic:0 Object>             (   1)[Li]
0002 opt_send_without_block                 <calldata!mid:new, argc:0, ARGS_SIMPLE>
0004 leave
```

New instructions:

```
> ./miniruby --dump=insns -e'Object.new'
== disasm: #<ISeq:<main>@-e:1 (1,0)-(1,10)>
0000 opt_getconstant_path                   <ic:0 Object>             (   1)[Li]
0002 putnil
0003 swap
0004 opt_new                                <calldata!mid:new, argc:0, ARGS_SIMPLE>, 11
0007 opt_send_without_block                 <calldata!mid:initialize, argc:0, FCALL|ARGS_SIMPLE>
0009 jump                                   14
0011 opt_send_without_block                 <calldata!mid:new, argc:0, ARGS_SIMPLE>
0013 swap
0014 pop
0015 leave
```

This commit speeds up basic object allocation (`Foo.new`) by 60%, but
classes that take keyword parameters see an even bigger benefit because
no hash is allocated when instantiating the object (3x to 6x faster).

Here is an example that uses `Hash.new(capacity: 0)`:

```
> hyperfine "ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'" "./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'"
Benchmark 1: ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
  Time (mean ± σ):      1.082 s ±  0.004 s    [User: 1.074 s, System: 0.008 s]
  Range (min … max):    1.076 s …  1.088 s    10 runs

Benchmark 2: ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
  Time (mean ± σ):     627.9 ms ±   3.5 ms    [User: 622.7 ms, System: 4.8 ms]
  Range (min … max):   622.7 ms … 633.2 ms    10 runs

Summary
  ./ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end' ran
    1.72 ± 0.01 times faster than ruby --disable-gems -e'i = 0; while i < 10_000_000; Hash.new(capacity: 0); i += 1; end'
```

This commit changes the backtrace for `initialize`:

```
aaron@tc ~/g/ruby (inline-new)> cat test.rb
class Foo
  def initialize
    puts caller
  end
end

def hello
  Foo.new
end

hello
aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb
ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
test.rb:8:in 'Class#new'
test.rb:8:in 'Object#hello'
test.rb:11:in '<main>'
aaron@tc ~/g/ruby (inline-new)> ./miniruby -v test.rb
ruby 3.5.0dev (2025-03-28T23:59:40Z inline-new c4157884e4) +PRISM [arm64-darwin24]
test.rb:8:in 'Object#hello'
test.rb:11:in '<main>'
```

It also increases memory usage for calls to `new` by 122 bytes:

```
aaron@tc ~/g/ruby (inline-new)> cat test.rb
require "objspace"

class Foo
  def initialize
    puts caller
  end
end

def hello
  Foo.new
end

puts ObjectSpace.memsize_of(RubyVM::InstructionSequence.of(method(:hello)))
aaron@tc ~/g/ruby (inline-new)> make runruby
RUBY_ON_BUG='gdb -x ./.gdbinit -p' ./miniruby -I./lib -I. -I.ext/common  ./tool/runruby.rb --extout=.ext  -- --disable-gems  ./test.rb
656
aaron@tc ~/g/ruby (inline-new)> ruby -v test.rb
ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [arm64-darwin24]
544
```

Thanks to @ko1 for coming up with this idea!

Co-Authored-By: John Hawthorn <john@hawthorn.email>
2025-04-25 13:46:05 -07:00
Jean Boussier
cb1ea54bbf objspace_dump: Include shareable flag
Given that the currently planned ractor local GC implementation
performance will heavilly be influenced by the number of shareable
objects it would be valuable to be able to know how many of them
are in the heap.
2025-04-24 10:14:29 +02:00
Jean Boussier
ca4325f6c9 Harden TestObjSpace#test_memsize_of_root_shared_string
This test occasionally fail because it runs into a String instance
that had its `==` method removed.

I couldn't identify where this String comes from, but in general
when using `each_object` it's best to not assume returned objectd
are functional.

By just inverting the operands of `==` we ensure it's always
`String#==` that is called.

```
  1) Error:
TestObjSpace#test_memsize_of_root_shared_string:
NoMethodError: undefined method '==' for #<String:0x00007f9b50e8c978>
    /tmp/ruby/src/trunk-random1/test/objspace/test_objspace.rb:35:in 'block in TestObjSpace#test_memsize_of_root_shared_string'
    /tmp/ruby/src/trunk-random1/test/objspace/test_objspace.rb:35:in 'ObjectSpace.each_object'
    /tmp/ruby/src/trunk-random1/test/objspace/test_objspace.rb:35:in 'TestObjSpace#test_memsize_of_root_shared_string'
```
2025-03-06 08:42:01 +01:00
Peter Zhu
5e45f2a0bc Add age to rb_gc_object_metadata
This will allow ObjectSpace.dump to output the age of the object.
2025-02-19 09:47:28 -05:00
Peter Zhu
d729c1575e Output object_id in ObjectSpace.dump
Outputs the object ID in the dump for objects that have it seen.
2025-01-30 11:48:14 -05:00
Peter Zhu
516a6cd1ad Check whether object is valid in allocation_info_tracer_compact
When reference updating ObjectSpace.trace_object_allocations, we need to
check whether the object is valid or not because it does not mark the
object so the object may be dead. This can cause a segmentation fault
if the object is on a free heap page.

For example, the following script crashes:

    require "objspace"

    objs = []
    ObjectSpace.trace_object_allocations do
      1_000_000.times do
        objs << Object.new
      end
    end

    objs = nil

    # Free pages that the objs were on
    GC.start

    # Run compaction and check that it doesn't crash
    GC.compact
2024-12-16 12:24:24 -05:00
Peter Zhu
15765eac0a Fix ObjectSpace.trace_object_allocations for compaction
We need to reinsert into the ST table when an object moves because it is
a numtable that hashes on the object address, so when an object moves we
need to reinsert it rather than just updating the key.
2024-12-16 10:12:54 -05:00
Jean Boussier
ee1cd1656f ObjectSpace.dump: handle Module#set_temporary_name
[Bug #20892]

Until the introduction of that method, it was impossible for a
Module name not to be valid JSON, hence it wasn't going through
the slower escaping function.

This assumption no longer hold.
2024-11-12 20:21:27 +01:00
Peter Zhu
0160dafc4c Replace all GC.disable with EnvUtil.without_gc 2024-09-17 10:34:26 -04:00
Peter Zhu
51bd816517 [Feature #20470] Split GC into gc_impl.c
This commit splits gc.c into two files:

- gc.c now only contains code not specific to Ruby GC. This includes
  code to mark objects (which the GC implementation may choose not to
  use) and wrappers for internal APIs that the implementation may need
  to use (e.g. locking the VM).

- gc_impl.c now contains the implementation of Ruby's GC. This includes
  marking, sweeping, compaction, and statistics. Most importantly,
  gc_impl.c only uses public APIs in Ruby and a limited set of functions
  exposed in gc.c. This allows us to build gc_impl.c independently of
  Ruby and plug Ruby's GC into itself.
2024-07-03 09:03:40 -04:00
Jean Boussier
e82138e48a Stabilize TestObjSpace#test_dump_special_consts
The test assumes `:foo` is a static symbol, but that is only true
if a literal `:foo` was parsed before `"foo".to_sym` was evaled:

```ruby
require 'objspace'
foo_sym = "foo".to_sym
puts ObjectSpace.dump(eval(":foo"))
```

```
{"address":"0x100fb46d0", "type":"SYMBOL", "shape_id":10, "slot_size":40, "class":"0x100d3e9c8", "frozen":true, "bytesize":3, "value":"foo", "memsize":40, "flags":{"wb_protected":true, "marking":true, "marked":true}}
```
2024-05-09 12:23:34 +02:00
Jean Boussier
09d8c99cdc Ensure test suite is compatible with --frozen-string-literal
As preparation for https://bugs.ruby-lang.org/issues/20205
making sure the test suite is compatible with frozen string
literals is making things easier.
2024-03-14 17:56:15 +01:00
Takashi Kokubun
1721bb9dc6 Skip a flaky objspace test on Visual Studio
This seems to happen only on VisualStudio:
1941837538

It fails relatively frequently. Nobody seems actively working on it, so
let's skip it until somebody starts working on it.
2023-12-07 09:42:56 -08:00
Jean Boussier
6391ae9ebc objspace_dump.c: dump call cache ids with dump_append_id
Not all `ID` have an associated string.

Fixes a SEGFAULT in ObjectSpace.dump_all spec.
2023-11-22 10:24:35 +01:00
Jean Boussier
a1887f4dc2 Revert "Fix crash caused by concurrent ObjectSpace.dump_all calls"
This reverts commit 9a62fd3cba.
2023-11-13 08:57:57 +01:00
KJ Tsanaktsidis
9a62fd3cba Fix crash caused by concurrent ObjectSpace.dump_all calls
Since the callback defined in the objspace module might give up the GVL,
we need to make sure the right cr->mfd value is set back after the GVL
is re-obtained.
2023-11-12 17:50:37 +01:00
Jean Boussier
ea1b1ea1aa String#force_encoding don't clear coderange if encoding is unchanged
Some code out there blind calls `force_encoding` without checking
what the original encoding was, which clears the coderange uselessly.

If the String is big, it can be a rather costly mistake.

For instance the `rack-utf8_sanitizer` gem does this on request
bodies.
2023-11-09 12:38:10 +01:00
John Hawthorn
635b92099e Fix ObjectSpace.dump with super() callinfo
super() uses 0 as mid for its callinfo, so we need to check for that to
avoid a segfault when using dump_all.
2023-10-12 10:22:32 +02:00
Takashi Kokubun
e210b899dc
Move the PC regardless of the leaf flag (#8232)
Co-authored-by: Alan Wu <alansi.xingwu@shopify.com>
2023-08-16 20:28:33 -07:00
Peter Zhu
ecbedf9bf1 Remove assumption about object order
The address of objects can't be assumed since a later object may be
allocate in a swept slot.
2023-07-18 14:52:37 -04:00
Peter Zhu
e8212c55f9 Fix flaky test in test_objspace.rb
Ensure that the frozen string is promoted to the old generation by
running the GC 4 times.
2023-05-31 14:03:30 -04:00
Nobuyoshi Nakada
4f4bc13eb9
Skip too-complex-shape test which is always flaky regardless JIT 2023-05-21 16:44:10 +09:00
Nobuyoshi Nakada
a997f144fb
Skip first if flaky [ci skip] 2023-05-21 10:31:38 +09:00
Takashi Kokubun
875adad948 The too-complex test isn't stablefor RJIT either
5020231516
2023-05-19 12:56:15 +09:00
Takashi Kokubun
b70e3f44c1 Skip test_dump_too_complex_shape for YJIT for now
It fails too often with YJIT:

* 8992254690
* 8995281395
* 9000322487
* 9000836915

ref: https://github.com/ruby/ruby/pull/7646
2023-05-19 11:33:19 +09:00
lukeg
d74b32db9d
change to test/objectspace, don't rely on Object's shape not being "too complex" 2023-05-18 09:17:24 -07:00
Yusuke Endoh
a19fa9b2bd Prevent warning: assigned but unused variable
20230510T123003Z.log.html.gz
```
/home/chkbuild/chkbuild/tmp/build/20230510T123003Z/ruby/test/objspace/test_objspace.rb:224: warning: assigned but unused variable - c4
/home/chkbuild/chkbuild/tmp/build/20230510T123003Z/ruby/test/ruby/test_class.rb:362: warning: assigned but unused variable - e
/home/chkbuild/chkbuild/tmp/build/20230510T123003Z/ruby/test/ruby/test_process.rb:2602: warning: assigned but unused variable - parent_pid
```
2023-05-10 23:49:44 +09:00
Nobuyoshi Nakada
e135a21a85
Define RubyVM::Shape dependent test only if available 2023-05-04 13:41:30 +09:00
Peter Zhu
e1bd45624c Fix crash when allocating classes with newobj hook
We need to zero out the whole slot when running the newobj hook for a
newly allocated class because the slot could be filled with garbage,
which would cause a crash if a GC runs inside of the newobj hook.

For example, the following script crashes:

```
require "objspace"

GC.stress = true

ObjectSpace.trace_object_allocations {
  100.times do
    Class.new
  end
}
```

[Bug #19482]
2023-03-08 08:47:18 -05:00
Peter Zhu
3e09822407 Fix incorrect line numbers in GC hook
If the previous instruction is not a leaf instruction, then the PC was
incremented before the instruction was ran (meaning the currently
executing instruction is actually the previous instruction), so we
should not increment the PC otherwise we will calculate the source
line for the next instruction.

This bug can be reproduced in the following script:

```
require "objspace"

ObjectSpace.trace_object_allocations_start
a =

  1.0 / 0.0
p [ObjectSpace.allocation_sourceline(a), ObjectSpace.allocation_sourcefile(a)]
```

Which outputs: [4, "test.rb"]

This is incorrect because the object was allocated on line 10 and not
line 4. The behaviour is correct when we use a leaf instruction (e.g.
if we replaced `1.0 / 0.0` with `"hello"`), then the output is:
[10, "test.rb"].

[Bug #19456]
2023-02-24 14:10:09 -05:00
Koichi Sasada
8c2b6926d2 Skip unfixed assertion about objspace/dump_all
```
{"address":"0x7f8c03e9fcf0", "type":"STRING", "shape_id":10, "slot_size":40, "class":"0x7f8c00dbed98", "frozen":true, "embedded":true, "fstring":true, "bytesize":5, "value":"TEST2", "encoding":"US-ASCII", "coderange":"7bit", "memsize":40, "flags":{"wb_protected":true}}
{"address":"0x7f8c03e9ffc0", "type":"STRING", "shape_id":0, "slot_size":40, "class":"0x7f8c00dbed98", "embedded":true, "bytesize":5, "value":"TEST2", "encoding":"US-ASCII", "coderange":"7bit", "memsize":40, "flags":{"wb_protected":true}}
{"address":"0x7f8c03e487c0", "type":"STRING", "shape_id":0, "slot_size":40, "class":"0x7f8c00dbed98", "embedded":true, "bytesize":5, "value":"TEST2", "encoding":"UTF-8", "coderange":"unknown", "file":"-", "line":4, "method":"dump_my_heap_please", "generation":1, "memsize":40, "flags":{"wb_protected":true}}
  1) Failure:
TestObjSpace#test_dump_all [/tmp/ruby/src/trunk-gc-asserts/test/objspace/test_objspace.rb:622]:
number of strings.
<2> expected but was
<3>.
```

This failure only occurred on a ruby built with `DEFS=\"-DRGENGC_CHECK_MODE=2\""`
and only on a specific machine (Docker container) and difficult to reproduce,
so skip this failure to check other failures.
2023-01-11 18:09:48 +09:00
Peter Zhu
2056c0a7c6 Add embedded status to dumps of T_OBJECT
This commit adds `"embedded":true` in ObjectSpace.dump for T_OBJECTs
that are embedded.
2023-01-05 16:00:36 -05:00
Koichi Sasada
3931921607 add debug print for the failure
http://ci.rvm.jp/results/trunk-gc-asserts@ruby-sp2-docker/4364584

```
  1) Failure:
TestObjSpace#test_dump_all [/tmp/ruby/src/trunk-gc-asserts/test/objspace/test_objspace.rb:599]:
number of strings.
<2> expected but was
<3>.
```
2022-12-28 15:46:16 +09:00
Jemma Issroff
e9ba3042e1 Indicate if a shape is too_complex in ObjectSpace#dump 2022-12-15 13:41:47 -08:00
Jean Boussier
73771e4b19 ObjectSpace.dump_all: dump shapes as well
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.
2022-12-08 18:46:16 +01:00
Peter Zhu
5f95228c76 Add RVALUE_OVERHEAD and move ractor_belonging_id
This commit adds RVALUE_OVERHEAD for storing metadata at the end of the
slot. This commit moves the ractor_belonging_id in debug builds from the
flags to RVALUE_OVERHEAD which frees the 16 bits in the headers for
object shapes.
2022-11-21 11:26:26 -05:00
Jemma Issroff
5246f4027e Transition shape when object's capacity changes
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>
2022-11-10 10:11:34 -05:00
Koichi Sasada
e35c528d72 push dummy frame for loading process
This patch pushes dummy frames when loading code for the
profiling purpose.

The following methods push a dummy frame:
* `Kernel#require`
* `Kernel#load`
* `RubyVM::InstructionSequence.compile_file`
* `RubyVM::InstructionSequence.load_from_binary`

https://bugs.ruby-lang.org/issues/18559
2022-10-20 17:38:28 +09:00
Aaron Patterson
06abfa5be6
Revert this until we can figure out WB issues or remove shapes from GC
Revert "* expand tabs. [ci skip]"

This reverts commit 830b5b5c35.

Revert "This commit implements the Object Shapes technique in CRuby."

This reverts commit 9ddfd2ca00.
2022-09-26 16:10:11 -07:00
Jemma Issroff
9ddfd2ca00 This commit implements the Object Shapes technique in CRuby.
Object Shapes is used for accessing instance variables and representing the
"frozenness" of objects.  Object instances have a "shape" and the shape
represents some attributes of the object (currently which instance variables are
set and the "frozenness").  Shapes form a tree data structure, and when a new
instance variable is set on an object, that object "transitions" to a new shape
in the shape tree.  Each shape has an ID that is used for caching. The shape
structure is independent of class, so objects of different types can have the
same shape.

For example:

```ruby
class Foo
  def initialize
    # Starts with shape id 0
    @a = 1 # transitions to shape id 1
    @b = 1 # transitions to shape id 2
  end
end

class Bar
  def initialize
    # Starts with shape id 0
    @a = 1 # transitions to shape id 1
    @b = 1 # transitions to shape id 2
  end
end

foo = Foo.new # `foo` has shape id 2
bar = Bar.new # `bar` has shape id 2
```

Both `foo` and `bar` instances have the same shape because they both set
instance variables of the same name in the same order.

This technique can help to improve inline cache hits as well as generate more
efficient machine code in JIT compilers.

This commit also adds some methods for debugging shapes on objects.  See
`RubyVM::Shape` for more details.

For more context on Object Shapes, see [Feature: #18776]

Co-Authored-By: Aaron Patterson <tenderlove@ruby-lang.org>
Co-Authored-By: Eileen M. Uchitelle <eileencodes@gmail.com>
Co-Authored-By: John Hawthorn <john@hawthorn.email>
2022-09-26 09:21:30 -07:00
Nobuyoshi Nakada
cf7d07570f
Dump non-ASCII char as unsigned
Non-ASCII code may be negative on platforms plain char is signed.
2022-07-22 09:56:48 +09:00
Jean byroot Boussier
f0ae583a3d Revert "objspace_dump.c: skip dumping method name if not pure ASCII"
This reverts commit 79406e3600.
2022-07-21 19:56:08 +02:00
Jean Boussier
79406e3600 objspace_dump.c: skip dumping method name if not pure ASCII
Sidekiq has a method named `❨╯°□°❩╯︵┻━┻`which corrupts
heap dumps.

Normally we could just dump is as is since it's valid UTF-8 and need
no escaping. But our code to escape control characters isn't UTF-8
aware so it's more complicated than it seems.

Ultimately since the overwhelming majority of method names are
pure ASCII, it's not a big loss to just skip it.
2022-07-21 18:43:45 +02:00
Jean Boussier
890df5f812 ObjectSpace.dump: Include string coderange
I suspect that some shared pages are invalidated because
some static string don't have their coderange set eagerly.

So the first time they are scanned, the entire memory page is
invalidated.

Being able to see the coderange in `ObjectSpace` would help debug
this.

And in addition `dump` currently call `is_broken_string()`  and `is_ascii_string()`
which both end up scanning the string and assigning coderange. I think it's
undesirable as `dump` should be read only.
2022-07-04 20:04:59 +02:00
Jemma Issroff
87123c4fc7 Refactor test_dump_all to make assertions about the contents of the dumped hash 2022-03-29 08:21:10 -07:00
Peter Zhu
fb724a887a Show embed status of array when len is 0 in objspace dump 2022-03-01 10:55:53 -05:00
John Hawthorn
05b1944c53 objspace: Hide identhash containing internal objs
Inside ObjectSpace.reachable_objects_from we keep an internal identhash
in order to de-duplicate reachable objects when wrapping them as
InternalObject. Previously this hash was not hidden, making it possible
to leak references to those internal objects to Ruby if using
ObjectSpace.each_object.

This commit solves this by hiding the hash. To simplify collection of
values, we instead now just use the hash as a set of visited objects,
and collect an Array (not hidden) of values to be returned.
2022-02-09 17:32:43 -08:00
Matt Valentine-House
9fab2c1a1a Add the size pool slot size to the output of ObjectSpace.dump/dump_all 2022-02-03 15:07:35 -05:00
Peter Zhu
7b77d46671 Decouple GC slot sizes from RVALUE
Add a new macro BASE_SLOT_SIZE that determines the slot size.

For Variable Width Allocation (compiled with USE_RVARGC=1), all slot
sizes are powers-of-2 multiples of BASE_SLOT_SIZE.

For USE_RVARGC=0, BASE_SLOT_SIZE is set to sizeof(RVALUE).
2022-02-02 09:52:04 -05:00