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

Conversation

SLTozer
Copy link
Contributor

@SLTozer SLTozer commented Sep 26, 2024

Following the previous patch which adds the "extend lifetimes" flag without (almost) any functionality, this patch adds the real feature by allowing Clang to emit fake uses. These are emitted as a new form of cleanup, set for variable addresses, which just emits a fake use intrinsic when the variable falls out of scope. The code for achieving this is simple, with most of the logic centered on determining whether to emit a fake use for a given address, and on ensuring that fake uses are ignored in a few cases.

All code originally written by @wolfy1961, while I'll be handling the review.

@SLTozer SLTozer self-assigned this Sep 26, 2024
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Sep 26, 2024
@llvmbot
Copy link
Member

llvmbot commented Sep 26, 2024

@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Stephen Tozer (SLTozer)

Changes

Following the previous patch which adds the "extend lifetimes" flag without (almost) any functionality, this patch adds the real feature by allowing Clang to emit fake uses. These are emitted as a new form of cleanup, set for variable addresses, which just emits a fake use intrinsic when the variable falls out of scope. The code for achieving this is simple, with most of the logic centered on determining whether to emit a fake use for a given address, and on ensuring that fake uses are ignored in a few cases.

All code originally written by @wolfy1961, while I'll be handling the review.


Patch is 26.63 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/110102.diff

21 Files Affected:

  • (modified) clang/lib/CodeGen/CGCall.cpp (+20-10)
  • (modified) clang/lib/CodeGen/CGCleanup.cpp (+5-2)
  • (modified) clang/lib/CodeGen/CGCleanup.h (+8)
  • (modified) clang/lib/CodeGen/CGDecl.cpp (+69)
  • (modified) clang/lib/CodeGen/CodeGenFunction.cpp (+3-3)
  • (modified) clang/lib/CodeGen/CodeGenFunction.h (+16)
  • (modified) clang/lib/CodeGen/CodeGenModule.h (+4)
  • (modified) clang/lib/CodeGen/EHScopeStack.h (+7-2)
  • (added) clang/test/CodeGen/extend-liveness1.c (+29)
  • (added) clang/test/CodeGen/extend-liveness2.cpp (+34)
  • (added) clang/test/CodeGen/fake-use-determinism.c (+18)
  • (added) clang/test/CodeGen/fake-use-lambda.cpp (+43)
  • (added) clang/test/CodeGen/fake-use-landingpad.c (+16)
  • (added) clang/test/CodeGen/fake-use-noreturn.cpp (+27)
  • (added) clang/test/CodeGen/fake-use-return-line.c (+15)
  • (added) clang/test/CodeGen/fake-use-sanitizer.cpp (+37)
  • (added) clang/test/CodeGen/fake-use-scalar.c (+22)
  • (added) clang/test/CodeGen/fake-use-small-aggs.c (+24)
  • (added) clang/test/CodeGen/fake-use-while.c (+18)
  • (added) clang/test/CodeGen/fake-use.cpp (+44)
  • (added) clang/test/CodeGen/no-fake-use-O0.cpp (+50)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 4ae981e4013e9c..13c556a4e20a13 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -3566,16 +3566,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.
-    for (llvm::Instruction &I : make_range(IP->rbegin(), IP->rend())) {
-      if (isa<llvm::BitCastInst>(&I))
-        continue;
-      if (auto *II = dyn_cast<llvm::IntrinsicInst>(&I))
-        if (II->getIntrinsicID() == llvm::Intrinsic::lifetime_end)
-          continue;
-
-      return GetStoreIfValid(&I);
+   // 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 (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;
   }
diff --git a/clang/lib/CodeGen/CGCleanup.cpp b/clang/lib/CodeGen/CGCleanup.cpp
index 5d253c92a38a81..82532e182bebbd 100644
--- a/clang/lib/CodeGen/CGCleanup.cpp
+++ b/clang/lib/CodeGen/CGCleanup.cpp
@@ -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;
   }
 
@@ -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
@@ -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
diff --git a/clang/lib/CodeGen/CGCleanup.h b/clang/lib/CodeGen/CGCleanup.h
index c73c97146abc4d..ba78e5478ac373 100644
--- a/clang/lib/CodeGen/CGCleanup.h
+++ b/clang/lib/CodeGen/CGCleanup.h
@@ -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;
@@ -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;
@@ -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;
diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp
index 563f728e29d781..7fdf18f3ca2e7b 100644
--- a/clang/lib/CodeGen/CGDecl.cpp
+++ b/clang/lib/CodeGen/CGDecl.cpp
@@ -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
@@ -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);
+}
+
+// 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
@@ -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;
 }
 
