Ruby 4.1.0dev (2026-03-20 revision eb04ab9117336f2b3613244cfe8a528c52faf6d6)
pm_constant_pool.c
3
7void
8pm_constant_id_list_init(pm_constant_id_list_t *list) {
9 list->ids = NULL;
10 list->size = 0;
11 list->capacity = 0;
12}
13
17void
18pm_constant_id_list_init_capacity(pm_arena_t *arena, pm_constant_id_list_t *list, size_t capacity) {
19 if (capacity) {
21 } else {
22 list->ids = NULL;
23 }
24
25 list->size = 0;
26 list->capacity = capacity;
27}
28
32void
33pm_constant_id_list_append(pm_arena_t *arena, pm_constant_id_list_t *list, pm_constant_id_t id) {
34 if (list->size >= list->capacity) {
35 size_t new_capacity = list->capacity == 0 ? 8 : list->capacity * 2;
37
38 if (list->size > 0) {
39 memcpy(new_ids, list->ids, list->size * sizeof(pm_constant_id_t));
40 }
41
42 list->ids = new_ids;
43 list->capacity = new_capacity;
44 }
45
46 list->ids[list->size++] = id;
47}
48
52void
53pm_constant_id_list_insert(pm_constant_id_list_t *list, size_t index, pm_constant_id_t id) {
54 assert(index < list->capacity);
55 assert(list->ids[index] == PM_CONSTANT_ID_UNSET);
56
57 list->ids[index] = id;
58 list->size++;
59}
60
64bool
65pm_constant_id_list_includes(pm_constant_id_list_t *list, pm_constant_id_t id) {
66 for (size_t index = 0; index < list->size; index++) {
67 if (list->ids[index] == id) return true;
68 }
69 return false;
70}
71
79static inline uint32_t
80pm_constant_pool_hash(const uint8_t *start, size_t length) {
81 // This constant is borrowed from wyhash. It is a 64-bit odd integer with
82 // roughly equal 0/1 bits, chosen for good avalanche behavior when used in
83 // multiply-xorshift sequences.
84 static const uint64_t secret = 0x517cc1b727220a95ULL;
85 uint64_t hash = (uint64_t) length;
86
87 if (length <= 8) {
88 // Short strings: read first and last 4 bytes (overlapping for len < 8).
89 // This covers the majority of Ruby identifiers with a single multiply.
90 if (length >= 4) {
91 uint32_t a, b;
92 memcpy(&a, start, 4);
93 memcpy(&b, start + length - 4, 4);
94 hash ^= (uint64_t) a | ((uint64_t) b << 32);
95 } else if (length > 0) {
96 hash ^= (uint64_t) start[0] | ((uint64_t) start[length >> 1] << 8) | ((uint64_t) start[length - 1] << 16);
97 }
98 hash *= secret;
99 } else if (length <= 16) {
100 // Medium strings: read first and last 8 bytes (overlapping).
101 // Two multiplies instead of the three the loop-based approach needs.
102 uint64_t word;
103 memcpy(&word, start, 8);
104 hash ^= word;
105 hash *= secret;
106 memcpy(&word, start + length - 8, 8);
107 hash ^= word;
108 hash *= secret;
109 } else {
110 const uint8_t *ptr = start;
111 size_t remaining = length;
112
113 while (remaining >= 8) {
114 uint64_t word;
115 memcpy(&word, ptr, 8);
116 hash ^= word;
117 hash *= secret;
118 ptr += 8;
119 remaining -= 8;
120 }
121
122 if (remaining > 0) {
123 // Read the last 8 bytes (overlapping with already-processed data).
124 uint64_t word;
125 memcpy(&word, start + length - 8, 8);
126 hash ^= word;
127 hash *= secret;
128 }
129 }
130
131 hash ^= hash >> 32;
132 return (uint32_t) hash;
133}
134
138static uint32_t
139next_power_of_two(uint32_t v) {
140 // Avoid underflow in subtraction on next line.
141 if (v == 0) {
142 // 1 is the nearest power of 2 to 0 (2^0)
143 return 1;
144 }
145 v--;
146 v |= v >> 1;
147 v |= v >> 2;
148 v |= v >> 4;
149 v |= v >> 8;
150 v |= v >> 16;
151 v++;
152 return v;
153}
154
155#ifndef NDEBUG
156static bool
157is_power_of_two(uint32_t size) {
158 return (size & (size - 1)) == 0;
159}
160#endif
161
165static inline void
166pm_constant_pool_resize(pm_arena_t *arena, pm_constant_pool_t *pool) {
167 assert(is_power_of_two(pool->capacity));
168
169 uint32_t next_capacity = pool->capacity * 2;
170 const uint32_t mask = next_capacity - 1;
171
173 pm_constant_t *next_constants = (pm_constant_t *) pm_arena_alloc(arena, next_capacity * sizeof(pm_constant_t), PRISM_ALIGNOF(pm_constant_t));
174
175 // For each bucket in the current constant pool, find the index in the
176 // next constant pool, and insert it.
177 for (uint32_t index = 0; index < pool->capacity; index++) {
178 pm_constant_pool_bucket_t *bucket = &pool->buckets[index];
179
180 // If an id is set on this constant, then we know we have content here.
181 // In this case we need to insert it into the next constant pool.
182 if (bucket->id != PM_CONSTANT_ID_UNSET) {
183 uint32_t next_index = bucket->hash & mask;
184
185 // This implements linear scanning to find the next available slot
186 // in case this index is already taken. We don't need to bother
187 // comparing the values since we know that the hash is unique.
188 while (next_buckets[next_index].id != PM_CONSTANT_ID_UNSET) {
189 next_index = (next_index + 1) & mask;
190 }
191
192 // Here we copy over the entire bucket, which includes the id so
193 // that they are consistent between resizes.
194 next_buckets[next_index] = *bucket;
195 }
196 }
197
198 // The constants are stable with respect to hash table resizes.
199 memcpy(next_constants, pool->constants, pool->size * sizeof(pm_constant_t));
200
201 pool->constants = next_constants;
202 pool->buckets = next_buckets;
203 pool->capacity = next_capacity;
204}
205
209void
210pm_constant_pool_init(pm_arena_t *arena, pm_constant_pool_t *pool, uint32_t capacity) {
211 capacity = next_power_of_two(capacity);
212
214 pool->constants = (pm_constant_t *) pm_arena_alloc(arena, capacity * sizeof(pm_constant_t), PRISM_ALIGNOF(pm_constant_t));
215 pool->size = 0;
216 pool->capacity = capacity;
217}
218
223pm_constant_pool_id_to_constant(const pm_constant_pool_t *pool, pm_constant_id_t constant_id) {
224 assert(constant_id != PM_CONSTANT_ID_UNSET && constant_id <= pool->size);
225 return &pool->constants[constant_id - 1];
226}
227
233pm_constant_pool_find(const pm_constant_pool_t *pool, const uint8_t *start, size_t length) {
234 assert(is_power_of_two(pool->capacity));
235 const uint32_t mask = pool->capacity - 1;
236
237 uint32_t hash = pm_constant_pool_hash(start, length);
238 uint32_t index = hash & mask;
240
241 while (bucket = &pool->buckets[index], bucket->id != PM_CONSTANT_ID_UNSET) {
242 if ((bucket->length == length) && memcmp(bucket->start, start, length) == 0) {
243 return bucket->id;
244 }
245
246 index = (index + 1) & mask;
247 }
248
250}
251
255static inline pm_constant_id_t
256pm_constant_pool_insert(pm_arena_t *arena, pm_constant_pool_t *pool, const uint8_t *start, size_t length, pm_constant_pool_bucket_type_t type) {
257 if (pool->size >= (pool->capacity / 4 * 3)) {
258 pm_constant_pool_resize(arena, pool);
259 }
260
261 assert(is_power_of_two(pool->capacity));
262 const uint32_t mask = pool->capacity - 1;
263
264 uint32_t hash = pm_constant_pool_hash(start, length);
265 uint32_t index = hash & mask;
267
268 while (bucket = &pool->buckets[index], bucket->id != PM_CONSTANT_ID_UNSET) {
269 // If there is a collision, then we need to check if the content is the
270 // same as the content we are trying to insert. If it is, then we can
271 // return the id of the existing constant.
272 if ((bucket->length == length) && memcmp(bucket->start, start, length) == 0) {
273 // Since we have found a match, we need to check if this is
274 // attempting to insert a shared or an owned constant. We want to
275 // prefer shared constants since they don't require allocations.
277 // If we're attempting to insert a shared constant and the
278 // existing constant is owned, then we can replace it with the
279 // shared constant to prefer non-owned references.
280 bucket->start = start;
281 bucket->type = (unsigned int) (type & 0x3);
282 pool->constants[bucket->id - 1].start = start;
283 }
284
285 return bucket->id;
286 }
287
288 index = (index + 1) & mask;
289 }
290
291 // IDs are allocated starting at 1, since the value 0 denotes a non-existent
292 // constant.
293 uint32_t id = ++pool->size;
294 assert(pool->size < ((uint32_t) (1 << 30)));
295
296 *bucket = (pm_constant_pool_bucket_t) {
297 .id = (unsigned int) (id & 0x3fffffff),
298 .type = (unsigned int) (type & 0x3),
299 .hash = hash,
300 .start = start,
301 .length = length
302 };
303
304 pool->constants[id - 1] = (pm_constant_t) {
305 .start = start,
306 .length = length,
307 };
308
309 return id;
310}
311
317pm_constant_pool_insert_shared(pm_arena_t *arena, pm_constant_pool_t *pool, const uint8_t *start, size_t length) {
318 return pm_constant_pool_insert(arena, pool, start, length, PM_CONSTANT_POOL_BUCKET_DEFAULT);
319}
320
327pm_constant_pool_insert_owned(pm_arena_t *arena, pm_constant_pool_t *pool, uint8_t *start, size_t length) {
328 return pm_constant_pool_insert(arena, pool, start, length, PM_CONSTANT_POOL_BUCKET_OWNED);
329}
330
337pm_constant_pool_insert_constant(pm_arena_t *arena, pm_constant_pool_t *pool, const uint8_t *start, size_t length) {
338 return pm_constant_pool_insert(arena, pool, start, length, PM_CONSTANT_POOL_BUCKET_CONSTANT);
339}
340
VALUE type(ANYARGS)
ANYARGS-ed function type.
A bump allocator for the prism parser.
static PRISM_FORCE_INLINE void * pm_arena_alloc(pm_arena_t *arena, size_t size, size_t alignment)
Allocate memory from the arena.
Definition pm_arena.h:80
static void * pm_arena_zalloc(pm_arena_t *arena, size_t size, size_t alignment)
Allocate zero-initialized memory from the arena.
Definition pm_arena.h:104
A data structure that stores a set of strings.
static const pm_constant_pool_bucket_type_t PM_CONSTANT_POOL_BUCKET_DEFAULT
By default, each constant is a slice of the source.
#define PM_CONSTANT_ID_UNSET
When we allocate constants into the pool, we reserve 0 to mean that the slot is not yet filled.
unsigned int pm_constant_pool_bucket_type_t
The type of bucket in the constant pool hash map.
uint32_t pm_constant_id_t
A constant id is a unique identifier for a constant in the constant pool.
static const pm_constant_pool_bucket_type_t PM_CONSTANT_POOL_BUCKET_OWNED
An owned constant is one for which memory has been allocated.
static const pm_constant_pool_bucket_type_t PM_CONSTANT_POOL_BUCKET_CONSTANT
A constant constant is known at compile time.
#define PRISM_ALIGNOF
Get the alignment requirement of a type.
Definition defines.h:358
A bump allocator.
Definition pm_arena.h:39
A list of constant IDs.
size_t size
The number of constant ids in the list.
size_t capacity
The number of constant ids that have been allocated in the list.
pm_constant_id_t * ids
The constant ids in the list.
A bucket in the hash map.
size_t length
The length of the string.
const uint8_t * start
A pointer to the start of the string, stored directly in the bucket to avoid a pointer chase to the c...
uint32_t hash
The hash of the bucket.
unsigned int id
The incremental ID used for indexing back into the pool.
pm_constant_pool_bucket_type_t type
The type of the bucket, which determines how to free it.
The overall constant pool, which stores constants found while parsing.
uint32_t capacity
The number of buckets that have been allocated in the hash map.
pm_constant_pool_bucket_t * buckets
The buckets in the hash map.
uint32_t size
The number of buckets in the hash map.
pm_constant_t * constants
The constants that are stored in the buckets.
A constant in the pool which effectively stores a string.
const uint8_t * start
A pointer to the start of the string.