[clang] [llvm] [Clang][AllocToken] Support TypeFuncHash and TypeFuncHashPointerSplit modes (PR #228492)

Ai Nozaki via cfe-commits cfe-commits at lists.llvm.org
Sat Oct 3 06:24:03 PDT 2026


https://github.com/ainozaki updated https://github.com/llvm/llvm-project/pull/228492

>From 7291fd0c3a949e70b0f31c0d67a7d167a85ec39a Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Tue, 29 Sep 2026 02:04:42 +0000
Subject: [PATCH 01/13] [AllocToken] Add TypeFuncHash and
 TypeFuncHashPointerSplit modes

Add new token modes that combine the allocated type with the name of the
function containing the allocation, so that allocations of the same type
in different functions receive different tokens.

The token ID is split into bitfields: the upper bits hold the hash of the
type name, and the lower bits hold the hash of the function name. With
pointer split, the most significant bit is set for types that contain
pointers. By default the full token width is used; otherwise the maximum
number of tokens is rounded down to a power of two, and must be at least 8.

The function name is carried as an optional third operand of !alloc_token,
mirrored by the new AllocTokenMetadata::FunctionName field. An empty type
name denotes an unknown type. Metadata without a function name is rejected
in the new modes, since the module may already contain tokens computed by
another mode.

- Verifier: accept 2 or 3 operands.
- Metadata merging: join function names with "|", like type names.
- Inliner: treat metadata with an unknown type as missing, so that it is
  replaced by the call site's metadata.

Like the stateful modes, the new modes are not supported in constant
expressions; llvm::getAllocToken() returns std::nullopt for them.
---
 llvm/docs/LangRef.md                          |   6 +-
 llvm/include/llvm/Support/AllocToken.h        |  16 ++-
 llvm/lib/IR/Metadata.cpp                      |  38 +++---
 llvm/lib/IR/Verifier.cpp                      |   5 +-
 llvm/lib/Support/AllocToken.cpp               |  13 ++
 .../Transforms/Instrumentation/AllocToken.cpp |  73 ++++++++++-
 llvm/lib/Transforms/Utils/InlineFunction.cpp  |  10 +-
 .../AllocToken/typefunchash-errors.ll         |  25 ++++
 .../AllocToken/typefunchash.ll                | 122 ++++++++++++++++++
 llvm/test/Transforms/Inline/alloc-token.ll    |  16 +++
 .../SimplifyCFG/merge-calls-alloc-token.ll    |  26 ++++
 11 files changed, 321 insertions(+), 29 deletions(-)
 create mode 100644 llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
 create mode 100644 llvm/test/Instrumentation/AllocToken/typefunchash.ll

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index ee7068b2125cc2..0e3812b45df2ca 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -9184,12 +9184,16 @@ allocation. This information is consumed by the `alloc-token` pass to
 instrument such calls with allocation token IDs.
 
 The metadata contains: string with the type of an allocation, and a boolean
-denoting if the type contains a pointer.
+denoting if the type contains a pointer. Optionally, it contains a string with
+the name of the function containing the allocation, in which case an empty type
+name denotes an unknown type.
 
 ```
 call ptr @malloc(i64 64), !alloc_token !0
+call ptr @malloc(i64 64), !alloc_token !1
 
 !0 = !{!"<type-name>", i1 <contains-pointer>}
+!1 = !{!"<type-name>", i1 <contains-pointer>, !"<function-name>"}
 ```
 
 #### '`stack-protector`' Metadata
diff --git a/llvm/include/llvm/Support/AllocToken.h b/llvm/include/llvm/Support/AllocToken.h
index 1dc3a0cacef242..d1016b9ced90e6 100644
--- a/llvm/include/llvm/Support/AllocToken.h
+++ b/llvm/include/llvm/Support/AllocToken.h
@@ -35,6 +35,14 @@ enum class AllocTokenMode {
   /// reserved for types that contain pointers and the bottom half for types
   /// that do not contain pointers.
   TypeHashPointerSplit,
+
+  /// Token ID based on allocated type hash (upper bits) and the hash of the
+  /// name of the function containing the allocation (lower bits).
+  TypeFuncHash,
+
+  /// Like TypeFuncHash, but the most significant bit of the token ID is set for
+  /// types that contain pointers.
+  TypeFuncHashPointerSplit,
 };
 
 /// The default allocation token mode.
@@ -53,10 +61,14 @@ LLVM_ABI StringRef getAllocTokenModeAsString(AllocTokenMode Mode);
 struct AllocTokenMetadata {
   SmallString<64> TypeName;
   bool ContainsPointer;
+  /// Name of the function containing the allocation. Only provided for modes
+  /// that use it (TypeFuncHash and TypeFuncHashPointerSplit).
+  std::optional<SmallString<64>> FunctionName = std::nullopt;
 };
 
-/// Calculates stable allocation token ID. Returns std::nullopt for stateful
-/// modes that are only available in the AllocToken pass.
+/// Calculates stable allocation token ID. Returns std::nullopt for modes that
+/// are only available in the AllocToken pass: stateful modes, and modes that
+/// depend on the function containing the allocation.
 ///
 /// \param Mode The token generation mode.
 /// \param Metadata The metadata about the allocation.
diff --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp
index bb07775a6f529e..0e2a0b60f49b33 100644
--- a/llvm/lib/IR/Metadata.cpp
+++ b/llvm/lib/IR/Metadata.cpp
@@ -1357,35 +1357,35 @@ MDNode *MDNode::getMergedAllocTokenMetadata(const MDNode *A, const MDNode *B) {
     return nullptr;
   if (A == B)
     return const_cast<MDNode *>(A);
-  if (A->getNumOperands() != 2 || B->getNumOperands() != 2)
+  const unsigned NumOps = A->getNumOperands();
+  if ((NumOps != 2 && NumOps != 3) || B->getNumOperands() != NumOps)
     return nullptr;
   auto *CIA = mdconst::dyn_extract_or_null<ConstantInt>(A->getOperand(1));
   auto *CIB = mdconst::dyn_extract_or_null<ConstantInt>(B->getOperand(1));
   if (!CIA || !CIB)
     return nullptr;
 
-  MDString *NameA = dyn_cast<MDString>(A->getOperand(0));
-  MDString *NameB = dyn_cast<MDString>(B->getOperand(0));
-  if (!NameA || !NameB)
-    return nullptr;
-
-  if (NameA == NameB)
-    return CIA->isOne() ? const_cast<MDNode *>(A) : const_cast<MDNode *>(B);
-
+  // Merge the names (type or function) at operand Idx, joined with '|'.
   LLVMContext &Ctx = A->getContext();
-  StringRef StrA = NameA->getString();
-  StringRef StrB = NameB->getString();
-
-  SmallString<64> Buffer;
-  Buffer.reserve(StrA.size() + 1 + StrB.size());
-  Buffer.append(StrA);
-  Buffer.push_back('|');
-  Buffer.append(StrB);
+  auto MergeNames = [&](unsigned Idx) -> Metadata * {
+    MDString *NameA = dyn_cast<MDString>(A->getOperand(Idx));
+    MDString *NameB = dyn_cast<MDString>(B->getOperand(Idx));
+    if (!NameA || !NameB)
+      return nullptr;
+    if (NameA == NameB)
+      return NameA;
+    return MDString::get(Ctx,
+                         (NameA->getString() + "|" + NameB->getString()).str());
+  };
 
   bool MergedContainsPointer = CIA->isOne() || CIB->isOne();
-  Metadata *Ops[] = {MDString::get(Ctx, Buffer),
-                     ConstantAsMetadata::get(ConstantInt::get(
+  SmallVector<Metadata *, 3> Ops = {
+      MergeNames(0), ConstantAsMetadata::get(ConstantInt::get(
                          Type::getInt1Ty(Ctx), MergedContainsPointer))};
+  if (NumOps == 3)
+    Ops.push_back(MergeNames(2));
+  if (is_contained(Ops, nullptr))
+    return nullptr;
   return MDNode::get(Ctx, Ops);
 }
 
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 2502d574dd9928..0c035dc96af7f4 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -5802,10 +5802,13 @@ void Verifier::visitCapturesMetadata(Instruction &I, const MDNode *Captures) {
 
 void Verifier::visitAllocTokenMetadata(Instruction &I, MDNode *MD) {
   Check(isa<CallBase>(I), "!alloc_token should only exist on calls", &I);
-  Check(MD->getNumOperands() == 2, "!alloc_token must have 2 operands", MD);
+  Check(MD->getNumOperands() == 2 || MD->getNumOperands() == 3,
+        "!alloc_token must have 2 or 3 operands", MD);
   Check(isa<MDString>(MD->getOperand(0)), "expected string", MD);
   Check(mdconst::dyn_extract_or_null<ConstantInt>(MD->getOperand(1)),
         "expected integer constant", MD);
+  if (MD->getNumOperands() == 3)
+    Check(isa<MDString>(MD->getOperand(2)), "expected string", MD);
 }
 
 void Verifier::visitInlineHistoryMetadata(Instruction &I, MDNode *MD) {
diff --git a/llvm/lib/Support/AllocToken.cpp b/llvm/lib/Support/AllocToken.cpp
index cabe52189c4bbf..bf103d4220a7a2 100644
--- a/llvm/lib/Support/AllocToken.cpp
+++ b/llvm/lib/Support/AllocToken.cpp
@@ -24,6 +24,9 @@ llvm::getAllocTokenModeFromString(StringRef Name) {
       .Case("random", AllocTokenMode::Random)
       .Case("typehash", AllocTokenMode::TypeHash)
       .Case("typehashpointersplit", AllocTokenMode::TypeHashPointerSplit)
+      .Case("typefunchash", AllocTokenMode::TypeFuncHash)
+      .Case("typefunchashpointersplit",
+            AllocTokenMode::TypeFuncHashPointerSplit)
       .Case("default", DefaultAllocTokenMode)
       .Default(std::nullopt);
 }
@@ -38,6 +41,10 @@ StringRef llvm::getAllocTokenModeAsString(AllocTokenMode Mode) {
     return "typehash";
   case AllocTokenMode::TypeHashPointerSplit:
     return "typehashpointersplit";
+  case AllocTokenMode::TypeFuncHash:
+    return "typefunchash";
+  case AllocTokenMode::TypeFuncHashPointerSplit:
+    return "typefunchashpointersplit";
   }
   llvm_unreachable("Unknown AllocTokenMode");
 }
@@ -58,6 +65,12 @@ std::optional<uint64_t> llvm::getAllocToken(AllocTokenMode Mode,
     // Stateful modes cannot be implemented as a pure function.
     return std::nullopt;
 
+  case AllocTokenMode::TypeFuncHash:
+  case AllocTokenMode::TypeFuncHashPointerSplit:
+    // Depends on the function containing the allocation, which is unknown in
+    // constant expressions; only supported by the AllocToken pass.
+    return std::nullopt;
+
   case AllocTokenMode::TypeHash:
     return getStableHash(Metadata, MaxTokens);
 
diff --git a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
index 6072289615e8ee..195eda43b83f03 100644
--- a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
@@ -40,6 +40,7 @@
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/RandomNumberGenerator.h"
 #include "llvm/Support/SipHash.h"
 #include <cassert>
@@ -104,7 +105,7 @@ STATISTIC(NumAllocationsInstrumented, "Allocations instrumented");
 
 /// Returns the !alloc_token metadata if available.
 ///
-/// Expected format is: !{<type-name>, <contains-pointer>}
+/// Expected format is: !{<type-name>, <contains-pointer>[, <function-name>]}
 MDNode *getAllocTokenMetadata(const CallBase &CB) {
   MDNode *Ret = nullptr;
   if (auto *II = dyn_cast<IntrinsicInst>(&CB);
@@ -119,7 +120,8 @@ MDNode *getAllocTokenMetadata(const CallBase &CB) {
     if (!Ret)
       return nullptr;
   }
-  assert(Ret->getNumOperands() == 2 && "bad !alloc_token");
+  assert((Ret->getNumOperands() == 2 || Ret->getNumOperands() == 3) &&
+         "bad !alloc_token");
   assert(isa<MDString>(Ret->getOperand(0)));
   assert(isa<ConstantAsMetadata>(Ret->getOperand(1)));
   return Ret;
@@ -230,6 +232,66 @@ class TypeHashPointerSplitMode : public TypeHashMode {
   }
 };
 
+/// Implementation for TokenMode::TypeFuncHash and
+/// TokenMode::TypeFuncHashPointerSplit. The token ID is split into bitfields:
+/// the upper bits hold the type name hash, and the lower bits hold the hash of
+/// the name of the function containing the allocation. With pointer split, the
+/// most significant bit is set for types that contain pointers.
+class TypeFuncHashMode : public TypeHashMode {
+public:
+  TypeFuncHashMode(const IntegerType &TokenTy, uint64_t MaxTokens,
+                   TokenMode Mode)
+      : TypeHashMode(TokenTy, MaxTokens), Mode(Mode),
+        // Use the full width by default, otherwise round down to power of 2.
+        Bits(this->MaxTokens == TokenTy.getBitMask()
+                 ? TokenTy.getBitWidth()
+                 : Log2_64(this->MaxTokens)) {
+    if (Bits < 3)
+      reportFatalUsageError("alloc-token-max must be at least 8 in mode " +
+                            getAllocTokenModeAsString(Mode));
+  }
+
+  uint64_t operator()(const CallBase &CB, OptimizationRemarkEmitter &ORE) {
+    MDNode *N = getAllocTokenMetadata(CB);
+    if (!N) {
+      remarkNoMetadata(CB, ORE);
+      return ClFallbackToken;
+    }
+    // Metadata without function name was generated by another mode, and the
+    // module may already contain tokens computed by that mode.
+    if (N->getNumOperands() != 3) {
+      CB.getContext().emitError(
+          &CB, "!alloc_token without function name is incompatible with mode " +
+                   getAllocTokenModeAsString(Mode));
+      return ClFallbackToken;
+    }
+    AllocTokenMetadata Metadata{cast<MDString>(N->getOperand(0))->getString(),
+                                containsPointer(N),
+                                cast<MDString>(N->getOperand(2))->getString()};
+
+    // If the number of bits is odd, the type name hash gets the extra bit.
+    const unsigned FuncBits = Bits / 2;
+    unsigned TypeBits = Bits - FuncBits;
+    uint64_t Token = 0;
+    if (Mode == TokenMode::TypeFuncHashPointerSplit) {
+      --TypeBits;
+      Token = uint64_t(Metadata.ContainsPointer) << (Bits - 1);
+    }
+    // An empty type name denotes an unknown type.
+    if (!Metadata.TypeName.empty())
+      Token |= (getStableSipHash(Metadata.TypeName) &
+                maskTrailingOnes<uint64_t>(TypeBits))
+               << FuncBits;
+    Token |= getStableSipHash(*Metadata.FunctionName) &
+             maskTrailingOnes<uint64_t>(FuncBits);
+    return Token;
+  }
+
+private:
+  const TokenMode Mode;
+  const unsigned Bits;
+};
+
 // Apply opt overrides and module flags.
 static AllocTokenOptions resolveOptions(AllocTokenOptions Opts,
                                         const Module &M) {
@@ -278,6 +340,11 @@ class AllocToken {
     case TokenMode::TypeHashPointerSplit:
       Mode.emplace<TypeHashPointerSplitMode>(*IntPtrTy, Options.MaxTokens);
       break;
+    case TokenMode::TypeFuncHash:
+    case TokenMode::TypeFuncHashPointerSplit:
+      Mode.emplace<TypeFuncHashMode>(*IntPtrTy, Options.MaxTokens,
+                                     Options.Mode);
+      break;
     }
   }
 
@@ -321,7 +388,7 @@ class AllocToken {
   DenseMap<std::pair<LibFunc, uint64_t>, FunctionCallee> TokenAllocFunctions;
   // Selected mode.
   std::variant<IncrementMode, RandomMode, TypeHashMode,
-               TypeHashPointerSplitMode>
+               TypeHashPointerSplitMode, TypeFuncHashMode>
       Mode;
 };
 
diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index 43a73e0d412a49..fec91110181ad7 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -985,9 +985,13 @@ propagateAllocTokenMetadata(Function *CalledFunc, CallBase &CB,
     if (InlinedFunctionInfo.isSimplified(OrigCall, ClonedCall))
       continue;
     // Fill missing only: never overwrite a more specific token the wrapper
-    // already set on an internal allocation.
-    if (ClonedCall->getMetadata(LLVMContext::MD_alloc_token))
-      continue;
+    // already set on an internal allocation. An empty type name denotes an
+    // unknown type, which is not more specific.
+    if (MDNode *MD = ClonedCall->getMetadata(LLVMContext::MD_alloc_token)) {
+      auto *TypeName = dyn_cast<MDString>(MD->getOperand(0));
+      if (!TypeName || !TypeName->getString().empty())
+        continue;
+    }
     ClonedCall->setMetadata(LLVMContext::MD_alloc_token, AllocTokenMD);
   }
 }
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll b/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
new file mode 100644
index 00000000000000..363fea383ca85e
--- /dev/null
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
@@ -0,0 +1,25 @@
+; Test errors in the typefunchash modes.
+;
+; Metadata without function name may come from bitcode compiled with another
+; mode, which may already contain token IDs computed by that mode.
+; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -disable-output 2>&1 | FileCheck %s --check-prefix=NOFUNC
+; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -disable-output 2>&1 | FileCheck %s --check-prefix=NOFUNC-SPLIT
+;
+; The token ID must have room for the pointer flag, type and function hashes.
+; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=4 -disable-output 2>&1 | FileCheck %s --check-prefix=MAX
+
+; NOFUNC: error: !alloc_token without function name is incompatible with mode typefunchash{{$}}
+; NOFUNC-SPLIT: error: !alloc_token without function name is incompatible with mode typefunchashpointersplit{{$}}
+; MAX: LLVM ERROR: alloc-token-max must be at least 8 in mode typefunchashpointersplit{{$}}
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+declare ptr @malloc(i64)
+
+define ptr @test_no_function_name() sanitize_alloc_token {
+entry:
+  %ptr = call ptr @malloc(i64 4), !alloc_token !0
+  ret ptr %ptr
+}
+
+!0 = !{!"int", i1 false}
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash.ll b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
new file mode 100644
index 00000000000000..48187734e9965c
--- /dev/null
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
@@ -0,0 +1,122 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; Test the typefunchash modes: the token ID consists of the type name hash in the
+; upper bits and the function name hash in the lower bits. With pointer split,
+; the most significant bit is set for types that contain pointers.
+;
+; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=HASH
+; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=SPLIT
+; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=1000 -S | FileCheck %s --check-prefix=SPLIT-ODD
+; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -S | FileCheck %s --check-prefix=SPLIT-DEFAULT
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+
+declare ptr @malloc(i64)
+declare i64 @llvm.alloc.token.id.i64(metadata)
+
+define void @test_typefunchash() sanitize_alloc_token {
+; HASH-LABEL: define void @test_typefunchash(
+; HASH-SAME: ) #[[ATTR2:[0-9]+]] {
+; HASH-NEXT:  [[ENTRY:.*:]]
+; HASH-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 41), !alloc_token [[META0:![0-9]+]]
+; HASH-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 89), !alloc_token [[META1:![0-9]+]]
+; HASH-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 42), !alloc_token [[META2:![0-9]+]]
+; HASH-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 9), !alloc_token [[META3:![0-9]+]]
+; HASH-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 33), !alloc_token [[META4:![0-9]+]]
+; HASH-NEXT:    ret void
+;
+; SPLIT-LABEL: define void @test_typefunchash(
+; SPLIT-SAME: ) #[[ATTR2:[0-9]+]] {
+; SPLIT-NEXT:  [[ENTRY:.*:]]
+; SPLIT-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 41), !alloc_token [[META0:![0-9]+]]
+; SPLIT-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 217), !alloc_token [[META1:![0-9]+]]
+; SPLIT-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 42), !alloc_token [[META2:![0-9]+]]
+; SPLIT-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 9), !alloc_token [[META3:![0-9]+]]
+; SPLIT-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 33), !alloc_token [[META4:![0-9]+]]
+; SPLIT-NEXT:    ret void
+;
+; SPLIT-ODD-LABEL: define void @test_typefunchash(
+; SPLIT-ODD-SAME: ) #[[ATTR2:[0-9]+]] {
+; SPLIT-ODD-NEXT:  [[ENTRY:.*:]]
+; SPLIT-ODD-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 41), !alloc_token [[META0:![0-9]+]]
+; SPLIT-ODD-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 345), !alloc_token [[META1:![0-9]+]]
+; SPLIT-ODD-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 42), !alloc_token [[META2:![0-9]+]]
+; SPLIT-ODD-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 9), !alloc_token [[META3:![0-9]+]]
+; SPLIT-ODD-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 33), !alloc_token [[META4:![0-9]+]]
+; SPLIT-ODD-NEXT:    ret void
+;
+; SPLIT-DEFAULT-LABEL: define void @test_typefunchash(
+; SPLIT-DEFAULT-SAME: ) #[[ATTR2:[0-9]+]] {
+; SPLIT-DEFAULT-NEXT:  [[ENTRY:.*:]]
+; SPLIT-DEFAULT-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435480860910281), !alloc_token [[META0:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 -5987466274009226551), !alloc_token [[META1:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435482481861194), !alloc_token [[META2:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 2433508041), !alloc_token [[META3:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435478597976305), !alloc_token [[META4:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    ret void
+;
+entry:
+  call ptr @malloc(i64 4), !alloc_token !0
+  call ptr @malloc(i64 8), !alloc_token !1
+  call ptr @malloc(i64 4), !alloc_token !2
+  call ptr @malloc(i64 4), !alloc_token !3
+  call ptr @malloc(i64 4), !alloc_token !4
+  ret void
+}
+
+define i64 @test_intrinsic_lowering() {
+; HASH-LABEL: define i64 @test_intrinsic_lowering() {
+; HASH-NEXT:  [[ENTRY:.*:]]
+; HASH-NEXT:    ret i64 89
+;
+; SPLIT-LABEL: define i64 @test_intrinsic_lowering() {
+; SPLIT-NEXT:  [[ENTRY:.*:]]
+; SPLIT-NEXT:    ret i64 217
+;
+; SPLIT-ODD-LABEL: define i64 @test_intrinsic_lowering() {
+; SPLIT-ODD-NEXT:  [[ENTRY:.*:]]
+; SPLIT-ODD-NEXT:    ret i64 345
+;
+; SPLIT-DEFAULT-LABEL: define i64 @test_intrinsic_lowering() {
+; SPLIT-DEFAULT-NEXT:  [[ENTRY:.*:]]
+; SPLIT-DEFAULT-NEXT:    ret i64 -5987466274009226551
+;
+entry:
+  %token = call i64 @llvm.alloc.token.id.i64(metadata !1)
+  ret i64 %token
+}
+
+; Type and function name.
+!0 = !{!"int", i1 false, !"foo"}
+; Type containing a pointer.
+!1 = !{!"int*", i1 true, !"foo"}
+; Same type in a different function.
+!2 = !{!"int", i1 false, !"bar"}
+; Unknown type: the type hash bits are zero.
+!3 = !{!"", i1 false, !"foo"}
+; Allocation outside of any function.
+!4 = !{!"int", i1 false, !""}
+;.
+; HASH: [[META0]] = !{!"int", i1 false, !"foo"}
+; HASH: [[META1]] = !{!"int*", i1 true, !"foo"}
+; HASH: [[META2]] = !{!"int", i1 false, !"bar"}
+; HASH: [[META3]] = !{!"", i1 false, !"foo"}
+; HASH: [[META4]] = !{!"int", i1 false, !""}
+;.
+; SPLIT: [[META0]] = !{!"int", i1 false, !"foo"}
+; SPLIT: [[META1]] = !{!"int*", i1 true, !"foo"}
+; SPLIT: [[META2]] = !{!"int", i1 false, !"bar"}
+; SPLIT: [[META3]] = !{!"", i1 false, !"foo"}
+; SPLIT: [[META4]] = !{!"int", i1 false, !""}
+;.
+; SPLIT-ODD: [[META0]] = !{!"int", i1 false, !"foo"}
+; SPLIT-ODD: [[META1]] = !{!"int*", i1 true, !"foo"}
+; SPLIT-ODD: [[META2]] = !{!"int", i1 false, !"bar"}
+; SPLIT-ODD: [[META3]] = !{!"", i1 false, !"foo"}
+; SPLIT-ODD: [[META4]] = !{!"int", i1 false, !""}
+;.
+; SPLIT-DEFAULT: [[META0]] = !{!"int", i1 false, !"foo"}
+; SPLIT-DEFAULT: [[META1]] = !{!"int*", i1 true, !"foo"}
+; SPLIT-DEFAULT: [[META2]] = !{!"int", i1 false, !"bar"}
+; SPLIT-DEFAULT: [[META3]] = !{!"", i1 false, !"foo"}
+; SPLIT-DEFAULT: [[META4]] = !{!"int", i1 false, !""}
+;.
diff --git a/llvm/test/Transforms/Inline/alloc-token.ll b/llvm/test/Transforms/Inline/alloc-token.ll
index 4e0e220ff8ae02..99ab7160050508 100644
--- a/llvm/test/Transforms/Inline/alloc-token.ll
+++ b/llvm/test/Transforms/Inline/alloc-token.ll
@@ -31,7 +31,23 @@ define ptr @no_overwrite() {
   ret ptr %c
 }
 
+define internal ptr @wrapper_unknown(i64 %size) alwaysinline {
+  %p = call ptr @malloc(i64 %size), !alloc_token !3
+  ret ptr %p
+}
+
+; Unless it denotes an unknown type (empty type name).
+; CHECK-LABEL: define ptr @overwrite_unknown(
+; CHECK: call ptr @malloc(i64 4){{.*}}, !alloc_token [[CALLER:![0-9]+]]
+define ptr @overwrite_unknown() {
+  %c = call ptr @wrapper_unknown(i64 4), !alloc_token !2
+  ret ptr %c
+}
+
 ; CHECK-DAG: [[MD]] = !{!"Outer", i1 true}
 ; CHECK-DAG: [[OWN]] = !{!"Inner", i1 false}
+; CHECK-DAG: [[CALLER]] = !{!"Outer", i1 true, !"overwrite_unknown"}
 !0 = !{!"Outer", i1 true}
 !1 = !{!"Inner", i1 false}
+!2 = !{!"Outer", i1 true, !"overwrite_unknown"}
+!3 = !{!"", i1 false, !"wrapper_unknown"}
diff --git a/llvm/test/Transforms/SimplifyCFG/merge-calls-alloc-token.ll b/llvm/test/Transforms/SimplifyCFG/merge-calls-alloc-token.ll
index 47aec75edbe741..a024613f869b86 100644
--- a/llvm/test/Transforms/SimplifyCFG/merge-calls-alloc-token.ll
+++ b/llvm/test/Transforms/SimplifyCFG/merge-calls-alloc-token.ll
@@ -120,12 +120,38 @@ if.end:
   ret ptr %x.0
 }
 
+define ptr @test_merge_alloc_token_func_name(i1 %b) {
+; CHECK-LABEL: define ptr @test_merge_alloc_token_func_name(
+; CHECK-SAME: i1 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CALL:%.*]] = call ptr @_Znwm(i64 4), !alloc_token [[META3:![0-9]+]]
+; CHECK-NEXT:    ret ptr [[CALL]]
+;
+entry:
+  br i1 %b, label %if.then, label %if.else
+
+if.then:
+  %call = call ptr @_Znwm(i64 4), !alloc_token !4
+  br label %if.end
+
+if.else:
+  %call1 = call ptr @_Znwm(i64 4), !alloc_token !5
+  br label %if.end
+
+if.end:
+  %x.0 = phi ptr [ %call, %if.then ], [ %call1, %if.else ]
+  ret ptr %x.0
+}
+
 !0 = !{!"int", i1 0}
 !1 = !{!"char[4]", i1 0}
 !2 = !{!"StructA", i1 1}
 !3 = !{!"StructB", i1 1}
+!4 = !{!"int", i1 0, !"foo"}
+!5 = !{!"char[4]", i1 1, !"bar"}
 ;.
 ; CHECK: [[META0]] = !{!"int", i1 false}
 ; CHECK: [[META1]] = !{!"int|char[4]", i1 false}
 ; CHECK: [[META2]] = !{!"StructA|StructB", i1 true}
+; CHECK: [[META3]] = !{!"int|char[4]", i1 true, !"foo|bar"}
 ;.

>From fcc8cf2f7c61d45ec7d2fa0ba83d83076e404a65 Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Tue, 29 Sep 2026 13:53:51 +0000
Subject: [PATCH 02/13] move token calculation to //Support

---
 llvm/include/llvm/Support/AllocToken.h        |  9 +++--
 llvm/lib/Support/AllocToken.cpp               | 37 ++++++++++++++++--
 .../Transforms/Instrumentation/AllocToken.cpp | 39 +++++--------------
 llvm/lib/Transforms/Utils/InlineFunction.cpp  |  8 ++--
 .../AllocToken/typefunchash-errors.ll         |  9 +++--
 .../AllocToken/typefunchash.ll                | 12 +++---
 6 files changed, 65 insertions(+), 49 deletions(-)

diff --git a/llvm/include/llvm/Support/AllocToken.h b/llvm/include/llvm/Support/AllocToken.h
index d1016b9ced90e6..25faf9f0e9d0bf 100644
--- a/llvm/include/llvm/Support/AllocToken.h
+++ b/llvm/include/llvm/Support/AllocToken.h
@@ -61,14 +61,15 @@ LLVM_ABI StringRef getAllocTokenModeAsString(AllocTokenMode Mode);
 struct AllocTokenMetadata {
   SmallString<64> TypeName;
   bool ContainsPointer;
-  /// Name of the function containing the allocation. Only provided for modes
+  /// Name of the function containing the allocation. Required by the modes
   /// that use it (TypeFuncHash and TypeFuncHashPointerSplit).
   std::optional<SmallString<64>> FunctionName = std::nullopt;
 };
 
-/// Calculates stable allocation token ID. Returns std::nullopt for modes that
-/// are only available in the AllocToken pass: stateful modes, and modes that
-/// depend on the function containing the allocation.
+/// Calculates stable allocation token ID. Returns std::nullopt for stateful
+/// modes that are only available in the AllocToken pass, and for modes that
+/// depend on the function containing the allocation if no function name is
+/// provided.
 ///
 /// \param Mode The token generation mode.
 /// \param Metadata The metadata about the allocation.
diff --git a/llvm/lib/Support/AllocToken.cpp b/llvm/lib/Support/AllocToken.cpp
index bf103d4220a7a2..b63865ba740932 100644
--- a/llvm/lib/Support/AllocToken.cpp
+++ b/llvm/lib/Support/AllocToken.cpp
@@ -13,6 +13,7 @@
 #include "llvm/Support/AllocToken.h"
 #include "llvm/ADT/StringSwitch.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/SipHash.h"
 
 using namespace llvm;
@@ -54,6 +55,33 @@ static uint64_t getStableHash(const AllocTokenMetadata &Metadata,
   return getStableSipHash(Metadata.TypeName) % MaxTokens;
 }
 
+/// The token ID is split into bitfields: the upper bits hold the type name
+/// hash, and the lower bits hold the hash of the name of the function
+/// containing the allocation. With pointer split, the most significant bit is
+/// set for types that contain pointers. Uses Log2(MaxTokens) bits, so that the
+/// token ID is always less than MaxTokens.
+static uint64_t getTypeFuncHash(const AllocTokenMetadata &Metadata,
+                                uint64_t MaxTokens, bool PointerSplit) {
+  const unsigned Bits = Log2_64(MaxTokens);
+  assert(Bits >= (PointerSplit ? 3u : 2u) && "MaxTokens too small");
+  // If the number of bits is odd, the type name hash gets the extra bit.
+  const unsigned FuncBits = Bits / 2;
+  unsigned TypeBits = Bits - FuncBits;
+  uint64_t Token = 0;
+  if (PointerSplit) {
+    --TypeBits;
+    Token = uint64_t(Metadata.ContainsPointer) << (Bits - 1);
+  }
+  // An empty type name denotes an unknown type.
+  if (!Metadata.TypeName.empty())
+    Token |= (getStableSipHash(Metadata.TypeName) &
+              maskTrailingOnes<uint64_t>(TypeBits))
+             << FuncBits;
+  Token |= getStableSipHash(*Metadata.FunctionName) &
+           maskTrailingOnes<uint64_t>(FuncBits);
+  return Token;
+}
+
 std::optional<uint64_t> llvm::getAllocToken(AllocTokenMode Mode,
                                             const AllocTokenMetadata &Metadata,
                                             uint64_t MaxTokens) {
@@ -67,9 +95,12 @@ std::optional<uint64_t> llvm::getAllocToken(AllocTokenMode Mode,
 
   case AllocTokenMode::TypeFuncHash:
   case AllocTokenMode::TypeFuncHashPointerSplit:
-    // Depends on the function containing the allocation, which is unknown in
-    // constant expressions; only supported by the AllocToken pass.
-    return std::nullopt;
+    // Depends on the function containing the allocation, which may be unknown
+    // (e.g. in constant expressions).
+    if (!Metadata.FunctionName)
+      return std::nullopt;
+    return getTypeFuncHash(Metadata, MaxTokens,
+                           Mode == AllocTokenMode::TypeFuncHashPointerSplit);
 
   case AllocTokenMode::TypeHash:
     return getStableHash(Metadata, MaxTokens);
diff --git a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
index 195eda43b83f03..1830297272df67 100644
--- a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
@@ -233,21 +233,19 @@ class TypeHashPointerSplitMode : public TypeHashMode {
 };
 
 /// Implementation for TokenMode::TypeFuncHash and
-/// TokenMode::TypeFuncHashPointerSplit. The token ID is split into bitfields:
-/// the upper bits hold the type name hash, and the lower bits hold the hash of
-/// the name of the function containing the allocation. With pointer split, the
-/// most significant bit is set for types that contain pointers.
+/// TokenMode::TypeFuncHashPointerSplit.
 class TypeFuncHashMode : public TypeHashMode {
 public:
   TypeFuncHashMode(const IntegerType &TokenTy, uint64_t MaxTokens,
                    TokenMode Mode)
-      : TypeHashMode(TokenTy, MaxTokens), Mode(Mode),
-        // Use the full width by default, otherwise round down to power of 2.
-        Bits(this->MaxTokens == TokenTy.getBitMask()
-                 ? TokenTy.getBitWidth()
-                 : Log2_64(this->MaxTokens)) {
-    if (Bits < 3)
-      reportFatalUsageError("alloc-token-max must be at least 8 in mode " +
+      : TypeHashMode(TokenTy, MaxTokens), Mode(Mode) {
+    // At least one bit each for the type and function hashes, plus one bit for
+    // the pointer flag with pointer split.
+    const unsigned MinBits =
+        Mode == TokenMode::TypeFuncHashPointerSplit ? 3 : 2;
+    if (Log2_64(this->MaxTokens) < MinBits)
+      reportFatalUsageError("alloc-token-max must be at least " +
+                            Twine(1u << MinBits) + " in mode " +
                             getAllocTokenModeAsString(Mode));
   }
 
@@ -268,28 +266,11 @@ class TypeFuncHashMode : public TypeHashMode {
     AllocTokenMetadata Metadata{cast<MDString>(N->getOperand(0))->getString(),
                                 containsPointer(N),
                                 cast<MDString>(N->getOperand(2))->getString()};
-
-    // If the number of bits is odd, the type name hash gets the extra bit.
-    const unsigned FuncBits = Bits / 2;
-    unsigned TypeBits = Bits - FuncBits;
-    uint64_t Token = 0;
-    if (Mode == TokenMode::TypeFuncHashPointerSplit) {
-      --TypeBits;
-      Token = uint64_t(Metadata.ContainsPointer) << (Bits - 1);
-    }
-    // An empty type name denotes an unknown type.
-    if (!Metadata.TypeName.empty())
-      Token |= (getStableSipHash(Metadata.TypeName) &
-                maskTrailingOnes<uint64_t>(TypeBits))
-               << FuncBits;
-    Token |= getStableSipHash(*Metadata.FunctionName) &
-             maskTrailingOnes<uint64_t>(FuncBits);
-    return Token;
+    return *getAllocToken(Mode, Metadata, MaxTokens);
   }
 
 private:
   const TokenMode Mode;
-  const unsigned Bits;
 };
 
 // Apply opt overrides and module flags.
diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index fec91110181ad7..f5651c893f44d0 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -985,11 +985,11 @@ propagateAllocTokenMetadata(Function *CalledFunc, CallBase &CB,
     if (InlinedFunctionInfo.isSimplified(OrigCall, ClonedCall))
       continue;
     // Fill missing only: never overwrite a more specific token the wrapper
-    // already set on an internal allocation. An empty type name denotes an
-    // unknown type, which is not more specific.
+    // already set on an internal allocation. With a function name, an empty
+    // type name denotes an unknown type, which is not more specific.
     if (MDNode *MD = ClonedCall->getMetadata(LLVMContext::MD_alloc_token)) {
-      auto *TypeName = dyn_cast<MDString>(MD->getOperand(0));
-      if (!TypeName || !TypeName->getString().empty())
+      if (MD->getNumOperands() != 3 ||
+          !cast<MDString>(MD->getOperand(0))->getString().empty())
         continue;
     }
     ClonedCall->setMetadata(LLVMContext::MD_alloc_token, AllocTokenMD);
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll b/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
index 363fea383ca85e..46307702faa266 100644
--- a/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
@@ -5,12 +5,15 @@
 ; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -disable-output 2>&1 | FileCheck %s --check-prefix=NOFUNC
 ; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -disable-output 2>&1 | FileCheck %s --check-prefix=NOFUNC-SPLIT
 ;
-; The token ID must have room for the pointer flag, type and function hashes.
-; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=4 -disable-output 2>&1 | FileCheck %s --check-prefix=MAX
+; The token ID must have room for the type and function hashes, and the pointer
+; flag with pointer split.
+; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -alloc-token-max=2 -disable-output 2>&1 | FileCheck %s --check-prefix=MAX
+; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=4 -disable-output 2>&1 | FileCheck %s --check-prefix=MAX-SPLIT
 
 ; NOFUNC: error: !alloc_token without function name is incompatible with mode typefunchash{{$}}
 ; NOFUNC-SPLIT: error: !alloc_token without function name is incompatible with mode typefunchashpointersplit{{$}}
-; MAX: LLVM ERROR: alloc-token-max must be at least 8 in mode typefunchashpointersplit{{$}}
+; MAX: LLVM ERROR: alloc-token-max must be at least 4 in mode typefunchash{{$}}
+; MAX-SPLIT: LLVM ERROR: alloc-token-max must be at least 8 in mode typefunchashpointersplit{{$}}
 
 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
 
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash.ll b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
index 48187734e9965c..4598f86ca55818 100644
--- a/llvm/test/Instrumentation/AllocToken/typefunchash.ll
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
@@ -47,11 +47,11 @@ define void @test_typefunchash() sanitize_alloc_token {
 ; SPLIT-DEFAULT-LABEL: define void @test_typefunchash(
 ; SPLIT-DEFAULT-SAME: ) #[[ATTR2:[0-9]+]] {
 ; SPLIT-DEFAULT-NEXT:  [[ENTRY:.*:]]
-; SPLIT-DEFAULT-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435480860910281), !alloc_token [[META0:![0-9]+]]
-; SPLIT-DEFAULT-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 -5987466274009226551), !alloc_token [[META1:![0-9]+]]
-; SPLIT-DEFAULT-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435482481861194), !alloc_token [[META2:![0-9]+]]
-; SPLIT-DEFAULT-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 2433508041), !alloc_token [[META3:![0-9]+]]
-; SPLIT-DEFAULT-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435478597976305), !alloc_token [[META4:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 3043217739499725513), !alloc_token [[META0:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 6229638898919432905), !alloc_token [[META1:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 3043217741120676426), !alloc_token [[META2:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 286024393), !alloc_token [[META3:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 3043217739384275185), !alloc_token [[META4:![0-9]+]]
 ; SPLIT-DEFAULT-NEXT:    ret void
 ;
 entry:
@@ -78,7 +78,7 @@ define i64 @test_intrinsic_lowering() {
 ;
 ; SPLIT-DEFAULT-LABEL: define i64 @test_intrinsic_lowering() {
 ; SPLIT-DEFAULT-NEXT:  [[ENTRY:.*:]]
-; SPLIT-DEFAULT-NEXT:    ret i64 -5987466274009226551
+; SPLIT-DEFAULT-NEXT:    ret i64 6229638898919432905
 ;
 entry:
   %token = call i64 @llvm.alloc.token.id.i64(metadata !1)

>From 16f626e69d93ff551011351ca5cacb0bfee17337 Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Tue, 29 Sep 2026 14:35:47 +0000
Subject: [PATCH 03/13] update max token

---
 llvm/lib/Support/AllocToken.cpp               |  6 +-
 .../Transforms/Instrumentation/AllocToken.cpp | 12 +---
 .../AllocToken/typefunchash-errors.ll         |  7 --
 .../AllocToken/typefunchash.ll                | 66 ++++++++++++-------
 4 files changed, 49 insertions(+), 42 deletions(-)

diff --git a/llvm/lib/Support/AllocToken.cpp b/llvm/lib/Support/AllocToken.cpp
index b63865ba740932..033dbd5078914f 100644
--- a/llvm/lib/Support/AllocToken.cpp
+++ b/llvm/lib/Support/AllocToken.cpp
@@ -59,11 +59,13 @@ static uint64_t getStableHash(const AllocTokenMetadata &Metadata,
 /// hash, and the lower bits hold the hash of the name of the function
 /// containing the allocation. With pointer split, the most significant bit is
 /// set for types that contain pointers. Uses Log2(MaxTokens) bits, so that the
-/// token ID is always less than MaxTokens.
+/// token ID is always less than MaxTokens; with few bits, the function name
+/// hash (and then the type name hash) may get no bits.
 static uint64_t getTypeFuncHash(const AllocTokenMetadata &Metadata,
                                 uint64_t MaxTokens, bool PointerSplit) {
   const unsigned Bits = Log2_64(MaxTokens);
-  assert(Bits >= (PointerSplit ? 3u : 2u) && "MaxTokens too small");
+  if (Bits == 0) // MaxTokens == 1
+    return 0;
   // If the number of bits is odd, the type name hash gets the extra bit.
   const unsigned FuncBits = Bits / 2;
   unsigned TypeBits = Bits - FuncBits;
diff --git a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
index 1830297272df67..0c87c5be4ddd66 100644
--- a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
@@ -40,7 +40,6 @@
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/Compiler.h"
 #include "llvm/Support/ErrorHandling.h"
-#include "llvm/Support/MathExtras.h"
 #include "llvm/Support/RandomNumberGenerator.h"
 #include "llvm/Support/SipHash.h"
 #include <cassert>
@@ -238,16 +237,7 @@ class TypeFuncHashMode : public TypeHashMode {
 public:
   TypeFuncHashMode(const IntegerType &TokenTy, uint64_t MaxTokens,
                    TokenMode Mode)
-      : TypeHashMode(TokenTy, MaxTokens), Mode(Mode) {
-    // At least one bit each for the type and function hashes, plus one bit for
-    // the pointer flag with pointer split.
-    const unsigned MinBits =
-        Mode == TokenMode::TypeFuncHashPointerSplit ? 3 : 2;
-    if (Log2_64(this->MaxTokens) < MinBits)
-      reportFatalUsageError("alloc-token-max must be at least " +
-                            Twine(1u << MinBits) + " in mode " +
-                            getAllocTokenModeAsString(Mode));
-  }
+      : TypeHashMode(TokenTy, MaxTokens), Mode(Mode) {}
 
   uint64_t operator()(const CallBase &CB, OptimizationRemarkEmitter &ORE) {
     MDNode *N = getAllocTokenMetadata(CB);
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll b/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
index 46307702faa266..d1911c5865b4ae 100644
--- a/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
@@ -4,16 +4,9 @@
 ; mode, which may already contain token IDs computed by that mode.
 ; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -disable-output 2>&1 | FileCheck %s --check-prefix=NOFUNC
 ; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -disable-output 2>&1 | FileCheck %s --check-prefix=NOFUNC-SPLIT
-;
-; The token ID must have room for the type and function hashes, and the pointer
-; flag with pointer split.
-; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -alloc-token-max=2 -disable-output 2>&1 | FileCheck %s --check-prefix=MAX
-; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=4 -disable-output 2>&1 | FileCheck %s --check-prefix=MAX-SPLIT
 
 ; NOFUNC: error: !alloc_token without function name is incompatible with mode typefunchash{{$}}
 ; NOFUNC-SPLIT: error: !alloc_token without function name is incompatible with mode typefunchashpointersplit{{$}}
-; MAX: LLVM ERROR: alloc-token-max must be at least 4 in mode typefunchash{{$}}
-; MAX-SPLIT: LLVM ERROR: alloc-token-max must be at least 8 in mode typefunchashpointersplit{{$}}
 
 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
 
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash.ll b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
index 4598f86ca55818..695b240fb369a8 100644
--- a/llvm/test/Instrumentation/AllocToken/typefunchash.ll
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
@@ -1,12 +1,14 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; Test the typefunchash modes: the token ID consists of the type name hash in the
 ; upper bits and the function name hash in the lower bits. With pointer split,
-; the most significant bit is set for types that contain pointers.
+; the most significant bit is set for types that contain pointers. Small
+; alloc-token-max values leave fewer (or no) bits for the hashes.
 ;
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=HASH
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=SPLIT
-; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=1000 -S | FileCheck %s --check-prefix=SPLIT-ODD
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -S | FileCheck %s --check-prefix=SPLIT-DEFAULT
+; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=2 -S | FileCheck %s --check-prefix=SPLIT-MAX2
+; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=1 -S | FileCheck %s --check-prefix=SPLIT-MAX1
 
 target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
 
@@ -34,16 +36,6 @@ define void @test_typefunchash() sanitize_alloc_token {
 ; SPLIT-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 33), !alloc_token [[META4:![0-9]+]]
 ; SPLIT-NEXT:    ret void
 ;
-; SPLIT-ODD-LABEL: define void @test_typefunchash(
-; SPLIT-ODD-SAME: ) #[[ATTR2:[0-9]+]] {
-; SPLIT-ODD-NEXT:  [[ENTRY:.*:]]
-; SPLIT-ODD-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 41), !alloc_token [[META0:![0-9]+]]
-; SPLIT-ODD-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 345), !alloc_token [[META1:![0-9]+]]
-; SPLIT-ODD-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 42), !alloc_token [[META2:![0-9]+]]
-; SPLIT-ODD-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 9), !alloc_token [[META3:![0-9]+]]
-; SPLIT-ODD-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 33), !alloc_token [[META4:![0-9]+]]
-; SPLIT-ODD-NEXT:    ret void
-;
 ; SPLIT-DEFAULT-LABEL: define void @test_typefunchash(
 ; SPLIT-DEFAULT-SAME: ) #[[ATTR2:[0-9]+]] {
 ; SPLIT-DEFAULT-NEXT:  [[ENTRY:.*:]]
@@ -54,6 +46,26 @@ define void @test_typefunchash() sanitize_alloc_token {
 ; SPLIT-DEFAULT-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 3043217739384275185), !alloc_token [[META4:![0-9]+]]
 ; SPLIT-DEFAULT-NEXT:    ret void
 ;
+; SPLIT-MAX2-LABEL: define void @test_typefunchash(
+; SPLIT-MAX2-SAME: ) #[[ATTR2:[0-9]+]] {
+; SPLIT-MAX2-NEXT:  [[ENTRY:.*:]]
+; SPLIT-MAX2-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 0), !alloc_token [[META0:![0-9]+]]
+; SPLIT-MAX2-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 1), !alloc_token [[META1:![0-9]+]]
+; SPLIT-MAX2-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 0), !alloc_token [[META2:![0-9]+]]
+; SPLIT-MAX2-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 0), !alloc_token [[META3:![0-9]+]]
+; SPLIT-MAX2-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 0), !alloc_token [[META4:![0-9]+]]
+; SPLIT-MAX2-NEXT:    ret void
+;
+; SPLIT-MAX1-LABEL: define void @test_typefunchash(
+; SPLIT-MAX1-SAME: ) #[[ATTR2:[0-9]+]] {
+; SPLIT-MAX1-NEXT:  [[ENTRY:.*:]]
+; SPLIT-MAX1-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 0), !alloc_token [[META0:![0-9]+]]
+; SPLIT-MAX1-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 0), !alloc_token [[META1:![0-9]+]]
+; SPLIT-MAX1-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 0), !alloc_token [[META2:![0-9]+]]
+; SPLIT-MAX1-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 0), !alloc_token [[META3:![0-9]+]]
+; SPLIT-MAX1-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 0), !alloc_token [[META4:![0-9]+]]
+; SPLIT-MAX1-NEXT:    ret void
+;
 entry:
   call ptr @malloc(i64 4), !alloc_token !0
   call ptr @malloc(i64 8), !alloc_token !1
@@ -72,14 +84,18 @@ define i64 @test_intrinsic_lowering() {
 ; SPLIT-NEXT:  [[ENTRY:.*:]]
 ; SPLIT-NEXT:    ret i64 217
 ;
-; SPLIT-ODD-LABEL: define i64 @test_intrinsic_lowering() {
-; SPLIT-ODD-NEXT:  [[ENTRY:.*:]]
-; SPLIT-ODD-NEXT:    ret i64 345
-;
 ; SPLIT-DEFAULT-LABEL: define i64 @test_intrinsic_lowering() {
 ; SPLIT-DEFAULT-NEXT:  [[ENTRY:.*:]]
 ; SPLIT-DEFAULT-NEXT:    ret i64 6229638898919432905
 ;
+; SPLIT-MAX2-LABEL: define i64 @test_intrinsic_lowering() {
+; SPLIT-MAX2-NEXT:  [[ENTRY:.*:]]
+; SPLIT-MAX2-NEXT:    ret i64 1
+;
+; SPLIT-MAX1-LABEL: define i64 @test_intrinsic_lowering() {
+; SPLIT-MAX1-NEXT:  [[ENTRY:.*:]]
+; SPLIT-MAX1-NEXT:    ret i64 0
+;
 entry:
   %token = call i64 @llvm.alloc.token.id.i64(metadata !1)
   ret i64 %token
@@ -108,15 +124,21 @@ entry:
 ; SPLIT: [[META3]] = !{!"", i1 false, !"foo"}
 ; SPLIT: [[META4]] = !{!"int", i1 false, !""}
 ;.
-; SPLIT-ODD: [[META0]] = !{!"int", i1 false, !"foo"}
-; SPLIT-ODD: [[META1]] = !{!"int*", i1 true, !"foo"}
-; SPLIT-ODD: [[META2]] = !{!"int", i1 false, !"bar"}
-; SPLIT-ODD: [[META3]] = !{!"", i1 false, !"foo"}
-; SPLIT-ODD: [[META4]] = !{!"int", i1 false, !""}
-;.
 ; SPLIT-DEFAULT: [[META0]] = !{!"int", i1 false, !"foo"}
 ; SPLIT-DEFAULT: [[META1]] = !{!"int*", i1 true, !"foo"}
 ; SPLIT-DEFAULT: [[META2]] = !{!"int", i1 false, !"bar"}
 ; SPLIT-DEFAULT: [[META3]] = !{!"", i1 false, !"foo"}
 ; SPLIT-DEFAULT: [[META4]] = !{!"int", i1 false, !""}
 ;.
+; SPLIT-MAX2: [[META0]] = !{!"int", i1 false, !"foo"}
+; SPLIT-MAX2: [[META1]] = !{!"int*", i1 true, !"foo"}
+; SPLIT-MAX2: [[META2]] = !{!"int", i1 false, !"bar"}
+; SPLIT-MAX2: [[META3]] = !{!"", i1 false, !"foo"}
+; SPLIT-MAX2: [[META4]] = !{!"int", i1 false, !""}
+;.
+; SPLIT-MAX1: [[META0]] = !{!"int", i1 false, !"foo"}
+; SPLIT-MAX1: [[META1]] = !{!"int*", i1 true, !"foo"}
+; SPLIT-MAX1: [[META2]] = !{!"int", i1 false, !"bar"}
+; SPLIT-MAX1: [[META3]] = !{!"", i1 false, !"foo"}
+; SPLIT-MAX1: [[META4]] = !{!"int", i1 false, !""}
+;.

>From df9a3a2f25c7856b4f268694c144c7dee174cbdf Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Tue, 29 Sep 2026 14:48:03 +0000
Subject: [PATCH 04/13] update comments

---
 llvm/include/llvm/Support/AllocToken.h            | 15 ++++++---------
 llvm/lib/IR/Metadata.cpp                          |  2 +-
 llvm/lib/Support/AllocToken.cpp                   | 15 ++++-----------
 .../lib/Transforms/Instrumentation/AllocToken.cpp |  3 +--
 llvm/lib/Transforms/Utils/InlineFunction.cpp      |  4 ++--
 .../AllocToken/typefunchash-errors.ll             |  6 ++----
 .../Instrumentation/AllocToken/typefunchash.ll    |  5 +----
 7 files changed, 17 insertions(+), 33 deletions(-)

diff --git a/llvm/include/llvm/Support/AllocToken.h b/llvm/include/llvm/Support/AllocToken.h
index 25faf9f0e9d0bf..7455b4b08fc53b 100644
--- a/llvm/include/llvm/Support/AllocToken.h
+++ b/llvm/include/llvm/Support/AllocToken.h
@@ -36,12 +36,11 @@ enum class AllocTokenMode {
   /// that do not contain pointers.
   TypeHashPointerSplit,
 
-  /// Token ID based on allocated type hash (upper bits) and the hash of the
-  /// name of the function containing the allocation (lower bits).
+  /// Token ID based on allocated type hash and containing function name hash.
   TypeFuncHash,
 
-  /// Like TypeFuncHash, but the most significant bit of the token ID is set for
-  /// types that contain pointers.
+  /// Like TypeFuncHash, but the top half ID-space is reserved for types that
+  /// contain pointers.
   TypeFuncHashPointerSplit,
 };
 
@@ -61,15 +60,13 @@ LLVM_ABI StringRef getAllocTokenModeAsString(AllocTokenMode Mode);
 struct AllocTokenMetadata {
   SmallString<64> TypeName;
   bool ContainsPointer;
-  /// Name of the function containing the allocation. Required by the modes
-  /// that use it (TypeFuncHash and TypeFuncHashPointerSplit).
+  /// Name of the function containing the allocation (TypeFuncHash modes).
   std::optional<SmallString<64>> FunctionName = std::nullopt;
 };
 
 /// Calculates stable allocation token ID. Returns std::nullopt for stateful
-/// modes that are only available in the AllocToken pass, and for modes that
-/// depend on the function containing the allocation if no function name is
-/// provided.
+/// modes that are only available in the AllocToken pass, or if a required
+/// function name is missing.
 ///
 /// \param Mode The token generation mode.
 /// \param Metadata The metadata about the allocation.
diff --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp
index 0e2a0b60f49b33..74c8dc83ea072d 100644
--- a/llvm/lib/IR/Metadata.cpp
+++ b/llvm/lib/IR/Metadata.cpp
@@ -1365,7 +1365,7 @@ MDNode *MDNode::getMergedAllocTokenMetadata(const MDNode *A, const MDNode *B) {
   if (!CIA || !CIB)
     return nullptr;
 
-  // Merge the names (type or function) at operand Idx, joined with '|'.
+  // Join different names with '|'.
   LLVMContext &Ctx = A->getContext();
   auto MergeNames = [&](unsigned Idx) -> Metadata * {
     MDString *NameA = dyn_cast<MDString>(A->getOperand(Idx));
diff --git a/llvm/lib/Support/AllocToken.cpp b/llvm/lib/Support/AllocToken.cpp
index 033dbd5078914f..5a553a58cc99fa 100644
--- a/llvm/lib/Support/AllocToken.cpp
+++ b/llvm/lib/Support/AllocToken.cpp
@@ -55,18 +55,13 @@ static uint64_t getStableHash(const AllocTokenMetadata &Metadata,
   return getStableSipHash(Metadata.TypeName) % MaxTokens;
 }
 
-/// The token ID is split into bitfields: the upper bits hold the type name
-/// hash, and the lower bits hold the hash of the name of the function
-/// containing the allocation. With pointer split, the most significant bit is
-/// set for types that contain pointers. Uses Log2(MaxTokens) bits, so that the
-/// token ID is always less than MaxTokens; with few bits, the function name
-/// hash (and then the type name hash) may get no bits.
+/// Splits the Log2(MaxTokens) bits into: [pointer flag,] type name hash,
+/// function name hash. The type name hash gets the extra bit, if any.
 static uint64_t getTypeFuncHash(const AllocTokenMetadata &Metadata,
                                 uint64_t MaxTokens, bool PointerSplit) {
-  const unsigned Bits = Log2_64(MaxTokens);
-  if (Bits == 0) // MaxTokens == 1
+  if (MaxTokens == 1)
     return 0;
-  // If the number of bits is odd, the type name hash gets the extra bit.
+  const unsigned Bits = Log2_64(MaxTokens);
   const unsigned FuncBits = Bits / 2;
   unsigned TypeBits = Bits - FuncBits;
   uint64_t Token = 0;
@@ -97,8 +92,6 @@ std::optional<uint64_t> llvm::getAllocToken(AllocTokenMode Mode,
 
   case AllocTokenMode::TypeFuncHash:
   case AllocTokenMode::TypeFuncHashPointerSplit:
-    // Depends on the function containing the allocation, which may be unknown
-    // (e.g. in constant expressions).
     if (!Metadata.FunctionName)
       return std::nullopt;
     return getTypeFuncHash(Metadata, MaxTokens,
diff --git a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
index 0c87c5be4ddd66..821f567ebf10a5 100644
--- a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
@@ -245,8 +245,7 @@ class TypeFuncHashMode : public TypeHashMode {
       remarkNoMetadata(CB, ORE);
       return ClFallbackToken;
     }
-    // Metadata without function name was generated by another mode, and the
-    // module may already contain tokens computed by that mode.
+    // Generated for another mode, whose tokens may already be in the module.
     if (N->getNumOperands() != 3) {
       CB.getContext().emitError(
           &CB, "!alloc_token without function name is incompatible with mode " +
diff --git a/llvm/lib/Transforms/Utils/InlineFunction.cpp b/llvm/lib/Transforms/Utils/InlineFunction.cpp
index f5651c893f44d0..ba5e208573bf7d 100644
--- a/llvm/lib/Transforms/Utils/InlineFunction.cpp
+++ b/llvm/lib/Transforms/Utils/InlineFunction.cpp
@@ -985,8 +985,8 @@ propagateAllocTokenMetadata(Function *CalledFunc, CallBase &CB,
     if (InlinedFunctionInfo.isSimplified(OrigCall, ClonedCall))
       continue;
     // Fill missing only: never overwrite a more specific token the wrapper
-    // already set on an internal allocation. With a function name, an empty
-    // type name denotes an unknown type, which is not more specific.
+    // already set on an internal allocation. An unknown type (empty type name
+    // with function name) is not more specific.
     if (MDNode *MD = ClonedCall->getMetadata(LLVMContext::MD_alloc_token)) {
       if (MD->getNumOperands() != 3 ||
           !cast<MDString>(MD->getOperand(0))->getString().empty())
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll b/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
index d1911c5865b4ae..e910cc4ed989d2 100644
--- a/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash-errors.ll
@@ -1,7 +1,5 @@
-; Test errors in the typefunchash modes.
-;
-; Metadata without function name may come from bitcode compiled with another
-; mode, which may already contain token IDs computed by that mode.
+; Test that metadata without function name (generated for another mode) is
+; rejected in the typefunchash modes.
 ; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -disable-output 2>&1 | FileCheck %s --check-prefix=NOFUNC
 ; RUN: not opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -disable-output 2>&1 | FileCheck %s --check-prefix=NOFUNC-SPLIT
 
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash.ll b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
index 695b240fb369a8..283e91505654ba 100644
--- a/llvm/test/Instrumentation/AllocToken/typefunchash.ll
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
@@ -1,8 +1,5 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
-; Test the typefunchash modes: the token ID consists of the type name hash in the
-; upper bits and the function name hash in the lower bits. With pointer split,
-; the most significant bit is set for types that contain pointers. Small
-; alloc-token-max values leave fewer (or no) bits for the hashes.
+; Test the typefunchash modes, including small alloc-token-max values.
 ;
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=HASH
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=SPLIT

>From 93fe0d8edc7925258f0086b2ffff14a21fcc37b7 Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Wed, 30 Sep 2026 11:37:10 +0000
Subject: [PATCH 05/13] update comments

---
 llvm/docs/LangRef.md                                 | 3 +--
 llvm/include/llvm/Support/AllocToken.h               | 4 ++--
 llvm/lib/Support/AllocToken.cpp                      | 2 +-
 llvm/lib/Transforms/Instrumentation/AllocToken.cpp   | 2 ++
 llvm/test/Instrumentation/AllocToken/typefunchash.ll | 4 +++-
 5 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/llvm/docs/LangRef.md b/llvm/docs/LangRef.md
index 0e3812b45df2ca..537c20aef5869e 100644
--- a/llvm/docs/LangRef.md
+++ b/llvm/docs/LangRef.md
@@ -9185,8 +9185,7 @@ instrument such calls with allocation token IDs.
 
 The metadata contains: string with the type of an allocation, and a boolean
 denoting if the type contains a pointer. Optionally, it contains a string with
-the name of the function containing the allocation, in which case an empty type
-name denotes an unknown type.
+the name of the function containing the allocation.
 
 ```
 call ptr @malloc(i64 64), !alloc_token !0
diff --git a/llvm/include/llvm/Support/AllocToken.h b/llvm/include/llvm/Support/AllocToken.h
index 7455b4b08fc53b..cefb3f349c1d20 100644
--- a/llvm/include/llvm/Support/AllocToken.h
+++ b/llvm/include/llvm/Support/AllocToken.h
@@ -65,8 +65,8 @@ struct AllocTokenMetadata {
 };
 
 /// Calculates stable allocation token ID. Returns std::nullopt for stateful
-/// modes that are only available in the AllocToken pass, or if a required
-/// function name is missing.
+/// modes that are only available in the AllocToken pass, and for TypeFuncHash
+/// modes if FunctionName is not set.
 ///
 /// \param Mode The token generation mode.
 /// \param Metadata The metadata about the allocation.
diff --git a/llvm/lib/Support/AllocToken.cpp b/llvm/lib/Support/AllocToken.cpp
index 5a553a58cc99fa..e86ffdbe9bd6d5 100644
--- a/llvm/lib/Support/AllocToken.cpp
+++ b/llvm/lib/Support/AllocToken.cpp
@@ -56,7 +56,7 @@ static uint64_t getStableHash(const AllocTokenMetadata &Metadata,
 }
 
 /// Splits the Log2(MaxTokens) bits into: [pointer flag,] type name hash,
-/// function name hash. The type name hash gets the extra bit, if any.
+/// function name hash. The function name hash gets Log2(MaxTokens) / 2 bits.
 static uint64_t getTypeFuncHash(const AllocTokenMetadata &Metadata,
                                 uint64_t MaxTokens, bool PointerSplit) {
   if (MaxTokens == 1)
diff --git a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
index 821f567ebf10a5..1ab8d88f7454ee 100644
--- a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
@@ -255,6 +255,8 @@ class TypeFuncHashMode : public TypeHashMode {
     AllocTokenMetadata Metadata{cast<MDString>(N->getOperand(0))->getString(),
                                 containsPointer(N),
                                 cast<MDString>(N->getOperand(2))->getString()};
+    if (Metadata.TypeName.empty())
+      remarkNoMetadata(CB, ORE);
     return *getAllocToken(Mode, Metadata, MaxTokens);
   }
 
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash.ll b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
index 283e91505654ba..6c2b8814f613b8 100644
--- a/llvm/test/Instrumentation/AllocToken/typefunchash.ll
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
@@ -1,7 +1,7 @@
 ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
 ; Test the typefunchash modes, including small alloc-token-max values.
 ;
-; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=HASH
+; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -alloc-token-max=256 -pass-remarks=alloc-token -S 2>&1 | FileCheck %s --check-prefix=HASH
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=SPLIT
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -S | FileCheck %s --check-prefix=SPLIT-DEFAULT
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=2 -S | FileCheck %s --check-prefix=SPLIT-MAX2
@@ -12,6 +12,8 @@ target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16
 declare ptr @malloc(i64)
 declare i64 @llvm.alloc.token.id.i64(metadata)
 
+; HASH: remark: <unknown>:0:0: Call to 'malloc' in 'test_typefunchash' without source-level type token
+
 define void @test_typefunchash() sanitize_alloc_token {
 ; HASH-LABEL: define void @test_typefunchash(
 ; HASH-SAME: ) #[[ATTR2:[0-9]+]] {

>From 7115221cc53feb636c7659a0ebcc56becf376bad Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Wed, 30 Sep 2026 23:14:48 +0900
Subject: [PATCH 06/13] diagnose null metadata and clarify id space

---
 llvm/include/llvm/Support/AllocToken.h | 5 +++--
 llvm/lib/IR/Verifier.cpp               | 5 +++--
 llvm/lib/Support/AllocToken.cpp        | 6 ++++--
 3 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/llvm/include/llvm/Support/AllocToken.h b/llvm/include/llvm/Support/AllocToken.h
index cefb3f349c1d20..8caa47897271e1 100644
--- a/llvm/include/llvm/Support/AllocToken.h
+++ b/llvm/include/llvm/Support/AllocToken.h
@@ -37,10 +37,11 @@ enum class AllocTokenMode {
   TypeHashPointerSplit,
 
   /// Token ID based on allocated type hash and containing function name hash.
+  /// The ID space is rounded down to the largest power of two <= MaxTokens.
   TypeFuncHash,
 
-  /// Like TypeFuncHash, but the top half ID-space is reserved for types that
-  /// contain pointers.
+  /// Like TypeFuncHash, but for MaxTokens > 1 the most significant bit of the
+  /// rounded-down ID space is set for types that contain pointers.
   TypeFuncHashPointerSplit,
 };
 
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 0c035dc96af7f4..d7380e5883f514 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -5804,11 +5804,12 @@ void Verifier::visitAllocTokenMetadata(Instruction &I, MDNode *MD) {
   Check(isa<CallBase>(I), "!alloc_token should only exist on calls", &I);
   Check(MD->getNumOperands() == 2 || MD->getNumOperands() == 3,
         "!alloc_token must have 2 or 3 operands", MD);
-  Check(isa<MDString>(MD->getOperand(0)), "expected string", MD);
+  Check(isa_and_nonnull<MDString>(MD->getOperand(0)), "expected string", MD);
   Check(mdconst::dyn_extract_or_null<ConstantInt>(MD->getOperand(1)),
         "expected integer constant", MD);
   if (MD->getNumOperands() == 3)
-    Check(isa<MDString>(MD->getOperand(2)), "expected string", MD);
+    Check(isa_and_nonnull<MDString>(MD->getOperand(2)),
+          "expected function name string", MD);
 }
 
 void Verifier::visitInlineHistoryMetadata(Instruction &I, MDNode *MD) {
diff --git a/llvm/lib/Support/AllocToken.cpp b/llvm/lib/Support/AllocToken.cpp
index e86ffdbe9bd6d5..c72e9e83008f41 100644
--- a/llvm/lib/Support/AllocToken.cpp
+++ b/llvm/lib/Support/AllocToken.cpp
@@ -55,8 +55,10 @@ static uint64_t getStableHash(const AllocTokenMetadata &Metadata,
   return getStableSipHash(Metadata.TypeName) % MaxTokens;
 }
 
-/// Splits the Log2(MaxTokens) bits into: [pointer flag,] type name hash,
-/// function name hash. The function name hash gets Log2(MaxTokens) / 2 bits.
+/// Rounds the ID space down to the largest power of two <= MaxTokens, then
+/// splits its floor(log2(MaxTokens)) bits into: [pointer flag,] type name hash,
+/// function name hash. The function name hash gets floor(log2(MaxTokens)) / 2
+/// bits. With pointer split, the MSB of this rounded-down space is the flag.
 static uint64_t getTypeFuncHash(const AllocTokenMetadata &Metadata,
                                 uint64_t MaxTokens, bool PointerSplit) {
   if (MaxTokens == 1)

>From 6277586e03ce6792f12f2a0dbe234a87aca87191 Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Thu, 1 Oct 2026 16:22:13 +0000
Subject: [PATCH 07/13] Assert function name operand is MDString

---
 llvm/lib/Transforms/Instrumentation/AllocToken.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
index 1ab8d88f7454ee..5cc97a439ffe84 100644
--- a/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AllocToken.cpp
@@ -123,6 +123,7 @@ MDNode *getAllocTokenMetadata(const CallBase &CB) {
          "bad !alloc_token");
   assert(isa<MDString>(Ret->getOperand(0)));
   assert(isa<ConstantAsMetadata>(Ret->getOperand(1)));
+  assert(Ret->getNumOperands() == 2 || isa<MDString>(Ret->getOperand(2)));
   return Ret;
 }
 

>From f9d2e20946cec00b9f544152355b9b544d8743c2 Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Thu, 1 Oct 2026 16:25:21 +0000
Subject: [PATCH 08/13] Keep unknown type when merging

---
 llvm/lib/IR/Metadata.cpp                      | 11 ++++++--
 .../SimplifyCFG/merge-calls-alloc-token.ll    | 25 +++++++++++++++++++
 2 files changed, 34 insertions(+), 2 deletions(-)

diff --git a/llvm/lib/IR/Metadata.cpp b/llvm/lib/IR/Metadata.cpp
index 74c8dc83ea072d..ef370015b0f1b4 100644
--- a/llvm/lib/IR/Metadata.cpp
+++ b/llvm/lib/IR/Metadata.cpp
@@ -1368,12 +1368,19 @@ MDNode *MDNode::getMergedAllocTokenMetadata(const MDNode *A, const MDNode *B) {
   // Join different names with '|'.
   LLVMContext &Ctx = A->getContext();
   auto MergeNames = [&](unsigned Idx) -> Metadata * {
-    MDString *NameA = dyn_cast<MDString>(A->getOperand(Idx));
-    MDString *NameB = dyn_cast<MDString>(B->getOperand(Idx));
+    auto *NameA = dyn_cast_or_null<MDString>(A->getOperand(Idx));
+    auto *NameB = dyn_cast_or_null<MDString>(B->getOperand(Idx));
     if (!NameA || !NameB)
       return nullptr;
     if (NameA == NameB)
       return NameA;
+    // An empty string denotes an unknown type, which must be preserved.
+    if (Idx == 0) {
+      if (NameA->getString().empty())
+        return NameA;
+      if (NameB->getString().empty())
+        return NameB;
+    }
     return MDString::get(Ctx,
                          (NameA->getString() + "|" + NameB->getString()).str());
   };
diff --git a/llvm/test/Transforms/SimplifyCFG/merge-calls-alloc-token.ll b/llvm/test/Transforms/SimplifyCFG/merge-calls-alloc-token.ll
index a024613f869b86..cfefe4657e74c1 100644
--- a/llvm/test/Transforms/SimplifyCFG/merge-calls-alloc-token.ll
+++ b/llvm/test/Transforms/SimplifyCFG/merge-calls-alloc-token.ll
@@ -143,15 +143,40 @@ if.end:
   ret ptr %x.0
 }
 
+define ptr @test_merge_alloc_token_func_name_unknown(i1 %b) {
+; CHECK-LABEL: define ptr @test_merge_alloc_token_func_name_unknown(
+; CHECK-SAME: i1 [[B:%.*]]) {
+; CHECK-NEXT:  [[ENTRY:.*:]]
+; CHECK-NEXT:    [[CALL:%.*]] = call ptr @_Znwm(i64 4), !alloc_token [[META4:![0-9]+]]
+; CHECK-NEXT:    ret ptr [[CALL]]
+;
+entry:
+  br i1 %b, label %if.then, label %if.else
+
+if.then:
+  %call = call ptr @_Znwm(i64 4), !alloc_token !4
+  br label %if.end
+
+if.else:
+  %call1 = call ptr @_Znwm(i64 4), !alloc_token !6
+  br label %if.end
+
+if.end:
+  %x.0 = phi ptr [ %call, %if.then ], [ %call1, %if.else ]
+  ret ptr %x.0
+}
+
 !0 = !{!"int", i1 0}
 !1 = !{!"char[4]", i1 0}
 !2 = !{!"StructA", i1 1}
 !3 = !{!"StructB", i1 1}
 !4 = !{!"int", i1 0, !"foo"}
 !5 = !{!"char[4]", i1 1, !"bar"}
+!6 = !{!"", i1 0, !"foo"}
 ;.
 ; CHECK: [[META0]] = !{!"int", i1 false}
 ; CHECK: [[META1]] = !{!"int|char[4]", i1 false}
 ; CHECK: [[META2]] = !{!"StructA|StructB", i1 true}
 ; CHECK: [[META3]] = !{!"int|char[4]", i1 true, !"foo|bar"}
+; CHECK: [[META4]] = !{!"", i1 false, !"foo"}
 ;.

>From 6caab1a4c7281f0d60bbe583c42d11635a31f00c Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Thu, 1 Oct 2026 16:35:08 +0000
Subject: [PATCH 09/13] Use all bits when MaxTokens is 2^k-1

---
 llvm/include/llvm/Support/AllocToken.h        |  5 +--
 llvm/lib/Support/AllocToken.cpp               | 11 ++++---
 .../AllocToken/typefunchash.ll                | 33 +++++++++++++++----
 3 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/llvm/include/llvm/Support/AllocToken.h b/llvm/include/llvm/Support/AllocToken.h
index 8caa47897271e1..134c93320a1610 100644
--- a/llvm/include/llvm/Support/AllocToken.h
+++ b/llvm/include/llvm/Support/AllocToken.h
@@ -37,11 +37,12 @@ enum class AllocTokenMode {
   TypeHashPointerSplit,
 
   /// Token ID based on allocated type hash and containing function name hash.
-  /// The ID space is rounded down to the largest power of two <= MaxTokens.
+  /// Uses k bits if MaxTokens is 2^k-1 (e.g. SIZE_MAX), or
+  /// floor(log2(MaxTokens)) bits otherwise.
   TypeFuncHash,
 
   /// Like TypeFuncHash, but for MaxTokens > 1 the most significant bit of the
-  /// rounded-down ID space is set for types that contain pointers.
+  /// ID space is set for types that contain pointers.
   TypeFuncHashPointerSplit,
 };
 
diff --git a/llvm/lib/Support/AllocToken.cpp b/llvm/lib/Support/AllocToken.cpp
index c72e9e83008f41..1d2644c670502b 100644
--- a/llvm/lib/Support/AllocToken.cpp
+++ b/llvm/lib/Support/AllocToken.cpp
@@ -55,15 +55,16 @@ static uint64_t getStableHash(const AllocTokenMetadata &Metadata,
   return getStableSipHash(Metadata.TypeName) % MaxTokens;
 }
 
-/// Rounds the ID space down to the largest power of two <= MaxTokens, then
-/// splits its floor(log2(MaxTokens)) bits into: [pointer flag,] type name hash,
-/// function name hash. The function name hash gets floor(log2(MaxTokens)) / 2
-/// bits. With pointer split, the MSB of this rounded-down space is the flag.
+/// Splits the Bits bits into: [pointer flag,] type name hash, function name
+/// hash. The function name hash gets Bits / 2 bits. Bits is k if MaxTokens is
+/// 2^k-1 (e.g. SIZE_MAX, tokens in [0, MaxTokens]), or Log2(MaxTokens)
+/// otherwise. With pointer split, the MSB is the pointer flag.
 static uint64_t getTypeFuncHash(const AllocTokenMetadata &Metadata,
                                 uint64_t MaxTokens, bool PointerSplit) {
   if (MaxTokens == 1)
     return 0;
-  const unsigned Bits = Log2_64(MaxTokens);
+  const unsigned Bits =
+      isMask_64(MaxTokens) ? llvm::countr_one(MaxTokens) : Log2_64(MaxTokens);
   const unsigned FuncBits = Bits / 2;
   unsigned TypeBits = Bits - FuncBits;
   uint64_t Token = 0;
diff --git a/llvm/test/Instrumentation/AllocToken/typefunchash.ll b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
index 6c2b8814f613b8..4c92e250d0f896 100644
--- a/llvm/test/Instrumentation/AllocToken/typefunchash.ll
+++ b/llvm/test/Instrumentation/AllocToken/typefunchash.ll
@@ -4,6 +4,7 @@
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchash>' -alloc-token-max=256 -pass-remarks=alloc-token -S 2>&1 | FileCheck %s --check-prefix=HASH
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=256 -S | FileCheck %s --check-prefix=SPLIT
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -S | FileCheck %s --check-prefix=SPLIT-DEFAULT
+; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=255 -S | FileCheck %s --check-prefix=SPLIT-MAX255
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=2 -S | FileCheck %s --check-prefix=SPLIT-MAX2
 ; RUN: opt < %s -passes='inferattrs,alloc-token<mode=typefunchashpointersplit>' -alloc-token-max=1 -S | FileCheck %s --check-prefix=SPLIT-MAX1
 
@@ -38,13 +39,23 @@ define void @test_typefunchash() sanitize_alloc_token {
 ; SPLIT-DEFAULT-LABEL: define void @test_typefunchash(
 ; SPLIT-DEFAULT-SAME: ) #[[ATTR2:[0-9]+]] {
 ; SPLIT-DEFAULT-NEXT:  [[ENTRY:.*:]]
-; SPLIT-DEFAULT-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 3043217739499725513), !alloc_token [[META0:![0-9]+]]
-; SPLIT-DEFAULT-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 6229638898919432905), !alloc_token [[META1:![0-9]+]]
-; SPLIT-DEFAULT-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 3043217741120676426), !alloc_token [[META2:![0-9]+]]
-; SPLIT-DEFAULT-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 286024393), !alloc_token [[META3:![0-9]+]]
-; SPLIT-DEFAULT-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 3043217739384275185), !alloc_token [[META4:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435480860910281), !alloc_token [[META0:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 -5987466274009226551), !alloc_token [[META1:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435482481861194), !alloc_token [[META2:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 2433508041), !alloc_token [[META3:![0-9]+]]
+; SPLIT-DEFAULT-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 6086435478597976305), !alloc_token [[META4:![0-9]+]]
 ; SPLIT-DEFAULT-NEXT:    ret void
 ;
+; SPLIT-MAX255-LABEL: define void @test_typefunchash(
+; SPLIT-MAX255-SAME: ) #[[ATTR2:[0-9]+]] {
+; SPLIT-MAX255-NEXT:  [[ENTRY:.*:]]
+; SPLIT-MAX255-NEXT:    [[TMP0:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 41), !alloc_token [[META0:![0-9]+]]
+; SPLIT-MAX255-NEXT:    [[TMP1:%.*]] = call ptr @__alloc_token_malloc(i64 8, i64 217), !alloc_token [[META1:![0-9]+]]
+; SPLIT-MAX255-NEXT:    [[TMP2:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 42), !alloc_token [[META2:![0-9]+]]
+; SPLIT-MAX255-NEXT:    [[TMP3:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 9), !alloc_token [[META3:![0-9]+]]
+; SPLIT-MAX255-NEXT:    [[TMP4:%.*]] = call ptr @__alloc_token_malloc(i64 4, i64 33), !alloc_token [[META4:![0-9]+]]
+; SPLIT-MAX255-NEXT:    ret void
+;
 ; SPLIT-MAX2-LABEL: define void @test_typefunchash(
 ; SPLIT-MAX2-SAME: ) #[[ATTR2:[0-9]+]] {
 ; SPLIT-MAX2-NEXT:  [[ENTRY:.*:]]
@@ -85,7 +96,11 @@ define i64 @test_intrinsic_lowering() {
 ;
 ; SPLIT-DEFAULT-LABEL: define i64 @test_intrinsic_lowering() {
 ; SPLIT-DEFAULT-NEXT:  [[ENTRY:.*:]]
-; SPLIT-DEFAULT-NEXT:    ret i64 6229638898919432905
+; SPLIT-DEFAULT-NEXT:    ret i64 -5987466274009226551
+;
+; SPLIT-MAX255-LABEL: define i64 @test_intrinsic_lowering() {
+; SPLIT-MAX255-NEXT:  [[ENTRY:.*:]]
+; SPLIT-MAX255-NEXT:    ret i64 217
 ;
 ; SPLIT-MAX2-LABEL: define i64 @test_intrinsic_lowering() {
 ; SPLIT-MAX2-NEXT:  [[ENTRY:.*:]]
@@ -129,6 +144,12 @@ entry:
 ; SPLIT-DEFAULT: [[META3]] = !{!"", i1 false, !"foo"}
 ; SPLIT-DEFAULT: [[META4]] = !{!"int", i1 false, !""}
 ;.
+; SPLIT-MAX255: [[META0]] = !{!"int", i1 false, !"foo"}
+; SPLIT-MAX255: [[META1]] = !{!"int*", i1 true, !"foo"}
+; SPLIT-MAX255: [[META2]] = !{!"int", i1 false, !"bar"}
+; SPLIT-MAX255: [[META3]] = !{!"", i1 false, !"foo"}
+; SPLIT-MAX255: [[META4]] = !{!"int", i1 false, !""}
+;.
 ; SPLIT-MAX2: [[META0]] = !{!"int", i1 false, !"foo"}
 ; SPLIT-MAX2: [[META1]] = !{!"int*", i1 true, !"foo"}
 ; SPLIT-MAX2: [[META2]] = !{!"int", i1 false, !"bar"}

>From 6bfc846aaeda123cc16a76e9bdb699b58b1eab7b Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Tue, 29 Sep 2026 02:05:48 +0000
Subject: [PATCH 10/13] [Clang][AllocToken] Emit function names in TypeFuncHash
 modes

In the TypeFuncHash and TypeFuncHashPointerSplit modes, always emit
!alloc_token metadata, with the qualified name of the function containing
the allocation as the third operand. If the allocated type cannot be
inferred, emit an empty type name so that the token is still derived from
the function name.

__builtin_infer_alloc_token() is not supported in constant expressions in
these modes.
---
 clang/docs/AllocToken.md                      | 13 +++-
 .../include/clang/Basic/DiagnosticASTKinds.td |  2 +-
 clang/lib/CodeGen/CGExpr.cpp                  | 35 +++++++--
 clang/test/CodeGen/alloc-token-inline.c       |  5 ++
 .../CodeGenCXX/alloc-token-typefunchash.cpp   | 72 +++++++++++++++++++
 .../test/SemaCXX/alloc-token-typefunchash.cpp | 14 ++++
 6 files changed, 131 insertions(+), 10 deletions(-)
 create mode 100644 clang/test/CodeGenCXX/alloc-token-typefunchash.cpp
 create mode 100644 clang/test/SemaCXX/alloc-token-typefunchash.cpp

diff --git a/clang/docs/AllocToken.md b/clang/docs/AllocToken.md
index 136f76bfb3111b..2d1496ba656865 100644
--- a/clang/docs/AllocToken.md
+++ b/clang/docs/AllocToken.md
@@ -34,6 +34,11 @@ change or removal. These may (experimentally) be selected with `-Xclang
 
 - `typehash`: This mode assigns a token ID based on the hash of the allocated
   type's name.
+- `typefunchash`: This mode assigns a token ID based on the hash of the
+  allocated type's name (upper half of the token ID bits) and the hash of the
+  name of the function containing the allocation (lower half).
+- `typefunchashpointersplit`: Like `typefunchash`, but the most significant bit
+  of the token ID is set for types that contain pointers.
 - `random`: This mode assigns a statically-determined random token ID to each
   allocation site.
 - `increment`: This mode assigns a simple, incrementally increasing token ID
@@ -43,7 +48,9 @@ The following command-line options affect generated token IDs:
 
 - `-falloc-token-max=<N>`
   : Configures the maximum number of token IDs. By default the number of tokens
-    is bounded by `SIZE_MAX`.
+    is bounded by `SIZE_MAX`. In the `typefunchash` and
+    `typefunchashpointersplit` modes, `N` must be at least 8, and is rounded
+    down to a power of two.
 
 ## Querying Token IDs with `__builtin_infer_alloc_token`
 
@@ -57,7 +64,9 @@ size_t __builtin_infer_alloc_token(<args>, ...);
 This builtin returns the token ID inferred from its argument expressions, which
 mirror arguments normally passed to any allocation function. The argument
 expressions are **unevaluated**, so it can be used with expressions that would
-have side effects without any runtime impact.
+have side effects without any runtime impact. The builtin cannot be used in
+constant expressions in the `increment`, `random`, `typefunchash`, and
+`typefunchashpointersplit` modes.
 
 For example, it can be used as follows:
 
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index 0aca1f75428f8a..6e770812a9298e 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -416,7 +416,7 @@ def note_constexpr_infer_alloc_token_type_inference_failed : Note<
 def note_constexpr_infer_alloc_token_no_metadata : Note<
   "could not get token metadata for inferred type">;
 def note_constexpr_infer_alloc_token_stateful_mode
-    : Note<"stateful alloc token mode not supported in constexpr">;
+    : Note<"alloc token mode not supported in constexpr">;
 
 def warn_attribute_needs_aggregate : Warning<
   "%0 attribute is ignored in non-aggregate type %1">,
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index b1d8c4295e99a4..02fddd6ea2fbe5 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1348,7 +1348,27 @@ void CodeGenFunction::EmitBoundsCheckImpl(const Expr *ArrayExpr,
 }
 
 llvm::MDNode *CodeGenFunction::buildAllocToken(QualType AllocType) {
-  auto ATMD = infer_alloc::getAllocTokenMetadata(AllocType, getContext());
+  std::optional<llvm::AllocTokenMetadata> ATMD;
+  if (!AllocType.isNull())
+    ATMD = infer_alloc::getAllocTokenMetadata(AllocType, getContext());
+
+  const llvm::AllocTokenMode Mode =
+      getLangOpts().AllocTokenMode.value_or(llvm::DefaultAllocTokenMode);
+  if (Mode == llvm::AllocTokenMode::TypeFuncHash ||
+      Mode == llvm::AllocTokenMode::TypeFuncHashPointerSplit) {
+    // An empty type name denotes an unknown type.
+    if (!ATMD)
+      ATMD = llvm::AllocTokenMetadata{{}, false};
+    // Use the function containing the allocation (for lambdas, the call
+    // operator; for blocks and captured statements, the enclosing function).
+    // Allocations outside of any function (e.g. global initializers) use "".
+    const Decl *D =
+        isa_and_nonnull<FunctionDecl>(CurCodeDecl) ? CurCodeDecl : CurFuncDecl;
+    std::string FuncName;
+    if (const auto *ND = dyn_cast_or_null<NamedDecl>(D))
+      FuncName = ND->getQualifiedNameAsString();
+    ATMD->FunctionName = FuncName;
+  }
   if (!ATMD)
     return nullptr;
 
@@ -1357,8 +1377,11 @@ llvm::MDNode *CodeGenFunction::buildAllocToken(QualType AllocType) {
   auto *ContainsPtrC = Builder.getInt1(ATMD->ContainsPointer);
   auto *ContainsPtrMD = MDB.createConstant(ContainsPtrC);
 
-  // Format: !{<type-name>, <contains-pointer>}
-  return llvm::MDNode::get(CGM.getLLVMContext(), {TypeNameMD, ContainsPtrMD});
+  // Format: !{<type-name>, <contains-pointer>[, <function-name>]}
+  SmallVector<llvm::Metadata *, 3> Ops = {TypeNameMD, ContainsPtrMD};
+  if (ATMD->FunctionName)
+    Ops.push_back(MDB.createString(*ATMD->FunctionName));
+  return llvm::MDNode::get(CGM.getLLVMContext(), Ops);
 }
 
 void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, QualType AllocType) {
@@ -1369,10 +1392,8 @@ void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, QualType AllocType) {
 }
 
 llvm::MDNode *CodeGenFunction::buildAllocToken(const CallExpr *E) {
-  QualType AllocType = infer_alloc::inferPossibleType(E, getContext(), CurCast);
-  if (!AllocType.isNull())
-    return buildAllocToken(AllocType);
-  return nullptr;
+  return buildAllocToken(
+      infer_alloc::inferPossibleType(E, getContext(), CurCast));
 }
 
 void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, const CallExpr *E) {
diff --git a/clang/test/CodeGen/alloc-token-inline.c b/clang/test/CodeGen/alloc-token-inline.c
index 04fb62b019c5d8..0b4bd0031f0fdf 100644
--- a/clang/test/CodeGen/alloc-token-inline.c
+++ b/clang/test/CodeGen/alloc-token-inline.c
@@ -1,5 +1,6 @@
 // RUN: %clang_cc1 -O -fsanitize=alloc-token -fsanitize-alloc-token-extended -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s --implicit-check-not=__alloc_token
 // RUN: %clang_cc1 -O -fsanitize=alloc-token -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s --implicit-check-not=__alloc_token
+// RUN: %clang_cc1 -O -fsanitize=alloc-token -falloc-token-mode=typefunchash -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefix=TYPEFUNC --implicit-check-not=__alloc_token
 
 typedef __typeof(sizeof(int)) size_t;
 
@@ -19,9 +20,13 @@ __attribute__((malloc, always_inline)) inline void *wrapper(size_t size) {
 // CHECK: call ptr @external_ptr_helper()
 // CHECK: call void @external_void_helper()
 // CHECK: call{{.*}} @__alloc_token_malloc(i64 noundef 4, i64 2689373973731826898){{.*}} !alloc_token [[META_INT:![0-9]+]]
+// TYPEFUNC-LABEL: @test_inlined_wrapper(
+// TYPEFUNC: call{{.*}} @__alloc_token_malloc(i64 noundef 4, i64 6086435482222324463){{.*}} !alloc_token [[TYPEFUNC_META_INT:![0-9]+]]
 void test_inlined_wrapper(void) {
   sink = wrapper(sizeof(int));
 }
 
 // CHECK: declare{{.*}} @__alloc_token_malloc(
 // CHECK: [[META_INT]] = !{!"int", i1 false}
+// TYPEFUNC: declare{{.*}} @__alloc_token_malloc(
+// TYPEFUNC: [[TYPEFUNC_META_INT]] = !{!"int", i1 false, !"test_inlined_wrapper"}
diff --git a/clang/test/CodeGenCXX/alloc-token-typefunchash.cpp b/clang/test/CodeGenCXX/alloc-token-typefunchash.cpp
new file mode 100644
index 00000000000000..657eb880179089
--- /dev/null
+++ b/clang/test/CodeGenCXX/alloc-token-typefunchash.cpp
@@ -0,0 +1,72 @@
+// Test that the typefunchash modes add the name of the function containing the
+// allocation to the !alloc_token metadata, and always emit metadata.
+//
+// RUN: %clang_cc1 -fsanitize=alloc-token -falloc-token-mode=typefunchash -triple x86_64-linux-gnu -std=c++20 -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s
+// RUN: %clang_cc1 -fsanitize=alloc-token -falloc-token-mode=typefunchashpointersplit -triple x86_64-linux-gnu -std=c++20 -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s
+
+typedef __typeof(sizeof(int)) size_t;
+extern "C" void *malloc(size_t size) __attribute__((malloc));
+
+struct WithPtr {
+  int a;
+  char *buf;
+};
+
+struct Incomplete;
+
+void *sink;
+
+// Unknown or incomplete types use an empty type name.
+// CHECK-LABEL: define {{.*}} @_Z9test_funcm(
+// CHECK: call {{.*}} @malloc(i64 noundef 4){{.*}}, !alloc_token [[META_FUNC:![0-9]+]]
+// CHECK: call {{.*}} @malloc(i64 noundef %{{.*}}){{.*}}, !alloc_token [[META_UNKNOWN:![0-9]+]]
+// CHECK: call {{.*}} @malloc(i64 noundef 4){{.*}}, !alloc_token [[META_UNKNOWN]]
+void test_func(size_t n) {
+  sink = (int *)malloc(sizeof(int));
+  sink = malloc(n);
+  sink = (Incomplete *)malloc(4);
+}
+
+namespace ns {
+struct S {
+  void *method();
+};
+// CHECK-LABEL: define {{.*}} @_ZN2ns1S6methodEv(
+// CHECK: call {{.*}} @_Znwm(i64 noundef 16){{.*}}, !alloc_token [[META_METHOD:![0-9]+]]
+void *S::method() { return new WithPtr; }
+} // namespace ns
+
+template <typename T>
+struct Tmpl {
+  static void *alloc() { return new T; }
+};
+
+// CHECK-LABEL: define {{.*}} @_ZN4TmplIlE5allocEv(
+// CHECK: call {{.*}} @_Znwm(i64 noundef 8){{.*}}, !alloc_token [[META_TMPL:![0-9]+]]
+void test_template() { sink = Tmpl<long>::alloc(); }
+
+// Lambdas use the name of their call operator.
+// CHECK-LABEL: define {{.*}} @"_ZZ11test_lambdavENK{{.*}}clEv"(
+// CHECK: call {{.*}} @_Znwm(i64 noundef 4){{.*}}, !alloc_token [[META_LAMBDA:![0-9]+]]
+void test_lambda() {
+  auto L = [] { return new int; };
+  sink = L();
+}
+
+// Runtime use of the builtin, which is not a constant expression in this mode.
+// CHECK-LABEL: define {{.*}} @_Z12test_builtinv(
+// CHECK: call i64 @llvm.alloc.token.id.i64(metadata [[META_BUILTIN:![0-9]+]])
+unsigned long test_builtin() { return __builtin_infer_alloc_token(sizeof(int)); }
+
+// Allocations outside of any function use an empty function name.
+// CHECK-LABEL: define internal void @__cxx_global_var_init(
+// CHECK: call {{.*}} @_Znwm(i64 noundef 4){{.*}}, !alloc_token [[META_GLOBAL:![0-9]+]]
+void *global = new int;
+
+// CHECK-DAG: [[META_FUNC]] = !{!"int", i1 false, !"test_func"}
+// CHECK-DAG: [[META_UNKNOWN]] = !{!"", i1 false, !"test_func"}
+// CHECK-DAG: [[META_METHOD]] = !{!"WithPtr", i1 true, !"ns::S::method"}
+// CHECK-DAG: [[META_TMPL]] = !{!"long", i1 false, !"Tmpl<long>::alloc"}
+// CHECK-DAG: [[META_LAMBDA]] = !{!"int", i1 false, !"test_lambda()::(lambda)::operator()"}
+// CHECK-DAG: [[META_BUILTIN]] = !{!"int", i1 false, !"test_builtin"}
+// CHECK-DAG: [[META_GLOBAL]] = !{!"int", i1 false, !""}
diff --git a/clang/test/SemaCXX/alloc-token-typefunchash.cpp b/clang/test/SemaCXX/alloc-token-typefunchash.cpp
new file mode 100644
index 00000000000000..50f1e73a130fc4
--- /dev/null
+++ b/clang/test/SemaCXX/alloc-token-typefunchash.cpp
@@ -0,0 +1,14 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -verify %s -falloc-token-mode=typefunchash
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -verify %s -falloc-token-mode=typefunchashpointersplit
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -verify %s -falloc-token-mode=typefunchash -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++23 -fsyntax-only -verify %s -falloc-token-mode=typefunchashpointersplit -fexperimental-new-constant-interpreter
+
+// The token ID depends on the function containing the allocation, which is only
+// known to the AllocToken pass: the builtin is not a constant expression.
+static_assert(!__builtin_constant_p(__builtin_infer_alloc_token(sizeof(int))));
+
+void test() {
+  constexpr auto token = __builtin_infer_alloc_token(sizeof(int)); // expected-error {{must be initialized by a constant expression}} \
+                                                                   // expected-note {{alloc token mode not supported in constexpr}}
+  auto runtime_token = __builtin_infer_alloc_token(sizeof(int));
+}

>From ad91dc6251f2815b38fc64fa4825c78a0bbe5063 Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Wed, 30 Sep 2026 11:47:06 +0000
Subject: [PATCH 11/13] update doc and test

---
 clang/docs/AllocToken.md                | 5 ++---
 clang/test/CodeGen/alloc-token-inline.c | 2 +-
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/clang/docs/AllocToken.md b/clang/docs/AllocToken.md
index 2d1496ba656865..bef49e93544394 100644
--- a/clang/docs/AllocToken.md
+++ b/clang/docs/AllocToken.md
@@ -38,7 +38,7 @@ change or removal. These may (experimentally) be selected with `-Xclang
   allocated type's name (upper half of the token ID bits) and the hash of the
   name of the function containing the allocation (lower half).
 - `typefunchashpointersplit`: Like `typefunchash`, but the most significant bit
-  of the token ID is set for types that contain pointers.
+  of the `log2(N)`-bit token ID is set for types that contain pointers.
 - `random`: This mode assigns a statically-determined random token ID to each
   allocation site.
 - `increment`: This mode assigns a simple, incrementally increasing token ID
@@ -49,8 +49,7 @@ The following command-line options affect generated token IDs:
 - `-falloc-token-max=<N>`
   : Configures the maximum number of token IDs. By default the number of tokens
     is bounded by `SIZE_MAX`. In the `typefunchash` and
-    `typefunchashpointersplit` modes, `N` must be at least 8, and is rounded
-    down to a power of two.
+    `typefunchashpointersplit` modes, `N` is rounded down to a power of two.
 
 ## Querying Token IDs with `__builtin_infer_alloc_token`
 
diff --git a/clang/test/CodeGen/alloc-token-inline.c b/clang/test/CodeGen/alloc-token-inline.c
index 0b4bd0031f0fdf..40cbf2db8abc7b 100644
--- a/clang/test/CodeGen/alloc-token-inline.c
+++ b/clang/test/CodeGen/alloc-token-inline.c
@@ -21,7 +21,7 @@ __attribute__((malloc, always_inline)) inline void *wrapper(size_t size) {
 // CHECK: call void @external_void_helper()
 // CHECK: call{{.*}} @__alloc_token_malloc(i64 noundef 4, i64 2689373973731826898){{.*}} !alloc_token [[META_INT:![0-9]+]]
 // TYPEFUNC-LABEL: @test_inlined_wrapper(
-// TYPEFUNC: call{{.*}} @__alloc_token_malloc(i64 noundef 4, i64 6086435482222324463){{.*}} !alloc_token [[TYPEFUNC_META_INT:![0-9]+]]
+// TYPEFUNC: call{{.*}} @__alloc_token_malloc(i64 noundef 4, i64 3043217740861139695){{.*}} !alloc_token [[TYPEFUNC_META_INT:![0-9]+]]
 void test_inlined_wrapper(void) {
   sink = wrapper(sizeof(int));
 }

>From 0e16e493465856ac3f88989caa177488e2ca01ba Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Fri, 2 Oct 2026 15:35:06 +0000
Subject: [PATCH 12/13] update doc and test

---
 clang/docs/AllocToken.md                | 6 ++++--
 clang/test/CodeGen/alloc-token-inline.c | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/clang/docs/AllocToken.md b/clang/docs/AllocToken.md
index bef49e93544394..3c77f5bcc3be9b 100644
--- a/clang/docs/AllocToken.md
+++ b/clang/docs/AllocToken.md
@@ -38,7 +38,7 @@ change or removal. These may (experimentally) be selected with `-Xclang
   allocated type's name (upper half of the token ID bits) and the hash of the
   name of the function containing the allocation (lower half).
 - `typefunchashpointersplit`: Like `typefunchash`, but the most significant bit
-  of the `log2(N)`-bit token ID is set for types that contain pointers.
+  of the token ID is set for types that contain pointers.
 - `random`: This mode assigns a statically-determined random token ID to each
   allocation site.
 - `increment`: This mode assigns a simple, incrementally increasing token ID
@@ -49,7 +49,9 @@ The following command-line options affect generated token IDs:
 - `-falloc-token-max=<N>`
   : Configures the maximum number of token IDs. By default the number of tokens
     is bounded by `SIZE_MAX`. In the `typefunchash` and
-    `typefunchashpointersplit` modes, `N` is rounded down to a power of two.
+    `typefunchashpointersplit` modes, `N` is rounded down to a power of two,
+    unless `N` is of the form `2^k-1` (e.g. the default `SIZE_MAX`), in which
+    case all `k` bits are used and token IDs are in `[0, N]`.
 
 ## Querying Token IDs with `__builtin_infer_alloc_token`
 
diff --git a/clang/test/CodeGen/alloc-token-inline.c b/clang/test/CodeGen/alloc-token-inline.c
index 40cbf2db8abc7b..0b4bd0031f0fdf 100644
--- a/clang/test/CodeGen/alloc-token-inline.c
+++ b/clang/test/CodeGen/alloc-token-inline.c
@@ -21,7 +21,7 @@ __attribute__((malloc, always_inline)) inline void *wrapper(size_t size) {
 // CHECK: call void @external_void_helper()
 // CHECK: call{{.*}} @__alloc_token_malloc(i64 noundef 4, i64 2689373973731826898){{.*}} !alloc_token [[META_INT:![0-9]+]]
 // TYPEFUNC-LABEL: @test_inlined_wrapper(
-// TYPEFUNC: call{{.*}} @__alloc_token_malloc(i64 noundef 4, i64 3043217740861139695){{.*}} !alloc_token [[TYPEFUNC_META_INT:![0-9]+]]
+// TYPEFUNC: call{{.*}} @__alloc_token_malloc(i64 noundef 4, i64 6086435482222324463){{.*}} !alloc_token [[TYPEFUNC_META_INT:![0-9]+]]
 void test_inlined_wrapper(void) {
   sink = wrapper(sizeof(int));
 }

>From 1b6ef005a3e0ead4734e5caea4044e611654519a Mon Sep 17 00:00:00 2001
From: ainozaki <ainozaki at google.com>
Date: Sat, 3 Oct 2026 13:23:31 +0000
Subject: [PATCH 13/13] update comment

---
 clang/lib/CodeGen/CGExpr.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 02fddd6ea2fbe5..ec287fcd888fb7 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1359,8 +1359,8 @@ llvm::MDNode *CodeGenFunction::buildAllocToken(QualType AllocType) {
     // An empty type name denotes an unknown type.
     if (!ATMD)
       ATMD = llvm::AllocTokenMetadata{{}, false};
-    // Use the function containing the allocation (for lambdas, the call
-    // operator; for blocks and captured statements, the enclosing function).
+    // Use the function containing the allocation. For lambdas, use the call
+    // operator. For blocks and captured statements, use the enclosing function.
     // Allocations outside of any function (e.g. global initializers) use "".
     const Decl *D =
         isa_and_nonnull<FunctionDecl>(CurCodeDecl) ? CurCodeDecl : CurFuncDecl;



More information about the cfe-commits mailing list