Skip to content

[Asan] Skip pre-split coroutine and noop coroutine frame #99415

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

Merged
merged 4 commits into from
Jul 22, 2024

Conversation

apolloww
Copy link
Contributor

@apolloww apolloww commented Jul 18, 2024

CoroSplit expects the second parameter of llvm.coro.id to be the promise alloca. Applying Asan on a pre-split coroutine breaks this assumption and causes split to fail. This should be NFC because asan pass happens late in the pipeline where all coroutines are split. This is to prevent crash in case the order of passes are switched.

Also NoopCoro.Frame.Const is a special coroutine frame that does nothing when resumed or destroyed. There is no point to do instrumentation on it.

@llvmbot
Copy link
Member

llvmbot commented Jul 18, 2024

@llvm/pr-subscribers-coroutines
@llvm/pr-subscribers-llvm-transforms

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Wei Wang (apolloww)

Changes

CoroSplit expects the second parameter of llvm.coro.id to be the promise alloca. Applying Asan on a pre-split coroutine breaks this assumption and causes split to fail.

Also NoopCoro.Frame.Const is a special coroutine frame that does nothing when resumed or destroyed. There is no point to do instrumentation on it.


Full diff: https://github.com/llvm/llvm-project/pull/99415.diff

2 Files Affected:

  • (modified) llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp (+2)
  • (added) llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll (+36)
diff --git a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
index adf77f20cb1c7..89cba2b219c25 100644
--- a/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
+++ b/llvm/lib/Transforms/Instrumentation/AddressSanitizer.cpp
@@ -2032,6 +2032,7 @@ bool ModuleAddressSanitizer::shouldInstrumentGlobal(GlobalVariable *G) const {
   if (G->isThreadLocal()) return false;
   // For now, just ignore this Global if the alignment is large.
   if (G->getAlign() && *G->getAlign() > getMinRedzoneSizeForGlobal()) return false;
+  if (G->getName() == "NoopCoro.Frame.Const") return false;
 
   // For non-COFF targets, only instrument globals known to be defined by this
   // TU.
@@ -2947,6 +2948,7 @@ bool AddressSanitizer::instrumentFunction(Function &F,
   if (F.getLinkage() == GlobalValue::AvailableExternallyLinkage) return false;
   if (!ClDebugFunc.empty() && ClDebugFunc == F.getName()) return false;
   if (F.getName().starts_with("__asan_")) return false;
+  if (F.isPresplitCoroutine()) return false;
 
   bool FunctionModified = false;
 
diff --git a/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll b/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll
new file mode 100644
index 0000000000000..7aaf6ee8c471b
--- /dev/null
+++ b/llvm/test/Instrumentation/AddressSanitizer/skip-coro.ll
@@ -0,0 +1,36 @@
+; Tests that asan skips pre-split coroutine and NoopCoro.Frame
+; RUN: opt < %s -S -passes=coro-early,asan | FileCheck %s
+
+; CHECK: %NoopCoro.Frame = type { ptr, ptr }
+; CHECK: @NoopCoro.Frame.Const = private constant %NoopCoro.Frame { ptr @__NoopCoro_ResumeDestroy, ptr @__NoopCoro_ResumeDestroy }
+; CHECK-NOT: @0 = private alias { %NoopCoro.Frame,
+
+%struct.Promise = type { %"struct.std::__n4861::coroutine_handle" }
+%"struct.std::__n4861::coroutine_handle" = type { ptr }
+
+; CHECK-LABEL: @foo(
+define ptr @foo() #0 {
+; CHECK-NEXT: entry
+; CHECK-NOT: %asan_local_stack_base
+entry:
+  %__promise = alloca %struct.Promise, align 8
+  %0 = call token @llvm.coro.id(i32 16, ptr nonnull %__promise, ptr nonnull @foo, ptr null)
+  %1 = call ptr @llvm.coro.noop()
+  ret ptr %1
+}
+
+declare token @llvm.coro.id(i32, ptr readnone, ptr nocapture readonly, ptr)
+declare ptr @llvm.coro.noop()
+
+attributes #0 = { sanitize_address presplitcoroutine }
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "hand-written", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2)
+!1 = !DIFile(filename: "<stdin>", directory: "")
+!2 = !{}
+!3 = !{i32 2, !"Dwarf Version", i32 4}
+!4 = !{i32 2, !"Debug Info Version", i32 3}
+
+

Copy link

github-actions bot commented Jul 18, 2024

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

@apolloww apolloww changed the title [Asan] Skip pre-split coroutine and noop coroutine [Asan] Skip pre-split coroutine and noop coroutine frame Jul 18, 2024
@apolloww apolloww requested review from vitalybuka and rnk July 18, 2024 00:34
@@ -2032,6 +2032,8 @@ bool ModuleAddressSanitizer::shouldInstrumentGlobal(GlobalVariable *G) const {
if (G->isThreadLocal()) return false;
// For now, just ignore this Global if the alignment is large.
if (G->getAlign() && *G->getAlign() > getMinRedzoneSizeForGlobal()) return false;
if (G->getName() == "NoopCoro.Frame.Const")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Seems attaching attach metadata on the global is nicer solution?
See line 2020

Copy link
Collaborator

Choose a reason for hiding this comment

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

Also there is

if (F.isPresplitCoroutine())
    return false;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the suggestion. Updated the GV metadata. I'll leave the function metadata unchanged because we still want to instrument post-split coroutine functions, and isPresplitCoroutine() can reflect the state.

@llvmbot llvmbot added the coroutines C++20 coroutines label Jul 18, 2024
Comment on lines +2950 to +2951
if (F.isPresplitCoroutine())
return false;
Copy link
Member

Choose a reason for hiding this comment

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

nit: for consistency with preceding lines:

Suggested change
if (F.isPresplitCoroutine())
return false;
if (F.isPresplitCoroutine()) return false;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

They were on the same line, and I don't see a problem with that (see the first commit), but clang-format complained.

@apolloww apolloww merged commit bee2654 into llvm:main Jul 22, 2024
7 checks passed
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
Summary:
CoroSplit expects the second parameter of `llvm.coro.id` to be the
promise alloca. Applying Asan on a pre-split coroutine breaks this
assumption and causes split to fail. This should be NFC because asan
pass happens late in the pipeline where all coroutines are split. This
is to prevent crash in case the order of passes are switched.

Also `NoopCoro.Frame.Const` is a special coroutine frame that does
nothing when resumed or destroyed. There is no point to do
instrumentation on it.

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60251181
apolloww added a commit that referenced this pull request Jul 30, 2024
…line (#100205)

This is re-land of #90310 after making asan skip pre-split coroutines in
#99415.

Skip CoroSplit and CoroCleanup in LTO pre-link pipeline so that
CoroElide can happen after callee coroutine is imported into caller's
module in ThinLTO.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants