2#include "internal/gc.h"
3#include "internal/concurrent_set.h"
8#define CONCURRENT_SET_CONTINUATION_BIT ((VALUE)1 << (sizeof(VALUE) * CHAR_BIT - 1))
9#define CONCURRENT_SET_HASH_MASK (~CONCURRENT_SET_CONTINUATION_BIT)
11enum concurrent_set_special_values {
13 CONCURRENT_SET_DELETED,
15 CONCURRENT_SET_SPECIAL_VALUE_COUNT
25 unsigned int capacity;
26 unsigned int deleted_entries;
34 if (curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT)
return;
36 RUBY_ASSERT((curr_hash_and_flags & CONCURRENT_SET_HASH_MASK) != 0);
38 VALUE new_hash = curr_hash_and_flags | CONCURRENT_SET_CONTINUATION_BIT;
39 VALUE prev_hash = rbimpl_atomic_value_cas(&entry->hash, curr_hash_and_flags, new_hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
44 RUBY_ASSERT(prev_hash == curr_hash_and_flags || prev_hash == new_hash);
51 VALUE hash = set->funcs->hash(key);
52 hash &= CONCURRENT_SET_HASH_MASK;
54 hash ^= CONCURRENT_SET_HASH_MASK;
57 RUBY_ASSERT(!(hash & CONCURRENT_SET_CONTINUATION_BIT));
62concurrent_set_free(
void *ptr)
65 SIZED_FREE_N(set->entries, set->capacity);
69concurrent_set_size(
const void *ptr)
82concurrent_set_mark(
void *ptr)
90 .dmark = concurrent_set_mark,
91 .dfree = concurrent_set_free,
92 .dsize = concurrent_set_size,
95 .flags = RUBY_TYPED_THREAD_SAFE_FREE | RUBY_TYPED_EMBEDDABLE
105 set->capacity = capacity;
108 RB_OBJ_SET_SHAREABLE(obj);
113rb_concurrent_set_size(
VALUE set_obj)
129 RUBY_ASSERT((set->capacity & (set->capacity - 1)) == 0);
131 probe->mask = set->capacity - 1;
132 probe->idx = hash & probe->mask;
140 probe->idx = (probe->idx + probe->d) & probe->mask;
145concurrent_set_try_resize_without_locking(
VALUE old_set_obj,
VALUE *set_obj_ptr)
148 if (rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE) != old_set_obj) {
152 struct concurrent_set *old_set = RTYPEDDATA_GET_DATA(old_set_obj);
156 int expected_size = rbimpl_atomic_load(&old_set->size, RBIMPL_ATOMIC_RELAXED) - old_set->deleted_entries;
160 int old_capacity = old_set->capacity;
161 int new_capacity = old_capacity * 2;
162 if (new_capacity > expected_size * 8) {
163 new_capacity = old_capacity / 2;
165 else if (new_capacity > expected_size * 4) {
166 new_capacity = old_capacity;
170 VALUE new_set_obj = rb_concurrent_set_new(old_set->funcs, new_capacity);
171 struct concurrent_set *new_set = RTYPEDDATA_GET_DATA(new_set_obj);
173 for (
int i = 0; i < old_capacity; i++) {
175 VALUE key = rbimpl_atomic_value_exchange(&old_entry->key, CONCURRENT_SET_MOVED, RBIMPL_ATOMIC_ACQUIRE);
178 if (key < CONCURRENT_SET_SPECIAL_VALUE_COUNT)
continue;
181 VALUE hash = rbimpl_atomic_value_load(&old_entry->hash, RBIMPL_ATOMIC_RELAXED) & CONCURRENT_SET_HASH_MASK;
183 RUBY_ASSERT(hash == concurrent_set_hash(old_set, key));
187 int idx = concurrent_set_probe_start(&probe, new_set, hash);
192 if (entry->hash == CONCURRENT_SET_EMPTY) {
196 RUBY_ASSERT(new_set->size <= new_set->capacity / 2);
203 RUBY_ASSERT(entry->key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT);
204 entry->hash |= CONCURRENT_SET_CONTINUATION_BIT;
205 idx = concurrent_set_probe_next(&probe);
209 rbimpl_atomic_value_store(set_obj_ptr, new_set_obj, RBIMPL_ATOMIC_RELEASE);
215concurrent_set_try_resize(
VALUE old_set_obj,
VALUE *set_obj_ptr)
218 concurrent_set_try_resize_without_locking(old_set_obj, set_obj_ptr);
223rb_concurrent_set_find(
VALUE *set_obj_ptr,
VALUE key)
225 RUBY_ASSERT(key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT);
234 set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE);
236 set = RTYPEDDATA_GET_DATA(set_obj);
241 hash = concurrent_set_hash(set, key);
243 RUBY_ASSERT(hash == concurrent_set_hash(set, key));
245 idx = concurrent_set_probe_start(&probe, set, hash);
249 VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE);
250 VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK;
251 bool continuation = curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT;
253 if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) {
257 if (curr_hash != hash) {
261 idx = concurrent_set_probe_next(&probe);
265 VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE);
268 case CONCURRENT_SET_EMPTY:
271 case CONCURRENT_SET_DELETED:
273 case CONCURRENT_SET_MOVED:
279 if (UNLIKELY(!
RB_SPECIAL_CONST_P(curr_key) && rb_objspace_garbage_object_p(curr_key))) {
285 if (set->funcs->cmp(key, curr_key)) {
299 idx = concurrent_set_probe_next(&probe);
304rb_concurrent_set_find_or_insert(
VALUE *set_obj_ptr,
VALUE key,
void *data)
306 RUBY_ASSERT(key >= CONCURRENT_SET_SPECIAL_VALUE_COUNT);
310 VALUE result = rb_concurrent_set_find(set_obj_ptr, key);
311 if (result)
return result;
315 VALUE set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE);
319 key = set->funcs->create(key, data);
320 VALUE hash = concurrent_set_hash(set, key);
329 set_obj = rbimpl_atomic_value_load(set_obj_ptr, RBIMPL_ATOMIC_ACQUIRE);
331 set = RTYPEDDATA_GET_DATA(set_obj);
333 RUBY_ASSERT(hash == concurrent_set_hash(set, key));
336 idx = concurrent_set_probe_start(&probe, set, hash);
340 VALUE curr_hash_and_flags = rbimpl_atomic_value_load(&entry->hash, RBIMPL_ATOMIC_ACQUIRE);
341 VALUE curr_hash = curr_hash_and_flags & CONCURRENT_SET_HASH_MASK;
342 bool continuation = curr_hash_and_flags & CONCURRENT_SET_CONTINUATION_BIT;
344 if (curr_hash_and_flags == CONCURRENT_SET_EMPTY) {
346 curr_hash_and_flags = rbimpl_atomic_value_cas(&entry->hash, CONCURRENT_SET_EMPTY, hash, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
347 if (curr_hash_and_flags != CONCURRENT_SET_EMPTY) {
353 curr_hash_and_flags = hash;
359 if (curr_hash != hash) {
363 VALUE curr_key = rbimpl_atomic_value_load(&entry->key, RBIMPL_ATOMIC_ACQUIRE);
366 case CONCURRENT_SET_EMPTY: {
367 rb_atomic_t prev_size = rbimpl_atomic_fetch_add(&set->size, 1, RBIMPL_ATOMIC_RELAXED);
370 bool load_factor_reached = (uint64_t)(prev_size * 4) >= (uint64_t)(set->capacity * 3);
372 if (UNLIKELY(load_factor_reached)) {
373 concurrent_set_try_resize(set_obj, set_obj_ptr);
377 VALUE prev_key = rbimpl_atomic_value_cas(&entry->key, CONCURRENT_SET_EMPTY, key, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
378 if (prev_key == CONCURRENT_SET_EMPTY) {
379 RUBY_ASSERT(rb_concurrent_set_find(set_obj_ptr, key) == key);
385 rbimpl_atomic_sub(&set->size, 1, RBIMPL_ATOMIC_RELAXED);
391 case CONCURRENT_SET_DELETED:
393 case CONCURRENT_SET_MOVED:
402 if (UNLIKELY(!
RB_SPECIAL_CONST_P(curr_key) && rb_objspace_garbage_object_p(curr_key))) {
407 VALUE prev = rbimpl_atomic_value_cas(&entry->key, curr_key, CONCURRENT_SET_EMPTY, RBIMPL_ATOMIC_RELEASE, RBIMPL_ATOMIC_RELAXED);
408 if (prev == curr_key) {
409 rbimpl_atomic_sub(&set->size, 1, RBIMPL_ATOMIC_RELAXED);
415 if (set->funcs->cmp(key, curr_key)) {
422 if (set->funcs->free) set->funcs->free(key);
430 RUBY_ASSERT(curr_hash_and_flags != CONCURRENT_SET_EMPTY);
431 concurrent_set_mark_continuation(entry, curr_hash_and_flags);
432 idx = concurrent_set_probe_next(&probe);
439 ASSERT_vm_locking_with_barrier();
441 if (entry->hash & CONCURRENT_SET_CONTINUATION_BIT) {
442 entry->hash = CONCURRENT_SET_CONTINUATION_BIT;
443 entry->key = CONCURRENT_SET_DELETED;
444 set->deleted_entries++;
447 entry->hash = CONCURRENT_SET_EMPTY;
448 entry->key = CONCURRENT_SET_EMPTY;
454rb_concurrent_set_delete_by_identity(
VALUE set_obj,
VALUE key)
456 ASSERT_vm_locking_with_barrier();
460 VALUE hash = concurrent_set_hash(set, key);
463 int idx = concurrent_set_probe_start(&probe, set, hash);
467 VALUE curr_key = entry->key;
470 case CONCURRENT_SET_EMPTY:
473 case CONCURRENT_SET_DELETED:
475 case CONCURRENT_SET_MOVED:
476 rb_bug(
"rb_concurrent_set_delete_by_identity: moved entry");
479 if (key == curr_key) {
480 RUBY_ASSERT((entry->hash & CONCURRENT_SET_HASH_MASK) == hash);
481 concurrent_set_delete_entry_locked(set, entry);
487 idx = concurrent_set_probe_next(&probe);
492rb_concurrent_set_foreach_with_replace(
VALUE set_obj,
int (*callback)(
VALUE *key,
void *data),
void *data)
494 ASSERT_vm_locking_with_barrier();
498 for (
unsigned int i = 0; i < set->capacity; i++) {
500 VALUE key = entry->key;
503 case CONCURRENT_SET_EMPTY:
504 case CONCURRENT_SET_DELETED:
506 case CONCURRENT_SET_MOVED:
507 rb_bug(
"rb_concurrent_set_foreach_with_replace: moved entry");
510 int ret = callback(&entry->key, data);
515 concurrent_set_delete_entry_locked(set, entry);
#define RUBY_ASSERT(...)
Asserts that the given expression is truthy if and only if RUBY_DEBUG is truthy.
std::atomic< unsigned > rb_atomic_t
Type that is eligible for atomic operations.
#define RUBY_ATOMIC_LOAD(var)
Atomic load.
#define ZALLOC_N
Old name of RB_ZALLOC_N.
#define RB_GC_GUARD(v)
Prevents premature destruction of local objects.
#define TypedData_Make_Struct(klass, type, data_type, sval)
Identical to TypedData_Wrap_Struct, except it allocates a new data region internally instead of takin...
static bool RB_SPECIAL_CONST_P(VALUE obj)
Checks if the given object is of enum ruby_special_consts.
This is the struct that holds necessary info for a struct.
const char * wrap_struct_name
Name of structs of this kind.
uintptr_t VALUE
Type that represents a Ruby object.