Skip to content

[ExtendLifetimes] Add extend lifetimes to emit fake uses from clang #106724

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 2 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
10 changes: 10 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,16 @@ New Compiler Flags
only for thread-local variables, and none (which corresponds to the
existing ``-fno-c++-static-destructors`` flag) skips all static
destructors registration.
- The ``-fextend-lifetimes`` and ``-fextend-this-ptr`` flags have been added to
allow for improved debugging of optimized code. Using ``-fextend-lifetimes``
will cause Clang to generate code that tries to preserve the lifetimes of
source variables, meaning that variables will typically be visible in a
debugger more often. The ``-fextend-this-ptr`` flag has the same behaviour,
but applies only to the ``this`` variable in C++ class member functions. Note
that this flag modifies the optimizations that Clang performs, which may
result in reduced performance in generated code. Also, for performance
reasons, we do not extend the lifetimes of variables of types that are larger
than ``4 * sizeof(unsigned int)``.

Deprecated Compiler Flags
-------------------------
Expand Down
6 changes: 6 additions & 0 deletions clang/include/clang/Basic/CodeGenOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,12 @@ CODEGENOPT(EnableTLSDESC, 1, 0)
/// Bit size of immediate TLS offsets (0 == use the default).
VALUE_CODEGENOPT(TLSSize, 8, 0)

/// Whether to extend the live range of the `this` pointer.
CODEGENOPT(ExtendThisPtr, 1, 0)

/// Whether to extend the live ranges of all local variables.
CODEGENOPT(ExtendLifetimes, 1, 0)

/// The default stack protector guard offset to use.
VALUE_CODEGENOPT(StackProtectorGuardOffset, 32, INT_MAX)

Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -4243,6 +4243,15 @@ def stack_usage_file : Separate<["-"], "stack-usage-file">,
Visibility<[CC1Option]>,
HelpText<"Filename (or -) to write stack usage output to">,
MarshallingInfoString<CodeGenOpts<"StackUsageOutput">>;
def fextend_this_ptr : Flag <["-"], "fextend-this-ptr">, Group<f_Group>,
MarshallingInfoFlag<CodeGenOpts<"ExtendThisPtr">>,
HelpText<"Extend the lifetime of the 'this' pointer to improve visibility "
"in optimized debugging">, Visibility<[ClangOption, CC1Option]>;
def fextend_lifetimes : Flag <["-"], "fextend-lifetimes">, Group<f_Group>,
MarshallingInfoFlag<CodeGenOpts<"ExtendLifetimes">>,
HelpText<"Extend the lifetimes of local variables and parameters to improve "
"visibility in optimized debugging">,
Visibility<[ClangOption, CC1Option]>;

defm unique_basic_block_section_names : BoolFOption<"unique-basic-block-section-names",
CodeGenOpts<"UniqueBasicBlockSectionNames">, DefaultFalse,
Expand Down
21 changes: 18 additions & 3 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2543,6 +2543,10 @@ void CodeGenModule::ConstructAttributeList(StringRef Name,
if (shouldDisableTailCalls())
FuncAttrs.addAttribute("disable-tail-calls", "true");

// Suppress the machine instruction scheduler when -fextend-lifetimes is on.
if (CodeGenOpts.ExtendLifetimes || CodeGenOpts.ExtendThisPtr)
FuncAttrs.addAttribute(llvm::Attribute::OptimizeForDebugging);

// CPU/feature overrides. addDefaultFunctionDefinitionAttributes
// handles these separately to set them based on the global defaults.
GetCPUAndFeaturesAttributes(CalleeInfo.getCalleeDecl(), FuncAttrs);
Expand Down Expand Up @@ -3558,15 +3562,26 @@ 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));
continue;
}
}
return GetStoreIfValid(&I);
}
return nullptr;
Expand Down
7 changes: 5 additions & 2 deletions clang/lib/CodeGen/CGCleanup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,11 @@ 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 (!cleanup || !(cleanup->isLifetimeMarker() || cleanup->isFakeUse()))
return false;
}

Expand Down Expand Up @@ -154,6 +154,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 +177,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
7 changes: 7 additions & 0 deletions clang/lib/CodeGen/CGCleanup.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ class EHScope {
LLVM_PREFERRED_TYPE(bool)
unsigned IsLifetimeMarker : 1;

/// Whether this cleanup is a fake use
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 +355,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 +388,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
69 changes: 69 additions & 0 deletions clang/lib/CodeGen/CGDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1353,6 +1353,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 @@ -1412,6 +1420,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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This limit should be documented in the associated release note.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call - the release note also ought to be part of this patch imo.

}

