Skip to content

Commit 238c0b7

Browse files
committed
Move ROBJECT_HEAP into shape
1 parent f7985af commit 238c0b7

20 files changed

Lines changed: 285 additions & 165 deletions

File tree

debug.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ const union {
5252
enum ruby_econv_flag_type econv_flag_types;
5353
rb_econv_result_t econv_result;
5454
enum ruby_preserved_encindex encoding_index;
55-
enum ruby_robject_flags robject_flags;
5655
enum ruby_rmodule_flags rmodule_flags;
5756
enum ruby_rstring_flags rstring_flags;
5857
enum ruby_rarray_flags rarray_flags;

ext/objspace/objspace_dump.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -592,7 +592,7 @@ dump_object(VALUE obj, struct dump_config *dc)
592592
break;
593593

594594
case T_OBJECT:
595-
if (!FL_TEST_RAW(obj, ROBJECT_HEAP)) {
595+
if (!rb_obj_shape_heap_p(obj)) {
596596
dump_append(dc, ", \"embedded\":true");
597597
}
598598

gc.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,7 +1356,7 @@ rb_gc_imemo_needs_cleanup_p(VALUE obj)
13561356
return ((rb_imemo_tmpbuf_t *)obj)->ptr != NULL;
13571357

13581358
case imemo_fields:
1359-
return FL_TEST_RAW(obj, OBJ_FIELD_HEAP) || (id2ref_tbl && rb_obj_shape_has_id(obj));
1359+
return rb_obj_shape_heap_p(obj) || (id2ref_tbl && rb_obj_shape_has_id(obj));
13601360
}
13611361
UNREACHABLE_RETURN(true);
13621362
}
@@ -1412,7 +1412,7 @@ rb_gc_obj_needs_cleanup_p(VALUE obj)
14121412

