Skip to content

Commit d82d8a4

Browse files
casperisfinecopybara-github
authored andcommitted
Ruby: Implement Write Barriers (#11793)
Write barrier protected objects are allowed to be promoted to the old generation, which means they only get marked on major GC. The downside is that the `RB_BJ_WRITE` macro MUST be used to set references, otherwise the referenced object may be garbaged collected. But the `*Descriptor` classes and `Arena` have very few references and are only set in a few places, so it's relatively easy to implement. cc @peterzhu2118 Closes #11793 COPYBARA_INTEGRATE_REVIEW=#11793 from casperisfine:descriptor-write-barrier 215e8fa PiperOrigin-RevId: 511875342
1 parent 08c5557 commit d82d8a4

File tree

3 files changed

+31
-17
lines changed

3 files changed

+31
-17
lines changed

ruby/ext/google/protobuf_c/defs.c

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ static void DescriptorPool_register(VALUE module) {
223223

224224
typedef struct {
225225
const upb_MessageDef* msgdef;
226+
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
227+
// macro to update VALUE references, as to trigger write barriers.
226228
VALUE klass;
227229
VALUE descriptor_pool;
228230
} Descriptor;
@@ -238,7 +240,7 @@ static void Descriptor_mark(void* _self) {
238240
static const rb_data_type_t Descriptor_type = {
239241
"Google::Protobuf::Descriptor",
240242
{Descriptor_mark, RUBY_DEFAULT_FREE, NULL},
241-
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
243+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
242244
};
243245

244246
static Descriptor* ruby_to_Descriptor(VALUE val) {
@@ -280,7 +282,7 @@ static VALUE Descriptor_initialize(VALUE _self, VALUE cookie,
280282
"Descriptor objects may not be created from Ruby.");
281283
}
282284

283-
self->descriptor_pool = descriptor_pool;
285+
RB_OBJ_WRITE(_self, &self->descriptor_pool, descriptor_pool);
284286
self->msgdef = (const upb_MessageDef*)NUM2ULL(ptr);
285287

286288
return Qnil;
@@ -390,7 +392,7 @@ static VALUE Descriptor_lookup_oneof(VALUE _self, VALUE name) {
390392
static VALUE Descriptor_msgclass(VALUE _self) {
391393
Descriptor* self = ruby_to_Descriptor(_self);
392394
if (self->klass == Qnil) {
393-
self->klass = build_class_from_descriptor(_self);
395+
RB_OBJ_WRITE(_self, &self->klass, build_class_from_descriptor(_self));
394396
}
395397
return self->klass;
396398
}
@@ -417,6 +419,8 @@ static void Descriptor_register(VALUE module) {
417419

418420
typedef struct {
419421
const upb_FileDef* filedef;
422+
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
423+
// macro to update VALUE references, as to trigger write barriers.
420424
VALUE descriptor_pool; // Owns the upb_FileDef.
421425
} FileDescriptor;
422426

@@ -430,7 +434,7 @@ static void FileDescriptor_mark(void* _self) {
430434
static const rb_data_type_t FileDescriptor_type = {
431435
"Google::Protobuf::FileDescriptor",
432436
{FileDescriptor_mark, RUBY_DEFAULT_FREE, NULL},
433-
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
437+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
434438
};
435439

436440
static FileDescriptor* ruby_to_FileDescriptor(VALUE val) {
@@ -463,7 +467,7 @@ static VALUE FileDescriptor_initialize(VALUE _self, VALUE cookie,
463467
"Descriptor objects may not be created from Ruby.");
464468
}
465469

466-
self->descriptor_pool = descriptor_pool;
470+
RB_OBJ_WRITE(_self, &self->descriptor_pool, descriptor_pool);
467471
self->filedef = (const upb_FileDef*)NUM2ULL(ptr);
468472

469473
return Qnil;
@@ -519,6 +523,8 @@ static void FileDescriptor_register(VALUE module) {
519523

520524
typedef struct {
521525
const upb_FieldDef* fielddef;
526+
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
527+
// macro to update VALUE references, as to trigger write barriers.
522528
VALUE descriptor_pool; // Owns the upb_FieldDef.
523529
} FieldDescriptor;
524530

@@ -532,7 +538,7 @@ static void FieldDescriptor_mark(void* _self) {
532538
static const rb_data_type_t FieldDescriptor_type = {
533539
"Google::Protobuf::FieldDescriptor",
534540
{FieldDescriptor_mark, RUBY_DEFAULT_FREE, NULL},
535-
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
541+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
536542
};
537543

538544
static FieldDescriptor* ruby_to_FieldDescriptor(VALUE val) {
@@ -570,7 +576,7 @@ static VALUE FieldDescriptor_initialize(VALUE _self, VALUE cookie,
570576
"Descriptor objects may not be created from Ruby.");
571577
}
572578

573-
self->descriptor_pool = descriptor_pool;
579+
RB_OBJ_WRITE(_self, &self->descriptor_pool, descriptor_pool);
574580
self->fielddef = (const upb_FieldDef*)NUM2ULL(ptr);
575581

576582
return Qnil;
@@ -884,6 +890,8 @@ static void FieldDescriptor_register(VALUE module) {
884890

885891
typedef struct {
886892
const upb_OneofDef* oneofdef;
893+
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
894+
// macro to update VALUE references, as to trigger write barriers.
887895
VALUE descriptor_pool; // Owns the upb_OneofDef.
888896
} OneofDescriptor;
889897

@@ -897,7 +905,7 @@ static void OneofDescriptor_mark(void* _self) {
897905
static const rb_data_type_t OneofDescriptor_type = {
898906
"Google::Protobuf::OneofDescriptor",
899907
{OneofDescriptor_mark, RUBY_DEFAULT_FREE, NULL},
900-
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
908+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
901909
};
902910

903911
static OneofDescriptor* ruby_to_OneofDescriptor(VALUE val) {
@@ -936,7 +944,7 @@ static VALUE OneofDescriptor_initialize(VALUE _self, VALUE cookie,
936944
"Descriptor objects may not be created from Ruby.");
937945
}
938946

939-
self->descriptor_pool = descriptor_pool;
947+
RB_OBJ_WRITE(_self, &self->descriptor_pool, descriptor_pool);
940948
self->oneofdef = (const upb_OneofDef*)NUM2ULL(ptr);
941949

942950
return Qnil;
@@ -988,6 +996,8 @@ static void OneofDescriptor_register(VALUE module) {
988996

989997
typedef struct {
990998
const upb_EnumDef* enumdef;
999+
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
1000+
// macro to update VALUE references, as to trigger write barriers.
9911001
VALUE module; // begins as nil
9921002
VALUE descriptor_pool; // Owns the upb_EnumDef.
9931003
} EnumDescriptor;
@@ -1003,7 +1013,7 @@ static void EnumDescriptor_mark(void* _self) {
10031013
static const rb_data_type_t EnumDescriptor_type = {
10041014
"Google::Protobuf::EnumDescriptor",
10051015
{EnumDescriptor_mark, RUBY_DEFAULT_FREE, NULL},
1006-
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
1016+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
10071017
};
10081018

10091019
static EnumDescriptor* ruby_to_EnumDescriptor(VALUE val) {
@@ -1042,7 +1052,7 @@ static VALUE EnumDescriptor_initialize(VALUE _self, VALUE cookie,
10421052
"Descriptor objects may not be created from Ruby.");
10431053
}
10441054

1045-
self->descriptor_pool = descriptor_pool;
1055+
RB_OBJ_WRITE(_self, &self->descriptor_pool, descriptor_pool);
10461056
self->enumdef = (const upb_EnumDef*)NUM2ULL(ptr);
10471057

10481058
return Qnil;
@@ -1138,7 +1148,7 @@ static VALUE EnumDescriptor_each(VALUE _self) {
11381148
static VALUE EnumDescriptor_enummodule(VALUE _self) {
11391149
EnumDescriptor* self = ruby_to_EnumDescriptor(_self);
11401150
if (self->module == Qnil) {
1141-
self->module = build_module_from_enumdesc(_self);
1151+
RB_OBJ_WRITE(_self, &self->module, build_module_from_enumdesc(_self));
11421152
}
11431153
return self->module;
11441154
}

ruby/ext/google/protobuf_c/message.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ VALUE MessageOrEnum_GetDescriptor(VALUE klass) {
5353
// -----------------------------------------------------------------------------
5454

5555
typedef struct {
56+
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
57+
// macro to update VALUE references, as to trigger write barriers.
5658
VALUE arena;
5759
const upb_Message* msg; // Can get as mutable when non-frozen.
5860
const upb_MessageDef*
@@ -65,9 +67,9 @@ static void Message_mark(void* _self) {
6567
}
6668

6769
static rb_data_type_t Message_type = {
68-
"Message",
70+
"Google::Protobuf::Message",
6971
{Message_mark, RUBY_DEFAULT_FREE, NULL},
70-
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
72+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
7173
};
7274

7375
static Message* ruby_to_Message(VALUE msg_rb) {
@@ -105,7 +107,7 @@ upb_Message* Message_GetMutable(VALUE msg_rb, const upb_MessageDef** m) {
105107
void Message_InitPtr(VALUE self_, upb_Message* msg, VALUE arena) {
106108
Message* self = ruby_to_Message(self_);
107109
self->msg = msg;
108-
self->arena = arena;
110+
RB_OBJ_WRITE(self_, &self->arena, arena);
109111
ObjectCache_Add(msg, self_);
110112
}
111113

ruby/ext/google/protobuf_c/protobuf.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ void StringBuilder_PrintMsgval(StringBuilder *b, upb_MessageValue val,
171171

172172
typedef struct {
173173
upb_Arena *arena;
174+
// IMPORTANT: WB_PROTECTED objects must only use the RB_OBJ_WRITE()
175+
// macro to update VALUE references, as to trigger write barriers.
174176
VALUE pinned_objs;
175177
} Arena;
176178

@@ -190,7 +192,7 @@ static VALUE cArena;
190192
const rb_data_type_t Arena_type = {
191193
"Google::Protobuf::Internal::Arena",
192194
{Arena_mark, Arena_free, NULL},
193-
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
195+
.flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED,
194196
};
195197

196198
static void* ruby_upb_allocfunc(upb_alloc* alloc, void* ptr, size_t oldsize, size_t size) {
@@ -233,7 +235,7 @@ void Arena_Pin(VALUE _arena, VALUE obj) {
233235
Arena *arena;
234236
TypedData_Get_Struct(_arena, Arena, &Arena_type, arena);
235237
if (arena->pinned_objs == Qnil) {
236-
arena->pinned_objs = rb_ary_new();
238+
RB_OBJ_WRITE(_arena, &arena->pinned_objs, rb_ary_new());
237239
}
238240
rb_ary_push(arena->pinned_objs, obj);
239241
}

0 commit comments

Comments
 (0)