diff --git a/src/check_decl.cpp b/src/check_decl.cpp index a82215815..c7f4ce761 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -901,9 +901,7 @@ void check_global_variable_decl(CheckerContext *ctx, Entity *&e, Ast *type_expr, } if (ac.require_declaration) { - gb_mutex_lock(&ctx->info->entity_mutex); - array_add(&ctx->info->required_global_variables, e); - gb_mutex_unlock(&ctx->info->entity_mutex); + mpmc_enqueue(&ctx->info->required_global_variable_queue, e); } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index beff1a532..70d8a891f 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -225,15 +225,13 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti return false; } - - gb_mutex_lock(&info->gen_procs_mutex); - defer (gb_mutex_unlock(&info->gen_procs_mutex)); - String name = base_entity->token.string; Type *src = base_type(base_entity->type); Type *dst = nullptr; - if (type != nullptr) dst = base_type(type); + if (type != nullptr) { + dst = base_type(type); + } if (param_operands == nullptr) { GB_ASSERT(dst != nullptr); @@ -242,6 +240,8 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti GB_ASSERT(dst == nullptr); } + gb_mutex_lock(&info->gen_procs_mutex); + defer (gb_mutex_unlock(&info->gen_procs_mutex)); if (!src->Proc.is_polymorphic || src->Proc.is_poly_specialized) { return false; @@ -314,9 +314,6 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti auto *found_gen_procs = map_get(&info->gen_procs, hash_pointer(base_entity->identifier)); if (found_gen_procs) { - // gb_mutex_lock(&info->gen_procs_mutex); - // defer (gb_mutex_unlock(&info->gen_procs_mutex)); - auto procs = *found_gen_procs; for_array(i, procs) { Entity *other = procs[i]; @@ -353,9 +350,6 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti } if (found_gen_procs) { - // gb_mutex_lock(&info->gen_procs_mutex); - // defer (gb_mutex_unlock(&info->gen_procs_mutex)); - auto procs = *found_gen_procs; for_array(i, procs) { Entity *other = procs[i]; @@ -425,7 +419,6 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti proc_info->generated_from_polymorphic = true; proc_info->poly_def_node = poly_def_node; - // gb_mutex_lock(&info->gen_procs_mutex); if (found_gen_procs) { array_add(found_gen_procs, entity); } else { @@ -433,7 +426,6 @@ bool find_or_generate_polymorphic_procedure(CheckerContext *c, Entity *base_enti array_add(&array, entity); map_set(&info->gen_procs, hash_pointer(base_entity->identifier), array); } - // gb_mutex_unlock(&info->gen_procs_mutex); GB_ASSERT(entity != nullptr); diff --git a/src/checker.cpp b/src/checker.cpp index 82bc544d0..c71a75503 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -233,6 +233,7 @@ Scope *create_scope(CheckerInfo *info, Scope *parent, isize init_elements_capaci s->delayed_directives.allocator = heap_allocator(); if (parent != nullptr && parent != builtin_pkg->scope) { + // TODO(bill): make this an atomic operation if (info) gb_mutex_lock(&info->scope_mutex); DLIST_APPEND(parent->first_child, parent->last_child, s); if (info) gb_mutex_unlock(&info->scope_mutex); @@ -844,9 +845,7 @@ void init_checker_info(CheckerInfo *i) { string_map_init(&i->packages, a); array_init(&i->variable_init_order, a); array_init(&i->required_foreign_imports_through_force, a); - array_init(&i->required_global_variables, a); array_init(&i->testing_procedures, a, 0, 0); - mpmc_init(&i->untyped_queue, heap_allocator(), 1<<20); i->allow_identifier_uses = build_context.query_data_set_settings.kind == QueryDataSet_GoToDefinitions; @@ -854,12 +853,15 @@ void init_checker_info(CheckerInfo *i) { array_init(&i->identifier_uses, a); } + mpmc_init(&i->entity_queue, a, 1<<20); + mpmc_init(&i->definition_queue, a, 1<<20); + mpmc_init(&i->required_global_variable_queue, a, 1<<10); + gb_mutex_init(&i->gen_procs_mutex); gb_mutex_init(&i->gen_types_mutex); gb_mutex_init(&i->type_info_mutex); gb_mutex_init(&i->deps_mutex); gb_mutex_init(&i->identifier_uses_mutex); - gb_mutex_init(&i->entity_mutex); gb_mutex_init(&i->foreign_mutex); gb_mutex_init(&i->scope_mutex); @@ -879,15 +881,16 @@ void destroy_checker_info(CheckerInfo *i) { array_free(&i->variable_init_order); array_free(&i->identifier_uses); array_free(&i->required_foreign_imports_through_force); - array_free(&i->required_global_variables); - mpmc_destroy(&i->untyped_queue); + + mpmc_destroy(&i->entity_queue); + mpmc_destroy(&i->definition_queue); + mpmc_destroy(&i->required_global_variable_queue); gb_mutex_destroy(&i->gen_procs_mutex); gb_mutex_destroy(&i->gen_types_mutex); gb_mutex_destroy(&i->type_info_mutex); gb_mutex_destroy(&i->deps_mutex); gb_mutex_destroy(&i->identifier_uses_mutex); - gb_mutex_destroy(&i->entity_mutex); gb_mutex_destroy(&i->foreign_mutex); gb_mutex_destroy(&i->scope_mutex); } @@ -958,6 +961,8 @@ bool init_checker(Checker *c, Parser *parser) { mpmc_init(&c->procs_to_check_queue, heap_allocator(), 1<<20); gb_semaphore_init(&c->procs_to_check_semaphore); + mpmc_init(&c->global_untyped_queue, a, 1<<20); + gb_mutex_init(&c->poly_type_mutex); gb_mutex_init(&c->poly_proc_mutex); return true; @@ -970,6 +975,8 @@ void destroy_checker(Checker *c) { mpmc_destroy(&c->procs_to_check_queue); gb_semaphore_destroy(&c->procs_to_check_semaphore); + + mpmc_destroy(&c->global_untyped_queue); } @@ -1165,9 +1172,7 @@ void add_entity_definition(CheckerInfo *i, Ast *identifier, Entity *entity) { GB_ASSERT(entity != nullptr); identifier->Ident.entity = entity; entity->identifier = identifier; - gb_mutex_lock(&i->entity_mutex); - array_add(&i->definitions, entity); - gb_mutex_unlock(&i->entity_mutex); + mpmc_enqueue(&i->definition_queue, entity); } bool redeclaration_error(String name, Entity *prev, Entity *found) { @@ -1290,13 +1295,12 @@ void add_entity_and_decl_info(CheckerContext *c, Ast *identifier, Entity *e, Dec CheckerInfo *info = c->info; add_entity_definition(info, identifier, e); GB_ASSERT(e->decl_info == nullptr); - gb_mutex_lock(&info->entity_mutex); e->decl_info = d; d->entity = e; - array_add(&info->entities, e); - e->order_in_src = info->entities.count; // Is this even correct? e->pkg = c->pkg; - gb_mutex_unlock(&info->entity_mutex); + + // Is this even correct? + e->order_in_src = 1+mpmc_enqueue(&info->entity_queue, e); } @@ -1307,11 +1311,7 @@ void add_implicit_entity(CheckerContext *c, Ast *clause, Entity *e) { clause->CaseClause.implicit_entity = e; } - - - - -void add_type_info_type(CheckerContext *c, Type *t) { +void add_type_info_type_internal(CheckerContext *c, Type *t) { if (t == nullptr) { return; } @@ -1363,12 +1363,12 @@ void add_type_info_type(CheckerContext *c, Type *t) { if (t->kind == Type_Named) { // NOTE(bill): Just in case - add_type_info_type(c, t->Named.base); + add_type_info_type_internal(c, t->Named.base); return; } Type *bt = base_type(t); - add_type_info_type(c, bt); + add_type_info_type_internal(c, bt); switch (bt->kind) { case Type_Invalid: @@ -1376,84 +1376,84 @@ void add_type_info_type(CheckerContext *c, Type *t) { case Type_Basic: switch (bt->Basic.kind) { case Basic_cstring: - add_type_info_type(c, t_u8_ptr); + add_type_info_type_internal(c, t_u8_ptr); break; case Basic_string: - add_type_info_type(c, t_u8_ptr); - add_type_info_type(c, t_int); + add_type_info_type_internal(c, t_u8_ptr); + add_type_info_type_internal(c, t_int); break; case Basic_any: - add_type_info_type(c, t_type_info_ptr); - add_type_info_type(c, t_rawptr); + add_type_info_type_internal(c, t_type_info_ptr); + add_type_info_type_internal(c, t_rawptr); break; case Basic_typeid: break; case Basic_complex64: - add_type_info_type(c, t_type_info_float); - add_type_info_type(c, t_f32); + add_type_info_type_internal(c, t_type_info_float); + add_type_info_type_internal(c, t_f32); break; case Basic_complex128: - add_type_info_type(c, t_type_info_float); - add_type_info_type(c, t_f64); + add_type_info_type_internal(c, t_type_info_float); + add_type_info_type_internal(c, t_f64); break; case Basic_quaternion128: - add_type_info_type(c, t_type_info_float); - add_type_info_type(c, t_f32); + add_type_info_type_internal(c, t_type_info_float); + add_type_info_type_internal(c, t_f32); break; case Basic_quaternion256: - add_type_info_type(c, t_type_info_float); - add_type_info_type(c, t_f64); + add_type_info_type_internal(c, t_type_info_float); + add_type_info_type_internal(c, t_f64); break; } break; case Type_BitSet: - add_type_info_type(c, bt->BitSet.elem); - add_type_info_type(c, bt->BitSet.underlying); + add_type_info_type_internal(c, bt->BitSet.elem); + add_type_info_type_internal(c, bt->BitSet.underlying); break; case Type_Pointer: - add_type_info_type(c, bt->Pointer.elem); + add_type_info_type_internal(c, bt->Pointer.elem); break; case Type_Array: - add_type_info_type(c, bt->Array.elem); - add_type_info_type(c, alloc_type_pointer(bt->Array.elem)); - add_type_info_type(c, t_int); + add_type_info_type_internal(c, bt->Array.elem); + add_type_info_type_internal(c, alloc_type_pointer(bt->Array.elem)); + add_type_info_type_internal(c, t_int); break; case Type_EnumeratedArray: - add_type_info_type(c, bt->EnumeratedArray.index); - add_type_info_type(c, t_int); - add_type_info_type(c, bt->EnumeratedArray.elem); - add_type_info_type(c, alloc_type_pointer(bt->EnumeratedArray.elem)); + add_type_info_type_internal(c, bt->EnumeratedArray.index); + add_type_info_type_internal(c, t_int); + add_type_info_type_internal(c, bt->EnumeratedArray.elem); + add_type_info_type_internal(c, alloc_type_pointer(bt->EnumeratedArray.elem)); break; case Type_DynamicArray: - add_type_info_type(c, bt->DynamicArray.elem); - add_type_info_type(c, alloc_type_pointer(bt->DynamicArray.elem)); - add_type_info_type(c, t_int); - add_type_info_type(c, t_allocator); + add_type_info_type_internal(c, bt->DynamicArray.elem); + add_type_info_type_internal(c, alloc_type_pointer(bt->DynamicArray.elem)); + add_type_info_type_internal(c, t_int); + add_type_info_type_internal(c, t_allocator); break; case Type_Slice: - add_type_info_type(c, bt->Slice.elem); - add_type_info_type(c, alloc_type_pointer(bt->Slice.elem)); - add_type_info_type(c, t_int); + add_type_info_type_internal(c, bt->Slice.elem); + add_type_info_type_internal(c, alloc_type_pointer(bt->Slice.elem)); + add_type_info_type_internal(c, t_int); break; case Type_Enum: - add_type_info_type(c, bt->Enum.base_type); + add_type_info_type_internal(c, bt->Enum.base_type); break; case Type_Union: if (union_tag_size(t) > 0) { - add_type_info_type(c, union_tag_type(t)); + add_type_info_type_internal(c, union_tag_type(t)); } else { - add_type_info_type(c, t_type_info_ptr); + add_type_info_type_internal(c, t_type_info_ptr); } for_array(i, bt->Union.variants) { - add_type_info_type(c, bt->Union.variants[i]); + add_type_info_type_internal(c, bt->Union.variants[i]); } break; @@ -1463,56 +1463,56 @@ void add_type_info_type(CheckerContext *c, Type *t) { Entity *e = bt->Struct.scope->elements.entries[i].value; switch (bt->Struct.soa_kind) { case StructSoa_Dynamic: - add_type_info_type(c, t_allocator); + add_type_info_type_internal(c, t_allocator); /*fallthrough*/ case StructSoa_Slice: case StructSoa_Fixed: - add_type_info_type(c, alloc_type_pointer(e->type)); + add_type_info_type_internal(c, alloc_type_pointer(e->type)); break; default: - add_type_info_type(c, e->type); + add_type_info_type_internal(c, e->type); break; } } } for_array(i, bt->Struct.fields) { Entity *f = bt->Struct.fields[i]; - add_type_info_type(c, f->type); + add_type_info_type_internal(c, f->type); } add_comparison_procedures_for_fields(c, bt); break; case Type_Map: init_map_internal_types(bt); - add_type_info_type(c, bt->Map.key); - add_type_info_type(c, bt->Map.value); - add_type_info_type(c, bt->Map.generated_struct_type); + add_type_info_type_internal(c, bt->Map.key); + add_type_info_type_internal(c, bt->Map.value); + add_type_info_type_internal(c, bt->Map.generated_struct_type); break; case Type_Tuple: for_array(i, bt->Tuple.variables) { Entity *var = bt->Tuple.variables[i]; - add_type_info_type(c, var->type); + add_type_info_type_internal(c, var->type); } break; case Type_Proc: - add_type_info_type(c, bt->Proc.params); - add_type_info_type(c, bt->Proc.results); + add_type_info_type_internal(c, bt->Proc.params); + add_type_info_type_internal(c, bt->Proc.results); break; case Type_SimdVector: - add_type_info_type(c, bt->SimdVector.elem); + add_type_info_type_internal(c, bt->SimdVector.elem); break; case Type_RelativePointer: - add_type_info_type(c, bt->RelativePointer.pointer_type); - add_type_info_type(c, bt->RelativePointer.base_integer); + add_type_info_type_internal(c, bt->RelativePointer.pointer_type); + add_type_info_type_internal(c, bt->RelativePointer.base_integer); break; case Type_RelativeSlice: - add_type_info_type(c, bt->RelativeSlice.slice_type); - add_type_info_type(c, bt->RelativeSlice.base_integer); + add_type_info_type_internal(c, bt->RelativeSlice.slice_type); + add_type_info_type_internal(c, bt->RelativeSlice.base_integer); break; default: @@ -1521,6 +1521,13 @@ void add_type_info_type(CheckerContext *c, Type *t) { } } +void add_type_info_type(CheckerContext *c, Type *t) { + gb_mutex_lock(&c->info->type_info_mutex); + add_type_info_type_internal(c, t); + gb_mutex_unlock(&c->info->type_info_mutex); +} + + gb_global bool global_procedure_body_in_worker_queue = false; void check_procedure_later(CheckerContext *c, ProcInfo *info) { @@ -1887,8 +1894,7 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) { add_dependency_to_set(c, e); } - for_array(i, c->info.required_global_variables) { - Entity *e = c->info.required_global_variables[i]; + for (Entity *e; mpmc_dequeue(&c->info.required_global_variable_queue, &e); /**/) { e->flags |= EntityFlag_Used; add_dependency_to_set(c, e); } @@ -4532,11 +4538,6 @@ void check_procedure_bodies(Checker *c) { gbThread dummy_main_thread = {}; dummy_main_thread.user_data = thread_data+worker_count; thread_proc_body(&dummy_main_thread); - gb_semaphore_release(&c->procs_to_check_semaphore); - - for (isize i = 0; i < worker_count; i++) { - gb_thread_join(threads+i); - } gb_semaphore_wait(&c->procs_to_check_semaphore); @@ -4544,10 +4545,8 @@ void check_procedure_bodies(Checker *c) { gb_thread_destroy(threads+i); } - { - isize remaining = c->procs_to_check_queue.count.load(std::memory_order_relaxed); - GB_ASSERT(remaining == 0); - } + isize global_remaining = c->procs_to_check_queue.count.load(std::memory_order_relaxed); + GB_ASSERT(global_remaining == 0); debugf("Total Procedure Bodies Checked: %td\n", total_bodies_checked.load(std::memory_order_relaxed)); @@ -4561,7 +4560,7 @@ void add_untyped_expressions(CheckerInfo *cinfo, UntypedExprInfoMap *untyped) { Ast *expr = cast(Ast *)cast(uintptr)untyped->entries[i].key.key; ExprInfo *info = untyped->entries[i].value; if (expr != nullptr && info != nullptr) { - mpmc_enqueue(&cinfo->untyped_queue, UntypedExprInfo{expr, info}); + mpmc_enqueue(&cinfo->checker->global_untyped_queue, UntypedExprInfo{expr, info}); } } map_clear(untyped); @@ -4747,6 +4746,23 @@ void check_unique_package_names(Checker *c) { } } +void check_add_entities_from_queues(Checker *c) { + { + isize cap = c->info.entities.count + c->info.entity_queue.count.load(std::memory_order_relaxed); + array_reserve(&c->info.entities, cap); + for (Entity *e; mpmc_dequeue(&c->info.entity_queue, &e); /**/) { + array_add(&c->info.entities, e); + } + } + { + isize cap = c->info.definitions.count + c->info.definition_queue.count.load(std::memory_order_relaxed); + array_reserve(&c->info.definitions, cap); + for (Entity *e; mpmc_dequeue(&c->info.definition_queue, &e); /**/) { + array_add(&c->info.definitions, e); + } + } +} + void check_parsed_files(Checker *c) { #define TIME_SECTION(str) do { debugf("[Section] %s\n", str); if (build_context.show_more_timings) timings_start_section(&global_timings, str_lit(str)); } while (0) @@ -4795,6 +4811,9 @@ void check_parsed_files(Checker *c) { TIME_SECTION("import entities"); check_import_entities(c); + TIME_SECTION("add entities from packages"); + check_add_entities_from_queues(c); + TIME_SECTION("check all global entities"); check_all_global_entities(c); @@ -4811,6 +4830,9 @@ void check_parsed_files(Checker *c) { TIME_SECTION("check procedure bodies"); check_procedure_bodies(c); + TIME_SECTION("add entities from procedure bodiess"); + check_add_entities_from_queues(c); + TIME_SECTION("check scope usage"); for_array(i, c->info.files.entries) { AstFile *f = c->info.files.entries[i].value; @@ -4832,7 +4854,7 @@ void check_parsed_files(Checker *c) { TIME_SECTION("add untyped expression values"); // Add untyped expression values - for (UntypedExprInfo u = {}; mpmc_dequeue(&c->info.untyped_queue, &u); /**/) { + for (UntypedExprInfo u = {}; mpmc_dequeue(&c->global_untyped_queue, &u); /**/) { GB_ASSERT(u.expr != nullptr && u.info != nullptr); if (is_type_typed(u.info->type)) { compiler_error("%s (type %s) is typed!", expr_to_string(u.expr), type_to_string(u.info->type)); diff --git a/src/checker.hpp b/src/checker.hpp index c531020a8..2fe7e8542 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -288,18 +288,20 @@ struct CheckerInfo { Array testing_procedures; + Array definitions; + Array entities; + + // Below are accessed within procedures // NOTE(bill): If the semantic checker (check_proc_body) is to ever to be multithreaded, // these variables will be of contention - gbMutex gen_procs_mutex; - gbMutex gen_types_mutex; - gbMutex type_info_mutex; - gbMutex deps_mutex; - gbMutex identifier_uses_mutex; - gbMutex entity_mutex; - gbMutex foreign_mutex; - gbMutex scope_mutex; + gbMutex gen_procs_mutex; // Possibly recursive + gbMutex gen_types_mutex; // Possibly recursive + gbMutex type_info_mutex; // NOT recursive + gbMutex deps_mutex; // NOT recursive & Only used in `check_proc_body` + gbMutex foreign_mutex; // NOT recursive + gbMutex scope_mutex; // NOT recursive & Only used in `create_scope` Map > gen_procs; // Key: Ast * | Identifier -> Entity Map > gen_types; // Key: Type * @@ -307,17 +309,20 @@ struct CheckerInfo { Array type_info_types; Map type_info_map; // Key: Type * - bool allow_identifier_uses; - Array identifier_uses; // only used by 'odin query' + StringMap foreigns; + Array required_foreign_imports_through_force; - Array definitions; - Array entities; - StringMap foreigns; + // only used by 'odin query' + bool allow_identifier_uses; + gbMutex identifier_uses_mutex; + Array identifier_uses; - Array required_global_variables; - Array required_foreign_imports_through_force; + // NOTE(bill): These are actually MPSC queues + // TODO(bill): Convert them to be MPSC queues + MPMCQueue definition_queue; + MPMCQueue entity_queue; + MPMCQueue required_global_variable_queue; - MPMCQueue untyped_queue; }; struct CheckerContext { @@ -363,6 +368,7 @@ struct CheckerContext { ProcBodyQueue *procs_to_check_queue; }; + struct Checker { Parser * parser; CheckerInfo info; @@ -373,6 +379,7 @@ struct Checker { ProcBodyQueue procs_to_check_queue; gbSemaphore procs_to_check_semaphore; + MPMCQueue global_untyped_queue; gbMutex poly_type_mutex; gbMutex poly_proc_mutex; diff --git a/src/queue.cpp b/src/queue.cpp index 92d83a76c..33ba5af28 100644 --- a/src/queue.cpp +++ b/src/queue.cpp @@ -46,7 +46,7 @@ void mpmc_destroy(MPMCQueue *q) { template -bool mpmc_enqueue(MPMCQueue *q, T const &data) { +isize mpmc_enqueue(MPMCQueue *q, T const &data) { isize head_idx = q->head_idx.load(std::memory_order_relaxed); for (;;) { @@ -59,8 +59,7 @@ bool mpmc_enqueue(MPMCQueue *q, T const &data) { if (q->head_idx.compare_exchange_weak(head_idx, next_head_idx)) { node->data = data; node->idx.store(next_head_idx, std::memory_order_release); - q->count.fetch_add(1, std::memory_order_release); - return true; + return q->count.fetch_add(1, std::memory_order_release); } } else if (diff < 0) { gb_mutex_lock(&q->mutex); @@ -70,7 +69,7 @@ bool mpmc_enqueue(MPMCQueue *q, T const &data) { if (q->buffer.data == nullptr) { GB_PANIC("Unable to resize enqueue: %td -> %td", old_size, new_size); gb_mutex_unlock(&q->mutex); - return false; + return -1; } for (isize i = old_size; i < new_size; i++) { q->buffer.data[i].idx.store(i, std::memory_order_relaxed); @@ -108,3 +107,71 @@ bool mpmc_dequeue(MPMCQueue *q, T *data_) { } } } + + +template +struct MPSCQueueNode { + std::atomic *> next; + T data; +}; + +template +struct MPSCQueue { + gbAllocator allocator; + + std::atomic count; + std::atomic *> head; + std::atomic *> tail; +}; + +template +void mpsc_init(MPSCQueue *q, gbAllocator a) { + using Node = MPSCQueueNode; + + q->allocator = a; + Node *front = cast(Node *)gb_alloc_align(q->allocator, gb_size_of(Node), 64); + front->next.store(nullptr, std::memory_order_relaxed); + q->head.store(front, std::memory_order_relaxed); + q->tail.store(front, std::memory_order_relaxed); +} + + +template +isize mpsc_enqueue(MPSCQueue *q, T const &value) { + using Node = MPSCQueueNode; + + Node *node = cast(Node *)gb_alloc_align(q->allocator, gb_size_of(Node), 64); + node->data = value; + node->next.store(nullptr, std::memory_order_relaxed); + + auto *prev_head = q->head.exchange(node, std::memory_order_acq_rel); + prev_head->next.store(node, std::memory_order_release); + return q->count.fetch_add(1, std::memory_order_release); +} + +template +bool mpsc_dequeue(MPSCQueue *q, T *value_) { + auto *tail = q->tail.load(std::memory_order_relaxed); + auto *next = tail->next.load(std::memory_order_acquire); + if (next == nullptr) { + return false; + } + + if (value_) *value_ = next->data; + q->tail.store(next, std::memory_order_release); + q->count.fetch_sub(1, std::memory_order_release); + gb_free(q->allocator, tail); + return true; +} + + +template +void mpsc_destroy(MPSCQueue *q) { + T output = {}; + while (mpsc_dequeue(q, &output)) { + // okay + } + auto *front = q->head.load(std::memory_order_relaxed); + gb_free(q->allocator, front); +} +