14131413
switch (flags & RUBY_T_MASK) {
14141414
case T_OBJECT:
1415-
if (flags & ROBJECT_HEAP) return true;
1415+
if (rb_obj_shape_heap_p(obj)) return true;
14161416
return false;
14171417

14181418
case T_DATA:
@@ -1557,7 +1557,7 @@ rb_gc_obj_free(void *objspace, VALUE obj)
15571557

15581558
switch (BUILTIN_TYPE(obj)) {
15591559
case T_OBJECT:
1560-
if (FL_TEST_RAW(obj, ROBJECT_HEAP)) {
1560+
if (rb_obj_shape_heap_p(obj)) {
15611561
if (rb_obj_shape_complex_p(obj)) {
15621562
RB_DEBUG_COUNTER_INC(obj_obj_complex);
15631563
st_free_table(ROBJECT_FIELDS_HASH(obj));
@@ -2198,7 +2198,7 @@ object_id0(VALUE obj)
21982198
id = generate_next_object_id();
21992199
rb_obj_field_set(obj, object_id_shape_id, 0, id);
22002200

2201-
RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == object_id_shape_id);
2201+
RUBY_ASSERT(RBASIC_SHAPE_ID(obj) == object_id_shape_id || RBASIC_SHAPE_ID(obj) == rb_shape_transition_heap(object_id_shape_id));
22022202
RUBY_ASSERT(rb_obj_shape_has_id(obj));
22032203

22042204
if (RB_UNLIKELY(id2ref_tbl)) {
@@ -2604,7 +2604,7 @@ rb_obj_memsize_of(VALUE obj)
26042604

26052605
switch (BUILTIN_TYPE(obj)) {
26062606
case T_OBJECT:
2607-
if (FL_TEST_RAW(obj, ROBJECT_HEAP)) {
2607+
if (rb_obj_shape_heap_p(obj)) {
26082608
if (rb_obj_shape_complex_p(obj)) {
26092609
size += rb_st_memsize(ROBJECT_FIELDS_HASH(obj));
26102610
}
@@ -3882,7 +3882,7 @@ gc_ref_update_object(void *objspace, VALUE v)
38823882
{
38833883
VALUE *ptr = ROBJECT_FIELDS(v);
38843884

3885-
if (FL_TEST_RAW(v, ROBJECT_HEAP)) {
3885+
if (rb_obj_shape_heap_p(v)) {
38863886
if (rb_obj_shape_complex_p(v)) {
38873887
gc_ref_update_table_values_only(ROBJECT_FIELDS_HASH(v));
38883888
return;
@@ -3894,7 +3894,7 @@ gc_ref_update_object(void *objspace, VALUE v)
38943894
// Object can be re-embedded
38953895
memcpy(ROBJECT(v)->as.ary, ptr, sizeof(VALUE) * ROBJECT_FIELDS_COUNT(v));
38963896
SIZED_FREE_N(ptr, ROBJECT_FIELDS_CAPACITY(v));
3897-
FL_UNSET_RAW(v, ROBJECT_HEAP);
3897+
RBASIC_SET_SHAPE_ID(v, rb_obj_shape_transition_embedded(v));
38983898
ptr = ROBJECT(v)->as.ary;
38993899
}
39003900
}
@@ -5087,7 +5087,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU
50875087
}
50885088
case T_OBJECT:
50895089
{
5090-
if (FL_TEST_RAW(obj, ROBJECT_HEAP)) {
5090+
if (rb_obj_shape_heap_p(obj)) {
50915091
if (rb_obj_shape_complex_p(obj)) {
50925092
size_t hash_len = rb_st_table_size(ROBJECT_FIELDS_HASH(obj));
50935093
APPEND_F("(complex) len:%zu", hash_len);

imemo.c

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ rb_imemo_fields_new(VALUE owner, shape_id_t shape_id, bool shareable)
141141
// layout in the shape describes the layout of the thing on which it is set.
142142
// Imemo fields have the same layout as robject, therefore the layout
143143
// should reflect that fact.
144-
RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id));
144+
RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(rb_shape_transition_embedded(shape_id)));
145145
RUBY_ASSERT(IMEMO_TYPE_P(fields, imemo_fields));
146146
return fields;
147147
}
@@ -151,12 +151,11 @@ rb_imemo_fields_new_complex(VALUE owner, shape_id_t shape_id, size_t capa, bool
151151
{
152152
VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable);
153153
IMEMO_OBJ_FIELDS(fields)->as.complex.table = st_init_numtable_with_size(capa);
154-
FL_SET_RAW(fields, OBJ_FIELD_HEAP);
155154
// imemo fields objects should always have "RObject" layout. The
156155
// layout in the shape describes the layout of the thing on which it is set.
157156
// Imemo fields have the same layout as robject, therefore the layout
158157
// should reflect that fact.
159-
RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id));
158+
RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(rb_shape_transition_heap(shape_id)));
160159
return fields;
161160
}
162161

@@ -180,12 +179,11 @@ rb_imemo_fields_new_complex_tbl(VALUE owner, shape_id_t shape_id, st_table *tbl,
180179
{
181180
VALUE fields = rb_imemo_new(imemo_fields, owner, sizeof(struct rb_fields), shareable);
182181
IMEMO_OBJ_FIELDS(fields)->as.complex.table = tbl;
183-
FL_SET_RAW(fields, OBJ_FIELD_HEAP);
184182
// imemo fields objects should always have "RObject" layout. The
185183
// layout in the shape describes the layout of the thing on which it is set.
186184
// Imemo fields have the same layout as robject, therefore the layout
187185
// should reflect that fact.
188-
RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(shape_id));
186+
RBASIC_SET_SHAPE_ID(fields, rb_shape_id_with_robject_layout(rb_shape_transition_heap(shape_id)));
189187
st_foreach(tbl, imemo_fields_trigger_wb_i, (st_data_t)fields);
190188
return fields;
191189
}
@@ -628,7 +626,7 @@ rb_free_const_table(struct rb_id_table *tbl)
628626
static inline void
629627
imemo_fields_free(struct rb_fields *fields)
630628
{
631-
if (FL_TEST_RAW((VALUE)fields, OBJ_FIELD_HEAP)) {
629+
if (rb_obj_shape_heap_p((VALUE)fields)) {
632630
RUBY_ASSERT(rb_shape_complex_p(RBASIC_SHAPE_ID((VALUE)fields)));
633631
st_free_table(fields->as.complex.table);
634632
}

include/ruby/internal/core/robject.h

Lines changed: 0 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -48,34 +48,6 @@
4848
#define ROBJECT_FIELDS ROBJECT_FIELDS
4949
/** @endcond */
5050

51-
/**
52-
* @private
53-
*
54-
* Bits that you can set to ::RBasic::flags.
55-
*/
56-
enum ruby_robject_flags {
57-
/**
58-
* This flag marks that the object's instance variables are stored in an
59-
* external heap buffer.
60-
* Normally, instance variable references are stored inside the object slot,
61-
* but if it overflow, Ruby may have to allocate a separate buffer and spills
62-
* the instance variables there.
63-
* This flag denotes that situation.
64-
*
65-
* @warning This bit has to be considered read-only. Setting/clearing
66-
* this bit without corresponding fix up must cause immediate
67-
* SEGV. Also, internal structures of an object change
68-
* dynamically and transparently throughout of its lifetime.
69-
* Don't assume it being persistent.
70-
*
71-
* @internal
72-
*
73-
* 3rd parties must not be aware that there even is more than one way to
74-
* store instance variables. Might better be hidden.
75-
*/
76-
ROBJECT_HEAP = RUBY_FL_USER4
77-
};
78-
7951
struct st_table;
8052

8153
/**

internal/imemo.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111
#include "ruby/internal/config.h"
1212
#include <stddef.h> /* for size_t */
13+
#include "shape.h"
1314
#include "id_table.h"
1415
#include "internal/array.h" /* for rb_ary_hidden_new_fill */
1516
#include "ruby/internal/stdbool.h" /* for bool */
@@ -171,7 +172,7 @@ imemo_type(VALUE imemo)
171172
static inline int
172173
imemo_type_p(VALUE imemo, enum imemo_type imemo_type)
173174
{
174-
if (LIKELY(!RB_SPECIAL_CONST_P(imemo))) {
175+
if (RB_LIKELY(!RB_SPECIAL_CONST_P(imemo))) {
175176
/* fixed at compile time if imemo_type is given. */
176177
const VALUE mask = (IMEMO_MASK << FL_USHIFT) | RUBY_T_MASK;
177178
const VALUE expected_type = (imemo_type << FL_USHIFT) | T_IMEMO;
@@ -258,7 +259,6 @@ struct rb_fields {
258259
// IMEMO/fields and T_OBJECT have exactly the same layout.
259260
// This is useful for JIT and common codepaths.
260261
#define OBJ_FIELD_HEAP ROBJECT_HEAP
261-
STATIC_ASSERT(imemo_fields_flags, OBJ_FIELD_HEAP == IMEMO_FL_USER0);
262262
STATIC_ASSERT(imemo_fields_embed_offset, offsetof(struct RObject, as.ary) == offsetof(struct rb_fields, as.embed.fields));
263263
STATIC_ASSERT(imemo_fields_external_offset, offsetof(struct RObject, as.heap.fields) == offsetof(struct rb_fields, as.external.ptr));
264264
STATIC_ASSERT(imemo_fields_complex_offset, offsetof(struct RObject, as.heap.fields) == offsetof(struct rb_fields, as.complex.table));
@@ -308,7 +308,7 @@ rb_imemo_fields_ptr(VALUE fields_obj)
308308

309309
RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields) || RB_TYPE_P(fields_obj, T_OBJECT));
310310

311-
if (UNLIKELY(FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP))) {
311+
if (RB_UNLIKELY(rb_obj_shape_heap_p(fields_obj))) {
312312
return IMEMO_OBJ_FIELDS(fields_obj)->as.external.ptr;
313313
}
314314
else {
@@ -324,7 +324,7 @@ rb_imemo_fields_complex_tbl(VALUE fields_obj)
324324
}
325325

326326
RUBY_ASSERT(IMEMO_TYPE_P(fields_obj, imemo_fields) || RB_TYPE_P(fields_obj, T_OBJECT));
327-
RUBY_ASSERT(FL_TEST_RAW(fields_obj, OBJ_FIELD_HEAP));
327+
RUBY_ASSERT(rb_obj_shape_heap_p(fields_obj));
328328

329329
// Some codepaths unconditionally access the fields_ptr, and assume it can be used as st_table if the
330330
// shape is complex.

internal/object.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
* @brief Internal header for Object.
1010
*/
1111
#include "ruby/ruby.h" /* for VALUE */
12+
#include "shape.h"
1213

1314
/* object.c */
1415

@@ -68,7 +69,7 @@ ROBJECT_FIELDS(VALUE obj)
6869

6970
struct RObject *const ptr = ROBJECT(obj);
7071

71-
if (RB_UNLIKELY(RB_FL_ANY_RAW(obj, ROBJECT_HEAP))) {
72+
if (RB_UNLIKELY(rb_obj_shape_heap_p(obj))) {
7273
return ptr->as.heap.fields;
7374
}
7475
else {

internal/variable.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "ruby/internal/stdbool.h" /* for bool */
1515
#include "ruby/ruby.h" /* for VALUE */
1616
#include "shape.h" /* for shape_id_t */
17+
#include "box.h"
18+
#include "internal/gc.h"
1719

1820
/* variable.c */
1921
void rb_gc_mark_global_tbl(void);

object.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj)
364364
RUBY_ASSERT(src_num_ivs <= dest_capa);
365365
if (initial_capa < dest_capa) {
366366
rb_ensure_iv_list_size(dest, 0, dest_capa);
367+
dest_shape_id = rb_shape_transition_heap(dest_shape_id);
367368
dest_buf = ROBJECT_FIELDS(dest);
368369
}
369370

repro_yjit_shape_capacity.rb

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
# frozen_string_literal: true
2+
3+
# Reproducer/probe for dynamic T_OBJECT slot capacity being narrowed before
4+
# shape code clamps it. Run from build/ with:
5+
#
6+
# ./ruby --yjit --yjit-call-threshold=1 ../repro_yjit_shape_capacity.rb
7+
#
8+
# A crashing YJIT run depends on the allocator returning a slot whose capacity
9+
# gets truncated to a smaller attr_index_t value. This script first checks the
10+
# underlying invariant directly so it is useful even when it does not crash.
11+
12+
require "objspace"
13+
14+
POINTER_SIZE = [0].pack("J").bytesize
15+
RBASIC_SIZE = GC::INTERNAL_CONSTANTS.fetch(:RBASIC_SIZE)
16+
17+
def dump_hash(obj)
18+
ObjectSpace.dump(obj).then do |dump|
19+
require "json"
20+
JSON.parse(dump)
21+
end
22+
end
23+
24+
def object_slot_capacity(obj)
25+
dump = dump_hash(obj)
26+
(dump.fetch("slot_size") - RBASIC_SIZE) / POINTER_SIZE
27+
end
28+
29+
def shape_capacity(obj)
30+
return RubyVM::Shape.of(obj).capacity if defined?(RubyVM::Shape)
31+
32+
# Without SHAPE_DEBUG, infer only from behavior below.
33+
nil
34+
end
35+
36+
def embedded?(obj)
37+
dump_hash(obj).key?("embedded")
38+
end
39+
40+
shape_max_fields = if defined?(RubyVM::Shape::SHAPE_MAX_FIELDS)
41+
RubyVM::Shape::SHAPE_MAX_FIELDS
42+
else
43+
126
44+
end
45+
46+
class ShapeCapacityProbe
47+
def read_last
48+
@v_last
49+
end
50+
end
51+
52+
# Force future ShapeCapacityProbe instances to request the largest normal
53+
# T_OBJECT slot that shapes are supposed to support.
54+
seed = ShapeCapacityProbe.new
55+
shape_max_fields.times do |i|
56+
seed.instance_variable_set(:"@seed_#{i}", i)
57+
end
58+
59+
probe = ShapeCapacityProbe.new
60+
slot_capacity = object_slot_capacity(probe)
61+
root_capacity = shape_capacity(probe)
62+
expected_capacity = [slot_capacity, shape_max_fields].min
63+
64+
puts "slot_capacity=#{slot_capacity} expected_shape_capacity=#{expected_capacity} shape_capacity=#{root_capacity.inspect}"
65+
puts "initial=#{ObjectSpace.dump(probe).strip}"
66+
67+
if root_capacity && root_capacity < expected_capacity
68+
abort "BUG: root shape capacity #{root_capacity} is smaller than actual supported capacity #{expected_capacity}"
69+
end
70+
71+
# Behavior-only check for non-SHAPE_DEBUG builds: fill up to the capacity that
72+
# should be embeddable in this slot. A bad truncated root capacity externalizes
73+
# this object before the slot is full.
74+
expected_capacity.times do |i|
75+
name = i == expected_capacity - 1 ? :@v_last : :"@v_#{i}"
76+
probe.instance_variable_set(name, i)
77+
end
78+
79+
puts "filled=#{ObjectSpace.dump(probe).strip}"
80+
81+
unless embedded?(probe)
82+
abort "BUG: object externalized after #{expected_capacity} ivars even though its slot can embed them"
83+
end
84+
85+
if RubyVM.const_defined?(:YJIT) && RubyVM::YJIT.enabled?
86+
expected = expected_capacity - 1
87+
20_000.times do
88+
value = probe.read_last
89+
abort "BUG: YJIT read wrong value #{value.inspect}, expected #{expected.inspect}" unless value == expected
90+
end
91+
puts "YJIT read stress passed"
92+
else
93+
warn "YJIT is not enabled; rerun with --yjit --yjit-call-threshold=1 for the JIT stress path."
94+
end
95+
96+
puts "No capacity mismatch reproduced on this build."

0 commit comments

Comments
 (0)