OSSACompleteLifetime crash at -O: SILCombine leaks a copy of a borrowed class reference stored in two noncopyable fields · Issue #93409 · swiftlang/swift · GitHub
Skip to content

OSSACompleteLifetime crash at -O: SILCombine leaks a copy of a borrowed class reference stored in two noncopyable fields #93409

Open

Description

@nicholas-lonsinger

Description

Compiling the program below with -O crashes the optimizer with Invalid SIL provided to OSSACompleteLifetime?!. It compiles cleanly at -Onone.

make passes its borrowed owner to Context.init(owner:), which stores the reference in two noncopyable fields. After inlining, SILCombine removes the move_value [lexical] on the copy passed to the initializer, and leaves that copy without a consuming use (details under Additional information).

The program also compiles at -O when:

  • make takes owner as consuming, or
  • either Files or Permit is Copyable.

Reproduction

public final class Instance {}

struct Files: ~Copyable {
    let owner: Instance
}

struct Permit: ~Copyable {
    let instance: Instance
    let tag: Int
}

public struct Context: ~Copyable {
    let files: Files
    let permit: Permit

    init(owner: Instance) {
        files = Files(owner: owner)
        permit = Permit(instance: owner, tag: 1)
    }
}

public func make(_ owner: Instance, _ body: (borrowing Context) -> Int) -> Int {
    body(Context(owner: owner))
}
swiftc -O -parse-as-library -c repro.swift

Stack dump