@@ -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
@@ -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 &&
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index eda96f3e352ce3..630a9c8db95fe5 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -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) {
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 3e2abbd9bc1094..1525eb0f09b6e5 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -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.
@@ -4975,6 +4989,8 @@ class CodeGenFunction : public CodeGenTypeCache {
 
   RValue EmitAtomicExpr(AtomicExpr *E);
 
+  void EmitFakeUse(Address Addr);
+
   //===--------------------------------------------------------------------===//
   //                         Annotations Emission
   //===--------------------------------------------------------------------===//
diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h
index c58bb88035ca8a..71198134f17413 100644
--- a/clang/lib/CodeGen/CodeGenModule.h
+++ b/clang/lib/CodeGen/CodeGenModule.h
@@ -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(...)
+  llvm::Function *FakeUseFn = nullptr;
+
   std::unique_ptr<SanitizerMetadata> SanitizerMD;
 
   llvm::MapVector<const Decl *, bool> DeferredEmptyCoverageMappingDecls;
@@ -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);
diff --git a/clang/lib/CodeGen/EHScopeStack.h b/clang/lib/CodeGen/EHScopeStack.h
index 0c667e80bb6d8c..ed11dc2bb05d73 100644
--- a/clang/lib/CodeGen/EHScopeStack.h
+++ b/clang/lib/CodeGen/EHScopeStack.h
@@ -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
@@ -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; }
diff --git a/clang/test/CodeGen/extend-liveness1.c b/clang/test/CodeGen/extend-liveness1.c
new file mode 100644
index 00000000000000..ef2d00eb6be312
--- /dev/null
+++ b/clang/test/CodeGen/extend-liveness1.c
@@ -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;
+}
diff --git a/clang/test/CodeGen/extend-liveness2.cpp b/clang/test/CodeGen/extend-liveness2.cpp
new file mode 100644
index 00000000000000..119c783c634806
--- /dev/null
+++ b/clang/test/CodeGen/extend-liveness2.cpp
@@ -0,0 +1,34 @@
+// RUN: %clang_cc1 %s -O2 -emit-llvm -fextend-lifetimes -fcxx-exceptions -fexceptions -o - | FileCheck %s
+// REQUIRES: x86-registered-target
+// This test checks that the fake_use concept works with exception handling and that we
+// can handle the __int128 data type.
+
+class A {
+public:
+  A(int i) : m_i(i) {}
+  void func(__int128 i128);
+
+  int m_i;
+};
+
+extern int bar();
+extern void foo();
+int glob;
+
+void A::func(__int128 i128) {
+  int j = 4;
+  try {
+    int k = bar();
+    foo();
+    // CHECK: [[SSAVAL:%[a-z0-9]*]] = invoke{{.*}}bar
+    glob = 0;
+    // CHECK: store{{.*}}glob
+    // CHECK-NEXT: call void (...) @llvm.fake.use(i32 [[SSAVAL]])
+  } catch (...) {
+    foo();
+  }
+  // CHECK-LABEL: try.cont:
+  // CHECK-DAG: call void (...) @llvm.fake.use({{.*%this}})
+  // CHECK-DAG: call void (...) @llvm.fake.use(i128 %i128.sroa.0.0.insert.insert)
+  // CHECK: ret void
+}
diff --git a/clang/test/CodeGen/fake-use-determinism.c b/clang/test/CodeGen/fake-use-determinism.c
new file mode 100644
index 00000000000000..d62efbb4efe7ee
--- /dev/null
+++ b/clang/test/CodeGen/fake-use-determinism.c
@@ -0,0 +1,18 @@
+// RUN: %clang -S -O2 -emit-llvm -fextend-lifetimes %s -o - | FileCheck %s
+// REQUIRES: asserts
+//
+// 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:       call {{.*}}void (...) @llvm.fake.use(i32 %k)
+// CHECK-NEXT:  call {{.*}}void (...) @llvm.fake.use(i32 %j)
+// CHECK-NEXT:  call {{.*}}void (...) @llvm.fake.use(i32 %i)
+
+extern void bar();
+void foo(int i, int j, int k)
+{
+   for (int l = 0; l < i; l++) {
+      bar();
+   }
+}
diff --git a/clang/test/CodeGen/fake-use-lambda.cpp b/clang/test/CodeGen/fake-use-lambda.cpp
new file mode 100644
index 00000000000000..fd4881b9bcd531
--- /dev/null
+++ b/clang/test/CodeGen/fake-use-lambda.cpp
@@ -0,0 +1,43 @@
+// RUN: %clang_cc1 %s -triple=%itanium_abi_triple -O1 -emit-llvm -fextend-lifetimes -o - | FileCheck %s
+// Make sure we don't crash compiling a lambda that is not nested in a function.
+// We also check that fake uses are properly issued in lambdas.
+
+int glob;
+
+extern int foo();
+
+struct S {
+  static const int a;
+};
+
+const int S::a = [](int b) __attribute__((noinline)) {
+  return b * foo();
+}
+(glob);
+
+int func(int param) {
+  return ([=](int lambdaparm) __attribute__((noinline))->int {
+    int lambdalocal = lambdaparm * 2;
+    return lambdalocal;
+  }(glob));
+}
+
+// We are looking for the first lambda's call operator, which should contain
+// 2 fake uses, one for 'b' and one for its 'this' pointer (in that order).
+// The mangled function name contains a $_0, followed by 'cl'.
+// This lambda is an orphaned lambda, i.e. one without lexical parent.
+//
+// CHECK:     define internal {{.+\"_Z.+\$_0.*cl.*\"}}
+// CHECK-NOT: ret
+// CHECK:     fake.use(i32
+// CHECK-NOT: ret
+// CHECK:     fake.use(ptr
+
+// The second lambda. We are looking for 3 fake uses.
+// CHECK:     define internal {{.+\"_Z.+\$_0.*cl.*\"}}
+// CHECK-NOT: ret
+// CHECK:     fake.use(i32
+// CHECK-NOT: ret
+// CHECK:     fake.use(i32
+// CHECK-NOT: ret
+// CHECK:     fake.use(ptr
diff --git a/clang/test/CodeGen/fake-use-landingpad.c b/clang/test/CodeGen/fake-use-landingpad.c
new file mode 100644
index 00000000000000..91526f29936146
--- /dev/null
+++ b/clang/test/CodeGen/fake-use-landingpad.c
@@ -0,0 +1,16 @@
+// RUN: %clang_cc1 %s -O3 -emit-llvm -fextend-lifetimes -fexceptions -o - | FileCheck %s
+
+// Check that fake uses do not mistakenly cause a landing pad to be generated when
+// exceptions are enabled.
+
+extern void bar(int);
+void foo(int p) {
+  int a = 17;
+  bar(a);
+}
+
+// CHECK:      define {{.*}} @foo
+// CHECK-NOT:  personality
+// CHECK:      entry:
+// CHECK:      llvm.fake.use
+// CHECK-NOT:  landingpad
diff --git a/clang/test/CodeGen/fake-use-noreturn.cpp b/clang/test/CodeGen/fake-use-noreturn.cpp
new file mode 100644
index 00000000000000..acfcb0ce90b5e4
--- /dev/null
+++ b/clang/test/CodeGen/fake-use-noreturn.cpp
@@ -0,0 +1,27 @@
+// RUN: %clang %s -S -emit-llvm -fextend-lifetimes -O2 -o - -fno-discard-value-names | FileCheck %s
+//
+// Check we can correctly produce fake uses for function-level variables even
+// when we have a return in a nested conditional and there is no code at the end
+// of the function.
+
+// CHECK-LABEL: define{{.*}}@_Z3fooi
+// CHECK:         call{{.*}}llvm.fake.use(i32 %i)
+// CHECK-LABEL: define{{.*}}@_ZN1C3barEi
+// CHECK-DAG:     call{{.*}}llvm.fake.use(i32 %i)
+// CHECK-DAG:     call{{.*}}llvm.fake.use({{.*}}%this)
+
+void foo(int i) {
+   while (0)
+     if (1)
+       return;
+}
+
+class C {
+  void bar(int i);
+};
+
+void C::bar(int i) {
+  while (0)
+    if (1)
+      return;
+}
diff --git a/clang/test/CodeGen/fake-use-return-line.c b/clang/test/CodeGen/fake-use-return-line.c
new file mode 100644
index 00000000000000..0c4377058d6628
--- /dev/null
+++ b/clang/test/CodeGen/fake-use-return-line.c
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -emit-llvm -O2 -debug-info-kind=limited -fextend-lifetimes -o - %s | FileCheck %s
+
+// Clang adjusts the line numbers of returns based on the line numbers of
+// dominating stores to %retval; we test that fake use intrinsics do not affect
+// this, and the return is given the correct line.
+
+// CHECK: define{{.*}}@main
+// CHECK: ret i32{{.*}}!dbg ![[MDINDEX:[0-9]*]]
+// CHECK: ![[MDINDEX]] = !DILocation(line: [[# @LINE + 5]]
+int main()
+{
+  volatile int a = 1;
+  int b = a + 2;
+  return b;
+}
diff --git a/clang/test/CodeGen/fake-use-sanitizer.cpp b/clang/test/CodeGen/fake-use-sanitizer.cpp
new file mode 100644
index 00000000000000..915924a2b31025
--- /dev/null
+++ b/clang/test/CodeGen/fake-use-san...
[truncated]

