From 127e90386055117df968e9826c148e146ef0a379 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 26 Aug 2026 08:52:46 +0200 Subject: [PATCH 1/4] hash.c: add a substition hint By guaranteeing that hints can't match `RHASH_AR_CLEARED_HINT` we can save on checking the key for `UNDEF_P` in a bunch of places. --- hash.c | 19 ++++++------------- spec/ruby/core/array/uniq_spec.rb | 12 ++++++------ spec/ruby/core/enumerable/uniq_spec.rb | 4 ++-- spec/ruby/core/hash/element_reference_spec.rb | 4 ++-- spec/ruby/core/hash/rehash_spec.rb | 4 ++-- spec/ruby/core/hash/shared/eql.rb | 4 ++-- 6 files changed, 20 insertions(+), 27 deletions(-) diff --git a/hash.c b/hash.c index b933cdb1edf48b..3ca035595d8231 100644 --- a/hash.c +++ b/hash.c @@ -431,7 +431,8 @@ RHASH_AR_TABLE_MAX_BOUND(VALUE h) #define RHASH_AR_TABLE_MISS RHASH_AR_TABLE_MAX_SIZE #define RHASH_AR_TABLE_REF(hash, n) (&RHASH_AR_TABLE(hash)->pairs[n]) -#define RHASH_AR_CLEARED_HINT 0xff +#define RHASH_AR_CLEARED_HINT 0x00 +#define RHASH_AR_SUBSTITUTION_HINT 0x01 static inline st_hash_t ar_do_hash(VALUE hash, st_data_t key) @@ -445,7 +446,8 @@ ar_do_hash(VALUE hash, st_data_t key) static inline ar_hint_t ar_do_hash_hint(st_hash_t hash_value) { - return (ar_hint_t)hash_value; + ar_hint_t hint = (ar_hint_t)hash_value; + return hint == RHASH_AR_CLEARED_HINT ? RHASH_AR_SUBSTITUTION_HINT : hint; } static inline ar_hint_t @@ -474,19 +476,10 @@ ar_clear_entry(VALUE hash, unsigned int index) ar_hint_set_hint(hash, index, RHASH_AR_CLEARED_HINT); } -static inline int +static inline bool ar_cleared_entry(VALUE hash, unsigned int index) { - if (ar_hint(hash, index) == RHASH_AR_CLEARED_HINT) { - /* RHASH_AR_CLEARED_HINT is only a hint, not mean cleared entry, - * so you need to check key == Qundef - */ - ar_table_pair *pair = RHASH_AR_TABLE_REF(hash, index); - return UNDEF_P(pair->key); - } - else { - return FALSE; - } + return ar_hint(hash, index) == RHASH_AR_CLEARED_HINT; } static inline void diff --git a/spec/ruby/core/array/uniq_spec.rb b/spec/ruby/core/array/uniq_spec.rb index 0289bee7c2cc3d..b88000b168eafc 100644 --- a/spec/ruby/core/array/uniq_spec.rb +++ b/spec/ruby/core/array/uniq_spec.rb @@ -34,8 +34,8 @@ y = mock('1') y.should_not_receive(:eql?) - x.should_receive(:hash).at_least(1).and_return(0) - y.should_receive(:hash).at_least(1).and_return(1) + x.should_receive(:hash).at_least(1).and_return(8) + y.should_receive(:hash).at_least(1).and_return(9) [x, y].uniq.should == [x, y] end @@ -155,9 +155,9 @@ def hash it "compares elements first with hash" do x = mock('0') - x.should_receive(:hash).at_least(1).and_return(0) + x.should_receive(:hash).at_least(1).and_return(8) y = mock('0') - y.should_receive(:hash).at_least(1).and_return(0) + y.should_receive(:hash).at_least(1).and_return(8) a = [x, y] a.uniq! @@ -170,8 +170,8 @@ def hash y = mock('1') y.should_not_receive(:eql?) - x.should_receive(:hash).at_least(1).and_return(0) - y.should_receive(:hash).at_least(1).and_return(1) + x.should_receive(:hash).at_least(1).and_return(8) + y.should_receive(:hash).at_least(1).and_return(9) a = [x, y] a.uniq! diff --git a/spec/ruby/core/enumerable/uniq_spec.rb b/spec/ruby/core/enumerable/uniq_spec.rb index e2999b8690fc83..65a2879465025f 100644 --- a/spec/ruby/core/enumerable/uniq_spec.rb +++ b/spec/ruby/core/enumerable/uniq_spec.rb @@ -33,8 +33,8 @@ y = mock('1') y.should_not_receive(:eql?) - x.should_receive(:hash).at_least(1).and_return(0) - y.should_receive(:hash).at_least(1).and_return(1) + x.should_receive(:hash).at_least(1).and_return(8) + y.should_receive(:hash).at_least(1).and_return(9) [x, y].to_enum.uniq.should == [x, y] end diff --git a/spec/ruby/core/hash/element_reference_spec.rb b/spec/ruby/core/hash/element_reference_spec.rb index 3d074b346d06b9..9ddda2161b8665 100644 --- a/spec/ruby/core/hash/element_reference_spec.rb +++ b/spec/ruby/core/hash/element_reference_spec.rb @@ -84,11 +84,11 @@ it "does not compare keys with different #hash values via #eql?" do x = mock('x') x.should_not_receive(:eql?) - x.stub!(:hash).and_return(0) + x.stub!(:hash).and_return(7) y = mock('y') y.should_not_receive(:eql?) - y.stub!(:hash).and_return(1) + y.stub!(:hash).and_return(8) { y => 1 }[x].should == nil end diff --git a/spec/ruby/core/hash/rehash_spec.rb b/spec/ruby/core/hash/rehash_spec.rb index fcd5a037bdcc88..6eeb38cda3762e 100644 --- a/spec/ruby/core/hash/rehash_spec.rb +++ b/spec/ruby/core/hash/rehash_spec.rb @@ -5,8 +5,8 @@ it "reorganizes the Hash by recomputing all key hash codes" do k1 = Object.new k2 = Object.new - def k1.hash; 0; end - def k2.hash; 1; end + def k1.hash; 5; end + def k2.hash; 6; end h = {} h[k1] = :v1 diff --git a/spec/ruby/core/hash/shared/eql.rb b/spec/ruby/core/hash/shared/eql.rb index 512e1ad016a785..a0147a049e8874 100644 --- a/spec/ruby/core/hash/shared/eql.rb +++ b/spec/ruby/core/hash/shared/eql.rb @@ -32,8 +32,8 @@ x.should_not_receive(:eql?) y.should_not_receive(:eql?) - x.should_receive(:hash).any_number_of_times.and_return(0) - y.should_receive(:hash).any_number_of_times.and_return(1) + x.should_receive(:hash).any_number_of_times.and_return(5) + y.should_receive(:hash).any_number_of_times.and_return(6) { x => 1 }.send(@method, { y => 1 }).should == false end From 89d3b11eace35b8e279b970b4ff5125f171d0d4b Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 5 Aug 2026 08:20:52 -0700 Subject: [PATCH 2/4] [ruby/openssl] Look the SSLContext up from the SSL_CTX in the ALPN/NPN callbacks ossl_sslctx_mark uses rb_gc_mark_movable, so the SSLContext relocates. Its VALUE is stored in four places: the SSL_CTX's ex_data, and the callback argument of the NPN advertise, NPN select and ALPN select callbacks. ossl_sslctx_compact updates the first. Nothing updates the other three, so after a compaction they hold the pre-move address. The three callbacks all receive the SSL, and the SSL_CTX's ex_data copy is already kept current -- so they can look the object up instead of carrying their own copy, which leaves exactly one stored copy and one place to maintain. Registration is one-shot (ossl_sslctx_setup returns early when self is frozen), so the stale address is captured at the first handshake and never refreshed. Fixes https://github.com/ruby/openssl/pull/1088. https://github.com/ruby/openssl/commit/37c62449ac --- ext/openssl/ossl_ssl.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index fcbbec0b3bc438..7cedec7bfbd0e8 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -579,12 +579,21 @@ ssl_npn_select_cb_common(SSL *ssl, VALUE cb, const unsigned char **out, return SSL_TLSEXT_ERR_OK; } +static VALUE +ossl_sslctx_obj_from_ssl(const SSL *ssl) +{ + SSL_CTX *ctx = SSL_get_SSL_CTX(ssl); + + return (VALUE)SSL_CTX_get_ex_data(ctx, ossl_sslctx_ex_ptr_idx); +} + #ifdef OSSL_USE_NEXTPROTONEG static int ssl_npn_advertise_cb(SSL *ssl, const unsigned char **out, unsigned int *outlen, void *arg) { - VALUE protocols = rb_attr_get((VALUE)arg, id_npn_protocols_encoded); + VALUE protocols = rb_attr_get(ossl_sslctx_obj_from_ssl(ssl), + id_npn_protocols_encoded); *out = (const unsigned char *) RSTRING_PTR(protocols); *outlen = RSTRING_LENINT(protocols); @@ -598,7 +607,7 @@ ssl_npn_select_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, { VALUE sslctx_obj, cb; - sslctx_obj = (VALUE) arg; + sslctx_obj = ossl_sslctx_obj_from_ssl(ssl); cb = rb_attr_get(sslctx_obj, id_i_npn_select_cb); return ssl_npn_select_cb_common(ssl, cb, (const unsigned char **)out, @@ -612,7 +621,7 @@ ssl_alpn_select_cb(SSL *ssl, const unsigned char **out, unsigned char *outlen, { VALUE sslctx_obj, cb; - sslctx_obj = (VALUE) arg; + sslctx_obj = ossl_sslctx_obj_from_ssl(ssl); cb = rb_attr_get(sslctx_obj, id_i_alpn_select_cb); return ssl_npn_select_cb_common(ssl, cb, out, outlen, in, inlen); @@ -807,11 +816,11 @@ ossl_sslctx_setup(VALUE self) if (!NIL_P(val)) { VALUE encoded = ssl_encode_npn_protocols(val); rb_ivar_set(self, id_npn_protocols_encoded, encoded); - SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, (void *)self); + SSL_CTX_set_next_protos_advertised_cb(ctx, ssl_npn_advertise_cb, NULL); OSSL_Debug("SSL NPN advertise callback added"); } if (RTEST(rb_attr_get(self, id_i_npn_select_cb))) { - SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, (void *) self); + SSL_CTX_set_next_proto_select_cb(ctx, ssl_npn_select_cb, NULL); OSSL_Debug("SSL NPN select callback added"); } #endif @@ -827,7 +836,7 @@ ossl_sslctx_setup(VALUE self) OSSL_Debug("SSL ALPN values added"); } if (RTEST(rb_attr_get(self, id_i_alpn_select_cb))) { - SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, (void *) self); + SSL_CTX_set_alpn_select_cb(ctx, ssl_alpn_select_cb, NULL); OSSL_Debug("SSL ALPN select callback added"); } From aa30b60133bf5954acd458271658b34532019dec Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Mon, 24 Aug 2026 16:26:27 +0900 Subject: [PATCH 3/4] Reduce memory usage of rb_id_table On 64-bit systems, we used 4 bytes to determine if there is a collision in the entry. This is a waste because it only needs 1 bit. This commit separates each item in rb_id_table into three parts: items (8 byte elements), keys (4 byte elements), and collision_table (bitmap). This will help us save 3.875 bytes for each entry in rb_id_table. All three parts are allocated in the same malloc buffer so there is no increase in the number of mallocs. --- id_table.c | 79 ++++++++++++++++++++++------------ id_table.h | 8 ++++ zjit/src/cruby_bindings.inc.rs | 2 +- 3 files changed, 60 insertions(+), 29 deletions(-) diff --git a/id_table.c b/id_table.c index 90913195c56a82..7027361f054c48 100644 --- a/id_table.c +++ b/id_table.c @@ -30,29 +30,36 @@ id2key(ID id) uses mark-bit on collisions - need extra 1 bit, ID is strictly 3 bits larger than rb_id_serial_t */ -typedef struct rb_id_item { - id_key_t key; #if SIZEOF_VALUE == 8 - int collision; -#endif - VALUE val; -} item_t; +#define COLLISION_TABLE_SIZE(capa) roomof((size_t)(capa), CHAR_BIT) +#define ID_TABLE_ITEMS_SIZE(capa) \ + ((sizeof(VALUE) + sizeof(id_key_t)) * (size_t)(capa) + COLLISION_TABLE_SIZE(capa)) + +#define ITEM_GET_KEY(tbl, i) ((tbl)->keys[i]) +#define ITEM_KEY_ISSET(tbl, i) ((tbl)->items && (tbl)->keys[i]) +#define ITEM_COLLIDED(tbl, i) ((tbl)->collision_table[(i) / CHAR_BIT] & ((uint8_t)1 << ((i) % CHAR_BIT))) +#define ITEM_SET_COLLIDED(tbl, i) ((tbl)->collision_table[(i) / CHAR_BIT] |= ((uint8_t)1 << ((i) % CHAR_BIT))) +#define ITEM_VALUE(tbl, i) ((tbl)->items[i]) -#if SIZEOF_VALUE == 8 -#define ITEM_GET_KEY(tbl, i) ((tbl)->items[i].key) -#define ITEM_KEY_ISSET(tbl, i) ((tbl)->items && (tbl)->items[i].key) -#define ITEM_COLLIDED(tbl, i) ((tbl)->items[i].collision) -#define ITEM_SET_COLLIDED(tbl, i) ((tbl)->items[i].collision = 1) static inline void ITEM_SET_KEY(struct rb_id_table *tbl, int i, id_key_t key) { - tbl->items[i].key = key; + tbl->keys[i] = key; } #else +typedef struct rb_id_item { + id_key_t key; + VALUE val; +} item_t; + +#define ID_TABLE_ITEMS_SIZE(capa) (sizeof(item_t) * (size_t)(capa)) + #define ITEM_GET_KEY(tbl, i) ((tbl)->items[i].key >> 1) #define ITEM_KEY_ISSET(tbl, i) ((tbl)->items[i].key > 1) #define ITEM_COLLIDED(tbl, i) ((tbl)->items[i].key & 1) #define ITEM_SET_COLLIDED(tbl, i) ((tbl)->items[i].key |= 1) +#define ITEM_VALUE(tbl, i) ((tbl)->items[i].val) + static inline void ITEM_SET_KEY(struct rb_id_table *tbl, int i, id_key_t key) { @@ -73,6 +80,19 @@ round_capa(int capa) return (capa + 1) << 2; } +static void +id_table_alloc_items(struct rb_id_table *tbl, int capa) +{ +#if SIZEOF_VALUE == 8 + /* The values, keys, and collision bitmap share a single allocation. */ + tbl->items = ruby_xcalloc(1, ID_TABLE_ITEMS_SIZE(capa)); + tbl->keys = (id_key_t *)(tbl->items + capa); + tbl->collision_table = (uint8_t *)(tbl->keys + capa); +#else + tbl->items = ZALLOC_N(item_t, capa); +#endif +} + struct rb_id_table * rb_id_table_init(struct rb_id_table *tbl, size_t s_capa) { @@ -81,7 +101,7 @@ rb_id_table_init(struct rb_id_table *tbl, size_t s_capa) if (capa > 0) { capa = round_capa(capa); tbl->capa = (int)capa; - tbl->items = ZALLOC_N(item_t, capa); + id_table_alloc_items(tbl, capa); } return tbl; } @@ -111,7 +131,10 @@ rb_id_table_clear(struct rb_id_table *tbl) { tbl->num = 0; tbl->used = 0; - MEMZERO(tbl->items, item_t, tbl->capa); + if (tbl->items) { + /* Values, keys, and collision bitmap live in one allocation. */ + memset(tbl->items, 0, ID_TABLE_ITEMS_SIZE(tbl->capa)); + } } size_t @@ -123,7 +146,7 @@ rb_id_table_size(const struct rb_id_table *tbl) size_t rb_id_table_memsize(const struct rb_id_table *tbl) { - return sizeof(item_t) * tbl->capa + sizeof(struct rb_id_table); + return ID_TABLE_ITEMS_SIZE(tbl->capa) + sizeof(struct rb_id_table); } static int @@ -161,7 +184,7 @@ hash_table_raw_insert(struct rb_id_table *tbl, id_key_t key, VALUE val) tbl->used++; } ITEM_SET_KEY(tbl, ix, key); - tbl->items[ix].val = val; + ITEM_VALUE(tbl, ix) = val; } static int @@ -173,7 +196,7 @@ hash_delete_index(struct rb_id_table *tbl, int ix) } tbl->num--; ITEM_SET_KEY(tbl, ix, 0); - tbl->items[ix].val = 0; + ITEM_VALUE(tbl, ix) = 0; return TRUE; } else { @@ -187,17 +210,17 @@ hash_table_extend(struct rb_id_table* tbl) if (tbl->used + (tbl->used >> 1) >= tbl->capa) { int new_cap = round_capa(tbl->num + (tbl->num >> 1)); int i; - item_t* old; - struct rb_id_table tmp_tbl = {0, 0, 0}; + void *old; + struct rb_id_table tmp_tbl = {0}; if (new_cap < tbl->capa) { new_cap = round_capa(tbl->used + (tbl->used >> 1)); } tmp_tbl.capa = new_cap; - tmp_tbl.items = ZALLOC_N(item_t, new_cap); + id_table_alloc_items(&tmp_tbl, new_cap); for (i = 0; i < tbl->capa; i++) { id_key_t key = ITEM_GET_KEY(tbl, i); if (key != 0) { - hash_table_raw_insert(&tmp_tbl, key, tbl->items[i].val); + hash_table_raw_insert(&tmp_tbl, key, ITEM_VALUE(tbl, i)); } } old = tbl->items; @@ -230,7 +253,7 @@ rb_id_table_lookup(struct rb_id_table *tbl, ID id, VALUE *valp) int index = hash_table_index(tbl, key); if (index >= 0) { - *valp = tbl->items[index].val; + *valp = ITEM_VALUE(tbl, index); return TRUE; } else { @@ -244,7 +267,7 @@ rb_id_table_insert_key(struct rb_id_table *tbl, const id_key_t key, const VALUE const int index = hash_table_index(tbl, key); if (index >= 0) { - tbl->items[index].val = val; + ITEM_VALUE(tbl, index) = val; } else { hash_table_extend(tbl); @@ -275,7 +298,7 @@ rb_id_table_foreach(struct rb_id_table *tbl, rb_id_table_foreach_func_t *func, v for (i=0; iitems[i].val, data); + enum rb_id_table_iterator_result ret = (*func)(key2id(key), ITEM_VALUE(tbl, i), data); RUBY_ASSERT(key != 0); if (ret == ID_TABLE_DELETE) @@ -297,7 +320,7 @@ rb_id_table_foreach_values(struct rb_id_table *tbl, rb_id_table_foreach_values_f for (i=0; iitems[i].val, data); + enum rb_id_table_iterator_result ret = (*func)(ITEM_VALUE(tbl, i), data); if (ret == ID_TABLE_DELETE) hash_delete_index(tbl, i); @@ -314,12 +337,12 @@ rb_id_table_foreach_values_with_replace(struct rb_id_table *tbl, rb_id_table_for for (i = 0; i < capa; i++) { if (ITEM_KEY_ISSET(tbl, i)) { - enum rb_id_table_iterator_result ret = (*func)(tbl->items[i].val, data); + enum rb_id_table_iterator_result ret = (*func)(ITEM_VALUE(tbl, i), data); if (ret == ID_TABLE_REPLACE) { - VALUE val = tbl->items[i].val; + VALUE val = ITEM_VALUE(tbl, i); ret = (*replace)(&val, data, TRUE); - tbl->items[i].val = val; + ITEM_VALUE(tbl, i) = val; } if (ret == ID_TABLE_STOP) diff --git a/id_table.h b/id_table.h index c4333bea98d972..0ee93fdde7c62b 100644 --- a/id_table.h +++ b/id_table.h @@ -4,13 +4,21 @@ #include #include "ruby/ruby.h" +#if SIZEOF_VALUE != 8 struct rb_id_item; +#endif struct rb_id_table { int capa; int num; int used; +#if SIZEOF_VALUE == 8 + VALUE *items; + uint32_t *keys; + uint8_t *collision_table; +#else struct rb_id_item *items; +#endif }; /* compatible with ST_* */ diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index a1b6da8e51c20f..8a59ac5a7c5277 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -410,7 +410,7 @@ pub type rb_atomic_t = ::std::os::raw::c_uint; #[repr(align(8))] #[derive(Debug, Copy, Clone)] pub struct rb_id_table { - pub _bindgen_opaque_blob: [u64; 3usize], + pub _bindgen_opaque_blob: [u64; 5usize], } pub const imemo_env: imemo_type = 0; pub const imemo_cref: imemo_type = 1; From 14a257cfa3bae95c347e6cf56390e0cdd3ee12f8 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 27 Aug 2026 13:23:58 +0900 Subject: [PATCH 4/4] Combine items, keys, collision_table in rb_id_table --- id_table.c | 89 +++++++++++++++++++++------------- id_table.h | 17 +++---- zjit/src/cruby_bindings.inc.rs | 2 +- 3 files changed, 63 insertions(+), 45 deletions(-) diff --git a/id_table.c b/id_table.c index 7027361f054c48..7a780bb9e0bcf5 100644 --- a/id_table.c +++ b/id_table.c @@ -31,20 +31,46 @@ id2key(ID id) ID is strictly 3 bits larger than rb_id_serial_t */ #if SIZEOF_VALUE == 8 +/* The table body is a single buffer laid out as: + + [VALUE values[capa] | id_key_t keys[capa] | collision bitmap] + + where the collision bitmap uses one mark bit per slot. Keeping the + keys out of the item struct avoids padding them to the alignment of + VALUE. The three regions are computed from buf and capa, so the + struct only needs to store the buffer pointer. */ #define COLLISION_TABLE_SIZE(capa) roomof((size_t)(capa), CHAR_BIT) -#define ID_TABLE_ITEMS_SIZE(capa) \ +#define ID_TABLE_BUF_SIZE(capa) \ ((sizeof(VALUE) + sizeof(id_key_t)) * (size_t)(capa) + COLLISION_TABLE_SIZE(capa)) -#define ITEM_GET_KEY(tbl, i) ((tbl)->keys[i]) -#define ITEM_KEY_ISSET(tbl, i) ((tbl)->items && (tbl)->keys[i]) -#define ITEM_COLLIDED(tbl, i) ((tbl)->collision_table[(i) / CHAR_BIT] & ((uint8_t)1 << ((i) % CHAR_BIT))) -#define ITEM_SET_COLLIDED(tbl, i) ((tbl)->collision_table[(i) / CHAR_BIT] |= ((uint8_t)1 << ((i) % CHAR_BIT))) -#define ITEM_VALUE(tbl, i) ((tbl)->items[i]) +static inline VALUE * +id_table_items(struct rb_id_table *tbl) +{ + return (VALUE *)tbl->buf; +} + +static inline id_key_t * +id_table_keys(struct rb_id_table *tbl) +{ + return (id_key_t *)(id_table_items(tbl) + tbl->capa); +} + +static inline uint8_t * +id_table_collision_table(struct rb_id_table *tbl) +{ + return (uint8_t *)(id_table_keys(tbl) + tbl->capa); +} + +#define ITEM_GET_KEY(tbl, i) (id_table_keys(tbl)[i]) +#define ITEM_KEY_ISSET(tbl, i) ((tbl)->buf && id_table_keys(tbl)[i]) +#define ITEM_COLLIDED(tbl, i) (id_table_collision_table(tbl)[(i) / CHAR_BIT] & ((uint8_t)1 << ((i) % CHAR_BIT))) +#define ITEM_SET_COLLIDED(tbl, i) (id_table_collision_table(tbl)[(i) / CHAR_BIT] |= ((uint8_t)1 << ((i) % CHAR_BIT))) +#define ITEM_VALUE(tbl, i) (id_table_items(tbl)[i]) static inline void ITEM_SET_KEY(struct rb_id_table *tbl, int i, id_key_t key) { - tbl->keys[i] = key; + id_table_keys(tbl)[i] = key; } #else typedef struct rb_id_item { @@ -52,18 +78,19 @@ typedef struct rb_id_item { VALUE val; } item_t; -#define ID_TABLE_ITEMS_SIZE(capa) (sizeof(item_t) * (size_t)(capa)) +#define ID_TABLE_BUF_SIZE(capa) (sizeof(item_t) * (size_t)(capa)) +#define id_table_items(tbl) ((item_t *)(tbl)->buf) -#define ITEM_GET_KEY(tbl, i) ((tbl)->items[i].key >> 1) -#define ITEM_KEY_ISSET(tbl, i) ((tbl)->items[i].key > 1) -#define ITEM_COLLIDED(tbl, i) ((tbl)->items[i].key & 1) -#define ITEM_SET_COLLIDED(tbl, i) ((tbl)->items[i].key |= 1) -#define ITEM_VALUE(tbl, i) ((tbl)->items[i].val) +#define ITEM_GET_KEY(tbl, i) (id_table_items(tbl)[i].key >> 1) +#define ITEM_KEY_ISSET(tbl, i) (id_table_items(tbl)[i].key > 1) +#define ITEM_COLLIDED(tbl, i) (id_table_items(tbl)[i].key & 1) +#define ITEM_SET_COLLIDED(tbl, i) (id_table_items(tbl)[i].key |= 1) +#define ITEM_VALUE(tbl, i) (id_table_items(tbl)[i].val) static inline void ITEM_SET_KEY(struct rb_id_table *tbl, int i, id_key_t key) { - tbl->items[i].key = (key << 1) | ITEM_COLLIDED(tbl, i); + id_table_items(tbl)[i].key = (key << 1) | ITEM_COLLIDED(tbl, i); } #endif @@ -81,15 +108,12 @@ round_capa(int capa) } static void -id_table_alloc_items(struct rb_id_table *tbl, int capa) +id_table_alloc_buf(struct rb_id_table *tbl, int capa) { #if SIZEOF_VALUE == 8 - /* The values, keys, and collision bitmap share a single allocation. */ - tbl->items = ruby_xcalloc(1, ID_TABLE_ITEMS_SIZE(capa)); - tbl->keys = (id_key_t *)(tbl->items + capa); - tbl->collision_table = (uint8_t *)(tbl->keys + capa); + tbl->buf = ruby_xcalloc(1, ID_TABLE_BUF_SIZE(capa)); #else - tbl->items = ZALLOC_N(item_t, capa); + tbl->buf = ZALLOC_N(item_t, capa); #endif } @@ -101,7 +125,7 @@ rb_id_table_init(struct rb_id_table *tbl, size_t s_capa) if (capa > 0) { capa = round_capa(capa); tbl->capa = (int)capa; - id_table_alloc_items(tbl, capa); + id_table_alloc_buf(tbl, capa); } return tbl; } @@ -116,13 +140,13 @@ rb_id_table_create(size_t capa) void rb_id_table_free_items(struct rb_id_table *tbl) { - xfree(tbl->items); + xfree(tbl->buf); } void rb_id_table_free(struct rb_id_table *tbl) { - xfree(tbl->items); + xfree(tbl->buf); xfree(tbl); } @@ -131,9 +155,8 @@ rb_id_table_clear(struct rb_id_table *tbl) { tbl->num = 0; tbl->used = 0; - if (tbl->items) { - /* Values, keys, and collision bitmap live in one allocation. */ - memset(tbl->items, 0, ID_TABLE_ITEMS_SIZE(tbl->capa)); + if (tbl->buf) { + memset(tbl->buf, 0, ID_TABLE_BUF_SIZE(tbl->capa)); } } @@ -146,7 +169,7 @@ rb_id_table_size(const struct rb_id_table *tbl) size_t rb_id_table_memsize(const struct rb_id_table *tbl) { - return ID_TABLE_ITEMS_SIZE(tbl->capa) + sizeof(struct rb_id_table); + return ID_TABLE_BUF_SIZE(tbl->capa) + sizeof(struct rb_id_table); } static int @@ -216,14 +239,14 @@ hash_table_extend(struct rb_id_table* tbl) new_cap = round_capa(tbl->used + (tbl->used >> 1)); } tmp_tbl.capa = new_cap; - id_table_alloc_items(&tmp_tbl, new_cap); + id_table_alloc_buf(&tmp_tbl, new_cap); for (i = 0; i < tbl->capa; i++) { id_key_t key = ITEM_GET_KEY(tbl, i); if (key != 0) { hash_table_raw_insert(&tmp_tbl, key, ITEM_VALUE(tbl, i)); } } - old = tbl->items; + old = tbl->buf; *tbl = tmp_tbl; xfree(old); } @@ -233,14 +256,14 @@ hash_table_extend(struct rb_id_table* tbl) static void hash_table_show(struct rb_id_table *tbl) { - const id_key_t *keys = tbl->keys; const int capa = tbl->capa; int i; fprintf(stderr, "tbl: %p (capa: %d, num: %d, used: %d)\n", tbl, tbl->capa, tbl->num, tbl->used); for (i=0; i [%d] %s %d\n", i, rb_id2name(key2id(keys[i])), (int)keys[i]); + const id_key_t key = ITEM_GET_KEY(tbl, i); + fprintf(stderr, " -> [%d] %s %d\n", i, rb_id2name(key2id(key)), (int)key); } } } @@ -314,7 +337,7 @@ rb_id_table_foreach_values(struct rb_id_table *tbl, rb_id_table_foreach_values_f { int i, capa = tbl->capa; - if (!tbl->items) { + if (!tbl->buf) { return; } @@ -390,7 +413,7 @@ rb_managed_id_table_create(const rb_data_type_t *type, size_t capa) struct rb_id_table *tbl; VALUE obj = TypedData_Make_Struct(0, struct rb_id_table, type, tbl); RB_OBJ_SET_SHAREABLE(obj); - rb_id_table_init(tbl, capa); // NOTE: this can cause GC, so dmark and dsize need to check tbl->items + rb_id_table_init(tbl, capa); // NOTE: this can cause GC, so dmark and dsize need to check tbl->buf return obj; } diff --git a/id_table.h b/id_table.h index 0ee93fdde7c62b..b9c1f8af0c3f8f 100644 --- a/id_table.h +++ b/id_table.h @@ -4,21 +4,16 @@ #include #include "ruby/ruby.h" -#if SIZEOF_VALUE != 8 -struct rb_id_item; -#endif - struct rb_id_table { int capa; int num; int used; -#if SIZEOF_VALUE == 8 - VALUE *items; - uint32_t *keys; - uint8_t *collision_table; -#else - struct rb_id_item *items; -#endif + /* The table body is a single buffer laid out as: + * + * [VALUE values[capa] | id_key_t keys[capa] | collision bitmap] + * + * where the collision bitmap uses one mark bit per slot. */ + void *buf; }; /* compatible with ST_* */ diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 8a59ac5a7c5277..a1b6da8e51c20f 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -410,7 +410,7 @@ pub type rb_atomic_t = ::std::os::raw::c_uint; #[repr(align(8))] #[derive(Debug, Copy, Clone)] pub struct rb_id_table { - pub _bindgen_opaque_blob: [u64; 5usize], + pub _bindgen_opaque_blob: [u64; 3usize], } pub const imemo_env: imemo_type = 0; pub const imemo_cref: imemo_type = 1;