Index: src/Concurrency/Keywords.cpp
===================================================================
--- src/Concurrency/Keywords.cpp	(revision 7d29089ce266e027efd90b5274730df2b9fbb059)
+++ src/Concurrency/Keywords.cpp	(revision a30fceb1a73c4ef2bbee39a2b5406da881f51111)
@@ -29,4 +29,5 @@
 #include "Common/Examine.hpp"
 #include "Common/Utility.hpp"
+#include "Concurrency/MutexFuncHash.hpp"
 #include "Common/UniqueName.hpp"
 #include "ControlStruct/LabelGenerator.hpp"
@@ -1077,5 +1078,5 @@
 
 	// In reverse order:
-	// monitor_dtor_guard_t __guard = { __monitor, func, false };
+	// monitor_dtor_guard_t __guard = { __monitor, func, func_id, false };
 	mutBody->push_front(
 		new ast::DeclStmt( location, new ast::ObjectDecl(
@@ -1094,4 +1095,6 @@
 							generic_func,
 							ast::ExplicitCast ) ),
+					new ast::SingleInit( location,
+						Concurrency::hashMangleExpr( location, func ) ),
 					new ast::SingleInit( location,
 						ast::ConstantExpr::from_bool( location, false ) ),
@@ -1175,4 +1178,6 @@
 						ast::ExplicitCast
 					) ),
+					new ast::SingleInit( location,
+						Concurrency::hashMangleExpr( location, func ) ),
 				},
 				{},
@@ -1475,5 +1480,5 @@
 
 ast::CompoundStmt * MutexKeyword::addThreadDtorStatements(
-		const ast::FunctionDecl*, const ast::CompoundStmt * body,
+		const ast::FunctionDecl* func, const ast::CompoundStmt * body,
 		const std::vector<const ast::DeclWithType * > & args ) {
 	assert( args.size() == 1 );
@@ -1487,5 +1492,5 @@
 	const CodeLocation & location = mutBody->location;
 
-	// thread_dtor_guard_t __guard = { this, intptr( 0 ) };
+	// thread_dtor_guard_t __guard = { this, func_id, intptr( 0 ) };
 	mutBody->push_front( new ast::DeclStmt(
 		location,
@@ -1500,4 +1505,6 @@
 						new ast::CastExpr( location,
 							new ast::VariableExpr( location, arg ), argType ) ),
+					new ast::SingleInit( location,
+						Concurrency::hashMangleExpr( location, func ) ),
 					new ast::SingleInit(
 						location,
Index: src/Concurrency/MutexFuncHash.hpp
===================================================================
--- src/Concurrency/MutexFuncHash.hpp	(revision a30fceb1a73c4ef2bbee39a2b5406da881f51111)
+++ src/Concurrency/MutexFuncHash.hpp	(revision a30fceb1a73c4ef2bbee39a2b5406da881f51111)
@@ -0,0 +1,50 @@
+//
+// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
+//
+// The contents of this file are covered under the licence agreement in the
+// file "LICENCE" distributed with Cforall.
+//
+// MutexFuncHash.hpp -- Hash utility for mutex function identity.
+//
+// Author           : Matthew Au-Yeung
+// Created On       : Tue Jan 28 2026
+//
+
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+#include "AST/Decl.hpp"
+#include "AST/Expr.hpp"
+#include "AST/Type.hpp"
+#include "SymTab/Mangler.hpp"
+
+namespace Concurrency {
+
+// FNV-1a hash of a function declaration's mangled name.
+// Used to identify mutex functions across translation units,
+// since function pointers may differ for static inline functions.
+static inline uint64_t hashMangle( const ast::DeclWithType * decl ) {
+	std::string name = Mangle::mangle( decl );
+	uint64_t hash = 14695981039346656037ULL; // FNV offset basis
+	for ( char c : name ) {
+		hash ^= static_cast<uint64_t>( c );
+		hash *= 1099511628211ULL; // FNV prime
+	}
+	return hash;
+}
+
+// Create a ConstantExpr for the hash with proper ULL suffix to avoid
+// C compiler warnings about large unsigned constants.
+static inline ast::ConstantExpr * hashMangleExpr(
+		const CodeLocation & location, const ast::DeclWithType * decl ) {
+	uint64_t hash = hashMangle( decl );
+	return new ast::ConstantExpr{
+		location,
+		new ast::BasicType{ ast::BasicKind::LongLongUnsignedInt },
+		std::to_string( hash ) + "ull",
+		(unsigned long long)hash };
+}
+
+} // namespace Concurrency
Index: src/Concurrency/Waitfor.cpp
===================================================================
--- src/Concurrency/Waitfor.cpp	(revision 7d29089ce266e027efd90b5274730df2b9fbb059)
+++ src/Concurrency/Waitfor.cpp	(revision a30fceb1a73c4ef2bbee39a2b5406da881f51111)
@@ -22,4 +22,5 @@
 #include "InitTweak/InitTweak.hpp"
 #include "ResolvExpr/Resolver.hpp"
+#include "Concurrency/MutexFuncHash.hpp"
 
 #include "AST/Print.hpp"
@@ -331,4 +332,8 @@
 		makeAccStmt( location, acceptables, index, "func",
 			funcExpr, context ),
+		makeAccStmt( location, acceptables, index, "func_id",
+			Concurrency::hashMangleExpr( location,
+				variableExpr->var.strict_as<ast::DeclWithType>() ),
+			context ),
 		makeAccStmt( location, acceptables, index, "data",
 			new ast::VariableExpr( location, monitors ), context ),
Index: src/Validate/Autogen.cpp
===================================================================
--- src/Validate/Autogen.cpp	(revision 7d29089ce266e027efd90b5274730df2b9fbb059)
+++ src/Validate/Autogen.cpp	(revision a30fceb1a73c4ef2bbee39a2b5406da881f51111)
@@ -402,17 +402,5 @@
 	}
 
-	ast::FunctionDecl * decl = genProto( "^?{}", { dst }, {} );
-	// For concurrent types, remove static storage and inline specifier, and add
-	// cfa_linkonce attribute so the destructor has external linkage with linkonce
-	// semantics. This is required for waitfor to work correctly across translation
-	// units - the function pointer must be the same everywhere, and cfa_linkonce
-	// ensures only one definition survives linking.
-	if ( isConcurrentType() ) {
-		auto mut = ast::mutate( decl );
-		mut->storage = ast::Storage::Classes();
-		mut->funcSpec = ast::Function::Specs();
-		mut->attributes.push_back( new ast::Attribute( "cfa_linkonce" ) );
-	}
-	return decl;
+	return genProto( "^?{}", { dst }, {} );
 }
 
