Skip to content

Commit d68f428

Browse files
committed
[AllocToken, Clang] Implement __builtin_infer_alloc_token() and llvm.alloc.token.id
Implement `__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 token ID that would be 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). The builtin is always available. The implementation bridges the frontend and middle-end via a new intrinsic, `llvm.alloc.token.id`. - In Clang, reuse the existing `EmitAllocTokenHint` logic to construct an `!alloc_token` metadata node. This node is then passed as a `metadata` argument to the `llvm.alloc.token.id` intrinsic. - The `AllocToken` pass is taught to recognize and lower this intrinsic. It extracts the metadata from the intrinsic's argument and feeds it into the same token-generation logic used for instrumenting allocation calls. The intrinsic is then replaced with the resulting constant integer token ID. 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/[email protected]/ 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. Pull Request: llvm#156842
1 parent d8880cf commit d68f428

File tree

14 files changed

+324
-46
lines changed

14 files changed

+324
-46
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 ``-mllvm
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
@@ -250,6 +250,9 @@ Non-comprehensive list of changes in this release
250250
allocator-level heap organization strategies. A feature to instrument all
251251
allocation functions with a token ID can be enabled via the
252252
``-fsanitize=alloc-token`` flag.
253+
- A builtin ``__builtin_infer_alloc_token(<args>, ...)`` is provided to allow
254+
compile-time querying of allocation token IDs, where the builtin arguments
255+
mirror those normally passed to an allocation function.
253256

254257
New Compiler Flags
255258
------------------

clang/include/clang/Basic/Builtins.td

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,12 @@ def AllocaWithAlignUninitialized : Builtin {
12861286
let Prototype = "void*(size_t, _Constant size_t)";
12871287
}
12881288

1289+
def InferAllocToken : Builtin {
1290+
let Spellings = ["__builtin_infer_alloc_token"];
1291+
let Attributes = [NoThrow, Const, Pure, CustomTypeChecking, UnevaluatedArguments];
1292+
let Prototype = "size_t(...)";
1293+
}
1294+
12891295
def CallWithStaticChain : Builtin {
12901296
let Spellings = ["__builtin_call_with_static_chain"];
12911297
let Attributes = [NoThrow, CustomTypeChecking];

clang/lib/CodeGen/BackendUtil.cpp

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

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

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

clang/lib/CodeGen/CGBuiltin.cpp

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

4528+
case Builtin::BI__builtin_infer_alloc_token: {
4529+
llvm::MDNode *MDN = buildAllocToken(E);
4530+
llvm::Value *MDV = MetadataAsValue::get(getLLVMContext(), MDN);
4531+
llvm::Function *F =
4532+
CGM.getIntrinsic(llvm::Intrinsic::alloc_token_id, {IntPtrTy});
4533+
llvm::CallBase *TokenID = Builder.CreateCall(F, MDV);
4534+
return RValue::get(TokenID);
4535+
}
4536+
45284537
case Builtin::BIbzero:
45294538
case Builtin::BI__builtin_bzero: {
45304539
Address Dest = EmitPointerWithAlignment(E->getArg(0));

clang/lib/CodeGen/CGExpr.cpp

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,10 +1322,7 @@ typeContainsPointer(QualType T,
13221322
return false;
13231323
}
13241324

1325-
void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, QualType AllocType) {
1326-
assert(SanOpts.has(SanitizerKind::AllocToken) &&
1327-
"Only needed with -fsanitize=alloc-token");
1328-
1325+
llvm::MDNode *CodeGenFunction::buildAllocToken(QualType AllocType) {
13291326
llvm::MDBuilder MDB(getLLVMContext());
13301327

13311328
// Get unique type name.
@@ -1344,14 +1341,19 @@ void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, QualType AllocType) {
13441341
const bool ContainsPtr =
13451342
typeContainsPointer(AllocType, VisitedRD, IncompleteType);
13461343
if (!ContainsPtr && IncompleteType)
1347-
return;
1344+
return nullptr;
13481345
auto *ContainsPtrC = Builder.getInt1(ContainsPtr);
13491346
auto *ContainsPtrMD = MDB.createConstant(ContainsPtrC);
13501347

13511348
// Format: !{<type-name>, <contains-pointer>}
1352-
auto *MDN =
1353-
llvm::MDNode::get(CGM.getLLVMContext(), {TypeNameMD, ContainsPtrMD});
1354-
CB->setMetadata(llvm::LLVMContext::MD_alloc_token, MDN);
1349+
return llvm::MDNode::get(CGM.getLLVMContext(), {TypeNameMD, ContainsPtrMD});
1350+
}
1351+
1352+
void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, QualType AllocType) {
1353+
assert(SanOpts.has(SanitizerKind::AllocToken) &&
1354+
"Only needed with -fsanitize=alloc-token");
1355+
CB->setMetadata(llvm::LLVMContext::MD_alloc_token,
1356+
buildAllocToken(AllocType));
13551357
}
13561358

13571359
namespace {
@@ -1445,7 +1447,7 @@ QualType inferPossibleTypeFromCastExpr(const CallExpr *CallE,
14451447
}
14461448
} // end anonymous namespace
14471449

1448-
void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, const CallExpr *E) {
1450+
llvm::MDNode *CodeGenFunction::buildAllocToken(const CallExpr *E) {
14491451
QualType AllocType;
14501452
// First check arguments.
14511453
for (const Expr *Arg : E->arguments()) {
@@ -1460,7 +1462,15 @@ void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, const CallExpr *E) {
14601462
AllocType = inferPossibleTypeFromCastExpr(E, CurCast);
14611463
// Emit if we were able to infer the type.
14621464
if (!AllocType.isNull())
1463-
EmitAllocToken(CB, AllocType);
1465+
return buildAllocToken(AllocType);
1466+
return nullptr;
1467+
}
1468+
1469+
void CodeGenFunction::EmitAllocToken(llvm::CallBase *CB, const CallExpr *E) {
1470+
assert(SanOpts.has(SanitizerKind::AllocToken) &&
1471+
"Only needed with -fsanitize=alloc-token");
1472+
if (llvm::MDNode *MDN = buildAllocToken(E))
1473+
CB->setMetadata(llvm::LLVMContext::MD_alloc_token, MDN);
14641474
}
14651475

14661476
CodeGenFunction::ComplexPairTy CodeGenFunction::

clang/lib/CodeGen/CodeGenFunction.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3352,9 +3352,14 @@ class CodeGenFunction : public CodeGenTypeCache {
33523352
SanitizerAnnotateDebugInfo(ArrayRef<SanitizerKind::SanitizerOrdinal> Ordinals,
33533353
SanitizerHandler Handler);
33543354

3355-
/// Emit additional metadata used by the AllocToken instrumentation.
3355+
/// Build metadata used by the AllocToken instrumentation.
3356+
llvm::MDNode *buildAllocToken(QualType AllocType);
3357+
/// Emit and set additional metadata used by the AllocToken instrumentation.
33563358
void EmitAllocToken(llvm::CallBase *CB, QualType AllocType);
3357-
/// Emit additional metadata used by the AllocToken instrumentation,
3359+
/// Build additional metadata used by the AllocToken instrumentation,
3360+
/// inferring the type from an allocation call expression.
3361+
llvm::MDNode *buildAllocToken(const CallExpr *E);
3362+
/// Emit and set additional metadata used by the AllocToken instrumentation,
33583363
/// inferring the type from an allocation call expression.
33593364
void EmitAllocToken(llvm::CallBase *CB, const CallExpr *E);
33603365

clang/lib/Sema/SemaChecking.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1498,6 +1498,24 @@ static void builtinAllocaAddrSpace(Sema &S, CallExpr *TheCall) {
14981498
TheCall->setType(S.Context.getPointerType(RT));
14991499
}
15001500

1501+
static bool checkBuiltinInferAllocToken(Sema &S, CallExpr *TheCall) {
1502+
if (S.checkArgCountAtLeast(TheCall, 1))
1503+
return true;
1504+
1505+
for (Expr *Arg : TheCall->arguments()) {
1506+
// If argument is dependent on a template parameter, we can't resolve now.
1507+
if (Arg->isTypeDependent() || Arg->isValueDependent())
1508+
continue;
1509+
// Reject void types.
1510+
QualType ArgTy = Arg->IgnoreParenImpCasts()->getType();
1511+
if (ArgTy->isVoidType())
1512+
return S.Diag(Arg->getBeginLoc(), diag::err_param_with_void_type);
1513+
}
1514+
1515+
TheCall->setType(S.Context.UnsignedLongLongTy);
1516+
return false;
1517+
}
1518+
15011519
namespace {
15021520
enum PointerAuthOpKind {
15031521
PAO_Strip,
@@ -2779,6 +2797,10 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
27792797
builtinAllocaAddrSpace(*this, TheCall);
27802798
}
27812799
break;
2800+
case Builtin::BI__builtin_infer_alloc_token:
2801+
if (checkBuiltinInferAllocToken(*this, TheCall))
2802+
return ExprError();
2803+
break;
27822804
case Builtin::BI__arithmetic_fence:
27832805
if (BuiltinArithmeticFence(TheCall))
27842806
return ExprError();

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: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Test IR generation of the builtin without evaluating the LLVM intrinsic.
2+
// RUN: %clang_cc1 -triple x86_64-linux-gnu -Werror -std=c++20 -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s --check-prefixes=CHECK,CHECK-CODEGEN
3+
// 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
4+
5+
extern "C" void *my_malloc(unsigned long, unsigned long);
6+
7+
struct NoPtr {
8+
int x;
9+
long y;
10+
};
11+
12+
struct WithPtr {
13+
int a;
14+
char *buf;
15+
};
16+
17+
int unevaluated_fn();
18+
19+
// CHECK-LABEL: @_Z16test_builtin_intv(
20+
unsigned long test_builtin_int() {
21+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[MD_INT:[0-9]+]])
22+
// CHECK-LOWER: ret i64 0
23+
return __builtin_infer_alloc_token(sizeof(1));
24+
}
25+
26+
// CHECK-LABEL: @_Z16test_builtin_ptrv(
27+
unsigned long test_builtin_ptr() {
28+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[MD_PTR:[0-9]+]])
29+
// CHECK-LOWER: ret i64 1
30+
return __builtin_infer_alloc_token(sizeof(int *));
31+
}
32+
33+
// CHECK-LABEL: @_Z25test_builtin_struct_noptrv(
34+
unsigned long test_builtin_struct_noptr() {
35+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[MD_NOPTR:[0-9]+]])
36+
// CHECK-LOWER: ret i64 0
37+
return __builtin_infer_alloc_token(sizeof(NoPtr));
38+
}
39+
40+
// CHECK-LABEL: @_Z25test_builtin_struct_w_ptrv(
41+
unsigned long test_builtin_struct_w_ptr() {
42+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[MD_WITHPTR:[0-9]+]])
43+
// CHECK-LOWER: ret i64 1
44+
return __builtin_infer_alloc_token(sizeof(WithPtr), 123);
45+
}
46+
47+
// CHECK-LABEL: @_Z24test_builtin_unevaluatedv(
48+
unsigned long test_builtin_unevaluated() {
49+
// CHECK-NOT: call{{.*}}unevaluated_fn
50+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[MD_INT:[0-9]+]])
51+
// CHECK-LOWER: ret i64 0
52+
return __builtin_infer_alloc_token(sizeof(int) * unevaluated_fn());
53+
}
54+
55+
// CHECK-LABEL: @_Z36test_builtin_unsequenced_unevaluatedi(
56+
void test_builtin_unsequenced_unevaluated(int x) {
57+
// CHECK: add nsw
58+
// CHECK-NOT: add nsw
59+
// CHECK-CODEGEN: %[[REG:[0-9]+]] = call i64 @llvm.alloc.token.id.i64(metadata ![[MD_UNKNOWN:[0-9]+]])
60+
// CHECK-CODEGEN: call{{.*}}@my_malloc({{.*}}, i64 noundef %[[REG]])
61+
// CHECK-LOWER: call{{.*}}@my_malloc({{.*}}, i64 noundef 0)
62+
my_malloc(++x, __builtin_infer_alloc_token(++x));
63+
}
64+
65+
// CHECK-LABEL: @_Z20test_builtin_unknownv(
66+
unsigned long test_builtin_unknown() {
67+
// CHECK-CODEGEN: call i64 @llvm.alloc.token.id.i64(metadata ![[MD_UNKNOWN:[0-9]+]])
68+
// CHECK-LOWER: ret i64 0
69+
return __builtin_infer_alloc_token(4096);
70+
}
71+
72+
// CHECK-CODEGEN: ![[MD_INT]] = !{!"int", i1 false}
73+
// CHECK-CODEGEN: ![[MD_PTR]] = !{!"int *", i1 true}
74+
// CHECK-CODEGEN: ![[MD_NOPTR]] = !{!"NoPtr", i1 false}
75+
// CHECK-CODEGEN: ![[MD_WITHPTR]] = !{!"WithPtr", i1 true}
76+
// CHECK-CODEGEN: ![[MD_UNKNOWN]] = !{}

0 commit comments

Comments
 (0)