Skip to content

[6.0] Devirtualizer: fix a crash due to a not supported bitcast of ABI compatible types #74653

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 2 commits into from
Jun 24, 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
14 changes: 14 additions & 0 deletions include/swift/SILOptimizer/Utils/InstOptUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,20 @@ std::pair<SILValue, bool /* changedCFG */>
castValueToABICompatibleType(SILBuilder *builder, SILLocation Loc,
SILValue value, SILType srcTy, SILType destTy,
ArrayRef<SILInstruction *> usePoints);

/// Returns true if the layout of a generic nominal type is dependent on its generic parameters.
/// This is usually the case. Some examples, where they layout is _not_ dependent:
/// ```
/// struct S<T> {
/// var x: Int // no members which depend on T
/// }
///
/// struct S<T> {
/// var c: SomeClass<T> // a class reference does not depend on the layout of the class
/// }
/// ```
bool layoutIsTypeDependent(NominalTypeDecl *decl);

/// Peek through trivial Enum initialization, typically for pointless
/// Optionals.
///
Expand Down
122 changes: 122 additions & 0 deletions lib/SILOptimizer/Utils/InstOptUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//

#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/AST/CanTypeVisitor.h"
#include "swift/AST/GenericSignature.h"
#include "swift/AST/SemanticAttrs.h"
#include "swift/AST/SubstitutionMap.h"
Expand Down Expand Up @@ -736,12 +737,133 @@ swift::castValueToABICompatibleType(SILBuilder *builder, SILLocation loc,
false};
}
}
NominalTypeDecl *srcNominal = srcTy.getNominalOrBoundGenericNominal();
NominalTypeDecl *destNominal = destTy.getNominalOrBoundGenericNominal();
if (srcNominal && srcNominal == destNominal &&
!layoutIsTypeDependent(srcNominal) &&
srcTy.isObject() && destTy.isObject()) {

// This can be a result from whole-module reasoning of protocol conformances.
// If a protocol only has a single conformance where the associated type (`ID`) is some
// concrete type (e.g. `Int`), then the devirtualizer knows that `p.get()`
// can only return an `Int`:
// ```
// public struct X2<ID> {
// let p: any P2<ID>
// public func testit(i: ID, x: ID) -> S2<ID> {
// return p.get(x: x)
// }
// }
// ```
// and after devirtualizing the `get` function, its result must be cast from `Int` to `ID`.
//
// The `layoutIsTypeDependent` utility is basically only used here to assert that this
// cast can only happen between layout compatible types.
return {builder->createUncheckedForwardingCast(loc, value, destTy), false};
}

llvm::errs() << "Source type: " << srcTy << "\n";
llvm::errs() << "Destination type: " << destTy << "\n";
llvm_unreachable("Unknown combination of types for casting");
}

