Skip to content

Fix an inliner crash when inlining begin_apply with scoped lifetime dependence #82033

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 1 commit into from
Jun 12, 2025
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 @@ -145,9 +145,13 @@ extension LifetimeDependentApply {
case .inherit:
continue
case .scope:
// FIXME: For yields with a scoped lifetime dependence, dependence on parameter operands is redundant,
// since we introduce dependence on the begin_apply's token as well.
// This can lead to duplicate lifetime dependence diagnostics in some cases.
// However this is neccessary for safety when begin_apply gets inlined which will delete the dependence on the token.
for yieldedValue in beginApply.yieldedValues {
let targetKind = yieldedValue.type.isAddress ? TargetKind.yieldAddress : TargetKind.yield
info.sources.push(LifetimeSource(targetKind: targetKind, convention: .inherit, value: operand.value))
info.sources.push(LifetimeSource(targetKind: targetKind, convention: dep, value: operand.value))
}
}
}
Expand Down
9 changes: 7 additions & 2 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -8934,10 +8934,10 @@ class MarkDependenceAddrInst

/// Shared API for MarkDependenceInst and MarkDependenceAddrInst.
class MarkDependenceInstruction {
const SILInstruction *inst = nullptr;
SILInstruction *inst = nullptr;

public:
explicit MarkDependenceInstruction(const SILInstruction *inst) {
explicit MarkDependenceInstruction(SILInstruction *inst) {
switch (inst->getKind()) {
case SILInstructionKind::MarkDependenceInst:
case SILInstructionKind::MarkDependenceAddrInst:
Expand Down Expand Up @@ -8998,6 +8998,11 @@ class MarkDependenceInstruction {
}
return false;
}

SILInstruction *operator->() { return inst; }
SILInstruction *operator->() const { return inst; }
SILInstruction *operator*() { return inst; }
SILInstruction *operator*() const { return inst; }
};

/// Promote an Objective-C block that is on the stack to the heap, or simply
Expand Down
26 changes: 26 additions & 0 deletions lib/SILOptimizer/Utils/SILInliner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,32 @@ class BeginApplySite {
collectAbortApply(abortApply);
endBorrowInsertPts.push_back(&*std::next(abortApply->getIterator()));
}

// We may have a mark_dependence/mark_dependence_addr on the coroutine's
// token. This is needed to represent lifetime dependence on values created
// within the coroutine. Delete such mark_dependence instructions since the
// dependencies on values created within the coroutine will be exposed after
// inlining.
if (BeginApply->getCalleeFunction()
->getLoweredFunctionType()
->hasLifetimeDependencies()) {
SmallVector<SILInstruction *> toDelete;
for (auto *tokenUser : BeginApply->getTokenResult()->getUsers()) {
auto mdi = MarkDependenceInstruction(tokenUser);
if (!mdi) {
continue;
}
assert(mdi.isNonEscaping());
if (auto *valueMDI = dyn_cast<MarkDependenceInst>(*mdi)) {
valueMDI->replaceAllUsesWith(valueMDI->getValue());
}
toDelete.push_back(*mdi);
}

for (auto *inst : toDelete) {
inst->eraseFromParent();
}
}
}

// Split the basic block before the end/abort_apply. We will insert code
Expand Down
9 changes: 5 additions & 4 deletions test/SILOptimizer/lifetime_dependence/coroutine.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,18 @@ func use(_ o : borrowing View) {}
// CHECK: [[ACCESS:%.*]] = begin_access [read] [static] [[NC]]
// CHECK: [[NCVAL:%.*]] = load [[ACCESS]]
// CHECK: ([[WRAPPER:%.*]], [[TOKEN1:%.*]]) = begin_apply %{{.*}}([[NCVAL]]) : $@yield_once @convention(method) (@guaranteed NCContainer) -> @lifetime(borrow 0) @yields @guaranteed Wrapper
// CHECK: retain_value [[WRAPPER]]
// CHECK: debug_value [[WRAPPER]], let, name "wrapper"
// CHECK: [[MDI:%.*]] = mark_dependence [nonescaping] [[WRAPPER]] on [[ACCESS]]
// CHECK: retain_value [[MDI]]
// CHECK: debug_value [[MDI]], let, name "wrapper"
// let view = wrapper.view
// CHECK: ([[VIEW:%.*]], [[TOKEN2:%.*]]) = begin_apply %{{.*}}([[WRAPPER]]) : $@yield_once @convention(method) (@guaranteed Wrapper) -> @lifetime(copy 0) @yields @guaranteed View
// CHECK: ([[VIEW:%.*]], [[TOKEN2:%.*]]) = begin_apply %{{.*}}([[MDI]]) : $@yield_once @convention(method) (@guaranteed Wrapper) -> @lifetime(copy 0) @yields @guaranteed View
// CHECK: retain_value [[VIEW]]
// CHECK: end_apply [[TOKEN2]] as $()
// CHECK: debug_value [[VIEW]], let, name "view"
// use(view)
// CHECK: apply %{{.*}}([[VIEW]]) : $@convention(thin) (@guaranteed View) -> ()
// CHECK: release_value [[VIEW]]
// CHECK: release_value [[WRAPPER]]
// CHECK: release_value [[MDI]]
// CHECK: end_apply [[TOKEN1]] as $()
// CHECK: end_access [[ACCESS]]
// CHECK: destroy_addr [[NC]]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// RUN: %target-sil-opt %s \
// RUN: -early-inline \
// RUN: -sil-print-types \
// RUN: -enable-experimental-feature Lifetimes | %FileCheck %s

// REQUIRES: swift_in_compiler
// REQUIRES: swift_feature_Lifetimes

import Swift
import Builtin

struct Wrapper : ~Copyable {
}

struct NE : ~Escapable {
}

struct MyBox : ~Copyable {
}

sil @$s4test7WrapperVACycfC : $@convention(method) (@thin Wrapper.Type) -> @owned Wrapper
sil @$s4test2NEVyAcA7WrapperVhcfC : $@convention(method) (@guaranteed Wrapper, @thin NE.Type) -> @lifetime(borrow 0) @owned NE
sil @$s4test5MyBoxVACycfC : $@convention(method) (@thin MyBox.Type) -> @owned MyBox
sil @$s4test3useyyAA2NEVF : $@convention(thin) (@guaranteed NE) -> ()

sil hidden [ossa] @$s4test5MyBoxV5valueAA2NEVvr : $@yield_once @convention(method) (@guaranteed MyBox) -> @lifetime(borrow 0) @yields @guaranteed NE {
bb0(%0 : @guaranteed $MyBox):
%1 = copy_value %0
%2 = mark_unresolved_non_copyable_value [no_consume_or_assign] %1
%4 = alloc_box ${ let Wrapper }, let, name "w"
%5 = begin_borrow [lexical] [var_decl] %4
%6 = project_box %5, 0
%7 = metatype $@thin Wrapper.Type
%8 = function_ref @$s4test7WrapperVACycfC : $@convention(method) (@thin Wrapper.Type) -> @owned Wrapper
%9 = apply %8(%7) : $@convention(method) (@thin Wrapper.Type) -> @owned Wrapper
store %9 to [init] %6
%11 = metatype $@thin NE.Type
%12 = mark_unresolved_non_copyable_value [no_consume_or_assign] %6
%13 = load_borrow %12
%14 = function_ref @$s4test2NEVyAcA7WrapperVhcfC : $@convention(method) (@guaranteed Wrapper, @thin NE.Type) -> @lifetime(borrow 0) @owned NE
%15 = apply %14(%13, %11) : $@convention(method) (@guaranteed Wrapper, @thin NE.Type) -> @lifetime(borrow 0) @owned NE
%16 = mark_dependence [nonescaping] %15 on %5
end_borrow %13
yield %16, resume bb1, unwind bb2

bb1:
destroy_value %16
end_borrow %5
destroy_value %4
destroy_value %2
%23 = tuple ()
return %23

bb2:
destroy_value %16
end_borrow %5
destroy_value %4
destroy_value %2
unwind
}

// Reduced SIL for validation-test/SILOptimizer/rdar151568816.swift

// CHECK-LABEL: sil hidden [ossa] @$s4test3fooyyF :
// CHECK: [[W:%.*]] = alloc_box ${ let Wrapper }, let, name "w"
// CHECK: [[B:%.*]] = begin_borrow [lexical] [var_decl] [[W]] : ${ let Wrapper }
// CHECK: [[INITFN:%.*]] = function_ref @$s4test2NEVyAcA7WrapperVhcfC : $@convention(method) (@guaranteed Wrapper, @thin NE.Type) -> @lifetime(borrow 0) @owned NE
// CHECK: [[NE:%.*]] = apply [[INITFN]]({{.*}}) : $@convention(method) (@guaranteed Wrapper, @thin NE.Type) -> @lifetime(borrow 0) @owned NE
// CHECK: [[MDI:%.*]] = mark_dependence [nonescaping] [[NE]] : $NE on [[B]] : ${ let Wrapper }
// CHECK-LABEL: } // end sil function '$s4test3fooyyF'
sil hidden [ossa] @$s4test3fooyyF : $@convention(thin) () -> () {
bb0:
%0 = alloc_box ${ let MyBox }, let, name "box"
%1 = begin_borrow [lexical] [var_decl] %0
%2 = project_box %1, 0
%3 = metatype $@thin MyBox.Type
%4 = function_ref @$s4test5MyBoxVACycfC : $@convention(method) (@thin MyBox.Type) -> @owned MyBox
%5 = apply %4(%3) : $@convention(method) (@thin MyBox.Type) -> @owned MyBox
store %5 to [init] %2
%7 = mark_unresolved_non_copyable_value [no_consume_or_assign] %2
%8 = load_borrow %7
%9 = function_ref @$s4test5MyBoxV5valueAA2NEVvr : $@yield_once @convention(method) (@guaranteed MyBox) -> @lifetime(borrow 0) @yields @guaranteed NE
(%10, %11) = begin_apply %9(%8) : $@yield_once @convention(method) (@guaranteed MyBox) -> @lifetime(borrow 0) @yields @guaranteed NE
%12 = mark_dependence [nonescaping] %10 on %11
%13 = mark_dependence [nonescaping] %12 on %1
%14 = copy_value %13
%15 = move_value [var_decl] %14
%17 = begin_borrow %15
%18 = function_ref @$s4test3useyyAA2NEVF : $@convention(thin) (@guaranteed NE) -> ()
%19 = apply %18(%17) : $@convention(thin) (@guaranteed NE) -> ()
end_borrow %17
destroy_value %15
%22 = end_apply %11 as $()
end_borrow %8
end_borrow %1
destroy_value %0
%26 = tuple ()
return %26
}

32 changes: 32 additions & 0 deletions validation-test/SILOptimizer/rdar151568816.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// RUN: %target-swift-frontend %s -enable-experimental-feature Lifetimes -emit-sil

// REQUIRES: swift_in_compiler
// REQUIRES: swift_feature_Lifetimes

// Ensure we don't crash

struct Wrapper : ~Copyable {
}

struct NE : ~Escapable {
@_lifetime(borrow w)
init(_ w: borrowing Wrapper) {}
}

struct MyBox : ~Copyable {
public var value: NE {
_read {
let w = Wrapper()
yield NE(w)
}
}
}

func use(_ n: borrowing NE) {}

func foo() {
let box = MyBox()
let value = box.value
use(value)
}