diff --git a/src/check_atomics.cpp b/src/check_atomics.cpp index 3ed779f2c..1071ab836 100644 --- a/src/check_atomics.cpp +++ b/src/check_atomics.cpp @@ -4,8 +4,8 @@ // stored with relaxed ordering, orders nothing, and is warned about, unless the address of the location is taken // elsewhere, as it may then be accessed through it. Only loads and stores are what it would pair with, as a // read-modify-write asking for an ordering which nothing pairs with, e.g. to count, is harmless. -// A call to a procedure with `@(futex=.Wait)` is a relaxed load of what its first argument points to, as the OS only -// compares it, and one with `@(futex=.Wake)` accesses nothing; neither is taking its address elsewhere. +// A call to a procedure with `@(futex=.Wait)` is a relaxed load of what it waits on, as the OS only compares it, and +// one with `@(futex=.Wake)` accesses nothing; neither is taking its address elsewhere. // A relaxed read acquires when an acquire fence may be reached after it: later in its procedure, in the same loop, in // a `defer`, or in a procedure which calls its own, directly or not, before the fence. A relaxed write releases, // likewise, when a release fence may be reached before it. @@ -203,6 +203,38 @@ gb_internal void check_atomic_address_taken(AtomicScan *s, Ast *expr) { } } +gb_internal Ast *check_atomic_call_address(Ast *call) { + ast_node(ce, CallExpr, call); + Ast *arg = nullptr; + Entity *e = entity_of_node(ce->proc); + if (e != nullptr && e->kind == Entity_Procedure && e->Procedure.futex != ProcedureFutex_None) { + Type *pt = base_type(e->type); + isize index = e->Procedure.futex_parameter; + if (pt->kind != Type_Proc || pt->Proc.params == nullptr || index >= pt->Proc.params->Tuple.variables.count) { + return nullptr; + } + String name = pt->Proc.params->Tuple.variables[index]->token.string; + for (Ast *a : ce->args) { + if (a->kind != Ast_FieldValue) { + continue; + } + Ast *field = a->FieldValue.field; + if (field->kind == Ast_Ident && field->Ident.token.string == name) { + arg = a->FieldValue.value; + } + } + if (arg == nullptr && index < ce->args.count && ce->args[index]->kind != Ast_FieldValue) { + arg = ce->args[index]; + } + } else if (ce->args.count > 0) { + arg = ce->args[0]; + } + if (arg == nullptr) { + return nullptr; + } + return check_atomic_address_of(arg); +} + gb_internal void check_atomic_first(Ast **site, Ast *call) { if (*site == nullptr || token_pos_cmp(ast_token(call).pos, ast_token(*site).pos) < 0) { *site = call; @@ -729,7 +761,7 @@ gb_internal void check_atomics(Checker *c) { } if (a.id == BuiltinProc_volatile_load || a.id == BuiltinProc_volatile_store) { // NOTE(bill): the access is not atomic nor taking its address elsewhere - Ast *ptr = check_atomic_address_of(a.call->CallExpr.args[0]); + Ast *ptr = check_atomic_call_address(a.call); if (ptr == nullptr) { continue; } @@ -745,7 +777,7 @@ gb_internal void check_atomics(Checker *c) { continue; } Ast *call = a.call; - Ast *ptr = check_atomic_address_of(call->CallExpr.args[0]); + Ast *ptr = check_atomic_call_address(call); if (ptr == nullptr) { continue; } @@ -1003,14 +1035,14 @@ gb_internal void check_atomics(Checker *c) { continue; } if (r.kind == AtomicReport_Local) { - Entity *l = check_atomic_local(check_atomic_address_of(r.site->CallExpr.args[0])->UnaryExpr.expr); + Entity *l = check_atomic_local(check_atomic_call_address(r.site)->UnaryExpr.expr); warning(r.site, "The address of '%.*s' is only taken by atomic operations, so nothing else can access it, and they order nothing", LIT(l->token.string)); error_line("\tSuggestion: To keep its accesses from being optimized away, use 'volatile_load' and 'volatile_store', which order nothing between threads\n"); error_line("\t If it is meant to be shared, it may be a copy of what is\n"); continue; } - gbString str = expr_to_string(check_atomic_address_of(r.site->CallExpr.args[0])->UnaryExpr.expr); + gbString str = expr_to_string(check_atomic_call_address(r.site)->UnaryExpr.expr); char const *other = token_pos_to_string(ast_token(r.other).pos); AtomicProcedure *p = nullptr; diff --git a/src/check_decl.cpp b/src/check_decl.cpp index 8e3b43115..5d07038ea 100644 --- a/src/check_decl.cpp +++ b/src/check_decl.cpp @@ -1552,14 +1552,25 @@ gb_internal void check_proc_decl(CheckerContext *ctx, Entity *e, DeclInfo *d) { e->Procedure.fast_math_flags = ac.fast_math_flags; e->Procedure.futex = cast(ProcedureFutex)ac.futex; + if (ac.futex == ProcedureFutex_None && ac.futex_parameter.len != 0) { + error(e->token, "@(futex_parameter) can only be used with @(futex)"); + } if (ac.futex != ProcedureFutex_None) { - // what it waits on or wakes, which may only be known for each instantiation - Type *word = nullptr; + Entity *param = nullptr; if (pt->param_count > 0) { - word = pt->params->Tuple.variables[0]->type; + for_array(i, pt->params->Tuple.variables) { + Entity *v = pt->params->Tuple.variables[i]; + if (ac.futex_parameter.len == 0 || v->token.string == ac.futex_parameter) { + param = v; + e->Procedure.futex_parameter = cast(i32)i; + break; + } + } } - if (word == nullptr || !is_type_pointer(word) || !(is_type_integer(type_deref(word)) || is_type_polymorphic(type_deref(word)))) { - error(e->token, "A procedure with @(futex) must take a pointer to an integer as its first parameter, which is what it waits on or wakes"); + if (param == nullptr && ac.futex_parameter.len != 0) { + error(e->token, "@(futex_parameter) names '%.*s', which is not a parameter of '%.*s'", LIT(ac.futex_parameter), LIT(e->token.string)); + } else if (param == nullptr || !is_type_pointer(param->type) || !(is_type_integer(type_deref(param->type)) || is_type_polymorphic(type_deref(param->type)))) { + error(e->token, "A procedure with @(futex) must take a pointer to an integer as its first parameter, or the one @(futex_parameter) names, which is what it waits on or wakes"); } } diff --git a/src/check_expr.cpp b/src/check_expr.cpp index 3a378b6b5..a6d5533ee 100644 --- a/src/check_expr.cpp +++ b/src/check_expr.cpp @@ -839,6 +839,7 @@ gb_internal bool find_or_generate_polymorphic_procedure(CheckerContext *old_c, E entity->Procedure.optimization_mode = base_entity->Procedure.optimization_mode; entity->Procedure.futex = base_entity->Procedure.futex; + entity->Procedure.futex_parameter = base_entity->Procedure.futex_parameter; entity->Procedure.generated_from_polymorphic = true; if (base_entity->flags & EntityFlag_Cold) { diff --git a/src/checker.cpp b/src/checker.cpp index 9bfe088b2..94f8c79ab 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -4639,6 +4639,14 @@ gb_internal DECL_ATTRIBUTE_PROC(proc_decl_attribute) { case OdinFutexOperation_Wake: ac->futex = ProcedureFutex_Wake; break; } return true; + } else if (name == "futex_parameter") { + ExactValue ev = check_decl_attribute_value(c, value); + if (ev.kind != ExactValue_String || ev.value_string.len == 0) { + error(elem, "Expected the name of a parameter for '%.*s'", LIT(name)); + } else { + ac->futex_parameter = ev.value_string; + } + return true; } return false; } diff --git a/src/checker.hpp b/src/checker.hpp index f05729278..472adc697 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -167,7 +167,8 @@ struct AttributeContext { u64 fast_math_flags; - u8 futex; // ProcedureFutex + u8 futex; // ProcedureFutex + String futex_parameter; bool raddbg_type_view; String raddbg_type_view_string; diff --git a/src/entity.cpp b/src/entity.cpp index a82ceab71..431842443 100644 --- a/src/entity.cpp +++ b/src/entity.cpp @@ -316,6 +316,7 @@ struct Entity { BlockingMutex gen_procs_mutex; ProcedureOptimizationMode optimization_mode; ProcedureFutex futex; + i32 futex_parameter; // the index of what `futex` waits on or wakes u64 fast_math_flags;