Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions llvm/include/llvm/IR/Intrinsics.td
Original file line number Diff line number Diff line change
Expand Up @@ -2853,7 +2853,15 @@ def int_ptrauth_blend :
def int_ptrauth_sign_generic :
DefaultAttrsIntrinsic<[llvm_i64_ty], [llvm_i64_ty, llvm_i64_ty], [IntrNoMem]>;

//===----------------- AllocToken Intrinsics ------------------------------===//

// Return the token ID for the given !alloc_token metadata.
def int_alloc_token_id :
DefaultAttrsIntrinsic<[llvm_anyint_ty], [llvm_metadata_ty],
[IntrNoMem, NoUndef<RetIndex>]>;

//===----------------------------------------------------------------------===//

//===------- Convergence Intrinsics ---------------------------------------===//

def int_experimental_convergence_entry
Expand Down
58 changes: 58 additions & 0 deletions llvm/include/llvm/Support/AllocToken.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//===- llvm/Support/AllocToken.h - Allocation Token Calculation -----*- C++ -*//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Definition of AllocToken modes and shared calculation of stateless token IDs.
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_SUPPORT_ALLOCTOKEN_H
#define LLVM_SUPPORT_ALLOCTOKEN_H

#include "llvm/ADT/SmallString.h"
#include <cstdint>
#include <optional>

namespace llvm {

/// Modes for generating allocation token IDs.
enum class AllocTokenMode {
/// Incrementally increasing token ID.
Increment,

/// Simple mode that returns a statically-assigned random token ID.
Random,

/// Token ID based on allocated type hash.
TypeHash,

/// Token ID based on allocated type hash, where the top half ID-space is
/// reserved for types that contain pointers and the bottom half for types
/// that do not contain pointers.
TypeHashPointerSplit,
};

/// Metadata about an allocation used to generate a token ID.
struct AllocTokenMetadata {
SmallString<64> TypeName;
bool ContainsPointer;
};

/// Calculates stable allocation token ID. Returns std::nullopt for stateful
/// modes that are only available in the AllocToken pass.
///
/// \param Mode The token generation mode.
/// \param Metadata The metadata about the allocation.
/// \param MaxTokens The maximum number of tokens (must not be 0)
/// \return The calculated allocation token ID, or std::nullopt.
std::optional<uint64_t> getAllocTokenHash(AllocTokenMode Mode,
const AllocTokenMetadata &Metadata,
uint64_t MaxTokens);

} // end namespace llvm

#endif // LLVM_SUPPORT_ALLOCTOKEN_H
46 changes: 46 additions & 0 deletions llvm/lib/Support/AllocToken.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//===- AllocToken.cpp - Allocation Token Calculation ----------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Definition of AllocToken modes and shared calculation of stateless token IDs.
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/AllocToken.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/SipHash.h"

namespace llvm {
std::optional<uint64_t> getAllocTokenHash(AllocTokenMode Mode,
const AllocTokenMetadata &Metadata,
uint64_t MaxTokens) {
assert(MaxTokens && "Must provide concrete max tokens");

switch (Mode) {
case AllocTokenMode::Increment:
case AllocTokenMode::Random:
// Stateful modes cannot be implemented as a pure function.
return std::nullopt;

case AllocTokenMode::TypeHash: {
return getStableSipHash(Metadata.TypeName) % MaxTokens;
}

case AllocTokenMode::TypeHashPointerSplit: {
if (MaxTokens == 1)
return 0;
const uint64_t HalfTokens = MaxTokens / 2;
uint64_t Hash = getStableSipHash(Metadata.TypeName) % HalfTokens;
if (Metadata.ContainsPointer)
Hash += HalfTokens;
return Hash;
}
}

llvm_unreachable("");
}
} // namespace llvm
1 change: 1 addition & 0 deletions llvm/lib/Support/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ add_llvm_component_library(LLVMSupport
AArch64BuildAttributes.cpp
ARMAttributeParser.cpp
ARMWinEH.cpp
AllocToken.cpp
Allocator.cpp
AutoConvert.cpp
Base64.cpp
Expand Down
130 changes: 78 additions & 52 deletions llvm/lib/Transforms/Instrumentation/AllocToken.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
#include "llvm/IR/InstIterator.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicInst.h"
#include "llvm/IR/Metadata.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Type.h"
#include "llvm/Support/AllocToken.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Compiler.h"
Expand All @@ -53,29 +55,12 @@
#include <variant>

using namespace llvm;
using TokenMode = AllocTokenMode;

#define DEBUG_TYPE "alloc-token"

namespace {

//===--- Constants --------------------------------------------------------===//

enum class TokenMode : unsigned {
/// Incrementally increasing token ID.
Increment = 0,

/// Simple mode that returns a statically-assigned random token ID.
Random = 1,

/// Token ID based on allocated type hash.
TypeHash = 2,

/// Token ID based on allocated type hash, where the top half ID-space is
/// reserved for types that contain pointers and the bottom half for types
/// that do not contain pointers.
TypeHashPointerSplit = 3,
};

//===--- Command-line options ---------------------------------------------===//

cl::opt<TokenMode> ClMode(
Expand Down Expand Up @@ -131,7 +116,7 @@ cl::opt<uint64_t> ClFallbackToken(

//===--- Statistics -------------------------------------------------------===//

STATISTIC(NumFunctionsInstrumented, "Functions instrumented");
STATISTIC(NumFunctionsModified, "Functions modified");
STATISTIC(NumAllocationsInstrumented, "Allocations instrumented");

//===----------------------------------------------------------------------===//
Expand All @@ -140,9 +125,19 @@ STATISTIC(NumAllocationsInstrumented, "Allocations instrumented");
///
/// Expected format is: !{<type-name>, <contains-pointer>}
MDNode *getAllocTokenMetadata(const CallBase &CB) {
MDNode *Ret = CB.getMetadata(LLVMContext::MD_alloc_token);
if (!Ret)
return nullptr;
MDNode *Ret = nullptr;
if (auto *II = dyn_cast<IntrinsicInst>(&CB);
II && II->getIntrinsicID() == Intrinsic::alloc_token_id) {
auto *MDV = cast<MetadataAsValue>(II->getArgOperand(0));
Ret = cast<MDNode>(MDV->getMetadata());
// If the intrinsic has an empty MDNode, type inference failed.
if (Ret->getNumOperands() == 0)
return nullptr;
} else {
Ret = CB.getMetadata(LLVMContext::MD_alloc_token);
if (!Ret)
return nullptr;
}
assert(Ret->getNumOperands() == 2 && "bad !alloc_token");
assert(isa<MDString>(Ret->getOperand(0)));
assert(isa<ConstantAsMetadata>(Ret->getOperand(1)));
Expand Down Expand Up @@ -206,22 +201,20 @@ class TypeHashMode : public ModeBase {
using ModeBase::ModeBase;

uint64_t operator()(const CallBase &CB, OptimizationRemarkEmitter &ORE) {
const auto [N, H] = getHash(CB, ORE);
return N ? boundedToken(H) : H;
}

protected:
std::pair<MDNode *, uint64_t> getHash(const CallBase &CB,
OptimizationRemarkEmitter &ORE) {
if (MDNode *N = getAllocTokenMetadata(CB)) {
MDString *S = cast<MDString>(N->getOperand(0));
return {N, getStableSipHash(S->getString())};
AllocTokenMetadata Metadata{S->getString(), containsPointer(N)};
if (auto Token =
getAllocTokenHash(TokenMode::TypeHash, Metadata, MaxTokens))
return *Token;
}
// Fallback.
remarkNoMetadata(CB, ORE);
return {nullptr, ClFallbackToken};
return ClFallbackToken;
}

protected:
/// Remark that there was no precise type information.
static void remarkNoMetadata(const CallBase &CB,
OptimizationRemarkEmitter &ORE) {
Expand All @@ -242,20 +235,18 @@ class TypeHashPointerSplitMode : public TypeHashMode {
using TypeHashMode::TypeHashMode;

uint64_t operator()(const CallBase &CB, OptimizationRemarkEmitter &ORE) {
if (MaxTokens == 1)
return 0;
const uint64_t HalfTokens = MaxTokens / 2;
const auto [N, H] = getHash(CB, ORE);
if (!N) {
// Pick the fallback token (ClFallbackToken), which by default is 0,
// meaning it'll fall into the pointer-less bucket. Override by setting
// -alloc-token-fallback if that is the wrong choice.
return H;
if (MDNode *N = getAllocTokenMetadata(CB)) {
MDString *S = cast<MDString>(N->getOperand(0));
AllocTokenMetadata Metadata{S->getString(), containsPointer(N)};
if (auto Token = getAllocTokenHash(TokenMode::TypeHashPointerSplit,
Metadata, MaxTokens))
return *Token;
}
uint64_t Hash = H % HalfTokens; // base hash
if (containsPointer(N))
Hash += HalfTokens;
return Hash;
// Pick the fallback token (ClFallbackToken), which by default is 0, meaning
// it'll fall into the pointer-less bucket. Override by setting
// -alloc-token-fallback if that is the wrong choice.
remarkNoMetadata(CB, ORE);
return ClFallbackToken;
}
};

Expand Down Expand Up @@ -315,6 +306,9 @@ class AllocToken {
FunctionCallee getTokenAllocFunction(const CallBase &CB, uint64_t TokenID,
LibFunc OriginalFunc);

/// Lower alloc_token_* intrinsics.
void replaceIntrinsicInst(IntrinsicInst *II, OptimizationRemarkEmitter &ORE);

/// Return the token ID from metadata in the call.
uint64_t getToken(const CallBase &CB, OptimizationRemarkEmitter &ORE) {
return std::visit([&](auto &&Mode) { return Mode(CB, ORE); }, Mode);
Expand All @@ -336,21 +330,32 @@ bool AllocToken::instrumentFunction(Function &F) {
// Do not apply any instrumentation for naked functions.
if (F.hasFnAttribute(Attribute::Naked))
return false;
if (F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation))
return false;
// Don't touch available_externally functions, their actual body is elsewhere.
if (F.getLinkage() == GlobalValue::AvailableExternallyLinkage)
return false;
// Only instrument functions that have the sanitize_alloc_token attribute.
if (!F.hasFnAttribute(Attribute::SanitizeAllocToken))
return false;

auto &ORE = FAM.getResult<OptimizationRemarkEmitterAnalysis>(F);
auto &TLI = FAM.getResult<TargetLibraryAnalysis>(F);
SmallVector<std::pair<CallBase *, LibFunc>, 4> AllocCalls;
SmallVector<IntrinsicInst *, 4> IntrinsicInsts;

// Only instrument functions that have the sanitize_alloc_token attribute.
const bool InstrumentFunction =
F.hasFnAttribute(Attribute::SanitizeAllocToken) &&
!F.hasFnAttribute(Attribute::DisableSanitizerInstrumentation);

// Collect all allocation calls to avoid iterator invalidation.
for (Instruction &I : instructions(F)) {
// Collect all alloc_token_* intrinsics.
if (auto *II = dyn_cast<IntrinsicInst>(&I);
II && II->getIntrinsicID() == Intrinsic::alloc_token_id) {
IntrinsicInsts.emplace_back(II);
continue;
}

if (!InstrumentFunction)
continue;

auto *CB = dyn_cast<CallBase>(&I);
if (!CB)
continue;
Expand All @@ -359,11 +364,22 @@ bool AllocToken::instrumentFunction(Function &F) {
}

bool Modified = false;
for (auto &[CB, Func] : AllocCalls)
Modified |= replaceAllocationCall(CB, Func, ORE, TLI);

if (Modified)
NumFunctionsInstrumented++;
if (!AllocCalls.empty()) {
for (auto &[CB, Func] : AllocCalls)
Modified |= replaceAllocationCall(CB, Func, ORE, TLI);
if (Modified)
NumFunctionsModified++;
}

if (!IntrinsicInsts.empty()) {
for (auto *II : IntrinsicInsts) {
replaceIntrinsicInst(II, ORE);
}
Modified = true;
NumFunctionsModified++;
}

return Modified;
}

Expand Down Expand Up @@ -528,6 +544,16 @@ FunctionCallee AllocToken::getTokenAllocFunction(const CallBase &CB,
return TokenAlloc;
}

void AllocToken::replaceIntrinsicInst(IntrinsicInst *II,
OptimizationRemarkEmitter &ORE) {
assert(II->getIntrinsicID() == Intrinsic::alloc_token_id);

uint64_t TokenID = getToken(*II, ORE);
Value *V = ConstantInt::get(IntPtrTy, TokenID);
II->replaceAllUsesWith(V);
II->eraseFromParent();
}

} // namespace

AllocTokenPass::AllocTokenPass(AllocTokenOptions Opts)
Expand Down
32 changes: 32 additions & 0 deletions llvm/test/Instrumentation/AllocToken/intrinsic.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
; Test that the alloc-token pass lowers the intrinsic to a constant token ID.
;
; RUN: opt < %s -passes=alloc-token -alloc-token-mode=typehashpointersplit -alloc-token-max=2 -S | FileCheck %s

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

declare i64 @llvm.alloc.token.id.i64(metadata)

define i64 @test_intrinsic_lowering() {
; CHECK-LABEL: define i64 @test_intrinsic_lowering() {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: ret i64 0
;
entry:
%token_no_ptr = call i64 @llvm.alloc.token.id.i64(metadata !0)
ret i64 %token_no_ptr
}

define i64 @test_intrinsic_lowering_ptr() {
; CHECK-LABEL: define i64 @test_intrinsic_lowering_ptr() {
; CHECK-NEXT: [[ENTRY:.*:]]
; CHECK-NEXT: ret i64 1
;
entry:
%token_with_ptr = call i64 @llvm.alloc.token.id.i64(metadata !1)
ret i64 %token_with_ptr
}

!0 = !{!"NoPointerType", i1 false}
!1 = !{!"PointerType", i1 true}
Loading