Skip to content
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 @@ -9,6 +9,15 @@
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// Pass dependencies:
///
/// - After MoveOnly checking fixes non-Copyable lifetimes.
///
/// - Before MoveOnlyTypeEliminator removes ownership operations on trivial types, which loses variable information
/// required for diagnostics.
///
//===----------------------------------------------------------------------===//

import AST
import SIL
Expand Down Expand Up @@ -250,22 +259,6 @@ private struct DiagnoseDependence {
}
}

private extension Instruction {
func findVarDecl() -> VarDecl? {
if let varDeclInst = self as? VarDeclInstruction {
return varDeclInst.varDecl
}
for result in results {
for use in result.uses {
if let debugVal = use.instruction as? DebugValueInst {
return debugVal.varDecl
}
}
}
return nil
}
}

// Identify a best-effort variable declaration based on a defining SIL
// value or any lifetime dependent use of that SIL value.
private struct LifetimeVariable {
Expand Down Expand Up @@ -327,7 +320,7 @@ private struct LifetimeVariable {
self = Self(introducer: allocStack)
case .global(let globalVar):
self.varDecl = globalVar.varDecl
self.sourceLoc = nil
self.sourceLoc = varDecl?.nameLoc
case .class(let refAddr):
self.varDecl = refAddr.varDecl
self.sourceLoc = refAddr.location.sourceLoc
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -564,3 +564,22 @@ extension AddressOwnershipLiveRange {
return .local(allocation, range)
}
}

let addressOwnershipLiveRangeTest = FunctionTest("address_ownership_live_range") {
function, arguments, context in
let address = arguments.takeValue()
print("Address: \(address)")
print("Base: \(address.accessBase)")
let begin = address.definingInstructionOrTerminator ?? {
assert(address is FunctionArgument)
return function.instructions.first!
}()
let localReachabilityCache = LocalVariableReachabilityCache()
guard var ownershipRange = AddressOwnershipLiveRange.compute(for: address, at: begin,
localReachabilityCache, context) else {
print("Error: indeterminate live range")
return
}
defer { ownershipRange.deinitialize() }
print(ownershipRange)
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
//
//===----------------------------------------------------------------------===//

import AST
import SIL

private let verbose = false
Expand Down Expand Up @@ -593,17 +594,13 @@ struct VariableIntroducerUseDefWalker : LifetimeDependenceUseDefWalker {
}

mutating func walkUp(value: Value, _ owner: Value?) -> WalkResult {
switch value.definingInstruction {
case let moveInst as MoveValueInst:
if moveInst.isFromVarDecl {
return introducer(moveInst, owner)
}
case let borrow as BeginBorrowInst:
if borrow.isFromVarDecl {
return introducer(borrow, owner)
}
default:
break
if let inst = value.definingInstruction, VariableScopeInstruction(inst) != nil {
return introducer(value, owner)
}
// Finding a variable introducer requires following the mark_dependence forwarded value, not the base value like the
// default LifetimeDependenceUseDefWalker.
if value is MarkDependenceInst {
return walkUpDefault(forwarded: value, owner)
}
return walkUpDefault(dependent: value, owner: owner)
}
Expand Down Expand Up @@ -749,6 +746,11 @@ extension LifetimeDependenceUseDefWalker {
return walkUp(newLifetime: store.source)
case let srcDestInst as SourceDestAddrInstruction:
return walkUp(address: srcDestInst.sourceOperand.value)
case let apply as FullApplySite:
if let f = apply.referencedFunction,
f.isConvertPointerToPointerArgument {
return walkUp(address: apply.parameterOperands[0].value)
}
default:
break
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ public func registerOptimizerTests() {
// Register each test.
registerFunctionTests(
getAccessBaseTest,
addressOwnershipLiveRangeTest,
argumentConventionsTest,
borrowIntroducersTest,
enclosingValuesTest,
Expand Down
2 changes: 2 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Function.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash

public var isAutodiffVJP: Bool { bridged.isAutodiffVJP() }

public var isConvertPointerToPointerArgument: Bool { bridged.isConvertPointerToPointerArgument() }

public var specializationLevel: Int { bridged.specializationLevel() }

public var hasOwnership: Bool { bridged.hasOwnership() }
Expand Down
82 changes: 82 additions & 0 deletions SwiftCompilerSources/Sources/SIL/Instruction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,88 @@ public protocol VarDeclInstruction {
var varDecl: VarDecl? { get }
}

/// A scoped instruction whose single result introduces a variable scope.
///
/// The scope-ending uses represent the end of the variable scope. This allows trivial 'let' variables to be treated
/// like a value with ownership. 'var' variables are primarily represented as addressable allocations via alloc_box or
/// alloc_stack, but may have redundant VariableScopeInstructions.
public enum VariableScopeInstruction {
case beginBorrow(BeginBorrowInst)
case moveValue(MoveValueInst)

public init?(_ inst: Instruction?) {
switch inst {
case let bbi as BeginBorrowInst:
guard bbi.isFromVarDecl else {
return nil
}
self = .beginBorrow(bbi)
case let mvi as MoveValueInst:
guard mvi.isFromVarDecl else {
return nil
}
self = .moveValue(mvi)
default:
return nil
}
}

public var instruction: Instruction {
switch self {
case let .beginBorrow(bbi):
return bbi
case let .moveValue(mvi):
return mvi
}
}

public var scopeBegin: Value {
instruction as! SingleValueInstruction
}

public var endOperands: LazyFilterSequence<UseList> {
return scopeBegin.uses.endingLifetime
}

// TODO: with SIL verification, we might be able to make varDecl non-Optional.
public var varDecl: VarDecl? {
if let debugVarDecl = instruction.debugVarDecl {
return debugVarDecl
}
// SILGen may produce double var_decl instructions for the same variable:
// %box = alloc_box [var_decl] "x"
// begin_borrow %box [var_decl]
//
// Assume that, if the begin_borrow or move_value does not have its own debug_value, then it must actually be
// associated with its operand's var_decl.
return instruction.operands[0].value.definingInstruction?.findVarDecl()
}
}

extension Instruction {
/// Find a variable declaration assoicated with this instruction.
public func findVarDecl() -> VarDecl? {
if let varDeclInst = self as? VarDeclInstruction {
return varDeclInst.varDecl
}
if let varScopeInst = VariableScopeInstruction(self) {
return varScopeInst.varDecl
}
return debugVarDecl
}

var debugVarDecl: VarDecl? {
for result in results {
for use in result.uses {
if let debugVal = use.instruction as? DebugValueInst {
return debugVal.varDecl
}
}
}
return nil
}
}

public protocol DebugVariableInstruction : VarDeclInstruction {
typealias DebugVariable = OptionalBridgedSILDebugVariable

Expand Down
1 change: 1 addition & 0 deletions include/swift/SIL/SILBridging.h
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,7 @@ struct BridgedFunction {
BRIDGED_INLINE void setLinkage(BridgedLinkage linkage) const;
BRIDGED_INLINE void setIsSerialized(bool isSerialized) const;
bool isTrapNoReturn() const;
bool isConvertPointerToPointerArgument() const;
bool isAutodiffVJP() const;
SwiftInt specializationLevel() const;
SWIFT_IMPORT_UNSAFE BridgedSubstitutionMap getMethodSubstitutions(BridgedSubstitutionMap contextSubs) const;
Expand Down
13 changes: 11 additions & 2 deletions lib/AST/LifetimeDependence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "swift/AST/DiagnosticsSema.h"
#include "swift/AST/Module.h"
#include "swift/AST/ParameterList.h"
#include "swift/AST/SourceFile.h"
#include "swift/AST/Type.h"
#include "swift/AST/TypeRepr.h"
#include "swift/Basic/Assertions.h"
Expand Down Expand Up @@ -538,10 +539,18 @@ LifetimeDependenceInfo::infer(AbstractFunctionDecl *afd) {
: afd->getParameters()->size();

auto *cd = dyn_cast<ConstructorDecl>(afd);
if (cd && cd->isImplicit()) {
if (cd->getParameters()->size() == 0) {
if (cd && cd->getParameters()->size() == 0) {
if (cd->isImplicit()) {
return std::nullopt;
}
if (auto *sf = afd->getParentSourceFile()) {
// The AST printer makes implicit initializers explicit, but does not
// print the @lifetime annotations. Until that is fixed, avoid diagnosing
// this as an error.
if (sf->Kind == SourceFileKind::SIL) {
return std::nullopt;
}
}
}

if (!ctx.LangOpts.hasFeature(Feature::LifetimeDependence)) {
Expand Down
3 changes: 1 addition & 2 deletions lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5006,8 +5006,7 @@ ParserResult<LifetimeEntry> Parser::parseLifetimeEntry(SourceLoc loc) {
auto lParenLoc = consumeAttributeLParen(); // consume the l_paren

std::optional<LifetimeDescriptor> targetDescriptor;
if (!isInSILMode() &&
Tok.isAny(tok::identifier, tok::integer_literal, tok::kw_self) &&
if (Tok.isAny(tok::identifier, tok::integer_literal, tok::kw_self) &&
peekToken().is(tok::colon)) {
targetDescriptor = parseLifetimeDescriptor(*this);
if (!targetDescriptor) {
Expand Down
9 changes: 9 additions & 0 deletions lib/SILOptimizer/PassManager/PassManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,15 @@ bool BridgedFunction::isTrapNoReturn() const {
return swift::isTrapNoReturnFunction(getFunction());
}

bool BridgedFunction::isConvertPointerToPointerArgument() const {
if (auto declRef = getFunction()->getDeclRef()) {
auto *conversionDecl =
declRef.getASTContext().getConvertPointerToPointerArgument();
return declRef.getFuncDecl() == conversionDecl;
}
return false;
}

bool BridgedFunction::isAutodiffVJP() const {
return swift::isDifferentiableFuncComponent(
getFunction(), swift::AutoDiffFunctionComponent::VJP);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ func bv_incorrect_annotation2(_ w1: borrowing Wrapper, _ w2: borrowing Wrapper)
} // expected-note @-1{{this use causes the lifetime-dependent value to escape}}

let ptr = UnsafeRawPointer(bitPattern: 1)!
let nc = NC(ptr, 0) // expected-error {{lifetime-dependent variable 'nc' escapes its scope}}
let nc = NC(ptr, 0)

func bv_global(dummy: BV) -> BV {
nc.getBV()
nc.getBV() // expected-error {{lifetime-dependent value escapes its scope}}
// expected-note @-4{{it depends on the lifetime of variable 'nc'}}
} // expected-note {{this use causes the lifetime-dependent value to escape}}

func testEmpty(nc: consuming NC) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,17 @@ func bv_borrow_var(p: UnsafeRawPointer, i: Int) {
let bv = nc.getBV()
use(bv)
}

// LifetimeDependence.Scope needs to see through typed-to-raw pointer conversion.
//
// CHECK-LABEL: sil hidden [ossa] @$s4test18bv_pointer_convert1pAA2BVVSPySiG_tF : $@convention(thin) (UnsafePointer<Int>) -> @lifetime(borrow 0) @owned BV {
// CHECK: bb0(%0 : $UnsafePointer<Int>):
// CHECK: apply %{{.*}}<UnsafePointer<Int>, UnsafeRawPointer>([[RAW:%.*]], %{{.*}}) : $@convention(thin) <τ_0_0, τ_0_1 where τ_0_0 : _Pointer, τ_0_1 : _Pointer> (@in_guaranteed τ_0_0) -> @out τ_0_1
// CHECK: [[RAW:%.*]] = load [trivial] %6 : $*UnsafeRawPointer
// CHECK: [[BV:%.*]] = apply %13([[RAW]], {{.*}}) : $@convention(method) (UnsafeRawPointer, Int, @thin BV.Type) -> @lifetime(borrow 0) @owned BV
// CHECK: return [[BV]] : $BV
// CHECK-LABEL: } // end sil function '$s4test18bv_pointer_convert1pAA2BVVSPySiG_tF'
@lifetime(borrow p)
func bv_pointer_convert(p: UnsafePointer<Int>) -> BV {
BV(p, 0)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// RUN: %target-sil-opt -test-runner %s -o /dev/null 2>&1 | %FileCheck %s

sil_stage canonical

import Builtin

class C {}
class D {
var object: C
}

// An address live range can be extended by a dependent value.
//
// CHECK-LABEL: testMarkDepAddressBase: address_ownership_live_range with: %f0
// CHECK-NEXT: Address: [[F0:%.*]] = ref_element_addr %0 : $D, #D.object
// CHECK-NEXT: Base: class - [[F0]] = ref_element_addr %0 : $D, #D.object
// CHECK-NEXT: borrow: functionArgument(%0 = argument of bb0 : $D
// CHECK-NEXT: begin: [[F0]] = ref_element_addr %0 : $D, #D.object
// CHECK-NEXT: ends: end_borrow %{{.*}} : $C
// CHECK-NEXT: exits:
// CHECK-NEXT: interiors: %{{.*}} = load_borrow %{{.*}} : $*C
// CHECK-NEXT: %{{.*}} = mark_dependence %{{.*}} : $*C on [[F0]] : $*C
// CHECK-NEXT: [[F0]] = ref_element_addr %0 : $D, #D.object
// CHECK-LABEL: end running test 1 of 1 on testMarkDepAddressBase: address_ownership_live_range with: %f0
sil [ossa] @testMarkDepAddressBase : $@convention(thin) (@guaranteed D, @guaranteed D) -> () {
bb0(%0 : @guaranteed $D, %1 : @guaranteed $D):
%f0 = ref_element_addr %0 : $D, #D.object
%f1 = ref_element_addr %1 : $D, #D.object
specify_test "address_ownership_live_range %f0"
%dependence = mark_dependence %f1 on %f0
%load = load_borrow %dependence
end_borrow %load
%99 = tuple()
return %99 : $()
}