// Helper function to determine whether a variable's or parameter's lifetime
// should be extended.
static bool extendLifetime(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 functions.
if (FuncDecl->hasAttr<NoDebugAttr>())
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 @@ -1664,6 +1705,17 @@ 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 (32 bytes).
// Larger non-scalars are often allocated in memory and may create unnecessary
// overhead.
if (CGM.getCodeGenOpts().ExtendLifetimes) {
if (extendLifetime(getContext(), CurCodeDecl, D, CXXABIThisDecl))
EHStack.pushCleanup<FakeUse>(NormalFakeUse,
emission.getAllocatedAddress());
}

return emission;
}

Expand Down Expand Up @@ -2523,6 +2575,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 @@ -2716,6 +2776,15 @@ 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().ExtendLifetimes ||
(CGM.getCodeGenOpts().ExtendThisPtr && &D == CXXABIThisDecl)) {
if (extendLifetime(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 @@ -4966,6 +4980,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 @@ -601,6 +601,9 @@ class CodeGenModule : public CodeGenTypeCache {
/// void @llvm.lifetime.end(i64 %size, i8* nocapture <ptr>)
llvm::Function *LifetimeEndFn = nullptr;

/// void @llvm.fake.use(i8* nocapture <ptr>)
llvm::Function *FakeUseFn = nullptr;

std::unique_ptr<SanitizerMetadata> SanitizerMD;

llvm::MapVector<const Decl *, bool> DeferredEmptyCoverageMappingDecls;
Expand Down Expand Up @@ -1268,6 +1271,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
5 changes: 5 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7581,6 +7581,11 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
if (Args.hasArg(options::OPT_fretain_comments_from_system_headers))
CmdArgs.push_back("-fretain-comments-from-system-headers");

if (Args.hasArg(options::OPT_fextend_this_ptr))
CmdArgs.push_back("-fextend-this-ptr");
if (Args.hasArg(options::OPT_fextend_lifetimes))
CmdArgs.push_back("-fextend-lifetimes");

// Forward -fcomment-block-commands to -cc1.
Args.AddAllArgs(CmdArgs, options::OPT_fcomment_block_commands);
// Forward -fparse-all-comments to -cc1.
Expand Down
5 changes: 5 additions & 0 deletions clang/lib/Frontend/CompilerInvocation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2219,6 +2219,11 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
Args.getAllArgValues(OPT_fsanitize_trap_EQ), Diags,
Opts.SanitizeTrap);

Opts.ExtendThisPtr =
Opts.OptimizationLevel > 0 && Args.hasArg(OPT_fextend_this_ptr);
Opts.ExtendLifetimes =
Opts.OptimizationLevel > 0 && Args.hasArg(OPT_fextend_lifetimes);

Opts.EmitVersionIdentMetadata = Args.hasFlag(OPT_Qy, OPT_Qn, true);

if (!LangOpts->CUDAIsDevice)
Expand Down
8 changes: 8 additions & 0 deletions clang/test/CodeGen/extend-lifetimes-optdebug.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// RUN: %clang_cc1 %s -emit-llvm -O2 -fextend-lifetimes -o - | FileCheck %s

// Emit the function attribute disable-post-ra when
// -fextend-lifetimes is on.

// CHECK: attributes #0 = {{{.*}}optdebug

void foo() {}
29 changes: 29 additions & 0 deletions clang/test/CodeGen/extend-liveness1.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// RUN: %clang_cc1 %s -O2 -emit-llvm -fextend-lifetimes -o - | FileCheck %s
// 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.

extern int use(int);
int glob1;
int glob2;
float globf;

int foo(int i) {
// CHECK: define{{.*}}foo
if (i < 4) {
int j = i * 3;
if (glob1 > 3) {
float f = globf;
// CHECK: [[SSAVAL:%[a-z0-9]*]] = load float{{.*}}globf
j = f;
glob2 = j;
// CHECK: store{{.*}}glob2
// CHECK-NEXT: call void (...) @llvm.fake.use(float [[SSAVAL]])
}
glob1 = j;
// CHECK: store{{.*}}glob1
// CHECK-NEXT: call void (...) @llvm.fake.use(i32 %j.
}
// CHECK: call void (...) @llvm.fake.use(i32 %i)
// CHECK-NEXT: ret
return 4;
}
Loading
Loading