Copy link

github-actions bot commented Sep 26, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@SLTozer
Copy link
Contributor Author

SLTozer commented Oct 15, 2024

Ping

@SLTozer
Copy link
Contributor Author

SLTozer commented Nov 12, 2024

Ping - if it helps reviewers, most of the line changes in this patch are test additions (clang/lib +126,-10, clang/test +377); for the functional changes, they can divided into:

  • CGCall.cpp, where we modify findDominatingStoreToReturnValue to correctly ignore loads into fake uses,
  • CGDecl.cpp, where we determine when to create fake uses, and have the code for emitting them, and
  • All other files, which define fake uses as a no-op cleanup so that we emit the intrinsics correctly at the end of each variable's scope.

Review for any of these areas individually is useful, even if it's only one part of the patch; I can't easily break the patch down further, since this is the minimum set needed to correctly produce llvm.fake.use intrinsics correctly from Clang (along with the prior patch #110000 which adds the flag itself).

@SLTozer SLTozer requested a review from tru November 14, 2024 11:38
Copy link
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

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

Behold, I've added a bunch of pedantic comments about the tests.

I think a significant matter is that they all run an LLVM optimisation pipeline, which I believe means they cover too much of the project to be "clang" tests, they're more end-to-end or cross project tests. Potentially we can move them there; it's more preferable to test for the exact output of clang without the optimisation passes instead though.

(If there's optimisation behaviour that does need testing, it should be part of the relevant LLVM pass).

}
glob1 = j;
// CHECK: store{{.*}}glob1
// CHECK-NEXT: call void (...) @llvm.fake.use(i32 %j.
Copy link
Member

Choose a reason for hiding this comment

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

Won't %j need capturing in a variable -- IIRC on Release builds we discard the names? Also fixable with -fno-discard-value-names I suppose.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

AFAICT, for tests that invoke %clang_cc1, -fno-discard-value-names is the default - for value names to be discarded, the driver needs to pass -discard-value-names to -cc1.

@@ -0,0 +1,34 @@
// RUN: %clang_cc1 %s -O2 -emit-llvm -fextend-lifetimes -fcxx-exceptions -fexceptions -o - | FileCheck %s
// REQUIRES: x86-registered-target
Copy link
Member

Choose a reason for hiding this comment

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

Requires x86 as a target, but doesn't specify a triple?

Comment on lines 3 to 4
// This test checks that the fake_use concept works with exception handling and that we
// can handle the __int128 data type.
Copy link
Member

Choose a reason for hiding this comment

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

IMO this can be more direct: "Test that fake uses of values defined in exception blocks..." etc. "... and that we emit fake uses of int128s". The future reader adding some subtle semantic shift is going to want to know the exact behaviour demanded.

@@ -0,0 +1,18 @@
// RUN: %clang -S -O2 -emit-llvm -fextend-lifetimes %s -o - | FileCheck %s
// REQUIRES: asserts
Copy link
Member

Choose a reason for hiding this comment

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

Is this REQUIRES a proxy for needing value-names, as above?

// The mangled function name contains a $_0, followed by 'cl'.
// This lambda is an orphaned lambda, i.e. one without lexical parent.
//
// CHECK: define internal {{.+\"_Z.+\$_0.*cl.*\"}}
Copy link
Member

Choose a reason for hiding this comment

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

CHECK-LABEL, and for the other define below?

Comment on lines 15 to 26
struct A s;
volatile int vloc;
struct A v[128];
char c[33];
return 0;
Copy link
Member

Choose a reason for hiding this comment

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

Should we not be initializing these items -- for the volatile at least, there's no defined value for it at any point, so it'd hard to generate a fake use anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't need initialization to generate fake uses - adding some positive tests should give assurance within the test itself that initializing is unnecessary.

@@ -0,0 +1,24 @@
// RUN: %clang_cc1 %s -O2 -emit-llvm -fextend-lifetimes -disable-llvm-passes -o - | FileCheck %s
// Check that we generate a fake_use call for small aggregate types.
Copy link
Member

Choose a reason for hiding this comment

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

So, aggregate types that get splatted into single integers? It's worth identifying that in fake-use-scalar.c just in case someone tries to add test coverage there IMHO.

extern struct s inits();
void foo(struct s S)
{
int arr[4];
Copy link
Member

Choose a reason for hiding this comment

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

What sets this apart as an aggregate type to be fake-used, and the character array in fake-use-scalar.c?

Comment on lines 4 to 9
// CHECK-DAG: %[[FAKEUSE1:[^ ]+]] = load{{.*}} %loc,
// CHECK-DAG: call{{.*}}llvm.fake.use({{.*}}%[[FAKEUSE1]])
// CHECK-DAG: %[[FAKEUSE2:[^ ]+]] = load{{.*}} %arr,
// CHECK-DAG: call{{.*}}llvm.fake.use({{.*}}%[[FAKEUSE2]])
// CHECK-DAG: %[[FAKEUSE3:[^ ]+]] = load{{.*}} %S,
// CHECK-DAG: call{{.*}}llvm.fake.use({{.*}}%[[FAKEUSE3]])
Copy link
Member

Choose a reason for hiding this comment

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

Better to remove the -DAG part and explicitly test that the fake uses are at the end of the function?

Comment on lines 1 to 3
// RUN: %clang_cc1 %s -O2 -emit-llvm -fextend-this-ptr -o - | FileCheck %s
// Check that we generate a fake_use call with the 'this' pointer as argument.
// The call should appear after the call to bar().
Copy link
Member

Choose a reason for hiding this comment

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

Hard to see why the rest of the class information in this test is necessary? There's a whole ton of fake uses generated that aren't tested for.

IMO: this is a good test for some other things, like which of the variables get fake uses (not params but yes locals?) and how the lifetime of the locally-allocated object 'V' is handled: seemingly not because it's returned, but what would happen if it wasn't?

Copy link
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

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

Tests are looking better (and github has helpfully thrown away most of my comments). I'll look at the code too now.

Comment on lines +3 to +5
// Check we can correctly produce fake uses for function-level variables even
// when we have a return in a nested conditional and there is no code at the end
// of the function.
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 this is now O0 and disable-O0-optnone, would this not be even better spelt as just -disable-llvm-passes? IMHO that's a much more substantially direct way of signalling "none of LLVM will run".

Comment on lines +9 to +11
// This test verifies that UBSan does not produce null-checks for arguments to
// llvm.fake.use, and that fake uses are not emitted for a variable on paths
// it has not been declared.
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for regenerating this with no optimisations happening; am I right in thinking that -fsanitize=null doesn't just turn off the sanitizer, it turns on ubsan for null values or something? I see there are calls to ubsantrap being generated, and want to settle my understanding of this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Correct, it turns on sanitizer checks for null values - I was confused at first, especially since the comment previously referenced the Address Sanitizer. In short, we're simply testing that we don't try to sanitize fake uses, for obvious reasons.

Comment on lines 1 to 11
// RUN: %clang_cc1 %s -O0 -emit-llvm -fextend-this-ptr -o - | FileCheck %s \
// RUN: --implicit-check-not="call void (...) @llvm.fake.use"
// RUN: %clang_cc1 %s -O0 -emit-llvm -fextend-lifetimes -o - | FileCheck %s \
// RUN: --implicit-check-not="call void (...) @llvm.fake.use"
// RUN: %clang_cc1 %s -O1 -emit-llvm -fextend-this-ptr -o - | FileCheck -check-prefix=OPT %s
// RUN: %clang_cc1 %s -O1 -emit-llvm -fextend-lifetimes -o - | FileCheck -check-prefix=OPT %s
// RUN: %clang_cc1 %s -Os -emit-llvm -fextend-this-ptr -o - | FileCheck -check-prefix=OPT %s
// RUN: %clang_cc1 %s -Os -emit-llvm -fextend-lifetimes -o - | FileCheck -check-prefix=OPT %s
// RUN: %clang_cc1 %s -Oz -emit-llvm -fextend-this-ptr -o - | FileCheck -check-prefix=OPT %s
// RUN: %clang_cc1 %s -Oz -emit-llvm -fextend-lifetimes -o - | FileCheck -check-prefix=OPT %s
// Check that we do not generate a fake_use call when we are not optimizing.
Copy link
Member

Choose a reason for hiding this comment

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

I reckon these can all be given --disable-llvm-passes too, as what we're checking is the effect of the optimisation flag on clang, not on the actual pipeline.

Copy link
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

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

Looking good, some comments and questions.

Having to fiddle with findDominatingStoreToReturnValue to make it skip fake-uses is slightly worrying, but it also says it's a heuristic and thus probably not fundamental for correctness?

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()))
Copy link
Member

