Skip to content

Move object_id in attributes#13159

Merged
byroot merged 6 commits into
ruby:masterfrom
Shopify:object_id-in-shape
May 8, 2025
Merged

Move object_id in attributes#13159
byroot merged 6 commits into
ruby:masterfrom
Shopify:object_id-in-shape

Conversation

@casperisfine

Copy link
Copy Markdown
Contributor

And get rid of the obj_to_id_tbl

It's no longer needed, the object_id is now stored inline
in the object.

We still need the inverse table in case _id2ref is invoked, but
we lazily build it by walking the heap if that happens.

@cloudbees-smart-tests

cloudbees-smart-tests Bot commented Apr 23, 2025

Copy link
Copy Markdown

All Tests passed!

✖️no tests failed ✔️62066 tests passed(1 flake)

@casperisfine
casperisfine force-pushed the object_id-in-shape branch 3 times, most recently from 1aae814 to d8c231b Compare April 24, 2025 09:31
Comment thread gc/default/default.c Outdated
}

if (rb_shape_has_object_id(shape)) {
// We could avoid locking if the object isn't shareable

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For T_MODULE and T_CLASS it might be worth finding 8B in rb_classext_t so we can do some atomic CAS and find_or_create the object_id with no actual locking.

But if we need to also set the FL_SEEN_OBJ_ID flag, it might be more tricky than that?

@casperisfine
casperisfine force-pushed the object_id-in-shape branch 3 times, most recently from f1b501e to 63e2d8d Compare April 25, 2025 11:56
Comment thread gc.c Outdated
@casperisfine
casperisfine force-pushed the object_id-in-shape branch 3 times, most recently from af67723 to 8ff0247 Compare April 30, 2025 15:13
@casperisfine
casperisfine force-pushed the object_id-in-shape branch 7 times, most recently from b58815c to 6adbaaf Compare May 4, 2025 11:24
Comment thread gc.c
Comment thread gc.c
Comment on lines +1831 to +1823
// We could avoid locking if the object isn't shareable
// but we'll lock anyway to lookup the next shape, and
// we'd at least need to generate the object_id using atomics.
lock_lev = rb_gc_vm_lock();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be best to do in a followup, to it's easier to review, but we're not yet reducing contention at all here.

As for looking up the next shape, we can now do it without locking in the single-child happy path: #13191.

So combined with an atomic increment of next_object_id we could in many case not lock even on the initial write.

Comment thread gc.c
Comment on lines +1725 to +1729
#define OBJ_ID_INCREMENT (RUBY_IMMEDIATE_MASK + 1)
#define OBJ_ID_INITIAL (OBJ_ID_INCREMENT)

static unsigned long long next_object_id = OBJ_ID_INITIAL;
static VALUE id_to_obj_value = 0;
static st_table *id_to_obj_tbl = NULL;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we should move object_id related logic in its own file? Might be followup worthy.

Comment thread gc.c Outdated
Comment on lines +1933 to +1927
if (FL_TEST(obj, FL_EXIVAR)) {
rb_free_generic_ivar((VALUE)obj);
FL_UNSET(obj, FL_EXIVAR);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be very easy to get rid of FL_EXIVAR, and replace it with something like:

!RB_TYPE_ANY(obj, T_OBJECT, T_CLASS, T_MODULE) && get_shape(obj)->next_field_index > 0

It adds one extra pointer chase, but it could be optimized out because generic objects with no ivars will always have either the ROOT shape or the ROOT_FROZEN shape. So it could be a very quick operation.

@casperisfine
casperisfine force-pushed the object_id-in-shape branch 4 times, most recently from 7f53212 to 2c256dc Compare May 5, 2025 10:43
Comment thread shape.c
Comment on lines +1104 to +1108
bool
rb_shape_too_complex_p(rb_shape_t *shape)
{
return shape->flags & SHAPE_FL_TOO_COMPLEX;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might want to turn these small helpers into static inline in shape.h for performance reason. But I'd rather do it in a followup as keeping all this logic private helps with refactoring for now.

@casperisfine
casperisfine force-pushed the object_id-in-shape branch 4 times, most recently from 7f8d390 to dc039da Compare May 5, 2025 13:59
Comment thread variable.c Outdated
@casperisfine
casperisfine force-pushed the object_id-in-shape branch from dc039da to 1f95d2a Compare May 5, 2025 14:37
byroot added 3 commits May 6, 2025 14:39
Ivars will longer be the only thing stored inline
via shapes, so keeping the `iv_index` and `ivptr` names
would be confusing.

Instance variables won't be the only thing stored inline
via shapes, so keeping the `ivptr` name would be confusing.

`field` encompass anything that can be stored in a VALUE array.

Similarly, `gen_ivtbl` becomes `gen_fields_tbl`.
Also refactor checks for `->type == SHAPE_OBJ_TOO_COMPLEX`.
This opens the door to store more informations in shapes, such
as the `object_id` or object address in case it has been observed
and the object has to be moved.
@casperisfine
casperisfine force-pushed the object_id-in-shape branch 9 times, most recently from 4df8c0e to f2534e3 Compare May 7, 2025 14:36
And get rid of the `obj_to_id_tbl`

It's no longer needed, the `object_id` is now stored inline
in the object alongside instance variables.

We still need the inverse table in case `_id2ref` is invoked, but
we lazily build it by walking the heap if that happens.

The `object_id` concern is also no longer a GC implementation
concern, but a generic implementation.

Co-Authored-By: Matt Valentine-House <[email protected]>
@casperisfine
casperisfine force-pushed the object_id-in-shape branch from f2534e3 to 3d9a88a Compare May 7, 2025 14:50
Comment thread gc.c Outdated
Comment thread gc.c
byroot and others added 2 commits May 7, 2025 17:56
Use `st_foreach_with_replace` rather than to call `st_insert`
from inside `st_foreach`, this saves from having to disable GC.

Co-Authored-By: Peter Zhu <[email protected]>

@tenderlove tenderlove left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine with me. I think we might have an opportunity to decrease the size of rb_shape, but I'm fine if we merge this as-is.

Comment thread shape.h
uint8_t flags;
shape_id_t parent_id;
redblack_node_t *ancestor_index;
};

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the size of rb_shape now? We might want to move flags to the end of the struct because I think this will leave wasted space between flags and parent_id.

On master it's 40 bytes:

(lldb) p sizeof(struct rb_shape)
(unsigned long) 40

I'm not sure how much we care about shape memory usage, but maybe we could split the type field. We might only need 4 bits for type.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's unchanged, I've put the flags here because there was some padding:

diff --git a/shape.c b/shape.c
index fb739d3614..8aec985496 100644
--- a/shape.c
+++ b/shape.c
@@ -1434,6 +1434,7 @@ Init_default_shapes(void)
 void
 Init_shape(void)
 {
+    fprintf(stderr, "sizeof(rb_shape_t) = %lu\n", sizeof(rb_shape_t));
 #if SHAPE_DEBUG
     /* Document-class: RubyVM::Shape
      * :nodoc: */
$ make -j run
sizeof(rb_shape_t) = 40

I'm not sure how much we care about shape memory usage

Not that much, but I think we could save some spaces by not using a full pointer for redblack_node_t *ancestor_index;. We could just have an offset. I think It would be nice if shapes were 32B instead of 40B.

@peterzhu2118 peterzhu2118 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense to me!

@byroot
byroot merged commit cf9046c into ruby:master May 8, 2025
peterzhu2118 added a commit to ruby/mmtk that referenced this pull request May 8, 2025
byroot added a commit to byroot/ruby that referenced this pull request May 8, 2025
Introduced in: ruby#13159

Now that there is no longer a unique TOO_COMPLEX shape with
no children, checking `shape->type == TOO_COMPLEX` is incorrect.
byroot added a commit that referenced this pull request May 8, 2025
Introduced in: #13159

Now that there is no longer a unique TOO_COMPLEX shape with
no children, checking `shape->type == TOO_COMPLEX` is incorrect.
ivoanjo added a commit to DataDog/dd-trace-rb that referenced this pull request May 16, 2025
**What does this PR do?**

This PR fixes three issues in the profiler when used with latest
ruby-head:

1. It's no longer possible to ask the object_id from a T_IMEMO object.
   This showed up as a Ruby VM crash with an error message
   "T_IMEMO can't have an object_id".
   (See ruby/ruby#13347 for the upstream change)

2. Creating new instances of a class is now inlined into the caller,
   and there is no longer a frame to represent the new.
   This broke some of our tests that expected the stack from
   allocating an object to have the `new` at the top.
   (See ruby/ruby#13080 for the upstream change)

3. Object ids now count towards the size of objects.
   This broke some of our tests that asserted on size of profiled
   objects.
   (See ruby/ruby#13159 for the upstream change)

**Motivation:**

Fix support for Ruby 3.5.

**Additional Notes:**

N/A

**How to test the change?**

I've updated our specs to cover these changes. Unfortunately, we don't
yet test with Ruby 3.5 in CI, so you'll have to test manually if you
want to see the fixes working with 3.5.

(Note that these changes showed up after 3.5.0-preview1, so testing
on -preview1 is not enough)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants