150 Commits

Author SHA1 Message Date
Augustin Gottlieb
6ef0eb29c6
Add early type validation for ObjectSpace.memsize_of_all klass argument 2026-01-23 11:00:37 +09:00
Peter Zhu
e8c61f5139 Remove ObjectSpace.count_nodes
ObjectSpace.count_nodes has been a no-op and returning an empty hash since
Ruby 2.5 because parser nodes are not GC managed.
2026-01-09 18:05:43 -05:00
Nobuyoshi Nakada
e01e13c239
Use assert_ruby_status if no assertion 2026-01-09 17:16:22 +09:00
Luke Gruber
56b67f1684
ObjectSpace.{dump,dump_all,dump_shapes} needs vm barrier. (#15569)
This allows these methods to be called from ractors.

Add new exported function `rb_vm_lock_with_barrier()` that requires
users to include "vm_sync.h"
2025-12-17 12:17:30 -05:00
John Hawthorn
de94f88c68 Register internal tracepoints globally
Internal tracepoints only make sense to run globally, and they already
took completely different paths.
2025-12-08 16:38:45 -08:00
Luke Gruber
7ff036d59b Prevent GC from running during newobj_of for internal_event_newobj.
If another ractor is calling for GC, we need to prevent the current one
from joining the barrier. Otherwise, our half-built object will be marked.

The repro script was:

test.rb:
```ruby
require "objspace"
1000.times do
  ObjectSpace.trace_object_allocations do
    r = Ractor.new do
      _obj = 'a' * 1024
    end

    r.join
  end
end
```

$ untilfail lldb -b ./exe/ruby -o "target create ./exe/ruby" -o "run test.rb" -o continue

It would fail at `ractor_port_mark`, rp->r was a garbage value. Credit to John for finding the
solution.

Co-authored-by: John Hawthorn <john.hawthorn@shopify.com>
2025-09-18 13:52:37 -07:00
Peter Zhu
df2f462acc Skip TestObjSpaceRactor#test_tracing_does_not_crash
It crashes frequently on CI but I am not able to reproduce locally:

https://ci.rvm.jp/results/trunk-random1@ruby-sp2-noble-docker/5954509
https://ci.rvm.jp/results/trunk-random0@ruby-sp2-noble-docker/5954501
2025-09-17 11:50:46 -04:00
Takashi Kokubun
5e8f9ea495
Increase timeout for a flaky test (#14233)
https://github.com/ruby/ruby/actions/runs/16977882022/job/48131284556
2025-08-14 23:41:01 +00:00
Koichi Sasada
ef2bb61018 Ractor::Port
* 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
2025-05-31 04:01:33 +09:00
Jean Boussier
a294427017 Add missing lock to rb_gc_impl_copy_finalizer 2025-05-16 20:16:52 +02:00
Jean Boussier
ed632cd0ba Add missing lock in rb_gc_impl_undefine_finalizer
The table is global so accesses must be synchronized.
2025-05-15 13:32:08 +02:00
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
Nobuyoshi Nakada
91485d7dc6
Adjust indent [ci skip] 2024-05-04 01:15:09 +09: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:
https://github.com/ruby/ruby/actions/runs/7130917319/job/19418375386

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 9a62fd3cbae2ebb60e2f9cad782af1ad18db4319.
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
https://github.com/ruby/ruby/actions/runs/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:

* https://github.com/ruby/ruby/actions/runs/5015976941/jobs/8992254690
* https://github.com/ruby/ruby/actions/runs/5017310353/jobs/8995281395
* https://github.com/ruby/ruby/actions/runs/5019625711/jobs/9000322487
* https://github.com/ruby/ruby/actions/runs/5019883965/jobs/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
http://rubyci.s3.amazonaws.com/debian10/ruby-master/log/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
KJ Tsanaktsidis
7bd7aee02e Fix interpreter crash caused by RUBY_INTERNAL_EVENT_NEWOBJ + Ractors
When a Ractor is created whilst a tracepoint for
RUBY_INTERNAL_EVENT_NEWOBJ is active, the interpreter crashes. This is
because during the early setup of the Ractor, the stdio objects are
created, which allocates Ruby objects, which fires the tracepoint.
However, the tracepoint machinery tries to dereference the control frame
(ec->cfp->pc), which isn't set up yet and so crashes with a null pointer
dereference.

Fix this by not firing GC tracepoints if cfp isn't yet set up.
2023-03-09 09:46:14 +01: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