namespace {
class TypeDependentVisitor : public CanTypeVisitor<TypeDependentVisitor, bool> {
public:
// If the type isn't actually dependent, we're okay.
bool visit(CanType type) {
if (!type->hasArchetype() && !type->hasTypeParameter())
return false;
return CanTypeVisitor::visit(type);
}

bool visitStructType(CanStructType type) {
return visitStructDecl(type->getDecl());
}
bool visitBoundGenericStructType(CanBoundGenericStructType type) {
return visitStructDecl(type->getDecl());
}
bool visitStructDecl(StructDecl *decl) {
auto rawLayout = decl->getAttrs().getAttribute<RawLayoutAttr>();
if (rawLayout) {
if (auto likeType = rawLayout->getResolvedScalarLikeType(decl)) {
return visit((*likeType)->getCanonicalType());
} else if (auto likeArray = rawLayout->getResolvedArrayLikeTypeAndCount(decl)) {
return visit(likeArray->first->getCanonicalType());
}
}

for (auto field : decl->getStoredProperties()) {
if (visit(field->getInterfaceType()->getCanonicalType()))
return true;
}
return false;
}

bool visitEnumType(CanEnumType type) {
return visitEnumDecl(type->getDecl());
}
bool visitBoundGenericEnumType(CanBoundGenericEnumType type) {
return visitEnumDecl(type->getDecl());
}
bool visitEnumDecl(EnumDecl *decl) {
if (decl->isIndirect())
return false;

for (auto elt : decl->getAllElements()) {
if (!elt->hasAssociatedValues() || elt->isIndirect())
continue;

if (visit(elt->getArgumentInterfaceType()->getCanonicalType()))
return true;
}
return false;
}

bool visitTupleType(CanTupleType type) {
for (auto eltTy : type.getElementTypes()) {
if (visit(eltTy->getCanonicalType()))
return true;
}
return false;
}

// A class reference does not depend on the layout of the class.
bool visitClassType(CanClassType type) {
return false;
}
bool visitBoundGenericClassType(CanBoundGenericClassType type) {
return false;
}

// The same for non-strong references.
bool visitReferenceStorageType(CanReferenceStorageType type) {
return false;
}

// All function types have the same layout.
bool visitAnyFunctionType(CanAnyFunctionType type) {
return false;
}

// The safe default for types we didn't handle above.
bool visitType(CanType type) {
return true;
}
};
} // end anonymous namespace

bool swift::layoutIsTypeDependent(NominalTypeDecl *decl) {
if (auto *classDecl = dyn_cast<ClassDecl>(decl)) {
return false;
} else if (auto *structDecl = dyn_cast<StructDecl>(decl)) {
return TypeDependentVisitor().visitStructDecl(structDecl);
} else {
auto *enumDecl = cast<EnumDecl>(decl);
return TypeDependentVisitor().visitEnumDecl(enumDecl);
}
}