Choose a reason for hiding this comment

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

I feel like the number of inversions here makes it harder to read; how about the suggestion,

Suggested change
if (!cleanup || !(cleanup->isLifetimeMarker() || cleanup->isFakeUse()))
if (!cleanup)
return false
if (!cleanup->isLifetimeMarker() && !cleanup->isFakeUse()))
return false;

I.e. it's slightly clearer that it's a dont-dereference-null check and a is-it-not-a-noop check.

Comment on lines 1357 to 1360
// We do not emit a fake use if we want to apply optnone to this function,
// even if we might not apply it anyway due to minsize or similar attributes.
if (!CGM.getCodeGenOpts().DisableO0ImplyOptNone &&
CGM.getCodeGenOpts().OptimizationLevel == 0)
return;
Copy link
Member

Choose a reason for hiding this comment

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

Hmmmmm. It feels a bit wrong to bake this kind of high level decision this deep into the code. Would it be better to instead have the clang driver decide whether to enable fextend-lifetimes depending on the optimisation mode, then pass the flag to cc1, and not have this kind of check?

This would reduce our testing matrix in the future to separate "cc1 does fextend lifetimes correctly" from "when should fextend-lifetimes be done".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes - that's how it originally worked, and the choice being moved to here is an accidental hangover from some earlier attempts at modifications I made; specifically I tried to make it so that fake uses would only be applied if the specific function did not have optnone, but the decision-making that applies optnone to functions happens after we finalize fake uses. I'm currently considering whether it's a good idea to just enable -fextend-lifetimes at O0, in which case this behaviour can be removed outright, otherwise it could be moved back to the driver as you've suggested.

