Skip to content

[6.0] Fix type lowering of ~Copyable and ~Escapable generics. #72946

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 5 commits into from
Apr 11, 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
3 changes: 3 additions & 0 deletions include/swift/SIL/AbstractionPattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,10 @@ class AbstractionPattern {

bool requiresClass() const;
LayoutConstraint getLayoutConstraint() const;
bool conformsToKnownProtocol(
CanType substTy, KnownProtocolKind protocolKind) const;
bool isNoncopyable(CanType substTy) const;
bool isEscapable(CanType substTy) const;

/// Return the Swift type which provides structure for this
/// abstraction pattern.
Expand Down
46 changes: 28 additions & 18 deletions lib/SIL/IR/AbstractionPattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,62 +284,64 @@ LayoutConstraint AbstractionPattern::getLayoutConstraint() const {
}
}

bool AbstractionPattern::isNoncopyable(CanType substTy) const {
auto copyable
= substTy->getASTContext().getProtocol(KnownProtocolKind::Copyable);
bool AbstractionPattern::conformsToKnownProtocol(
CanType substTy, KnownProtocolKind protocolKind) const {
auto suppressible
= substTy->getASTContext().getProtocol(protocolKind);

auto isDefinitelyCopyable = [&](CanType t) -> bool {
auto result = copyable->getParentModule()
->checkConformanceWithoutContext(substTy, copyable,
auto definitelyConforms = [&](CanType t) -> bool {
auto result = suppressible->getParentModule()
->checkConformanceWithoutContext(t, suppressible,
/*allowMissing=*/false);
return result.has_value() && !result.value().isInvalid();
};

// If the substituted type definitely conforms, that's authoritative.
if (isDefinitelyCopyable(substTy)) {
return false;
if (definitelyConforms(substTy)) {
return true;
}

// If the substituted type is fully concrete, that's it. If there are unbound
// type variables in the type, then we may have to account for the upper
// abstraction bound from the abstraction pattern.
if (!substTy->hasTypeParameter()) {
return true;
return false;
}

switch (getKind()) {
case Kind::Opaque: {
// The abstraction pattern doesn't provide any more specific bounds.
return true;
return false;
}
case Kind::Type:
case Kind::Discard:
case Kind::ClangType: {
// See whether the abstraction pattern's context gives us an upper bound
// that ensures the type is copyable.
// that ensures the type conforms.
auto type = getType();
if (hasGenericSignature() && getType()->hasTypeParameter()) {
type = GenericEnvironment::mapTypeIntoContext(
getGenericSignature().getGenericEnvironment(), getType())
->getReducedType(getGenericSignature());
}

return !isDefinitelyCopyable(type);
return definitelyConforms(type);
}
case Kind::Tuple: {
// A tuple is noncopyable if any element is.
// A tuple conforms if all elements do.
if (doesTupleVanish()) {
return getVanishingTupleElementPatternType().value()
.isNoncopyable(substTy);
.conformsToKnownProtocol(substTy, protocolKind);
}
auto substTupleTy = cast<TupleType>(substTy);

for (unsigned i = 0, e = getNumTupleElements(); i < e; ++i) {
if (getTupleElementType(i).isNoncopyable(substTupleTy.getElementType(i))){
return true;
if (!getTupleElementType(i).conformsToKnownProtocol(
substTupleTy.getElementType(i), protocolKind)) {
return false;
}
}
return false;
return true;
}
// Functions are, at least for now, always copyable.
case Kind::CurriedObjCMethodType:
Expand All @@ -354,13 +356,21 @@ bool AbstractionPattern::isNoncopyable(CanType substTy) const {
case Kind::PartialCurriedCXXMethodType:
case Kind::OpaqueFunction:
case Kind::OpaqueDerivativeFunction:
return false;
return true;

case Kind::Invalid:
llvm_unreachable("asking invalid abstraction pattern");
}
}

bool AbstractionPattern::isNoncopyable(CanType substTy) const {
return !conformsToKnownProtocol(substTy, KnownProtocolKind::Copyable);
}

bool AbstractionPattern::isEscapable(CanType substTy) const {
return conformsToKnownProtocol(substTy, KnownProtocolKind::Escapable);
}

bool AbstractionPattern::matchesTuple(CanType substType) const {
switch (getKind()) {
case Kind::Invalid:
Expand Down
13 changes: 8 additions & 5 deletions lib/SIL/IR/TypeLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2441,7 +2441,9 @@ namespace {
return new (TC) MoveOnlyLoadableStructTypeLowering(
structType, properties, Expansion);
}
if (D->canBeEscapable() != TypeDecl::CanBeInvertible::Always) {
// Regardless of their member types, Nonescapable values have ownership
// for lifetime diagnostics.
if (!origType.isEscapable(structType)) {
properties.setNonTrivial();
}
return handleAggregateByProperties<LoadableStructTypeLowering>(structType,
Expand Down Expand Up @@ -2538,10 +2540,11 @@ namespace {
return new (TC)
MoveOnlyLoadableEnumTypeLowering(enumType, properties, Expansion);
}

assert(D->canBeEscapable() == TypeDecl::CanBeInvertible::Always
&& "missing typelowering case here!");

// Regardless of their member types, Nonescapable values have ownership
// for lifetime diagnostics.
if (!origType.isEscapable(enumType)) {
properties.setNonTrivial();
}
return handleAggregateByProperties<LoadableEnumTypeLowering>(enumType,
properties);
}
Expand Down
207 changes: 206 additions & 1 deletion test/SIL/type_lowering_unit.sil
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// RUN: %target-sil-opt -test-runner %s -o /dev/null 2>&1 | %FileCheck %s
// RUN: %target-sil-opt -test-runner \
// RUN: -enable-experimental-feature NoncopyableGenerics -enable-experimental-feature NonescapableTypes \
// RUN: -enable-experimental-feature BitwiseCopyable \
// RUN: %s -o /dev/null 2>&1 | %FileCheck %s

sil_stage raw

Expand All @@ -7,6 +10,8 @@ import Swift

struct S : ~Copyable {}

struct SCopyable {}

// CHECK-LABEL: begin {{.*}} print-type-lowering with: @argument[0]
// CHECK: isLexical: true
// CHECK-LABEL: end {{.*}} print-type-lowering with: @argument[0]
Expand Down Expand Up @@ -106,3 +111,203 @@ entry(%addr : $*Builtin.RawPointer):
specify_test "print_ast_type_lowering %instance"
return undef : $()
}

struct GSNC<T: ~Copyable>: ~Copyable {
var x: T
}

extension GSNC: Copyable where T: Copyable {}

struct GSNE<T: ~Escapable>: ~Escapable {
var x: T

// 60_MERGE: an explicit initializer is temporarily required until initializer inferrence is merged.
init(x: consuming T) { self.x = x }
}

extension GSNE: Escapable where T: Escapable {}

enum GENC<T: ~Copyable>: ~Copyable {
case x(T)
case knoll
}

enum GENE<T: ~Escapable>: ~Escapable {
case x(T)
case knoll
}

extension GENC: Copyable where T: Copyable {}

extension GENE: Escapable where T: Escapable {}

// TODO: We should have 'isTrivial: true' for the following four tests: gsnc_argument, gsne_argument, genc_argument,
// gene_argument. This requires fixing Typelowering visitAnyStructType and visitAnyEnumType to apply the current
// abstraction pattern for each stored property that refers to an archetype. Similar how TypeLowering handles the
// aggregate type by calling AbstractionPattern::conformsToKnownProtocol.
//
// CHECK-LABEL: begin running test 1 of 1 on gsnc_argument: print-type-lowering with: @argument[0]
// CHECK: Type Lowering for lowered type: $*GSNC<T>.
// CHECK: isTrivial: false.
// CHECK-LABEL: end running test 1 of 1 on gsnc_argument: print-type-lowering with: @argument[0]
sil [ossa] @gsnc_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed GSNC<T>) -> () {
bb0(%0 : $*GSNC<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on gsne_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: false.
// CHECK-LABEL: end running test 1 of 1 on gsne_argument: print-type-lowering with: @argument[0]
sil [ossa] @gsne_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed GSNE<T>) -> () {
bb0(%0 : $*GSNE<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on genc_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: false.
// CHECK-LABEL: end running test 1 of 1 on genc_argument: print-type-lowering with: @argument[0]
sil [ossa] @genc_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed GENC<T>) -> () {
bb0(%0 : $*GENC<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on gene_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: false.
// CHECK-LABEL: end running test 1 of 1 on gene_argument: print-type-lowering with: @argument[0]
sil [ossa] @gene_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed GENE<T>) -> () {
bb0(%0 : $*GENE<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

struct Bitwise<T: _BitwiseCopyable>: _BitwiseCopyable {
var x: T
}

// TODO: This should return 'isTrivial: true'. This is particularly inexcusable because the 'Bitwise' cannot be
// nontrivial over any 'T', *and* the declaration itself is declared BitwiseCopyable.
//
// CHECK-LABEL: begin running test 1 of 1 on bitwise_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: false.
// CHECK-LABEL: end running test 1 of 1 on bitwise_argument: print-type-lowering with: @argument[0]
sil [ossa] @bitwise_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed Bitwise<T>) -> () {
bb0(%0 : $*Bitwise<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on gsnc_specialized_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: true.
// CHECK-LABEL: end running test 1 of 1 on gsnc_specialized_argument: print-type-lowering with: @argument[0]
sil [ossa] @gsnc_specialized_argument : $@convention(thin) (@in_guaranteed GSNC<SCopyable>) -> () {
bb0(%0 : $*GSNC<SCopyable>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on gsne_specialized_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: true.
// CHECK-LABEL: end running test 1 of 1 on gsne_specialized_argument: print-type-lowering with: @argument[0]
sil [ossa] @gsne_specialized_argument : $@convention(thin) (@in_guaranteed GSNE<SCopyable>) -> () {
bb0(%0 : $*GSNE<SCopyable>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on genc_specialized_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: true.
// CHECK-LABEL: end running test 1 of 1 on genc_specialized_argument: print-type-lowering with: @argument[0]
sil [ossa] @genc_specialized_argument : $@convention(thin) (@in_guaranteed GENC<SCopyable>) -> () {
bb0(%0 : $*GENC<SCopyable>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on gene_specialized_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: true.
// CHECK-LABEL: end running test 1 of 1 on gene_specialized_argument: print-type-lowering with: @argument[0]
sil [ossa] @gene_specialized_argument : $@convention(thin) (@in_guaranteed GENE<SCopyable>) -> () {
bb0(%0 : $*GENE<SCopyable>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

struct GSNCInt<T: ~Copyable>: ~Copyable {
var x: Int
}

extension GSNCInt: Copyable where T: Copyable {}

struct GSNEInt<T: ~Escapable>: ~Escapable {
var x: Int
@_unsafeNonescapableResult
init() { x = 0 }
}

extension GSNEInt: Escapable where T: Escapable {}

enum GENCInt<T: ~Copyable>: ~Copyable {
case x(Int)
case knoll
}

enum GENEInt<T: ~Escapable>: ~Escapable {
case x(Int)
case knoll
}

extension GENCInt: Copyable where T: Copyable {}

extension GENEInt: Escapable where T: Escapable {}

// CHECK-LABEL: begin running test 1 of 1 on gsncint_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: true.
// CHECK-LABEL: end running test 1 of 1 on gsncint_argument: print-type-lowering with: @argument[0]
sil [ossa] @gsncint_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed GSNCInt<T>) -> () {
bb0(%0 : $*GSNCInt<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on gsneint_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: true.
// CHECK-LABEL: end running test 1 of 1 on gsneint_argument: print-type-lowering with: @argument[0]
sil [ossa] @gsneint_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed GSNEInt<T>) -> () {
bb0(%0 : $*GSNEInt<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on gencint_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: true.
// CHECK-LABEL: end running test 1 of 1 on gencint_argument: print-type-lowering with: @argument[0]
sil [ossa] @gencint_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed GENCInt<T>) -> () {
bb0(%0 : $*GENCInt<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}

// CHECK-LABEL: begin running test 1 of 1 on geneint_argument: print-type-lowering with: @argument[0]
// CHECK: isTrivial: true.
// CHECK-LABEL: end running test 1 of 1 on geneint_argument: print-type-lowering with: @argument[0]
sil [ossa] @geneint_argument : $@convention(thin) <T: _BitwiseCopyable> (@in_guaranteed GENEInt<T>) -> () {
bb0(%0 : $*GENEInt<T>):
specify_test "print-type-lowering @argument[0]"
%retval = tuple ()
return %retval : $()
}
Loading