Skip to content

Commit 8c8f2df

Browse files
authored
[Clang][CodeGen] Implement code generation for __builtin_infer_alloc_token() (#156842)
Implement code generation for `__builtin_infer_alloc_token()`. The `AllocToken` pass is now registered to run unconditionally in the optimization pipeline. This ensures that all instances of the `llvm.alloc.token.id` intrinsic are lowered to constant token IDs, regardless of whether `-fsanitize=alloc-token` is enabled. This guarantees that the builtin always resolves to a token value, providing a consistent and reliable mechanism for compile-time token querying. This completes `__builtin_infer_alloc_token(<malloc-args>, ...)` to allow compile-time querying of the token ID, where the builtin arguments mirror those normally passed to any allocation function. The argument expressions are unevaluated operands. For type-based token modes, the same type inference logic is used as for untyped allocation calls. For example the ID that is passed to (with `-fsanitize=alloc-token`): some_malloc(sizeof(Type), ...) is equivalent to the token ID returned by __builtin_infer_alloc_token(sizeof(Type), ...) The builtin provides a mechanism to pass or compare token IDs in code that needs to be explicitly allocation token-aware (such as inside an allocator, or through wrapper macros). A more concrete demonstration of __builtin_infer_alloc_token's use is enabling type-aware Slab allocations in the Linux kernel: https://lore.kernel.org/all/20250825154505.1558444-1-elver@google.com/ Notably, any kind of allocation-call rewriting is a poor fit for the Linux kernel's kmalloc-family functions, which are macros that wrap (multiple) layers of inline and non-inline wrapper functions. Given the Linux kernel defines its own allocation APIs, the more explicit builtin gives the right level of control over where the type inference happens and the resulting token is passed.
1 parent 624d4f6 commit 8c8f2df

File tree

6 files changed

+166
-22
lines changed

6 files changed

+166
-22
lines changed

clang/docs/AllocToken.rst

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,39 @@ change or removal. These may (experimentally) be selected with ``-Xclang
4949
* ``increment``: This mode assigns a simple, incrementally increasing token ID
5050
to each allocation site.
5151

52+
The following command-line options affect generated token IDs:
53+
54+
* ``-falloc-token-max=<N>``
55+
Configures the maximum number of tokens. No max by default (tokens bounded
56+
by ``SIZE_MAX``).
57+
58+
Querying Token IDs with ``__builtin_infer_alloc_token``
59+
=======================================================
60+
61+
For use cases where the token ID must be known at compile time, Clang provides
62+
a builtin function:
63+
64+
.. code-block:: c
65+
66+
size_t __builtin_infer_alloc_token(<args>, ...);
67+
68+
This builtin returns the token ID inferred from its argument expressions, which
69+
mirror arguments normally passed to any allocation function. The argument
70+
expressions are **unevaluated**, so it can be used with expressions that would
71+
have side effects without any runtime impact.
72+
73+
For example, it can be used as follows:
74+
75+
.. code-block:: c
76+
77+
struct MyType { ... };
78+
void *__partition_alloc(size_t size, size_t partition);
79+
#define partition_alloc(...) __partition_alloc(__VA_ARGS__, __builtin_infer_alloc_token(__VA_ARGS__))
80+
81+
void foo(void) {
82+
MyType *x = partition_alloc(sizeof(*x));
83+
}
84+
5285
Allocation Token Instrumentation
5386
================================
5487

@@ -70,16 +103,6 @@ example:
70103
// Instrumented:
71104
ptr = __alloc_token_malloc(size, <token id>);
72105
73-
The following command-line options affect generated token IDs:
74-
75-
* ``-falloc-token-max=<N>``
76-
Configures the maximum number of tokens. No max by default (tokens bounded
77-
by ``SIZE_MAX``).
78-
79-
.. code-block:: console
80-
81-
% clang++ -fsanitize=alloc-token -falloc-token-max=512 example.cc
82-
83106
Runtime Interface
84107
-----------------
85108

clang/docs/ReleaseNotes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,9 @@ Non-comprehensive list of changes in this release
281281
allocator-level heap organization strategies. A feature to instrument all
282282
allocation functions with a token ID can be enabled via the
283283
``-fsanitize=alloc-token`` flag.
284+
- A builtin ``__builtin_infer_alloc_token(<args>, ...)`` is provided to allow
285+
compile-time querying of allocation token IDs, where the builtin arguments
286+
mirror those normally passed to an allocation function.
284287

285288
- Clang now rejects the invalid use of ``constexpr`` with ``auto`` and an explicit type in C. (#GH163090)
286289

clang/lib/CodeGen/BackendUtil.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -800,16 +800,6 @@ static void addSanitizers(const Triple &TargetTriple,
800800
MPM.addPass(DataFlowSanitizerPass(LangOpts.NoSanitizeFiles,
801801
PB.getVirtualFileSystemPtr()));
802802
}
803-
804-
if (LangOpts.Sanitize.has(SanitizerKind::AllocToken)) {
805-
if (Level == OptimizationLevel::O0) {
806-
// The default pass builder only infers libcall function attrs when
807-
// optimizing, so we insert it here because we need it for accurate
808-
// memory allocation function detection.
809-
MPM.addPass(InferFunctionAttrsPass());
810-
}
811-
MPM.addPass(AllocTokenPass(getAllocTokenOptions(LangOpts, CodeGenOpts)));
812-
}
813803
};
814804
if (ClSanitizeOnOptimizerEarlyEP) {
815805
PB.registerOptimizerEarlyEPCallback(
@@ -852,6 +842,23 @@ static void addSanitizers(const Triple &TargetTriple,
852842
}
853843
}
854844

845+
static void addAllocTokenPass(const Triple &TargetTriple,
846+
const CodeGenOptions &CodeGenOpts,
847+
const LangOptions &LangOpts, PassBuilder &PB) {
848+
PB.registerOptimizerLastEPCallback([&](ModulePassManager &MPM,
849+
OptimizationLevel Level,
850+
ThinOrFullLTOPhase) {
851+
if (Level == OptimizationLevel::O0 &&
852+
LangOpts.Sanitize.has(SanitizerKind::AllocToken)) {
853+
// The default pass builder only infers libcall function attrs when
854+
// optimizing, so we insert it here because we need it for accurate
855+
// memory allocation function detection with -fsanitize=alloc-token.
856+
MPM.addPass(InferFunctionAttrsPass());
857+
}
858+
MPM.addPass(AllocTokenPass(getAllocTokenOptions(LangOpts, CodeGenOpts)));
859+
});
860+
}
861+
855862
void EmitAssemblyHelper::RunOptimizationPipeline(
856863
BackendAction Action, std::unique_ptr<raw_pwrite_stream> &OS,
857864
std::unique_ptr<llvm::ToolOutputFile> &ThinLinkOS, BackendConsumer *BC) {
@@ -1106,6 +1113,7 @@ void EmitAssemblyHelper::RunOptimizationPipeline(
11061113
if (!IsThinLTOPostLink) {
11071114
addSanitizers(TargetTriple, CodeGenOpts, LangOpts, PB);
11081115
addKCFIPass(TargetTriple, LangOpts, PB);
1116+
addAllocTokenPass(TargetTriple, CodeGenOpts, LangOpts, PB);
11091117
}
11101118

11111119
if (std::optional<GCOVOptions> Options =

clang/lib/CodeGen/CGBuiltin.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4506,6 +4506,15 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
45064506
return RValue::get(AI);
45074507
}
45084508

4509+
case Builtin::BI__builtin_infer_alloc_token: {
4510+
llvm::MDNode *MDN = buildAllocToken(E);
4511+
llvm::Value *MDV = MetadataAsValue::get(getLLVMContext(), MDN);
4512+
llvm::Function *F =
4513+
CGM.getIntrinsic(llvm::Intrinsic::alloc_token_id, {IntPtrTy});
4514+
llvm::CallBase *TokenID = Builder.CreateCall(F, MDV);
4515+
return RValue::get(TokenID);
4516+
}
4517+
45094518
case Builtin::BIbzero:
45104519
case Builtin::BI__builtin_bzero: {
45114520
Address Dest = EmitPointerWithAlignment(E->getArg(0));

clang/test/CodeGen/lto-newpm-pipeline.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@
3232
// CHECK-FULL-O0-NEXT: Running pass: AlwaysInlinerPass
3333
// CHECK-FULL-O0-NEXT: Running analysis: ProfileSummaryAnalysis
3434
// CHECK-FULL-O0-NEXT: Running pass: CoroConditionalWrapper
35+
// CHECK-FULL-O0-NEXT: Running pass: AllocTokenPass
36+
// CHECK-FULL-O0-NEXT: Running analysis: OptimizationRemarkEmitterAnalysis
37+
// CHECK-FULL-O0-NEXT: Running analysis: TargetLibraryAnalysis
3538
// CHECK-FULL-O0-NEXT: Running pass: CanonicalizeAliasesPass
3639
// CHECK-FULL-O0-NEXT: Running pass: NameAnonGlobalPass
3740
// CHECK-FULL-O0-NEXT: Running pass: AnnotationRemarksPass
38-
// CHECK-FULL-O0-NEXT: Running analysis: TargetLibraryAnalysis
3941
// CHECK-FULL-O0-NEXT: Running pass: VerifierPass
4042
// CHECK-FULL-O0-NEXT: Running pass: BitcodeWriterPass
4143

@@ -46,10 +48,12 @@
4648
// CHECK-THIN-O0-NEXT: Running pass: AlwaysInlinerPass
4749
// CHECK-THIN-O0-NEXT: Running analysis: ProfileSummaryAnalysis
4850
// CHECK-THIN-O0-NEXT: Running pass: CoroConditionalWrapper
51+
// CHECK-THIN-O0-NEXT: Running pass: AllocTokenPass
52+
// CHECK-THIN-O0-NEXT: Running analysis: OptimizationRemarkEmitterAnalysis
53+
// CHECK-THIN-O0-NEXT: Running analysis: TargetLibraryAnalysis
4954
// CHECK-THIN-O0-NEXT: Running pass: CanonicalizeAliasesPass
5055
// CHECK-THIN-O0-NEXT: Running pass: NameAnonGlobalPass
5156
// CHECK-THIN-O0-NEXT: Running pass: AnnotationRemarksPass
52-
// CHECK-THIN-O0-NEXT: Running analysis: TargetLibraryAnalysis
5357
// CHECK-THIN-O0-NEXT: Running pass: VerifierPass
5458
// CHECK-THIN-O0-NEXT: Running pass: ThinLTOBitcodeWriterPass
5559

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// To test IR generation of the builtin without evaluating the LLVM intrinsic,
2+
// we set the mode to a stateful mode, which prohibits constant evaluation.
3+
// RUN: %clang_cc1 -triple x86_64-linux-gnu -Werror -std=c++20 -emit-llvm -falloc-token-mode=random -disable-llvm-passes %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-CODEGEN
4+
// RUN: %clang_cc1 -triple x86_64-linux-gnu -Werror -std=c++20 -emit-llvm -falloc-token-max=2 %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-LOWER
5+
6+
extern "C" void *my_malloc(unsigned long, unsigned long);
7+
8+
struct NoPtr {
9+
int x;
10+
long y;
11+
};
12+
13+
struct WithPtr {
14+
int a;
15+
char *buf;
16+
};
17+
18+
int unevaluated_fn();
19+
20+
// CHECK-LABEL: @_Z16test_builtin_intv(
21+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[META_INT:[0-9]+]])
22+
// CHECK-LOWER: ret i64 0
23+
unsigned long test_builtin_int() {
24+
return __builtin_infer_alloc_token(sizeof(1));
25+
}
26+
27+
// CHECK-LABEL: @_Z16test_builtin_ptrv(
28+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[META_PTR:[0-9]+]])
29+
// CHECK-LOWER: ret i64 1
30+
unsigned long test_builtin_ptr() {
31+
return __builtin_infer_alloc_token(sizeof(int *));
32+
}
33+
34+
// CHECK-LABEL: @_Z25test_builtin_struct_noptrv(
35+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[META_NOPTR:[0-9]+]])
36+
// CHECK-LOWER: ret i64 0
37+
unsigned long test_builtin_struct_noptr() {
38+
return __builtin_infer_alloc_token(sizeof(NoPtr));
39+
}
40+
41+
// CHECK-LABEL: @_Z25test_builtin_struct_w_ptrv(
42+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[META_WITHPTR:[0-9]+]])
43+
// CHECK-LOWER: ret i64 1
44+
unsigned long test_builtin_struct_w_ptr() {
45+
return __builtin_infer_alloc_token(sizeof(WithPtr), 123);
46+
}
47+
48+
// CHECK-LABEL: @_Z24test_builtin_unevaluatedv(
49+
// CHECK-NOT: call{{.*}}unevaluated_fn
50+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[META_INT:[0-9]+]])
51+
// CHECK-LOWER: ret i64 0
52+
unsigned long test_builtin_unevaluated() {
53+
return __builtin_infer_alloc_token(sizeof(int) * unevaluated_fn());
54+
}
55+
56+
// CHECK-LABEL: @_Z36test_builtin_unsequenced_unevaluatedi(
57+
// CHECK: add nsw
58+
// CHECK-NOT: add nsw
59+
// CHECK-CODEGEN: %[[REG:[0-9]+]] = call i64 @llvm.alloc.token.id.i64(metadata ![[META_UNKNOWN:[0-9]+]])
60+
// CHECK-CODEGEN: call{{.*}}@my_malloc({{.*}}, i64 noundef %[[REG]])
61+
// CHECK-LOWER: call{{.*}}@my_malloc({{.*}}, i64 noundef 0)
62+
void test_builtin_unsequenced_unevaluated(int x) {
63+
my_malloc(++x, __builtin_infer_alloc_token(++x));
64+
}
65+
66+
// CHECK-LABEL: @_Z20test_builtin_unknownv(
67+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[META_UNKNOWN:[0-9]+]])
68+
// CHECK-LOWER: ret i64 0
69+
unsigned long test_builtin_unknown() {
70+
return __builtin_infer_alloc_token(4096);
71+
}
72+
73+
// Test template instantiation.
74+
template <typename T>
75+
constexpr unsigned long get_token() {
76+
return __builtin_infer_alloc_token(sizeof(T));
77+
}
78+
79+
// CHECK-LABEL: @_Z13get_token_intv()
80+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[META_INT]])
81+
// CHECK-LOWER: ret i64 0
82+
unsigned long get_token_int() {
83+
return get_token<int>();
84+
}
85+
86+
// CHECK-LABEL: @_Z13get_token_ptrv()
87+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[META_PTR]])
88+
// CHECK-LOWER: ret i64 1
89+
unsigned long get_token_ptr() {
90+
return get_token<int *>();
91+
}
92+
93+
// CHECK-CODEGEN: ![[META_INT]] = !{!"int", i1 false}
94+
// CHECK-CODEGEN: ![[META_PTR]] = !{!"int *", i1 true}
95+
// CHECK-CODEGEN: ![[META_NOPTR]] = !{!"NoPtr", i1 false}
96+
// CHECK-CODEGEN: ![[META_WITHPTR]] = !{!"WithPtr", i1 true}
97+
// CHECK-CODEGEN: ![[META_UNKNOWN]] = !{}

0 commit comments

Comments
 (0)