Copy link
Collaborator

Choose a reason for hiding this comment

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

There's no value to enabling -fextend-lifetimes at O0, because we only emit stack-home locations at O0, which are valid for the entire function. However, I don't remember whether that decision is a blanket decision based on opt level or per-function based on optnone. If it's based on opt level, then adding fake-uses to an optnone function (at O1 and up) might actually be the right thing to do.

Copy link
Collaborator

Choose a reason for hiding this comment

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

(FWIW - clang IRGen always emit stack-home locations, at any optimization level - it's only a question of whether we run the optimizations (& whether they fire, eg: we might run the optimization pass, but it detects a function is optnone, then it does nothing) that remove those stack homes)

Copy link
Contributor Author

@SLTozer SLTozer Dec 4, 2024

Choose a reason for hiding this comment

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

The thoughts I have are that by default, in a typical -O0 build, -fextend-lifetimes is useless. But there may be atypical circumstances that would make it useful - such as following the somewhat common pattern (in LLVM development) of compiling with -O0 -emit-llvm -disable-O0-optnone to produce unoptimized IR (except for compulsory optimizations) from clang to pass to opt or some other tool that may modify it; in these cases if a user has explicitly requested -fextend-lifetimes, it seems reasonable to me to still emit fake uses. The argument against emitting fake uses is that they have no value, but given that the flag is opt-in only I think it's fine to trust that the user isn't going to accidentally add a flag that they don't need.

Also, I just realized that my comment above had some bad wording - by "enable extend lifetimes at O0", I meant to not automatically disable the flag's effects at O0, not to turn it on by default at O0.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Objection withdrawn. Because it's opt-in, we should do what the user requested. As you say, in a normal -O0 situation all this does is increase IR size for no benefit, but it has its uses.

Copy link
Collaborator

Choose a reason for hiding this comment

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

(might be worth removing "-disable-O0-optnone" as it's a bit of a hazard for reasons like this - if folks want optimizable IR, but without optimizations applied, they'd probably be better off disabling optimizations (-disable-llvm-optzns) I think?)


// Helper function to determine whether a variable's or parameter's lifetime
// should be extended.
static bool extendLifetime(const ASTContext &Context, const Decl *FuncDecl,
Copy link
Member

Choose a reason for hiding this comment

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

IMO: this function should be named shouldExtendLifetime

@@ -1664,6 +1710,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).
Copy link
Member

Choose a reason for hiding this comment

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

16 bytes presumably, or 32 bits for unsigned int? Best to avoid being too specific IMO as it's machine dependent. (Perhaps all the world's a VAX, but we can avoid saying it in comments).

