@@ -167,30 +167,55 @@ void StringBuilder_PrintMsgval(StringBuilder* b, upb_msgval val,
167167// Arena
168168// -----------------------------------------------------------------------------
169169
170- void Arena_free (void * data ) { upb_arena_free (data ); }
170+ typedef struct {
171+ upb_arena * arena ;
172+ VALUE pinned_objs ;
173+ } Arena ;
174+
175+ static void Arena_mark (void * data ) {
176+ Arena * arena = data ;
177+ rb_gc_mark (arena -> pinned_objs );
178+ }
179+
180+ static void Arena_free (void * data ) {
181+ Arena * arena = data ;
182+ upb_arena_free (arena -> arena );
183+ }
171184
172185static VALUE cArena ;
173186
174187const rb_data_type_t Arena_type = {
175188 "Google::Protobuf::Internal::Arena" ,
176- { NULL , Arena_free , NULL },
189+ { Arena_mark , Arena_free , NULL },
190+ .flags = RUBY_TYPED_FREE_IMMEDIATELY ,
177191};
178192
179193static VALUE Arena_alloc (VALUE klass ) {
180- upb_arena * arena = upb_arena_new ();
194+ Arena * arena = ALLOC (Arena );
195+ arena -> arena = upb_arena_new ();
196+ arena -> pinned_objs = Qnil ;
181197 return TypedData_Wrap_Struct (klass , & Arena_type , arena );
182198}
183199
184200upb_arena * Arena_get (VALUE _arena ) {
185- upb_arena * arena ;
186- TypedData_Get_Struct (_arena , upb_arena , & Arena_type , arena );
187- return arena ;
201+ Arena * arena ;
202+ TypedData_Get_Struct (_arena , Arena , & Arena_type , arena );
203+ return arena -> arena ;
188204}
189205
190206VALUE Arena_new () {
191207 return Arena_alloc (cArena );
192208}
193209
210+ void Arena_Pin (VALUE _arena , VALUE obj ) {
211+ Arena * arena ;
212+ TypedData_Get_Struct (_arena , Arena , & Arena_type , arena );
213+ if (arena -> pinned_objs == Qnil ) {
214+ arena -> pinned_objs = rb_ary_new ();
215+ }
216+ rb_ary_push (arena -> pinned_objs , obj );
217+ }
218+
194219void Arena_register (VALUE module ) {
195220 VALUE internal = rb_define_module_under (module , "Internal" );
196221 VALUE klass = rb_define_class_under (internal , "Arena" , rb_cObject );
@@ -209,122 +234,79 @@ void Arena_register(VALUE module) {
209234// different wrapper objects for the same C object, which saves memory and
210235// preserves object identity.
211236//
212- // We use Hash and/or WeakMap for the cache. WeakMap is faster overall
213- // (probably due to removal being integrated with GC) but doesn't work for Ruby
214- // <2.7 (see note below). We need Hash for Ruby <2.7 and for cases where we
215- // need to GC-root the object (notably when the object has been frozen).
237+ // We use WeakMap for the cache. For Ruby <2.7 we also need a secondary Hash
238+ // to store WeakMap keys because Ruby <2.7 WeakMap doesn't allow non-finalizable
239+ // keys.
216240
217241#if RUBY_API_VERSION_CODE >= 20700
218- #define USE_WEAK_MAP 1
242+ #define USE_SECONDARY_MAP 0
219243#else
220- #define USE_WEAK_MAP 0
244+ #define USE_SECONDARY_MAP 1
221245#endif
222246
223- static VALUE ObjectCache_GetKey (const void * key ) {
224- char buf [sizeof (key )];
225- memcpy (& buf , & key , sizeof (key ));
226- intptr_t key_int = (intptr_t )key ;
227- PBRUBY_ASSERT ((key_int & 3 ) == 0 );
228- return LL2NUM (key_int >> 2 );
229- }
247+ #if USE_SECONDARY_MAP
230248
231- // Strong object cache, uses regular Hash and GC-roots objects .
232- // - For Ruby <2.7, used for all objects .
233- // - For Ruby >=2.7, used only for frozen objects, so we preserve the "frozen"
234- // bit (since this information is not preserved at the upb level).
249+ // Maps Numeric -> Object. The object is then used as a key into the WeakMap .
250+ // This is needed for Ruby <2.7 where a number cannot be a key to WeakMap .
251+ // The object is used only for its identity; it does not contain any data.
252+ VALUE secondary_map = Qnil ;
235253
236- VALUE strong_obj_cache = Qnil ;
237-
238- static void StrongObjectCache_Init () {
239- rb_gc_register_address (& strong_obj_cache );
240- strong_obj_cache = rb_hash_new ();
254+ static void SecondaryMap_Init () {
255+ rb_gc_register_address (& secondary_map );
256+ secondary_map = rb_hash_new ();
241257}
242258
243- static void StrongObjectCache_Remove (void * key ) {
244- VALUE key_rb = ObjectCache_GetKey (key );
245- PBRUBY_ASSERT (rb_hash_lookup (strong_obj_cache , key_rb ) != Qnil );
246- rb_hash_delete (strong_obj_cache , key_rb );
259+ static VALUE SecondaryMap_Get (VALUE key ) {
260+ VALUE ret = rb_hash_lookup (secondary_map , key );
261+ if (ret == Qnil ) {
262+ ret = rb_eval_string ("Object.new" );
263+ rb_hash_aset (secondary_map , key , ret );
264+ }
265+ return ret ;
247266}
248267
249- static VALUE StrongObjectCache_Get (const void * key ) {
250- VALUE key_rb = ObjectCache_GetKey (key );
251- return rb_hash_lookup (strong_obj_cache , key_rb );
252- }
268+ #endif
253269
254- static void StrongObjectCache_Add (const void * key , VALUE val ,
255- upb_arena * arena ) {
256- PBRUBY_ASSERT (StrongObjectCache_Get (key ) == Qnil );
257- VALUE key_rb = ObjectCache_GetKey (key );
258- rb_hash_aset (strong_obj_cache , key_rb , val );
259- upb_arena_addcleanup (arena , (void * )key , StrongObjectCache_Remove );
270+ static VALUE ObjectCache_GetKey (const void * key ) {
271+ char buf [sizeof (key )];
272+ memcpy (& buf , & key , sizeof (key ));
273+ intptr_t key_int = (intptr_t )key ;
274+ PBRUBY_ASSERT ((key_int & 3 ) == 0 );
275+ VALUE ret = LL2NUM (key_int >> 2 );
276+ #if USE_SECONDARY_MAP
277+ ret = SecondaryMap_Get (ret );
278+ #endif
279+ return ret ;
260280}
261281
262- // Weak object cache. This speeds up the test suite significantly, so we
263- // presume it speeds up real code also. However we can only use it in Ruby
264- // >=2.7 due to:
265- // https://bugs.ruby-lang.org/issues/16035
266-
267- #if USE_WEAK_MAP
282+ // Public ObjectCache API.
268283
269284VALUE weak_obj_cache = Qnil ;
285+ ID item_get ;
286+ ID item_set ;
270287
271- static void WeakObjectCache_Init () {
288+ static void ObjectCache_Init () {
272289 rb_gc_register_address (& weak_obj_cache );
273290 VALUE klass = rb_eval_string ("ObjectSpace::WeakMap" );
274291 weak_obj_cache = rb_class_new_instance (0 , NULL , klass );
275- }
276-
277- static VALUE WeakObjectCache_Get (const void * key ) {
278- VALUE key_rb = ObjectCache_GetKey (key );
279- VALUE ret = rb_funcall (weak_obj_cache , rb_intern ("[]" ), 1 , key_rb );
280- return ret ;
281- }
282-
283- static void WeakObjectCache_Add (const void * key , VALUE val ) {
284- PBRUBY_ASSERT (WeakObjectCache_Get (key ) == Qnil );
285- VALUE key_rb = ObjectCache_GetKey (key );
286- rb_funcall (weak_obj_cache , rb_intern ("[]=" ), 2 , key_rb , val );
287- PBRUBY_ASSERT (WeakObjectCache_Get (key ) == val );
288- }
289-
290- #endif
291-
292- // Public ObjectCache API.
293-
294- static void ObjectCache_Init () {
295- StrongObjectCache_Init ();
296- #if USE_WEAK_MAP
297- WeakObjectCache_Init ();
292+ item_get = rb_intern ("[]" );
293+ item_set = rb_intern ("[]=" );
294+ #if USE_SECONDARY_MAP
295+ SecondaryMap_Init ();
298296#endif
299297}
300298
301- void ObjectCache_Add (const void * key , VALUE val , upb_arena * arena ) {
302- #if USE_WEAK_MAP
303- (void )arena ;
304- WeakObjectCache_Add (key , val );
305- #else
306- StrongObjectCache_Add (key , val , arena );
307- #endif
299+ void ObjectCache_Add (const void * key , VALUE val ) {
300+ PBRUBY_ASSERT (ObjectCache_Get (key ) == Qnil );
301+ VALUE key_rb = ObjectCache_GetKey (key );
302+ rb_funcall (weak_obj_cache , item_set , 2 , key_rb , val );
303+ PBRUBY_ASSERT (ObjectCache_Get (key ) == val );
308304}
309305
310306// Returns the cached object for this key, if any. Otherwise returns Qnil.
311307VALUE ObjectCache_Get (const void * key ) {
312- #if USE_WEAK_MAP
313- return WeakObjectCache_Get (key );
314- #else
315- return StrongObjectCache_Get (key );
316- #endif
317- }
318-
319- void ObjectCache_Pin (const void * key , VALUE val , upb_arena * arena ) {
320- #if USE_WEAK_MAP
321- PBRUBY_ASSERT (WeakObjectCache_Get (key ) == val );
322- // This will GC-root the object, but we'll still use the weak map for
323- // actual lookup.
324- StrongObjectCache_Add (key , val , arena );
325- #else
326- // Value is already pinned, nothing to do.
327- #endif
308+ VALUE key_rb = ObjectCache_GetKey (key );
309+ return rb_funcall (weak_obj_cache , item_get , 1 , key_rb );
328310}
329311
330312/*
0 commit comments