diff --git a/src/check_builtin.cpp b/src/check_builtin.cpp index 60a094408..7f5e759a6 100644 --- a/src/check_builtin.cpp +++ b/src/check_builtin.cpp @@ -7004,6 +7004,9 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As variants[i] = alloc_type_pointer(bt->Union.variants[i]); } new_type->Union.variants = variants; + // This union is built directly (not via check_union_type), so signal that its variants + // are ready or a wait_signal_until_available on it would block forever. + wait_signal_set(&new_type->Union.variants_wait_signal); // NOTE(bill): Is this even correct? new_type->Union.node = operand->expr; @@ -7181,6 +7184,8 @@ gb_internal bool check_builtin_procedure(CheckerContext *c, Operand *operand, As } merged_union->Union.variants = slice_from_array(variants); + // Built directly (not via check_union_type); signal that variants are ready. + wait_signal_set(&merged_union->Union.variants_wait_signal); operand->mode = Addressing_Type; operand->type = merged_union; diff --git a/src/check_expr.cpp b/src/check_expr.cpp index e62e1e689..c913bc38b 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -95,7 +95,8 @@ gb_internal void check_struct_type (CheckerContext *c, Type *st Type *named_type = nullptr, Type *original_type_for_poly = nullptr, GenTypesData *poly_gen_types_to_unlock = nullptr); gb_internal void check_union_type (CheckerContext *c, Type *union_type, Ast *node, Array *poly_operands, - Type *named_type = nullptr, Type *original_type_for_poly = nullptr); + Type *named_type = nullptr, Type *original_type_for_poly = nullptr, + GenTypesData *poly_gen_types_to_unlock = nullptr); gb_internal Type * check_init_variable (CheckerContext *c, Entity *e, Operand *operand, String context_name); @@ -8814,7 +8815,9 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O set_base_type(named_type, union_type); check_open_scope(&ctx, node); - check_union_type(&ctx, union_type, node, &ordered_operands, named_type, original_type); + check_union_type(&ctx, union_type, node, &ordered_operands, named_type, original_type, found_gen_types); + // check_union_type released found_gen_types->mutex after publishing the instantiation. + gen_types_locked = false; check_close_scope(&ctx); } else { GB_PANIC("Unsupported parametric polymorphic record type"); diff --git a/src/check_type.cpp b/src/check_type.cpp index 4ef8e2f77..ea7c7ed27 100644 --- a/src/check_type.cpp +++ b/src/check_type.cpp @@ -834,7 +834,7 @@ gb_internal void check_struct_type(CheckerContext *ctx, Type *struct_type, Ast * #undef ST_ALIGN } -gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array *poly_operands, Type *named_type, Type *original_type_for_poly) { +gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *node, Array *poly_operands, Type *named_type, Type *original_type_for_poly, GenTypesData *poly_gen_types_to_unlock) { GB_ASSERT(is_type_union(union_type)); ast_node(ut, UnionType, node); @@ -854,6 +854,16 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no // Finalize the name before publishing (see set_polymorphic_record_instantiation_name). set_polymorphic_record_instantiation_name(named_type, original_type_for_poly); add_polymorphic_record_entity(ctx, node, named_type, original_type_for_poly); + + // NOTE(bill): Release the originating record's gen_types mutex now that this instantiation + // is published, before checking its variants (which can instantiate other polymorphic + // records). Holding it across variant checking is what allows a cross-record ABBA deadlock + // between mutually-recursive generic unions instantiated concurrently. Concurrent requesters + // that find this in-progress entity synchronize on variants_wait_signal (set at the end of + // this function) before reading its variants. Mirrors check_struct_type. + if (poly_gen_types_to_unlock != nullptr) { + mutex_unlock(&poly_gen_types_to_unlock->mutex); + } } if (!union_type->Union.is_polymorphic) { @@ -951,6 +961,12 @@ gb_internal void check_union_type(CheckerContext *ctx, Type *union_type, Ast *no } } } + + // NOTE(bill): `variants` is now fully populated; wake any thread that found this (possibly + // in-progress, early-released) instantiation and is waiting to read its variants. Mirrors the + // struct fields_wait_signal. Set unconditionally: an unspecialized polymorphic template has no + // variants and is never published, so no one waits on it, but setting it is harmless. + wait_signal_set(&union_type->Union.variants_wait_signal); } gb_internal void check_enum_type(CheckerContext *ctx, Type *enum_type, Type *named_type, Ast *node) { diff --git a/src/types.cpp b/src/types.cpp index 2d43a07f5..8ebd60b86 100644 --- a/src/types.cpp +++ b/src/types.cpp @@ -180,6 +180,7 @@ struct TypeUnion { Type * polymorphic_params; // Type_Tuple Type * polymorphic_parent; Wait_Signal polymorphic_wait_signal; + Wait_Signal variants_wait_signal; // signalled once `variants` is populated (mirrors TypeStruct.fields_wait_signal) std::atomic tag_size; bool is_polymorphic;