if (CGM.getCodeGenOpts().ExtendLifetimes) {
if (extendLifetime(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.

Following the previous patch which adds the "extend lifetimes" flag
without (almost) any functionality, this patch adds the real feature by
allowing Clang to emit fake uses. These are emitted as a new form of cleanup,
set for variable addresses, which just emits a fake use intrinsic when the
variable falls out of scope. The code for achieving this is simple, with most
of the logic centered on determining whether to emit a fake use for a given
address, and on ensuring that fake uses are ignored in a few cases.
…ow fake use emission with disable-O0-optnone
@SLTozer SLTozer force-pushed the implement-extend-lifetimes-flag branch from 7ce4159 to 124ada4 Compare November 29, 2024 18:56
@SLTozer
Copy link
Contributor Author

SLTozer commented Dec 12, 2024

I've removed the logic that disables this flag at O0 - this has also removed the need for -disable-O0-optnone in any tests. Following the prior patch updating this flag to be an = flag represented by an enum, the latest commit changes this patch to handle that type. Finally, I've addressed some minor points - renaming extendLifetimes=>shouldExtendLifetimes, and making a vaguely-principled choice of getAllocatedAddress() vs getOriginalAllocatedAddress() (see my comment above).

@SLTozer
Copy link
Contributor Author

SLTozer commented Jan 9, 2025

Post-holiday ping; I think all the outstanding comments have been addressed, with some minor changes since the last round of review - primarily referencing the new CodeGenOpts settings, and removing the O0-related logic.

@SLTozer
Copy link
Contributor Author

SLTozer commented Jan 23, 2025

Ping

Copy link
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

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

LGTM with an inline question that's probably me misunderstanding something.

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.

@@ -87,6 +87,10 @@ class EHScope {
LLVM_PREFERRED_TYPE(bool)
unsigned IsLifetimeMarker : 1;

/// Whether this cleanup is a fake use
Copy link
Collaborator

Choose a reason for hiding this comment

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

Full-stop at the end

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
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.

SLTozer added a commit that referenced this pull request Jan 28, 2025
Following the previous patch which adds the "extend lifetimes" flag
without (almost) any functionality, this patch adds the real feature by
allowing Clang to emit fake uses. These are emitted as a new form of cleanup,
set for variable addresses, which just emits a fake use intrinsic when the
variable falls out of scope. The code for achieving this is simple, with most
of the logic centered on determining whether to emit a fake use for a given
address, and on ensuring that fake uses are ignored in a few cases.

Co-authored-by: Stephen Tozer <[email protected]>
@SLTozer
Copy link
Contributor Author

SLTozer commented Jan 28, 2025

As with the previous commit #110000, this has been merged in 4424c44c, via command line so as to provide proper commit author attribution to @wolfy1961 (as this doesn't seem to be possible via the web interface).

I'm currently looking at a couple buildbot errors; one build failure on several sanitizer bots due to using a deprecated function (trivial update should fix), one test failure on a clang-armv8 bot that at-a-glance looks like the test might be assuming things about the output that aren't true for all arches.

SLTozer added a commit that referenced this pull request Jan 28, 2025
Fixes two buildbot errors caused by 4424c44 (#110102):

The first error, seen on some sanitizer bots:
https://lab.llvm.org/buildbot/#/builders/51/builds/9901

The initial commit used the deprecated getDeclaration intrinsic instead
of the non-deprecated getOrInsert- equivalent. This patch trivially
updates the code in question to use the new intrinsic.

The second error, seen on the clang-armv8-quick bot:
https://lab.llvm.org/buildbot/#/builders/154/builds/10983

One of the tests depends on a particular triple to get the exact output
expected by the test, but did not specify this triple; this patch adds
the triple in question.
@SLTozer
Copy link
Contributor Author

SLTozer commented Jan 28, 2025

Pushed up a fix-forward commit, 8ad9e1e, that should fix the above issues; if issues still remain, I'll revert and recommit after fixing offline.

@SLTozer
Copy link
Contributor Author

SLTozer commented Jan 28, 2025

Looks like the fix commit is passing everywhere, marking this one closed by 4424c44c and 8ad9e1e.

@SLTozer SLTozer closed this Jan 28, 2025
@gulfemsavrun
Copy link
Contributor

This recently added tests failed on Windows like extend-variable-liveness-except.cpp, and 8ad9e1e did not fix this issue. Could you please revert while investigating?

FAIL: Clang :: CodeGen/extend-variable-liveness-except.cpp (7227 of 21842)
******************** TEST 'Clang :: CodeGen/extend-variable-liveness-except.cpp' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\b\s\w\ir\x\w\llvm_build\bin\clang.exe -cc1 -internal-isystem C:\b\s\w\ir\x\w\llvm_build\lib\clang\20\include -nostdsysteminc C:\b\s\w\ir\x\w\llvm-llvm-project\clang\test\CodeGen\extend-variable-liveness-except.cpp -emit-llvm -fextend-variable-liveness -fcxx-exceptions -fexceptions -o - | c:\b\s\w\ir\x\w\llvm_build\bin\filecheck.exe C:\b\s\w\ir\x\w\llvm-llvm-project\clang\test\CodeGen\extend-variable-liveness-except.cpp
# executed command: 'c:\b\s\w\ir\x\w\llvm_build\bin\clang.exe' -cc1 -internal-isystem 'C:\b\s\w\ir\x\w\llvm_build\lib\clang\20\include' -nostdsysteminc 'C:\b\s\w\ir\x\w\llvm-llvm-project\clang\test\CodeGen\extend-variable-liveness-except.cpp' -emit-llvm -fextend-variable-liveness -fcxx-exceptions -fexceptions -o -
# executed command: 'c:\b\s\w\ir\x\w\llvm_build\bin\filecheck.exe' 'C:\b\s\w\ir\x\w\llvm-llvm-project\clang\test\CodeGen\extend-variable-liveness-except.cpp'
# .---command stderr------------
# | C:\b\s\w\ir\x\w\llvm-llvm-project\clang\test\CodeGen\extend-variable-liveness-except.cpp:15:11: error: CHECK: expected string not found in input
# | // CHECK: [[CATCH_PTR:%[a-zA-Z0-9\.]+]] = call ptr @__cxa_begin_catch(
# |           ^
# | <stdin>:1:1: note: scanning from here
# | ; ModuleID = 'C:\b\s\w\ir\x\w\llvm-llvm-project\clang\test\CodeGen\extend-variable-liveness-except.cpp'
# | ^
# | <stdin>:25:1: note: possible intended match here
# | catch: ; preds = %catch.dispatch
# | ^
# | 
# | Input file: <stdin>
# | Check file: C:\b\s\w\ir\x\w\llvm-llvm-project\clang\test\CodeGen\extend-variable-liveness-except.cpp
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |             1: ; ModuleID = 'C:\b\s\w\ir\x\w\llvm-llvm-project\clang\test\CodeGen\extend-variable-liveness-except.cpp' 
# | check:15'0     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |             2: source_filename = "C:\\b\\s\\w\\ir\\x\\w\\llvm-llvm-project\\clang\\test\\CodeGen\\extend-variable-liveness-except.cpp" 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             3: target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128" 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             4: target triple = "x86_64-pc-windows-msvc" 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             5:  
# | check:15'0     ~
# |             6: %rtti.TypeDescriptor2 = type { ptr, ptr, [3 x i8] } 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             .
# |             .
# |             .
# |            20:  to label %invoke.cont unwind label %catch.dispatch 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            21:  
# | check:15'0     ~
# |            22: catch.dispatch: ; preds = %entry 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            23:  %0 = catchswitch within none [label %catch] unwind to caller 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            24:  
# | check:15'0     ~
# |            25: catch: ; preds = %catch.dispatch 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | check:15'1     ?                                 possible intended match
# |            26:  %1 = catchpad within %0 [ptr @"??_R0H@8", i32 0, ptr %l] 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            27:  store i32 8, ptr %m, align 4 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            28:  %fake.use1 = load i32, ptr %m, align 4 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            29:  notail call void (...) @llvm.fake.use(i32 %fake.use1) #2 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            30:  catchret from %1 to label %catchret.dest 
# | check:15'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |             .
# |             .
# |             .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

https://luci-milo.appspot.com/ui/p/fuchsia/builders/toolchain.ci/clang-windows-x64/b8724497761651154417/overview

@gulfemsavrun gulfemsavrun reopened this Jan 28, 2025
@lizhengxing
Copy link
Contributor

It's a name mangling issue on Windows (or maybe other OS).

For example, the check // CHECK-LABEL: define{{.*}}**_ZN1C4testEi**(ptr{{[^,]*}} %this, i32{{.*}} %p) in fake-use-this.cpp expects the string contains _ZN1C4testEi.

However, the compiler on Windows outputs define dso_local noundef zeroext i1 @"?test@C@@QEAA_NH@Z"

@SLTozer
Copy link
Contributor Author

SLTozer commented Jan 28, 2025

I think the forward fix has been identified, and importantly it will take a bit longer to revert than to submit the fix (adding triples to the tests), because there are 4 commits that need to be reverted and I need to run a quick build to ensure that nothing is broken by the revert itself - I'm running that build right now, but will push the fix commit up while that build is running since that might fix the problem sooner.

SLTozer added a commit that referenced this pull request Jan 28, 2025
Attempted fix for errors observed on:
https://luci-milo.appspot.com/ui/p/fuchsia/builders/toolchain.ci/clang-windows-x64/b8724497761651154417/overview

Several tests that required an explicit triple had none present; this
patch adds those triples.
@bogner
Copy link
Contributor

bogner commented Jan 28, 2025

I think the forward fix has been identified, and importantly it will take a bit longer to revert than to submit the fix (adding triples to the tests), because there are 4 commits that need to be reverted and I need to run a quick build to ensure that nothing is broken by the revert itself - I'm running that build right now, but will push the fix commit up while that build is running since that might fix the problem sooner.

Commented on the commit but I'm not sure if that sends notifications. FWIW I think you can use %itanium_abi_triple for this kind of thing rather than an entirely explicit triple

@SLTozer
Copy link
Contributor Author

SLTozer commented Jan 28, 2025

Commented on the commit but I'm not sure if that sends notifications.

I saw it, thanks - I'll likely update the tests afterwards, though for now I'm keeping an eye on the bots/merge to make sure things turn/stay green.

SLTozer added a commit that referenced this pull request Jan 28, 2025
Attempted fix for errors observed on:
https://luci-milo.appspot.com/ui/p/fuchsia/builders/toolchain.ci/clang-windows-x64/b8724466517233166081/overview

Following the previous fixes, one test that needed an explicit triple
was missed; this commit adds that triple.
SLTozer added a commit to SLTozer/llvm-project that referenced this pull request Jan 28, 2025
@SLTozer
Copy link
Contributor Author

SLTozer commented Jan 28, 2025

I made a mistake and missed one of the tests in the fix-forward commit - the build with the fix commit only recently finished, so I've pushed up a second fix now - since the other tests were fixed by this, I expect the last one should be as well. I can't continue to monitor this review right now, and there are now a number of small patches stacked on top of this one - in case there's some further error that requires a revert, the commit here is a clean and (locally) green revert of this and all dependent patches, to save other users the trouble of manually unpicking them.

@gulfemsavrun
Copy link
Contributor

I made a mistake and missed one of the tests in the fix-forward commit - the build with the fix commit only recently finished, so I've pushed up a second fix now - since the other tests were fixed by this, I expect the last one should be as well. I can't continue to monitor this review right now, and there are now a number of small patches stacked on top of this one - in case there's some further error that requires a revert, the commit here is a clean and (locally) green revert of this and all dependent patches, to save other users the trouble of manually unpicking them.

Thanks for the fix, and I verified that it fixes the test failures on Windows.

@SLTozer SLTozer closed this Jan 29, 2025
bherrera pushed a commit to misttech/integration that referenced this pull request Feb 4, 2025
llvm/llvm-project#110102 added
new tests that are broken in Windows. This patch disables
those tests on Windows as a workaround to fix the toolchain
builders.

Original-Bug: 392954079
Original-Reviewed-on: https://fuchsia-review.googlesource.com/c/infra/recipes/+/1193839
Original-Revision: 45e0deaa8ba8298493595c2fb6a01432f5b2938d
GitOrigin-RevId: b9d00d6bc0b342c12f45324ae9df990ead7f8853
Change-Id: I9638e767e6adf5cbcc487ac32e9cef0478818a51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:codegen IR generation bugs: mangling, exceptions, etc. clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants