Skip to content

Fix LifetimeDependenceDiagnostics to ignore closure captures #72635

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 7 commits into from
Mar 28, 2024
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@ private func log(prefix: Bool = true, _ message: @autoclosure () -> String) {
let lifetimeDependenceDiagnosticsPass = FunctionPass(
name: "lifetime-dependence-diagnostics")
{ (function: Function, context: FunctionPassContext) in
if !context.options.hasFeature(.NonescapableTypes) {
return
}
log(prefix: false, "\n--- Diagnosing lifetime dependence in \(function.name)")
log("\(function)")

Expand All @@ -45,7 +42,7 @@ let lifetimeDependenceDiagnosticsPass = FunctionPass(
}
}
for instruction in function.instructions {
if let markDep = instruction as? MarkDependenceInst {
if let markDep = instruction as? MarkDependenceInst, markDep.isUnresolved {
if let lifetimeDep = LifetimeDependence(markDep, context) {
analyze(dependence: lifetimeDep, context)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ private func log(prefix: Bool = true, _ message: @autoclosure () -> String) {
let lifetimeDependenceInsertionPass = FunctionPass(
name: "lifetime-dependence-insertion")
{ (function: Function, context: FunctionPassContext) in
if !context.options.hasFeature(.NonescapableTypes) {
return
}
log(prefix: false, "\n--- Inserting lifetime dependence markers in \(function.name)")

for instruction in function.instructions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ private func log(prefix: Bool = true, _ message: @autoclosure () -> String) {
let lifetimeDependenceScopeFixupPass = FunctionPass(
name: "lifetime-dependence-scope-fixup")
{ (function: Function, context: FunctionPassContext) in
if !context.options.hasFeature(.NonescapableTypes) {
return
}
log(prefix: false, "\n--- Scope fixup for lifetime dependence in \(function.name)")

let localReachabilityCache = LocalVariableReachabilityCache()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1660,12 +1660,16 @@ bool DataflowState::process(

{
auto diag = diag::sil_movechecking_consuming_use_here;
diagnose(astContext, mvi->getLoc().getSourceLoc(), diag);
if (auto sourceLoc = mvi->getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc, diag);
}
}

{
auto diag = diag::sil_movechecking_nonconsuming_use_here;
diagnose(astContext, iter->second->getLoc().getSourceLoc(), diag);
if (auto sourceLoc = iter->second->getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc, diag);
}
}

emittedSingleDiagnostic = true;
Expand All @@ -1690,13 +1694,17 @@ bool DataflowState::process(

{
auto diag = diag::sil_movechecking_consuming_use_here;
diagnose(astContext, mvi->getLoc().getSourceLoc(), diag);
if (auto sourceLoc = mvi->getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc, diag);
}
}

{
auto diag = diag::sil_movechecking_nonconsuming_use_here;
for (auto *user : iter->second->pairedUseInsts) {
diagnose(astContext, user->getLoc().getSourceLoc(), diag);
if (auto sourceLoc = user->getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc, diag);
}
}
}

Expand Down Expand Up @@ -2115,12 +2123,16 @@ bool ConsumeOperatorCopyableAddressesChecker::performSingleBasicBlockAnalysis(
}

auto diag = diag::sil_movechecking_consuming_use_here;
diagnose(astCtx, mvi->getLoc().getSourceLoc(), diag);
if (auto sourceLoc = mvi->getLoc().getSourceLoc()) {
diagnose(astCtx, sourceLoc, diag);
}

{
auto diag = diag::sil_movechecking_nonconsuming_use_here;
for (auto *user : interestingClosureUsers) {
diagnose(astCtx, user->getLoc().getSourceLoc(), diag);
if (auto sourceLoc = user->getLoc().getSourceLoc()) {
diagnose(astCtx, sourceLoc, diag);
}
}
}

Expand Down Expand Up @@ -2157,12 +2169,16 @@ bool ConsumeOperatorCopyableAddressesChecker::performSingleBasicBlockAnalysis(

{
auto diag = diag::sil_movechecking_consuming_use_here;
diagnose(astCtx, mvi->getLoc().getSourceLoc(), diag);
if (auto sourceLoc = mvi->getLoc().getSourceLoc()) {
diagnose(astCtx, sourceLoc, diag);
}
}

{
auto diag = diag::sil_movechecking_nonconsuming_use_here;
diagnose(astCtx, interestingUser->getLoc().getSourceLoc(), diag);
if (auto sourceLoc = interestingUser->getLoc().getSourceLoc()) {
diagnose(astCtx, sourceLoc, diag);
}
}

// We purposely continue to see if at least in simple cases, we can flag
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ bool CheckerLivenessInfo::compute() {
}
}
}
// FIXME: this ignores all other forms of Borrow ownership, such as
// partial_apply [onstack] and mark_dependence [nonescaping].
break;
}
case OperandOwnership::GuaranteedForwarding:
Expand Down Expand Up @@ -250,8 +252,10 @@ void ConsumeOperatorCopyableValuesChecker::emitDiagnosticForMove(
diagnose(astContext, getSourceLocFromValue(borrowedValue),
diag::sil_movechecking_value_used_after_consume,
borrowedValueName);
diagnose(astContext, mvi->getLoc().getSourceLoc(),
diag::sil_movechecking_consuming_use_here);
if (auto sourceLoc = mvi->getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc,
diag::sil_movechecking_consuming_use_here);
}

// Then we do a bit of work to figure out where /all/ of the later uses than
// mvi are so we can emit notes to the user telling them this is a problem
Expand Down Expand Up @@ -281,8 +285,10 @@ void ConsumeOperatorCopyableValuesChecker::emitDiagnosticForMove(
case PrunedLiveness::NonLifetimeEndingUse:
case PrunedLiveness::LifetimeEndingUse:
LLVM_DEBUG(llvm::dbgs() << "Emitting note for in block use: " << inst);
diagnose(astContext, inst.getLoc().getSourceLoc(),
diag::sil_movechecking_nonconsuming_use_here);
if (auto sourceLoc = inst.getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc,
diag::sil_movechecking_nonconsuming_use_here);
}
break;
}
}
Expand Down Expand Up @@ -340,8 +346,10 @@ void ConsumeOperatorCopyableValuesChecker::emitDiagnosticForMove(
case PrunedLiveness::LifetimeEndingUse:
LLVM_DEBUG(llvm::dbgs()
<< "(3) Emitting diagnostic for user: " << inst);
diagnose(astContext, inst.getLoc().getSourceLoc(),
diag::sil_movechecking_nonconsuming_use_here);
if (auto sourceLoc = inst.getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc,
diag::sil_movechecking_nonconsuming_use_here);
}
break;
}
}
Expand All @@ -366,8 +374,10 @@ void ConsumeOperatorCopyableValuesChecker::emitDiagnosticForMove(
if (livenessInfo.nonLifetimeEndingUsesInLiveOut.contains(&inst)) {
LLVM_DEBUG(llvm::dbgs()
<< "(1) Emitting diagnostic for user: " << inst);
diagnose(astContext, inst.getLoc().getSourceLoc(),
diag::sil_movechecking_nonconsuming_use_here);
if (auto sourceLoc = inst.getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc,
diag::sil_movechecking_nonconsuming_use_here);
}
continue;
}

Expand All @@ -388,8 +398,10 @@ void ConsumeOperatorCopyableValuesChecker::emitDiagnosticForMove(

LLVM_DEBUG(llvm::dbgs()
<< "(2) Emitting diagnostic for user: " << inst);
diagnose(astContext, inst.getLoc().getSourceLoc(),
diag::sil_movechecking_nonconsuming_use_here);
if (auto sourceLoc = inst.getLoc().getSourceLoc()) {
diagnose(astContext, sourceLoc,
diag::sil_movechecking_nonconsuming_use_here);
}
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ static void diagnose(ASTContext &context, SILInstruction *inst, Diag<T...> diag,
if (SilentlyEmitDiagnostics)
return;

context.Diags.diagnose(loc.getSourceLoc(), diag, std::forward<U>(args)...);
auto sourceLoc = loc.getSourceLoc();
auto diagKind = context.Diags.declaredDiagnosticKindFor(diag.ID);
// Do not emit notes on invalid source locations.
if (!sourceLoc && diagKind == DiagnosticKind::Note) {
return;
}
context.Diags.diagnose(sourceLoc, diag, std::forward<U>(args)...);
}

template <typename... T, typename... U>
Expand Down
20 changes: 13 additions & 7 deletions lib/SILOptimizer/PassManager/PassPipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,6 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
// Check noImplicitCopy and move only types for objects and addresses.
P.addMoveOnlyChecker();

// Check ~Escapable.
if (P.getOptions().EnableLifetimeDependenceDiagnostics) {
P.addLifetimeDependenceDiagnostics();
}
if (EnableDeinitDevirtualizer)
P.addDeinitDevirtualizer();

// FIXME: rdar://122701694 (`consuming` keyword causes verification error on
// invalid SIL types)
//
Expand All @@ -189,6 +182,19 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
// No uses after consume operator of copyable value.
P.addConsumeOperatorCopyableValuesChecker();

// Check ~Escapable.
if (P.getOptions().EnableLifetimeDependenceDiagnostics) {
P.addLifetimeDependenceDiagnostics();
}

// Devirtualize deinits early if requested.
//
// FIXME: why is DeinitDevirtualizer in the middle of the mandatory pipeline,
// and what passes/compilation modes depend on it? This pass is never executed
// or tested without '-Xllvm enable-deinit-devirtualizer'.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@eeckstein if you know why the DeinitDevirtualizer runs in the middle of the mandatory pipeline or know of any passes that depend on it, in embedded mode or otherwise, please document that in the pass header. I couldn't figure it out after wasting an hour investigating.

if (EnableDeinitDevirtualizer)
P.addDeinitDevirtualizer();

// As a temporary measure, we also eliminate move only for non-trivial types
// until we can audit the later part of the pipeline. Eventually, this should
// occur before IRGen.
Expand Down
10 changes: 4 additions & 6 deletions test/SILOptimizer/lifetime_dependence_borrow.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// RUN: -verify \
// RUN: -sil-verify-all \
// RUN: -module-name test \
// RUN: -enable-experimental-feature NoncopyableGenerics \
// RUN: -enable-experimental-feature NonescapableTypes

// REQUIRES: asserts
Expand All @@ -15,8 +16,7 @@ struct CN: ~Copyable {
}

// Some Bufferview-ish thing.
@_nonescapable
struct BV {
struct BV : ~Escapable {
let p: UnsafeRawPointer
let i: Int

Expand All @@ -35,8 +35,7 @@ struct BV {
}

// Some MutableBufferview-ish thing.
@_nonescapable
struct MBV : ~Copyable {
struct MBV : ~Escapable, ~Copyable {
let p: UnsafeRawPointer
let i: Int

Expand All @@ -53,8 +52,7 @@ struct MBV : ~Copyable {
}

// Nonescapable wrapper.
@_nonescapable
struct NEBV {
struct NEBV : ~Escapable {
var bv: BV

init(_ bv: consuming BV) {
Expand Down
7 changes: 3 additions & 4 deletions test/SILOptimizer/lifetime_dependence_borrow_fail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
// RUN: -verify \
// RUN: -sil-verify-all \
// RUN: -module-name test \
// RUN: -enable-experimental-feature NoncopyableGenerics \
// RUN: -enable-experimental-feature NonescapableTypes

// REQUIRES: asserts
// REQUIRES: swift_in_compiler

@_nonescapable
struct BV {
struct BV : ~Escapable {
let p: UnsafeRawPointer
let i: Int

Expand All @@ -34,8 +34,7 @@ struct NC : ~Copyable {
}
}

@_nonescapable
struct NE {
struct NE : ~Escapable {
let p: UnsafeRawPointer
let i: Int

Expand Down
4 changes: 2 additions & 2 deletions test/SILOptimizer/lifetime_dependence_closure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
// RUN: -sil-verify-all \
// RUN: -module-name test \
// RUN: -disable-experimental-parser-round-trip \
// RUN: -enable-experimental-feature NoncopyableGenerics \
// RUN: -enable-experimental-feature NonescapableTypes

// REQUIRES: asserts
Expand All @@ -15,8 +16,7 @@ struct NCInt: ~Copyable {
init(_ value: Int) { self.value = value }
}

@_nonescapable
struct NEInt /*: ~Escapable*/ {
struct NEInt : ~Escapable {
let value: Int

init(borrowed: borrowing NCInt) -> dependsOn(borrowed) Self {
Expand Down
17 changes: 15 additions & 2 deletions test/SILOptimizer/lifetime_dependence_diagnostics.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// RUN: %target-swift-frontend %s -emit-sil \
// RUN: -sil-verify-all \
// RUN: -module-name test \
// RUN: -enable-experimental-feature NoncopyableGenerics \
// RUN: -enable-experimental-feature NonescapableTypes \
// RUN: 2>&1 | %FileCheck %s

// REQUIRES: asserts
// REQUIRES: swift_in_compiler

@_nonescapable
struct BV {
struct BV : ~Escapable {
let p: UnsafeRawPointer
let c: Int
@_unsafeNonescapableResult
Expand All @@ -22,6 +22,12 @@ func bv_copy(_ bv: borrowing BV) -> dependsOn(bv) BV {
copy bv
}

struct NCInt: ~Copyable {
var value: Int
}

func takeClosure(_: () -> ()) {}

// No mark_dependence is needed for a inherited scope.
//
// CHECK-LABEL: sil hidden @$s4test14bv_borrow_copyyAA2BVVADYlsF : $@convention(thin) (@guaranteed BV) -> _scope(1) @owned BV {
Expand All @@ -45,3 +51,10 @@ func bv_borrow_copy(_ bv: borrowing BV) -> dependsOn(scoped bv) BV {
func bv_borrow_borrow(bv: borrowing BV) -> dependsOn(scoped bv) BV {
bv_borrow_copy(bv)
}

// This already has a mark_dependence [nonescaping] before diagnostics. If it triggers diagnostics again, it will fail
// because lifetime dependence does not expect a dependence directly on an 'inout' address without any 'begin_access'
// marker.
func ncint_capture(ncInt: inout NCInt) {
takeClosure { _ = ncInt.value }
}
Loading