Skip to content

[Clang] Add fake use emission to Clang with -fextend-lifetimes #110102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
19 changes: 16 additions & 3 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3595,15 +3595,28 @@ static llvm::StoreInst *findDominatingStoreToReturnValue(CodeGenFunction &CGF) {
llvm::BasicBlock *IP = CGF.Builder.GetInsertBlock();
if (IP->empty()) return nullptr;

// Look at directly preceding instruction, skipping bitcasts and lifetime
// markers.
// Look at directly preceding instruction, skipping bitcasts, lifetime
// markers, and fake uses and their operands.
const llvm::Instruction *LoadIntoFakeUse = nullptr;
for (llvm::Instruction &I : make_range(IP->rbegin(), IP->rend())) {
// Ignore instructions that are just loads for fake uses; the load should
// immediately precede the fake use, so we only need to remember the
// operand for the last fake use seen.
if (LoadIntoFakeUse == &I)
continue;
if (isa<llvm::BitCastInst>(&I))
continue;
if (auto *II = dyn_cast<llvm::IntrinsicInst>(&I))
if (auto *II = dyn_cast<llvm::IntrinsicInst>(&I)) {
if (II->getIntrinsicID() == llvm::Intrinsic::lifetime_end)
continue;

if (II->getIntrinsicID() == llvm::Intrinsic::fake_use) {
LoadIntoFakeUse = dyn_cast<llvm::Instruction>(II->getArgOperand(0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be nitpicking now; but we don't test that it's actually a load, right? So technically a legitimate instruction that's followed by a fake use might be ignored? (Perhaps this is a situation that never occurs).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's looking at the operand, not the preceding instruction. CodeGenFunction::EmitFakeUse generates the operand as a load, and that's the only place fake uses get created IIUC. You could have an assert here that the operand is a load.

Copy link
Contributor Author

@SLTozer SLTozer Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All fake uses at this stage have a load (which should be the immediately preceding instruction) as their operand, since fake uses are implemented as cleanups for stack variables. A check that it's a load could be added here, but if anything I'd say an assert would be preferred, since if the above fact ever changes this will need to be revisited.
Edit: Beaten to the punch! But it seems there's agreement that an assertion makes sense.

assert(isa<llvm::LoadInst>(LoadIntoFakeUse) &&
"Expected fake use operand to be a load instruction.");
continue;
}
}
return GetStoreIfValid(&I);
}
return nullptr;
Expand Down
11 changes: 9 additions & 2 deletions clang/lib/CodeGen/CGCleanup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,15 @@ void EHScopeStack::deallocate(size_t Size) {
StartOfData += llvm::alignTo(Size, ScopeStackAlignment);
}

bool EHScopeStack::containsOnlyLifetimeMarkers(
bool EHScopeStack::containsOnlyNoopCleanups(
EHScopeStack::stable_iterator Old) const {
for (EHScopeStack::iterator it = begin(); stabilize(it) != Old; it++) {
EHCleanupScope *cleanup = dyn_cast<EHCleanupScope>(&*it);
if (!cleanup || !cleanup->isLifetimeMarker())
// If this is anything other than a lifetime marker or fake use cleanup,
// then the scope stack does not contain only noop cleanups.
if (!cleanup)
return false;
if (!cleanup->isLifetimeMarker() && !cleanup->isFakeUse())
return false;
}

Expand Down Expand Up @@ -154,6 +158,7 @@ void *EHScopeStack::pushCleanup(CleanupKind Kind, size_t Size) {
bool IsNormalCleanup = Kind & NormalCleanup;
bool IsEHCleanup = Kind & EHCleanup;
bool IsLifetimeMarker = Kind & LifetimeMarker;
bool IsFakeUse = Kind & FakeUse;

// Per C++ [except.terminate], it is implementation-defined whether none,
// some, or all cleanups are called before std::terminate. Thus, when
Expand All @@ -176,6 +181,8 @@ void *EHScopeStack::pushCleanup(CleanupKind Kind, size_t Size) {
InnermostEHScope = stable_begin();
if (IsLifetimeMarker)
Scope->setLifetimeMarker();
if (IsFakeUse)
Scope->setFakeUse();

// With Windows -EHa, Invoke llvm.seh.scope.begin() for EHCleanup
// If exceptions are disabled/ignored and SEH is not in use, then there is no
Expand Down
8 changes: 8 additions & 0 deletions clang/lib/CodeGen/CGCleanup.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ class EHScope {
LLVM_PREFERRED_TYPE(bool)
unsigned IsLifetimeMarker : 1;

/// Whether this cleanup is a fake use.
LLVM_PREFERRED_TYPE(bool)
unsigned IsFakeUse : 1;

/// Whether the normal cleanup should test the activation flag.
LLVM_PREFERRED_TYPE(bool)
unsigned TestFlagInNormalCleanup : 1;
Expand Down Expand Up @@ -352,6 +356,7 @@ class alignas(8) EHCleanupScope : public EHScope {
CleanupBits.IsEHCleanup = isEH;
CleanupBits.IsActive = true;
CleanupBits.IsLifetimeMarker = false;
CleanupBits.IsFakeUse = false;
CleanupBits.TestFlagInNormalCleanup = false;
CleanupBits.TestFlagInEHCleanup = false;
CleanupBits.CleanupSize = cleanupSize;
Expand Down Expand Up @@ -384,6 +389,9 @@ class alignas(8) EHCleanupScope : public EHScope {
bool isLifetimeMarker() const { return CleanupBits.IsLifetimeMarker; }
void setLifetimeMarker() { CleanupBits.IsLifetimeMarker = true; }

bool isFakeUse() const { return CleanupBits.IsFakeUse; }
void setFakeUse() { CleanupBits.IsFakeUse = true; }

bool hasActiveFlag() const { return ActiveFlag.isValid(); }
Address getActiveFlag() const {
return ActiveFlag;
Expand Down
73 changes: 73 additions & 0 deletions clang/lib/CodeGen/CGDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,14 @@ void CodeGenFunction::EmitLifetimeEnd(llvm::Value *Size, llvm::Value *Addr) {
C->setDoesNotThrow();
}

void CodeGenFunction::EmitFakeUse(Address Addr) {
auto NL = ApplyDebugLocation::CreateEmpty(*this);
llvm::Value *V = Builder.CreateLoad(Addr, "fake.use");
llvm::CallInst *C = Builder.CreateCall(CGM.getLLVMFakeUseFn(), {V});
C->setDoesNotThrow();
C->setTailCallKind(llvm::CallInst::TCK_NoTail);
}

void CodeGenFunction::EmitAndRegisterVariableArrayDimensions(
CGDebugInfo *DI, const VarDecl &D, bool EmitDebugInfo) {
// For each dimension stores its QualType and corresponding
Expand Down Expand Up @@ -1411,6 +1419,39 @@ void CodeGenFunction::EmitAndRegisterVariableArrayDimensions(
}
}

/// Return the maximum size of an aggregate for which we generate a fake use
/// intrinsic when -fextend-lifetimes is in effect.
static uint64_t maxFakeUseAggregateSize(const ASTContext &C) {
return 4 * C.getTypeSize(C.UnsignedIntTy);
}

// Helper function to determine whether a variable's or parameter's lifetime
// should be extended.
static bool shouldExtendLifetime(const ASTContext &Context,
const Decl *FuncDecl, const VarDecl &D,
ImplicitParamDecl *CXXABIThisDecl) {
// When we're not inside a valid function it is unlikely that any
// lifetime extension is useful.
if (!FuncDecl)
return false;
if (FuncDecl->isImplicit())
return false;
// Do not extend compiler-created variables except for the this pointer.
if (D.isImplicit() && &D != CXXABIThisDecl)
return false;
QualType Ty = D.getType();
// No need to extend volatiles, they have a memory location.
if (Ty.isVolatileQualified())
return false;
// Don't extend variables that exceed a certain size.
if (Context.getTypeSize(Ty) > maxFakeUseAggregateSize(Context))
return false;
// Do not extend variables in nodebug or optnone functions.
if (FuncDecl->hasAttr<NoDebugAttr>() || FuncDecl->hasAttr<OptimizeNoneAttr>())
return false;
return true;
}

/// EmitAutoVarAlloca - Emit the alloca and debug information for a
/// local variable. Does not emit initialization or destruction.
CodeGenFunction::AutoVarEmission
Expand Down Expand Up @@ -1663,6 +1704,18 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) {
emission.getOriginalAllocatedAddress(),
emission.getSizeForLifetimeMarkers());

// Analogous to lifetime markers, we use a 'cleanup' to emit fake.use
// calls for local variables. We are exempting volatile variables and
// non-scalars larger than 4 times the size of an unsigned int. Larger
// non-scalars are often allocated in memory and may create unnecessary
// overhead.
if (CGM.getCodeGenOpts().getExtendVariableLiveness() ==
CodeGenOptions::ExtendVariableLivenessKind::All) {
if (shouldExtendLifetime(getContext(), CurCodeDecl, D, CXXABIThisDecl))
EHStack.pushCleanup<FakeUse>(NormalFakeUse,
emission.getAllocatedAddress());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the nearby EHStack cleanup code uses getOriginalAllocatedAddress -- should we be using that instead? Related comments talk about there being a difference the address and the object; dunno what to make of that, but it's worth checking.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll look into it - I'm not familiar enough to know, and a quick test indicates it's nothing obviously wrong, but I'll try to verify if it should change or not.

Copy link
Contributor Author

@SLTozer SLTozer Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the difference is about allocas in a non-default address space - essentially we'll get either:

  %i = alloca i32, align 4, addrspace(5)
  %i.ascast = addrspacecast ptr addrspace(5) %i to ptr
  store i32 %someval, ptr %i.ascast, align 4
; getAllocatedAddress
  %fake.use = load i32, ptr %i.ascast, align 4
  call void (...) @llvm.fake.use(i32 %fake.use)
; getOriginalAllocatedAddress
  %fake.use = load i32, ptr addrspace(5) %i, align 4
  call void (...) @llvm.fake.use(i32 %fake.use)

In practice, both of them result in extended variable lifetimes (the fake use will continue to track %someval in both cases), so I think this is unimportant. On the one hand, the pointer that we use to store and load is %i.ascast, so it seems consistent for the fake use to use that; on the other hand, if a #dbg_declare is created, it will refer to ptr addrspace(5) %i, so that could be more appropriate for the fake use. In summary: I don't know, and I don't think it matters. I slightly lean towards the getAllocatedAddress version.

}

return emission;
}

Expand Down Expand Up @@ -2524,6 +2577,14 @@ llvm::Function *CodeGenModule::getLLVMLifetimeEndFn() {
return LifetimeEndFn;
}

/// Lazily declare the @llvm.fake.use intrinsic.
llvm::Function *CodeGenModule::getLLVMFakeUseFn() {
if (!FakeUseFn)
FakeUseFn = llvm::Intrinsic::getDeclaration(&getModule(),
llvm::Intrinsic::fake_use);
return FakeUseFn;
}

namespace {
/// A cleanup to perform a release of an object at the end of a
/// function. This is used to balance out the incoming +1 of a
Expand Down Expand Up @@ -2717,6 +2778,18 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, ParamValue Arg,

setAddrOfLocalVar(&D, DeclPtr);

// Push a FakeUse 'cleanup' object onto the EHStack for the parameter,
// which may be the 'this' pointer. This causes the emission of a fake.use
// call with the parameter as argument at the end of the function.
if (CGM.getCodeGenOpts().getExtendVariableLiveness() ==
CodeGenOptions::ExtendVariableLivenessKind::All ||
(CGM.getCodeGenOpts().getExtendVariableLiveness() ==
CodeGenOptions::ExtendVariableLivenessKind::This &&
&D == CXXABIThisDecl)) {
if (shouldExtendLifetime(getContext(), CurCodeDecl, D, CXXABIThisDecl))
EHStack.pushCleanup<FakeUse>(NormalFakeUse, DeclPtr);
}

// Emit debug info for param declarations in non-thunk functions.
if (CGDebugInfo *DI = getDebugInfo()) {
if (CGM.getCodeGenOpts().hasReducedDebugInfo() && !CurFuncIsThunk &&
Expand Down
6 changes: 3 additions & 3 deletions clang/lib/CodeGen/CodeGenFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -403,9 +403,9 @@ void CodeGenFunction::FinishFunction(SourceLocation EndLoc) {
// important to do this before we enter the return block or return
// edges will be *really* confused.
bool HasCleanups = EHStack.stable_begin() != PrologueCleanupDepth;
bool HasOnlyLifetimeMarkers =
HasCleanups && EHStack.containsOnlyLifetimeMarkers(PrologueCleanupDepth);
bool EmitRetDbgLoc = !HasCleanups || HasOnlyLifetimeMarkers;
bool HasOnlyNoopCleanups =
HasCleanups && EHStack.containsOnlyNoopCleanups(PrologueCleanupDepth);
bool EmitRetDbgLoc = !HasCleanups || HasOnlyNoopCleanups;

std::optional<ApplyDebugLocation> OAL;
if (HasCleanups) {
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -718,6 +718,20 @@ class CodeGenFunction : public CodeGenTypeCache {
}
};

// We are using objects of this 'cleanup' class to emit fake.use calls
// for -fextend-lifetimes and -fextend-this-ptr. They are placed at the end of
// a variable's scope analogous to lifetime markers.
class FakeUse final : public EHScopeStack::Cleanup {
Address Addr;

public:
FakeUse(Address addr) : Addr(addr) {}

void Emit(CodeGenFunction &CGF, Flags flags) override {
CGF.EmitFakeUse(Addr);
}
};

/// Header for data within LifetimeExtendedCleanupStack.
struct LifetimeExtendedCleanupHeader {
/// The size of the following cleanup object.
Expand Down Expand Up @@ -4991,6 +5005,8 @@ class CodeGenFunction : public CodeGenTypeCache {

RValue EmitAtomicExpr(AtomicExpr *E);

void EmitFakeUse(Address Addr);

//===--------------------------------------------------------------------===//
// Annotations Emission
//===--------------------------------------------------------------------===//
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/CodeGen/CodeGenModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -603,6 +603,9 @@ class CodeGenModule : public CodeGenTypeCache {
/// void @llvm.lifetime.end(i64 %size, i8* nocapture <ptr>)
llvm::Function *LifetimeEndFn = nullptr;

/// void @llvm.fake.use(...)
llvm::Function *FakeUseFn = nullptr;

std::unique_ptr<SanitizerMetadata> SanitizerMD;

llvm::MapVector<const Decl *, bool> DeferredEmptyCoverageMappingDecls;
Expand Down Expand Up @@ -1280,6 +1283,7 @@ class CodeGenModule : public CodeGenTypeCache {

llvm::Function *getLLVMLifetimeStartFn();
llvm::Function *getLLVMLifetimeEndFn();
llvm::Function *getLLVMFakeUseFn();

// Make sure that this type is translated.
void UpdateCompletedType(const TagDecl *TD);
Expand Down
9 changes: 7 additions & 2 deletions clang/lib/CodeGen/EHScopeStack.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ enum CleanupKind : unsigned {

LifetimeMarker = 0x8,
NormalEHLifetimeMarker = LifetimeMarker | NormalAndEHCleanup,

// FakeUse needs to be recognized as a special cleanup similar to lifetime
// markers chiefly to be ignored in most contexts.
FakeUse = 0x10,
NormalFakeUse = FakeUse | NormalCleanup,
};

/// A stack of scopes which respond to exceptions, including cleanups
Expand Down Expand Up @@ -352,8 +357,8 @@ class EHScopeStack {
void popTerminate();

// Returns true iff the current scope is either empty or contains only
// lifetime markers, i.e. no real cleanup code
bool containsOnlyLifetimeMarkers(stable_iterator Old) const;
// noop cleanups, i.e. lifetime markers and fake uses.
bool containsOnlyNoopCleanups(stable_iterator Old) const;

/// Determines whether the exception-scopes stack is empty.
bool empty() const { return StartOfData == EndOfBuffer; }
Expand Down
34 changes: 34 additions & 0 deletions clang/test/CodeGen/extend-variable-liveness-except.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// RUN: %clang_cc1 %s -emit-llvm -fextend-variable-liveness -fcxx-exceptions -fexceptions -o - | FileCheck %s
// This test checks that the fake uses can be generated in exception handling
// blocks and that we can emit fake uses for the __int128 data type.

extern int bar();

/// Try block: fake use ends at try-block scope.
// [[BAR_VAL::%[a-zA-Z0-9\.]+]] = invoke{{.*}} i32 @_Z3barv()
// store i32 %[[BAR_VAL]], ptr [[K_ALLOC_VAL:%[a-zA-Z0-9\.]+]], align 4
// [[K_FAKE_USE:%[a-zA-Z0-9\.]+]] = load i32, ptr [[K_ALLOC_VAL]], align 4
// call void (...) @llvm.fake.use(i32 [[K_FAKE_USE]]) #2
// br label

/// Catch block: fetching the caught value...
// CHECK: [[CATCH_PTR:%[a-zA-Z0-9\.]+]] = call ptr @__cxa_begin_catch(
// CHECK: [[L_VAL:%[a-zA-Z0-9\.]+]] = load i32, ptr [[CATCH_PTR]], align 4

/// Storing to allocas...
// CHECK-DAG: store i32 8, ptr [[M_ALLOC_VAL:%[a-zA-Z0-9\.]+]]
// CHECK-DAG: store i32 [[L_VAL]], ptr [[L_ALLOC_VAL:%[a-zA-Z0-9\.]+]], align 4

/// Load into fake uses - expect M to precede L.
// CHECK: [[M_FAKE_VAL:%[a-zA-Z0-9\.]+]] = load i32, ptr [[M_ALLOC_VAL]]
// CHECK: call void (...) @llvm.fake.use(i32 [[M_FAKE_VAL]])
// CHECK: [[L_FAKE_VAL:%[a-zA-Z0-9\.]+]] = load i32, ptr [[L_ALLOC_VAL]]
// CHECK: call void (...) @llvm.fake.use(i32 [[L_FAKE_VAL]])
void foo() {
try {
int k = bar();
} catch (int l) {
/// The catch block contains a fake use for the local within its scope.
int m = 8;
}
}
11 changes: 11 additions & 0 deletions clang/test/CodeGen/extend-variable-liveness-wide-scalar.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// RUN: %clang_cc1 %s -emit-llvm -fextend-variable-liveness -triple x86_64-unknown-linux -o - | FileCheck %s
// REQUIRES: x86-registered-target
// This test checks that the fake uses can be generated in exception handling
// blocks and that we can emit fake uses for the __int128 data type.

void bar();

// CHECK: call void (...) @llvm.fake.use(i128 %
void foo(__int128 wide_int) {
bar();
}
29 changes: 29 additions & 0 deletions clang/test/CodeGen/extend-variable-liveness.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// RUN: %clang_cc1 %s -emit-llvm -fextend-variable-liveness -o - | FileCheck %s --implicit-check-not=llvm.fake.use
// Check that fake use calls are emitted at the correct locations, i.e.
// at the end of lexical blocks and at the end of the function.

int glob_i;
char glob_c;
float glob_f;

int foo(int i) {
// CHECK-LABEL: define{{.*}}foo
if (i < 4) {
char j = i * 3;
if (glob_i > 3) {
float f = glob_f;
j = f;
glob_c = j;
// CHECK: call void (...) @llvm.fake.use(float %
// CHECK-NEXT: br label %
}
glob_i = j;
// CHECK: call void (...) @llvm.fake.use(i8 %
// CHECK-NEXT: br label %
}
// CHECK: call void (...) @llvm.fake.use(i32 %
// CHECK-NEXT: ret
return 4;
}

// CHECK: declare void @llvm.fake.use(...)
20 changes: 20 additions & 0 deletions clang/test/CodeGen/fake-use-determinism.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// RUN: %clang_cc1 -emit-llvm -fextend-variable-liveness %s -o - | FileCheck %s
//
// We are checking that the fake.use calls for i, j and k appear
// in a particular order. It is not the order itself that is important
// but that it remains the same between different test runs.

// CHECK: [[K_FAKE_USE:%[a-zA-Z0-9\.]+]] = load i32, ptr %k.addr
// CHECK-NEXT: call void (...) @llvm.fake.use(i32 [[K_FAKE_USE]]) #2
// CHECK-NEXT: [[J_FAKE_USE:%[a-zA-Z0-9\.]+]] = load i32, ptr %j.addr
// CHECK-NEXT: call void (...) @llvm.fake.use(i32 [[J_FAKE_USE]]) #2
// CHECK-NEXT: [[I_FAKE_USE:%[a-zA-Z0-9\.]+]] = load i32, ptr %i.addr
// CHECK-NEXT: call void (...) @llvm.fake.use(i32 [[I_FAKE_USE]]) #2

void bar();
void foo(int i, int j, int k)
{
for (int l = 0; l < i; l++) {
bar();
}
}
Loading
Loading