ProjectBoxInst *swift::getOrCreateProjectBox(AllocBoxInst *abi,
unsigned index) {
SILBasicBlock::iterator iter(abi);
Expand Down
182 changes: 175 additions & 7 deletions test/SILOptimizer/devirtualize_existential.swift
Original file line number Diff line number Diff line change
@@ -1,19 +1,187 @@
// RUN: %target-swift-frontend %s -O -emit-sil | %FileCheck %s
// RUN: %target-swift-frontend %s -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test -emit-sil | %FileCheck %s

// RUN: %empty-directory(%t)
// RUN: %target-build-swift -wmo -O -Xllvm -sil-disable-pass=FunctionSignatureOpts -module-name=test %s -o %t/a.out
// RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT
// REQUIRES: executable_test

// Test conversions of return types.

public struct S1<ID> {
var x: Int
}

protocol P1<ID> {
associatedtype ID

func get(x: ID) -> S1<ID>
}

struct Y1: P1 {
func get(x: Int) -> S1<Int> {
return S1(x: 27)
}
}

public struct X1<ID> {
let p: any P1<ID>

// CHECK-LABEL: sil {{.*}} @$s4test2X1V6testit1i1xAA2S1VyxGx_xtF :
// CHECK: unchecked_trivial_bit_cast
// CHECK: } // end sil function '$s4test2X1V6testit1i1xAA2S1VyxGx_xtF'
@_semantics("optimize.sil.specialize.generic.never")
@inline(never)
public func testit(i: ID, x: ID) -> S1<ID> {
return p.get(x: x)
}
}

public struct S2<ID> {
var x: String
}

protocol P2<ID> {
associatedtype ID

func get(x: ID) -> S2<ID>
}

struct Y2: P2 {
func get(x: Int) -> S2<Int> {
return S2(x: "27")
}
}

public struct X2<ID> {
let p: any P2<ID>

// CHECK-LABEL: sil {{.*}} @$s4test2X2V6testit1i1xAA2S2VyxGx_xtF :
// CHECK: unchecked_bitwise_cast
// CHECK: } // end sil function '$s4test2X2V6testit1i1xAA2S2VyxGx_xtF'
@_semantics("optimize.sil.specialize.generic.never")
@inline(never)
public func testit(i: ID, x: ID) -> S2<ID> {
return p.get(x: x)
}
}


class C3<T> {}

public struct S3<ID> {
var x: C3<ID>
}

protocol P3<ID> {
associatedtype ID

func get(x: ID) -> S3<ID>
}

struct Y3: P3 {
func get(x: Int) -> S3<Int> {
return S3(x: C3<Int>())
}
}

public struct X3<ID> {
let p: any P3<ID>

// CHECK-LABEL: sil {{.*}} @$s4test2X3V6testit1i1xAA2S3VyxGx_xtF :
// CHECK: unchecked_bitwise_cast
// CHECK: } // end sil function '$s4test2X3V6testit1i1xAA2S3VyxGx_xtF'
@_semantics("optimize.sil.specialize.generic.never")
@inline(never)
public func testit(i: ID, x: ID) -> S3<ID> {
return p.get(x: x)
}
}


public class C4<T> {}

protocol P4<ID> {
associatedtype ID

func get(x: ID) -> C4<ID>
}

struct Y4: P4 {
func get(x: Int) -> C4<Int> {
return C4()
}
}

public struct X4<ID> {
let p: any P4<ID>

// CHECK-LABEL: sil {{.*}} @$s4test2X4V6testit1i1xAA2C4CyxGx_xtF :
// CHECK: unchecked_ref_cast
// CHECK: } // end sil function '$s4test2X4V6testit1i1xAA2C4CyxGx_xtF'
@_semantics("optimize.sil.specialize.generic.never")
@inline(never)
public func testit(i: ID, x: ID) -> C4<ID> {
return p.get(x: x)
}
}


public struct S5<ID> {
var x: (Int, C4<ID>)
}

protocol P5<ID> {
associatedtype ID

func get(x: ID) -> S5<ID>
}

struct Y5: P5 {
func get(x: Int) -> S5<Int> {
return S5(x: (27, C4<Int>()))
}
}

public struct X5<ID> {
let p: any P5<ID>

// CHECK-LABEL: sil {{.*}} @$s4test2X5V6testit1i1xAA2S5VyxGx_xtF :
// CHECK: unchecked_bitwise_cast
// CHECK: } // end sil function '$s4test2X5V6testit1i1xAA2S5VyxGx_xtF'
@_semantics("optimize.sil.specialize.generic.never")
@inline(never)
public func testit(i: ID, x: ID) -> S5<ID> {
return p.get(x: x)
}
}

// Basic test

protocol Pingable {
func ping(_ x : Int);
}
class Foo : Pingable {
func ping(_ x : Int) { var t : Int }
func ping(_ x : Int) { _ = 1 }
}

// Everything gets devirtualized, inlined, and promoted to the stack.
//CHECK: @$s24devirtualize_existential17interesting_stuffyyF
//CHECK-NOT: init_existential_addr
//CHECK-NOT: apply
//CHECK: return
//CHECK-LABEL: sil @$s4test17interesting_stuffyyF :
//CHECK-NOT: init_existential_addr
//CHECK-NOT: apply
//CHECK: } // end sil function '$s4test17interesting_stuffyyF'
public func interesting_stuff() {
var x : Pingable = Foo()
let x : Pingable = Foo()
x.ping(1)
}

// CHECK-OUTPUT: S1<Int>(x: 27)
print(X1<Int>(p: Y1()).testit(i: 1, x: 2))
// CHECK-OUTPUT: S2<Int>(x: "27")
print(X2<Int>(p: Y2()).testit(i: 1, x: 2))
// CHECK-OUTPUT: S3<Int>(x: test.C3<Swift.Int>)
print(X3<Int>(p: Y3()).testit(i: 1, x: 2))
// CHECK-OUTPUT: test.C4<Swift.Int>
print(X4<Int>(p: Y4()).testit(i: 1, x: 2))
// CHECK-OUTPUT: S5<Int>(x: (27, test.C4<Swift.Int>))
print(X5<Int>(p: Y5()).testit(i: 1, x: 2))