Invalid SIL provided to OSSACompleteLifetime?! {{
OSSACompleteLifetime is visiting the availability boundary of   %4 = copy_value %0 : $Instance                  // user: %11
When walking forward from the def to the availability boundary a non-dead-end successor-less block was encountered: bb0
Its terminator must be an unreachable but is actually instead   return %17 : $Int                               // id: %25
The walk stops at consumes, so reaching such a block means the value was leaked.  The function with the leak is as follows:
// make(_:_:)
// Isolation: unspecified
sil [ossa] @$s5repro4makeySiAA8InstanceC_SiAA7ContextVXEtF : $@convention(thin) (@guaranteed Instance, @guaranteed @noescape @callee_guaranteed (@guaranteed Context) -> Int) -> Int {
[%0: read c*.v**, write c*.v**, copy **, destroy c*.v**]
[%1: read v**.c*.v**, write v**.c*.v**, copy v**.**, destroy v**.c*.v**]
[global: read,write,copy,destroy,allocate,deinit_barrier]
// %0 "owner"                                     // users: %5, %6, %9, %2, %13, %4
// %1 "body"                                      // users: %17, %3
bb0(%0 : @guaranteed $Instance, %1 : @guaranteed $@noescape @callee_guaranteed (@guaranteed Context) -> Int):
  debug_value %0, let, name "owner", argno 1      // id: %2
  debug_value %1, let, name "body", argno 2       // id: %3
  %4 = copy_value %0                              // user: %11
  debug_value %0, let, name "owner", argno 1      // id: %5
  debug_value %0, var, name "self", type $Context, expr op_fragment:#Context.files:op_fragment:#Files.owner // id: %6
  %7 = integer_literal $Builtin.Int64, 1          // user: %8
  %8 = struct $Int (%7)                           // users: %14, %10
  debug_value %0, var, name "self", type $Context, expr op_fragment:#Context.permit:op_fragment:#Permit.instance // id: %9
  debug_value %8, var, name "self", type $Context, expr op_fragment:#Context.permit:op_fragment:#Permit.tag // id: %10
  %11 = copy_value %4                             // user: %12
  %12 = struct $Files (%11)                       // user: %15
  %13 = copy_value %0                             // user: %14
  %14 = struct $Permit (%13, %8)                  // user: %15
  %15 = struct $Context (%12, %14)                // users: %18, %17
  debug_value undef : $*Context, var, name "self", expr op_deref // id: %16
  %17 = apply %1(%15) : $@noescape @callee_guaranteed (@guaranteed Context) -> Int // user: %25
  (%18, %19) = destructure_struct %15             // users: %20, %22
  %20 = destructure_struct %18                    // user: %21
  destroy_value %20                               // id: %21
  (%22, %23) = destructure_struct %19             // user: %24
  destroy_value %22                               // id: %24
  return %17                                      // id: %25
} // end sil function '$s5repro4makeySiAA8InstanceC_SiAA7ContextVXEtF'

Invalid SIL provided to OSSACompleteLifetime?! }}
Something that ran before OSSACompleteLifetime (the current pass, an earlier pass, SILGen) has introduced a leak of this value.
Please rerun the crashing swift-frontend command with the following flags added and file a bug with the output:
-sil-ownership-verify-all -Xllvm '-sil-print-function=$s5repro4makeySiAA8InstanceC_SiAA7ContextVXEtF' -Xllvm -sil-print-types -Xllvm -sil-print-module-on-error
Use the -disable-sil-ownership-verifier frontend flag to disable this check.
<unknown>:0: error: fatal error encountered during compilation; please submit a bug report (https://swift.org/contributing/#reporting-bugs)
<unknown>:0: note: Invalid lifetime of value whose availability boundary is being visited.
Please submit a bug report (https://swift.org/contributing/#reporting-bugs) and include the crash backtrace.
Stack dump:
0.	Program arguments: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-frontend -frontend -c -primary-file repro.swift -target arm64-apple-macosx27.2.0 -Xllvm -aarch64-use-tbi -enable-objc-interop -stack-check -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX27.0.sdk -no-color-diagnostics -Xcc -fno-color-diagnostics -O -new-driver-path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-driver -empty-abi-descriptor -no-auto-bridging-header-chaining -module-name repro -disable-clang-spi -target-sdk-version 27.0 -target-sdk-name macosx27.0 -external-plugin-path /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib/swift/host/plugins#/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/bin/swift-plugin-server -external-plugin-path /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/local/lib/swift/host/plugins#/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/bin/swift-plugin-server -in-process-plugin-server-path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/libSwiftInProcPluginServer.dylib -plugin-path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/plugins -plugin-path /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/local/lib/swift/host/plugins -enable-default-cmo -parse-as-library -o repro.o
1.	Apple Swift version 6.4 (swiftlang-6.4.0.34.1 clang-2100.3.34.1)
2.	Compiling with effective version 5.10
3.	While evaluating request ExecuteSILPipelineRequest(Run pipelines { PrepareOptimizationPasses, EarlyModulePasses, HighLevel,Function+EarlyLoopOpt, HighLevel,Module+StackPromote, MidLevel,Function, ClosureSpecialize, LowLevel,Function, LateLoopOpt, SIL Debug Info Generator } on SIL for repro)
4.	While running pass #912 SILFunctionTransform "SemanticARCOpts" on SILFunction "@$s5repro4makeySiAA8InstanceC_SiAA7ContextVXEtF".
 for 'make(_:_:)' (at repro.swift:22:8)
Stack dump without symbol names (ensure you have llvm-symbolizer in your PATH or set the environment var `LLVM_SYMBOLIZER_PATH` to point to it):
0  swift-frontend           0x0000000106a93bd0 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) + 56
1  swift-frontend           0x0000000106a91358 llvm::sys::RunSignalHandlers() + 172
2  swift-frontend           0x0000000106a941c8 SignalHandler(int, __siginfo*, void*) + 360
3  libsystem_platform.dylib 0x000000019dc21144 _sigtramp + 56
4  libsystem_pthread.dylib  0x0000000198c50960 pthread_kill + 296
5  libsystem_c.dylib        0x0000000198ce4480 abort + 148
6  swift-frontend           0x00000001009c0ff8 swift::DiagnosticHelper::~DiagnosticHelper() + 0
7  swift-frontend           0x0000000106a02460 llvm::report_fatal_error(llvm::Twine const&, bool) + 292
8  swift-frontend           0x0000000106a0233c llvm::report_fatal_error(llvm::Twine const&, bool) + 0
9  swift-frontend           0x0000000100fc11ec visitUsersOutsideLinearLivenessBoundary(swift::SILValue, swift::SSAPrunedLiveness const&, llvm::function_ref<void (swift::SILInstruction*)>) + 0
10 swift-frontend           0x0000000101550d40 swift::semanticarc::OwnershipLiveRange::OwnershipLiveRange(swift::SILValue, llvm::ArrayRef<std::__1::pair<swift::Operand*, swift::SILValue>>) + 1188
11 swift-frontend           0x0000000101551f34 swift::semanticarc::SemanticARCOptVisitor::performGuaranteedCopyValueOptimization(swift::CopyValueInst*) + 116
12 swift-frontend           0x00000001015533e4 swift::semanticarc::SemanticARCOptVisitor::visitCopyValueInst(swift::CopyValueInst*) + 96
13 swift-frontend           0x000000010155766c swift::semanticarc::SemanticARCOptVisitor::processWorklist() + 1060
14 swift-frontend           0x000000010154f454 (anonymous namespace)::SemanticARCOpts::run() + 2640
15 swift-frontend           0x00000001015087b8 swift::SILPassManager::runFunctionPasses(unsigned int, unsigned int) + 3564
16 swift-frontend           0x0000000101500cb0 swift::SILPassManager::executePassPipelinePlan(swift::SILPassPipelinePlan const&) + 364
17 swift-frontend           0x0000000101523b0c swift::SimpleRequest<swift::ExecuteSILPipelineRequest, std::__1::tuple<> (swift::SILPipelineExecutionDescriptor), (swift::RequestFlags)1>::evaluateRequest(swift::ExecuteSILPipelineRequest const&, swift::Evaluator&) + 52
18 swift-frontend           0x000000010150b6d4 swift::ExecuteSILPipelineRequest::OutputType swift::Evaluator::getResultUncached<swift::ExecuteSILPipelineRequest, swift::ExecuteSILPipelineRequest::OutputType swift::evaluateOrFatal<swift::ExecuteSILPipelineRequest>(swift::Evaluator&, swift::ExecuteSILPipelineRequest)::'lambda'()>(swift::ExecuteSILPipelineRequest const&, swift::ExecuteSILPipelineRequest::OutputType swift::evaluateOrFatal<swift::ExecuteSILPipelineRequest>(swift::Evaluator&, swift::ExecuteSILPipelineRequest)::'lambda'()) + 388
19 swift-frontend           0x000000010150dd58 swift::runSILOptimizationPasses(swift::SILModule&) + 464
20 swift-frontend           0x00000001009e19c4 swift::CompilerInstance::performSILProcessing(swift::SILModule*) + 1616
21 swift-frontend           0x00000001005a4860 performCompileStepsPostSILGen(swift::CompilerInstance&, std::__1::unique_ptr<swift::SILModule, std::__1::default_delete<swift::SILModule>>, llvm::PointerUnion<swift::ModuleDecl*, swift::SourceFile*>, swift::PrimarySpecificPaths const&, int&, swift::FrontendObserver*, llvm::ArrayRef<char const*>, llvm::ArrayRef<swift::PrimarySpecificPaths>) + 1584
22 swift-frontend           0x00000001005a3248 swift::performCompileStepsPostSema(swift::CompilerInstance&, int&, swift::FrontendObserver*, llvm::ArrayRef<char const*>) + 992
23 swift-frontend           0x00000001005a8c0c performCompile(swift::CompilerInstance&, int&, swift::FrontendObserver*, llvm::ArrayRef<char const*>) + 1312
24 swift-frontend           0x00000001005a5dc4 swift::performFrontend(llvm::ArrayRef<char const*>, char const*, void*, swift::FrontendObserver*) + 3776
25 swift-frontend           0x0000000100525008 swift::mainEntry(int, char const**) + 5600
26 dyld                     0x0000000198b7cd7c start + 6652

Expected behavior

The program compiles at -O, as it does at -Onone.

Environment

$ swiftc -version
swift-driver version: 1.168.6 Apple Swift version 6.4 (swiftlang-6.4.0.34.1 clang-2100.3.34.1)
Target: arm64-apple-macosx27.2.0

Xcode 27.1 (27A9275), macOS 27.0 SDK.

Additional information

Rerunning with the flags the crash message asks for (-sil-ownership-verify-all -Xllvm -sil-print-function=… -Xllvm -sil-print-types -Xllvm -sil-print-module-on-error) moves the failure to the end of pass #902 SILCombine (stage HighLevel,Function+EarlyLoopOpt): "Found a leaked owned value that was never consumed. Value: %4 = copy_value %0 : $Instance".

make before that SILCombine run (debug_values omitted):

bb0(%0 : @guaranteed $Instance, %1 : @guaranteed $@noescape @callee_guaranteed (@guaranteed Context) -> Int):
  %4 = copy_value %0
  %5 = move_value [lexical] %4
  %8 = integer_literal $Builtin.Int64, 1
  %9 = struct $Int (%8)
  %12 = copy_value %5
  %13 = struct $Files (%12)
  %14 = struct $Permit (%5, %9)
  %15 = struct $Context (%13, %14)
  %17 = apply %1(%15) : $@noescape @callee_guaranteed (@guaranteed Context) -> Int
  (%18, %19) = destructure_struct %15
  %20 = destructure_struct %18
  destroy_value %20
  (%22, %23) = destructure_struct %19
  destroy_value %22
  return %17

After:

bb0(%0 : @guaranteed $Instance, %1 : @guaranteed $@noescape @callee_guaranteed (@guaranteed Context) -> Int):
  %4 = copy_value %0
  %7 = integer_literal $Builtin.Int64, 1
  %8 = struct $Int (%7)
  %11 = copy_value %4
  %12 = struct $Files (%11)
  %13 = copy_value %0
  %14 = struct $Permit (%13, %8)
  %15 = struct $Context (%12, %14)
  %17 = apply %1(%15) : $@noescape @callee_guaranteed (@guaranteed Context) -> Int
  (%18, %19) = destructure_struct %15
  %20 = destructure_struct %18
  destroy_value %20
  (%22, %23) = destructure_struct %19
  destroy_value %22
  return %17

Removing %5 = move_value [lexical] %4 does two things:

  • It gives the consuming use (struct $Permit) a fresh copy_value %0.
  • It redirects the non-consuming use to %4.

After that, nothing consumes %4.

The same crash happens when the borrowed reference is a closure capture, e.g. { body(Context(owner: owner)) } in an escaping closure that captures owner. It came up in a real project, where a whole-module -O build crashed on that shape. The workaround there was to pass the initializer an owned reference.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions