From ded9b78d254f1168b9dababae723fa926958f05a Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Wed, 31 Aug 2022 18:00:57 -0700 Subject: [PATCH 01/37] Warn on redundant comparison to 'Optional.none', like we already do for 'nil' --- include/swift/AST/DiagnosticsSema.def | 3 ++ lib/Sema/MiscDiagnostics.cpp | 44 ++++++++++++++++----------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index d0ba730396b6d..de22a229b227d 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4009,6 +4009,9 @@ WARNING(use_of_qq_on_non_optional_value,none, WARNING(nonoptional_compare_to_nil,none, "comparing non-optional value of type %0 to 'nil' always returns" " %select{false|true}1", (Type, bool)) +WARNING(nonoptional_compare_to_optional_none_case,none, + "comparing non-optional value of type %0 to 'Optional.none' always returns" + " %select{false|true}1", (Type, bool)) WARNING(optional_check_nonoptional,none, "non-optional expression of type %0 used in a check for optionals", (Type)) diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 83501d41679bb..612ab080feddf 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -1282,25 +1282,19 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, } - /// Return true if this is a 'nil' literal. This looks - /// like this if the type is Optional: - /// - /// (dot_syntax_call_expr implicit type='Int?' - /// (declref_expr implicit decl=Optional.none) - /// (type_expr type=Int?)) - /// - /// Or like this if it is any other ExpressibleByNilLiteral type: - /// - /// (nil_literal_expr) - /// - bool isTypeCheckedOptionalNil(Expr *E) { + /// Returns true if this is a 'nil' literal + bool isNilLiteral(Expr *E) { if (dyn_cast(E)) return true; - + return false; + } + + /// Returns true if this is a expression is a reference to + /// the `Optional.none` case specifically (e.g. not `nil`). + bool isOptionalNoneCase(Expr *E) { auto CE = dyn_cast(E->getSemanticsProvidingExpr()); - if (!CE || !CE->isImplicit()) + if (!CE) return false; - // First case -- Optional.none if (auto DRE = dyn_cast(CE->getSemanticFn())) return DRE->getDecl() == Ctx.getOptionalNoneDecl(); @@ -1345,9 +1339,12 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, if (calleeName == "==" || calleeName == "!=" || calleeName == "===" || calleeName == "!==") { + bool isTrue = calleeName == "!=" || calleeName == "!=="; + + // Diagnose a redundant comparison of a non-optional to a `nil` literal if (((subExpr = isImplicitPromotionToOptional(lhs)) && - isTypeCheckedOptionalNil(rhs)) || - (isTypeCheckedOptionalNil(lhs) && + isNilLiteral(rhs)) || + (isNilLiteral(lhs) && (subExpr = isImplicitPromotionToOptional(rhs)))) { bool isTrue = calleeName == "!=" || calleeName == "!=="; @@ -1357,6 +1354,19 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, .highlight(rhs->getSourceRange()); return; } + + // Diagnose a redundant comparison of a non-optional to the `Optional.none` case + if (((subExpr = isImplicitPromotionToOptional(lhs)) && + isOptionalNoneCase(rhs)) || + (isOptionalNoneCase(lhs) && + (subExpr = isImplicitPromotionToOptional(rhs)))) { + + Ctx.Diags.diagnose(DRE->getLoc(), diag::nonoptional_compare_to_optional_none_case, + subExpr->getType(), isTrue) + .highlight(lhs->getSourceRange()) + .highlight(rhs->getSourceRange()); + return; + } } } }; From 88b65f406fcec4e9183405df31043c5b2223ebd4 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Wed, 31 Aug 2022 18:09:37 -0700 Subject: [PATCH 02/37] Update tests --- test/ClangImporter/nullability.swift | 1 + test/Constraints/diagnostics.swift | 9 +++++++++ test/expr/cast/nil_value_to_optional.swift | 5 +++++ test/expr/expressions.swift | 5 +++++ 4 files changed, 20 insertions(+) diff --git a/test/ClangImporter/nullability.swift b/test/ClangImporter/nullability.swift index a7419ff57cbf5..2fe3660aef5a1 100644 --- a/test/ClangImporter/nullability.swift +++ b/test/ClangImporter/nullability.swift @@ -6,6 +6,7 @@ func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let ao1: Any = sc.methodA(osc) _ = ao1 if sc.methodA(osc) == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' always returns false}} + if sc.methodA(osc) == Optional.none { } // expected-warning {{comparing non-optional value of type 'Any' to 'Optional.none' always returns false}} let ao2: Any = sc.methodB(nil) _ = ao2 diff --git a/test/Constraints/diagnostics.swift b/test/Constraints/diagnostics.swift index e725725d3052a..3685d0974ed9e 100644 --- a/test/Constraints/diagnostics.swift +++ b/test/Constraints/diagnostics.swift @@ -746,9 +746,13 @@ class C_44203 { _ = (i === nil) // expected-error {{value of type 'Int?' cannot be compared by reference; did you mean to compare by value?}} {{12-15===}} _ = (bytes === nil) // expected-error {{type 'UnsafeMutablePointer' is not optional, value can never be nil}} _ = (self === nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns false}} + _ = (self === .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns false}} + _ = (self === Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns false}} _ = (i !== nil) // expected-error {{value of type 'Int?' cannot be compared by reference; did you mean to compare by value?}} {{12-15=!=}} _ = (bytes !== nil) // expected-error {{type 'UnsafeMutablePointer' is not optional, value can never be nil}} _ = (self !== nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns true}} + _ = (self !== .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns true}} + _ = (self !== Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns true}} } } @@ -757,6 +761,11 @@ func nilComparison(i: Int, o: AnyObject) { _ = nil == i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns false}} _ = i != nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns true}} _ = nil != i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns true}} + + _ = i == Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns false}} + _ = Optional.none == i // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns false}} + _ = i != Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns true}} + _ = Optional.none != i // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns true}} // FIXME(integers): uncomment these tests once the < is no longer ambiguous // _ = i < nil // _xpected-error {{type 'Int' is not optional, value can never be nil}} diff --git a/test/expr/cast/nil_value_to_optional.swift b/test/expr/cast/nil_value_to_optional.swift index dab75fb7626b8..f336b84216a71 100644 --- a/test/expr/cast/nil_value_to_optional.swift +++ b/test/expr/cast/nil_value_to_optional.swift @@ -7,6 +7,8 @@ func markUsed(_ t: T) {} markUsed(t != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' always returns true}} markUsed(f != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' always returns true}} +markUsed(t != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'Optional.none' always returns true}} +markUsed(f != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'Optional.none' always returns true}} class C : Equatable {} @@ -16,6 +18,9 @@ func == (lhs: C, rhs: C) -> Bool { func test(_ c: C) { if c == nil {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' always returns false}} + if c == .none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} + if c == Optional.none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} + if c == C?.none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} } class D {} diff --git a/test/expr/expressions.swift b/test/expr/expressions.swift index 46e2b1d86278f..2773d644966f7 100644 --- a/test/expr/expressions.swift +++ b/test/expr/expressions.swift @@ -767,6 +767,11 @@ _ = nil == Int.self // expected-warning {{comparing non-optional value of type _ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} _ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} +_ = Int.self == .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} +_ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} +_ = Int.self != .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns true}} +_ = .none != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns true}} + // Disallow postfix ? when not chaining func testOptionalChaining(_ a : Int?, b : Int!, c : Int??) { _ = a? // expected-error {{optional chain has no effect, expression already produces 'Int?'}} {{8-9=}} From 2e7656218ec1fd39ed17a69eee79b835f8d82db1 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 1 Sep 2022 06:23:42 -0700 Subject: [PATCH 03/37] Combine the two diagnostics --- include/swift/AST/DiagnosticsSema.def | 5 +-- lib/Sema/MiscDiagnostics.cpp | 42 +++++++++------------- test/ClangImporter/nullability.swift | 12 +++---- test/Constraints/diagnostics.swift | 38 ++++++++++---------- test/expr/cast/nil_value_to_optional.swift | 18 +++++----- test/expr/expressions.swift | 18 +++++----- 6 files changed, 60 insertions(+), 73 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index de22a229b227d..d0902799b17a2 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4007,10 +4007,7 @@ WARNING(use_of_qq_on_non_optional_value,none, "left side of nil coalescing operator '?""?' has non-optional type %0, " "so the right side is never used", (Type)) WARNING(nonoptional_compare_to_nil,none, - "comparing non-optional value of type %0 to 'nil' always returns" - " %select{false|true}1", (Type, bool)) -WARNING(nonoptional_compare_to_optional_none_case,none, - "comparing non-optional value of type %0 to 'Optional.none' always returns" + "comparing non-optional value of type %0 to 'nil' or 'Optional.none' always returns" " %select{false|true}1", (Type, bool)) WARNING(optional_check_nonoptional,none, "non-optional expression of type %0 used in a check for optionals", diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 612ab080feddf..3e478b1e8172a 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -1282,19 +1282,25 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, } - /// Returns true if this is a 'nil' literal - bool isNilLiteral(Expr *E) { + /// Return true if this is a 'nil' literal. This looks + /// like this if the type is Optional: + /// + /// (dot_syntax_call_expr implicit type='Int?' + /// (declref_expr implicit decl=Optional.none) + /// (type_expr type=Int?)) + /// + /// Or like this if it is any other ExpressibleByNilLiteral type: + /// + /// (nil_literal_expr) + /// + bool isTypeCheckedOptionalNil(Expr *E) { if (dyn_cast(E)) return true; - return false; - } - - /// Returns true if this is a expression is a reference to - /// the `Optional.none` case specifically (e.g. not `nil`). - bool isOptionalNoneCase(Expr *E) { + auto CE = dyn_cast(E->getSemanticsProvidingExpr()); if (!CE) return false; + // First case -- Optional.none if (auto DRE = dyn_cast(CE->getSemanticFn())) return DRE->getDecl() == Ctx.getOptionalNoneDecl(); @@ -1339,12 +1345,9 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, if (calleeName == "==" || calleeName == "!=" || calleeName == "===" || calleeName == "!==") { - bool isTrue = calleeName == "!=" || calleeName == "!=="; - - // Diagnose a redundant comparison of a non-optional to a `nil` literal if (((subExpr = isImplicitPromotionToOptional(lhs)) && - isNilLiteral(rhs)) || - (isNilLiteral(lhs) && + isTypeCheckedOptionalNil(rhs)) || + (isTypeCheckedOptionalNil(lhs) && (subExpr = isImplicitPromotionToOptional(rhs)))) { bool isTrue = calleeName == "!=" || calleeName == "!=="; @@ -1354,19 +1357,6 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, .highlight(rhs->getSourceRange()); return; } - - // Diagnose a redundant comparison of a non-optional to the `Optional.none` case - if (((subExpr = isImplicitPromotionToOptional(lhs)) && - isOptionalNoneCase(rhs)) || - (isOptionalNoneCase(lhs) && - (subExpr = isImplicitPromotionToOptional(rhs)))) { - - Ctx.Diags.diagnose(DRE->getLoc(), diag::nonoptional_compare_to_optional_none_case, - subExpr->getType(), isTrue) - .highlight(lhs->getSourceRange()) - .highlight(rhs->getSourceRange()); - return; - } } } }; diff --git a/test/ClangImporter/nullability.swift b/test/ClangImporter/nullability.swift index 2fe3660aef5a1..15bac7ca39dc8 100644 --- a/test/ClangImporter/nullability.swift +++ b/test/ClangImporter/nullability.swift @@ -5,12 +5,12 @@ import CoreCooling func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let ao1: Any = sc.methodA(osc) _ = ao1 - if sc.methodA(osc) == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' always returns false}} - if sc.methodA(osc) == Optional.none { } // expected-warning {{comparing non-optional value of type 'Any' to 'Optional.none' always returns false}} + if sc.methodA(osc) == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' or 'Optional.none' always returns false}} + if sc.methodA(osc) == Optional.none { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' or 'Optional.none' always returns false}} let ao2: Any = sc.methodB(nil) _ = ao2 - if sc.methodA(osc) == nil { }// expected-warning {{comparing non-optional value of type 'Any' to 'nil' always returns false}} + if sc.methodA(osc) == nil { }// expected-warning {{comparing non-optional value of type 'Any' to 'nil' or 'Optional.none' always returns false}} let ao3: Any? = sc.property.flatMap { .some($0) } _ = ao3 @@ -19,7 +19,7 @@ func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let ao4: Any = sc.methodD() _ = ao4 - if sc.methodD() == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' always returns false}} + if sc.methodD() == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' or 'Optional.none' always returns false}} sc.methodE(sc) sc.methodE(osc) // expected-error{{value of optional type 'SomeClass?' must be unwrapped}} @@ -44,7 +44,7 @@ func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let sc2 = SomeClass(int: ci) let sc2a: SomeClass = sc2 _ = sc2a - if sc2 == nil { } // expected-warning {{comparing non-optional value of type 'SomeClass' to 'nil' always returns false}} + if sc2 == nil { } // expected-warning {{comparing non-optional value of type 'SomeClass' to 'nil' or 'Optional.none' always returns false}} let sc3 = SomeClass(double: 1.5) if sc3 == nil { } // okay @@ -56,7 +56,7 @@ func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let sc4 = sc.returnMe() let sc4a: SomeClass = sc4 _ = sc4a - if sc4 == nil { } // expected-warning {{comparing non-optional value of type 'SomeClass' to 'nil' always returns false}} + if sc4 == nil { } // expected-warning {{comparing non-optional value of type 'SomeClass' to 'nil' or 'Optional.none' always returns false}} } // Nullability with CF types. diff --git a/test/Constraints/diagnostics.swift b/test/Constraints/diagnostics.swift index 3685d0974ed9e..4e4c4a12630e2 100644 --- a/test/Constraints/diagnostics.swift +++ b/test/Constraints/diagnostics.swift @@ -222,7 +222,7 @@ class r20201968C { // QoI: Poor compilation error calling assert func r21459429(_ a : Int) { assert(a != nil, "ASSERT COMPILATION ERROR") - // expected-warning @-1 {{comparing non-optional value of type 'Int' to 'nil' always returns true}} + // expected-warning @-1 {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} } @@ -537,7 +537,7 @@ func r21684487() { func r18397777(_ d : r21447318?) { let c = r21447318() - if c != nil { // expected-warning {{comparing non-optional value of type 'r21447318' to 'nil' always returns true}} + if c != nil { // expected-warning {{comparing non-optional value of type 'r21447318' to 'nil' or 'Optional.none' always returns true}} } if d { // expected-error {{optional type 'r21447318?' cannot be used as a boolean; test for '!= nil' instead}} {{6-6=(}} {{7-7= != nil)}} @@ -745,27 +745,27 @@ class C_44203 { func f(bytes : UnsafeMutablePointer, _ i : Int?) { _ = (i === nil) // expected-error {{value of type 'Int?' cannot be compared by reference; did you mean to compare by value?}} {{12-15===}} _ = (bytes === nil) // expected-error {{type 'UnsafeMutablePointer' is not optional, value can never be nil}} - _ = (self === nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns false}} - _ = (self === .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns false}} - _ = (self === Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns false}} + _ = (self === nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns false}} + _ = (self === .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns false}} + _ = (self === Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns false}} _ = (i !== nil) // expected-error {{value of type 'Int?' cannot be compared by reference; did you mean to compare by value?}} {{12-15=!=}} _ = (bytes !== nil) // expected-error {{type 'UnsafeMutablePointer' is not optional, value can never be nil}} - _ = (self !== nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns true}} - _ = (self !== .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns true}} - _ = (self !== Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns true}} + _ = (self !== nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns true}} + _ = (self !== .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns true}} + _ = (self !== Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns true}} } } func nilComparison(i: Int, o: AnyObject) { - _ = i == nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns false}} - _ = nil == i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns false}} - _ = i != nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns true}} - _ = nil != i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns true}} - - _ = i == Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns false}} - _ = Optional.none == i // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns false}} - _ = i != Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns true}} - _ = Optional.none != i // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns true}} + _ = i == nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns false}} + _ = nil == i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns false}} + _ = i != nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} + _ = nil != i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} + + _ = i == Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns false}} + _ = Optional.none == i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns false}} + _ = i != Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} + _ = Optional.none != i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} // FIXME(integers): uncomment these tests once the < is no longer ambiguous // _ = i < nil // _xpected-error {{type 'Int' is not optional, value can never be nil}} @@ -777,8 +777,8 @@ func nilComparison(i: Int, o: AnyObject) { // _ = i >= nil // _xpected-error {{type 'Int' is not optional, value can never be nil}} // _ = nil >= i // _xpected-error {{type 'Int' is not optional, value can never be nil}} - _ = o === nil // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns false}} - _ = o !== nil // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns true}} + _ = o === nil // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns false}} + _ = o !== nil // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns true}} } // QoI: incorrect ambiguity error due to implicit conversion diff --git a/test/expr/cast/nil_value_to_optional.swift b/test/expr/cast/nil_value_to_optional.swift index f336b84216a71..49a6ef6979fb3 100644 --- a/test/expr/cast/nil_value_to_optional.swift +++ b/test/expr/cast/nil_value_to_optional.swift @@ -5,10 +5,10 @@ var f = false func markUsed(_ t: T) {} -markUsed(t != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' always returns true}} -markUsed(f != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' always returns true}} -markUsed(t != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'Optional.none' always returns true}} -markUsed(f != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'Optional.none' always returns true}} +markUsed(t != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' or 'Optional.none' always returns true}} +markUsed(f != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' or 'Optional.none' always returns true}} +markUsed(t != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' or 'Optional.none' always returns true}} +markUsed(f != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' or 'Optional.none' always returns true}} class C : Equatable {} @@ -17,10 +17,10 @@ func == (lhs: C, rhs: C) -> Bool { } func test(_ c: C) { - if c == nil {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' always returns false}} - if c == .none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} - if c == Optional.none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} - if c == C?.none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} + if c == nil {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' or 'Optional.none' always returns false}} + if c == .none {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' or 'Optional.none' always returns false}} + if c == Optional.none {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' or 'Optional.none' always returns false}} + if c == C?.none {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' or 'Optional.none' always returns false}} } class D {} @@ -34,4 +34,4 @@ _ = d! // expected-error {{cannot force unwrap value of non-optional type 'D'}} _ = dopt == nil _ = diuopt == nil _ = diuopt is ExpressibleByNilLiteral // expected-warning {{'is' test is always true}} -_ = produceD() is ExpressibleByNilLiteral // expected-warning {{'is' test is always true}} +_ = produceD() is ExpressibleByNilLiteral // expected-warning {{'is' test is always true}} \ No newline at end of file diff --git a/test/expr/expressions.swift b/test/expr/expressions.swift index 2773d644966f7..76ed90c6bbe07 100644 --- a/test/expr/expressions.swift +++ b/test/expr/expressions.swift @@ -762,15 +762,15 @@ func invalidDictionaryLiteral() { //===----------------------------------------------------------------------===// // nil/metatype comparisons //===----------------------------------------------------------------------===// -_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}} -_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}} -_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} -_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} - -_ = Int.self == .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} -_ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} -_ = Int.self != .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns true}} -_ = .none != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns true}} +_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns false}} +_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns false}} +_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns true}} +_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns true}} + +_ = Int.self == .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns false}} +_ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns false}} +_ = Int.self != .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns true}} +_ = .none != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns true}} // Disallow postfix ? when not chaining func testOptionalChaining(_ a : Int?, b : Int!, c : Int??) { From 1b224bd945dd1f0cb8f9df192c984840b116da6b Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 1 Sep 2022 06:24:05 -0700 Subject: [PATCH 04/37] Add a test that verifyies comparing to a .none case that isn't Optional.none still works --- test/expr/cast/nil_value_to_optional.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/test/expr/cast/nil_value_to_optional.swift b/test/expr/cast/nil_value_to_optional.swift index 49a6ef6979fb3..d178e83361dee 100644 --- a/test/expr/cast/nil_value_to_optional.swift +++ b/test/expr/cast/nil_value_to_optional.swift @@ -34,4 +34,14 @@ _ = d! // expected-error {{cannot force unwrap value of non-optional type 'D'}} _ = dopt == nil _ = diuopt == nil _ = diuopt is ExpressibleByNilLiteral // expected-warning {{'is' test is always true}} -_ = produceD() is ExpressibleByNilLiteral // expected-warning {{'is' test is always true}} \ No newline at end of file +_ = produceD() is ExpressibleByNilLiteral // expected-warning {{'is' test is always true}} + +enum E { + case none +} +func test(_ e: E) { + _ = e == .none + _ = e == E.none + _ = e == Optional.none // expected-warning {{comparing non-optional value of type 'E' to 'nil' or 'Optional.none' always returns false}} + _ = e == E?.none // expected-warning {{comparing non-optional value of type 'E' to 'nil' or 'Optional.none' always returns false}} +} \ No newline at end of file From 568549f2c329e66ba1f01fcb185c742e0f4f41e3 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 1 Sep 2022 06:38:58 -0700 Subject: [PATCH 05/37] Update comment --- lib/Sema/MiscDiagnostics.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 3e478b1e8172a..0e72727f676b6 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -1285,9 +1285,12 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, /// Return true if this is a 'nil' literal. This looks /// like this if the type is Optional: /// - /// (dot_syntax_call_expr implicit type='Int?' - /// (declref_expr implicit decl=Optional.none) - /// (type_expr type=Int?)) + /// (dot_syntax_call_expr type='String?' + /// (declref_expr type='(Optional.Type) -> Optional' + /// decl=Swift.(file).Optional.none function_ref=unapplied) + /// (argument_list implicit + /// (argument + /// (type_expr implicit type='String?.Type' typerepr='String?')))) /// /// Or like this if it is any other ExpressibleByNilLiteral type: /// From c2bf0f943e55866e467ad8a71485492719e3976e Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 1 Sep 2022 08:11:46 -0700 Subject: [PATCH 06/37] Revert "Combine the two diagnostics" This reverts commit 2e7656218ec1fd39ed17a69eee79b835f8d82db1. --- include/swift/AST/DiagnosticsSema.def | 5 ++- lib/Sema/MiscDiagnostics.cpp | 45 +++++++++++++--------- test/ClangImporter/nullability.swift | 12 +++--- test/Constraints/diagnostics.swift | 38 +++++++++--------- test/expr/cast/nil_value_to_optional.swift | 18 ++++----- test/expr/expressions.swift | 18 ++++----- 6 files changed, 73 insertions(+), 63 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index d0902799b17a2..de22a229b227d 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4007,7 +4007,10 @@ WARNING(use_of_qq_on_non_optional_value,none, "left side of nil coalescing operator '?""?' has non-optional type %0, " "so the right side is never used", (Type)) WARNING(nonoptional_compare_to_nil,none, - "comparing non-optional value of type %0 to 'nil' or 'Optional.none' always returns" + "comparing non-optional value of type %0 to 'nil' always returns" + " %select{false|true}1", (Type, bool)) +WARNING(nonoptional_compare_to_optional_none_case,none, + "comparing non-optional value of type %0 to 'Optional.none' always returns" " %select{false|true}1", (Type, bool)) WARNING(optional_check_nonoptional,none, "non-optional expression of type %0 used in a check for optionals", diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 0e72727f676b6..612ab080feddf 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -1282,28 +1282,19 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, } - /// Return true if this is a 'nil' literal. This looks - /// like this if the type is Optional: - /// - /// (dot_syntax_call_expr type='String?' - /// (declref_expr type='(Optional.Type) -> Optional' - /// decl=Swift.(file).Optional.none function_ref=unapplied) - /// (argument_list implicit - /// (argument - /// (type_expr implicit type='String?.Type' typerepr='String?')))) - /// - /// Or like this if it is any other ExpressibleByNilLiteral type: - /// - /// (nil_literal_expr) - /// - bool isTypeCheckedOptionalNil(Expr *E) { + /// Returns true if this is a 'nil' literal + bool isNilLiteral(Expr *E) { if (dyn_cast(E)) return true; - + return false; + } + + /// Returns true if this is a expression is a reference to + /// the `Optional.none` case specifically (e.g. not `nil`). + bool isOptionalNoneCase(Expr *E) { auto CE = dyn_cast(E->getSemanticsProvidingExpr()); if (!CE) return false; - // First case -- Optional.none if (auto DRE = dyn_cast(CE->getSemanticFn())) return DRE->getDecl() == Ctx.getOptionalNoneDecl(); @@ -1348,9 +1339,12 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, if (calleeName == "==" || calleeName == "!=" || calleeName == "===" || calleeName == "!==") { + bool isTrue = calleeName == "!=" || calleeName == "!=="; + + // Diagnose a redundant comparison of a non-optional to a `nil` literal if (((subExpr = isImplicitPromotionToOptional(lhs)) && - isTypeCheckedOptionalNil(rhs)) || - (isTypeCheckedOptionalNil(lhs) && + isNilLiteral(rhs)) || + (isNilLiteral(lhs) && (subExpr = isImplicitPromotionToOptional(rhs)))) { bool isTrue = calleeName == "!=" || calleeName == "!=="; @@ -1360,6 +1354,19 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, .highlight(rhs->getSourceRange()); return; } + + // Diagnose a redundant comparison of a non-optional to the `Optional.none` case + if (((subExpr = isImplicitPromotionToOptional(lhs)) && + isOptionalNoneCase(rhs)) || + (isOptionalNoneCase(lhs) && + (subExpr = isImplicitPromotionToOptional(rhs)))) { + + Ctx.Diags.diagnose(DRE->getLoc(), diag::nonoptional_compare_to_optional_none_case, + subExpr->getType(), isTrue) + .highlight(lhs->getSourceRange()) + .highlight(rhs->getSourceRange()); + return; + } } } }; diff --git a/test/ClangImporter/nullability.swift b/test/ClangImporter/nullability.swift index 15bac7ca39dc8..2fe3660aef5a1 100644 --- a/test/ClangImporter/nullability.swift +++ b/test/ClangImporter/nullability.swift @@ -5,12 +5,12 @@ import CoreCooling func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let ao1: Any = sc.methodA(osc) _ = ao1 - if sc.methodA(osc) == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' or 'Optional.none' always returns false}} - if sc.methodA(osc) == Optional.none { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' or 'Optional.none' always returns false}} + if sc.methodA(osc) == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' always returns false}} + if sc.methodA(osc) == Optional.none { } // expected-warning {{comparing non-optional value of type 'Any' to 'Optional.none' always returns false}} let ao2: Any = sc.methodB(nil) _ = ao2 - if sc.methodA(osc) == nil { }// expected-warning {{comparing non-optional value of type 'Any' to 'nil' or 'Optional.none' always returns false}} + if sc.methodA(osc) == nil { }// expected-warning {{comparing non-optional value of type 'Any' to 'nil' always returns false}} let ao3: Any? = sc.property.flatMap { .some($0) } _ = ao3 @@ -19,7 +19,7 @@ func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let ao4: Any = sc.methodD() _ = ao4 - if sc.methodD() == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' or 'Optional.none' always returns false}} + if sc.methodD() == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' always returns false}} sc.methodE(sc) sc.methodE(osc) // expected-error{{value of optional type 'SomeClass?' must be unwrapped}} @@ -44,7 +44,7 @@ func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let sc2 = SomeClass(int: ci) let sc2a: SomeClass = sc2 _ = sc2a - if sc2 == nil { } // expected-warning {{comparing non-optional value of type 'SomeClass' to 'nil' or 'Optional.none' always returns false}} + if sc2 == nil { } // expected-warning {{comparing non-optional value of type 'SomeClass' to 'nil' always returns false}} let sc3 = SomeClass(double: 1.5) if sc3 == nil { } // okay @@ -56,7 +56,7 @@ func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let sc4 = sc.returnMe() let sc4a: SomeClass = sc4 _ = sc4a - if sc4 == nil { } // expected-warning {{comparing non-optional value of type 'SomeClass' to 'nil' or 'Optional.none' always returns false}} + if sc4 == nil { } // expected-warning {{comparing non-optional value of type 'SomeClass' to 'nil' always returns false}} } // Nullability with CF types. diff --git a/test/Constraints/diagnostics.swift b/test/Constraints/diagnostics.swift index 4e4c4a12630e2..3685d0974ed9e 100644 --- a/test/Constraints/diagnostics.swift +++ b/test/Constraints/diagnostics.swift @@ -222,7 +222,7 @@ class r20201968C { // QoI: Poor compilation error calling assert func r21459429(_ a : Int) { assert(a != nil, "ASSERT COMPILATION ERROR") - // expected-warning @-1 {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} + // expected-warning @-1 {{comparing non-optional value of type 'Int' to 'nil' always returns true}} } @@ -537,7 +537,7 @@ func r21684487() { func r18397777(_ d : r21447318?) { let c = r21447318() - if c != nil { // expected-warning {{comparing non-optional value of type 'r21447318' to 'nil' or 'Optional.none' always returns true}} + if c != nil { // expected-warning {{comparing non-optional value of type 'r21447318' to 'nil' always returns true}} } if d { // expected-error {{optional type 'r21447318?' cannot be used as a boolean; test for '!= nil' instead}} {{6-6=(}} {{7-7= != nil)}} @@ -745,27 +745,27 @@ class C_44203 { func f(bytes : UnsafeMutablePointer, _ i : Int?) { _ = (i === nil) // expected-error {{value of type 'Int?' cannot be compared by reference; did you mean to compare by value?}} {{12-15===}} _ = (bytes === nil) // expected-error {{type 'UnsafeMutablePointer' is not optional, value can never be nil}} - _ = (self === nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns false}} - _ = (self === .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns false}} - _ = (self === Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns false}} + _ = (self === nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns false}} + _ = (self === .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns false}} + _ = (self === Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns false}} _ = (i !== nil) // expected-error {{value of type 'Int?' cannot be compared by reference; did you mean to compare by value?}} {{12-15=!=}} _ = (bytes !== nil) // expected-error {{type 'UnsafeMutablePointer' is not optional, value can never be nil}} - _ = (self !== nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns true}} - _ = (self !== .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns true}} - _ = (self !== Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns true}} + _ = (self !== nil) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns true}} + _ = (self !== .none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns true}} + _ = (self !== Optional.none) // expected-warning {{comparing non-optional value of type 'AnyObject' to 'Optional.none' always returns true}} } } func nilComparison(i: Int, o: AnyObject) { - _ = i == nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns false}} - _ = nil == i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns false}} - _ = i != nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} - _ = nil != i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} - - _ = i == Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns false}} - _ = Optional.none == i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns false}} - _ = i != Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} - _ = Optional.none != i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' or 'Optional.none' always returns true}} + _ = i == nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns false}} + _ = nil == i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns false}} + _ = i != nil // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns true}} + _ = nil != i // expected-warning {{comparing non-optional value of type 'Int' to 'nil' always returns true}} + + _ = i == Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns false}} + _ = Optional.none == i // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns false}} + _ = i != Optional.none // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns true}} + _ = Optional.none != i // expected-warning {{comparing non-optional value of type 'Int' to 'Optional.none' always returns true}} // FIXME(integers): uncomment these tests once the < is no longer ambiguous // _ = i < nil // _xpected-error {{type 'Int' is not optional, value can never be nil}} @@ -777,8 +777,8 @@ func nilComparison(i: Int, o: AnyObject) { // _ = i >= nil // _xpected-error {{type 'Int' is not optional, value can never be nil}} // _ = nil >= i // _xpected-error {{type 'Int' is not optional, value can never be nil}} - _ = o === nil // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns false}} - _ = o !== nil // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' or 'Optional.none' always returns true}} + _ = o === nil // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns false}} + _ = o !== nil // expected-warning {{comparing non-optional value of type 'AnyObject' to 'nil' always returns true}} } // QoI: incorrect ambiguity error due to implicit conversion diff --git a/test/expr/cast/nil_value_to_optional.swift b/test/expr/cast/nil_value_to_optional.swift index d178e83361dee..e5b85b5396752 100644 --- a/test/expr/cast/nil_value_to_optional.swift +++ b/test/expr/cast/nil_value_to_optional.swift @@ -5,10 +5,10 @@ var f = false func markUsed(_ t: T) {} -markUsed(t != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' or 'Optional.none' always returns true}} -markUsed(f != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' or 'Optional.none' always returns true}} -markUsed(t != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' or 'Optional.none' always returns true}} -markUsed(f != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' or 'Optional.none' always returns true}} +markUsed(t != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' always returns true}} +markUsed(f != nil) // expected-warning {{comparing non-optional value of type 'Bool' to 'nil' always returns true}} +markUsed(t != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'Optional.none' always returns true}} +markUsed(f != Optional.none) // expected-warning {{comparing non-optional value of type 'Bool' to 'Optional.none' always returns true}} class C : Equatable {} @@ -17,10 +17,10 @@ func == (lhs: C, rhs: C) -> Bool { } func test(_ c: C) { - if c == nil {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' or 'Optional.none' always returns false}} - if c == .none {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' or 'Optional.none' always returns false}} - if c == Optional.none {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' or 'Optional.none' always returns false}} - if c == C?.none {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' or 'Optional.none' always returns false}} + if c == nil {} // expected-warning {{comparing non-optional value of type 'C' to 'nil' always returns false}} + if c == .none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} + if c == Optional.none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} + if c == C?.none {} // expected-warning {{comparing non-optional value of type 'C' to 'Optional.none' always returns false}} } class D {} @@ -44,4 +44,4 @@ func test(_ e: E) { _ = e == E.none _ = e == Optional.none // expected-warning {{comparing non-optional value of type 'E' to 'nil' or 'Optional.none' always returns false}} _ = e == E?.none // expected-warning {{comparing non-optional value of type 'E' to 'nil' or 'Optional.none' always returns false}} -} \ No newline at end of file +} diff --git a/test/expr/expressions.swift b/test/expr/expressions.swift index 76ed90c6bbe07..2773d644966f7 100644 --- a/test/expr/expressions.swift +++ b/test/expr/expressions.swift @@ -762,15 +762,15 @@ func invalidDictionaryLiteral() { //===----------------------------------------------------------------------===// // nil/metatype comparisons //===----------------------------------------------------------------------===// -_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns false}} -_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns false}} -_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns true}} -_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns true}} - -_ = Int.self == .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns false}} -_ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns false}} -_ = Int.self != .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns true}} -_ = .none != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' or 'Optional.none' always returns true}} +_ = Int.self == nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}} +_ = nil == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns false}} +_ = Int.self != nil // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} +_ = nil != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'nil' always returns true}} + +_ = Int.self == .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} +_ = .none == Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns false}} +_ = Int.self != .none // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns true}} +_ = .none != Int.self // expected-warning {{comparing non-optional value of type 'any Any.Type' to 'Optional.none' always returns true}} // Disallow postfix ? when not chaining func testOptionalChaining(_ a : Int?, b : Int!, c : Int??) { From c3e74e06b4efa19f929680671e359ec223e3e74c Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 1 Sep 2022 08:18:17 -0700 Subject: [PATCH 07/37] Use a single warning, but dynamically specify 'nil' or 'Optional.none' --- include/swift/AST/DiagnosticsSema.def | 7 +--- lib/Sema/MiscDiagnostics.cpp | 48 ++++++++++------------ test/expr/cast/nil_value_to_optional.swift | 4 +- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index de22a229b227d..f39e109ffcb61 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -4007,11 +4007,8 @@ WARNING(use_of_qq_on_non_optional_value,none, "left side of nil coalescing operator '?""?' has non-optional type %0, " "so the right side is never used", (Type)) WARNING(nonoptional_compare_to_nil,none, - "comparing non-optional value of type %0 to 'nil' always returns" - " %select{false|true}1", (Type, bool)) -WARNING(nonoptional_compare_to_optional_none_case,none, - "comparing non-optional value of type %0 to 'Optional.none' always returns" - " %select{false|true}1", (Type, bool)) + "comparing non-optional value of type %0 to '%select{Optional.none|nil}1' always returns" + " %select{false|true}2", (Type, bool, bool)) WARNING(optional_check_nonoptional,none, "non-optional expression of type %0 used in a check for optionals", (Type)) diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 612ab080feddf..2aa324f7e7108 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -1282,19 +1282,28 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, } - /// Returns true if this is a 'nil' literal - bool isNilLiteral(Expr *E) { + /// Return true if this is a 'nil' literal. This looks + /// like this if the type is Optional: + /// + /// (dot_syntax_call_expr type='String?' + /// (declref_expr type='(Optional.Type) -> Optional' + /// decl=Swift.(file).Optional.none function_ref=unapplied) + /// (argument_list implicit + /// (argument + /// (type_expr implicit type='String?.Type' typerepr='String?')))) + /// + /// Or like this if it is any other ExpressibleByNilLiteral type: + /// + /// (nil_literal_expr) + /// + bool isTypeCheckedOptionalNil(Expr *E) { if (dyn_cast(E)) return true; - return false; - } - - /// Returns true if this is a expression is a reference to - /// the `Optional.none` case specifically (e.g. not `nil`). - bool isOptionalNoneCase(Expr *E) { + auto CE = dyn_cast(E->getSemanticsProvidingExpr()); if (!CE) return false; + // First case -- Optional.none if (auto DRE = dyn_cast(CE->getSemanticFn())) return DRE->getDecl() == Ctx.getOptionalNoneDecl(); @@ -1339,30 +1348,15 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, if (calleeName == "==" || calleeName == "!=" || calleeName == "===" || calleeName == "!==") { - bool isTrue = calleeName == "!=" || calleeName == "!=="; - - // Diagnose a redundant comparison of a non-optional to a `nil` literal if (((subExpr = isImplicitPromotionToOptional(lhs)) && - isNilLiteral(rhs)) || - (isNilLiteral(lhs) && + isTypeCheckedOptionalNil(rhs)) || + (isTypeCheckedOptionalNil(lhs) && (subExpr = isImplicitPromotionToOptional(rhs)))) { bool isTrue = calleeName == "!=" || calleeName == "!=="; + bool isNilLiteral = isa(lhs) || isa(rhs); Ctx.Diags.diagnose(DRE->getLoc(), diag::nonoptional_compare_to_nil, - subExpr->getType(), isTrue) - .highlight(lhs->getSourceRange()) - .highlight(rhs->getSourceRange()); - return; - } - - // Diagnose a redundant comparison of a non-optional to the `Optional.none` case - if (((subExpr = isImplicitPromotionToOptional(lhs)) && - isOptionalNoneCase(rhs)) || - (isOptionalNoneCase(lhs) && - (subExpr = isImplicitPromotionToOptional(rhs)))) { - - Ctx.Diags.diagnose(DRE->getLoc(), diag::nonoptional_compare_to_optional_none_case, - subExpr->getType(), isTrue) + subExpr->getType(), isNilLiteral, isTrue) .highlight(lhs->getSourceRange()) .highlight(rhs->getSourceRange()); return; diff --git a/test/expr/cast/nil_value_to_optional.swift b/test/expr/cast/nil_value_to_optional.swift index e5b85b5396752..4f1ec1fa042fd 100644 --- a/test/expr/cast/nil_value_to_optional.swift +++ b/test/expr/cast/nil_value_to_optional.swift @@ -42,6 +42,6 @@ enum E { func test(_ e: E) { _ = e == .none _ = e == E.none - _ = e == Optional.none // expected-warning {{comparing non-optional value of type 'E' to 'nil' or 'Optional.none' always returns false}} - _ = e == E?.none // expected-warning {{comparing non-optional value of type 'E' to 'nil' or 'Optional.none' always returns false}} + _ = e == Optional.none // expected-warning {{comparing non-optional value of type 'E' to 'Optional.none' always returns false}} + _ = e == E?.none // expected-warning {{comparing non-optional value of type 'E' to 'Optional.none' always returns false}} } From 992a9d320dc80e95c9158252a53cc16e1028acf3 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Thu, 1 Sep 2022 11:44:55 -0700 Subject: [PATCH 08/37] remove warning test case that doesn't actually compile --- test/ClangImporter/nullability.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/test/ClangImporter/nullability.swift b/test/ClangImporter/nullability.swift index 2fe3660aef5a1..a7419ff57cbf5 100644 --- a/test/ClangImporter/nullability.swift +++ b/test/ClangImporter/nullability.swift @@ -6,7 +6,6 @@ func testSomeClass(_ sc: SomeClass, osc: SomeClass?) { let ao1: Any = sc.methodA(osc) _ = ao1 if sc.methodA(osc) == nil { } // expected-warning {{comparing non-optional value of type 'Any' to 'nil' always returns false}} - if sc.methodA(osc) == Optional.none { } // expected-warning {{comparing non-optional value of type 'Any' to 'Optional.none' always returns false}} let ao2: Any = sc.methodB(nil) _ = ao2 From 7cbadf2c8dfe3a729a4afe7455e65169b2487a2b Mon Sep 17 00:00:00 2001 From: Karoy Lorentey Date: Tue, 6 Sep 2022 21:15:58 -0700 Subject: [PATCH 09/37] [stdlib] _modify: Ensure cleanup is always executed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup code in _modify accessors will only run reliably if it is put in a defer statement. (Statements that follow the `yield` aren’t executed if the yielded-to code throws an error.) --- stdlib/public/core/Array.swift | 2 +- stdlib/public/core/ArraySlice.swift | 2 +- stdlib/public/core/ContiguousArray.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/public/core/Array.swift b/stdlib/public/core/Array.swift index cb60e4c180d0e..383a0d05a269e 100644 --- a/stdlib/public/core/Array.swift +++ b/stdlib/public/core/Array.swift @@ -751,8 +751,8 @@ extension Array: RandomAccessCollection, MutableCollection { _makeMutableAndUnique() // makes the array native, too _checkSubscript_mutating(index) let address = _buffer.mutableFirstElementAddress + index + defer { _endMutation() } yield &address.pointee - _endMutation(); } } diff --git a/stdlib/public/core/ArraySlice.swift b/stdlib/public/core/ArraySlice.swift index e5ab7418596ff..be6363e3a9483 100644 --- a/stdlib/public/core/ArraySlice.swift +++ b/stdlib/public/core/ArraySlice.swift @@ -546,8 +546,8 @@ extension ArraySlice: RandomAccessCollection, MutableCollection { _makeMutableAndUnique() // makes the array native, too _checkSubscript_native(index) let address = _buffer.subscriptBaseAddress + index + defer { _endMutation() } yield &address.pointee - _endMutation(); } } diff --git a/stdlib/public/core/ContiguousArray.swift b/stdlib/public/core/ContiguousArray.swift index 20756033fc8f2..9ae4dc15c7195 100644 --- a/stdlib/public/core/ContiguousArray.swift +++ b/stdlib/public/core/ContiguousArray.swift @@ -409,8 +409,8 @@ extension ContiguousArray: RandomAccessCollection, MutableCollection { _makeMutableAndUnique() _checkSubscript_mutating(index) let address = _buffer.mutableFirstElementAddress + index + defer { _endMutation() } yield &address.pointee - _endMutation(); } } From 606151683cebf9f9ce9cfbd3de5fd98ea8799b3c Mon Sep 17 00:00:00 2001 From: Artem Chikin Date: Tue, 6 Sep 2022 17:19:23 -0700 Subject: [PATCH 10/37] [Dependency Scanner] Add API to query emitted diagnostics during a scan --- .../swift-c/DependencyScan/DependencyScan.h | 36 ++++++++++- .../swift/DependencyScan/DependencyScanImpl.h | 6 ++ .../DependencyScan/DependencyScanningTool.h | 30 ++++++++- lib/DependencyScan/DependencyScanningTool.cpp | 59 +++++++++++++++-- tools/libSwiftScan/libSwiftScan.cpp | 64 +++++++++++++++++++ tools/libSwiftScan/libSwiftScan.exports | 5 ++ 6 files changed, 193 insertions(+), 7 deletions(-) diff --git a/include/swift-c/DependencyScan/DependencyScan.h b/include/swift-c/DependencyScan/DependencyScan.h index 6c092acc1ca8d..87cc29113f1f3 100644 --- a/include/swift-c/DependencyScan/DependencyScan.h +++ b/include/swift-c/DependencyScan/DependencyScan.h @@ -25,7 +25,7 @@ /// SWIFTSCAN_VERSION_MINOR should increase when there are API additions. /// SWIFTSCAN_VERSION_MAJOR is intended for "major" source/ABI breaking changes. #define SWIFTSCAN_VERSION_MAJOR 0 -#define SWIFTSCAN_VERSION_MINOR 2 +#define SWIFTSCAN_VERSION_MINOR 3 SWIFTSCAN_BEGIN_DECLS @@ -52,6 +52,9 @@ typedef struct swiftscan_dependency_graph_s *swiftscan_dependency_graph_t; /// Opaque container to contain the result of a dependency prescan. typedef struct swiftscan_import_set_s *swiftscan_import_set_t; +/// Opaque container to contain the info of a diagnostics emitted by the scanner. +typedef struct swiftscan_diagnostic_info_s *swiftscan_diagnostic_info_t; + /// Full Dependency Graph (Result) typedef struct { swiftscan_dependency_info_t *modules; @@ -329,6 +332,37 @@ swiftscan_batch_scan_result_create(swiftscan_scanner_t scanner, SWIFTSCAN_PUBLIC swiftscan_import_set_t swiftscan_import_set_create( swiftscan_scanner_t scanner, swiftscan_scan_invocation_t invocation); + +//=== Scanner Diagnostics -------------------------------------------------===// +typedef enum { + SWIFTSCAN_DIAGNOSTIC_SEVERITY_ERROR = 0, + SWIFTSCAN_DIAGNOSTIC_SEVERITY_WARNING = 1, + SWIFTSCAN_DIAGNOSTIC_SEVERITY_NOTE = 2, + SWIFTSCAN_DIAGNOSTIC_SEVERITY_REMARK = 3 +} swiftscan_diagnostic_severity_t; + +typedef struct { + swiftscan_diagnostic_info_t *diagnostics; + size_t count; +} swiftscan_diagnostic_set_t; + +/// For the specified \c scanner instance, query all insofar emitted diagnostics +SWIFTSCAN_PUBLIC swiftscan_diagnostic_set_t* +swiftscan_scanner_diagnostics_query(swiftscan_scanner_t scanner); + +/// For the specified \c scanner instance, reset its diagnostic state +SWIFTSCAN_PUBLIC void +swiftscan_scanner_diagnostics_reset(swiftscan_scanner_t scanner); + +SWIFTSCAN_PUBLIC swiftscan_string_ref_t +swiftscan_diagnostic_get_message(swiftscan_diagnostic_info_t diagnostic); + +SWIFTSCAN_PUBLIC swiftscan_diagnostic_severity_t +swiftscan_diagnostic_get_severity(swiftscan_diagnostic_info_t diagnostic); + +SWIFTSCAN_PUBLIC void +swiftscan_diagnostics_set_dispose(swiftscan_diagnostic_set_t* diagnostics); + //=== Scanner Cache Operations --------------------------------------------===// // The following operations expose an implementation detail of the dependency // scanner: its module dependencies cache. This is done in order diff --git a/include/swift/DependencyScan/DependencyScanImpl.h b/include/swift/DependencyScan/DependencyScanImpl.h index d856edea84e92..491173a4f32c4 100644 --- a/include/swift/DependencyScan/DependencyScanImpl.h +++ b/include/swift/DependencyScan/DependencyScanImpl.h @@ -160,4 +160,10 @@ struct swiftscan_scan_invocation_s { swiftscan_string_set_t *argv; }; +struct swiftscan_diagnostic_info_s { + swiftscan_string_ref_t message; + swiftscan_diagnostic_severity_t severity; + // TODO: SourceLoc +}; + #endif // SWIFT_C_DEPENDENCY_SCAN_IMPL_H diff --git a/include/swift/DependencyScan/DependencyScanningTool.h b/include/swift/DependencyScan/DependencyScanningTool.h index 615433fa78efa..50dd48f72af66 100644 --- a/include/swift/DependencyScan/DependencyScanningTool.h +++ b/include/swift/DependencyScan/DependencyScanningTool.h @@ -23,6 +23,28 @@ namespace swift { namespace dependencies { +class DependencyScanningTool; + +/// Diagnostic consumer that simply collects the diagnostics emitted so-far +class DependencyScannerDiagnosticCollectingConsumer : public DiagnosticConsumer { +public: + friend DependencyScanningTool; + DependencyScannerDiagnosticCollectingConsumer() {} + void reset() { Diagnostics.clear(); } +private: + struct ScannerDiagnosticInfo { + std::string Message; + llvm::SourceMgr::DiagKind Severity; + }; + + void handleDiagnostic(SourceManager &SM, + const DiagnosticInfo &Info) override; + ScannerDiagnosticInfo convertDiagnosticInfo(SourceManager &SM, + const DiagnosticInfo &Info); + void addDiagnostic(SourceManager &SM, const DiagnosticInfo &Info); + std::vector Diagnostics; +}; + /// Given a set of arguments to a print-target-info frontend tool query, produce the /// JSON target info. @@ -68,6 +90,10 @@ class DependencyScanningTool { bool loadCache(llvm::StringRef path); /// Discard the tool's current `SharedCache` and start anew. void resetCache(); + + const std::vector& getDiagnostics() const { return CDC.Diagnostics; } + /// Discared the collection of diagnostics encountered so far. + void resetDiagnostics(); private: /// Using the specified invocation command, initialize the scanner instance @@ -88,8 +114,8 @@ class DependencyScanningTool { /// command-line options specified in the batch scan input entry. std::unique_ptr VersionedPCMInstanceCacheCache; - /// A shared consumer that, for now, just prints the encountered diagnostics. - PrintingDiagnosticConsumer PDC; + /// A shared consumer that accumulates encountered diagnostics. + DependencyScannerDiagnosticCollectingConsumer CDC; llvm::BumpPtrAllocator Alloc; llvm::StringSaver Saver; }; diff --git a/lib/DependencyScan/DependencyScanningTool.cpp b/lib/DependencyScan/DependencyScanningTool.cpp index f3571e0b73067..1ec61dfc5a540 100644 --- a/lib/DependencyScan/DependencyScanningTool.cpp +++ b/lib/DependencyScan/DependencyScanningTool.cpp @@ -56,11 +56,58 @@ llvm::ErrorOr getTargetInfo(ArrayRef Comma return c_string_utils::create_clone(ResultStr.c_str()); } +void DependencyScannerDiagnosticCollectingConsumer::handleDiagnostic(SourceManager &SM, + const DiagnosticInfo &Info) { + addDiagnostic(SM, Info); + for (auto ChildInfo : Info.ChildDiagnosticInfo) { + addDiagnostic(SM, *ChildInfo); + } +} + +void DependencyScannerDiagnosticCollectingConsumer::addDiagnostic(SourceManager &SM, const DiagnosticInfo &Info) { + // Determine what kind of diagnostic we're emitting. + llvm::SourceMgr::DiagKind SMKind; + switch (Info.Kind) { + case DiagnosticKind::Error: + SMKind = llvm::SourceMgr::DK_Error; + break; + case DiagnosticKind::Warning: + SMKind = llvm::SourceMgr::DK_Warning; + break; + case DiagnosticKind::Note: + SMKind = llvm::SourceMgr::DK_Note; + break; + case DiagnosticKind::Remark: + SMKind = llvm::SourceMgr::DK_Remark; + break; + } + // Translate ranges. + SmallVector Ranges; + for (auto R : Info.Ranges) + Ranges.push_back(getRawRange(SM, R)); + // Translate fix-its. + SmallVector FixIts; + for (DiagnosticInfo::FixIt F : Info.FixIts) + FixIts.push_back(getRawFixIt(SM, F)); + + std::string ResultingMessage; + llvm::raw_string_ostream Stream(ResultingMessage); + const llvm::SourceMgr &rawSM = SM.getLLVMSourceMgr(); + + // Actually substitute the diagnostic arguments into the diagnostic text. + llvm::SmallString<256> Text; + llvm::raw_svector_ostream Out(Text); + DiagnosticEngine::formatDiagnosticText(Out, Info.FormatString, + Info.FormatArgs); + auto Msg = SM.GetMessage(Info.Loc, SMKind, Text, Ranges, FixIts); + Diagnostics.push_back(ScannerDiagnosticInfo{Msg.getMessage().str(), SMKind}); +} + DependencyScanningTool::DependencyScanningTool() : SharedCache(std::make_unique()), VersionedPCMInstanceCacheCache( std::make_unique()), - PDC(), Alloc(), Saver(Alloc) {} + CDC(), Alloc(), Saver(Alloc) {} llvm::ErrorOr DependencyScanningTool::getDependencies( @@ -126,7 +173,7 @@ DependencyScanningTool::getDependencies( void DependencyScanningTool::serializeCache(llvm::StringRef path) { SourceManager SM; DiagnosticEngine Diags(SM); - Diags.addConsumer(PDC); + Diags.addConsumer(CDC); module_dependency_cache_serialization::writeInterModuleDependenciesCache( Diags, path, *SharedCache); } @@ -134,7 +181,7 @@ void DependencyScanningTool::serializeCache(llvm::StringRef path) { bool DependencyScanningTool::loadCache(llvm::StringRef path) { SourceManager SM; DiagnosticEngine Diags(SM); - Diags.addConsumer(PDC); + Diags.addConsumer(CDC); SharedCache = std::make_unique(); bool readFailed = module_dependency_cache_serialization::readInterModuleDependenciesCache( @@ -149,6 +196,10 @@ void DependencyScanningTool::resetCache() { SharedCache.reset(new GlobalModuleDependenciesCache()); } +void DependencyScanningTool::resetDiagnostics() { + CDC.reset(); +} + llvm::ErrorOr> DependencyScanningTool::initScannerForAction( ArrayRef Command) { @@ -165,7 +216,7 @@ DependencyScanningTool::initCompilerInstanceForScan( ArrayRef CommandArgs) { // State unique to an individual scan auto Instance = std::make_unique(); - Instance->addDiagnosticConsumer(&PDC); + Instance->addDiagnosticConsumer(&CDC); // Basic error checking on the arguments if (CommandArgs.empty()) { diff --git a/tools/libSwiftScan/libSwiftScan.cpp b/tools/libSwiftScan/libSwiftScan.cpp index 927b6300c9924..050dcc4570a9f 100644 --- a/tools/libSwiftScan/libSwiftScan.cpp +++ b/tools/libSwiftScan/libSwiftScan.cpp @@ -534,6 +534,70 @@ swiftscan_compiler_supported_features_query() { return swift::c_string_utils::create_set(allFeatures); } +//=== Scanner Diagnostics -------------------------------------------------===// +swiftscan_diagnostic_set_t* +swiftscan_scanner_diagnostics_query(swiftscan_scanner_t scanner) { + DependencyScanningTool *ScanningTool = unwrap(scanner); + auto NumDiagnostics = ScanningTool->getDiagnostics().size(); + + swiftscan_diagnostic_set_t *Result = new swiftscan_diagnostic_set_t; + Result->count = NumDiagnostics; + Result->diagnostics = new swiftscan_diagnostic_info_t[NumDiagnostics]; + + for (size_t i = 0; i < NumDiagnostics; ++i) { + const auto &Diagnostic = ScanningTool->getDiagnostics()[i]; + swiftscan_diagnostic_info_s *DiagnosticInfo = new swiftscan_diagnostic_info_s; + DiagnosticInfo->message = swift::c_string_utils::create_clone(Diagnostic.Message.c_str()); + switch (Diagnostic.Severity) { + case llvm::SourceMgr::DK_Error: + DiagnosticInfo->severity = SWIFTSCAN_DIAGNOSTIC_SEVERITY_ERROR; + break; + case llvm::SourceMgr::DK_Warning: + DiagnosticInfo->severity = SWIFTSCAN_DIAGNOSTIC_SEVERITY_WARNING; + break; + case llvm::SourceMgr::DK_Note: + DiagnosticInfo->severity = SWIFTSCAN_DIAGNOSTIC_SEVERITY_NOTE; + break; + case llvm::SourceMgr::DK_Remark: + DiagnosticInfo->severity = SWIFTSCAN_DIAGNOSTIC_SEVERITY_REMARK; + break; + } + Result->diagnostics[i] = DiagnosticInfo; + } + + return Result; +} + +void +swiftscan_scanner_diagnostics_reset(swiftscan_scanner_t scanner) { + DependencyScanningTool *ScanningTool = unwrap(scanner); + ScanningTool->resetDiagnostics(); +} + +swiftscan_string_ref_t +swiftscan_diagnostic_get_message(swiftscan_diagnostic_info_t diagnostic) { + return diagnostic->message; +} + +swiftscan_diagnostic_severity_t +swiftscan_diagnostic_get_severity(swiftscan_diagnostic_info_t diagnostic) { + return diagnostic->severity; +} + +void swiftscan_diagnostic_dispose(swiftscan_diagnostic_info_t diagnostic) { + swiftscan_string_dispose(diagnostic->message); + delete diagnostic; +} + +void +swiftscan_diagnostics_set_dispose(swiftscan_diagnostic_set_t* diagnostics){ + for (size_t i = 0; i < diagnostics->count; ++i) { + swiftscan_diagnostic_dispose(diagnostics->diagnostics[i]); + } + delete[] diagnostics->diagnostics; + delete diagnostics; +} + //=== Experimental Compiler Invocation Functions ------------------------===// int invoke_swift_compiler(int argc, const char **argv) { diff --git a/tools/libSwiftScan/libSwiftScan.exports b/tools/libSwiftScan/libSwiftScan.exports index 23dcdaf05bf4a..2400404c02cba 100644 --- a/tools/libSwiftScan/libSwiftScan.exports +++ b/tools/libSwiftScan/libSwiftScan.exports @@ -59,4 +59,9 @@ swiftscan_compiler_target_info_query swiftscan_scanner_cache_serialize swiftscan_scanner_cache_load swiftscan_scanner_cache_reset +swiftscan_scanner_diagnostics_query +swiftscan_scanner_diagnostics_reset +swiftscan_diagnostic_get_message +swiftscan_diagnostic_get_severity +swiftscan_diagnostics_set_dispose invoke_swift_compiler From 1d52c9cce109c0468348b9f7ee087dd04471bd7f Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Mon, 29 Aug 2022 10:53:18 -0700 Subject: [PATCH 11/37] AST: Move some #available parsing diagnostics to the right section of DiagnosticsParse.def. --- include/swift/AST/DiagnosticsParse.def | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 2d92a2c56511e..b59b70cf336c9 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -990,12 +990,6 @@ WARNING(parameter_unnamed_warn,none, ERROR(parameter_curry_syntax_removed,none, "cannot have more than one parameter list", ()) -ERROR(availability_cannot_be_mixed,none, - "#available and #unavailable cannot be in the same statement", ()) - -ERROR(false_available_is_called_unavailable,none, - "#available cannot be used as an expression, did you mean to use '#unavailable'?", ()) - ERROR(initializer_as_typed_pattern,none, "unexpected initializer in pattern; did you mean to use '='?", ()) @@ -1935,6 +1929,12 @@ ERROR(pound_available_package_description_not_allowed, none, ERROR(availability_query_repeated_platform, none, "version for '%0' already specified", (StringRef)) +ERROR(availability_cannot_be_mixed,none, + "#available and #unavailable cannot be in the same statement", ()) + +ERROR(false_available_is_called_unavailable,none, + "#available cannot be used as an expression, did you mean to use '#unavailable'?", ()) + ERROR(attr_requires_concurrency, none, "'%0' %select{attribute|modifier}1 is only valid when experimental " "concurrency is enabled", From f5f1d3c0281542a09dd7be6076bf0bd037da9c3f Mon Sep 17 00:00:00 2001 From: Allan Shortlidge Date: Fri, 26 Aug 2022 17:27:26 -0700 Subject: [PATCH 12/37] AST: Parse `#_hasSymbol` Introduce the compiler directive `#_hasSymbol` which will be used to detect whether weakly linked symbols are present at runtime. It is intended for use in combination with `@_weakLinked import` or `-weak-link-at-target`. ``` if #_hasSymbol(foo(_:)) { foo(42) } ``` Parsing only; SILGen is coming in a later commit. Resolves rdar://99342017 --- include/swift/AST/DiagnosticsParse.def | 15 ++++- include/swift/AST/Stmt.h | 92 ++++++++++++++++++++------ include/swift/Parse/Parser.h | 1 + lib/AST/ASTDumper.cpp | 57 ++++++++-------- lib/AST/ASTScopeCreation.cpp | 1 + lib/AST/ASTVerifier.cpp | 4 +- lib/AST/ASTWalker.cpp | 4 +- lib/AST/Stmt.cpp | 15 +++++ lib/Parse/ParseExpr.cpp | 19 +++++- lib/Parse/ParseStmt.cpp | 40 +++++++++++ lib/SILGen/SILGenDecl.cpp | 9 ++- lib/Sema/BuilderTransform.cpp | 1 + lib/Sema/CSApply.cpp | 1 + lib/Sema/CSClosure.cpp | 1 + lib/Sema/CSGen.cpp | 1 + lib/Sema/TypeCheckStmt.cpp | 16 +++++ test/Parse/has_symbol.swift | 36 ++++++++++ 17 files changed, 259 insertions(+), 54 deletions(-) create mode 100644 test/Parse/has_symbol.swift diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index b59b70cf336c9..e22d79a57395e 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -1280,7 +1280,7 @@ ERROR(string_literal_no_atsign,none, ERROR(invalid_float_literal_missing_leading_zero,none, "'.%0' is not a valid floating point literal; it must be written '0.%0'", (StringRef)) -ERROR(availability_query_outside_if_stmt_guard, none, +ERROR(special_condition_outside_if_stmt_guard, none, "%0 may only be used as condition of an 'if', 'guard'" " or 'while' statement", (StringRef)) @@ -1935,6 +1935,19 @@ ERROR(availability_cannot_be_mixed,none, ERROR(false_available_is_called_unavailable,none, "#available cannot be used as an expression, did you mean to use '#unavailable'?", ()) +//------------------------------------------------------------------------------ +// MARK: #_hasSymbol query parsing diagnostics +//------------------------------------------------------------------------------ + +ERROR(has_symbol_expected_lparen,PointsToFirstBadToken, + "expected '(' in #_hasSymbol directive", ()) + +ERROR(has_symbol_expected_expr,PointsToFirstBadToken, + "expected expression in #_hasSymbol", ()) + +ERROR(has_symbol_expected_rparen,PointsToFirstBadToken, + "expected ')' in #_hasSymbol condition", ()) + ERROR(attr_requires_concurrency, none, "'%0' %select{attribute|modifier}1 is only valid when experimental " "concurrency is enabled", diff --git a/include/swift/AST/Stmt.h b/include/swift/AST/Stmt.h index beea6d879ef18..f73ff9c40b3c3 100644 --- a/include/swift/AST/Stmt.h +++ b/include/swift/AST/Stmt.h @@ -389,6 +389,39 @@ class alignas(8) PoundAvailableInfo final : bool isUnavailability() const { return _isUnavailability; } }; +/// An expression that guards execution based on whether the symbols for the +/// declaration identified by the given expression are non-null at run-time, e.g. +/// +/// if #_hasSymbol(foo(_:)) { foo(42) } +/// +class PoundHasSymbolInfo final : public ASTAllocated { + Expr *SymbolExpr; + + SourceLoc PoundLoc; + SourceLoc LParenLoc; + SourceLoc RParenLoc; + + PoundHasSymbolInfo(SourceLoc PoundLoc, SourceLoc LParenLoc, Expr *SymbolExpr, + SourceLoc RParenLoc) + : SymbolExpr(SymbolExpr), PoundLoc(PoundLoc), LParenLoc(LParenLoc), + RParenLoc(RParenLoc){}; + +public: + static PoundHasSymbolInfo *create(ASTContext &Ctx, SourceLoc PoundLoc, + SourceLoc LParenLoc, Expr *SymbolExpr, + SourceLoc RParenLoc); + + Expr *getSymbolExpr() const { return SymbolExpr; } + void setSymbolExpr(Expr *E) { SymbolExpr = E; } + + SourceLoc getLParenLoc() const { return LParenLoc; } + SourceLoc getRParenLoc() const { return RParenLoc; } + SourceLoc getStartLoc() const { return PoundLoc; } + SourceLoc getEndLoc() const { return RParenLoc; } + SourceRange getSourceRange() const { + return SourceRange(getStartLoc(), getEndLoc()); + } +}; /// This represents an entry in an "if" or "while" condition. Pattern bindings /// can bind any number of names in the pattern binding decl, and may have an @@ -413,20 +446,21 @@ class alignas(1 << PatternAlignInBits) StmtConditionElement { /// to this as an 'implicit' pattern. Pattern *ThePattern = nullptr; - /// This is either the boolean condition, the initializer for a pattern - /// binding, or the #available information. - llvm::PointerUnion CondInitOrAvailable; + /// This is either the boolean condition, the #available information, or + /// the #_hasSymbol information. + llvm::PointerUnion + Condition; public: StmtConditionElement() {} - StmtConditionElement(SourceLoc IntroducerLoc, Pattern *ThePattern, - Expr *Init) - : IntroducerLoc(IntroducerLoc), ThePattern(ThePattern), - CondInitOrAvailable(Init) {} - StmtConditionElement(Expr *cond) : CondInitOrAvailable(cond) {} + StmtConditionElement(SourceLoc IntroducerLoc, Pattern *ThePattern, Expr *Init) + : IntroducerLoc(IntroducerLoc), ThePattern(ThePattern), Condition(Init) {} + StmtConditionElement(Expr *cond) : Condition(cond) {} + + StmtConditionElement(PoundAvailableInfo *Info) : Condition(Info) {} + + StmtConditionElement(PoundHasSymbolInfo *Info) : Condition(Info) {} - StmtConditionElement(PoundAvailableInfo *Info) : CondInitOrAvailable(Info) {} - SourceLoc getIntroducerLoc() const { return IntroducerLoc; } void setIntroducerLoc(SourceLoc loc) { IntroducerLoc = loc; } @@ -434,26 +468,33 @@ class alignas(1 << PatternAlignInBits) StmtConditionElement { enum ConditionKind { CK_Boolean, CK_PatternBinding, - CK_Availability + CK_Availability, + CK_HasSymbol, }; ConditionKind getKind() const { - if (ThePattern) return CK_PatternBinding; - return CondInitOrAvailable.is() ? CK_Boolean : CK_Availability; + if (ThePattern) + return CK_PatternBinding; + if (Condition.is()) + return CK_Boolean; + if (Condition.is()) + return CK_Availability; + assert(Condition.is()); + return CK_HasSymbol; } /// Boolean Condition Accessors. Expr *getBooleanOrNull() const { - return getKind() == CK_Boolean ? CondInitOrAvailable.get() : nullptr; + return getKind() == CK_Boolean ? Condition.get() : nullptr; } Expr *getBoolean() const { assert(getKind() == CK_Boolean && "Not a condition"); - return CondInitOrAvailable.get(); + return Condition.get(); } void setBoolean(Expr *E) { assert(getKind() == CK_Boolean && "Not a condition"); - CondInitOrAvailable = E; + Condition = E; } /// Pattern Binding Accessors. @@ -473,22 +514,33 @@ class alignas(1 << PatternAlignInBits) StmtConditionElement { Expr *getInitializer() const { assert(getKind() == CK_PatternBinding && "Not a pattern binding condition"); - return CondInitOrAvailable.get(); + return Condition.get(); } void setInitializer(Expr *E) { assert(getKind() == CK_PatternBinding && "Not a pattern binding condition"); - CondInitOrAvailable = E; + Condition = E; } // Availability Accessors PoundAvailableInfo *getAvailability() const { assert(getKind() == CK_Availability && "Not an #available condition"); - return CondInitOrAvailable.get(); + return Condition.get(); } void setAvailability(PoundAvailableInfo *Info) { assert(getKind() == CK_Availability && "Not an #available condition"); - CondInitOrAvailable = Info; + Condition = Info; + } + + // #_hasSymbol Accessors + PoundHasSymbolInfo *getHasSymbolInfo() const { + assert(getKind() == CK_HasSymbol && "Not a #_hasSymbol condition"); + return Condition.get(); + } + + void setHasSymbolInfo(PoundHasSymbolInfo *Info) { + assert(getKind() == CK_HasSymbol && "Not a #_hasSymbol condition"); + Condition = Info; } SourceLoc getStartLoc() const; diff --git a/include/swift/Parse/Parser.h b/include/swift/Parse/Parser.h index eafa45029beab..fd899086f8b1c 100644 --- a/include/swift/Parse/Parser.h +++ b/include/swift/Parse/Parser.h @@ -1839,6 +1839,7 @@ class Parser { ParserStatus parseStmtCondition(StmtCondition &Result, Diag<> ID, StmtKind ParentKind); ParserResult parseStmtConditionPoundAvailable(); + ParserResult parseStmtConditionPoundHasSymbol(); ParserResult parseStmtIf(LabeledStmtInfo LabelInfo, bool IfWasImplicitlyInserted = false); ParserResult parseStmtGuard(); diff --git a/lib/AST/ASTDumper.cpp b/lib/AST/ASTDumper.cpp index 5fc8b4fe90b7d..5400fae9517f9 100644 --- a/lib/AST/ASTDumper.cpp +++ b/lib/AST/ASTDumper.cpp @@ -123,6 +123,15 @@ static void printGenericParameters(raw_ostream &OS, GenericParamList *Params) { Params->print(OS); } +static void printSourceRange(raw_ostream &OS, const SourceRange R, + const ASTContext &Ctx) { + if (!R.isValid()) + return; + + PrintWithColorRAII(OS, RangeColor) << " range="; + R.print(PrintWithColorRAII(OS, RangeColor).getOS(), Ctx.SourceMgr, + /*PrintText=*/false); +} static StringRef getSILFunctionTypeRepresentationString(SILFunctionType::Representation value) { @@ -497,12 +506,7 @@ namespace { if (D->isHoisted()) PrintWithColorRAII(OS, DeclModifierColor) << " hoisted"; - auto R = D->getSourceRange(); - if (R.isValid()) { - PrintWithColorRAII(OS, RangeColor) << " range="; - R.print(PrintWithColorRAII(OS, RangeColor).getOS(), - D->getASTContext().SourceMgr, /*PrintText=*/false); - } + printSourceRange(OS, D->getSourceRange(), D->getASTContext()); if (D->TrailingSemiLoc.isValid()) PrintWithColorRAII(OS, DeclModifierColor) << " trailing_semi"; @@ -1013,12 +1017,7 @@ namespace { ctx = ¶ms->get(0)->getASTContext(); if (ctx) { - auto R = params->getSourceRange(); - if (R.isValid()) { - PrintWithColorRAII(OS, RangeColor) << " range="; - R.print(PrintWithColorRAII(OS, RangeColor).getOS(), - ctx->SourceMgr, /*PrintText=*/false); - } + printSourceRange(OS, params->getSourceRange(), *ctx); } Indent += 2; @@ -1483,6 +1482,18 @@ class PrintStmt : public StmtVisitor { PrintWithColorRAII(OS, ParenthesisColor) << ")"; Indent -= 2; break; + case StmtConditionElement::CK_HasSymbol: + Indent += 2; + OS.indent(Indent); + PrintWithColorRAII(OS, ParenthesisColor) << '('; + OS << "#_hasSymbol"; + if (Ctx) + printSourceRange(OS, C.getSourceRange(), *Ctx); + OS << "\n"; + printRec(C.getHasSymbolInfo()->getSymbolExpr()); + PrintWithColorRAII(OS, ParenthesisColor) << ")"; + Indent -= 2; + break; } } @@ -1494,14 +1505,8 @@ class PrintStmt : public StmtVisitor { if (S->isImplicit()) OS << " implicit"; - if (Ctx) { - auto R = S->getSourceRange(); - if (R.isValid()) { - PrintWithColorRAII(OS, RangeColor) << " range="; - R.print(PrintWithColorRAII(OS, RangeColor).getOS(), - Ctx->SourceMgr, /*PrintText=*/false); - } - } + if (Ctx) + printSourceRange(OS, S->getSourceRange(), *Ctx); if (S->TrailingSemiLoc.isValid()) OS << " trailing_semi"; @@ -1555,9 +1560,10 @@ class PrintStmt : public StmtVisitor { void visitIfStmt(IfStmt *S) { printCommon(S, "if_stmt") << '\n'; - for (auto elt : S->getCond()) + for (auto elt : S->getCond()) { printRec(elt); - OS << '\n'; + OS << "\n"; + } printRec(S->getThenStmt()); if (S->getElseStmt()) { OS << '\n'; @@ -1826,12 +1832,7 @@ class PrintExpr : public ExprVisitor { L.print(PrintWithColorRAII(OS, LocationColor).getOS(), Ctx.SourceMgr); } - auto R = E->getSourceRange(); - if (R.isValid()) { - PrintWithColorRAII(OS, RangeColor) << " range="; - R.print(PrintWithColorRAII(OS, RangeColor).getOS(), - Ctx.SourceMgr, /*PrintText=*/false); - } + printSourceRange(OS, E->getSourceRange(), Ctx); } if (E->TrailingSemiLoc.isValid()) diff --git a/lib/AST/ASTScopeCreation.cpp b/lib/AST/ASTScopeCreation.cpp index 5eefe65d36845..ab7f12b968cac 100644 --- a/lib/AST/ASTScopeCreation.cpp +++ b/lib/AST/ASTScopeCreation.cpp @@ -1167,6 +1167,7 @@ ASTScopeImpl *LabeledConditionalStmtScope::createNestedConditionalClauseScopes( for (auto &sec : stmt->getCond()) { switch (sec.getKind()) { case StmtConditionElement::CK_Availability: + case StmtConditionElement::CK_HasSymbol: break; case StmtConditionElement::CK_Boolean: scopeCreator.addToScopeTree(sec.getBoolean(), insertionPoint); diff --git a/lib/AST/ASTVerifier.cpp b/lib/AST/ASTVerifier.cpp index 1489ac40e6b14..a4602fcec6cdb 100644 --- a/lib/AST/ASTVerifier.cpp +++ b/lib/AST/ASTVerifier.cpp @@ -1030,7 +1030,9 @@ class Verifier : public ASTWalker { void checkConditionElement(const StmtConditionElement &elt) { switch (elt.getKind()) { - case StmtConditionElement::CK_Availability: break; + case StmtConditionElement::CK_Availability: + case StmtConditionElement::CK_HasSymbol: + break; case StmtConditionElement::CK_Boolean: { auto *E = elt.getBoolean(); if (shouldVerifyChecked(E)) diff --git a/lib/AST/ASTWalker.cpp b/lib/AST/ASTWalker.cpp index 6df547963c6ba..319b590558483 100644 --- a/lib/AST/ASTWalker.cpp +++ b/lib/AST/ASTWalker.cpp @@ -1340,7 +1340,9 @@ class Traversal : public ASTVisitorgetSourceRange(); case StmtConditionElement::CK_Availability: return getAvailability()->getSourceRange(); + case StmtConditionElement::CK_HasSymbol: + return getHasSymbolInfo()->getSourceRange(); case StmtConditionElement::CK_PatternBinding: SourceLoc Start; if (IntroducerLoc.isValid()) @@ -386,6 +388,15 @@ SourceRange StmtConditionElement::getSourceRange() const { llvm_unreachable("Unhandled StmtConditionElement in switch."); } +PoundHasSymbolInfo *PoundHasSymbolInfo::create(ASTContext &Ctx, + SourceLoc PoundLoc, + SourceLoc LParenLoc, + Expr *SymbolExpr, + SourceLoc RParenLoc) { + return new (Ctx) + PoundHasSymbolInfo(PoundLoc, LParenLoc, SymbolExpr, RParenLoc); +} + SourceLoc StmtConditionElement::getStartLoc() const { switch (getKind()) { case StmtConditionElement::CK_Boolean: @@ -394,6 +405,8 @@ SourceLoc StmtConditionElement::getStartLoc() const { return getAvailability()->getStartLoc(); case StmtConditionElement::CK_PatternBinding: return getSourceRange().Start; + case StmtConditionElement::CK_HasSymbol: + return getHasSymbolInfo()->getStartLoc(); } llvm_unreachable("Unhandled StmtConditionElement in switch."); @@ -407,6 +420,8 @@ SourceLoc StmtConditionElement::getEndLoc() const { return getAvailability()->getEndLoc(); case StmtConditionElement::CK_PatternBinding: return getSourceRange().End; + case StmtConditionElement::CK_HasSymbol: + return getHasSymbolInfo()->getEndLoc(); } llvm_unreachable("Unhandled StmtConditionElement in switch."); diff --git a/lib/Parse/ParseExpr.cpp b/lib/Parse/ParseExpr.cpp index 83ce0d29d32a6..cb85f288c00e4 100644 --- a/lib/Parse/ParseExpr.cpp +++ b/lib/Parse/ParseExpr.cpp @@ -235,7 +235,8 @@ ParserResult Parser::parseExprSequence(Diag<> Message, // if we're in a stmt-condition. if (Tok.getText() == "&&" && peekToken().isAny(tok::pound_available, tok::pound_unavailable, - tok::kw_let, tok::kw_var, tok::kw_case)) + tok::pound__hasSymbol, tok::kw_let, tok::kw_var, + tok::kw_case)) goto done; // Parse the operator. @@ -1829,7 +1830,7 @@ ParserResult Parser::parseExprPrimary(Diag<> ID, bool isExprBasic) { case tok::pound_unavailable: { // For better error recovery, parse but reject availability in an expr // context. - diagnose(Tok.getLoc(), diag::availability_query_outside_if_stmt_guard, + diagnose(Tok.getLoc(), diag::special_condition_outside_if_stmt_guard, Tok.getText()); auto res = parseStmtConditionPoundAvailable(); if (res.hasCodeCompletion()) @@ -1840,6 +1841,20 @@ ParserResult Parser::parseExprPrimary(Diag<> ID, bool isExprBasic) { ErrorExpr(res.get()->getSourceRange())); } + case tok::pound__hasSymbol: { + // For better error recovery, parse but reject #_hasSymbol in an expr + // context. + diagnose(Tok.getLoc(), diag::special_condition_outside_if_stmt_guard, + Tok.getText()); + auto res = parseStmtConditionPoundHasSymbol(); + if (res.hasCodeCompletion()) + return makeParserCodeCompletionStatus(); + if (res.isParseErrorOrHasCompletion() || res.isNull()) + return nullptr; + return makeParserResult(new (Context) + ErrorExpr(res.get()->getSourceRange())); + } + #define POUND_OBJECT_LITERAL(Name, Desc, Proto) \ case tok::pound_##Name: \ return parseExprObjectLiteral(ObjectLiteralExpr::Name, isExprBasic); diff --git a/lib/Parse/ParseStmt.cpp b/lib/Parse/ParseStmt.cpp index 228946327aa5f..e304d199b8deb 100644 --- a/lib/Parse/ParseStmt.cpp +++ b/lib/Parse/ParseStmt.cpp @@ -1469,6 +1469,33 @@ Parser::parseAvailabilitySpecList(SmallVectorImpl &Specs, return Status; } +// #_hasSymbol(...) +ParserResult Parser::parseStmtConditionPoundHasSymbol() { + SyntaxParsingContext ConditionCtxt(SyntaxContext, + SyntaxKind::HasSymbolCondition); + SourceLoc PoundLoc = consumeToken(tok::pound__hasSymbol); + + if (!Tok.isFollowingLParen()) { + diagnose(Tok, diag::has_symbol_expected_lparen); + return makeParserError(); + } + + SourceLoc LParenLoc = consumeToken(tok::l_paren); + ParserStatus status = makeParserSuccess(); + + auto ExprResult = parseExprBasic(diag::has_symbol_expected_expr); + status |= ExprResult; + + SourceLoc RParenLoc; + if (parseMatchingToken(tok::r_paren, RParenLoc, + diag::has_symbol_expected_rparen, LParenLoc)) + status.setIsParseError(); + + auto *result = PoundHasSymbolInfo::create( + Context, PoundLoc, LParenLoc, ExprResult.getPtrOrNull(), RParenLoc); + return makeParserResult(status, result); +} + ParserStatus Parser::parseStmtConditionElement(SmallVectorImpl &result, Diag<> DefaultID, StmtKind ParentKind, @@ -1496,6 +1523,18 @@ Parser::parseStmtConditionElement(SmallVectorImpl &result, return Status; } + // Parse a leading #_hasSymbol condition if present. + if (Tok.is(tok::pound__hasSymbol)) { + auto res = parseStmtConditionPoundHasSymbol(); + if (res.isNull() || res.hasCodeCompletion()) { + Status |= res; + return Status; + } + BindingKindStr = StringRef(); + result.push_back({res.get()}); + return Status; + } + // Handle code completion after the #. if (Tok.is(tok::pound) && peekToken().is(tok::code_complete) && Tok.getLoc().getAdvancedLoc(1) == peekToken().getLoc()) { @@ -1691,6 +1730,7 @@ Parser::parseStmtConditionElement(SmallVectorImpl &result, /// expr-basic /// ('var' | 'let' | 'case') pattern '=' expr-basic /// '#available' '(' availability-spec (',' availability-spec)* ')' +/// '#_hasSymbol' '(' expr ')' /// /// The use of expr-basic here disallows trailing closures, which are /// problematic given the curly braces around the if/while body. diff --git a/lib/SILGen/SILGenDecl.cpp b/lib/SILGen/SILGenDecl.cpp index c420cf049af11..784276ac3bd7d 100644 --- a/lib/SILGen/SILGenDecl.cpp +++ b/lib/SILGen/SILGenDecl.cpp @@ -1483,7 +1483,8 @@ void SILGenFunction::emitStmtCondition(StmtCondition Cond, JumpDest FalseDest, booleanTestLoc = expr; break; } - case StmtConditionElement::CK_Availability: + + case StmtConditionElement::CK_Availability: { // Check the running OS version to determine whether it is in the range // specified by elt. PoundAvailableInfo *availability = elt.getAvailability(); @@ -1515,6 +1516,12 @@ void SILGenFunction::emitStmtCondition(StmtCondition Cond, JumpDest FalseDest, break; } + case StmtConditionElement::CK_HasSymbol: { + llvm::report_fatal_error("Can't SILGen #_hasSymbol yet!"); + break; + } + } + // Now that we have a boolean test as a Builtin.i1, emit the branch. assert(booleanTestValue->getType(). castTo()->isFixedWidth(1) && diff --git a/lib/Sema/BuilderTransform.cpp b/lib/Sema/BuilderTransform.cpp index c51b146c0d509..877486713b856 100644 --- a/lib/Sema/BuilderTransform.cpp +++ b/lib/Sema/BuilderTransform.cpp @@ -48,6 +48,7 @@ const StmtConditionElement *findAvailabilityCondition(StmtCondition stmtCond) { switch (cond.getKind()) { case StmtConditionElement::CK_Boolean: case StmtConditionElement::CK_PatternBinding: + case StmtConditionElement::CK_HasSymbol: continue; case StmtConditionElement::CK_Availability: diff --git a/lib/Sema/CSApply.cpp b/lib/Sema/CSApply.cpp index 8767cfe42d1da..46eb8274fa7e0 100644 --- a/lib/Sema/CSApply.cpp +++ b/lib/Sema/CSApply.cpp @@ -8909,6 +8909,7 @@ ExprWalker::rewriteTarget(SolutionApplicationTarget target) { for (auto &condElement : *stmtCondition) { switch (condElement.getKind()) { case StmtConditionElement::CK_Availability: + case StmtConditionElement::CK_HasSymbol: continue; case StmtConditionElement::CK_Boolean: { diff --git a/lib/Sema/CSClosure.cpp b/lib/Sema/CSClosure.cpp index 4274c016c9eb8..2d19bbce14eed 100644 --- a/lib/Sema/CSClosure.cpp +++ b/lib/Sema/CSClosure.cpp @@ -1919,6 +1919,7 @@ class ResultBuilderRewriter : public SyntacticElementSolutionApplication { switch (cond.getKind()) { case StmtConditionElement::CK_Boolean: case StmtConditionElement::CK_PatternBinding: + case StmtConditionElement::CK_HasSymbol: continue; case StmtConditionElement::CK_Availability: diff --git a/lib/Sema/CSGen.cpp b/lib/Sema/CSGen.cpp index 8e68a043b83f5..675d5242a559e 100644 --- a/lib/Sema/CSGen.cpp +++ b/lib/Sema/CSGen.cpp @@ -4342,6 +4342,7 @@ bool ConstraintSystem::generateConstraints(StmtCondition condition, for (const auto &condElement : condition) { switch (condElement.getKind()) { case StmtConditionElement::CK_Availability: + case StmtConditionElement::CK_HasSymbol: // Nothing to do here. continue; diff --git a/lib/Sema/TypeCheckStmt.cpp b/lib/Sema/TypeCheckStmt.cpp index 2a8b1ad44551f..a96a3e84b1572 100644 --- a/lib/Sema/TypeCheckStmt.cpp +++ b/lib/Sema/TypeCheckStmt.cpp @@ -422,6 +422,8 @@ bool TypeChecker::typeCheckStmtConditionElement(StmtConditionElement &elt, bool &isFalsable, DeclContext *dc) { auto &Context = dc->getASTContext(); + + // Typecheck a #available or #unavailable condition. if (elt.getKind() == StmtConditionElement::CK_Availability) { isFalsable = true; @@ -445,6 +447,20 @@ bool TypeChecker::typeCheckStmtConditionElement(StmtConditionElement &elt, return false; } + // Typecheck a #_hasSymbol condition. + if (elt.getKind() == StmtConditionElement::CK_HasSymbol) { + auto Info = elt.getHasSymbolInfo(); + auto E = Info->getSymbolExpr(); + if (E) { + // FIXME: Implement #_hasSymbol typechecking. + (void)TypeChecker::typeCheckExpression(E, dc); + Info->setSymbolExpr(E); + } + isFalsable = true; + + return false; + } + if (auto E = elt.getBooleanOrNull()) { assert(!E->getType() && "the bool condition is already type checked"); bool hadError = TypeChecker::typeCheckCondition(E, dc); diff --git a/test/Parse/has_symbol.swift b/test/Parse/has_symbol.swift new file mode 100644 index 0000000000000..76198ec11262b --- /dev/null +++ b/test/Parse/has_symbol.swift @@ -0,0 +1,36 @@ +// RUN: %target-typecheck-verify-swift + +func foo() {} +func bar() {} + +if #_hasSymbol(foo) {} +if #_hasSymbol(foo), #_hasSymbol(bar) {} +guard #_hasSymbol(foo) else { fatalError() } +while #_hasSymbol(foo) {} + +if #_hasSymbol(foo) == false {} // expected-error {{expected '{' after 'if' condition}} + +_ = #_hasSymbol(foo) {} // expected-error {{#_hasSymbol may only be used as condition of}} + +(#_hasSymbol(foo) ? 1 : 0) // expected-error {{#_hasSymbol may only be used as condition of}} + +if !#_hasSymbol(foo) {} // expected-error {{#_hasSymbol may only be used as condition of}} + +if let _ = Optional(5), !#_hasSymbol(foo) {} {} // expected-error {{#_hasSymbol may only be used as condition of}} + +if #_hasSymbol {} // expected-error {{expected '(' in #_hasSymbol directive}} + +// expected-error@+3 {{expected ')' in #_hasSymbol condition}} +// expected-error@+2 {{expected '{' after 'if' condition}} +// expected-note@+1 {{to match this opening '('}} +if #_hasSymbol(struct) {} // expected-error {{expected expression in #_hasSymbol}} + +// expected-error@+3 {{expected ')' in #_hasSymbol condition}} +// expected-error@+2 {{expected '{' after 'if' condition}} +// expected-note@+1 {{to match this opening '('}} +if #_hasSymbol(foo bar) {} + +// expected-error@+2 {{expected ')' in #_hasSymbol condition}} +// expected-note@+1 {{to match this opening '('}} +if #_hasSymbol(foo {} + From d9be4a721311dae452f89f3e74ca332855c3ba99 Mon Sep 17 00:00:00 2001 From: Cal Stephens Date: Fri, 9 Sep 2022 08:41:31 -0700 Subject: [PATCH 13/37] Update check for Optional.none --- lib/Sema/MiscDiagnostics.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/Sema/MiscDiagnostics.cpp b/lib/Sema/MiscDiagnostics.cpp index 2aa324f7e7108..ec03cbaa27863 100644 --- a/lib/Sema/MiscDiagnostics.cpp +++ b/lib/Sema/MiscDiagnostics.cpp @@ -1299,13 +1299,10 @@ static void diagSyntacticUseRestrictions(const Expr *E, const DeclContext *DC, bool isTypeCheckedOptionalNil(Expr *E) { if (dyn_cast(E)) return true; - auto CE = dyn_cast(E->getSemanticsProvidingExpr()); - if (!CE) - return false; - - // First case -- Optional.none - if (auto DRE = dyn_cast(CE->getSemanticFn())) - return DRE->getDecl() == Ctx.getOptionalNoneDecl(); + if (auto *DSCE = dyn_cast_or_null(E->getSemanticsProvidingExpr())) { + if (auto *DRE = dyn_cast(DSCE->getSemanticFn())) + return DRE->getDecl() == Ctx.getOptionalNoneDecl(); + } return false; } From 6d9b82b4602670f37e28f280258e30e31444c222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Tue, 6 Sep 2022 13:03:05 -0700 Subject: [PATCH 14/37] [Sema] Error on aliases desugaring to a reference SPI only imported There are only new users of @_spiOnly so we can already make this diagnostic an error. --- lib/Sema/ResilienceDiagnostics.cpp | 6 ++++-- test/Sema/implicit-import-typealias.swift | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index 46e1557767ab0..25dfc0fa033bc 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -132,7 +132,8 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc, TAD->getName(), definingModule->getNameStr(), D->getNameStr(), static_cast(*reason), definingModule->getName(), static_cast(originKind)) - .warnUntilSwiftVersion(6); + .warnUntilSwiftVersionIf(originKind != DisallowedOriginKind::SPIOnly, + 6); } else { ctx.Diags .diagnose(loc, @@ -140,7 +141,8 @@ static bool diagnoseTypeAliasDeclRefExportability(SourceLoc loc, TAD->getName(), definingModule->getNameStr(), D->getNameStr(), fragileKind.getSelector(), definingModule->getName(), static_cast(originKind)) - .warnUntilSwiftVersion(6); + .warnUntilSwiftVersionIf(originKind != DisallowedOriginKind::SPIOnly, + 6); } D->diagnose(diag::kind_declared_here, DescriptiveDeclKind::Type); diff --git a/test/Sema/implicit-import-typealias.swift b/test/Sema/implicit-import-typealias.swift index 053dc011b4146..8a23e22ae4117 100644 --- a/test/Sema/implicit-import-typealias.swift +++ b/test/Sema/implicit-import-typealias.swift @@ -9,6 +9,7 @@ // RUN: %target-swift-frontend -typecheck -verify %t/UsesAliasesNoImport.swift -I %t // RUN: %target-swift-frontend -typecheck -verify %t/UsesAliasesImplementationOnlyImport.swift -I %t +// RUN: %target-swift-frontend -typecheck -verify %t/UsesAliasesSPIOnlyImport.swift -I %t -experimental-spi-only-imports // RUN: %target-swift-frontend -typecheck -verify %t/UsesAliasesWithImport.swift -I %t /// The swiftinterface is broken by the missing import without the workaround. @@ -101,6 +102,20 @@ import Aliases public func takesGeneric(_ t: T) {} +//--- UsesAliasesSPIOnlyImport.swift + +import Aliases +@_spiOnly import Original + +@inlinable public func inlinableFunc() { + // expected-error@+1 {{'StructAlias' aliases 'Original.Struct' and cannot be used in an '@inlinable' function because 'Original' was imported for SPI only}} + _ = StructAlias.self +} + +// expected-error@+1 {{'ProtoAlias' aliases 'Original.Proto' and cannot be used here because 'Original' was imported for SPI only}} +public func takesGeneric(_ t: T) {} + + //--- UsesAliasesWithImport.swift import Aliases From bc4efe518f2b7434c306a45fec3456bb17a83439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Thu, 8 Sep 2022 13:13:26 -0700 Subject: [PATCH 15/37] [Sema] Always error on public uses of conformance from @_spiOnly import --- lib/Sema/ResilienceDiagnostics.cpp | 3 ++- test/SPI/spi-only-import-exportability.swift | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Sema/ResilienceDiagnostics.cpp b/lib/Sema/ResilienceDiagnostics.cpp index 25dfc0fa033bc..ff534769d121a 100644 --- a/lib/Sema/ResilienceDiagnostics.cpp +++ b/lib/Sema/ResilienceDiagnostics.cpp @@ -255,7 +255,8 @@ TypeChecker::diagnoseConformanceExportability(SourceLoc loc, M->getName(), static_cast(originKind)) .warnUntilSwiftVersionIf((useConformanceAvailabilityErrorsOption && - !ctx.LangOpts.EnableConformanceAvailabilityErrors) || + !ctx.LangOpts.EnableConformanceAvailabilityErrors && + originKind != DisallowedOriginKind::SPIOnly) || originKind == DisallowedOriginKind::ImplicitlyImported, 6); diff --git a/test/SPI/spi-only-import-exportability.swift b/test/SPI/spi-only-import-exportability.swift index 1ebcdbf5ba031..c9e8973f367d2 100644 --- a/test/SPI/spi-only-import-exportability.swift +++ b/test/SPI/spi-only-import-exportability.swift @@ -118,6 +118,7 @@ public func publicInlinableUser() { let p: PublicType = PublicType() p.spiOnlyExtensionMethod() // expected-error {{instance method 'spiOnlyExtensionMethod()' cannot be used in an '@inlinable' function because 'SPIOnlyImportedLib' was imported for SPI only}} + conformanceUse(p) // expected-error{{cannot use conformance of 'PublicType' to 'PublicProtocol' here; 'SPIOnlyImportedLib' was imported for SPI only}} } #endif @@ -131,6 +132,7 @@ public func spiInlinableUser() { let p: PublicType = PublicType() p.spiOnlyExtensionMethod() + conformanceUse(p) } public func implementationDetailsUser() { @@ -142,6 +144,7 @@ public func implementationDetailsUser() { let p: PublicType = PublicType() p.spiOnlyExtensionMethod() + conformanceUse(p) } public struct ClientStruct { From ebc2162d4b182831952432abf4941a6fcb9b5cbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20Laferri=C3=A8re?= Date: Fri, 26 Aug 2022 20:07:25 -0700 Subject: [PATCH 16/37] [Sema] @_spiOnly isn't a public import that triggers the diagnostic --- lib/Sema/TypeCheckDeclPrimary.cpp | 1 + .../Sema/implementation-only-import-suggestion.swift | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/Sema/TypeCheckDeclPrimary.cpp b/lib/Sema/TypeCheckDeclPrimary.cpp index 2cfd7ba1dcb61..690e0e7484a4d 100644 --- a/lib/Sema/TypeCheckDeclPrimary.cpp +++ b/lib/Sema/TypeCheckDeclPrimary.cpp @@ -1848,6 +1848,7 @@ class DeclChecker : public DeclVisitor { auto importer = ID->getModuleContext(); if (target && !ID->getAttrs().hasAttribute() && + !ID->getAttrs().hasAttribute() && target->getLibraryLevel() == LibraryLevel::SPI) { auto &diags = ID->getASTContext().Diags; diff --git a/test/Sema/implementation-only-import-suggestion.swift b/test/Sema/implementation-only-import-suggestion.swift index a7e3bc354cddf..41fe8896843f0 100644 --- a/test/Sema/implementation-only-import-suggestion.swift +++ b/test/Sema/implementation-only-import-suggestion.swift @@ -54,6 +54,18 @@ import LocalClang // expected-error{{private module 'LocalClang' is imported pub @_implementationOnly import FullyPrivateClang @_implementationOnly import LocalClang +/// Expect no errors with spi-only imports. +// RUN: %target-swift-frontend -typecheck -sdk %t/sdk -module-cache-path %t %s \ +// RUN: -experimental-spi-only-imports \ +// RUN: -F %t/sdk/System/Library/PrivateFrameworks/ \ +// RUN: -library-level api -D SPI_ONLY_IMPORTS +#elseif SPI_ONLY_IMPORTS + +@_spiOnly import PrivateSwift +@_spiOnly import PublicClang_Private +@_spiOnly import FullyPrivateClang +@_spiOnly import LocalClang + #endif /// Test error message on an unknown library level name. From 03136e06aa7a79c432d9f64c790511909dbaf0bf Mon Sep 17 00:00:00 2001 From: Artem Chikin Date: Fri, 9 Sep 2022 12:08:41 -0700 Subject: [PATCH 17/37] [Dependency Scanner] Ensure the Clang dependency scanner working directory matches the invocation. The Swift compiler does not have a concept of a working directory. It is instead handled by the Swift driver by resolving relative paths according to the driver's working directory argument. On the other hand, Clang does have a concept working directory which may be specified on this Clang invocation with '-working-directory'. If so, it is crucial that we use this directory as an argument to the Clang scanner API. Otherwiswe, we risk having a mismatch between the working directory specified on the scanner's Clang invocation and the one use from the scanner API entry-points, which leads to downstream inconsistencies and errors. --- .../ClangModuleDependencyScanner.cpp | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/ClangImporter/ClangModuleDependencyScanner.cpp b/lib/ClangImporter/ClangModuleDependencyScanner.cpp index 9d551286095df..f9d77ae848131 100644 --- a/lib/ClangImporter/ClangModuleDependencyScanner.cpp +++ b/lib/ClangImporter/ClangModuleDependencyScanner.cpp @@ -333,8 +333,25 @@ Optional ClangImporter::getModuleDependencies( // Determine the command-line arguments for dependency scanning. std::vector commandLineArgs = getClangDepScanningInvocationArguments(ctx, *importHackFile); - std::string workingDir = - ctx.SourceMgr.getFileSystem()->getCurrentWorkingDirectory().get(); + // The Swift compiler does not have a concept of a working directory. + // It is instead handled by the Swift driver by resolving relative paths + // according to the driver's notion of a working directory. On the other hand, + // Clang does have a concept working directory which may be specified on this + // Clang invocation with '-working-directory'. If so, it is crucial that we use + // this directory as an argument to the Clang scanner invocation below. + std::string workingDir; + auto clangWorkingDirPos = std::find(commandLineArgs.rbegin(), + commandLineArgs.rend(), + "-working-directory"); + if (clangWorkingDirPos == commandLineArgs.rend()) + workingDir = ctx.SourceMgr.getFileSystem()->getCurrentWorkingDirectory().get(); + else { + if (clangWorkingDirPos - 1 == commandLineArgs.rend()) { + ctx.Diags.diagnose(SourceLoc(), diag::clang_dependency_scan_error, "Missing '-working-directory' argument"); + return None; + } + workingDir = *(clangWorkingDirPos - 1); + } auto clangDependencies = clangImpl->tool.getFullDependencies( commandLineArgs, workingDir, clangImpl->alreadySeen); From 467b742a5c47e3f137a397e20f4f5d38f19f3fbf Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Tue, 2 Aug 2022 13:36:57 -0700 Subject: [PATCH 18/37] [move-only] Update the non-address move checker part of the optimizer to handle mark_must_check on addresses. --- lib/SIL/Utils/MemAccessUtils.cpp | 19 +++++++++---- lib/SIL/Utils/OwnershipUtils.cpp | 2 +- .../LoadBorrowImmutabilityChecker.cpp | 11 ++++++++ lib/SILGen/SILGenDecl.cpp | 5 ++++ .../Mandatory/AccessEnforcementSelection.cpp | 28 +++++++++++++------ .../Mandatory/DIMemoryUseCollector.cpp | 12 ++++++++ .../DiagnoseInvalidEscapingCaptures.cpp | 11 ++++++++ .../Mandatory/MoveOnlyObjectChecker.cpp | 9 ++++-- .../Transforms/AllocBoxToStack.cpp | 10 ++++++- ... moveonly_objectchecker_diagnostics.swift} | 0 10 files changed, 90 insertions(+), 17 deletions(-) rename test/SILOptimizer/{moveonly_diagnostics.swift => moveonly_objectchecker_diagnostics.swift} (100%) diff --git a/lib/SIL/Utils/MemAccessUtils.cpp b/lib/SIL/Utils/MemAccessUtils.cpp index 19dc43d063a9d..521e82a519a9c 100644 --- a/lib/SIL/Utils/MemAccessUtils.cpp +++ b/lib/SIL/Utils/MemAccessUtils.cpp @@ -1368,12 +1368,14 @@ class AccessPathVisitor : public FindAccessVisitorImpl { } else { // Ignore everything in getAccessProjectionOperand that is an access // projection with no affect on the access path. - assert(isa(projectedAddr) - || isa(projectedAddr) - || isa(projectedAddr) + assert(isa(projectedAddr) || + isa(projectedAddr) || + isa(projectedAddr) // project_box is not normally an access projection but we treat it // as such when it operates on unchecked_take_enum_data_addr. - || isa(projectedAddr)); + || isa(projectedAddr) + // Ignore mark_must_check, we just look through it when we see it. + || isa(projectedAddr)); } return sourceAddr->get(); } @@ -1862,7 +1864,14 @@ AccessPathDefUseTraversal::visitSingleValueUser(SingleValueInstruction *svi, return IgnoredUse; } - // MARK: Access projections + case SILInstructionKind::MarkMustCheckInst: { + // Mark must check goes on the project_box, so it isn't a ref. + assert(!dfs.isRef()); + pushUsers(svi, dfs); + return IgnoredUse; + } + + // MARK: Access projections case SILInstructionKind::StructElementAddrInst: case SILInstructionKind::TupleElementAddrInst: diff --git a/lib/SIL/Utils/OwnershipUtils.cpp b/lib/SIL/Utils/OwnershipUtils.cpp index 58dd54a5c65e0..d33737ee336e3 100644 --- a/lib/SIL/Utils/OwnershipUtils.cpp +++ b/lib/SIL/Utils/OwnershipUtils.cpp @@ -936,7 +936,7 @@ swift::findTransitiveUsesForAddress(SILValue projectedAddress, isa(user) || isa(user) || isa(user) || isa(user) || isa(user) || isa(user) || - isa(user)) { + isa(user) || isa(user)) { transitiveResultUses(op); continue; } diff --git a/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp b/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp index 51aeb4af390b0..9680d3f55088a 100644 --- a/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp +++ b/lib/SIL/Verifier/LoadBorrowImmutabilityChecker.cpp @@ -169,6 +169,17 @@ bool GatherWritesVisitor::visitUse(Operand *op, AccessUseType useTy) { } return true; + case SILInstructionKind::ExplicitCopyAddrInst: + if (cast(user)->getDest() == op->get()) { + writeAccumulator.push_back(op); + return true; + } + // This operand is the copy source. Check if it is taken. + if (cast(user)->isTakeOfSrc()) { + writeAccumulator.push_back(op); + } + return true; + case SILInstructionKind::MarkUnresolvedMoveAddrInst: if (cast(user)->getDest() == op->get()) { writeAccumulator.push_back(op); diff --git a/lib/SILGen/SILGenDecl.cpp b/lib/SILGen/SILGenDecl.cpp index c420cf049af11..0facd4d68dc6b 100644 --- a/lib/SILGen/SILGenDecl.cpp +++ b/lib/SILGen/SILGenDecl.cpp @@ -376,6 +376,11 @@ class LocalVariableInitialization : public SingleBufferInitialization { } Addr = SGF.B.createProjectBox(decl, Box, 0); + if (Addr->getType().isMoveOnly()) { + // TODO: Handle no implicit copy here. + Addr = SGF.B.createMarkMustCheckInst( + decl, Addr, MarkMustCheckInst::CheckKind::NoImplicitCopy); + } // Push a cleanup to destroy the local variable. This has to be // inactive until the variable is initialized. diff --git a/lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp b/lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp index 191d612a2e1dc..ef6033004891f 100644 --- a/lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp +++ b/lib/SILOptimizer/Mandatory/AccessEnforcementSelection.cpp @@ -31,6 +31,7 @@ /// //===----------------------------------------------------------------------===// +#include "swift/SIL/SILInstruction.h" #define DEBUG_TYPE "access-enforcement-selection" #include "swift/Basic/Defer.h" #include "swift/SIL/ApplySite.h" @@ -194,7 +195,8 @@ class SelectEnforcement { private: void analyzeUsesOfBox(SingleValueInstruction *source); - void analyzeProjection(ProjectBoxInst *projection); + // Used for project_box and mark_must_initialize. + void analyzeProjection(SingleValueInstruction *project); /// Note that the given instruction is a use of the box (or a use of /// a projection from it) in which the address escapes. @@ -237,13 +239,13 @@ void SelectEnforcement::analyzeUsesOfBox(SingleValueInstruction *source) { for (auto use : source->getUses()) { auto user = use->getUser(); - if (auto BBI = dyn_cast(user)) { - analyzeUsesOfBox(BBI); + if (auto bbi = dyn_cast(user)) { + analyzeUsesOfBox(bbi); continue; } - if (auto MUI = dyn_cast(user)) { - analyzeUsesOfBox(MUI); + if (auto mui = dyn_cast(user)) { + analyzeUsesOfBox(mui); continue; } @@ -281,10 +283,16 @@ static void checkUsesOfAccess(BeginAccessInst *access) { #endif } -void SelectEnforcement::analyzeProjection(ProjectBoxInst *projection) { +void SelectEnforcement::analyzeProjection(SingleValueInstruction *projection) { for (auto *use : projection->getUses()) { auto user = use->getUser(); + // Look through mark must check. + if (auto *mmi = dyn_cast(user)) { + analyzeProjection(mmi); + continue; + } + // Collect accesses. if (auto *access = dyn_cast(user)) { if (access->getEnforcement() == SILAccessEnforcement::Unknown) @@ -687,8 +695,12 @@ AccessEnforcementSelection::getAccessKindForBox(ProjectBoxInst *projection) { SourceAccess AccessEnforcementSelection::getSourceAccess(SILValue address) { // Recurse through MarkUninitializedInst. - if (auto *MUI = dyn_cast(address)) - return getSourceAccess(MUI->getOperand()); + if (auto *mui = dyn_cast(address)) + return getSourceAccess(mui->getOperand()); + + // Recurse through mark must check. + if (auto *mmci = dyn_cast(address)) + return getSourceAccess(mmci->getOperand()); if (auto box = dyn_cast(address)) return getAccessKindForBox(box); diff --git a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp index 042312d2bba06..82422f9bac9ba 100644 --- a/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp +++ b/lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp @@ -747,6 +747,12 @@ void ElementUseCollector::collectUses(SILValue Pointer, unsigned BaseEltNo) { continue; } + // Look through mark_must_check. To us, it is not interesting. + if (auto *mmi = dyn_cast(User)) { + collectUses(mmi, BaseEltNo); + continue; + } + // Loads are a use of the value. if (isa(User)) { addElementUses(BaseEltNo, PointeeType, User, DIUseKind::Load); @@ -1588,6 +1594,12 @@ collectDelegatingInitUses(const DIMemoryObjectInfo &TheMemory, continue; } + // Look through mark_must_check. + if (auto *MMCI = dyn_cast(User)) { + collectDelegatingInitUses(TheMemory, UseInfo, MMCI); + continue; + } + // Ignore end_access if (isa(User)) continue; diff --git a/lib/SILOptimizer/Mandatory/DiagnoseInvalidEscapingCaptures.cpp b/lib/SILOptimizer/Mandatory/DiagnoseInvalidEscapingCaptures.cpp index 8ffc0b62cdcf5..790bab5b86bf8 100644 --- a/lib/SILOptimizer/Mandatory/DiagnoseInvalidEscapingCaptures.cpp +++ b/lib/SILOptimizer/Mandatory/DiagnoseInvalidEscapingCaptures.cpp @@ -148,6 +148,10 @@ static bool checkNoEscapePartialApplyUse(Operand *oper, FollowUse followUses) { } const ParamDecl *getParamDeclFromOperand(SILValue value) { + // Look through mark must check. + if (auto *mmci = dyn_cast(value)) + value = mmci->getOperand(); + if (auto *arg = dyn_cast(value)) if (auto *decl = dyn_cast_or_null(arg->getDecl())) return decl; @@ -263,6 +267,13 @@ static void diagnoseCaptureLoc(ASTContext &Context, DeclContext *DC, if (isIncidentalUse(user) || onlyAffectsRefCount(user)) continue; + // Look through mark must check inst. + if (auto *mmci = dyn_cast(user)) { + for (auto *use : mmci->getUses()) + uselistInsert(use); + continue; + } + // Look through copies, borrows, and conversions. if (SingleValueInstruction *copy = getSingleValueCopyOrCast(user)) { // Only follow the copied operand. Other operands are incidental, diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp index 6e08f760329ca..4187e156a6c3c 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp @@ -299,8 +299,13 @@ bool MoveOnlyChecker::searchForCandidateMarkMustChecks() { // We then RAUW the mark_must_check once we have emitted the error since // later passes expect that mark_must_check has been eliminated by // us. Since we are failing already, this is ok to do. - diagnose(fn->getASTContext(), mmci->getLoc().getSourceLoc(), - diag::sil_moveonlychecker_not_understand_mark_move); + if (mmci->getType().isMoveOnlyWrapped()) { + diagnose(fn->getASTContext(), mmci->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_not_understand_no_implicit_copy); + } else { + diagnose(fn->getASTContext(), mmci->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_not_understand_moveonly); + } mmci->replaceAllUsesWith(mmci->getOperand()); mmci->eraseFromParent(); changed = true; diff --git a/lib/SILOptimizer/Transforms/AllocBoxToStack.cpp b/lib/SILOptimizer/Transforms/AllocBoxToStack.cpp index efe9f40db57f1..5e84579355528 100644 --- a/lib/SILOptimizer/Transforms/AllocBoxToStack.cpp +++ b/lib/SILOptimizer/Transforms/AllocBoxToStack.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #define DEBUG_TYPE "allocbox-to-stack" + #include "swift/AST/DiagnosticsSIL.h" #include "swift/Basic/BlotMapVector.h" #include "swift/Basic/GraphNodeWorklist.h" @@ -591,9 +592,16 @@ static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) { for (auto LastRelease : FinalReleases) { SILBuilderWithScope Builder(LastRelease); if (!isa(LastRelease)&& !Lowering.isTrivial()) { + // If we have a mark_must_check use of our stack box, we want to destroy + // that. + SILValue valueToDestroy = StackBox; + if (auto *mmci = StackBox->getSingleUserOfType()) { + valueToDestroy = mmci; + } + // For non-trivial types, insert destroys for each final release-like // instruction we found that isn't an explicit dealloc_box. - Builder.emitDestroyAddrAndFold(Loc, StackBox); + Builder.emitDestroyAddrAndFold(Loc, valueToDestroy); } Builder.createDeallocStack(Loc, ASI); } diff --git a/test/SILOptimizer/moveonly_diagnostics.swift b/test/SILOptimizer/moveonly_objectchecker_diagnostics.swift similarity index 100% rename from test/SILOptimizer/moveonly_diagnostics.swift rename to test/SILOptimizer/moveonly_objectchecker_diagnostics.swift From 058345740dd0150a1033a8f04bd3b1b10332200a Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 31 Aug 2022 16:39:58 -0700 Subject: [PATCH 19/37] [sil] Add a try_emplace/simple failable get method to BasicBlockData. --- include/swift/SIL/BasicBlockData.h | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/include/swift/SIL/BasicBlockData.h b/include/swift/SIL/BasicBlockData.h index 842b9e9ab13d4..d119c274d62bf 100644 --- a/include/swift/SIL/BasicBlockData.h +++ b/include/swift/SIL/BasicBlockData.h @@ -213,6 +213,32 @@ class BasicBlockData { } return data[getIndex(block)]; } + + /// Look up the state associated with \p block. Returns nullptr upon failure. + NullablePtr get(SILBasicBlock *block) const { + if (block->index < 0) + return {nullptr}; + Data *d = &const_cast(this)->data[getIndex(block)]; + return NullablePtr(d); + } + + /// If \p block is a new block, i.e. created after this BasicBlockData was + /// constructed, creates a new Data by calling + /// Data(std::forward(Args)...). + template + std::pair try_emplace(SILBasicBlock *block, + ArgTypes &&...Args) { + if (block->index != 0) { + return {&data[getIndex(block)], false}; + } + + assert(validForBlockOrder == function->BlockListChangeIdx && + "BasicBlockData invalid because the function's block list changed"); + validForBlockOrder = ++function->BlockListChangeIdx; + block->index = data.size(); + data.emplace_back(std::forward(Args)...); + return {&data.back(), true}; + } }; } // end swift namespace From 82322705d841927ebd1bc6ba5031e846b9d77f02 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Wed, 7 Sep 2022 15:44:39 -0700 Subject: [PATCH 20/37] [TypeLowering] Correct assertion. When examining non-trivial types which were non-lexical, when a field lacks a field decl (as happens with tuples), whether the type was annotated @_eagerMove was overlooked, resulting in incorrectly determining that a leaf was lexical. --- lib/SIL/IR/TypeLowering.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index 451c0a02c5447..358b859c15a3f 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2500,10 +2500,9 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, // Otherwise, the whole type would be lexical. if (!fieldDecl) { - // The field is non-trivial and the whole type is non-lexical. - // If there's no field decl which might be annotated - // @_eagerMove, we have a problem. - return false; + // There is no field decl that might be annotated @_eagerMove. The + // field is @_eagerMove iff its type is annotated @_eagerMove. + return getLifetimeAnnotation(ty) == LifetimeAnnotation::EagerMove; } // The field is non-trivial and the whole type is non-lexical. From f3cc0819c8d61c41a661b734218c147082245a11 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Wed, 7 Sep 2022 15:46:21 -0700 Subject: [PATCH 21/37] [NFC] Fix verification in TypeLowering. --- include/swift/SIL/SILType.h | 20 +--- include/swift/SIL/TypeLowering.h | 9 ++ lib/SIL/IR/SILType.cpp | 63 ----------- lib/SIL/IR/TypeLowering.cpp | 180 ++++++++++++++++++++++++------- 4 files changed, 153 insertions(+), 119 deletions(-) diff --git a/include/swift/SIL/SILType.h b/include/swift/SIL/SILType.h index 9af89080bf1de..03ddc7f0aba3f 100644 --- a/include/swift/SIL/SILType.h +++ b/include/swift/SIL/SILType.h @@ -20,6 +20,7 @@ #include "swift/AST/SILLayout.h" #include "swift/AST/Types.h" +#include "swift/SIL/AbstractionPattern.h" #include "swift/SIL/Lifetime.h" #include "llvm/ADT/Hashing.h" #include "llvm/ADT/PointerIntPair.h" @@ -753,25 +754,6 @@ class SILType { void dump() const; void print(raw_ostream &OS, const PrintOptions &PO = PrintOptions::printSIL()) const; - -#ifndef NDEBUG - /// Visit the distinct types of the fields out of which a type is aggregated. - /// - /// As we walk into the field types, if an aggregate is encountered, it may - /// still be a leaf. It is a leaf if the \p isLeafAggregate predicate - /// returns true. - /// - /// Returns false if the leaves cannot be visited or if any invocation of the - /// visitor returns false. - /// - /// NOTE: This function is meant for use in verification. For real use-cases, - /// recursive walks of type leaves should be done via - /// TypeLowering::RecursiveProperties. - bool visitAggregateLeaves( - Lowering::TypeConverter &TC, TypeExpansionContext context, - std::function isLeafAggregate, - std::function visit) const; -#endif }; // Statically prevent SILTypes from being directly cast to a type diff --git a/include/swift/SIL/TypeLowering.h b/include/swift/SIL/TypeLowering.h index 1566451b49f07..03b87d08f82c6 100644 --- a/include/swift/SIL/TypeLowering.h +++ b/include/swift/SIL/TypeLowering.h @@ -1250,6 +1250,15 @@ class TypeConverter { /// getTypeLowering(AbstractionPattern,Type,TypeExpansionContext). void verifyLowering(const TypeLowering &, AbstractionPattern origType, Type origSubstType, TypeExpansionContext forExpansion); + bool visitAggregateLeaves( + Lowering::AbstractionPattern origType, Type substType, + TypeExpansionContext context, + std::function>)> + isLeafAggregate, + std::function>)> + visit); #endif }; diff --git a/lib/SIL/IR/SILType.cpp b/lib/SIL/IR/SILType.cpp index a21772fb67f6b..1126cba51fd6d 100644 --- a/lib/SIL/IR/SILType.cpp +++ b/lib/SIL/IR/SILType.cpp @@ -942,66 +942,3 @@ bool SILType::isMoveOnly() const { return true; return isMoveOnlyWrapped(); } - -#ifndef NDEBUG -bool SILType::visitAggregateLeaves( - Lowering::TypeConverter &TC, TypeExpansionContext context, - std::function isLeaf, - std::function visit) const { - - llvm::SmallSet, - 16> - visited; - llvm::SmallVector< - std::tuple, 16> - worklist; - auto insertIntoWorklist = [&visited, &worklist](SILType parent, SILType type, - VarDecl *decl) -> bool { - if (!visited.insert({parent.value, type.value, decl}).second) { - return false; - } - worklist.push_back({parent.value, type.value, decl}); - return true; - }; - auto popFromWorklist = - [&worklist]() -> std::tuple { - SILType::ValueType parentOpaqueType; - SILType::ValueType opaqueType; - VarDecl *decl; - std::tie(parentOpaqueType, opaqueType, decl) = worklist.pop_back_val(); - return {parentOpaqueType, opaqueType, decl}; - }; - insertIntoWorklist(SILType(), *this, nullptr); - while (!worklist.empty()) { - SILType parent; - SILType ty; - VarDecl *decl; - std::tie(parent, ty, decl) = popFromWorklist(); - if (ty.isAggregate() && !isLeaf(parent, ty, decl)) { - if (auto tupleTy = ty.getAs()) { - for (unsigned index = 0, num = tupleTy->getNumElements(); index < num; - ++index) { - insertIntoWorklist(ty, ty.getTupleElementType(index), nullptr); - } - } else if (auto *decl = ty.getStructOrBoundGenericStruct()) { - for (auto *field : decl->getStoredProperties()) { - insertIntoWorklist(ty, ty.getFieldType(field, TC, context), field); - } - } else if (auto *decl = ty.getEnumOrBoundGenericEnum()) { - for (auto *field : decl->getStoredProperties()) { - insertIntoWorklist(ty, ty.getFieldType(field, TC, context), field); - } - } else { - llvm_unreachable("unknown aggregate kind!"); - } - continue; - } - - // This type is a leaf. Visit it. - auto success = visit(parent, ty, decl); - if (!success) - return false; - } - return true; -} -#endif diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index 358b859c15a3f..dc6a3a93637a9 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2422,74 +2422,180 @@ TypeConverter::getTypeLowering(AbstractionPattern origType, } #ifndef NDEBUG - verifyLowering(*lowering, origType, origSubstType, forExpansion); + verifyLowering(*lowering, origType, loweredSubstType, forExpansion); #endif return *lowering; } #ifndef NDEBUG +bool TypeConverter::visitAggregateLeaves( + Lowering::AbstractionPattern origType, Type substType, + TypeExpansionContext context, + std::function>)> + isLeafAggregate, + std::function>)> + visit) { + llvm::SmallSet, 16> visited; + llvm::SmallVector< + std::tuple, 16> + worklist; + auto insertIntoWorklist = + [&visited, + &worklist](Type substTy, AbstractionPattern origTy, + Optional> either) -> bool { + ValueDecl *field; + unsigned index; + if (either) { + if (either->isa()) { + field = either->get(); + index = UINT_MAX; + } else { + field = nullptr; + index = either->get(); + } + } else { + field = nullptr; + index = UINT_MAX; + } + if (!visited.insert({substTy.getPointer(), field, index}).second) + return false; + worklist.push_back({substTy.getPointer(), origTy, field, index}); + return true; + }; + auto popFromWorklist = [&worklist]() + -> std::tuple>> { + TypeBase *ty; + AbstractionPattern origTy = AbstractionPattern::getOpaque(); + Optional> either; + ValueDecl *field; + unsigned index = 0; + std::tie(ty, origTy, field, index) = worklist.pop_back_val(); + if (field) { + either = TaggedUnion(field); + } else if (index != UINT_MAX) { + either = TaggedUnion(index); + } else { + either = llvm::None; + } + return {ty->getCanonicalType(), origTy, either}; + }; + auto isAggregate = [](Type ty) { + return ty->is() || ty->getEnumOrBoundGenericEnum() || + ty->getStructOrBoundGenericStruct(); + }; + insertIntoWorklist(substType, origType, llvm::None); + while (!worklist.empty()) { + Type ty; + AbstractionPattern origTy = AbstractionPattern::getOpaque(); + Optional> path; + std::tie(ty, origTy, path) = popFromWorklist(); + if (isAggregate(ty) && !isLeafAggregate(ty, origTy, path)) { + if (auto tupleTy = ty->getAs()) { + for (unsigned index = 0, num = tupleTy->getNumElements(); index < num; + ++index) { + auto origElementTy = origTy.getTupleElementType(index); + auto substElementTy = + tupleTy->getElementType(index)->getCanonicalType(); + substElementTy = + computeLoweredRValueType(context, origElementTy, substElementTy); + insertIntoWorklist(substElementTy, origElementTy, + TaggedUnion(index)); + } + } else if (auto *decl = ty->getStructOrBoundGenericStruct()) { + for (auto *field : decl->getStoredProperties()) { + auto subMap = ty->getContextSubstitutionMap(&M, decl); + auto substFieldTy = + field->getInterfaceType().subst(subMap)->getCanonicalType(); + auto sig = field->getDeclContext()->getGenericSignatureOfContext(); + auto interfaceTy = field->getInterfaceType()->getReducedType(sig); + auto origFieldType = + origTy.unsafeGetSubstFieldType(field, interfaceTy); + insertIntoWorklist(substFieldTy, origFieldType, + TaggedUnion( + static_cast(field))); + } + } else if (auto *decl = ty->getEnumOrBoundGenericEnum()) { + auto subMap = ty->getContextSubstitutionMap(&M, decl); + for (auto *element : decl->getAllElements()) { + if (!element->hasAssociatedValues()) + continue; + // TODO: Callback for indirect elements. + if (element->isIndirect()) + continue; + auto substElementType = element->getArgumentInterfaceType() + .subst(subMap) + ->getCanonicalType(); + auto origElementTy = origTy.unsafeGetSubstFieldType( + element, element->getArgumentInterfaceType()->getReducedType( + decl->getGenericSignature())); + + insertIntoWorklist(substElementType, origElementTy, + TaggedUnion( + static_cast(element))); + } + } else { + llvm_unreachable("unknown aggregate kind!"); + } + continue; + } + + // This type is a leaf. Visit it. + auto success = visit(ty, origTy, path); + if (!success) + return false; + } + return true; +} + void TypeConverter::verifyLowering(const TypeLowering &lowering, - AbstractionPattern origType, - Type origSubstType, + AbstractionPattern origType, Type substType, TypeExpansionContext forExpansion) { // Non-trivial lowerings should always be lexical unless all non-trivial // fields are eager move. if (!lowering.isTrivial() && !lowering.isLexical()) { - auto getLifetimeAnnotation = [](SILType ty) -> LifetimeAnnotation { + if (lowering.getRecursiveProperties().isInfinite()) + return; + auto getLifetimeAnnotation = [](Type ty) -> LifetimeAnnotation { NominalTypeDecl *nominal; - if (!(nominal = ty.getASTType().getAnyNominal())) + if (!(nominal = ty->getAnyNominal())) return LifetimeAnnotation::None; return nominal->getLifetimeAnnotation(); }; - auto loweredType = lowering.getLoweredType(); - bool hasNoNontrivialLexicalLeaf = loweredType.visitAggregateLeaves( - *this, forExpansion, + bool hasNoNontrivialLexicalLeaf = visitAggregateLeaves( + origType, substType, forExpansion, /*isLeaf=*/ - [&](auto parent, auto ty, auto *fieldDecl) -> bool { + [&](auto ty, auto origTy, auto either) -> bool { // The field's type is an aggregate. Treat it as a leaf if it // has a lifetime annotation. - // If we don't have a field decl, it's either a field of a tuple - // or the top-level type. Either way, there's no var decl on - // which to look for an attribute. - // - // It's a leaf if the type has a lifetime annotation. - if (!fieldDecl) + // If it's the top-level type or a field of a tuple, there's no var + // decl on which to look for an attribute. It's a leaf iff the type + // has a lifetime annotation. + if (!either || either->template isa()) return getLifetimeAnnotation(ty).isSome(); // It's a field of a struct or an enum. It's a leaf if the type // or the var decl has a lifetime annotation. - return fieldDecl->getLifetimeAnnotation().isSome() || + return either->template get() + ->getLifetimeAnnotation() + .isSome() || getLifetimeAnnotation(ty); }, /*visit=*/ - [&](auto parent, auto ty, auto *fieldDecl) -> bool { + [&](auto ty, auto origTy, auto either) -> bool { // Look at each leaf: if it is non-trivial, verify that it is // attributed @_eagerMove. // If the leaf is the whole type, verify that it is annotated // @_eagerMove. - if (ty == loweredType) + if (ty->getCanonicalType() == substType->getCanonicalType()) return getLifetimeAnnotation(ty) == LifetimeAnnotation::EagerMove; - // Get ty's lowering. - CanGenericSignature sig; - if (fieldDecl) { - AbstractionPattern origFieldTy = getAbstractionPattern(fieldDecl); - CanType substFieldTy; - if (fieldDecl->hasClangNode()) { - substFieldTy = origFieldTy.getType(); - } else { - substFieldTy = parent.getASTType() - ->getTypeOfMember(&M, fieldDecl) - ->getCanonicalType(); - } - sig = getAbstractionPattern(fieldDecl).getGenericSignatureOrNull(); - } else { - sig = CanGenericSignature(); - } - auto &tyLowering = getTypeLowering(ty, forExpansion, sig); + auto &tyLowering = getTypeLowering(origTy, ty, forExpansion); // Leaves which are trivial aren't of interest. if (tyLowering.isTrivial()) @@ -2499,7 +2605,7 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, // not lexical. The leaf must be annotated @_eagerMove. // Otherwise, the whole type would be lexical. - if (!fieldDecl) { + if (!either || either->template isa()) { // There is no field decl that might be annotated @_eagerMove. The // field is @_eagerMove iff its type is annotated @_eagerMove. return getLifetimeAnnotation(ty) == LifetimeAnnotation::EagerMove; @@ -2508,7 +2614,7 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, // The field is non-trivial and the whole type is non-lexical. // That's fine as long as the field or its type is annotated // @_eagerMove. - return fieldDecl->getLifetimeAnnotation() == + return either->template get()->getLifetimeAnnotation() == LifetimeAnnotation::EagerMove || getLifetimeAnnotation(ty) == LifetimeAnnotation::EagerMove; }); From af6749e84cef5d470d8a3eeef9c8297a371aec06 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Fri, 9 Sep 2022 11:43:28 -0700 Subject: [PATCH 22/37] [TypeLowering] Mark lexical when marking non-trivial. In addition to starting all RecursiveProperties values which are NonTrivial as Lexical, also mark instances of the structs which are modified to become NonTrivial to simultaneously become Lexical. --- lib/SIL/IR/TypeLowering.cpp | 7 +++++++ .../type-classification-non-trivial-silgen.swift | 14 +++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index dc6a3a93637a9..1ac0252017a0e 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2099,6 +2099,7 @@ namespace { if (D->isCxxNonTrivial()) { properties.setAddressOnly(); properties.setNonTrivial(); + properties.setLexical(IsLexical); } auto subMap = structType->getContextSubstitutionMap(&TC.M, D); @@ -2158,6 +2159,9 @@ namespace { // may be added resiliently later. if (D->isIndirect()) { properties.setNonTrivial(); + properties.setLexical(IsLexical); + properties = + applyLifetimeAnnotation(D->getLifetimeAnnotation(), properties); return new (TC) LoadableEnumTypeLowering(enumType, properties, Expansion); } @@ -2173,6 +2177,9 @@ namespace { // Indirect elements only make the type nontrivial. if (elt->isIndirect()) { properties.setNonTrivial(); + properties.setLexical(IsLexical); + properties = + applyLifetimeAnnotation(elt->getLifetimeAnnotation(), properties); continue; } diff --git a/test/Interop/Cxx/class/type-classification-non-trivial-silgen.swift b/test/Interop/Cxx/class/type-classification-non-trivial-silgen.swift index b72df37a6753a..412f7265ca27b 100644 --- a/test/Interop/Cxx/class/type-classification-non-trivial-silgen.swift +++ b/test/Interop/Cxx/class/type-classification-non-trivial-silgen.swift @@ -5,7 +5,7 @@ import TypeClassification // Make sure that "StructWithDestructor" is marked as non-trivial by checking for a // "destroy_addr". // CHECK-LABEL: sil [ossa] @$s4main24testStructWithDestructoryyF -// CHECK: [[AS:%.*]] = alloc_stack $StructWithDestructor +// CHECK: [[AS:%.*]] = alloc_stack [lexical] $StructWithDestructor // CHECK: [[FN:%.*]] = function_ref @{{_ZN20StructWithDestructorC1Ev|\?\?0StructWithDestructor@@QEAA@XZ}} : $@convention(c) () -> @out StructWithDestructor // CHECK: apply [[FN]]([[AS]]) : $@convention(c) () -> @out StructWithDestructor // CHECK: destroy_addr [[AS]] @@ -20,7 +20,7 @@ public func testStructWithDestructor() { // Make sure that "HasMemberWithDestructor" is marked as non-trivial by checking // for a "destroy_addr". // CHECK-LABEL: sil [ossa] @$s4main33testStructWithSubobjectDestructoryyF : $@convention(thin) () -> () -// CHECK: [[AS:%.*]] = alloc_stack $StructWithSubobjectDestructor +// CHECK: [[AS:%.*]] = alloc_stack [lexical] $StructWithSubobjectDestructor // CHECK: [[FN:%.*]] = function_ref @{{_ZN29StructWithSubobjectDestructorC1Ev|\?\?0StructWithSubobjectDestructor@@QEAA@XZ}} : $@convention(c) () -> @out StructWithSubobjectDestructor // CHECK: apply [[FN]]([[AS]]) : $@convention(c) () -> @out StructWithSubobjectDestructor // CHECK: destroy_addr [[AS]] @@ -32,7 +32,7 @@ public func testStructWithSubobjectDestructor() { } // CHECK-LABEL: sil [ossa] @$s4main37testStructWithCopyConstructorAndValueSbyF -// CHECK: [[AS:%.*]] = alloc_stack $StructWithCopyConstructorAndValue +// CHECK: [[AS:%.*]] = alloc_stack [lexical] $StructWithCopyConstructorAndValue // CHECK: [[FN:%.*]] = function_ref @{{_ZN33StructWithCopyConstructorAndValueC1Ei|\?\?0StructWithCopyConstructorAndValue@@QEAA@H@Z}} : $@convention(c) (Int32) -> @out StructWithCopyConstructorAndValue // CHECK: apply [[FN]]([[AS]], %{{.*}}) : $@convention(c) (Int32) -> @out StructWithCopyConstructorAndValue // CHECK: [[OBJ_VAL_ADDR:%.*]] = struct_element_addr [[AS]] : $*StructWithCopyConstructorAndValue, #StructWithCopyConstructorAndValue.value @@ -53,10 +53,10 @@ public func testStructWithCopyConstructorAndValue() -> Bool { } // CHECK-LABEL: sil [ossa] @$s4main46testStructWithSubobjectCopyConstructorAndValueSbyF : $@convention(thin) () -> Bool -// CHECK: [[MEMBER_0:%.*]] = alloc_stack $StructWithCopyConstructorAndValue +// CHECK: [[MEMBER_0:%.*]] = alloc_stack [lexical] $StructWithCopyConstructorAndValue // CHECK: [[MAKE_MEMBER_FN:%.*]] = function_ref @{{_ZN33StructWithCopyConstructorAndValueC1Ei|\?\?0StructWithCopyConstructorAndValue@@QEAA@H@Z}} : $@convention(c) (Int32) -> @out StructWithCopyConstructorAndValue // CHECK: apply [[MAKE_MEMBER_FN]]([[MEMBER_0]], %{{.*}}) : $@convention(c) (Int32) -> @out StructWithCopyConstructorAndValue -// CHECK: [[AS:%.*]] = alloc_stack $StructWithSubobjectCopyConstructorAndValue +// CHECK: [[AS:%.*]] = alloc_stack [lexical] $StructWithSubobjectCopyConstructorAndValue // CHECK: [[META:%.*]] = metatype $@thin StructWithSubobjectCopyConstructorAndValue.Type // CHECK: [[MEMBER_1:%.*]] = alloc_stack $StructWithCopyConstructorAndValue // CHECK: copy_addr %0 to [initialization] [[MEMBER_1]] : $*StructWithCopyConstructorAndValue @@ -87,10 +87,10 @@ public func testStructWithSubobjectCopyConstructorAndValue() -> Bool { // testStructWithCopyConstructorAndSubobjectCopyConstructorAndValue() // CHECK-LABEL: sil [ossa] @$s4main041testStructWithCopyConstructorAndSubobjectefG5ValueSbyF : $@convention(thin) () -> Bool -// CHECK: [[MEMBER_0:%.*]] = alloc_stack $StructWithCopyConstructorAndValue +// CHECK: [[MEMBER_0:%.*]] = alloc_stack [lexical] $StructWithCopyConstructorAndValue // CHECK: [[CREATE_MEMBER_FN:%.*]] = function_ref @{{_ZN33StructWithCopyConstructorAndValueC1Ei|\?\?0StructWithCopyConstructorAndValue@@QEAA@H@Z}} : $@convention(c) (Int32) -> @out StructWithCopyConstructorAndValue // CHECK: apply [[CREATE_MEMBER_FN]]([[MEMBER_0]], %{{.*}}) : $@convention(c) (Int32) -> @out StructWithCopyConstructorAndValue -// CHECK: [[AS:%.*]] = alloc_stack $StructWithCopyConstructorAndSubobjectCopyConstructorAndValue +// CHECK: [[AS:%.*]] = alloc_stack [lexical] $StructWithCopyConstructorAndSubobjectCopyConstructorAndValue // CHECK: [[MEMBER_1:%.*]] = alloc_stack $StructWithCopyConstructorAndValue // CHECK: copy_addr [[MEMBER_0]] to [initialization] [[MEMBER_1]] : $*StructWithCopyConstructorAndValue // CHECK: [[FN:%.*]] = function_ref @{{_ZN60StructWithCopyConstructorAndSubobjectCopyConstructorAndValueC1E33StructWithCopyConstructorAndValue|\?\?0StructWithCopyConstructorAndSubobjectCopyConstructorAndValue@@QEAA@UStructWithCopyConstructorAndValue@@@Z}} : $@convention(c) (@in StructWithCopyConstructorAndValue) -> @out StructWithCopyConstructorAndSubobjectCopyConstructorAndValue From b4d7a0c208c6efc1ec5c2a12e4ce5f2feae38a4c Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Fri, 9 Sep 2022 13:53:52 -0700 Subject: [PATCH 23/37] [interop][SwiftToCxx] bridge returned C++ record types back to C++ from Swift --- lib/PrintAsClang/ModuleContentsWriter.cpp | 15 ++- lib/PrintAsClang/PrintAsClang.cpp | 31 +++-- lib/PrintAsClang/PrintClangFunction.cpp | 112 ++++++++++++++-- ...ridge-cxx-struct-back-to-cxx-execution.cpp | 79 ++++++++++++ .../bridge-cxx-struct-back-to-cxx.swift | 122 ++++++++++++++++++ test/Interop/CxxToSwiftToCxx/lit.local.cfg | 1 + test/PrintAsCxx/empty.swift | 5 +- test/PrintAsObjC/empty.swift | 2 +- test/PrintAsObjC/mixed-framework-fwd.swift | 2 +- test/PrintAsObjC/mixed-framework.swift | 2 +- 10 files changed, 339 insertions(+), 32 deletions(-) create mode 100644 test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp create mode 100644 test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift create mode 100644 test/Interop/CxxToSwiftToCxx/lit.local.cfg diff --git a/lib/PrintAsClang/ModuleContentsWriter.cpp b/lib/PrintAsClang/ModuleContentsWriter.cpp index b59143de5ba6d..48547450d4da3 100644 --- a/lib/PrintAsClang/ModuleContentsWriter.cpp +++ b/lib/PrintAsClang/ModuleContentsWriter.cpp @@ -26,6 +26,7 @@ #include "swift/AST/SwiftNameTranslation.h" #include "swift/AST/TypeDeclFinder.h" #include "swift/ClangImporter/ClangImporter.h" +#include "swift/Strings.h" #include "clang/AST/Decl.h" #include "clang/Basic/Module.h" @@ -178,6 +179,14 @@ class ModuleWriter { } } + if (outputLangMode == OutputLanguageMode::Cxx) { + // Only add C++ imports in C++ mode for now. + if (!D->hasClangNode()) + return true; + if (otherModule->getName().str() == CLANG_HEADER_MODULE_NAME) + return true; + } + imports.insert(otherModule); return true; } @@ -258,8 +267,10 @@ class ModuleWriter { if (outputLangMode == OutputLanguageMode::Cxx) { if (isa(TD) || isa(TD)) { auto *NTD = cast(TD); - forwardDeclare( - NTD, [&]() { ClangValueTypePrinter::forwardDeclType(os, NTD); }); + if (!addImport(NTD)) { + forwardDeclare( + NTD, [&]() { ClangValueTypePrinter::forwardDeclType(os, NTD); }); + } } return; } diff --git a/lib/PrintAsClang/PrintAsClang.cpp b/lib/PrintAsClang/PrintAsClang.cpp index 67cc2dc1f585d..2574245d3fba3 100644 --- a/lib/PrintAsClang/PrintAsClang.cpp +++ b/lib/PrintAsClang/PrintAsClang.cpp @@ -395,8 +395,11 @@ static int compareImportModulesByName(const ImportModuleTy *left, static void writeImports(raw_ostream &out, llvm::SmallPtrSetImpl &imports, - ModuleDecl &M, StringRef bridgingHeader) { - out << "#if __has_feature(modules)\n"; + ModuleDecl &M, StringRef bridgingHeader, + bool useCxxImport = false) { + // Note: we can't use has_feature(modules) as it's always enabled in C++20 + // mode. + out << "#if __has_feature(objc_modules)\n"; out << "#if __has_warning(\"-Watimport-in-framework-header\")\n" << "#pragma clang diagnostic ignored \"-Watimport-in-framework-header\"\n" @@ -420,6 +423,8 @@ static void writeImports(raw_ostream &out, // Track printed names to handle overlay modules. llvm::SmallPtrSet seenImports; bool includeUnderlying = false; + StringRef importDirective = + useCxxImport ? "#pragma clang module import" : "@import"; for (auto import : sortedImports) { if (auto *swiftModule = import.dyn_cast()) { auto Name = swiftModule->getName(); @@ -428,12 +433,12 @@ static void writeImports(raw_ostream &out, continue; } if (seenImports.insert(Name).second) - out << "@import " << Name.str() << ";\n"; + out << importDirective << ' ' << Name.str() << ";\n"; } else { const auto *clangModule = import.get(); assert(clangModule->isSubModule() && "top-level modules should use a normal swift::ModuleDecl"); - out << "@import "; + out << importDirective << ' '; ModuleDecl::ReverseFullNameIterator(clangModule).printForward(out); out << ";\n"; } @@ -489,16 +494,14 @@ static std::string computeMacroGuard(const ModuleDecl *M) { return (llvm::Twine(M->getNameStr().upper()) + "_SWIFT_H").str(); } -static std::string -getModuleContentsCxxString(ModuleDecl &M, - SwiftToClangInteropContext &interopContext, - bool requiresExposedAttribute) { - SmallPtrSet imports; +static std::string getModuleContentsCxxString( + ModuleDecl &M, SmallPtrSet &imports, + SwiftToClangInteropContext &interopContext, bool requiresExposedAttribute) { std::string moduleContentsBuf; llvm::raw_string_ostream moduleContents{moduleContentsBuf}; printModuleContentsAsCxx(moduleContents, imports, M, interopContext, requiresExposedAttribute); - return moduleContents.str(); + return std::move(moduleContents.str()); } bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, @@ -522,9 +525,13 @@ bool swift::printAsClangHeader(raw_ostream &os, ModuleDecl *M, // FIXME: Expose Swift with @expose by default. if (ExposePublicDeclsInClangHeader || M->DeclContext::getASTContext().LangOpts.EnableCXXInterop) { - os << getModuleContentsCxxString( - *M, interopContext, + SmallPtrSet imports; + auto contents = getModuleContentsCxxString( + *M, imports, interopContext, /*requiresExposedAttribute=*/!ExposePublicDeclsInClangHeader); + // FIXME: In ObjC++ mode, we do not need to reimport duplicate modules. + writeImports(os, imports, *M, bridgingHeader, /*useCxxImport=*/true); + os << contents; } }); writeEpilogue(os); diff --git a/lib/PrintAsClang/PrintClangFunction.cpp b/lib/PrintAsClang/PrintClangFunction.cpp index 44c14dbb8adcd..06bab8bb5a2e5 100644 --- a/lib/PrintAsClang/PrintClangFunction.cpp +++ b/lib/PrintAsClang/PrintClangFunction.cpp @@ -27,6 +27,9 @@ #include "swift/AST/TypeVisitor.h" #include "swift/ClangImporter/ClangImporter.h" #include "swift/IRGen/IRABIDetailsProvider.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/NestedNameSpecifier.h" #include "llvm/ADT/STLExtras.h" using namespace swift; @@ -90,6 +93,74 @@ struct CFunctionSignatureTypePrinterModifierDelegate { mapValueTypeUseKind = None; }; +class ClangTypeHandler { +public: + ClangTypeHandler(const clang::Decl *typeDecl) : typeDecl(typeDecl) {} + + bool isRepresentable() const { + // We can only return trivial types, or + // types that can be moved or copied. + if (auto *record = dyn_cast(typeDecl)) { + return record->isTrivial() || record->hasMoveConstructor() || + record->hasCopyConstructorWithConstParam(); + } + return false; + } + + void printTypeName(raw_ostream &os) const { + auto &clangCtx = typeDecl->getASTContext(); + clang::PrintingPolicy pp(clangCtx.getLangOpts()); + const auto *NS = clang::NestedNameSpecifier::getRequiredQualification( + clangCtx, clangCtx.getTranslationUnitDecl(), + typeDecl->getLexicalDeclContext()); + if (NS) + NS->print(os, pp); + assert(cast(typeDecl)->getDeclName().isIdentifier()); + os << cast(typeDecl)->getName(); + if (auto *ctd = + dyn_cast(typeDecl)) { + if (ctd->getTemplateArgs().size()) { + os << '<'; + llvm::interleaveComma(ctd->getTemplateArgs().asArray(), os, + [&](const clang::TemplateArgument &arg) { + arg.print(pp, os, /*IncludeType=*/true); + }); + os << '>'; + } + } + } + + void printReturnScaffold(raw_ostream &os, + llvm::function_ref bodyOfReturn) { + std::string fullQualifiedType; + std::string typeName; + { + llvm::raw_string_ostream typeNameOS(fullQualifiedType); + printTypeName(typeNameOS); + llvm::raw_string_ostream unqualTypeNameOS(typeName); + unqualTypeNameOS << cast(typeDecl)->getName(); + } + os << "alignas(alignof(" << fullQualifiedType << ")) char storage[sizeof(" + << fullQualifiedType << ")];\n"; + os << "auto * _Nonnull storageObjectPtr = reinterpret_cast<" + << fullQualifiedType << " *>(storage);\n"; + bodyOfReturn("storage"); + os << ";\n"; + auto *cxxRecord = cast(typeDecl); + if (cxxRecord->isTrivial()) { + // Trivial object can be just copied and not destroyed. + os << "return *storageObjectPtr;\n"; + return; + } + os << fullQualifiedType << " result(std::move(*storageObjectPtr));\n"; + os << "storageObjectPtr->~" << typeName << "();\n"; + os << "return result;\n"; + } + +private: + const clang::Decl *typeDecl; +}; + // Prints types in the C function signature that corresponds to the // native Swift function/method. class CFunctionSignatureTypePrinter @@ -235,6 +306,14 @@ class CFunctionSignatureTypePrinter if (languageMode != OutputLanguageMode::Cxx) return ClangRepresentation::unsupported; + if (decl->hasClangNode()) { + ClangTypeHandler handler(decl->getClangDecl()); + if (!handler.isRepresentable()) + return ClangRepresentation::unsupported; + handler.printTypeName(os); + return ClangRepresentation::representable; + } + // FIXME: Handle optional structures. if (typeUseKind == FunctionSignatureTypeUse::ParamType) { if (!isInOutParam) { @@ -938,24 +1017,31 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody( return; } if (auto *decl = resultTy->getNominalOrBoundGenericNominal()) { + auto valueTypeReturnThunker = [&](StringRef resultPointerName) { + if (auto directResultType = signature.getDirectResultType()) { + std::string typeEncoding = + encodeTypeInfo(*directResultType, moduleContext, typeMapping); + os << cxx_synthesis::getCxxImplNamespaceName() + << "::swift_interop_returnDirect_" << typeEncoding << '(' + << resultPointerName << ", "; + printCallToCFunc(None); + os << ')'; + } else { + printCallToCFunc(/*firstParam=*/resultPointerName); + } + }; + if (decl->hasClangNode()) { + ClangTypeHandler handler(decl->getClangDecl()); + assert(handler.isRepresentable()); + handler.printReturnScaffold(os, valueTypeReturnThunker); + return; + } ClangValueTypePrinter valueTypePrinter(os, cPrologueOS, interopContext); valueTypePrinter.printValueTypeReturnScaffold( decl, moduleContext, [&]() { printTypeImplTypeSpecifier(resultTy, moduleContext); }, - [&](StringRef resultPointerName) { - if (auto directResultType = signature.getDirectResultType()) { - std::string typeEncoding = - encodeTypeInfo(*directResultType, moduleContext, typeMapping); - os << cxx_synthesis::getCxxImplNamespaceName() - << "::swift_interop_returnDirect_" << typeEncoding << '(' - << resultPointerName << ", "; - printCallToCFunc(None); - os << ')'; - } else { - printCallToCFunc(/*firstParam=*/resultPointerName); - } - }); + valueTypeReturnThunker); return; } } diff --git a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp new file mode 100644 index 0000000000000..ff38652e818be --- /dev/null +++ b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp @@ -0,0 +1,79 @@ +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxx -emit-clang-header-path %t/UseCxx.h -I %t -enable-experimental-cxx-interop -clang-header-expose-public-decls + +// RUN: %target-interop-build-clangxx -c %t/use-swift-cxx-types.cpp -I %t -o %t/swift-cxx-execution.o -g +// RUN: %target-interop-build-swift %t/use-cxx-types.swift -o %t/swift-cxx-execution -Xlinker %t/swift-cxx-execution.o -module-name UseCxx -Xfrontend -entry-point-function-name -Xfrontend swiftMain -I %t -g + +// RUN: %target-codesign %t/swift-cxx-execution +// RUN: %target-run %t/swift-cxx-execution | %FileCheck %s + +// REQUIRES: executable_test + +//--- header.h + +extern "C" void puts(const char *); + +struct Trivial { + int x, y; + + inline Trivial(int x, int y) : x(x), y(y) {} +}; + +template +struct NonTrivialTemplate { + T x; + + inline NonTrivialTemplate(T x) : x(x) { + puts("create NonTrivialTemplate"); + } + inline NonTrivialTemplate(const NonTrivialTemplate &) = default; + inline NonTrivialTemplate(NonTrivialTemplate &&other) : x(static_cast(other.x)) { + puts("move NonTrivialTemplate"); + } + inline ~NonTrivialTemplate() { + puts("~NonTrivialTemplate"); + } +}; + +//--- module.modulemap +module CxxTest { + header "header.h" + requires cplusplus +} + +//--- use-cxx-types.swift +import CxxTest + +public func retNonTrivial(y: CInt) -> NonTrivialTemplate { + return NonTrivialTemplate(Trivial(42, y)) +} + +public func retTrivial(_ x: CInt) -> Trivial { + return Trivial(x, -x) +} + +//--- use-swift-cxx-types.cpp + +#include "header.h" +#include "UseCxx.h" +#include + +int main() { + { + auto x = UseCxx::retTrivial(423421); + assert(x.x == 423421); + assert(x.y == -423421); + } + { + auto x = UseCxx::retNonTrivial(-942); + assert(x.x.y == -942); + assert(x.x.x == 42); + } +// CHECK: create NonTrivialTemplate +// CHECK-NEXT: move NonTrivialTemplate +// CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: ~NonTrivialTemplate + return 0; +} diff --git a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift new file mode 100644 index 0000000000000..30d667e60c5f3 --- /dev/null +++ b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift @@ -0,0 +1,122 @@ +// RUN: %empty-directory(%t) +// RUN: split-file %s %t + +// RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxxTy -emit-clang-header-path %t/UseCxxTy.h -I %t -enable-experimental-cxx-interop -clang-header-expose-public-decls + +// RUN: %FileCheck %s < %t/UseCxxTy.h + +// FIXME: remove once https://github.com/apple/swift/pull/60971 lands. +// RUN: echo "#include \"header.h\"" > %t/full-cxx-swift-cxx-bridging.h +// RUN: cat %t/UseCxxTy.h >> %t/full-cxx-swift-cxx-bridging.h + +// RUN: %check-interop-cxx-header-in-clang(%t/full-cxx-swift-cxx-bridging.h -Wno-reserved-identifier) + +// FIXME: test in C++ with modules (but libc++ modularization is preventing this) + +//--- header.h + +struct Trivial { + short x, y; +}; + +namespace ns { + + struct TrivialinNS { + short x, y; + }; + + template + struct NonTrivialTemplate { + T x; + + NonTrivialTemplate(); + NonTrivialTemplate(const NonTrivialTemplate &) = default; + NonTrivialTemplate(NonTrivialTemplate &&) = default; + ~NonTrivialTemplate() {} + }; + + using TypeAlias = NonTrivialTemplate; + + struct NonTrivialImplicitMove { + NonTrivialTemplate member; + }; +} + +//--- module.modulemap +module CxxTest { + header "header.h" + requires cplusplus +} + +//--- use-cxx-types.swift +import CxxTest + +public func retNonTrivial() -> ns.NonTrivialTemplate { + return ns.NonTrivialTemplate() +} + +public func retNonTrivial2() -> ns.NonTrivialTemplate { + return ns.NonTrivialTemplate() +} + +public func retNonTrivialImplicitMove() -> ns.NonTrivialImplicitMove { + return ns.NonTrivialImplicitMove() +} + +public func retNonTrivialTypeAlias() -> ns.TypeAlias { + return ns.TypeAlias() +} + +public func retTrivial() -> Trivial { + return Trivial() +} + +// CHECK: #if __has_feature(objc_modules) +// CHECK: #if __has_feature(objc_modules) +// CHECK-NEXT: #if __has_warning("-Watimport-in-framework-header") +// CHECK-NEXT: #pragma clang diagnostic ignored "-Watimport-in-framework-header" +// CHECK-NEXT:#endif +// CHECK-NEXT: #pragma clang module import CxxTest; +// CHECK-NEXT: #endif + + +// CHECK: SWIFT_EXTERN void $s8UseCxxTy13retNonTrivialSo2nsO02__b18TemplateInstN2ns18efH4IiEEVyF(SWIFT_INDIRECT_RESULT void * _Nonnull) SWIFT_NOEXCEPT SWIFT_CALL; // retNonTrivial() +// CHECK: SWIFT_EXTERN struct swift_interop_returnStub_UseCxxTy_uint32_t_0_4 $s8UseCxxTy10retTrivialSo0E0VyF(void) SWIFT_NOEXCEPT SWIFT_CALL; // retTrivial() + +// CHECK: inline ns::NonTrivialTemplate retNonTrivial() noexcept SWIFT_WARN_UNUSED_RESULT { +// CHECK-NEXT: alignas(alignof(ns::NonTrivialTemplate)) char storage[sizeof(ns::NonTrivialTemplate)]; +// CHECK-NEXT: auto * _Nonnull storageObjectPtr = reinterpret_cast *>(storage); +// CHECK-NEXT: _impl::$s8UseCxxTy13retNonTrivialSo2nsO02__b18TemplateInstN2ns18efH4IiEEVyF(storage); +// CHECK-NEXT: ns::NonTrivialTemplate result(std::move(*storageObjectPtr)); +// CHECK-NEXT: storageObjectPtr->~NonTrivialTemplate(); +// CHECK-NEXT: return result; +// CHECK-NEXT: } +// CHECK-EMPTY: +// CHECK-EMPTY: +// CHECK: inline ns::NonTrivialTemplate retNonTrivial2() noexcept SWIFT_WARN_UNUSED_RESULT { +// CHECK-NEXT: alignas(alignof(ns::NonTrivialTemplate)) char storage[sizeof(ns::NonTrivialTemplate)]; +// CHECK-NEXT: auto * _Nonnull storageObjectPtr = reinterpret_cast *>(storage); +// CHECK-NEXT: _impl::$s8UseCxxTy14retNonTrivial2So2nsO02__b18TemplateInstN2ns18e7TrivialH20INS_11TrivialinNSEEEVyF(storage); +// CHECK-NEXT: ns::NonTrivialTemplate result(std::move(*storageObjectPtr)); +// CHECK-NEXT: storageObjectPtr->~NonTrivialTemplate(); +// CHECK-NEXT: return result; +// CHECK-NEXT: } + +// CHECK: inline ns::NonTrivialImplicitMove retNonTrivialImplicitMove() noexcept SWIFT_WARN_UNUSED_RESULT { +// CHECK-NEXT: alignas(alignof(ns::NonTrivialImplicitMove)) char storage[sizeof(ns::NonTrivialImplicitMove)]; +// CHECK-NEXT: auto * _Nonnull storageObjectPtr = reinterpret_cast(storage); +// CHECK-NEXT: _impl::$s8UseCxxTy25retNonTrivialImplicitMoveSo2nsO0efgH0VyF(storage); +// CHECK-NEXT: ns::NonTrivialImplicitMove result(std::move(*storageObjectPtr)); +// CHECK-NEXT: storageObjectPtr->~NonTrivialImplicitMove(); +// CHECK-NEXT: return result; +// CHECK-NEXT: } + +// CHECK: ns::NonTrivialTemplate retNonTrivialTypeAlias() noexcept SWIFT_WARN_UNUSED_RESULT { + +// CHECK: inline Trivial retTrivial() noexcept SWIFT_WARN_UNUSED_RESULT { +// CHECK-NEXT: alignas(alignof(Trivial)) char storage[sizeof(Trivial)]; +// CHECK-NEXT: auto * _Nonnull storageObjectPtr = reinterpret_cast(storage); +// CHECK-NEXT: _impl::swift_interop_returnDirect_UseCxxTy_uint32_t_0_4(storage, _impl::$s8UseCxxTy10retTrivialSo0E0VyF()); +// CHECK-NEXT: return *storageObjectPtr; +// CHECK-NEXT: } + diff --git a/test/Interop/CxxToSwiftToCxx/lit.local.cfg b/test/Interop/CxxToSwiftToCxx/lit.local.cfg new file mode 100644 index 0000000000000..c55f1dcb05f0f --- /dev/null +++ b/test/Interop/CxxToSwiftToCxx/lit.local.cfg @@ -0,0 +1 @@ +config.suffixes = ['.swift', '.cpp', '.mm'] diff --git a/test/PrintAsCxx/empty.swift b/test/PrintAsCxx/empty.swift index 88dd8ba085ad3..ec2d961552f3c 100644 --- a/test/PrintAsCxx/empty.swift +++ b/test/PrintAsCxx/empty.swift @@ -90,12 +90,13 @@ // CHECK: # define SWIFT_ERROR_RESULT __attribute__((swift_error_result)) // CHECK-LABEL: #if defined(__OBJC__) -// CHECK-NEXT: #if __has_feature(modules) +// CHECK-NEXT: #if __has_feature(objc_modules) // CHECK-LABEL: #if defined(__OBJC__) // CHECK-NEXT: #endif // CHECK-NEXT: #if defined(__cplusplus) -// CHECK-NEXT: #ifndef SWIFT_PRINTED_CORE +// CHECK-NEXT: #if __has_feature(objc_modules) +// CHECK: #ifndef SWIFT_PRINTED_CORE // CHECK: } // namespace swift // CHECK-EMPTY: // CHECK-NEXT: #endif diff --git a/test/PrintAsObjC/empty.swift b/test/PrintAsObjC/empty.swift index a39e2c3c750b8..d4b38e1d652ef 100644 --- a/test/PrintAsObjC/empty.swift +++ b/test/PrintAsObjC/empty.swift @@ -34,7 +34,7 @@ // CHECK: # define SWIFT_EXTENSION(M) // CHECK: # define OBJC_DESIGNATED_INITIALIZER -// CHECK-LABEL: #if __has_feature(modules) +// CHECK-LABEL: #if __has_feature(objc_modules) // CHECK-NEXT: #if __has_warning // CHECK-NEXT: #pragma clang diagnostic // CHECK-NEXT: #endif diff --git a/test/PrintAsObjC/mixed-framework-fwd.swift b/test/PrintAsObjC/mixed-framework-fwd.swift index 168ce13f05791..709ac6024ad05 100644 --- a/test/PrintAsObjC/mixed-framework-fwd.swift +++ b/test/PrintAsObjC/mixed-framework-fwd.swift @@ -17,7 +17,7 @@ // REQUIRES: objc_interop -// CHECK-LABEL: #if __has_feature(modules) +// CHECK-LABEL: #if __has_feature(objc_modules) // CHECK-NEXT: #if __has_warning // CHECK-NEXT: #pragma clang diagnostic // CHECK-NEXT: #endif diff --git a/test/PrintAsObjC/mixed-framework.swift b/test/PrintAsObjC/mixed-framework.swift index f819850088945..11dab8ee4dda2 100644 --- a/test/PrintAsObjC/mixed-framework.swift +++ b/test/PrintAsObjC/mixed-framework.swift @@ -12,7 +12,7 @@ // CHECK: #pragma clang diagnostic push -// CHECK-LABEL: #if __has_feature(modules) +// CHECK-LABEL: #if __has_feature(objc_modules) // CHECK-NEXT: #if __has_warning("-Watimport-in-framework-header") // CHECK-NEXT: #pragma clang diagnostic ignored "-Watimport-in-framework-header" // CHECK-NEXT: #endif From 0a16cc362a2d522ca9540a7ffce2b32f4e581f45 Mon Sep 17 00:00:00 2001 From: Michael Gottesman Date: Wed, 31 Aug 2022 16:46:00 -0700 Subject: [PATCH 24/37] [move-only] Implement an initial version of the move only address checker. Some notes: 1. I added support for both loadable/address only types. 2. These tests are based off of porting the move only object tests for inout, vars, mutating self, etc. 3. I did not include already written tests for address only types in this specific merge since I need to change us to borrow move only var like types. Without that, we get a lot of spurious error msgs and the burden of writing that is not worth it. So instead in a forthcoming commit where I fix that issue in SILGen, I will commit the corresponding address only tests for this work. 4. I did not include support for trivial types in this. I am going to do object/address for that at the same time. --- docs/SIL.rst | 8 +- include/swift/AST/DiagnosticsSIL.def | 14 +- include/swift/SIL/PrunedLiveness.h | 132 ++ .../swift/SILOptimizer/PassManager/Passes.def | 2 + lib/SIL/Utils/PrunedLiveness.cpp | 50 +- lib/SILGen/SILGenBuilder.cpp | 3 +- lib/SILGen/SILGenPattern.cpp | 2 +- lib/SILGen/SILGenProlog.cpp | 13 +- lib/SILOptimizer/Mandatory/CMakeLists.txt | 1 + .../Mandatory/MoveOnlyAddressChecker.cpp | 1714 ++++++++++++++ lib/SILOptimizer/PassManager/PassPipeline.cpp | 20 +- test/SILGen/moveonly_var.swift | 57 + .../moveonly_addresschecker_diagnostics.swift | 2070 +++++++++++++++++ 13 files changed, 4067 insertions(+), 19 deletions(-) create mode 100644 lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp create mode 100644 test/SILGen/moveonly_var.swift create mode 100644 test/SILOptimizer/moveonly_addresschecker_diagnostics.swift diff --git a/docs/SIL.rst b/docs/SIL.rst index 36325edb97176..d66bd0cd256fe 100644 --- a/docs/SIL.rst +++ b/docs/SIL.rst @@ -2593,9 +2593,9 @@ only values" that are guaranteed to never be copied. This is enforced by: * Having SILGen emit copies as it normally does. * Use OSSA canonicalization to eliminate copies that aren't needed semantically - due to consuming uses of the value. This is implemented by the pass guaranteed - pass "MoveOnlyChecker". Emit errors on any of the consuming uses that we found - in said pass. + due to consuming uses of the value. This is implemented by the guaranteed + passes "MoveOnlyObjectChecker" and "MoveOnlyAddressChecker". We will emit + errors on any of the consuming uses that we found in said pass. Assuming that no errors are emitted, we can then conclude before we reach canonical SIL that the value was never copied and thus is a "move only value" @@ -2667,7 +2667,7 @@ rather than a viral type level annotation that would constrain the type system. As mentioned above trivial move only wrapped types are actually non-trivial. This is because in SIL ownership is tied directly to non-trivialness so unless we did that we could not track ownership -accurately. This is loss of triviality is not an issue for most of the pipeline +accurately. This loss of triviality is not an issue for most of the pipeline since we eliminate all move only wrapper types for trivial types during the guaranteed optimizations after we have run various ownership checkers but before we have run diagnostics for trivial types (e.x.: DiagnosticConstantPropagation). diff --git a/include/swift/AST/DiagnosticsSIL.def b/include/swift/AST/DiagnosticsSIL.def index 4f7b7134fcbe5..8067fe5036a3a 100644 --- a/include/swift/AST/DiagnosticsSIL.def +++ b/include/swift/AST/DiagnosticsSIL.def @@ -727,13 +727,25 @@ ERROR(noimplicitcopy_used_on_generic_or_existential, none, // move only checker diagnostics ERROR(sil_moveonlychecker_owned_value_consumed_more_than_once, none, "'%0' consumed more than once", (StringRef)) +ERROR(sil_moveonlychecker_value_used_after_consume, none, + "'%0' used after consume. Lifetime extension of variable requires a copy", (StringRef)) ERROR(sil_moveonlychecker_guaranteed_value_consumed, none, "'%0' has guaranteed ownership but was consumed", (StringRef)) +ERROR(sil_moveonlychecker_inout_not_reinitialized_before_end_of_function, none, + "'%0' consumed but not reinitialized before end of function", (StringRef)) +ERROR(sil_moveonlychecker_value_consumed_in_a_loop, none, + "'%0' consumed by a use in a loop", (StringRef)) + NOTE(sil_moveonlychecker_consuming_use_here, none, "consuming use", ()) -ERROR(sil_moveonlychecker_not_understand_mark_move, none, +NOTE(sil_moveonlychecker_nonconsuming_use_here, none, + "non-consuming use", ()) +ERROR(sil_moveonlychecker_not_understand_no_implicit_copy, none, "Usage of @noImplicitCopy that the move checker does not know how to " "check!", ()) +ERROR(sil_moveonlychecker_not_understand_moveonly, none, + "Usage of a move only type that the move checker does not know how to " + "check!", ()) // move kills copyable values checker diagnostics ERROR(sil_movekillscopyablevalue_value_consumed_more_than_once, none, diff --git a/include/swift/SIL/PrunedLiveness.h b/include/swift/SIL/PrunedLiveness.h index adbfac36ad7dd..5896fa112a128 100644 --- a/include/swift/SIL/PrunedLiveness.h +++ b/include/swift/SIL/PrunedLiveness.h @@ -556,9 +556,141 @@ struct PrunedLivenessBoundary { ArrayRef postDomBlocks); }; +//===----------------------------------------------------------------------===// +// Field Sensitive Pruned Liveness +//===----------------------------------------------------------------------===// + /// Given a type T and a descendent field F in T's type tree, then the /// sub-element number of F is the first leaf element of the type tree in its /// linearized representation. +/// +/// Linearized Representation of Structs/Tuples +/// ------------------------------------------- +/// +/// For structs/tuples, the linearized representation is just an array with one +/// element for each leaf element. Thus if we have a struct of the following +/// sort: +/// +/// ``` +/// struct Pair { +/// var lhs: Int +/// var rhs: Int +/// } +/// +/// struct MyStruct { +/// var firstField: Int +/// var pairField: Pair +/// var tupleField: (Int, Int) +/// } +/// ``` +/// +/// the linearized representation of MyStruct's type tree leaves would be: +/// +/// ``` +/// [firstField, pairField.lhs, pairField.rhs, tupleField.0, tupleField.1] +/// ``` +/// +/// So if one had an uninitialized myStruct and initialized pairField, one would +/// get the following bit set of liveness: +/// +/// ``` +/// [0, 1, 1, 0, 0] +/// ``` +/// +/// Linearized Representation of Enums +/// ---------------------------------- +/// +/// Since enums are sum types, an enum can require different numbers of bits in +/// its linearized representation depending on the payload of the case that the +/// enum is initialized to. To work around this problem in our representation, +/// we always store enough bits for the max sized payload of all cases of the +/// enum and add an additional last bit for the discriminator. Any extra bits +/// that may be needed (e.x.: we are processing a enum case with a smaller +/// payload) are always assumed to be set to the same value that the +/// discriminator bit is set to. This representation allows us to track liveness +/// trading off the ability to determine information about the actual case that +/// we are tracking. Since we just care about liveness, this is a trade off that +/// we are willing to make since our goal (computing liveness) is still solved. +/// +/// With the above paragraph in mind, an example of the bit layout of an enum +/// looks as follows: +/// +/// ``` +/// [ PAYLOAD BITS | EXTRA_TOP_LEVEL_BITS | DISCRIMINATOR_BIT ] +/// ``` +/// +/// Notice how placing the discriminator bit last ensures that separately the +/// payload and the extra top level bits/discriminator bit are both contiguous +/// in the representation. This ensures that we can test both that the payload +/// is live and separately that the discriminator/extra top level bits are live +/// with a single contiguous range of bits. This is important since field +/// sensitive liveness can only compute liveness for contiguous ranges of bits. +/// +/// Lets look at some examples, starting with E: +/// +/// ``` +/// enum E { +/// case firstCase +/// case secondCase(Int) +/// case thirdCase(Pair) +/// } +/// ``` +/// +/// The linearized representation of E would be three slots since the payload +/// with the largest linearized representation is Pair: +/// +/// ----- |E| -------- +/// / \ +/// / \ +/// v v +/// | Pair | | Discriminator | +/// / \ +/// / \ +/// / \ +/// v v +/// | LHS | | RHS | +/// +/// This in term would mean the following potential bit representations given +/// various cases/states of deinitialization of the payload. +/// +/// ``` +/// firstCase inited: [1, 1, 1] +/// firstCase deinited: [0, 0, 0] +/// +/// secondCase inited: [1, 1, 1] +/// secondCase payload deinited: [0, 1, 1] +/// secondCase deinited: [0, 0, 0] +/// +/// thirdCase inited: [1, 1, 1] +/// thirdCase payload deinited: [0, 0, 1] +/// thirdCase deinited: [0, 0, 0] +/// ``` +/// +/// Now lets consider an enum without any payload cases. Given such an enum: +/// +/// ``` +/// enum E2 { +/// case firstCase +/// case secondCase +/// case thirdCase +/// } +/// ``` +/// +/// we would only use a single bit in our linearized representation, just for +/// the discriminator value. +/// +/// Enums and Partial Initialization +/// -------------------------------- +/// +/// One property of our representation of structs and tuples is that a code +/// generator can reinitialize a struct/tuple completely just by re-initializing +/// each of its sub-types individually. This is not possible for enums in our +/// representation since if one just took the leaf nodes for the payload, one +/// would not update the bit for the enum case itself and any additional spare +/// bits. Luckily for us, this is actually impossible to do in SIL since it is +/// impossible to dynamically change the payload of an enum without destroying +/// the original enum and its payload since that would be a verifier caught +/// leak. struct SubElementNumber { unsigned number; diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index bd135256f74cb..23ba3b489fe75 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -454,6 +454,8 @@ PASS(AssemblyVisionRemarkGenerator, "assembly-vision-remark-generator", "Emit assembly vision remarks that provide source level guidance of where runtime calls ended up") PASS(MoveOnlyObjectChecker, "sil-move-only-object-checker", "Pass that enforces move only invariants on raw SIL for objects") +PASS(MoveOnlyAddressChecker, "sil-move-only-address-checker", + "Pass that enforces move only invariants on raw SIL for addresses") PASS(MoveKillsCopyableValuesChecker, "sil-move-kills-copyable-values-checker", "Pass that checks that any copyable (non-move only) value that is passed " "to _move do not have any uses later than the _move") diff --git a/lib/SIL/Utils/PrunedLiveness.cpp b/lib/SIL/Utils/PrunedLiveness.cpp index e6c83f0a1617e..d072462eeec5d 100644 --- a/lib/SIL/Utils/PrunedLiveness.cpp +++ b/lib/SIL/Utils/PrunedLiveness.cpp @@ -11,10 +11,12 @@ //===----------------------------------------------------------------------===// #include "swift/SIL/PrunedLiveness.h" +#include "swift/AST/TypeExpansionContext.h" #include "swift/Basic/Defer.h" #include "swift/SIL/BasicBlockDatastructures.h" #include "swift/SIL/BasicBlockUtils.h" #include "swift/SIL/OwnershipUtils.h" +#include "swift/SIL/SILInstruction.h" using namespace swift; @@ -406,8 +408,26 @@ TypeSubElementCount::TypeSubElementCount(SILType type, SILModule &mod, return; } - // If this isn't a tuple or struct, it is a single element. This was our - // default value, so we can just return. + // If we have an enum, we add one for tracking if the base enum is set and use + // the remaining bits for the max sized payload. This ensures that if we have + // a smaller sized payload, we still get all of the bits set, allowing for a + // homogeneous representation. + if (auto *enumDecl = type.getEnumOrBoundGenericEnum()) { + unsigned numElements = 0; + for (auto *eltDecl : enumDecl->getAllElements()) { + if (!eltDecl->hasAssociatedValues()) + continue; + numElements = std::max( + numElements, + unsigned(TypeSubElementCount( + type.getEnumElementType(eltDecl, mod, context), mod, context))); + } + number = numElements + 1; + return; + } + + // If this isn't a tuple, struct, or enum, it is a single element. This was + // our default value, so we can just return. } Optional @@ -463,8 +483,30 @@ SubElementNumber::compute(SILValue projectionDerivedFromRoot, continue; } - // This fails when we visit unchecked_take_enum_data_addr. We should just - // add support for enums. + // In the case of enums, we note that our representation is: + // + // ---------|Enum| --- + // / \ + // / \ + // v v + // |Bits for Max Sized Payload| |Discrim Bit| + // + // So our payload is always going to start at the current field number since + // we are the left most child of our parent enum. So we just need to look + // through to our parent enum. + if (auto *enumData = dyn_cast( + projectionDerivedFromRoot)) { + projectionDerivedFromRoot = enumData->getOperand(); + continue; + } + + // Init enum data addr is treated like unchecked take enum data addr. + if (auto *initData = + dyn_cast(projectionDerivedFromRoot)) { + projectionDerivedFromRoot = initData->getOperand(); + continue; + } + #ifndef NDEBUG if (!isa(projectionDerivedFromRoot)) { llvm::errs() << "Unknown access path instruction!\n"; diff --git a/lib/SILGen/SILGenBuilder.cpp b/lib/SILGen/SILGenBuilder.cpp index 6b62139a49632..e167db21d435c 100644 --- a/lib/SILGen/SILGenBuilder.cpp +++ b/lib/SILGen/SILGenBuilder.cpp @@ -946,7 +946,8 @@ ManagedValue SILGenBuilder::createGuaranteedCopyableToMoveOnlyWrapperValue( ManagedValue SILGenBuilder::createMarkMustCheckInst(SILLocation loc, ManagedValue value, MarkMustCheckInst::CheckKind kind) { - assert(value.isPlusOne(SGF) && "Argument must be at +1!"); + assert((value.isPlusOne(SGF) || value.isLValue()) && + "Argument must be at +1 or be an inout!"); CleanupCloner cloner(*this, value); auto *mdi = SILBuilder::createMarkMustCheckInst( loc, value.forward(getSILGenFunction()), kind); diff --git a/lib/SILGen/SILGenPattern.cpp b/lib/SILGen/SILGenPattern.cpp index 58d7b10529d49..d7d59b838c33b 100644 --- a/lib/SILGen/SILGenPattern.cpp +++ b/lib/SILGen/SILGenPattern.cpp @@ -2834,7 +2834,7 @@ void SILGenFunction::emitSwitchStmt(SwitchStmt *S) { auto subject = ([&]() -> ConsumableManagedValue { // If we have a move only value, ensure plus one and convert it. Switches // always consume move only values. - if (subjectMV.getType().isMoveOnly()) { + if (subjectMV.getType().isMoveOnly() && subjectMV.getType().isObject()) { if (subjectMV.getType().isMoveOnlyWrapped()) { subjectMV = B.createOwnedMoveOnlyWrapperToCopyableValue( S, subjectMV.ensurePlusOne(*this, S)); diff --git a/lib/SILGen/SILGenProlog.cpp b/lib/SILGen/SILGenProlog.cpp index 70d5fd7853e7a..59cc18c19f9f8 100644 --- a/lib/SILGen/SILGenProlog.cpp +++ b/lib/SILGen/SILGenProlog.cpp @@ -22,6 +22,7 @@ #include "swift/AST/ParameterList.h" #include "swift/AST/PropertyWrappers.h" #include "swift/SIL/SILArgument.h" +#include "swift/SIL/SILArgumentConvention.h" #include "swift/SIL/SILInstruction.h" using namespace swift; @@ -108,8 +109,14 @@ class EmitBBArguments : public CanTypeVisitorcreateFunctionArgument(ty, VD); + if (isInOut && (ty.isMoveOnly() && !ty.isMoveOnlyWrapped())) { + arg = SGF.B.createMarkMustCheckInst( + Loc, arg, MarkMustCheckInst::CheckKind::NoImplicitCopy); + } SGF.VarLocs[VD] = SILGenFunction::VarLoc::get(arg); SILDebugVariable DbgVar(VD->isLet(), ArgNo); if (ty.isAddress()) { diff --git a/lib/SILOptimizer/Mandatory/CMakeLists.txt b/lib/SILOptimizer/Mandatory/CMakeLists.txt index 3e5221dcbfa81..e0464196b573c 100644 --- a/lib/SILOptimizer/Mandatory/CMakeLists.txt +++ b/lib/SILOptimizer/Mandatory/CMakeLists.txt @@ -25,6 +25,7 @@ target_sources(swiftSILOptimizer PRIVATE MoveKillsCopyableAddressesChecker.cpp MoveKillsCopyableValuesChecker.cpp MoveOnlyObjectChecker.cpp + MoveOnlyAddressChecker.cpp NestedSemanticFunctionCheck.cpp OptimizeHopToExecutor.cpp PerformanceDiagnostics.cpp diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp new file mode 100644 index 0000000000000..39781333dd8f6 --- /dev/null +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp @@ -0,0 +1,1714 @@ +//===--- MoveOnlyAddressChecker.cpp ---------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +/// +/// Move Only Checking of Addresses +/// ------------------------------- +/// +/// In this file, we implement move checking of addresses. This allows for the +/// compiler to perform move checking of address only lets, vars, inout args, +/// and mutating self. +/// +/// Algorithm At a High Level +/// ------------------------- +/// +/// At a high level, this algorithm can be conceptualized as attempting to +/// completely classify the memory behavior of the recursive uses of a move only +/// marked address and then seek to transform those uses such that they are in +/// "simple move only address" form. We define "simple move only address" to +/// mean that along any path from an init of an address to a consume, all uses +/// are guaranteed to be semantically "borrow uses". If we find that any such +/// owned uses can not be turned into a "borrow" use, then we emit an error +/// since we would need to insert a copy there. +/// +/// To implement this, our algorithm works in 4 stages: a use classification +/// stage, a dataflow stage, and then depending on success/failure one of two +/// transform stagesWe describe them below. +/// +/// Use Classification Stage +/// ~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Here we use an AccessPath based analysis to transitively visit all uses of +/// our marked address and classify a use as one of the following kinds of uses: +/// +/// * init - store [init], copy_addr [init] %dest. +/// * destroy - destroy_addr. +/// * pureTake - load [take], copy_addr [take] %src. +/// * copyTransformableToTake - certain load [copy], certain copy_addr ![take] +/// %src of a temporary %dest. +/// * reinit - store [assign], copy_addr ![init] %dest +/// * borrow - load_borror, a load [copy] without consuming uses. +/// * livenessOnly - a read only use of the address. +/// +/// We classify these by adding them to several disjoint SetVectors which track +/// membership. +/// +/// When we classify an instruction as copyTransformableToTake, we perform some +/// extra preprocessing to determine if we can actually transform this copy to a +/// take. This means that we: +/// +/// 1. For loads, we perform object move only checking. If we find a need for +/// multiple copies, we emit an error. If we find no extra copies needed, we +/// classify the load [copy] as a take if it has any last consuming uses and a +/// borrow if it only has destroy_addr consuming uses. +/// +/// 2. For copy_addr, we pattern match if a copy_addr is initializing a "simple +/// temporary" (an alloc_stack with only one use that initializes it, a +/// copy_addr [init] in the same block). In this case, if the copy_addr only has +/// destroy_addr consuming uses, we treat it as a borrow... otherwise, we treat +/// it as a take. If we find any extra initializations, we fail the visitor so +/// we emit a "I don't understand this error" so that users report this case and +/// we can extend it as appropriate. +/// +/// If we fail in either case, if we emit an error, we bail early with success +/// so we can assume invariants later in the dataflow stages that make the +/// dataflow easier. +/// +/// Dataflow Stage +/// ~~~~~~~~~~~~~~ +/// +/// To perform our dataflow, we do the following: +/// +/// 1. We walk each block from top to bottom performing the single block version +/// of the algorithm and preparing field sensitive pruned liveness. +/// +/// 2. If we need to, we then use field sensitive pruned liveness to perform +/// global dataflow to determine if any of our takeOrCopies are within the +/// boundary lifetime implying a violation. +/// +/// Success Transformation +/// ~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Upon success Now that we know that we can change our address into "simple +/// move only address form", we transform the IR in the following way: +/// +/// 1. Any load [copy] that are classified as borrows are changed to +/// load_borrow. +/// 2. Any load [copy] that are classified as takes are changed to load [take]. +/// 3. Any copy_addr [init] temporary allocation are eliminated with their +/// destroy_addr. All uses are placed on the source address. +/// 4. Any destroy_addr that is paired with a copyTransformableToTake is +/// eliminated. +/// +/// Fail Transformation +/// ~~~~~~~~~~~~~~~~~~~ +/// +/// If we emit any diagnostics, we loop through the function one last time after +/// we are done processing and convert all load [copy]/copy_addr of move only +/// types into their explicit forms. We take a little more compile time, but we +/// are going to fail anyways at this point, so it is ok to do so since we will +/// fail before attempting to codegen into LLVM IR. +/// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "sil-move-only-checker" + +#include "swift/AST/DiagnosticEngine.h" +#include "swift/AST/DiagnosticsSIL.h" +#include "swift/Basic/Defer.h" +#include "swift/Basic/FrozenMultiMap.h" +#include "swift/SIL/ApplySite.h" +#include "swift/SIL/BasicBlockBits.h" +#include "swift/SIL/BasicBlockData.h" +#include "swift/SIL/BasicBlockDatastructures.h" +#include "swift/SIL/BasicBlockUtils.h" +#include "swift/SIL/Consumption.h" +#include "swift/SIL/DebugUtils.h" +#include "swift/SIL/InstructionUtils.h" +#include "swift/SIL/MemAccessUtils.h" +#include "swift/SIL/OwnershipUtils.h" +#include "swift/SIL/PostOrder.h" +#include "swift/SIL/PrunedLiveness.h" +#include "swift/SIL/SILArgument.h" +#include "swift/SIL/SILArgumentConvention.h" +#include "swift/SIL/SILBasicBlock.h" +#include "swift/SIL/SILBuilder.h" +#include "swift/SIL/SILFunction.h" +#include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILUndef.h" +#include "swift/SIL/SILValue.h" +#include "swift/SILOptimizer/Analysis/ClosureScope.h" +#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" +#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" +#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h" +#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h" +#include "swift/SILOptimizer/Utils/InstructionDeleter.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" + +using namespace swift; + +//===----------------------------------------------------------------------===// +// MARK: Utilities +//===----------------------------------------------------------------------===// + +template +static void diagnose(ASTContext &Context, SourceLoc loc, Diag diag, + U &&...args) { + Context.Diags.diagnose(loc, diag, std::forward(args)...); +} + +static StringRef getVariableNameForValue(MarkMustCheckInst *mmci) { + if (auto *allocInst = dyn_cast(mmci->getOperand())) { + DebugVarCarryingInst debugVar(allocInst); + if (auto varInfo = debugVar.getVarInfo()) { + return varInfo->Name; + } else { + if (auto *decl = debugVar.getDecl()) { + return decl->getBaseName().userFacingName(); + } + } + } + + if (auto *use = getSingleDebugUse(mmci)) { + DebugVarCarryingInst debugVar(use->getUser()); + if (auto varInfo = debugVar.getVarInfo()) { + return varInfo->Name; + } else { + if (auto *decl = debugVar.getDecl()) { + return decl->getBaseName().userFacingName(); + } + } + } + + return "unknown"; +} + +namespace { + +struct DiagnosticEmitter { + // Track any violating uses we have emitted a diagnostic for so we don't emit + // multiple diagnostics for the same use. + SmallPtrSet useWithDiagnostic; + + void clear() { useWithDiagnostic.clear(); } + + void emitAddressDiagnostic(MarkMustCheckInst *markedValue, + SILInstruction *lastLiveUse, + SILInstruction *violatingUse, bool isUseConsuming, + bool isInOutEndOfFunction); + void emitInOutEndOfFunctionDiagnostic(MarkMustCheckInst *markedValue, + SILInstruction *violatingUse); + void emitAddressDiagnosticNoCopy(MarkMustCheckInst *markedValue, + SILInstruction *consumingUse); +}; + +} // namespace + +void DiagnosticEmitter::emitAddressDiagnostic( + MarkMustCheckInst *markedValue, SILInstruction *lastLiveUse, + SILInstruction *violatingUse, bool isUseConsuming, + bool isInOutEndOfFunction = false) { + if (!useWithDiagnostic.insert(violatingUse).second) + return; + + auto &astContext = markedValue->getFunction()->getASTContext(); + StringRef varName = getVariableNameForValue(markedValue); + + LLVM_DEBUG(llvm::dbgs() << "Emitting error!\n"); + LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue); + LLVM_DEBUG(llvm::dbgs() << " Last Live Use: " << *lastLiveUse); + LLVM_DEBUG(llvm::dbgs() << " Last Live Use Is Consuming? " + << (isUseConsuming ? "yes" : "no") << '\n'); + LLVM_DEBUG(llvm::dbgs() << " Violating Use: " << *violatingUse); + + // If our liveness use is the same as our violating use, then we know that we + // had a loop. Give a better diagnostic. + if (lastLiveUse == violatingUse) { + diagnose(astContext, + markedValue->getDefiningInstruction()->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_value_consumed_in_a_loop, varName); + diagnose(astContext, violatingUse->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); + return; + } + + if (isInOutEndOfFunction) { + diagnose( + astContext, + markedValue->getDefiningInstruction()->getLoc().getSourceLoc(), + diag:: + sil_moveonlychecker_inout_not_reinitialized_before_end_of_function, + varName); + diagnose(astContext, violatingUse->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); + return; + } + + // First if we are consuming emit an error for no implicit copy semantics. + if (isUseConsuming) { + diagnose(astContext, + markedValue->getDefiningInstruction()->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_owned_value_consumed_more_than_once, + varName); + diagnose(astContext, violatingUse->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); + diagnose(astContext, lastLiveUse->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); + return; + } + + // Otherwise, use the "used after consuming use" error. + diagnose(astContext, + markedValue->getDefiningInstruction()->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_value_used_after_consume, varName); + diagnose(astContext, violatingUse->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); + diagnose(astContext, lastLiveUse->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_nonconsuming_use_here); +} + +void DiagnosticEmitter::emitInOutEndOfFunctionDiagnostic( + MarkMustCheckInst *markedValue, SILInstruction *violatingUse) { + if (!useWithDiagnostic.insert(violatingUse).second) + return; + + assert(cast(markedValue->getOperand()) + ->getArgumentConvention() + .isInoutConvention() && + "Expected markedValue to be on an inout"); + + auto &astContext = markedValue->getFunction()->getASTContext(); + StringRef varName = getVariableNameForValue(markedValue); + + LLVM_DEBUG(llvm::dbgs() << "Emitting inout error error!\n"); + LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue); + LLVM_DEBUG(llvm::dbgs() << " Violating Use: " << *violatingUse); + + // Otherwise, we need to do no implicit copy semantics. If our last use was + // consuming message: + diagnose( + astContext, + markedValue->getDefiningInstruction()->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_inout_not_reinitialized_before_end_of_function, + varName); + diagnose(astContext, violatingUse->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); +} + +void DiagnosticEmitter::emitAddressDiagnosticNoCopy( + MarkMustCheckInst *markedValue, SILInstruction *consumingUse) { + if (!useWithDiagnostic.insert(consumingUse).second) + return; + + auto &astContext = markedValue->getFunction()->getASTContext(); + StringRef varName = getVariableNameForValue(markedValue); + + LLVM_DEBUG(llvm::dbgs() << "Emitting no copy error!\n"); + LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue); + LLVM_DEBUG(llvm::dbgs() << " Consuming Use: " << *consumingUse); + + // Otherwise, we need to do no implicit copy semantics. If our last use was + // consuming message: + diagnose(astContext, + markedValue->getDefiningInstruction()->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_guaranteed_value_consumed, varName); + diagnose(astContext, consumingUse->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); +} + +static bool memInstMustInitialize(Operand *memOper) { + SILValue address = memOper->get(); + + SILInstruction *memInst = memOper->getUser(); + + switch (memInst->getKind()) { + default: + return false; + + case SILInstructionKind::CopyAddrInst: { + auto *CAI = cast(memInst); + return CAI->getDest() == address && CAI->isInitializationOfDest(); + } + case SILInstructionKind::ExplicitCopyAddrInst: { + auto *CAI = cast(memInst); + return CAI->getDest() == address && CAI->isInitializationOfDest(); + } + case SILInstructionKind::MarkUnresolvedMoveAddrInst: { + return cast(memInst)->getDest() == address; + } + case SILInstructionKind::InitExistentialAddrInst: + case SILInstructionKind::InitEnumDataAddrInst: + case SILInstructionKind::InjectEnumAddrInst: + return true; + + case SILInstructionKind::BeginApplyInst: + case SILInstructionKind::TryApplyInst: + case SILInstructionKind::ApplyInst: { + FullApplySite applySite(memInst); + return applySite.isIndirectResultOperand(*memOper); + } + case SILInstructionKind::StoreInst: { + auto qual = cast(memInst)->getOwnershipQualifier(); + return qual == StoreOwnershipQualifier::Init || + qual == StoreOwnershipQualifier::Trivial; + } + +#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \ + case SILInstructionKind::Store##Name##Inst: \ + return cast(memInst)->isInitializationOfDest(); +#include "swift/AST/ReferenceStorage.def" + } +} + +static bool memInstMustReinitialize(Operand *memOper) { + SILValue address = memOper->get(); + + SILInstruction *memInst = memOper->getUser(); + + switch (memInst->getKind()) { + default: + return false; + + case SILInstructionKind::CopyAddrInst: { + auto *CAI = cast(memInst); + return CAI->getDest() == address && !CAI->isInitializationOfDest(); + } + case SILInstructionKind::ExplicitCopyAddrInst: { + auto *CAI = cast(memInst); + return CAI->getDest() == address && !CAI->isInitializationOfDest(); + } + case SILInstructionKind::YieldInst: { + auto *yield = cast(memInst); + return yield->getYieldInfoForOperand(*memOper).isIndirectInOut(); + } + case SILInstructionKind::BeginApplyInst: + case SILInstructionKind::TryApplyInst: + case SILInstructionKind::ApplyInst: { + FullApplySite applySite(memInst); + return applySite.getArgumentOperandConvention(*memOper).isInoutConvention(); + } + case SILInstructionKind::StoreInst: + return cast(memInst)->getOwnershipQualifier() == + StoreOwnershipQualifier::Assign; + +#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \ + case SILInstructionKind::Store##Name##Inst: \ + return !cast(memInst)->isInitializationOfDest(); +#include "swift/AST/ReferenceStorage.def" + } +} + +static bool memInstMustConsume(Operand *memOper) { + SILValue address = memOper->get(); + + SILInstruction *memInst = memOper->getUser(); + + switch (memInst->getKind()) { + default: + return false; + + case SILInstructionKind::CopyAddrInst: { + auto *CAI = cast(memInst); + return (CAI->getSrc() == address && CAI->isTakeOfSrc()) || + (CAI->getDest() == address && !CAI->isInitializationOfDest()); + } + case SILInstructionKind::ExplicitCopyAddrInst: { + auto *CAI = cast(memInst); + return (CAI->getSrc() == address && CAI->isTakeOfSrc()) || + (CAI->getDest() == address && !CAI->isInitializationOfDest()); + } + case SILInstructionKind::BeginApplyInst: + case SILInstructionKind::TryApplyInst: + case SILInstructionKind::ApplyInst: { + FullApplySite applySite(memInst); + return applySite.getArgumentOperandConvention(*memOper).isOwnedConvention(); + } + case SILInstructionKind::PartialApplyInst: { + // If we are on the stack, we do not consume. Otherwise, we do. + return !cast(memInst)->isOnStack(); + } + case SILInstructionKind::DestroyAddrInst: + return true; + case SILInstructionKind::LoadInst: + return cast(memInst)->getOwnershipQualifier() == + LoadOwnershipQualifier::Take; + } +} + +//===----------------------------------------------------------------------===// +// MARK: Use State +//===----------------------------------------------------------------------===// + +namespace { + +struct UseState { + SILValue address; + SmallSetVector destroys; + llvm::SmallMapVector livenessUses; + llvm::SmallMapVector borrows; + llvm::SmallMapVector takeInsts; + llvm::SmallMapVector initInsts; + llvm::SmallMapVector reinitInsts; + + SILFunction *getFunction() const { return address->getFunction(); } + void clear() { + address = SILValue(); + destroys.clear(); + livenessUses.clear(); + borrows.clear(); + takeInsts.clear(); + initInsts.clear(); + reinitInsts.clear(); + } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// MARK: Liveness Processor +//===----------------------------------------------------------------------===// + +namespace { + +struct PerBlockLivenessState { + using InstLeafTypePair = std::pair; + SmallVector initDownInsts; + SmallVector takeUpInsts; + SmallVector livenessUpInsts; + llvm::DenseMap> + blockToState; +}; + +struct BlockState { + using Map = llvm::DenseMap; + + /// This is either the liveness up or take up inst that projects + /// up. We set this state according to the following rules: + /// + /// 1. If we are tracking a takeUp, we always take it even if we have a + /// livenessUp. + /// + /// 2. If we have a livenessUp and do not have a take up, we track that + /// instead. + /// + /// The reason why we do this is that we want to catch use after frees when + /// non-consuming uses are later than a consuming use. + SILInstruction *userUp; + + /// If we are init down, then we know that we can not transfer our take + /// through this block and should stop traversing. + bool isInitDown; + + BlockState() : userUp(nullptr) {} + + BlockState(SILInstruction *userUp, bool isInitDown) + : userUp(userUp), isInitDown(isInitDown) {} +}; + +/// Post process the found liveness and emit errors if needed. TODO: Better +/// name. +struct LivenessChecker { + MarkMustCheckInst *markedAddress; + FieldSensitiveAddressPrunedLiveness &liveness; + SmallBitVector livenessVector; + bool hadAnyErrorUsers = false; + SmallPtrSetImpl &inoutTermUsers; + BlockState::Map &blockToState; + + DiagnosticEmitter &diagnosticEmitter; + + LivenessChecker(MarkMustCheckInst *markedAddress, + FieldSensitiveAddressPrunedLiveness &liveness, + SmallPtrSetImpl &inoutTermUsers, + BlockState::Map &blockToState, + DiagnosticEmitter &diagnosticEmitter) + : markedAddress(markedAddress), liveness(liveness), + inoutTermUsers(inoutTermUsers), blockToState(blockToState), + diagnosticEmitter(diagnosticEmitter) {} + + /// Returns true if we emitted any errors. + bool + compute(SmallVectorImpl> + &takeUpInsts, + SmallVectorImpl> + &takeDownInsts); + + void clear() { + livenessVector.clear(); + hadAnyErrorUsers = false; + } + + std::pair testInstVectorLiveness( + SmallVectorImpl> + &takeInsts); +}; + +} // namespace + +std::pair LivenessChecker::testInstVectorLiveness( + SmallVectorImpl> + &takeInsts) { + bool emittedDiagnostic = false; + bool foundSingleBlockTakeDueToInitDown = false; + + for (auto takeInstAndValue : takeInsts) { + LLVM_DEBUG(llvm::dbgs() << " Checking: " << *takeInstAndValue.first); + + // Check if we are in the boundary... + liveness.isWithinBoundary(takeInstAndValue.first, livenessVector); + + // If the bit vector does not contain any set bits, then we know that we did + // not have any boundary violations for any leaf node of our root value. + if (!livenessVector.any()) { + // TODO: Today, we don't tell the user the actual field itself where the + // violation occured and just instead just shows the two instructions. We + // could be more specific though... + LLVM_DEBUG(llvm::dbgs() << " Not within the boundary.\n"); + continue; + } + LLVM_DEBUG(llvm::dbgs() + << " Within the boundary! Emitting an error\n"); + + // Ok, we have an error and via the bit vector know which specific leaf + // elements of our root type were within the per field boundary. We need to + // go find the next reachable use that overlap with its sub-element. We only + // emit a single error per use even if we get multiple sub elements that + // match it. That helps reduce the amount of errors. + // + // DISCUSSION: It is important to note that this follows from the separation + // of concerns behind this pass: we have simplified how we handle liveness + // by losing this information. That being said, since we are erroring it is + // ok that we are taking a little more time since we are not going to + // codegen this code. + // + // That being said, set the flag that we saw at least one error, so we can + // exit early after this loop. + hadAnyErrorUsers = true; + + // B/c of the separation of concerns with our liveness, we now need to walk + // blocks to go find the specific later takes that are reachable from this + // take. It is ok that we are doing a bit more work here since we are going + // to exit and not codegen. + auto *errorUser = takeInstAndValue.first; + + // Before we do anything, grab the state for our errorUser's block from the + // blockState and check if it is an init block. If so, we have no further + // work to do since we already found correctness due to our single basic + // block check. So, we have nothing further to do. + if (blockToState.find(errorUser->getParent())->second.isInitDown) { + // Set the flag that we saw an init down so that our assert later that we + // actually emitted an error doesn't trigger. + foundSingleBlockTakeDueToInitDown = true; + continue; + } + + BasicBlockWorklist worklist(errorUser->getFunction()); + for (auto *succBlock : errorUser->getParent()->getSuccessorBlocks()) + worklist.pushIfNotVisited(succBlock); + + LLVM_DEBUG(llvm::dbgs() << "Performing forward traversal from errorUse " + "looking for the cause of liveness!\n"); + + while (auto *block = worklist.pop()) { + LLVM_DEBUG(llvm::dbgs() + << "Visiting block: bb" << block->getDebugID() << "\n"); + auto iter = blockToState.find(block); + if (iter == blockToState.end()) { + LLVM_DEBUG(llvm::dbgs() << " No State! Skipping!\n"); + for (auto *succBlock : block->getSuccessorBlocks()) + worklist.pushIfNotVisited(succBlock); + continue; + } + + auto *blockUser = iter->second.userUp; + + if (blockUser) { + LLVM_DEBUG(llvm::dbgs() << " Found userUp: " << *blockUser); + auto info = liveness.isInterestingUser(blockUser); + // Make sure that it overlaps with our range... + if (info.second->contains(*info.second)) { + LLVM_DEBUG(llvm::dbgs() << " Emitted diagnostic for it!\n"); + // and if it does... emit our diagnostic and continue to see if we + // find errors along other paths. + // if (invalidUsesWithDiagnostics.insert(errorUser + bool isConsuming = + info.first == + FieldSensitiveAddressPrunedLiveness::LifetimeEndingUse; + diagnosticEmitter.emitAddressDiagnostic( + markedAddress, blockUser, errorUser, isConsuming, + inoutTermUsers.count(blockUser)); + emittedDiagnostic = true; + continue; + } + } + + // Otherwise, add successors and continue! We didn't overlap with this + // use. + LLVM_DEBUG(llvm::dbgs() << " Does not overlap at the type level, no " + "diagnostic! Visiting successors!\n"); + for (auto *succBlock : block->getSuccessorBlocks()) + worklist.pushIfNotVisited(succBlock); + } + } + + return {emittedDiagnostic, foundSingleBlockTakeDueToInitDown}; +} + +bool LivenessChecker::compute( + SmallVectorImpl> + &takeUpInsts, + SmallVectorImpl> + &takeDownInsts) { + // Then revisit our takes, this time checking if we are within the boundary + // and if we are, emit an error. + LLVM_DEBUG(llvm::dbgs() << "Checking takes for errors!\n"); + bool emittedDiagnostic = false; + bool foundSingleBlockTakeDueToInitDown = false; + + auto pair = testInstVectorLiveness(takeUpInsts); + emittedDiagnostic |= pair.first; + foundSingleBlockTakeDueToInitDown |= pair.second; + + pair = testInstVectorLiveness(takeDownInsts); + emittedDiagnostic |= pair.first; + foundSingleBlockTakeDueToInitDown |= pair.second; + + // If we emitted an error user, we should always emit at least one + // diagnostic. If we didn't there is a bug in the implementation. + assert(!hadAnyErrorUsers || emittedDiagnostic || + foundSingleBlockTakeDueToInitDown); + return hadAnyErrorUsers; +} + +//===----------------------------------------------------------------------===// +// MARK: Forward Declaration of Main Checker +//===----------------------------------------------------------------------===// + +namespace { + +struct MoveOnlyChecker { + bool changed = false; + + SILFunction *fn; + + /// A set of mark_must_check that we are actually going to process. + SmallSetVector moveIntroducersToProcess; + + /// A per mark must check, vector of uses that copy propagation says need a + /// copy and thus are not final consuming uses. + SmallVector consumingUsesNeedingCopy; + + /// A per mark must check, vector of consuming uses that copy propagation says + /// are actual last uses. + SmallVector finalConsumingUses; + + /// A set of mark must checks that we emitted diagnostics for. Used to + /// reprocess mark_must_checks and clean up after the cases where we did not + /// emit a diagnostic. We don't care about the cases where we emitted + /// diagnostics since we are going to fail. + SmallPtrSet valuesWithDiagnostics; + + /// The instruction deleter used by \p canonicalizer. + InstructionDeleter deleter; + + /// The OSSA canonicalizer used to perform move checking for objects. + CanonicalizeOSSALifetime canonicalizer; + + /// Per mark must check address use state. + UseState addressUseState; + + /// Post order analysis used to lazily initialize post order function info + /// only if we need it. + PostOrderAnalysis *poa; + + /// Lazy function info, do not use directly. Use getPostOrderInfo() instead + /// which will initialize this. + PostOrderFunctionInfo *lazyPOI; + + /// Diagnostic emission routines wrapped around a consuming use cache. This + /// ensures that we only emit a single error per use per marked value. + DiagnosticEmitter diagnosticEmitter; + + MoveOnlyChecker(SILFunction *fn, DeadEndBlocks *deBlocks, + NonLocalAccessBlockAnalysis *accessBlockAnalysis, + DominanceInfo *domTree, PostOrderAnalysis *poa) + : fn(fn), + deleter(InstModCallbacks().onDelete([&](SILInstruction *instToDelete) { + if (auto *mvi = dyn_cast(instToDelete)) + moveIntroducersToProcess.remove(mvi); + instToDelete->eraseFromParent(); + })), + canonicalizer( + false /*pruneDebugMode*/, false /*poisonRefsMode*/, + accessBlockAnalysis, domTree, deleter, + [&](Operand *use) { consumingUsesNeedingCopy.push_back(use); }, + [&](Operand *use) { finalConsumingUses.push_back(use); }) {} + + /// Search through the current function for candidate mark_must_check + /// [noimplicitcopy]. If we find one that does not fit a pattern that we + /// understand, emit an error diagnostic telling the programmer that the move + /// checker did not know how to recognize this code pattern. + void searchForCandidateMarkMustChecks(); + + /// Emits an error diagnostic for \p markedValue. + void emitObjectDiagnostic(MarkMustCheckInst *markedValue, + bool originalValueGuaranteed); + + void performObjectCheck(MarkMustCheckInst *markedValue); + + bool performSingleCheck(MarkMustCheckInst *markedValue); + + bool check(); + + PostOrderFunctionInfo *getPostOrderInfo() { + if (!lazyPOI) + lazyPOI = poa->get(fn); + return lazyPOI; + } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// MARK: GatherLexicalLifetimeUseVisitor +//===----------------------------------------------------------------------===// + +namespace { + +/// Visit all of the uses of value in preparation for running our algorithm. +struct GatherUsesVisitor : public AccessUseVisitor { + MoveOnlyChecker &moveChecker; + UseState &useState; + MarkMustCheckInst *markedValue; + bool emittedEarlyDiagnostic = false; + DiagnosticEmitter &diagnosticEmitter; + + GatherUsesVisitor(MoveOnlyChecker &moveChecker, UseState &useState, + MarkMustCheckInst *markedValue, + DiagnosticEmitter &diagnosticEmitter) + : AccessUseVisitor(AccessUseType::Overlapping, + NestedAccessType::IgnoreAccessBegin), + moveChecker(moveChecker), useState(useState), markedValue(markedValue), + diagnosticEmitter(diagnosticEmitter) {} + + bool visitUse(Operand *op, AccessUseType useTy) override; + void reset(SILValue address) { useState.address = address; } + void clear() { useState.clear(); } + + /// For now always markedValue. If we start using this for move address + /// checking, we need to check against the operand of the markedValue. This is + /// because for move checking, our marker is placed along the variables + /// initialization so we are always going to have all later uses from the + /// marked value. For the move operator though we will want this to be the + /// base address that we are checking which should be the operand of the mark + /// must check value. + SILValue getRootAddress() const { return markedValue; } +}; + +} // end anonymous namespace + +// Filter out recognized uses that do not write to memory. +// +// TODO: Ensure that all of the conditional-write logic below is encapsulated in +// mayWriteToMemory and just call that instead. Possibly add additional +// verification that visitAccessPathUses recognizes all instructions that may +// propagate pointers (even though they don't write). +bool GatherUsesVisitor::visitUse(Operand *op, AccessUseType useTy) { + // If this operand is for a dependent type, then it does not actually access + // the operand's address value. It only uses the metatype defined by the + // operation (e.g. open_existential). + if (op->isTypeDependent()) { + return true; + } + + // We don't care about debug instructions. + if (op->getUser()->isDebugInstruction()) + return true; + + // For convenience, grab the user of op. + auto *user = op->getUser(); + + // First check if we have init/reinit. These are quick/simple. + if (::memInstMustInitialize(op)) { + LLVM_DEBUG(llvm::dbgs() << "Found init: " << *user); + + // TODO: What about copy_addr of itself. We really should just pre-process + // those maybe. + assert(!useState.initInsts.count(user)); + useState.initInsts.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + return true; + } + + if (::memInstMustReinitialize(op)) { + LLVM_DEBUG(llvm::dbgs() << "Found reinit: " << *user); + assert(!useState.reinitInsts.count(user)); + useState.reinitInsts.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + return true; + } + + // Then handle destroy_addr specially. We want to as part of our dataflow to + // ignore destroy_addr, so we need to track it separately from other uses. + if (auto *dvi = dyn_cast(user)) { + // If we see a destroy_addr not on our base address, bail! Just error and + // say that we do not understand the code. + if (dvi->getOperand() != useState.address) { + LLVM_DEBUG(llvm::dbgs() + << "!!! Error! Found destroy_addr no on base address: " + << *useState.address << "destroy: " << *dvi); + return false; + } + LLVM_DEBUG(llvm::dbgs() << "Found destroy_addr: " << *dvi); + useState.destroys.insert(dvi); + return true; + } + + // Ignore dealloc_stack. + if (isa(user)) + return true; + + // Ignore end_access. + if (isa(user)) + return true; + + // At this point, we have handled all of the non-loadTakeOrCopy/consuming + // uses. + if (auto *copyAddr = dyn_cast(user)) { + if (markedValue->getCheckKind() == MarkMustCheckInst::CheckKind::NoCopy) { + LLVM_DEBUG(llvm::dbgs() + << "Found mark must check [nocopy] error: " << *user); + diagnosticEmitter.emitAddressDiagnosticNoCopy(markedValue, copyAddr); + moveChecker.valuesWithDiagnostics.insert(markedValue); + emittedEarlyDiagnostic = true; + return true; + } + + LLVM_DEBUG(llvm::dbgs() << "Found take: " << *user); + useState.takeInsts.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + return true; + } + + // Then find load [copy], load [take] that are really takes since we need + // copies for the loaded value. If we find that we need copies at that level + // (due to e.x.: multiple consuming uses), we emit an error and bail. This + // ensures that later on, we can assume that all of our load [take], load + // [copy] actually follow move semantics at the object level and thus are + // viewed as a consume requiring a copy. This is important since SILGen often + // emits code of this form and we need to recognize it as a copy of the + // underlying var. + if (auto *li = dyn_cast(user)) { + if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy || + li->getOwnershipQualifier() == LoadOwnershipQualifier::Take) { + LLVM_DEBUG(llvm::dbgs() << "Found load: " << *li); + SWIFT_DEFER { + moveChecker.consumingUsesNeedingCopy.clear(); + moveChecker.finalConsumingUses.clear(); + }; + + // Canonicalize the lifetime of the load [take], load [copy]. + moveChecker.changed |= + moveChecker.canonicalizer.canonicalizeValueLifetime(li); + + // If we are asked to perform guaranteed checking, emit an error if we + // have /any/ consuming uses. This is a case that can always be converted + // to a load_borrow if we pass the check. + if (markedValue->getCheckKind() == MarkMustCheckInst::CheckKind::NoCopy) { + if (!moveChecker.consumingUsesNeedingCopy.empty() || + !moveChecker.finalConsumingUses.empty()) { + LLVM_DEBUG(llvm::dbgs() + << "Found mark must check [nocopy] error: " << *user); + moveChecker.emitObjectDiagnostic(markedValue, + true /*original value guaranteed*/); + emittedEarlyDiagnostic = true; + moveChecker.valuesWithDiagnostics.insert(markedValue); + return true; + } + + // If set, this will tell the checker that we can change this load into + // a load_borrow. + LLVM_DEBUG(llvm::dbgs() << "Found potential borrow: " << *user); + useState.borrows.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + return true; + } + + // First check if we had any consuming uses that actually needed a + // copy. This will always be an error and we allow the user to recompile + // and eliminate the error. This just allows us to rely on invariants + // later. + if (!moveChecker.consumingUsesNeedingCopy.empty()) { + LLVM_DEBUG(llvm::dbgs() + << "Found that load at object level requires copies!\n"); + // If we failed to understand how to perform the check or did not find + // any targets... continue. In the former case we want to fail with a + // checker did not understand diagnostic later and in the former, we + // succeeded. + // Otherwise, emit the diagnostic. + moveChecker.emitObjectDiagnostic(markedValue, + false /*original value guaranteed*/); + emittedEarlyDiagnostic = true; + moveChecker.valuesWithDiagnostics.insert(markedValue); + LLVM_DEBUG(llvm::dbgs() << "Emitted early object level diagnostic.\n"); + return true; + } + + // Then if we had any final consuming uses, mark that this liveness use is + // a take and if not, mark this as a borrow. + if (moveChecker.finalConsumingUses.empty()) { + LLVM_DEBUG(llvm::dbgs() << "Found borrow inst: " << *user); + useState.borrows.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + } else { + LLVM_DEBUG(llvm::dbgs() << "Found take inst: " << *user); + useState.takeInsts.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + } + return true; + } + } + + // Now that we have handled or loadTakeOrCopy, we need to now track our Takes. + if (::memInstMustConsume(op)) { + LLVM_DEBUG(llvm::dbgs() << "Pure consuming use: " << *user); + useState.takeInsts.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + return true; + } + + if (auto fas = FullApplySite::isa(op->getUser())) { + switch (fas.getArgumentConvention(*op)) { + case SILArgumentConvention::Indirect_In_Guaranteed: + useState.livenessUses.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + return true; + + case SILArgumentConvention::Indirect_Inout: + case SILArgumentConvention::Indirect_InoutAliasable: + case SILArgumentConvention::Indirect_In: + case SILArgumentConvention::Indirect_In_Constant: + case SILArgumentConvention::Indirect_Out: + case SILArgumentConvention::Direct_Unowned: + case SILArgumentConvention::Direct_Owned: + case SILArgumentConvention::Direct_Guaranteed: + break; + } + } + + // If we don't fit into any of those categories, just track as a liveness + // use. We assume all such uses must only be reads to the memory. So we assert + // to be careful. + LLVM_DEBUG(llvm::dbgs() << "Found liveness use: " << *user); +#ifndef NDEBUG + if (user->mayWriteToMemory()) { + llvm::errs() << "Found a write classified as a liveness use?!\n"; + llvm::errs() << "Use: " << *user; + llvm_unreachable("standard failure"); + } +#endif + useState.livenessUses.insert( + {user, TypeTreeLeafTypeRange(op->get(), getRootAddress())}); + + return true; +} + +//===----------------------------------------------------------------------===// +// MARK: Main Pass Implementation +//===----------------------------------------------------------------------===// + +void MoveOnlyChecker::searchForCandidateMarkMustChecks() { + for (auto &block : *fn) { + for (auto ii = block.begin(), ie = block.end(); ii != ie;) { + auto *mmci = dyn_cast(&*ii); + ++ii; + + if (!mmci || !mmci->hasMoveCheckerKind() || !mmci->getType().isAddress()) + continue; + + // Skip any alloc_box due to heap to stack failing on a box capture. This + // will just cause an error. + if (auto *pbi = dyn_cast(mmci->getOperand())) { + if (auto *bbi = dyn_cast(pbi->getOperand())) { + if (isa(bbi->getOperand())) { + if (mmci->getType().isMoveOnlyWrapped()) { + diagnose( + fn->getASTContext(), mmci->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_not_understand_no_implicit_copy); + } else { + diagnose(fn->getASTContext(), mmci->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_not_understand_moveonly); + } + valuesWithDiagnostics.insert(mmci); + continue; + } + } + } + + moveIntroducersToProcess.insert(mmci); + } + } +} + +void MoveOnlyChecker::emitObjectDiagnostic(MarkMustCheckInst *markedValue, + bool originalValueGuaranteed) { + auto &astContext = fn->getASTContext(); + StringRef varName = getVariableNameForValue(markedValue); + LLVM_DEBUG(llvm::dbgs() << "Emitting error for: " << *markedValue); + + if (originalValueGuaranteed) { + diagnose(astContext, + markedValue->getDefiningInstruction()->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_guaranteed_value_consumed, varName); + } else { + diagnose(astContext, + markedValue->getDefiningInstruction()->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_owned_value_consumed_more_than_once, + varName); + } + + while (consumingUsesNeedingCopy.size()) { + auto *consumingUse = consumingUsesNeedingCopy.pop_back_val(); + + LLVM_DEBUG(llvm::dbgs() + << "Consuming use needing copy: " << *consumingUse->getUser()); + // See if the consuming use is an owned moveonly_to_copyable whose only + // user is a return. In that case, use the return loc instead. We do this + // b/c it is illegal to put a return value location on a non-return value + // instruction... so we have to hack around this slightly. + auto *user = consumingUse->getUser(); + auto loc = user->getLoc(); + if (auto *mtc = dyn_cast(user)) { + if (auto *ri = mtc->getSingleUserOfType()) { + loc = ri->getLoc(); + } + } + + diagnose(astContext, loc.getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); + } + + while (finalConsumingUses.size()) { + auto *consumingUse = finalConsumingUses.pop_back_val(); + LLVM_DEBUG(llvm::dbgs() + << "Final consuming use: " << *consumingUse->getUser()); + // See if the consuming use is an owned moveonly_to_copyable whose only + // user is a return. In that case, use the return loc instead. We do this + // b/c it is illegal to put a return value location on a non-return value + // instruction... so we have to hack around this slightly. + auto *user = consumingUse->getUser(); + auto loc = user->getLoc(); + if (auto *mtc = dyn_cast(user)) { + if (auto *ri = mtc->getSingleUserOfType()) { + loc = ri->getLoc(); + } + } + + diagnose(astContext, loc.getSourceLoc(), + diag::sil_moveonlychecker_consuming_use_here); + } +} + +bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) { + SWIFT_DEFER { diagnosticEmitter.clear(); }; + + auto accessPathWithBase = AccessPathWithBase::compute(markedAddress); + auto accessPath = accessPathWithBase.accessPath; + if (!accessPath.isValid()) { + LLVM_DEBUG(llvm::dbgs() << "Invalid access path: " << *markedAddress); + return false; + } + + // Then gather all uses of our address by walking from def->uses. We use this + // to categorize the uses of this address into their ownership behavior (e.x.: + // init, reinit, take, destroy, etc.). + GatherUsesVisitor visitor(*this, addressUseState, markedAddress, + diagnosticEmitter); + SWIFT_DEFER { visitor.clear(); }; + visitor.reset(markedAddress); + if (!visitAccessPathUses(visitor, accessPath, fn)) { + LLVM_DEBUG(llvm::dbgs() << "Failed access path visit: " << *markedAddress); + return false; + } + + // If we found a load [copy] or copy_addr that requires multiple copies, then + // we emitted an early error. Bail now and allow the user to fix those errors + // and recompile to get further errors. + // + // DISCUSSION: The reason why we do this is in the dataflow below we want to + // be able to assume that the load [copy] or copy_addr/copy_addr [init] are + // actual last uses, but the frontend that emitted the code for simplicity + // emitted a copy from the base address + a destroy_addr of the use. By + // bailing here, we can make that assumption since we would have errored + // earlier otherwise. + if (visitor.emittedEarlyDiagnostic) + return true; + + SmallVector discoveredBlocks; + FieldSensitiveAddressPrunedLiveness liveness(fn, markedAddress, + &discoveredBlocks); + + // Now walk all of the blocks in the function... performing the single basic + // block version of the algorithm and initializing our pruned liveness for our + // interprocedural processing. + using InstLeafTypePair = std::pair; + using InstOptionalLeafTypePair = + std::pair>; + SmallVector initDownInsts; + SmallVector takeDownInsts; + SmallVector takeUpInsts; + SmallVector livenessUpInsts; + BlockState::Map blockToState; + + bool isInOut = false; + if (auto *fArg = dyn_cast(markedAddress->getOperand())) + isInOut |= fArg->getArgumentConvention().isInoutConvention(); + + LLVM_DEBUG(llvm::dbgs() << "Performing single basic block checks!\n"); + for (auto &block : *fn) { + LLVM_DEBUG(llvm::dbgs() + << "Visiting block: bb" << block.getDebugID() << "\n"); + // Then walk backwards from the bottom of the block to the top, initializing + // its state, handling any completely in block diagnostics. + InstOptionalLeafTypePair takeUp = {nullptr, {}}; + InstOptionalLeafTypePair firstTake = {nullptr, {}}; + InstOptionalLeafTypePair firstInit = {nullptr, {}}; + InstOptionalLeafTypePair livenessUp = {nullptr, {}}; + bool foundFirstNonLivenessUse = false; + auto *term = block.getTerminator(); + bool isExitBlock = term->isFunctionExiting(); + for (auto &inst : llvm::reverse(block)) { + if (isExitBlock && isInOut && &inst == term) { + LLVM_DEBUG(llvm::dbgs() + << " Found inout term liveness user: " << inst); + livenessUp = {&inst, TypeTreeLeafTypeRange(markedAddress)}; + continue; + } + + { + auto iter = addressUseState.takeInsts.find(&inst); + if (iter != addressUseState.takeInsts.end()) { + SWIFT_DEFER { foundFirstNonLivenessUse = true; }; + + // If we are not yet tracking a "take up" or a "liveness up", then we + // can update our state. In those other two cases we emit an error + // diagnostic below. + if (!takeUp.first && !livenessUp.first) { + LLVM_DEBUG(llvm::dbgs() + << " Tracking new take up: " << *iter->first); + if (!foundFirstNonLivenessUse) { + firstTake = {iter->first, iter->second}; + } + takeUp = {iter->first, iter->second}; + continue; + } + + bool emittedSomeDiagnostic = false; + if (takeUp.first) { + LLVM_DEBUG(llvm::dbgs() + << " Found two takes, emitting error!\n"); + LLVM_DEBUG(llvm::dbgs() << " First take: " << *takeUp.first); + LLVM_DEBUG(llvm::dbgs() << " Second take: " << inst); + diagnosticEmitter.emitAddressDiagnostic( + markedAddress, takeUp.first, &inst, true /*is consuming*/); + emittedSomeDiagnostic = true; + } + + if (livenessUp.first) { + if (livenessUp.first == term && isInOut) { + LLVM_DEBUG(llvm::dbgs() + << " Found liveness inout error: " << inst); + // Even though we emit a diagnostic for inout here, we actually + // want to no longer track the inout liveness use and instead want + // to track the consuming use so that earlier errors are on the + // take and not on the inout. This is to ensure that we only emit + // a single inout not reinitialized before end of function error + // if we have multiple consumes along that path. + diagnosticEmitter.emitInOutEndOfFunctionDiagnostic(markedAddress, + &inst); + livenessUp = {nullptr, {}}; + takeUp = {iter->first, iter->second}; + } else { + LLVM_DEBUG(llvm::dbgs() << " Found liveness error: " << inst); + diagnosticEmitter.emitAddressDiagnostic( + markedAddress, livenessUp.first, &inst, + false /*is not consuming*/); + } + emittedSomeDiagnostic = true; + } + + (void)emittedSomeDiagnostic; + assert(emittedSomeDiagnostic); + valuesWithDiagnostics.insert(markedAddress); + continue; + } + } + + { + auto iter = addressUseState.livenessUses.find(&inst); + if (iter != addressUseState.livenessUses.end()) { + if (!livenessUp.first) { + LLVM_DEBUG(llvm::dbgs() + << " Found liveness use! Propagating liveness. Inst: " + << inst); + livenessUp = {iter->first, iter->second}; + } else { + LLVM_DEBUG( + llvm::dbgs() + << " Found liveness use! Already tracking liveness. Inst: " + << inst); + } + continue; + } + } + + // Just treat borrows at this point as liveness requiring. + { + auto iter = addressUseState.borrows.find(&inst); + if (iter != addressUseState.borrows.end()) { + if (!livenessUp.first) { + LLVM_DEBUG(llvm::dbgs() + << " Found borrowed use! Propagating liveness. Inst: " + << inst); + livenessUp = {iter->first, iter->second}; + } else { + LLVM_DEBUG( + llvm::dbgs() + << " Found borrowed use! Already tracking liveness. Inst: " + << inst); + } + continue; + } + } + + // If we have an init, then unset previous take up and liveness up. + { + auto iter = addressUseState.initInsts.find(&inst); + if (iter != addressUseState.initInsts.end()) { + takeUp = {nullptr, {}}; + livenessUp = {nullptr, {}}; + if (!foundFirstNonLivenessUse) { + LLVM_DEBUG(llvm::dbgs() << " Updated with new init: " << inst); + firstInit = {iter->first, iter->second}; + } else { + LLVM_DEBUG( + llvm::dbgs() + << " Found init! Already have first non liveness use. Inst: " + << inst); + } + foundFirstNonLivenessUse = true; + continue; + } + } + + // We treat the reinit a reinit as only a kill and not an additional + // take. This is because, we are treating their destroy as a last use like + // destroy_addr. The hope is that as part of doing fixups, we can just + // change this to a store [init] if there isn't an earlier reachable take + // use, like we do with destroy_addr. + { + auto iter = addressUseState.reinitInsts.find(&inst); + if (iter != addressUseState.reinitInsts.end()) { + takeUp = {nullptr, {}}; + livenessUp = {nullptr, {}}; + if (!foundFirstNonLivenessUse) { + LLVM_DEBUG(llvm::dbgs() << " Updated with new reinit: " << inst); + firstInit = {iter->first, iter->second}; + } else { + LLVM_DEBUG(llvm::dbgs() << " Found new reinit, but already " + "tracking a liveness init. Inst: " + << inst); + } + foundFirstNonLivenessUse = true; + continue; + } + } + } + + LLVM_DEBUG(llvm::dbgs() << "End of block. Dumping Results!\n"); + if (firstInit.first) { + LLVM_DEBUG(llvm::dbgs() << " First Init Down: " << *firstInit.first); + initDownInsts.emplace_back(firstInit.first, *firstInit.second); + } else { + LLVM_DEBUG(llvm::dbgs() << " No Init Down!\n"); + } + + // At this point we want to begin mapping blocks to "first" users. + if (takeUp.first) { + LLVM_DEBUG(llvm::dbgs() << "Take Up: " << *takeUp.first); + blockToState.try_emplace(&block, + BlockState(takeUp.first, firstInit.first)); + takeUpInsts.emplace_back(takeUp.first, *takeUp.second); + } else { + LLVM_DEBUG(llvm::dbgs() << " No Take Up!\n"); + } + + if (livenessUp.first) { + // This try_emplace fail if we already above initialized blockToState + // above in the previous if block. + blockToState.try_emplace(&block, + BlockState(livenessUp.first, firstInit.first)); + LLVM_DEBUG(llvm::dbgs() << "Liveness Up: " << *livenessUp.first); + livenessUpInsts.emplace_back(livenessUp.first, *livenessUp.second); + } else { + LLVM_DEBUG(llvm::dbgs() << " No Liveness Up!\n"); + } + + if (firstTake.first) { + LLVM_DEBUG(llvm::dbgs() << " First Take Down: " << *firstTake.first); + takeDownInsts.emplace_back(firstTake.first, *firstTake.second); + // We only emplace if we didn't already have a takeUp above. In such a + // case, the try_emplace fails. + blockToState.try_emplace(&block, BlockState(nullptr, false)); + } else { + LLVM_DEBUG(llvm::dbgs() << " No Take Down!\n"); + } + } + + // If we emitted a diagnostic while performing the single block check, just + // bail early and let the user fix these issues in this functionand re-run the + // compiler. + // + // DISCUSSION: This ensures that later when performing the global dataflow, we + // can rely on the invariant that any potential single block cases are correct + // already. + if (valuesWithDiagnostics.size()) + return true; + + //--- + // Multi-Block Liveness Dataflow + // + + // At this point, we have handled all of the single block cases and have + // simplified the remaining cases to global cases that we compute using + // liveness. We begin by using all of our init down blocks as def blocks. + for (auto initInstAndValue : initDownInsts) + liveness.initializeDefBlock(initInstAndValue.first->getParent(), + initInstAndValue.second); + + // Then add all of the takes that we saw propagated up to the top of our + // block. Since we have done this for all of our defs + for (auto takeInstAndValue : takeUpInsts) + liveness.updateForUse(takeInstAndValue.first, takeInstAndValue.second, + true /*lifetime ending*/); + // Do the same for our borrow and liveness insts. + for (auto livenessInstAndValue : livenessUpInsts) + liveness.updateForUse(livenessInstAndValue.first, + livenessInstAndValue.second, + false /*lifetime ending*/); + + // Finally, if we have an inout argument, add a liveness use of the entire + // value on terminators in blocks that are exits from the function. This + // ensures that along all paths, if our inout is not reinitialized before we + // exit the function, we will get an error. We also stash these users into + // inoutTermUser so we can quickly recognize them later and emit a better + // error msg. + SmallPtrSet inoutTermUser; + if (auto *fArg = dyn_cast(markedAddress->getOperand())) { + if (fArg->getArgumentConvention() == + SILArgumentConvention::Indirect_Inout) { + SmallVector exitBlocks; + markedAddress->getFunction()->findExitingBlocks(exitBlocks); + for (auto *block : exitBlocks) { + inoutTermUser.insert(block->getTerminator()); + liveness.updateForUse(block->getTerminator(), + TypeTreeLeafTypeRange(markedAddress), + false /*lifetime ending*/); + } + } + } + + // If we have multiple blocks in the function, now run the global pruned + // liveness dataflow. + if (std::next(fn->begin()) != fn->end()) { + // Then compute the takes that are within the cumulative boundary of + // liveness that we have computed. If we find any, they are the errors ones. + LivenessChecker emitter(markedAddress, liveness, inoutTermUser, + blockToState, diagnosticEmitter); + + // If we had any errors, we do not want to modify the SIL... just bail. + if (emitter.compute(takeUpInsts, takeDownInsts)) { + // TODO: Remove next line. + valuesWithDiagnostics.insert(markedAddress); + return true; + } + } + + // Now before we do anything more, just exit and say we errored. This will not + // actually make us fail and will cause code later in the checker to rewrite + // all non-explicit copy instructions to their explicit variant. I did this + // for testing purposes only. + valuesWithDiagnostics.insert(markedAddress); + return true; + + // Ok, we not have emitted our main errors. Now we begin the + // transformation. We begin by processing borrows. We can also emit errors + // here if we find that we can not expand the borrow scope of the load [copy] + // to all of its uses. + SmallVector destroys; + SmallVector endAccesses; + for (auto pair : addressUseState.borrows) { + if (auto *li = dyn_cast(pair.first)) { + if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) { + // If we had a load [copy], borrow then we know that all of its destroys + // must have been destroy_value. So we can just gather up those + // destroy_value and use then to create a new load_borrow scope. + SILBuilderWithScope builder(li); + SILValue base = stripAccessMarkers(li->getOperand()); + bool foundAccess = base != li->getOperand(); + + // Insert a new access scope. + SILValue newBase = base; + if (foundAccess) { + newBase = builder.createBeginAccess( + li->getLoc(), base, SILAccessKind::Read, + SILAccessEnforcement::Static, false /*no nested conflict*/, + false /*is from builtin*/); + } + auto *lbi = builder.createLoadBorrow(li->getLoc(), newBase); + + for (auto *consumeUse : li->getConsumingUses()) { + auto *dvi = cast(consumeUse->getUser()); + SILBuilderWithScope destroyBuilder(dvi); + destroyBuilder.createEndBorrow(dvi->getLoc(), lbi); + if (foundAccess) + destroyBuilder.createEndAccess(dvi->getLoc(), newBase, + false /*aborted*/); + destroys.push_back(dvi); + changed = true; + } + + // TODO: We need to check if this is the only use. + if (auto *access = dyn_cast(li->getOperand())) { + bool foundUnknownUse = false; + for (auto *accUse : access->getUses()) { + if (accUse->getUser() == li) + continue; + if (auto *ea = dyn_cast(accUse->getUser())) { + endAccesses.push_back(ea); + continue; + } + foundUnknownUse = true; + } + if (!foundUnknownUse) { + for (auto *end : endAccesses) + end->eraseFromParent(); + access->replaceAllUsesWithUndef(); + access->eraseFromParent(); + } + } + + for (auto *d : destroys) + d->eraseFromParent(); + li->replaceAllUsesWith(lbi); + li->eraseFromParent(); + continue; + } + } + + llvm_unreachable("Unhandled case?!"); + } + + // Now that we have rewritten all read only copies into borrows, we need to + // handle merging of copyable takes/destroys. Begin by pairing destroy_addr + // and copyableTake. + for (auto *destroy : addressUseState.destroys) { + // First if we aren't the first instruction in the block, see if we have a + // copyableToTake that pairs with us. If we are the first instruction in the + // block, then we just go straight to the global dataflow stage below. + if (destroy->getIterator() != destroy->getParent()->begin()) { + bool foundSingleBlockCase = false; + for (auto ii = std::prev(destroy->getReverseIterator()), + ie = destroy->getParent()->rend(); + ii != ie; ++ii) { + if (addressUseState.initInsts.count(&*ii) || + addressUseState.reinitInsts.count(&*ii)) + break; + + if (addressUseState.takeInsts.count(&*ii)) { + if (auto *li = dyn_cast(&*ii)) { + if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) { + // If we have a copy in the single block case, erase the destroy + // and visit the next one. + destroy->eraseFromParent(); + changed = true; + foundSingleBlockCase = true; + break; + } + llvm_unreachable("Error?! This is a double destroy?!"); + } + } + + if (addressUseState.livenessUses.count(&*ii)) { + // If we have a liveness use, we break since this destroy_addr is + // needed along this path. + break; + } + } + + if (foundSingleBlockCase) + continue; + } + } + + // Now that we have rewritten all borrows, convert any takes that are today + // copies into their take form. If we couldn't do this, we would have had an + // error. + for (auto take : addressUseState.takeInsts) { + if (auto *li = dyn_cast(take.first)) { + switch (li->getOwnershipQualifier()) { + case LoadOwnershipQualifier::Unqualified: + case LoadOwnershipQualifier::Trivial: + llvm_unreachable("Should never hit this"); + case LoadOwnershipQualifier::Take: + // This is already correct. + continue; + case LoadOwnershipQualifier::Copy: + // Convert this to its take form. + if (auto *access = dyn_cast(li->getOperand())) + access->setAccessKind(SILAccessKind::Modify); + li->setOwnershipQualifier(LoadOwnershipQualifier::Take); + changed = true; + continue; + } + } + + llvm_unreachable("Unhandled case?!"); + } + + return true; +} + +bool MoveOnlyChecker::check() { + // First search for candidates to process and emit diagnostics on any + // mark_must_check [noimplicitcopy] we didn't recognize. + searchForCandidateMarkMustChecks(); + + // If we didn't find any introducers to check, just return changed. + // + // NOTE: changed /can/ be true here if we had any mark_must_check + // [noimplicitcopy] that we didn't understand and emitting a diagnostic upon + // and then deleting. + if (moveIntroducersToProcess.empty()) + return changed; + + for (auto *markedValue : moveIntroducersToProcess) { + LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *markedValue); + + // Perform our address check. + if (!performSingleCheck(markedValue)) { + // If we fail the address check in some way, set the diagnose! + if (markedValue->getType().isMoveOnlyWrapped()) { + diagnose(fn->getASTContext(), markedValue->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_not_understand_no_implicit_copy); + } else { + diagnose(fn->getASTContext(), markedValue->getLoc().getSourceLoc(), + diag::sil_moveonlychecker_not_understand_moveonly); + } + valuesWithDiagnostics.insert(markedValue); + } + } + bool emittedDiagnostic = valuesWithDiagnostics.size(); + + // Ok, now that we have performed our checks, we need to eliminate all mark + // must check inst since it is invalid for these to be in canonical SIL and + // our work is done here. + while (!moveIntroducersToProcess.empty()) { + auto *markedInst = moveIntroducersToProcess.pop_back_val(); + markedInst->replaceAllUsesWith(markedInst->getOperand()); + markedInst->eraseFromParent(); + changed = true; + } + + // Once we have finished processing, if we emitted any diagnostics, then we + // may have copy_addr [init], load [copy] of @moveOnly typed values. This is + // not valid in Canonical SIL, so we need to ensure that those copy_value + // become explicit_copy_value. This is ok to do since we are already going to + // fail the compilation and just are trying to maintain SIL invariants. + // + // It is also ok that we use a little more compile time and go over the + // function again, since we are going to fail the compilation and not codegen. + if (emittedDiagnostic) { + for (auto &block : *fn) { + for (auto ii = block.begin(), ie = block.end(); ii != ie;) { + auto *inst = &*ii; + ++ii; + + // Convert load [copy] -> load_borrow + explicit_copy_value. + if (auto *li = dyn_cast(inst)) { + if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) { + SILBuilderWithScope builder(li); + auto *lbi = + builder.createLoadBorrow(li->getLoc(), li->getOperand()); + auto *cvi = builder.createExplicitCopyValue(li->getLoc(), lbi); + builder.createEndBorrow(li->getLoc(), lbi); + li->replaceAllUsesWith(cvi); + li->eraseFromParent(); + changed = true; + } + } + + // Convert copy_addr !take of src to its explicit value form so we don't + // error. + if (auto *copyAddr = dyn_cast(inst)) { + if (!copyAddr->isTakeOfSrc()) { + SILBuilderWithScope builder(copyAddr); + builder.createExplicitCopyAddr( + copyAddr->getLoc(), copyAddr->getSrc(), copyAddr->getDest(), + IsTake_t(copyAddr->isTakeOfSrc()), + IsInitialization_t(copyAddr->isInitializationOfDest())); + copyAddr->eraseFromParent(); + changed = true; + } + } + } + } + } + + return changed; +} + +//===----------------------------------------------------------------------===// +// Top Level Entrypoint +//===----------------------------------------------------------------------===// + +namespace { + +class MoveOnlyCheckerPass : public SILFunctionTransform { + void run() override { + auto *fn = getFunction(); + + // Don't rerun diagnostics on deserialized functions. + if (getFunction()->wasDeserializedCanonical()) + return; + + assert(fn->getModule().getStage() == SILStage::Raw && + "Should only run on Raw SIL"); + LLVM_DEBUG(llvm::dbgs() << "===> MoveOnly Addr Checker. Visiting: " + << fn->getName() << '\n'); + auto *accessBlockAnalysis = getAnalysis(); + auto *dominanceAnalysis = getAnalysis(); + auto *postOrderAnalysis = getAnalysis(); + DominanceInfo *domTree = dominanceAnalysis->get(fn); + auto *deAnalysis = getAnalysis()->get(fn); + + if (MoveOnlyChecker(getFunction(), deAnalysis, accessBlockAnalysis, domTree, + postOrderAnalysis) + .check()) { + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + } + } +}; + +} // anonymous namespace + +SILTransform *swift::createMoveOnlyAddressChecker() { + return new MoveOnlyCheckerPass(); +} diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index c4e42552c5a5c..0c64deebf44f6 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -129,17 +129,23 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) { P.addNoReturnFolding(); addDefiniteInitialization(P); - //===--- - // Ownership Optimizations + // Begin Ownership Optimizations // - P.addMoveKillsCopyableAddressesChecker(); - P.addMoveOnlyObjectChecker(); // Check noImplicitCopy and move only - // types. + P.addMoveOnlyAddressChecker(); // Check noImplicitCopy and move only types for + // addresses. + P.addMoveKillsCopyableAddressesChecker(); // Check _move for addresses. + P.addMoveOnlyObjectChecker(); // Check noImplicitCopy and move only + // types for objects P.addMoveKillsCopyableValuesChecker(); // No uses after _move of copyable - // value. - P.addTrivialMoveOnlyTypeEliminator(); + // value. + P.addTrivialMoveOnlyTypeEliminator(); // Lower move only wrapped trivial + // types. + + // + // End Ownership Optimizations + //===--- P.addAddressLowering(); diff --git a/test/SILGen/moveonly_var.swift b/test/SILGen/moveonly_var.swift new file mode 100644 index 0000000000000..8146c7a2aff0a --- /dev/null +++ b/test/SILGen/moveonly_var.swift @@ -0,0 +1,57 @@ +// RUN: %target-swift-emit-silgen -enable-experimental-move-only %s | %FileCheck %s +// RUN: %target-swift-emit-sil -enable-experimental-move-only %s | %FileCheck %s + +////////////////// +// Declarations // +////////////////// + +@_moveOnly +public class Klass { + var intField: Int + var klsField: Klass? + + init() { + klsField = Klass() + intField = 5 + } +} + +public func nonConsumingUseKlass(_ k: Klass) {} + +/////////// +// Tests // +/////////// + +// ----------- +// Class Tests +// + +// CHECK-LABEL: useVarKlass +public func useVarKlassNoErrorSimple() { + var k = Klass() + k = Klass() + + nonConsumingUseKlass(k) + let k2 = k + let _ = k2 +} + +/* +public func useVarKlassErrorSimple() { + var k = Klass() + let k1 = k + let _ = k1 + let k2 = k + let _ = k2 + + k = Klass() + let k3 = k + let _ = k3 + let k4 = k + let _ = k4 + + k = Klass() + let k5 = k + let _ = k5 +} +*/ diff --git a/test/SILOptimizer/moveonly_addresschecker_diagnostics.swift b/test/SILOptimizer/moveonly_addresschecker_diagnostics.swift new file mode 100644 index 0000000000000..0f8e792c7f071 --- /dev/null +++ b/test/SILOptimizer/moveonly_addresschecker_diagnostics.swift @@ -0,0 +1,2070 @@ +// RUN: %target-swift-emit-sil -verify -enable-experimental-move-only %s + +////////////////// +// Declarations // +////////////////// + +@_moveOnly +public class Klass { + var intField: Int + var k: Klass? + init() { + k = Klass() + intField = 5 + } +} + +var boolValue: Bool { return true } + +public func classUseMoveOnlyWithoutEscaping(_ x: Klass) { +} +public func classConsume(_ x: __owned Klass) { +} + +@_moveOnly +public struct NonTrivialStruct { + var k = Klass() +} + +public func nonConsumingUseNonTrivialStruct(_ s: NonTrivialStruct) {} + +@_moveOnly +public enum NonTrivialEnum { + case first + case second(Klass) + case third(NonTrivialStruct) +} + +public func nonConsumingUseNonTrivialEnum(_ e : NonTrivialEnum) {} + +@_moveOnly +public final class FinalKlass { + var k: Klass? = nil +} + +/////////// +// Tests // +/////////// + +///////////////// +// Class Tests // +///////////////// + +public func classSimpleChainTest(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-note {{consuming use}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = x // expected-note {{consuming use}} + let y2 = x2 // expected-note {{consuming use}} + let k2 = y2 + let k3 = x2 // expected-note {{consuming use}} + let _ = k3 + classUseMoveOnlyWithoutEscaping(k2) +} + +public func classSimpleChainArgTest(_ x2: inout Klass) { + // expected-error @-1 {{'x2' consumed more than once}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + var y2 = x2 // expected-note {{consuming use}} + y2 = x2 // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + let k2 = y2 + classUseMoveOnlyWithoutEscaping(k2) +} + +public func classSimpleNonConsumingUseTest(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-note {{consuming use}} + x2 = x // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x2) +} + +public func classSimpleNonConsumingUseArgTest(_ x2: inout Klass) { + classUseMoveOnlyWithoutEscaping(x2) +} + +public func classMultipleNonConsumingUseTest(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-note {{consuming use}} + x2 = x // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x2) + classUseMoveOnlyWithoutEscaping(x2) + print(x2) +} + +public func classMultipleNonConsumingUseArgTest(_ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + classUseMoveOnlyWithoutEscaping(x2) + classUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} +} + +public func classMultipleNonConsumingUseArgTest2(_ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + classUseMoveOnlyWithoutEscaping(x2) + classUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x2) +} + +public func classMultipleNonConsumingUseArgTest3(_ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x2' consumed more than once}} + classUseMoveOnlyWithoutEscaping(x2) + classUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func classMultipleNonConsumingUseArgTest4(_ x2: inout Klass) { // expected-error {{'x2' used after consume. Lifetime extension of variable requires a copy}} + classUseMoveOnlyWithoutEscaping(x2) + classUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x2) // expected-note {{non-consuming use}} + x2 = Klass() +} + + +public func classUseAfterConsume(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-error {{'x2' consumed more than once}} + // expected-note @-1 {{consuming use}} + x2 = x // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} +} + +public func classUseAfterConsumeArg(_ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x2' consumed more than once}} + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func classDoubleConsume(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-error {{'x2' consumed more than once}} + // expected-note @-1 {{consuming use}} + x2 = Klass() + classConsume(x2) // expected-note {{consuming use}} + classConsume(x2) // expected-note {{consuming use}} +} + +public func classDoubleConsumeArg(_ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x2' consumed more than once}} + classConsume(x2) // expected-note {{consuming use}} + classConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func classLoopConsume(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-error {{'x2' consumed by a use in a loop}} + // expected-note @-1 {{consuming use}} + x2 = Klass() + for _ in 0..<1024 { + classConsume(x2) // expected-note {{consuming use}} + } +} + +public func classLoopConsumeArg(_ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + classConsume(x2) // expected-note {{consuming use}} + } +} + +public func classLoopConsumeArg2(_ x2: inout Klass) { // expected-error {{'x2' consumed by a use in a loop}} + for _ in 0..<1024 { + classConsume(x2) // expected-note {{consuming use}} + } + x2 = Klass() +} + +public func classDiamond(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-note {{consuming use}} + x2 = Klass() + if boolValue { + classConsume(x2) + } else { + classConsume(x2) + } +} + +public func classDiamondArg(_ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + if boolValue { + classConsume(x2) // expected-note {{consuming use}} + } else { + classConsume(x2) // expected-note {{consuming use}} + } +} + +public func classDiamondInLoop(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + // expected-note @-2 {{consuming use}} + x2 = Klass() + for _ in 0..<1024 { + if boolValue { + classConsume(x2) // expected-note {{consuming use}} + } else { + classConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + } +} + +public func classDiamondInLoopArg(_ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + if boolValue { + classConsume(x2) // expected-note {{consuming use}} + } else { + classConsume(x2) // expected-note {{consuming use}} + } + } +} + +public func classDiamondInLoopArg2(_ x2: inout Klass) { // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + for _ in 0..<1024 { + if boolValue { + classConsume(x2) // expected-note {{consuming use}} + } else { + classConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + } + x2 = Klass() +} + +public func classAssignToVar1(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-error {{'x2' consumed more than once}} + // expected-note @-1 {{consuming use}} + x2 = Klass() + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + x3 = x // expected-note {{consuming use}} + print(x3) +} + +public func classAssignToVar1Arg(_ x2: inout Klass) { // expected-error {{'x2' consumed more than once}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + x3 = Klass() + print(x3) +} + +public func classAssignToVar2(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-error {{'x2' consumed more than once}} + // expected-note @-1 {{consuming use}} + x2 = Klass() + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x3) +} + +public func classAssignToVar2Arg(_ x2: inout Klass) { // expected-error {{'x2' consumed more than once}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + classUseMoveOnlyWithoutEscaping(x3) +} + +public func classAssignToVar3(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-note {{consuming use}} + x2 = Klass() + var x3 = x2 + x3 = x // expected-note {{consuming use}} + print(x3) +} + +public func classAssignToVar3Arg(_ x: Klass, _ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x' has guaranteed ownership but was consumed}} + var x3 = x2 // expected-note {{consuming use}} + x3 = x // expected-note {{consuming use}} + print(x3) +} + +public func classAssignToVar3Arg2(_ x: Klass, _ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x' has guaranteed ownership but was consumed}} + var x3 = x2 // expected-note {{consuming use}} + x3 = x // expected-note {{consuming use}} + print(x3) +} + +public func classAssignToVar4(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-error {{'x2' consumed more than once}} + // expected-note @-1 {{consuming use}} + x2 = Klass() + let x3 = x2 // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + print(x3) +} + +public func classAssignToVar4Arg(_ x2: inout Klass) { // expected-error {{'x2' consumed more than once}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + let x3 = x2 // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + print(x3) +} + +public func classAssignToVar5() { + var x2 = Klass() // expected-error {{'x2' used after consume. Lifetime extension of variable requires a copy}} + x2 = Klass() + var x3 = x2 // expected-note {{consuming use}} + // TODO: Need to mark this as the lifetime extending use. We fail + // appropriately though. + classUseMoveOnlyWithoutEscaping(x2) // expected-note {{non-consuming use}} + x3 = Klass() + print(x3) +} + +public func classAssignToVar5Arg(_ x: Klass, _ x2: inout Klass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x' has guaranteed ownership but was consumed}} + var x3 = x2 // expected-note {{consuming use}} + // TODO: Need to mark this as the lifetime extending use. We fail + // appropriately though. + classUseMoveOnlyWithoutEscaping(x2) + x3 = x // expected-note {{consuming use}} + print(x3) +} + +public func classAssignToVar5Arg2(_ x: Klass, _ x2: inout Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + // expected-error @-1 {{'x2' used after consume. Lifetime extension of variable requires a copy}} + var x3 = x2 // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x2) // expected-note {{non-consuming use}} + x3 = x // expected-note {{consuming use}} + print(x3) + x2 = Klass() +} + +public func classAccessAccessField(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-note {{consuming use}} + x2 = Klass() + classUseMoveOnlyWithoutEscaping(x2.k!) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.k!) + } +} + +public func classAccessAccessFieldArg(_ x2: inout Klass) { + classUseMoveOnlyWithoutEscaping(x2.k!) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.k!) + } +} + +public func classAccessConsumeField(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}} + var x2 = x // expected-note {{consuming use}} + x2 = Klass() + // Since a class is a reference type, we do not emit an error here. + classConsume(x2.k!) + for _ in 0..<1024 { + classConsume(x2.k!) + } +} + +public func classAccessConsumeFieldArg(_ x2: inout Klass) { + // Since a class is a reference type, we do not emit an error here. + classConsume(x2.k!) + for _ in 0..<1024 { + classConsume(x2.k!) + } +} + +extension Klass { + func testNoUseSelf() { // expected-error {{'self' has guaranteed ownership but was consumed}} + let x = self // expected-note {{consuming use}} + let _ = x + } +} + +///////////////// +// Final Class // +///////////////// + +public func finalClassUseMoveOnlyWithoutEscaping(_ x: FinalKlass) { +} +public func finalClassConsume(_ x: __owned FinalKlass) { +} + +public func finalClassSimpleChainTest() { + var x2 = FinalKlass() + x2 = FinalKlass() + let y2 = x2 + let k2 = y2 + finalClassUseMoveOnlyWithoutEscaping(k2) +} + +public func finalClassSimpleChainTestArg(_ x2: inout FinalKlass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + let y2 = x2 // expected-note {{consuming use}} + let k2 = y2 + finalClassUseMoveOnlyWithoutEscaping(k2) +} + +public func finalClassSimpleChainTestArg2(_ x2: inout FinalKlass) { + let y2 = x2 + let k2 = y2 + finalClassUseMoveOnlyWithoutEscaping(k2) + x2 = FinalKlass() +} + +public func finalClassSimpleChainTestArg3(_ x2: inout FinalKlass) { + for _ in 0..<1024 {} + let y2 = x2 + let k2 = y2 + finalClassUseMoveOnlyWithoutEscaping(k2) + x2 = FinalKlass() +} + +public func finalClassSimpleNonConsumingUseTest(_ x: __owned FinalKlass) { + var x2 = x + x2 = FinalKlass() + finalClassUseMoveOnlyWithoutEscaping(x2) +} + +public func finalClassSimpleNonConsumingUseTestArg(_ x2: inout FinalKlass) { + finalClassUseMoveOnlyWithoutEscaping(x2) +} + +public func finalClassMultipleNonConsumingUseTest() { + var x2 = FinalKlass() + x2 = FinalKlass() + finalClassUseMoveOnlyWithoutEscaping(x2) + finalClassUseMoveOnlyWithoutEscaping(x2) + print(x2) +} + +public func finalClassMultipleNonConsumingUseTestArg(_ x2: inout FinalKlass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + finalClassUseMoveOnlyWithoutEscaping(x2) + finalClassUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} +} + +public func finalClassUseAfterConsume() { + var x2 = FinalKlass() // expected-error {{'x2' consumed more than once}} + x2 = FinalKlass() + finalClassUseMoveOnlyWithoutEscaping(x2) + finalClassConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} +} + +public func finalClassUseAfterConsumeArg(_ x2: inout FinalKlass) { // expected-error {{'x2' consumed more than once}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + finalClassUseMoveOnlyWithoutEscaping(x2) + finalClassConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func finalClassDoubleConsume() { + var x2 = FinalKlass() // expected-error {{'x2' consumed more than once}} + x2 = FinalKlass() + finalClassConsume(x2) // expected-note {{consuming use}} + finalClassConsume(x2) // expected-note {{consuming use}} +} + +public func finalClassDoubleConsumeArg(_ x2: inout FinalKlass) { // expected-error {{'x2' consumed more than once}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + finalClassConsume(x2) // expected-note {{consuming use}} + finalClassConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func finalClassLoopConsume() { + var x2 = FinalKlass() // expected-error {{'x2' consumed by a use in a loop}} + x2 = FinalKlass() + for _ in 0..<1024 { + finalClassConsume(x2) // expected-note {{consuming use}} + } +} + +public func finalClassLoopConsumeArg(_ x2: inout FinalKlass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + finalClassConsume(x2) // expected-note {{consuming use}} + } +} + +public func finalClassDiamond() { + var x2 = FinalKlass() + x2 = FinalKlass() + if boolValue { + finalClassConsume(x2) + } else { + finalClassConsume(x2) + } +} + +public func finalClassDiamondArg(_ x2: inout FinalKlass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + if boolValue { + finalClassConsume(x2) // expected-note {{consuming use}} + } else { + finalClassConsume(x2) // expected-note {{consuming use}} + } +} + +public func finalClassDiamondInLoop() { + var x2 = FinalKlass() // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = FinalKlass() + for _ in 0..<1024 { + if boolValue { + finalClassConsume(x2) // expected-note {{consuming use}} + } else { + finalClassConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + } +} + +public func finalClassDiamondInLoopArg(_ x2: inout FinalKlass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + if boolValue { + finalClassConsume(x2) // expected-note {{consuming use}} + } else { + finalClassConsume(x2) // expected-note {{consuming use}} + } + } +} + +public func finalClassDiamondInLoopArg2(_ x2: inout FinalKlass) { // expected-error {{consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + for _ in 0..<1024 { + if boolValue { + finalClassConsume(x2) // expected-note {{consuming use}} + } else { + finalClassConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + } + + x2 = FinalKlass() +} + +public func finalClassAssignToVar1() { + var x2 = FinalKlass() // expected-error {{'x2' consumed more than once}} + x2 = FinalKlass() + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + x3 = FinalKlass() + print(x3) +} + +public func finalClassAssignToVar1Arg(_ x2: inout FinalKlass) { + // expected-error @-1 {{'x2' consumed more than once}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + x3 = FinalKlass() + print(x3) +} + +public func finalClassAssignToVar2() { + var x2 = FinalKlass() // expected-error {{'x2' consumed more than once}} + x2 = FinalKlass() + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + finalClassUseMoveOnlyWithoutEscaping(x3) +} + +public func finalClassAssignToVar2Arg(_ x2: inout FinalKlass) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + finalClassUseMoveOnlyWithoutEscaping(x3) +} + +public func finalClassAssignToVar3() { + var x2 = Klass() + x2 = Klass() + var x3 = x2 + x3 = Klass() + print(x3) +} + +public func finalClassAssignToVar3Arg(_ x2: inout FinalKlass) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + var x3 = x2 // expected-note {{consuming use}} + x3 = FinalKlass() + print(x3) +} + +public func finalClassAssignToVar4() { + var x2 = FinalKlass() // expected-error {{'x2' consumed more than once}} + x2 = FinalKlass() + let x3 = x2 // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + print(x3) +} + +public func finalClassAssignToVar4Arg(_ x2: inout FinalKlass) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + let x3 = x2 // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + print(x3) +} + +public func finalClassAssignToVar5() { + var x2 = FinalKlass() // expected-error {{'x2' used after consume. Lifetime extension of variable requires a copy}} + x2 = FinalKlass() + var x3 = x2 // expected-note {{consuming use}} + finalClassUseMoveOnlyWithoutEscaping(x2) // expected-note {{non-consuming use}} + x3 = FinalKlass() + print(x3) +} + +public func finalClassAssignToVar5Arg(_ x2: inout FinalKlass) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + var x3 = x2 // expected-note {{consuming use}} + finalClassUseMoveOnlyWithoutEscaping(x2) + x3 = FinalKlass() + print(x3) +} + +public func finalClassAccessField() { + var x2 = FinalKlass() + x2 = FinalKlass() + classUseMoveOnlyWithoutEscaping(x2.k!) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.k!) + } +} + +public func finalClassAccessFieldArg(_ x2: inout FinalKlass) { + classUseMoveOnlyWithoutEscaping(x2.k!) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.k!) + } +} + +public func finalClassConsumeField() { + var x2 = FinalKlass() + x2 = FinalKlass() + + classConsume(x2.k!) + for _ in 0..<1024 { + classConsume(x2.k!) + } +} + +public func finalClassConsumeFieldArg(_ x2: inout FinalKlass) { + classConsume(x2.k!) + for _ in 0..<1024 { + classConsume(x2.k!) + } +} + +////////////////////// +// Aggregate Struct // +////////////////////// + +@_moveOnly +public struct KlassPair { + var lhs: Klass = Klass() + var rhs: Klass = Klass() +} + +@_moveOnly +public struct AggStruct { + var lhs: Klass = Klass() + var center: Int = 5 + var rhs: Klass = Klass() + var pair: KlassPair = KlassPair() + + init() {} + + // Testing that DI ignores normal init. We also get an error on our return + // value from the function since we do not reinitialize self. + // + // TODO: Improve error message! + init(myInit: Int) { // expected-error {{'self' consumed more than once}} + let x = self // expected-note {{consuming use}} + let _ = x + } // expected-note {{consuming use}} + + // Make sure we can reinitialize successfully. + init(myInit2: Int) { + let x = self + let _ = x + self = AggStruct(myInit: myInit2) + } + + // Testing delegating init. + // + // TODO: Improve error to say need to reinitialize self.lhs before end of + // function. + init(myInit3: Int) { // expected-error {{'self' consumed more than once}} + self.init() + self.center = myInit3 + let x = self.lhs // expected-note {{consuming use}} + let _ = x + } // expected-note {{consuming use}} + + init(myInit4: Int) { + self.init() + self.center = myInit4 + let x = self.lhs + let _ = x + self = AggStruct(myInit: myInit4) + } + + init(myInit5: Int) { + self.init() + self.center = myInit5 + let x = self.lhs + let _ = x + self.lhs = Klass() + } +} + +public func aggStructUseMoveOnlyWithoutEscaping(_ x: AggStruct) { +} +public func aggStructConsume(_ x: __owned AggStruct) { +} + +public func aggStructSimpleChainTest() { + var x2 = AggStruct() + x2 = AggStruct() + let y2 = x2 + let k2 = y2 + aggStructUseMoveOnlyWithoutEscaping(k2) +} + +public func aggStructSimpleChainTestArg(_ x2: inout AggStruct) { + // expected-error @-1 {{'x2' consumed more than once}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + var y2 = x2 // expected-note {{consuming use}} + y2 = x2 // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + let k2 = y2 + aggStructUseMoveOnlyWithoutEscaping(k2) +} + +public func aggStructSimpleNonConsumingUseTest() { + var x2 = AggStruct() + x2 = AggStruct() + aggStructUseMoveOnlyWithoutEscaping(x2) +} + +public func aggStructSimpleNonConsumingUseTestArg(_ x2: inout AggStruct) { + aggStructUseMoveOnlyWithoutEscaping(x2) +} + +public func aggStructMultipleNonConsumingUseTest() { + var x2 = AggStruct() + x2 = AggStruct() + aggStructUseMoveOnlyWithoutEscaping(x2) + aggStructUseMoveOnlyWithoutEscaping(x2) + print(x2) +} + +public func aggStructMultipleNonConsumingUseTestArg(_ x2: inout AggStruct) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + aggStructUseMoveOnlyWithoutEscaping(x2) + aggStructUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} +} + +public func aggStructUseAfterConsume() { + var x2 = AggStruct() // expected-error {{'x2' consumed more than once}} + x2 = AggStruct() + aggStructUseMoveOnlyWithoutEscaping(x2) + aggStructConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} +} + +public func aggStructUseAfterConsumeArg(_ x2: inout AggStruct) { + // expected-error @-1 {{'x2' consumed more than once}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + aggStructUseMoveOnlyWithoutEscaping(x2) + aggStructConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func aggStructDoubleConsume() { + var x2 = AggStruct() // expected-error {{'x2' consumed more than once}} + x2 = AggStruct() + aggStructConsume(x2) // expected-note {{consuming use}} + aggStructConsume(x2) // expected-note {{consuming use}} +} + +public func aggStructDoubleConsumeArg(_ x2: inout AggStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + aggStructConsume(x2) // expected-note {{consuming use}} + aggStructConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func aggStructLoopConsume() { + var x2 = AggStruct() // expected-error {{'x2' consumed by a use in a loop}} + x2 = AggStruct() + for _ in 0..<1024 { + aggStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggStructLoopConsumeArg(_ x2: inout AggStruct) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + aggStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggStructDiamond() { + var x2 = AggStruct() + x2 = AggStruct() + if boolValue { + aggStructConsume(x2) + } else { + aggStructConsume(x2) + } +} + +public func aggStructDiamondArg(_ x2: inout AggStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + if boolValue { + aggStructConsume(x2) // expected-note {{consuming use}} + } else { + aggStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggStructDiamondInLoop() { + var x2 = AggStruct() + // expected-error @-1 {{'x2' consumed by a use in a loop}} + // expected-error @-2 {{'x2' consumed more than once}} + x2 = AggStruct() + for _ in 0..<1024 { + if boolValue { + aggStructConsume(x2) // expected-note {{consuming use}} + } else { + aggStructConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + } +} + +public func aggStructDiamondInLoopArg(_ x2: inout AggStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + if boolValue { + aggStructConsume(x2) // expected-note {{consuming use}} + } else { + aggStructConsume(x2) // expected-note {{consuming use}} + } + } +} + +public func aggStructAccessField() { + var x2 = AggStruct() + x2 = AggStruct() + classUseMoveOnlyWithoutEscaping(x2.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.lhs) + } +} + +public func aggStructAccessFieldArg(_ x2: inout AggStruct) { + classUseMoveOnlyWithoutEscaping(x2.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.lhs) + } +} + +public func aggStructConsumeField() { + var x2 = AggStruct() // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = AggStruct() + classConsume(x2.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.lhs) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } +} + +public func aggStructConsumeFieldArg(_ x2: inout AggStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + classConsume(x2.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.lhs) // expected-note {{consuming use}} + } +} + +public func aggStructAccessGrandField() { + var x2 = AggStruct() + x2 = AggStruct() + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + } +} + +public func aggStructAccessGrandFieldArg(_ x2: inout AggStruct) { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + } +} + +public func aggStructConsumeGrandField() { + var x2 = AggStruct() // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = AggStruct() + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } +} + +public func aggStructConsumeGrandFieldArg(_ x2: inout AggStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + } +} + +////////////////////////////// +// Aggregate Generic Struct // +////////////////////////////// + +@_moveOnly +public struct AggGenericStruct { + var lhs: Klass = Klass() + var rhs: UnsafeRawPointer? = nil + var pair: KlassPair = KlassPair() + var ptr2: UnsafePointer? = nil + + init() {} + + // Testing that DI ignores normal init. We also get an error on our return + // value from the function since we do not reinitialize self. + // + // TODO: Improve error message! + init(myInit: UnsafeRawPointer) { // expected-error {{'self' consumed more than once}} + let x = self // expected-note {{consuming use}} + let _ = x + } // expected-note {{consuming use}} + + // Make sure we can reinitialize successfully. + init(myInit2: UnsafeRawPointer) { + let x = self + let _ = x + self = AggGenericStruct(myInit: myInit2) + } + + // Testing delegating init. + // + // TODO: Improve error to say need to reinitialize self.lhs before end of + // function. + init(myInit3: UnsafeRawPointer) { // expected-error {{'self' consumed more than once}} + self.init() + self.rhs = myInit3 + let x = self.lhs // expected-note {{consuming use}} + let _ = x + } // expected-note {{consuming use}} + + init(myInit4: UnsafeRawPointer) { + self.init() + self.rhs = myInit4 + let x = self.lhs + let _ = x + self = AggGenericStruct(myInit: myInit4) + } + + init(myInit5: UnsafeRawPointer) { + self.init() + self.rhs = myInit5 + let x = self.lhs + let _ = x + self.lhs = Klass() + } +} + +public func aggGenericStructUseMoveOnlyWithoutEscaping(_ x: AggGenericStruct) { +} +public func aggGenericStructConsume(_ x: __owned AggGenericStruct) { +} + +public func aggGenericStructSimpleChainTest() { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + let y2 = x2 + let k2 = y2 + aggGenericStructUseMoveOnlyWithoutEscaping(k2) +} + +public func aggGenericStructSimpleChainTestArg(_ x2: inout AggGenericStruct) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + let y2 = x2 // expected-note {{consuming use}} + let k2 = y2 + aggGenericStructUseMoveOnlyWithoutEscaping(k2) +} + +public func aggGenericStructSimpleNonConsumingUseTest() { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + aggGenericStructUseMoveOnlyWithoutEscaping(x2) +} + +public func aggGenericStructSimpleNonConsumingUseTestArg(_ x2: inout AggGenericStruct) { + aggGenericStructUseMoveOnlyWithoutEscaping(x2) +} + +public func aggGenericStructMultipleNonConsumingUseTest() { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + print(x2) +} + +public func aggGenericStructMultipleNonConsumingUseTestArg(_ x2: inout AggGenericStruct) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} +} + +public func aggGenericStructUseAfterConsume() { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed more than once}} + x2 = AggGenericStruct() + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + aggGenericStructConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} +} + +public func aggGenericStructUseAfterConsumeArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + aggGenericStructConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}}x +} + +public func aggGenericStructDoubleConsume() { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed more than once}} + x2 = AggGenericStruct() + aggGenericStructConsume(x2) // expected-note {{consuming use}} + aggGenericStructConsume(x2) // expected-note {{consuming use}} +} + +public func aggGenericStructDoubleConsumeArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + aggGenericStructConsume(x2) // expected-note {{consuming use}} + aggGenericStructConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func aggGenericStructLoopConsume() { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed by a use in a loop}} + x2 = AggGenericStruct() + for _ in 0..<1024 { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggGenericStructLoopConsumeArg(_ x2: inout AggGenericStruct) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggGenericStructDiamond() { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + if boolValue { + aggGenericStructConsume(x2) + } else { + aggGenericStructConsume(x2) + } +} + +public func aggGenericStructDiamondArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + if boolValue { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } else { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggGenericStructDiamondInLoop() { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = AggGenericStruct() + for _ in 0..<1024 { + if boolValue { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } else { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + } +} + +public func aggGenericStructDiamondInLoopArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + if boolValue { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } else { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } + } +} + +public func aggGenericStructAccessField() { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + classUseMoveOnlyWithoutEscaping(x2.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.lhs) + } +} + +public func aggGenericStructAccessFieldArg(_ x2: inout AggGenericStruct) { + classUseMoveOnlyWithoutEscaping(x2.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.lhs) + } +} + +public func aggGenericStructConsumeField() { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = AggGenericStruct() + classConsume(x2.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.lhs) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } +} + +public func aggGenericStructConsumeFieldArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + classConsume(x2.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.lhs) // expected-note {{consuming use}} + } +} + +public func aggGenericStructAccessGrandField() { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + } +} + +public func aggGenericStructAccessGrandFieldArg(_ x2: inout AggGenericStruct) { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + } +} + +public func aggGenericStructConsumeGrandField() { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = AggGenericStruct() + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } +} + +public func aggGenericStructConsumeGrandField2() { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed more than once}} + x2 = AggGenericStruct() + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + } + classConsume(x2.pair.lhs) // expected-note {{consuming use}} +} + +public func aggGenericStructConsumeGrandFieldArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + } +} + +//////////////////////////////////////////////////////////// +// Aggregate Generic Struct + Generic But Body is Trivial // +//////////////////////////////////////////////////////////// + +public func aggGenericStructUseMoveOnlyWithoutEscaping(_ x: AggGenericStruct) { +} +public func aggGenericStructConsume(_ x: __owned AggGenericStruct) { +} + +public func aggGenericStructSimpleChainTest(_ x: T.Type) { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + let y2 = x2 + let k2 = y2 + aggGenericStructUseMoveOnlyWithoutEscaping(k2) +} + +public func aggGenericStructSimpleChainTestArg(_ x2: inout AggGenericStruct) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + let y2 = x2 // expected-note {{consuming use}} + let k2 = y2 + aggGenericStructUseMoveOnlyWithoutEscaping(k2) +} + +public func aggGenericStructSimpleNonConsumingUseTest(_ x: T.Type) { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + aggGenericStructUseMoveOnlyWithoutEscaping(x2) +} + +public func aggGenericStructSimpleNonConsumingUseTestArg(_ x2: inout AggGenericStruct) { + aggGenericStructUseMoveOnlyWithoutEscaping(x2) +} + +public func aggGenericStructMultipleNonConsumingUseTest(_ x: T.Type) { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + print(x2) +} + +public func aggGenericStructMultipleNonConsumingUseTestArg(_ x2: inout AggGenericStruct) { //expected-error {{'x2' consumed but not reinitialized before end of function}} + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} +} + +public func aggGenericStructUseAfterConsume(_ x: T.Type) { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed more than once}} + x2 = AggGenericStruct() + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + aggGenericStructConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} +} + +public func aggGenericStructUseAfterConsumeArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + aggGenericStructUseMoveOnlyWithoutEscaping(x2) + aggGenericStructConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func aggGenericStructDoubleConsume(_ x: T.Type) { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed more than once}} + x2 = AggGenericStruct() + aggGenericStructConsume(x2) // expected-note {{consuming use}} + aggGenericStructConsume(x2) // expected-note {{consuming use}} +} + +public func aggGenericStructDoubleConsumeArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + aggGenericStructConsume(x2) // expected-note {{consuming use}} + aggGenericStructConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func aggGenericStructLoopConsume(_ x: T.Type) { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed by a use in a loop}} + x2 = AggGenericStruct() + for _ in 0..<1024 { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggGenericStructLoopConsumeArg(_ x2: inout AggGenericStruct) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggGenericStructDiamond(_ x: T.Type) { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + if boolValue { + aggGenericStructConsume(x2) + } else { + aggGenericStructConsume(x2) + } +} + +public func aggGenericStructDiamondArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + if boolValue { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } else { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } +} + +public func aggGenericStructDiamondInLoop(_ x: T.Type) { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed more than once}} + // expected-error @-1 {{'x2' consumed by a use in a loop}} + x2 = AggGenericStruct() + for _ in 0..<1024 { + if boolValue { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } else { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + } +} + +public func aggGenericStructDiamondInLoopArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + if boolValue { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } else { + aggGenericStructConsume(x2) // expected-note {{consuming use}} + } + } +} + +public func aggGenericStructAccessField(_ x: T.Type) { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + classUseMoveOnlyWithoutEscaping(x2.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.lhs) + } +} + +public func aggGenericStructAccessFieldArg(_ x2: inout AggGenericStruct) { + classUseMoveOnlyWithoutEscaping(x2.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.lhs) + } +} + +public func aggGenericStructConsumeField(_ x: T.Type) { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = AggGenericStruct() + classConsume(x2.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.lhs) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } +} + +public func aggGenericStructConsumeFieldArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + classConsume(x2.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.lhs) // expected-note {{consuming use}} + } +} + +public func aggGenericStructAccessGrandField(_ x: T.Type) { + var x2 = AggGenericStruct() + x2 = AggGenericStruct() + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + } +} + +public func aggGenericStructAccessGrandFieldArg(_ x2: inout AggGenericStruct) { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + for _ in 0..<1024 { + classUseMoveOnlyWithoutEscaping(x2.pair.lhs) + } +} + +public func aggGenericStructConsumeGrandField(_ x: T.Type) { + var x2 = AggGenericStruct() // expected-error {{'x2' consumed by a use in a loop}} + // expected-error @-1 {{'x2' consumed more than once}} + x2 = AggGenericStruct() + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } +} + +public func aggGenericStructConsumeGrandFieldArg(_ x2: inout AggGenericStruct) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + for _ in 0..<1024 { + classConsume(x2.pair.lhs) // expected-note {{consuming use}} + } +} + +///////////////////// +// Enum Test Cases // +///////////////////// + +@_moveOnly +public enum EnumTy { + case klass(Klass) + case int(Int) + + func doSomething() -> Bool { true } +} + +public func enumUseMoveOnlyWithoutEscaping(_ x: EnumTy) { +} +public func enumConsume(_ x: __owned EnumTy) { +} + +public func enumSimpleChainTest() { + var x2 = EnumTy.klass(Klass()) + x2 = EnumTy.klass(Klass()) + let y2 = x2 + let k2 = y2 + enumUseMoveOnlyWithoutEscaping(k2) +} + +public func enumSimpleChainTestArg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + let y2 = x2 // expected-note {{consuming use}} + let k2 = y2 + enumUseMoveOnlyWithoutEscaping(k2) +} + +public func enumSimpleNonConsumingUseTest() { + var x2 = EnumTy.klass(Klass()) + x2 = EnumTy.klass(Klass()) + enumUseMoveOnlyWithoutEscaping(x2) +} + +public func enumSimpleNonConsumingUseTestArg(_ x2: inout EnumTy) { + enumUseMoveOnlyWithoutEscaping(x2) +} + +public func enumMultipleNonConsumingUseTest() { + var x2 = EnumTy.klass(Klass()) + x2 = EnumTy.klass(Klass()) + enumUseMoveOnlyWithoutEscaping(x2) + enumUseMoveOnlyWithoutEscaping(x2) + print(x2) +} + +public func enumMultipleNonConsumingUseTestArg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + enumUseMoveOnlyWithoutEscaping(x2) + enumUseMoveOnlyWithoutEscaping(x2) + print(x2) // expected-note {{consuming use}} +} + +public func enumUseAfterConsume() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' consumed more than once}} + x2 = EnumTy.klass(Klass()) + enumUseMoveOnlyWithoutEscaping(x2) + enumConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} +} + +public func enumUseAfterConsumeArg(_ x2: inout EnumTy) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + enumUseMoveOnlyWithoutEscaping(x2) + enumConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func enumDoubleConsume() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' consumed more than once}} + x2 = EnumTy.klass(Klass()) + enumConsume(x2) // expected-note {{consuming use}} + enumConsume(x2) // expected-note {{consuming use}} +} + +public func enumDoubleConsumeArg(_ x2: inout EnumTy) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + enumConsume(x2) // expected-note {{consuming use}} + enumConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} +} + +public func enumLoopConsume() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' consumed by a use in a loop}} + x2 = EnumTy.klass(Klass()) + for _ in 0..<1024 { + enumConsume(x2) // expected-note {{consuming use}} + } +} + +public func enumLoopConsumeArg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + enumConsume(x2) // expected-note {{consuming use}} + } +} + +public func enumDiamond() { + var x2 = EnumTy.klass(Klass()) + x2 = EnumTy.klass(Klass()) + if boolValue { + enumConsume(x2) + } else { + enumConsume(x2) + } +} + +public func enumDiamondArg(_ x2: inout EnumTy) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + if boolValue { + enumConsume(x2) // expected-note {{consuming use}} + } else { + enumConsume(x2) // expected-note {{consuming use}} + } +} + +public func enumDiamondInLoop() { + var x2 = EnumTy.klass(Klass()) + // expected-error @-1 {{'x2' consumed by a use in a loop}} + // expected-error @-2 {{'x2' consumed more than once}} + x2 = EnumTy.klass(Klass()) + for _ in 0..<1024 { + if boolValue { + enumConsume(x2) // expected-note {{consuming use}} + } else { + enumConsume(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + } +} + +public func enumDiamondInLoopArg(_ x2: inout EnumTy) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + if boolValue { + enumConsume(x2) // expected-note {{consuming use}} + } else { + enumConsume(x2) // expected-note {{consuming use}} + } + } +} + +public func enumAssignToVar1() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' consumed more than once}} + x2 = EnumTy.klass(Klass()) + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + x3 = EnumTy.klass(Klass()) + print(x3) +} + +public func enumAssignToVar1Arg(_ x2: inout EnumTy) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + x3 = EnumTy.klass(Klass()) + print(x3) +} + +public func enumAssignToVar2() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' consumed more than once}} + x2 = EnumTy.klass(Klass()) + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + enumUseMoveOnlyWithoutEscaping(x3) +} + +public func enumAssignToVar2Arg(_ x2: inout EnumTy) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + var x3 = x2 // expected-note {{consuming use}} + x3 = x2 // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + enumUseMoveOnlyWithoutEscaping(x3) +} + +public func enumAssignToVar3() { + var x2 = EnumTy.klass(Klass()) + x2 = EnumTy.klass(Klass()) + var x3 = x2 + x3 = EnumTy.klass(Klass()) + print(x3) +} + +public func enumAssignToVar3Arg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + + var x3 = x2 // expected-note {{consuming use}} + x3 = EnumTy.klass(Klass()) + print(x3) +} + +public func enumAssignToVar4() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' consumed more than once}} + x2 = EnumTy.klass(Klass()) + let x3 = x2 // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + print(x3) +} + +public func enumAssignToVar4Arg(_ x2: inout EnumTy) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + let x3 = x2 // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + print(x3) +} + +public func enumAssignToVar5() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' used after consume. Lifetime extension of variable requires a copy}} + x2 = EnumTy.klass(Klass()) + var x3 = x2 // expected-note {{consuming use}} + enumUseMoveOnlyWithoutEscaping(x2) // expected-note {{non-consuming use}} + x3 = EnumTy.klass(Klass()) + print(x3) +} + +public func enumAssignToVar5Arg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + + var x3 = x2 // expected-note {{consuming use}} + enumUseMoveOnlyWithoutEscaping(x2) + x3 = EnumTy.klass(Klass()) + print(x3) +} + +public func enumPatternMatchIfLet1() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' consumed more than once}} + x2 = EnumTy.klass(Klass()) + if case let EnumTy.klass(x) = x2 { // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x) + } + if case let EnumTy.klass(x) = x2 { // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x) + } +} + +public func enumPatternMatchIfLet1Arg(_ x2: inout EnumTy) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + if case let EnumTy.klass(x) = x2 { // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x) + } + if case let EnumTy.klass(x) = x2 { // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + classUseMoveOnlyWithoutEscaping(x) + } +} + +public func enumPatternMatchIfLet2() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' consumed by a use in a loop}} + x2 = EnumTy.klass(Klass()) + for _ in 0..<1024 { + if case let EnumTy.klass(x) = x2 { // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x) + } + } +} + +public func enumPatternMatchIfLet2Arg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + for _ in 0..<1024 { + if case let EnumTy.klass(x) = x2 { // expected-note {{consuming use}} + classUseMoveOnlyWithoutEscaping(x) + } + } +} + +// This is wrong. +public func enumPatternMatchSwitch1() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' used after consume. Lifetime extension of variable requires a copy}} + x2 = EnumTy.klass(Klass()) + switch x2 { // expected-note {{consuming use}} + case let EnumTy.klass(k): + classUseMoveOnlyWithoutEscaping(k) + // This should be flagged as the use after free use. We are atleast + // erroring though. + enumUseMoveOnlyWithoutEscaping(x2) // expected-note {{non-consuming use}} + case .int: + break + } +} + +public func enumPatternMatchSwitch1Arg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + switch x2 { // expected-note {{consuming use}} + case let EnumTy.klass(k): + classUseMoveOnlyWithoutEscaping(k) + // This should be flagged as the use after free use. We are atleast + // erroring though. + enumUseMoveOnlyWithoutEscaping(x2) + case .int: + break + } +} + +public func enumPatternMatchSwitch2() { + var x2 = EnumTy.klass(Klass()) + x2 = EnumTy.klass(Klass()) + switch x2 { + case let EnumTy.klass(k): + classUseMoveOnlyWithoutEscaping(k) + case .int: + break + } +} + +public func enumPatternMatchSwitch2Arg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + switch x2 { // expected-note {{consuming use}} + case let EnumTy.klass(k): + classUseMoveOnlyWithoutEscaping(k) + case .int: + break + } +} + +// QOI: We can do better here. We should also flag x2 +public func enumPatternMatchSwitch2WhereClause() { + var x2 = EnumTy.klass(Klass()) // expected-error {{'x2' used after consume. Lifetime extension of variable requires a copy}} + x2 = EnumTy.klass(Klass()) + switch x2 { // expected-note {{consuming use}} + case let EnumTy.klass(k) + where x2.doSomething(): // expected-note {{non-consuming use}} + classUseMoveOnlyWithoutEscaping(k) + case .int: + break + case EnumTy.klass: + break + } +} + +public func enumPatternMatchSwitch2WhereClauseArg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + switch x2 { // expected-note {{consuming use}} + case let EnumTy.klass(k) + where x2.doSomething(): + classUseMoveOnlyWithoutEscaping(k) + case .int: + break + case EnumTy.klass: + break + } +} + +public func enumPatternMatchSwitch2WhereClause2() { + var x2 = EnumTy.klass(Klass()) + x2 = EnumTy.klass(Klass()) + switch x2 { + case let EnumTy.klass(k) + where boolValue: + classUseMoveOnlyWithoutEscaping(k) + case .int: + break + case EnumTy.klass: + break + } +} + +public func enumPatternMatchSwitch2WhereClause2Arg(_ x2: inout EnumTy) { // expected-error {{'x2' consumed but not reinitialized before end of function}} + switch x2 { // expected-note {{consuming use}} + case let EnumTy.klass(k) + where boolValue: + classUseMoveOnlyWithoutEscaping(k) + case .int: + break + case EnumTy.klass: + break + } +} + +///////////////////////////// +// Closure and Defer Tests // +///////////////////////////// + +public func closureClassUseAfterConsume1() { + let f = { + var x2 = Klass() // expected-error {{'x2' consumed more than once}} + x2 = Klass() + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + } + f() +} + +public func closureClassUseAfterConsume2() { + let f = { () in + var x2 = Klass() // expected-error {{'x2' consumed more than once}} + x2 = Klass() + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + } + f() +} + +public func closureClassUseAfterConsumeArg(_ argX: inout Klass) { + // TODO: Fix this + let f = { (_ x2: inout Klass) in + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + f(&argX) +} + +// We do not support captures of vars by closures today. +public func closureCaptureClassUseAfterConsume() { + var x2 = Klass() + // expected-error @-1 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-2 {{Usage of a move only type that the move checker does not know how to check!}} + x2 = Klass() + let f = { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) + print(x2) + } + f() +} + +public func closureCaptureClassUseAfterConsumeError() { + var x2 = Klass() + // expected-error @-1 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-2 {{Usage of a move only type that the move checker does not know how to check!}} + x2 = Klass() + let f = { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) + print(x2) + } + f() + let x3 = x2 + let _ = x3 +} + +public func closureCaptureClassArgUseAfterConsume(_ x2: inout Klass) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-3 {{'x2' consumed more than once}} + // expected-note @-4 {{'x2' is declared 'inout'}} + let f = { // expected-note {{consuming use}} + // expected-error @-1 {{escaping closure captures 'inout' parameter 'x2'}} + classUseMoveOnlyWithoutEscaping(x2) // expected-note {{captured here}} + classConsume(x2) // expected-note {{captured here}} + // expected-note @-1 {{consuming use}} + print(x2) // expected-note {{captured here}} + // expected-note @-1 {{consuming use}} + // expected-note @-2 {{consuming use}} + } + f() +} + +// TODO: Improve error msg here to make it clear the use is due to the defer. +public func deferCaptureClassUseAfterConsume() { + var x2 = Klass() + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + x2 = Klass() + defer { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + print(x2) +} + +public func deferCaptureClassUseAfterConsume2() { + var x2 = Klass() + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + x2 = Klass() + defer { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + // Shouldn't we get a use after free error on the defer? + let x3 = x2 + let _ = x3 +} + +public func deferCaptureClassArgUseAfterConsume(_ x2: inout Klass) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed more than once}} + classUseMoveOnlyWithoutEscaping(x2) + defer { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) // expected-note {{consuming use}} + // expected-note @-1 {{consuming use}} + } + print("foo") +} + +public func closureAndDeferCaptureClassUseAfterConsume() { + var x2 = Klass() + // expected-error @-1 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-2 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-3 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-4 {{'x2' consumed more than once}} + x2 = Klass() + let f = { + defer { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) + // expected-note @-1 {{consuming use}} + // expected-note @-2 {{consuming use}} + } + print("foo") + } + f() +} + +public func closureAndDeferCaptureClassUseAfterConsume2() { + var x2 = Klass() + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-3 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-4 {{'x2' consumed more than once}} + x2 = Klass() + let f = { + classConsume(x2) + defer { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) + // expected-note @-1 {{consuming use}} + // expected-note @-2 {{consuming use}} + } + print("foo") + } + f() +} + +public func closureAndDeferCaptureClassUseAfterConsume3() { + var x2 = Klass() + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-3 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-4 {{'x2' consumed more than once}} + x2 = Klass() + let f = { + classConsume(x2) + defer { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) // expected-note {{consuming use}} + print(x2) + // expected-note @-1 {{consuming use}} + // expected-note @-2 {{consuming use}} + } + print("foo") + } + f() + classConsume(x2) +} + +public func closureAndDeferCaptureClassArgUseAfterConsume(_ x2: inout Klass) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-3 {{'x2' consumed more than once}} + // expected-note @-4 {{'x2' is declared 'inout'}} + let f = { // expected-error {{escaping closure captures 'inout' parameter 'x2'}} + // expected-note @-1 {{consuming use}} + defer { // expected-note {{captured indirectly by this call}} + classUseMoveOnlyWithoutEscaping(x2) // expected-note {{captured here}} + classConsume(x2) // expected-note {{captured here}} + // expected-note @-1 {{consuming use}} + print(x2) // expected-note {{captured here}} + // expected-note @-1 {{consuming use}} + // expected-note @-2 {{consuming use}} + } + print("foo") + } + f() +} + +public func closureAndClosureCaptureClassUseAfterConsume() { + var x2 = Klass() + // expected-error @-1 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-2 {{Usage of a move only type that the move checker does not know how to check!}} + x2 = Klass() + let f = { + let g = { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) + print(x2) + } + g() + } + f() +} + +public func closureAndClosureCaptureClassUseAfterConsume2() { + var x2 = Klass() + // expected-error @-1 {{Usage of a move only type that the move checker does not know how to check!}} + // expected-error @-2 {{Usage of a move only type that the move checker does not know how to check!}} + x2 = Klass() + let f = { + let g = { + classUseMoveOnlyWithoutEscaping(x2) + classConsume(x2) + print(x2) + } + g() + } + f() + print(x2) +} + + +public func closureAndClosureCaptureClassArgUseAfterConsume(_ x2: inout Klass) { + // expected-error @-1 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-2 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-3 {{'x2' consumed but not reinitialized before end of function}} + // expected-error @-4 {{'x2' consumed more than once}} + // expected-note @-5 {{'x2' is declared 'inout'}} + // expected-note @-6 {{'x2' is declared 'inout'}} + let f = { // expected-error {{escaping closure captures 'inout' parameter 'x2'}} + // expected-note @-1 {{consuming use}} + let g = { // expected-error {{escaping closure captures 'inout' parameter 'x2'}} + // expected-note @-1 {{consuming use}} + // expected-note @-2 {{captured indirectly by this call}} + classUseMoveOnlyWithoutEscaping(x2) + // expected-note @-1 {{captured here}} + // expected-note @-2 {{captured here}} + classConsume(x2) + // expected-note @-1 {{captured here}} + // expected-note @-2 {{captured here}} + // expected-note @-3 {{consuming use}} + print(x2) + // expected-note @-1 {{captured here}} + // expected-note @-2 {{captured here}} + // expected-note @-3 {{consuming use}} + // expected-note @-4 {{consuming use}} + } + g() + } + f() +} From e327848b67107ac33b1c3852980993f20f68b3c3 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Sun, 11 Sep 2022 21:04:03 -0700 Subject: [PATCH 25/37] Workaround MSVC overload resolution. --- include/swift/SIL/TypeLowering.h | 18 +++++------ lib/SIL/IR/TypeLowering.cpp | 52 ++++++++++++++------------------ 2 files changed, 32 insertions(+), 38 deletions(-) diff --git a/include/swift/SIL/TypeLowering.h b/include/swift/SIL/TypeLowering.h index 03b87d08f82c6..c388a61a21fa2 100644 --- a/include/swift/SIL/TypeLowering.h +++ b/include/swift/SIL/TypeLowering.h @@ -1250,15 +1250,15 @@ class TypeConverter { /// getTypeLowering(AbstractionPattern,Type,TypeExpansionContext). void verifyLowering(const TypeLowering &, AbstractionPattern origType, Type origSubstType, TypeExpansionContext forExpansion); - bool visitAggregateLeaves( - Lowering::AbstractionPattern origType, Type substType, - TypeExpansionContext context, - std::function>)> - isLeafAggregate, - std::function>)> - visit); + bool + visitAggregateLeaves(Lowering::AbstractionPattern origType, Type substType, + TypeExpansionContext context, + std::function)> + isLeafAggregate, + std::function)> + visit); #endif }; diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index 1ac0252017a0e..0106b3719200f 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2440,53 +2440,45 @@ bool TypeConverter::visitAggregateLeaves( Lowering::AbstractionPattern origType, Type substType, TypeExpansionContext context, std::function>)> + TaggedUnion)> isLeafAggregate, std::function>)> + TaggedUnion)> visit) { llvm::SmallSet, 16> visited; llvm::SmallVector< std::tuple, 16> worklist; auto insertIntoWorklist = - [&visited, - &worklist](Type substTy, AbstractionPattern origTy, - Optional> either) -> bool { + [&visited, &worklist](Type substTy, AbstractionPattern origTy, + TaggedUnion either) -> bool { ValueDecl *field; unsigned index; - if (either) { - if (either->isa()) { - field = either->get(); - index = UINT_MAX; - } else { - field = nullptr; - index = either->get(); - } + if (either.isa()) { + field = either.get(); + index = UINT_MAX; } else { field = nullptr; - index = UINT_MAX; + index = either.get(); } if (!visited.insert({substTy.getPointer(), field, index}).second) return false; worklist.push_back({substTy.getPointer(), origTy, field, index}); return true; }; - auto popFromWorklist = [&worklist]() - -> std::tuple>> { + auto popFromWorklist = + [&worklist]() -> std::tuple> { TypeBase *ty; AbstractionPattern origTy = AbstractionPattern::getOpaque(); - Optional> either; + TaggedUnion either((ValueDecl *)nullptr); ValueDecl *field; unsigned index = 0; std::tie(ty, origTy, field, index) = worklist.pop_back_val(); - if (field) { - either = TaggedUnion(field); - } else if (index != UINT_MAX) { + if (index != UINT_MAX) { either = TaggedUnion(index); } else { - either = llvm::None; + either = TaggedUnion(field); } return {ty->getCanonicalType(), origTy, either}; }; @@ -2494,11 +2486,11 @@ bool TypeConverter::visitAggregateLeaves( return ty->is() || ty->getEnumOrBoundGenericEnum() || ty->getStructOrBoundGenericStruct(); }; - insertIntoWorklist(substType, origType, llvm::None); + insertIntoWorklist(substType, origType, (ValueDecl *)nullptr); while (!worklist.empty()) { Type ty; AbstractionPattern origTy = AbstractionPattern::getOpaque(); - Optional> path; + TaggedUnion path((ValueDecl *)nullptr); std::tie(ty, origTy, path) = popFromWorklist(); if (isAggregate(ty) && !isLeafAggregate(ty, origTy, path)) { if (auto tupleTy = ty->getAs()) { @@ -2579,15 +2571,16 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, // The field's type is an aggregate. Treat it as a leaf if it // has a lifetime annotation. - // If it's the top-level type or a field of a tuple, there's no var + // If it's a field of a tuple or the top-level type, there's no value // decl on which to look for an attribute. It's a leaf iff the type // has a lifetime annotation. - if (!either || either->template isa()) + if (either.template isa() || + either.template get() == nullptr) return getLifetimeAnnotation(ty).isSome(); // It's a field of a struct or an enum. It's a leaf if the type // or the var decl has a lifetime annotation. - return either->template get() + return either.template get() ->getLifetimeAnnotation() .isSome() || getLifetimeAnnotation(ty); @@ -2612,7 +2605,8 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, // not lexical. The leaf must be annotated @_eagerMove. // Otherwise, the whole type would be lexical. - if (!either || either->template isa()) { + if (either.template isa() || + either.template get() == nullptr) { // There is no field decl that might be annotated @_eagerMove. The // field is @_eagerMove iff its type is annotated @_eagerMove. return getLifetimeAnnotation(ty) == LifetimeAnnotation::EagerMove; @@ -2621,7 +2615,7 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, // The field is non-trivial and the whole type is non-lexical. // That's fine as long as the field or its type is annotated // @_eagerMove. - return either->template get()->getLifetimeAnnotation() == + return either.template get()->getLifetimeAnnotation() == LifetimeAnnotation::EagerMove || getLifetimeAnnotation(ty) == LifetimeAnnotation::EagerMove; }); From e1ff0aa23565c937a9e955ce14c66e67ee6151d1 Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 12 Sep 2022 08:18:41 +0200 Subject: [PATCH 26/37] SmallProjectionPath: allow parsing an empty path If the string to parse doesn't start with a token which belongs to a projection path, just return an empty path instead of throwing an error. --- .../Sources/SIL/SmallProjectionPath.swift | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/SwiftCompilerSources/Sources/SIL/SmallProjectionPath.swift b/SwiftCompilerSources/Sources/SIL/SmallProjectionPath.swift index 8d54d6a60529c..626e5eb1c7e04 100644 --- a/SwiftCompilerSources/Sources/SIL/SmallProjectionPath.swift +++ b/SwiftCompilerSources/Sources/SIL/SmallProjectionPath.swift @@ -471,7 +471,7 @@ extension StringParser { mutating func parseProjectionPathFromSIL() throws -> SmallProjectionPath { var entries: [(SmallProjectionPath.FieldKind, Int)] = [] - repeat { + while true { if consume("**") { entries.append((.anything, 0)) } else if consume("c*") { @@ -497,12 +497,10 @@ extension StringParser { entries.append((.structField, idx)) } else if let tupleElemIdx = consumeInt() { entries.append((.tupleField, tupleElemIdx)) - } else { - try throwError("expected selection path component") + } else if !consume(".") { + return try createPath(from: entries) } - } while consume(".") - - return try createPath(from: entries) + } } private func createPath(from entries: [(SmallProjectionPath.FieldKind, Int)]) throws -> SmallProjectionPath { From 8e2e7a73c56b9a6568c371c1aad16937a8cec36d Mon Sep 17 00:00:00 2001 From: Erik Eckstein Date: Mon, 12 Sep 2022 08:24:47 +0200 Subject: [PATCH 27/37] SIL: make argument effects more readable in textual SIL So far, argument effects were printed in square brackets before the function name, e.g. ``` sil [escapes !%0.**, !%1, %1.c*.v** => %0.v**] @foo : $@convention(thin) (@guaranteed T) -> @out S { bb0(%0 : $*S, %1 : @guaranteed $T): ... ``` As we are adding more argument effects, this becomes unreadable. To make it more readable, print the effects after the opening curly brace, and print a separate line for each argument. E.g. ``` sil [ossa] @foo : $@convention(thin) (@guaranteed T) -> @out S { [%0: noescape **] [%1: noescape, escape c*.v** => %0.v**] bb0(%0 : $*S, %1 : @guaranteed $T): ... ``` --- .../Sources/SIL/Effects.swift | 118 ++++++++++----- .../Sources/SIL/Function.swift | 51 +++---- docs/SIL.rst | 56 ++++---- include/swift/SIL/SILBridging.h | 12 +- include/swift/SIL/SILFunction.h | 12 +- lib/SIL/IR/SILFunction.cpp | 31 ++-- lib/SIL/IR/SILFunctionBuilder.cpp | 2 +- lib/SIL/IR/SILPrinter.cpp | 36 +---- lib/SIL/Parser/ParseSIL.cpp | 135 ++++++++---------- lib/Serialization/DeserializeSIL.cpp | 7 +- lib/Serialization/ModuleFormat.h | 2 +- lib/Serialization/SILFormat.h | 1 + lib/Serialization/SerializeSIL.cpp | 7 +- test/SILGen/effectsattr.swift | 13 +- test/SILOptimizer/addr_escape_info.sil | 16 ++- test/SILOptimizer/cross-module-effects.swift | 107 +++++++++----- test/SILOptimizer/escape_info.sil | 86 ++++++++--- test/SILOptimizer/escape_info_objc.sil | 13 +- ...existential_specializer_indirect_class.sil | 8 +- test/SILOptimizer/function_effects.sil | 111 ++++++++++---- test/SILOptimizer/function_effects_objc.sil | 8 +- .../SILOptimizer/specialize_reabstraction.sil | 14 +- test/SILOptimizer/stack_promotion.sil | 9 +- 23 files changed, 520 insertions(+), 335 deletions(-) diff --git a/SwiftCompilerSources/Sources/SIL/Effects.swift b/SwiftCompilerSources/Sources/SIL/Effects.swift index ec022a3277892..2f399c0d39166 100644 --- a/SwiftCompilerSources/Sources/SIL/Effects.swift +++ b/SwiftCompilerSources/Sources/SIL/Effects.swift @@ -16,22 +16,22 @@ public struct ArgumentEffect : CustomStringConvertible, CustomReflectable { public typealias Path = SmallProjectionPath public enum Kind { - /// The selected argument value does not escape. + /// The argument value does not escape. /// /// Syntax examples: - /// !%0 // argument 0 does not escape - /// !%0.** // argument 0 and all transitively contained values do not escape + /// [%0: noescape] // argument 0 does not escape + /// [%0: noescape **] // argument 0 and all transitively contained values do not escape /// case notEscaping - /// The selected argument value escapes to the specified selection (= first payload). + /// The argument value escapes to the function return value. /// /// Syntax examples: - /// %0.s1 => %r // field 2 of argument 0 exclusively escapes via return. - /// %0.s1 -> %1 // field 2 of argument 0 - and other values - escape to argument 1. + /// [%0: escape s1 => %r] // field 2 of argument 0 exclusively escapes via return. + /// [%0: escape s1 -> %r] // field 2 of argument 0 - and other values - escape via return /// - /// The "exclusive" flag (= second payload) is true if only the selected argument escapes - /// to the specified selection, but nothing else escapes to it. + /// The "exclusive" flag (= second payload) is true if only the argument escapes, + /// but nothing else escapes to the return value. /// For example, "exclusive" is true for the following function: /// /// @_effect(escaping c => return) @@ -46,6 +46,14 @@ public struct ArgumentEffect : CustomStringConvertible, CustomReflectable { /// case escapingToReturn(Path, Bool) // toPath, exclusive + /// Like `escapingToReturn`, but the argument escapes to another argument. + /// + /// Example: The argument effects of + /// func argToArgEscape(_ r: inout Class, _ c: Class) { r = c } + /// + /// would be + /// [%1: escape => %0] // Argument 1 escapes to argument 0 + /// case escapingToArgument(Int, Path, Bool) // toArgumentIndex, toPath, exclusive } @@ -111,21 +119,28 @@ public struct ArgumentEffect : CustomStringConvertible, CustomReflectable { return argumentIndex == rhsArgIdx && rhsPath.matches(pattern: pathPattern) } - public var description: String { - let selectedArg = "%\(argumentIndex)" + (pathPattern.isEmpty ? "" : ".\(pathPattern)") + public var headerDescription: String { + "%\(argumentIndex)\(isDerived ? "" : "!"): " + } + public var bodyDescription: String { + let patternStr = pathPattern.isEmpty ? "" : " \(pathPattern)" switch kind { case .notEscaping: - return "!\(selectedArg)" + return "noescape\(patternStr)" case .escapingToReturn(let toPath, let exclusive): - let pathStr = (toPath.isEmpty ? "" : ".\(toPath)") - return "\(selectedArg) \(exclusive ? "=>" : "->") %r\(pathStr)" + let toPathStr = (toPath.isEmpty ? "" : ".\(toPath)") + return "escape\(patternStr) \(exclusive ? "=>" : "->") %r\(toPathStr)" case .escapingToArgument(let toArgIdx, let toPath, let exclusive): - let pathStr = (toPath.isEmpty ? "" : ".\(toPath)") - return "\(selectedArg) \(exclusive ? "=>" : "->") %\(toArgIdx)\(pathStr)" + let toPathStr = (toPath.isEmpty ? "" : ".\(toPath)") + return "escape\(patternStr) \(exclusive ? "=>" : "->") %\(toArgIdx)\(toPathStr)" } } + public var description: String { + headerDescription + bodyDescription + } + public var customMirror: Mirror { Mirror(self, children: []) } } @@ -164,8 +179,27 @@ public struct FunctionEffects : CustomStringConvertible, CustomReflectable { argumentEffects = argumentEffects.filter { !$0.isDerived } } + public var argumentEffectsDescription: String { + var currentArgIdx = -1 + var currentIsDerived = false + var result = "" + for effect in argumentEffects { + if effect.argumentIndex != currentArgIdx || effect.isDerived != currentIsDerived { + if currentArgIdx >= 0 { result += "]\n" } + result += "[\(effect.headerDescription)" + currentArgIdx = effect.argumentIndex + currentIsDerived = effect.isDerived + } else { + result += ", " + } + result += effect.bodyDescription + } + if currentArgIdx >= 0 { result += "]\n" } + return result + } + public var description: String { - return "[" + argumentEffects.map { $0.description }.joined(separator: ", ") + "]" + return argumentEffectsDescription } public var customMirror: Mirror { Mirror(self, children: []) } @@ -237,24 +271,37 @@ extension StringParser { return ArgumentEffect.Path() } - mutating func parseEffectFromSIL(for function: Function, isDerived: Bool) throws -> ArgumentEffect { - if consume("!") { - let argIdx = try parseArgumentIndexFromSIL() - let path = try parsePathPatternFromSIL() - return ArgumentEffect(.notEscaping, argumentIndex: argIdx, pathPattern: path, isDerived: isDerived) + mutating func parseEffectsFromSIL(to effects: inout FunctionEffects) throws { + let argumentIndex = try parseArgumentIndexFromSIL() + let isDerived = !consume("!") + if !consume(":") { + try throwError("expected ':'") } - let fromArgIdx = try parseArgumentIndexFromSIL() - let fromPath = try parsePathPatternFromSIL() - let exclusive = try parseEscapingArrow() - if consume("%r") { - let toPath = try parsePathPatternFromSIL() - return ArgumentEffect(.escapingToReturn(toPath, exclusive), - argumentIndex: fromArgIdx, pathPattern: fromPath, isDerived: isDerived) + repeat { + let effect = try parseEffectFromSIL(argumentIndex: argumentIndex, isDerived: isDerived) + effects.argumentEffects.append(effect) + } while consume(",") + } + + mutating func parseEffectFromSIL(argumentIndex: Int, isDerived: Bool) throws -> ArgumentEffect { + if consume("noescape") { + let path = try parseProjectionPathFromSIL() + return ArgumentEffect(.notEscaping, argumentIndex: argumentIndex, pathPattern: path, isDerived: isDerived) + } + if consume("escape") { + let fromPath = try parseProjectionPathFromSIL() + let exclusive = try parseEscapingArrow() + if consume("%r") { + let toPath = consume(".") ? try parseProjectionPathFromSIL() : ArgumentEffect.Path() + return ArgumentEffect(.escapingToReturn(toPath, exclusive), + argumentIndex: argumentIndex, pathPattern: fromPath, isDerived: isDerived) + } + let toArgIdx = try parseArgumentIndexFromSIL() + let toPath = consume(".") ? try parseProjectionPathFromSIL() : ArgumentEffect.Path() + return ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive), + argumentIndex: argumentIndex, pathPattern: fromPath, isDerived: isDerived) } - let toArgIdx = try parseArgumentIndexFromSIL() - let toPath = try parsePathPatternFromSIL() - return ArgumentEffect(.escapingToArgument(toArgIdx, toPath, exclusive), - argumentIndex: fromArgIdx, pathPattern: fromPath, isDerived: isDerived) + try throwError("unknown effect") } mutating func parseArgumentIndexFromSIL() throws -> Int { @@ -267,13 +314,6 @@ extension StringParser { try throwError("expected parameter") } - mutating func parsePathPatternFromSIL() throws -> ArgumentEffect.Path { - if consume(".") { - return try parseProjectionPathFromSIL() - } - return ArgumentEffect.Path() - } - private mutating func parseEscapingArrow() throws -> Bool { if consume("=>") { return true } if consume("->") { return false } diff --git a/SwiftCompilerSources/Sources/SIL/Function.swift b/SwiftCompilerSources/Sources/SIL/Function.swift index 3a0d720f15b76..63c1d53cef3e2 100644 --- a/SwiftCompilerSources/Sources/SIL/Function.swift +++ b/SwiftCompilerSources/Sources/SIL/Function.swift @@ -133,27 +133,36 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash }, // writeFn { (f: BridgedFunction, os: BridgedOStream, idx: Int) in - let s = f.function.effects.argumentEffects[idx].description + let s: String + if idx >= 0 { + s = f.function.effects.argumentEffects[idx].bodyDescription + } else { + s = f.function.effects.argumentEffectsDescription + } s._withStringRef { OStream_write(os, $0) } }, // parseFn: - { (f: BridgedFunction, str: llvm.StringRef, fromSIL: Int, isDerived: Int, paramNames: BridgedArrayRef) -> BridgedParsingError in + { (f: BridgedFunction, str: llvm.StringRef, fromSIL: Int, argumentIndex: Int, isDerived: Int, paramNames: BridgedArrayRef) -> BridgedParsingError in do { var parser = StringParser(str.string) - let effect: ArgumentEffect - if fromSIL != 0 { - effect = try parser.parseEffectFromSIL(for: f.function, isDerived: isDerived != 0) + + if fromSIL != 0 && argumentIndex < 0 { + try parser.parseEffectsFromSIL(to: &f.function.effects) } else { - let paramToIdx = paramNames.withElements(ofType: llvm.StringRef.self) { - (buffer: UnsafeBufferPointer) -> Dictionary in - let keyValPairs = buffer.enumerated().lazy.map { ($0.1.string, $0.0) } - return Dictionary(uniqueKeysWithValues: keyValPairs) + let effect: ArgumentEffect + if fromSIL != 0 { + effect = try parser.parseEffectFromSIL(argumentIndex: argumentIndex, isDerived: isDerived != 0) + } else { + let paramToIdx = paramNames.withElements(ofType: llvm.StringRef.self) { + (buffer: UnsafeBufferPointer) -> Dictionary in + let keyValPairs = buffer.enumerated().lazy.map { ($0.1.string, $0.0) } + return Dictionary(uniqueKeysWithValues: keyValPairs) + } + effect = try parser.parseEffectFromSource(for: f.function, params: paramToIdx) } - effect = try parser.parseEffectFromSource(for: f.function, params: paramToIdx) + f.function.effects.argumentEffects.append(effect) } if !parser.isEmpty() { try parser.throwError("syntax error") } - - f.function.effects.argumentEffects.append(effect) } catch let error as ParsingError { return BridgedParsingError(message: error.message.utf8Start, position: error.position) } catch { @@ -179,20 +188,14 @@ final public class Function : CustomStringConvertible, HasShortDescription, Hash resultArgDelta: destResultArgs - srcResultArgs) return 1 }, - // getEffectFlags - { (f: BridgedFunction, idx: Int) -> Int in + // getEffectInfo + { (f: BridgedFunction, idx: Int) -> BridgedEffectInfo in let argEffects = f.function.effects.argumentEffects - if idx >= argEffects.count { return 0 } - let effect = argEffects[idx] - var flags = 0 - switch effect.kind { - case .notEscaping, .escapingToArgument, .escapingToReturn: - flags |= Int(EffectsFlagEscape) - } - if effect.isDerived { - flags |= Int(EffectsFlagDerived) + if idx >= argEffects.count { + return BridgedEffectInfo(argumentIndex: -1, isDerived: false) } - return flags + let effect = argEffects[idx] + return BridgedEffectInfo(argumentIndex: effect.argumentIndex, isDerived: effect.isDerived) } ) } diff --git a/docs/SIL.rst b/docs/SIL.rst index d66bd0cd256fe..7de208427b1df 100644 --- a/docs/SIL.rst +++ b/docs/SIL.rst @@ -956,7 +956,7 @@ Functions decl ::= sil-function sil-function ::= 'sil' sil-linkage? sil-function-attribute+ sil-function-name ':' sil-type - '{' sil-basic-block+ '}' + '{' argument-effect* sil-basic-block* '}' sil-function-name ::= '@' [A-Za-z_0-9]+ SIL functions are defined with the ``sil`` keyword. SIL function names @@ -966,6 +966,8 @@ and is usually the mangled name of the originating Swift declaration. The ``sil`` syntax declares the function's name and SIL type, and defines the body of the function inside braces. The declared type must be a function type, which may be generic. +If there are no `sil-basic-block`s contained in the body, the function +is an external declaration. Function Attributes @@ -1094,30 +1096,6 @@ from the command line. sil-function-effects ::= 'releasenone' The specified memory effects of the function. -:: - - sil-function-attribute ::= '[' 'escapes' escape-list ']' - sil-function-attribute ::= '[' 'defined_escapes' escape-list ']' - escape-list ::= (escape-list ',')? escape - escape ::= '!' arg-selection // not-escaping - escape ::= arg-selection '=>' arg-selection // exclusive escaping - escape ::= arg-selection '->' arg-selection // not-exclusive escaping - arg-selection ::= arg-or-return ('.' projection-path)? - arg-or-return ::= '%' [0-9]+ - arg-or-return ::= '%r' - projection-path ::= (projection-path '.')? path-component - path-component ::= 's' [0-9]+ // struct field - path-component ::= 'c' [0-9]+ // class field - path-component ::= 'ct' // class tail element - path-component ::= 'e' [0-9]+ // enum case - path-component ::= [0-9]+ // tuple element - path-component ::= 'v**' // any value fields - path-component ::= 'c*' // any class field - path-component ::= '**' // anything - -The escaping effects for function arguments. For details see the documentation -in ``SwiftCompilerSources/Sources/SIL/Effects.swift``. - :: sil-function-attribute ::= '[_semantics "' [A-Za-z._0-9]+ '"]' @@ -1147,6 +1125,34 @@ Specifies the performance constraints for the function, which defines which type of runtime functions are allowed to be called from the function. +Argument Effects +```````````````` + +The effects for function arguments. For details see the documentation +in ``SwiftCompilerSources/Sources/SIL/Effects.swift``. +:: + + argument-effect ::= '[' argument-name defined-effect? ':' effect (',' effect)*]' + argument-name ::= '%' [0-9]+ + defined-effect ::= '!' // the effect is defined in the source code and not + // derived by the optimizer + + effect ::= 'noescape' projection-path? + effect ::= 'escape' projection-path? '=>' arg-or-return // exclusive escape + effect ::= 'escape' projection-path? '->' arg-or-return // not-exclusive escape + arg-or-return ::= argument-name ('.' projection-path)? + arg-or-return ::= '%r' ('.' projection-path)? + + projection-path ::= path-component ('.' path-component)* + path-component ::= 's' [0-9]+ // struct field + path-component ::= 'c' [0-9]+ // class field + path-component ::= 'ct' // class tail element + path-component ::= 'e' [0-9]+ // enum case + path-component ::= [0-9]+ // tuple element + path-component ::= 'v**' // any value fields + path-component ::= 'c*' // any class field + path-component ::= '**' // anything + Basic Blocks ~~~~~~~~~~~~ :: diff --git a/include/swift/SIL/SILBridging.h b/include/swift/SIL/SILBridging.h index d1a9fe91f25d2..b5f115490f77f 100644 --- a/include/swift/SIL/SILBridging.h +++ b/include/swift/SIL/SILBridging.h @@ -217,9 +217,9 @@ typedef enum { #include "swift/AST/Builtins.def" } BridgedBuiltinID; -enum { - EffectsFlagEscape = 0x1, - EffectsFlagDerived = 0x2 +struct BridgedEffectInfo { + SwiftInt argumentIndex; + bool isDerived; }; void registerBridgedClass(llvm::StringRef className, SwiftMetatype metatype); @@ -231,17 +231,17 @@ typedef void (* _Nonnull FunctionWriteFn)(BridgedFunction, BridgedOStream, SwiftInt); typedef BridgedParsingError (*_Nonnull FunctionParseFn)(BridgedFunction, llvm::StringRef, - SwiftInt, SwiftInt, + SwiftInt, SwiftInt, SwiftInt, BridgedArrayRef); typedef SwiftInt (* _Nonnull FunctionCopyEffectsFn)(BridgedFunction, BridgedFunction); -typedef SwiftInt (* _Nonnull FunctionGetEffectFlagsFn)(BridgedFunction, SwiftInt); +typedef BridgedEffectInfo (* _Nonnull FunctionGetEffectInfoFn)(BridgedFunction, SwiftInt); void Function_register(SwiftMetatype metatype, FunctionRegisterFn initFn, FunctionRegisterFn destroyFn, FunctionWriteFn writeFn, FunctionParseFn parseFn, FunctionCopyEffectsFn copyEffectsFn, - FunctionGetEffectFlagsFn hasEffectsFn); + FunctionGetEffectInfoFn effectInfoFn); SwiftInt PassContext_continueWithNextSubpassRun(BridgedPassContext passContext, OptionalBridgedInstruction inst); diff --git a/include/swift/SIL/SILFunction.h b/include/swift/SIL/SILFunction.h index 13d409418a028..392800915e3b4 100644 --- a/include/swift/SIL/SILFunction.h +++ b/include/swift/SIL/SILFunction.h @@ -1031,18 +1031,16 @@ class SILFunction EffectsKindAttr = unsigned(E); } - enum class ArgEffectKind { - Unknown, - Escape - }; - std::pair parseEffects(StringRef attrs, bool fromSIL, - bool isDerived, + int argumentIndex, bool isDerived, ArrayRef paramNames); void writeEffect(llvm::raw_ostream &OS, int effectIdx) const; + void writeEffects(llvm::raw_ostream &OS) const { + writeEffect(OS, -1); + } void copyEffects(SILFunction *from); bool hasArgumentEffects() const; - void visitArgEffects(std::function c) const; + void visitArgEffects(std::function c) const; Purpose getSpecialPurpose() const { return specialPurpose; } diff --git a/lib/SIL/IR/SILFunction.cpp b/lib/SIL/IR/SILFunction.cpp index 05a460be38d1c..858acae15e1b1 100644 --- a/lib/SIL/IR/SILFunction.cpp +++ b/lib/SIL/IR/SILFunction.cpp @@ -135,7 +135,7 @@ static FunctionRegisterFn destroyFunction = nullptr; static FunctionWriteFn writeFunction = nullptr; static FunctionParseFn parseFunction = nullptr; static FunctionCopyEffectsFn copyEffectsFunction = nullptr; -static FunctionGetEffectFlagsFn getEffectFlagsFunction = nullptr; +static FunctionGetEffectInfoFn getEffectInfoFunction = nullptr; SILFunction::SILFunction( SILModule &Module, SILLinkage Linkage, StringRef Name, @@ -907,22 +907,23 @@ void Function_register(SwiftMetatype metatype, FunctionRegisterFn initFn, FunctionRegisterFn destroyFn, FunctionWriteFn writeFn, FunctionParseFn parseFn, FunctionCopyEffectsFn copyEffectsFn, - FunctionGetEffectFlagsFn getEffectFlagsFn) { + FunctionGetEffectInfoFn effectInfoFn) { functionMetatype = metatype; initFunction = initFn; destroyFunction = destroyFn; writeFunction = writeFn; parseFunction = parseFn; copyEffectsFunction = copyEffectsFn; - getEffectFlagsFunction = getEffectFlagsFn; + getEffectInfoFunction = effectInfoFn; } std::pair SILFunction:: -parseEffects(StringRef attrs, bool fromSIL, bool isDerived, +parseEffects(StringRef attrs, bool fromSIL, int argumentIndex, bool isDerived, ArrayRef paramNames) { if (parseFunction) { BridgedParsingError error = parseFunction( - {this}, attrs, (SwiftInt)fromSIL, (SwiftInt)isDerived, + {this}, attrs, (SwiftInt)fromSIL, + (SwiftInt)argumentIndex, (SwiftInt)isDerived, {(const unsigned char *)paramNames.data(), paramNames.size()}); return {(const char *)error.message, (int)error.position}; } @@ -942,25 +943,25 @@ void SILFunction::copyEffects(SILFunction *from) { } bool SILFunction::hasArgumentEffects() const { - if (getEffectFlagsFunction) { - return getEffectFlagsFunction({const_cast(this)}, 0) != 0; + if (getEffectInfoFunction) { + BridgedFunction f = {const_cast(this)}; + return getEffectInfoFunction(f, 0).argumentIndex >= 0; } return false; } void SILFunction:: -visitArgEffects(std::function c) const { - if (!getEffectFlagsFunction) +visitArgEffects(std::function c) const { + if (!getEffectInfoFunction) return; int idx = 0; BridgedFunction bridgedFn = {const_cast(this)}; - while (int flags = getEffectFlagsFunction(bridgedFn, idx)) { - ArgEffectKind kind = ArgEffectKind::Unknown; - if (flags & EffectsFlagEscape) - kind = ArgEffectKind::Escape; - - c(idx, (flags & EffectsFlagDerived) != 0, kind); + while (true) { + BridgedEffectInfo ei = getEffectInfoFunction(bridgedFn, idx); + if (ei.argumentIndex < 0) + return; + c(idx, ei.argumentIndex, ei.isDerived); idx++; } } diff --git a/lib/SIL/IR/SILFunctionBuilder.cpp b/lib/SIL/IR/SILFunctionBuilder.cpp index 3461a04ca9fd1..e45330d7d5554 100644 --- a/lib/SIL/IR/SILFunctionBuilder.cpp +++ b/lib/SIL/IR/SILFunctionBuilder.cpp @@ -140,7 +140,7 @@ void SILFunctionBuilder::addFunctionAttributes( } for (const EffectsAttr *effectsAttr : llvm::reverse(customEffects)) { auto error = F->parseEffects(effectsAttr->getCustomString(), - /*fromSIL*/ false, /*isDerived*/ false, paramNames); + /*fromSIL*/ false, /*argumentIndex*/ -1, /*isDerived*/ false, paramNames); if (error.first) { SourceLoc loc = effectsAttr->getCustomStringLocation(); if (loc.isValid()) diff --git a/lib/SIL/IR/SILPrinter.cpp b/lib/SIL/IR/SILPrinter.cpp index 8b3d4a2402cf5..ac519bb3fb8e0 100644 --- a/lib/SIL/IR/SILPrinter.cpp +++ b/lib/SIL/IR/SILPrinter.cpp @@ -3050,36 +3050,6 @@ void SILFunction::print(SILPrintContext &PrintCtx) const { else if (getEffectsKind() == EffectsKind::ReleaseNone) OS << "[releasenone] "; - llvm::SmallVector definedEscapesIndices; - llvm::SmallVector escapesIndices; - visitArgEffects([&](int effectIdx, bool isDerived, ArgEffectKind kind) { - if (kind == ArgEffectKind::Escape) { - if (isDerived) { - escapesIndices.push_back(effectIdx); - } else { - definedEscapesIndices.push_back(effectIdx); - } - } - }); - if (!definedEscapesIndices.empty()) { - OS << "[defined_escapes "; - for (int effectIdx : definedEscapesIndices) { - if (effectIdx > 0) - OS << ", "; - writeEffect(OS, effectIdx); - } - OS << "] "; - } - if (!escapesIndices.empty()) { - OS << "[escapes "; - for (int effectIdx : escapesIndices) { - if (effectIdx > 0) - OS << ", "; - writeEffect(OS, effectIdx); - } - OS << "] "; - } - if (auto *replacedFun = getDynamicallyReplacedFunction()) { OS << "[dynamic_replacement_for \""; OS << replacedFun->getName(); @@ -3137,10 +3107,16 @@ void SILFunction::print(SILPrintContext &PrintCtx) const { OS << " !function_entry_count(" << eCount.getValue() << ")"; } OS << " {\n"; + + writeEffects(OS); SILPrinter(PrintCtx, sugaredTypeNames.empty() ? nullptr : &sugaredTypeNames) .print(this); OS << "} // end sil function '" << getName() << '\''; + } else if (hasArgumentEffects()) { + OS << " {\n"; + writeEffects(OS); + OS << "} // end sil function '" << getName() << '\''; } OS << "\n\n"; diff --git a/lib/SIL/Parser/ParseSIL.cpp b/lib/SIL/Parser/ParseSIL.cpp index 5aea406c0c6e6..bf02b4daf6e22 100644 --- a/lib/SIL/Parser/ParseSIL.cpp +++ b/lib/SIL/Parser/ParseSIL.cpp @@ -960,12 +960,6 @@ void SILParser::convertRequirements(ArrayRef From, } } -struct ArgEffectLoc { - SourceLoc start; - SourceLoc end; - bool isDerived; -}; - static bool parseDeclSILOptional(bool *isTransparent, IsSerialized_t *isSerialized, bool *isCanonical, @@ -990,7 +984,6 @@ static bool parseDeclSILOptional(bool *isTransparent, SmallVectorImpl *SpecAttrs, ValueDecl **ClangDecl, EffectsKind *MRK, - llvm::SmallVectorImpl *argEffectLocs, SILParser &SP, SILModule &M) { while (SP.P.consumeIf(tok::l_square)) { if (isLet && SP.P.Tok.is(tok::kw_let)) { @@ -1216,20 +1209,6 @@ static bool parseDeclSILOptional(bool *isTransparent, SP.P.parseToken(tok::r_square, diag::expected_in_attribute_list); continue; - } else if (argEffectLocs && (SP.P.Tok.getText() == "escapes" || - SP.P.Tok.getText() == "defined_escapes")) { - bool isDerived = SP.P.Tok.getText() == "escapes"; - SP.P.consumeToken(); - do { - SourceLoc effectsStart = SP.P.Tok.getLoc(); - while (SP.P.Tok.isNot(tok::r_square, tok::comma, tok::eof)) { - SP.P.consumeToken(); - } - SourceLoc effectsEnd = SP.P.Tok.getLoc(); - argEffectLocs->push_back({effectsStart, effectsEnd, isDerived}); - } while (SP.P.consumeIf(tok::comma)); - SP.P.consumeToken(); - continue; } else { SP.P.diagnose(SP.P.Tok, diag::expected_in_attribute_list); return true; @@ -6548,7 +6527,6 @@ bool SILParserState::parseDeclSIL(Parser &P) { SmallVector SpecAttrs; ValueDecl *ClangDecl = nullptr; EffectsKind MRK = EffectsKind::Unspecified; - llvm::SmallVector argEffectLocs; SILFunction *DynamicallyReplacedFunction = nullptr; SILFunction *AdHocWitnessFunction = nullptr; Identifier objCReplacementFor; @@ -6560,7 +6538,7 @@ bool SILParserState::parseDeclSIL(Parser &P) { &inlineStrategy, &optimizationMode, &perfConstr, nullptr, &isWeakImported, &needStackProtection, &availability, &isWithoutActuallyEscapingThunk, &Semantics, - &SpecAttrs, &ClangDecl, &MRK, &argEffectLocs, FunctionState, M) || + &SpecAttrs, &ClangDecl, &MRK, FunctionState, M) || P.parseToken(tok::at_sign, diag::expected_sil_function_name) || P.parseIdentifier(FnName, FnNameLoc, /*diagnoseDollarPrefix=*/false, diag::expected_sil_function_name) || @@ -6608,20 +6586,6 @@ bool SILParserState::parseDeclSIL(Parser &P) { FunctionState.F->setPerfConstraints(perfConstr); FunctionState.F->setEffectsKind(MRK); - for (ArgEffectLoc aeLoc : argEffectLocs) { - StringRef effectsStr = P.SourceMgr.extractText( - CharSourceRange(P.SourceMgr, aeLoc.start, aeLoc.end)); - auto error = FunctionState.F->parseEffects(effectsStr, /*fromSIL*/true, - /*isDerived*/ aeLoc.isDerived, - {}); - if (error.first) { - SourceLoc loc = aeLoc.start; - if (loc.isValid()) - loc = loc.getAdvancedLoc(error.second); - P.diagnose(loc, diag::error_in_effects_attribute, StringRef(error.first)); - return true; - } - } if (ClangDecl) FunctionState.F->setClangNodeOwner(ClangDecl); for (auto &Attr : Semantics) { @@ -6633,45 +6597,68 @@ bool SILParserState::parseDeclSIL(Parser &P) { SourceLoc LBraceLoc = P.Tok.getLoc(); if (P.consumeIf(tok::l_brace)) { - isDefinition = true; - - FunctionState.ContextGenericSig = GenericSig; - FunctionState.ContextGenericParams = GenericParams; - FunctionState.F->setGenericEnvironment(GenericSig.getGenericEnvironment()); - - if (GenericSig && !SpecAttrs.empty()) { - for (auto &Attr : SpecAttrs) { - SmallVector requirements; - // Resolve types and convert requirements. - FunctionState.convertRequirements(Attr.requirements, requirements); - auto *fenv = FunctionState.F->getGenericEnvironment(); - auto genericSig = buildGenericSignature(P.Context, - fenv->getGenericSignature(), - /*addedGenericParams=*/{ }, - std::move(requirements)); - FunctionState.F->addSpecializeAttr(SILSpecializeAttr::create( - FunctionState.F->getModule(), genericSig, Attr.exported, - Attr.kind, Attr.target, Attr.spiGroupID, Attr.spiModule, Attr.availability)); + while (P.consumeIf(tok::l_square)) { + SourceLoc effectsStart = P.Tok.getLoc(); + while (P.Tok.isNot(tok::r_square, tok::eof)) { + P.consumeToken(); + } + SourceLoc effectsEnd = P.Tok.getLoc(); + P.consumeToken(); + StringRef effectsStr = P.SourceMgr.extractText( + CharSourceRange(P.SourceMgr, effectsStart, effectsEnd)); + + auto error = FunctionState.F->parseEffects(effectsStr, /*fromSIL*/true, + /*argumentIndex*/ -1, /*isDerived*/ false, + {}); + if (error.first) { + SourceLoc loc = effectsStart; + if (loc.isValid()) + loc = loc.getAdvancedLoc(error.second); + P.diagnose(loc, diag::error_in_effects_attribute, StringRef(error.first)); + return true; } } + if (!P.consumeIf(tok::r_brace)) { + isDefinition = true; + + FunctionState.ContextGenericSig = GenericSig; + FunctionState.ContextGenericParams = GenericParams; + FunctionState.F->setGenericEnvironment(GenericSig.getGenericEnvironment()); + + if (GenericSig && !SpecAttrs.empty()) { + for (auto &Attr : SpecAttrs) { + SmallVector requirements; + // Resolve types and convert requirements. + FunctionState.convertRequirements(Attr.requirements, requirements); + auto *fenv = FunctionState.F->getGenericEnvironment(); + auto genericSig = buildGenericSignature(P.Context, + fenv->getGenericSignature(), + /*addedGenericParams=*/{ }, + std::move(requirements)); + FunctionState.F->addSpecializeAttr(SILSpecializeAttr::create( + FunctionState.F->getModule(), genericSig, Attr.exported, + Attr.kind, Attr.target, Attr.spiGroupID, Attr.spiModule, Attr.availability)); + } + } - // Parse the basic block list. - SILBuilder B(*FunctionState.F); + // Parse the basic block list. + SILBuilder B(*FunctionState.F); - do { - if (FunctionState.parseSILBasicBlock(B)) - return true; - } while (P.Tok.isNot(tok::r_brace) && P.Tok.isNot(tok::eof)); + do { + if (FunctionState.parseSILBasicBlock(B)) + return true; + } while (P.Tok.isNot(tok::r_brace) && P.Tok.isNot(tok::eof)); - SourceLoc RBraceLoc; - P.parseMatchingToken(tok::r_brace, RBraceLoc, diag::expected_sil_rbrace, - LBraceLoc); + SourceLoc RBraceLoc; + P.parseMatchingToken(tok::r_brace, RBraceLoc, diag::expected_sil_rbrace, + LBraceLoc); - // Check that there are no unresolved forward definitions of opened - // archetypes. - if (M.hasUnresolvedOpenedArchetypeDefinitions()) - llvm_unreachable( - "All forward definitions of opened archetypes should be resolved"); + // Check that there are no unresolved forward definitions of opened + // archetypes. + if (M.hasUnresolvedOpenedArchetypeDefinitions()) + llvm_unreachable( + "All forward definitions of opened archetypes should be resolved"); + } } FunctionState.F->setLinkage(resolveSILLinkage(FnLinkage, isDefinition)); @@ -6791,7 +6778,7 @@ bool SILParserState::parseSILGlobal(Parser &P) { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, &isLet, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, nullptr, State, M) || + nullptr, nullptr, nullptr, State, M) || P.parseToken(tok::at_sign, diag::expected_sil_value_name) || P.parseIdentifier(GlobalName, NameLoc, /*diagnoseDollarPrefix=*/false, diag::expected_sil_value_name) || @@ -6842,7 +6829,7 @@ bool SILParserState::parseSILProperty(Parser &P) { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, SP, M)) + nullptr, SP, M)) return true; ValueDecl *VD; @@ -6911,7 +6898,7 @@ bool SILParserState::parseSILVTable(Parser &P) { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, VTableState, M)) + nullptr, nullptr, VTableState, M)) return true; // Parse the class name. @@ -7431,7 +7418,7 @@ bool SILParserState::parseSILWitnessTable(Parser &P) { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, WitnessState, M)) + nullptr, nullptr, WitnessState, M)) return true; // Parse the protocol conformance. diff --git a/lib/Serialization/DeserializeSIL.cpp b/lib/Serialization/DeserializeSIL.cpp index 5dafd8df5708f..515e16fab8ada 100644 --- a/lib/Serialization/DeserializeSIL.cpp +++ b/lib/Serialization/DeserializeSIL.cpp @@ -734,10 +734,13 @@ SILDeserializer::readSILFunctionChecked(DeclID FID, SILFunction *existingFn, if (kind == SIL_ARG_EFFECTS_ATTR) { IdentifierID effectID; unsigned isDerived; - SILArgEffectsAttrLayout::readRecord(scratch, effectID, isDerived); + unsigned argumentIndex; + SILArgEffectsAttrLayout::readRecord(scratch, effectID, + argumentIndex, isDerived); if (shouldAddEffectAttrs) { StringRef effectStr = MF->getIdentifierText(effectID); - auto error = fn->parseEffects(effectStr, /*fromSIL*/ true, isDerived, {}); + auto error = fn->parseEffects(effectStr, /*fromSIL*/ true, + argumentIndex, isDerived, {}); (void)error; assert(!error.first && "effects deserialization error"); } diff --git a/lib/Serialization/ModuleFormat.h b/lib/Serialization/ModuleFormat.h index e92ddc0fe5ed8..82f52376d7773 100644 --- a/lib/Serialization/ModuleFormat.h +++ b/lib/Serialization/ModuleFormat.h @@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0; /// describe what change you made. The content of this comment isn't important; /// it just ensures a conflict if two people change the module format. /// Don't worry about adhering to the 80-column limit for this line. -const uint16_t SWIFTMODULE_VERSION_MINOR = 709; // needsStackProtection instruction flags +const uint16_t SWIFTMODULE_VERSION_MINOR = 710; // new argument-effects format /// A standard hash seed used for all string hashes in a serialized module. /// diff --git a/lib/Serialization/SILFormat.h b/lib/Serialization/SILFormat.h index 0dc2c789ef459..aa0ecfa4e5f96 100644 --- a/lib/Serialization/SILFormat.h +++ b/lib/Serialization/SILFormat.h @@ -308,6 +308,7 @@ namespace sil_block { using SILArgEffectsAttrLayout = BCRecordLayout, // argumentIndex BCFixed<1> // isDerived >; diff --git a/lib/Serialization/SerializeSIL.cpp b/lib/Serialization/SerializeSIL.cpp index db987882079fc..cada3067d9ee7 100644 --- a/lib/Serialization/SerializeSIL.cpp +++ b/lib/Serialization/SerializeSIL.cpp @@ -482,7 +482,7 @@ void SILSerializer::writeSILFunction(const SILFunction &F, bool DeclOnly) { auto resilience = F.getModule().getSwiftModule()->getResilienceStrategy(); F.visitArgEffects( - [&](int effectIdx, bool isDerived, SILFunction::ArgEffectKind) { + [&](int effectIdx, int argumentIndex, bool isDerived) { if (isDerived && resilience == ResilienceStrategy::Resilient) return; numAttrs++; @@ -512,7 +512,7 @@ void SILSerializer::writeSILFunction(const SILFunction &F, bool DeclOnly) { genericSigID, clangNodeOwnerID, parentModuleID, SemanticsIDs); F.visitArgEffects( - [&](int effectIdx, bool isDerived, SILFunction::ArgEffectKind) { + [&](int effectIdx, int argumentIndex, bool isDerived) { if (isDerived && resilience == ResilienceStrategy::Resilient) return; @@ -524,7 +524,8 @@ void SILSerializer::writeSILFunction(const SILFunction &F, bool DeclOnly) { unsigned abbrCode = SILAbbrCodes[SILArgEffectsAttrLayout::Code]; SILArgEffectsAttrLayout::emitRecord( - Out, ScratchRecord, abbrCode, effectsStrID, (unsigned)isDerived); + Out, ScratchRecord, abbrCode, + effectsStrID, (unsigned)argumentIndex, (unsigned)isDerived); }); if (NoBody) diff --git a/test/SILGen/effectsattr.swift b/test/SILGen/effectsattr.swift index 11b4f4dd7662e..7b2dda1cd46d6 100644 --- a/test/SILGen/effectsattr.swift +++ b/test/SILGen/effectsattr.swift @@ -14,16 +14,23 @@ //CHECK: [releasenone] [ossa] @func4 @_effects(releasenone) @_silgen_name("func4") func func4() { } -//CHECK: [defined_escapes !%0.**] [ossa] @func5 +//CHECK-LABEL: sil hidden [ossa] @func5 +//CHECK-NEXT: [%0!: noescape **] +//CHECK-NEXT: {{^[^[]}} @_effects(notEscaping t.**) @_silgen_name("func5") func func5(_ t: T) { } -//CHECK: [defined_escapes %1.v**.c* => %0.v**] [ossa] @func6 +//CHECK-LABEL: sil hidden [ossa] @func6 +//CHECK-NEXT: [%1!: escape v**.c* => %0.v**] +//CHECK-NEXT: {{^[^[]}} @_effects(escaping t.value**.class* => return.value**) @_silgen_name("func6") func func6(_ t: T) -> T { } struct Mystr { var sf: T - //CHECK: [defined_escapes !%3.**, %2.v** -> %1.s0.v**] [ossa] @func7 + //CHECK-LABEL: sil hidden [ossa] @func7 + //CHECK-NEXT: [%3!: noescape **] + //CHECK-NEXT: [%2!: escape v** -> %1.s0.v**] + //CHECK-NEXT: {{^[^[]}} @_effects(notEscaping self.**) @_effects(escaping s.value** -> t.sf.value**) @_silgen_name("func7") func func7(_ t: inout Mystr, _ s: T) -> T { } diff --git a/test/SILOptimizer/addr_escape_info.sil b/test/SILOptimizer/addr_escape_info.sil index d3478834af8fe..306de5038ccba 100644 --- a/test/SILOptimizer/addr_escape_info.sil +++ b/test/SILOptimizer/addr_escape_info.sil @@ -36,12 +36,20 @@ sil @indirect_argument : $@convention(thin) (@in Int) -> () sil @indirect_struct_argument : $@convention(thin) (@in Str) -> () sil @direct_argument : $@convention(thin) (Int) -> () sil @class_argument : $@convention(thin) (@guaranteed X) -> () -sil [escapes !%0] @non_escaping_class_argument : $@convention(thin) (@guaranteed X) -> () -sil [escapes !%0.**] @inout_class_argument : $@convention(thin) (@inout X) -> () +sil @non_escaping_class_argument : $@convention(thin) (@guaranteed X) -> () { +[%0: noescape] +} +sil @inout_class_argument : $@convention(thin) (@inout X) -> () { +[%0: noescape **] +} sil @container_argument : $@convention(thin) (@guaranteed Container) -> () sil @take_closure_as_addr : $@convention(thin) (@in @callee_guaranteed () -> ()) -> () -sil [escapes !%0.**] @closure_with_inout : $@convention(thin) (@inout Str) -> () -sil [escapes %0 => %r, %0.c*.v** => %r.c*.v**] @initX : $@convention(method) (@owned X) -> @owned X +sil @closure_with_inout : $@convention(thin) (@inout Str) -> () { +[%0: noescape **] +} +sil @initX : $@convention(method) (@owned X) -> @owned X { +[%0: escape => %r, escape c*.v** => %r.c*.v**] +} sil @modifyStr : $@convention(method) (@inout Str) -> () // CHECK-LABEL: Address escape information for test_simple: diff --git a/test/SILOptimizer/cross-module-effects.swift b/test/SILOptimizer/cross-module-effects.swift index 79f322b7483ea..0308a14375988 100644 --- a/test/SILOptimizer/cross-module-effects.swift +++ b/test/SILOptimizer/cross-module-effects.swift @@ -24,45 +24,61 @@ public final class X { public init() { } } -// CHECK-FR-MODULE-DAG: sil [escapes !%0.**] [canonical] @$s6Module17noInlineNoEffectsySiAA1XCF : +// CHECK-FR-MODULE-LABEL: sil [canonical] @$s6Module17noInlineNoEffectsySiAA1XCF : +// CHECK-FR-MODULE-NEXT: [%0: noescape **] +// CHECK-FR-MODULE-NEXT: {{^[^[]}} // CHECK-LE-MODULE-NOT: @$s6Module17noInlineNoEffectsySiAA1XCF public func noInlineNoEffects(_ x: X) -> Int { return x.i } -// CHECK-FR-MODULE-DAG: sil [escapes !%0.**] [canonical] @$s6Module14internalCalleeySiAA1XCF : -// CHECK-LE-MODULE-DAG: sil [canonical] @$s6Module14internalCalleeySiAA1XCF : -@usableFromInline -func internalCallee(_ x: X) -> Int { - return x.i -} - -// CHECK-FR-MODULE-DAG: sil [serialized] [noinline] [defined_escapes !%0.**] [canonical] @$s6Module17inlineWithEffectsySiAA1XCF : -// CHECK-LE-MODULE-DAG: sil [serialized] [noinline] [defined_escapes !%0.**] [canonical] @$s6Module17inlineWithEffectsySiAA1XCF : +// CHECK-FR-MODULE-LABEL: sil [serialized] [noinline] [canonical] @$s6Module15inlineNoEffectsySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-FR-MODULE-NEXT: [%0: noescape **] +// CHECK-FR-MODULE-NEXT: {{^[^[]}} +// CHECK-LE-MODULE-LABEL: sil [serialized] [noinline] [canonical] @$s6Module15inlineNoEffectsySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-LE-MODULE-NEXT: {{^[^[]}} @inlinable @inline(never) -@_effects(notEscaping x.**) -public func inlineWithEffects(_ x: X) -> Int { +public func inlineNoEffects(_ x: X) -> Int { return internalCallee(x) } -// CHECK-FR-MODULE-DAG: sil [serialized] [noinline] [escapes !%0.**] [canonical] @$s6Module15inlineNoEffectsySiAA1XCF : -// CHECK-LE-MODULE-DAG: sil [serialized] [noinline] [canonical] @$s6Module15inlineNoEffectsySiAA1XCF : -@inlinable -@inline(never) -public func inlineNoEffects(_ x: X) -> Int { - return internalCallee(x) +// CHECK-FR-MODULE-LABEL: sil [canonical] @$s6Module14internalCalleeySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-FR-MODULE-NEXT: [%0: noescape **] +// CHECK-FR-MODULE-NEXT: {{^[^[]}} +// CHECK-LE-MODULE-LABEL: sil [canonical] @$s6Module14internalCalleeySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int{{$}} +@usableFromInline +func internalCallee(_ x: X) -> Int { + return x.i } -// CHECK-FR-MODULE-DAG: sil [defined_escapes !%0.**] [canonical] @$s6Module19noInlineWithEffectsySiAA1XCF : +// CHECK-FR-MODULE-LABEL: sil [canonical] @$s6Module19noInlineWithEffectsySiAA1XCF : +// CHECK-FR-MODULE-NEXT: [%0!: noescape **] +// CHECK-FR-MODULE-NEXT: {{^[^[]}} // CHECK-LE-MODULE-NOT: @$s6Module19noInlineWithEffectsySiAA1XCF @_effects(notEscaping x.**) public func noInlineWithEffects(_ x: X) -> Int { return x.i } -// CHECK-FR-MODULE-DAG: sil [serialized] [noinline] [escapes !%0.**] [canonical] @$s6Module12simpleInlineySiAA1XCF : -// CHECK-LE-MODULE-DAG: sil [serialized] [noinline] [canonical] @$s6Module12simpleInlineySiAA1XCF : +// CHECK-FR-MODULE-LABEL: sil [serialized] [noinline] [canonical] @$s6Module17inlineWithEffectsySiAA1XCF : +// CHECK-FR-MODULE-NEXT: [%0!: noescape **] +// CHECK-FR-MODULE-NEXT: {{^[^[]}} +// CHECK-LE-MODULE-LABEL: sil [serialized] [noinline] [canonical] @$s6Module17inlineWithEffectsySiAA1XCF : +// CHECK-LE-MODULE-NEXT: [%0!: noescape **] +// CHECK-LE-MODULE-NEXT: {{^[^[]}} +@inlinable +@inline(never) +@_effects(notEscaping x.**) +public func inlineWithEffects(_ x: X) -> Int { + return internalCallee(x) +} + +// CHECK-FR-MODULE-LABEL: sil [serialized] [noinline] [canonical] @$s6Module12simpleInlineySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-FR-MODULE-NEXT: [%0: noescape **] +// CHECK-FR-MODULE-NEXT: {{^[^[]}} +// CHECK-LE-MODULE-LABEL: sil [serialized] [noinline] [canonical] @$s6Module12simpleInlineySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-LE-MODULE-NEXT: {{^[^[]}} @inlinable @inline(never) public func simpleInline(_ x: X) -> Int { @@ -75,7 +91,7 @@ import Module // CHECK-MAIN-LABEL: sil [noinline] @$s4Main7callit1SiyF : // CHECK-FR-MAIN: alloc_ref [stack] $X -// CHECK-EV-MAIN: alloc_ref $X +// CHECK-LE-MAIN: alloc_ref $X // CHECK-MAIN: } // end sil function '$s4Main7callit1SiyF' @inline(never) public func callit1() -> Int { @@ -83,12 +99,14 @@ public func callit1() -> Int { return noInlineNoEffects(x) } -// CHECK-FR-MAIN: sil [escapes !%0.**] @$s6Module17noInlineNoEffectsySiAA1XCF : -// CHECK-EV-MAIN: sil @$s6Module17noInlineNoEffectsySiAA1XCF : +// CHECK-FR-MAIN-LABEL: sil @$s6Module17noInlineNoEffectsySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int +// CHECK-FR-MAIN-NEXT: [%0: noescape **] +// CHECK-FR-MAIN-NEXT: {{^[^[]}} +// CHECK-LE-MAIN-LABEL: sil @$s6Module17noInlineNoEffectsySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int{{$}} // CHECK-MAIN-LABEL: sil [noinline] @$s4Main7callit2SiyF : // CHECK-FR-MAIN: alloc_ref [stack] $X -// CHECK-EV-MAIN: alloc_ref $X +// CHECK-LE-MAIN: alloc_ref $X // CHECK-MAIN: } // end sil function '$s4Main7callit2SiyF' @inline(never) public func callit2() -> Int { @@ -96,12 +114,15 @@ public func callit2() -> Int { return inlineNoEffects(x) } -// CHECK-FR-MAIN: sil public_external [noinline] [escapes !%0.**] @$s6Module15inlineNoEffectsySiAA1XCF : -// CHECK-EV-MAIN: sil public_external [noinline] @$s6Module15inlineNoEffectsySiAA1XCF : +// CHECK-FR-MAIN-LABEL: sil public_external [noinline] @$s6Module15inlineNoEffectsySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-FR-MAIN-NEXT: [%0: noescape **] +// CHECK-FR-MAIN-NEXT: {{^[^[]}} +// CHECK-LE-MAIN-LABEL: sil public_external [noinline] @$s6Module15inlineNoEffectsySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-LE-MAIN-NEXT: {{^[^[]}} // CHECK-MAIN-LABEL: sil [noinline] @$s4Main7callit3SiyF : // CHECK-FR-MAIN: alloc_ref [stack] $X -// CHECK-EV-MAIN: alloc_ref [stack] $X +// CHECK-LE-MAIN: = function_ref @$s6Module1XCACycfc // CHECK-MAIN: } // end sil function '$s4Main7callit3SiyF' @inline(never) public func callit3() -> Int { @@ -109,12 +130,13 @@ public func callit3() -> Int { return noInlineWithEffects(x) } -// CHECK-FR-MAIN: sil [defined_escapes !%0.**] @$s6Module19noInlineWithEffectsySiAA1XCF -// CHECK-EV-MAIN: sil @$s6Module17noInlineNoEffectsySiAA1XCF : +// CHECK-FR-MAIN-LABEL: sil @$s6Module19noInlineWithEffectsySiAA1XCF +// CHECK-FR-MAIN-NEXT: [%0!: noescape **] +// CHECK-FR-MAIN-NEXT: {{^[^[]}} // CHECK-MAIN-LABEL: sil [noinline] @$s4Main7callit4SiyF : // CHECK-FR-MAIN: alloc_ref [stack] $X -// CHECK-EV-MAIN: alloc_ref [stack] $X +// CHECK-LE-MAIN: = function_ref @$s6Module1XCACycfc // CHECK-MAIN: } // end sil function '$s4Main7callit4SiyF' @inline(never) public func callit4() -> Int { @@ -122,12 +144,16 @@ public func callit4() -> Int { return inlineWithEffects(x) } -// CHECK-FR-MAIN: sil public_external [noinline] [defined_escapes !%0.**] @$s6Module17inlineWithEffectsySiAA1XCF : -// CHECK-EV-MAIN: sil public_external [noinline] @$s6Module17inlineWithEffectsySiAA1XCF : +// CHECK-FR-MAIN-LABEL: sil public_external [noinline] @$s6Module17inlineWithEffectsySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-FR-MAIN-NEXT: [%0!: noescape **] +// CHECK-FR-MAIN-NEXT: {{^[^[]}} +// CHECK-LE-MAIN-LABEL: sil public_external [noinline] @$s6Module17inlineWithEffectsySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-LE-MAIN-NEXT: [%0!: noescape **] +// CHECK-LE-MAIN-NEXT: {{^[^[]}} // CHECK-MAIN-LABEL: sil [noinline] @$s4Main7callit5SiyF : // CHECK-FR-MAIN: alloc_ref [stack] $X -// CHECK-EV-MAIN: alloc_ref $X +// CHECK-LE-MAIN: alloc_ref $X // CHECK-MAIN: } // end sil function '$s4Main7callit5SiyF' @inline(never) public func callit5() -> Int { @@ -135,11 +161,16 @@ public func callit5() -> Int { return simpleInline(x) } -// CHECK-FR-MAIN: sil public_external [noinline] [escapes !%0.**] @$s6Module12simpleInlineySiAA1XCF : -// CHECK-EV-MAIN: sil public_external [noinline] @$s6Module12simpleInlineySiAA1XCF : +// CHECK-FR-MAIN-LABEL: sil public_external [noinline] @$s6Module12simpleInlineySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-FR-MAIN-NEXT: [%0: noescape **] +// CHECK-FR-MAIN-NEXT: {{^[^[]}} +// CHECK-LE-MAIN-LABEL: sil public_external [noinline] @$s6Module12simpleInlineySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-LE-MAIN-NEXT: {{^[^[]}} -// CHECK-FR-MAIN: sil [escapes !%0.**] @$s6Module14internalCalleeySiAA1XCF : -// CHECK-EV-MAIN: sil @$s6Module14internalCalleeySiAA1XCF : +// CHECK-FR-MAIN-LABEL: sil @$s6Module14internalCalleeySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int { +// CHECK-FR-MAIN-NEXT: [%0: noescape **] +// CHECK-FR-MAIN-NEXT: {{^[^[]}} +// CHECK-LE-MAIN-LABEL: sil @$s6Module14internalCalleeySiAA1XCF : $@convention(thin) (@guaranteed X) -> Int{{$}} #endif diff --git a/test/SILOptimizer/escape_info.sil b/test/SILOptimizer/escape_info.sil index f22f7bc49bc68..d6333fd93bcbe 100644 --- a/test/SILOptimizer/escape_info.sil +++ b/test/SILOptimizer/escape_info.sil @@ -70,19 +70,46 @@ enum E { case B(Z) } -sil [escapes !%0.**] @not_escaping_argument : $@convention(thin) (@guaranteed Z) -> () -sil [ossa] [escapes !%1.**] @not_escaping_second_argument : $@convention(thin) (@owned X, @owned Y) -> () -sil [escapes %0 => %r, %0.c*.c*.v** => %r.c*.c*.v**] @exclusive_arg_to_return : $@convention(thin) (@owned Z) -> @owned Z -sil [escapes %0 => %r, %0.c*.c*.v** -> %r.c*.c*.v**] @nonexclusive_arg_to_return : $@convention(thin) (@owned Z) -> @owned Z -sil [ossa] [escapes %0 => %r.s0] @arg_to_return : $@convention(thin) (@owned X) -> @owned Str -sil [escapes %1 => %0.s0, !%0] [ossa] @arg_to_arg : $@convention(thin) (@owned X) -> @out Str -sil [escapes !%0, %0.c* => %r] [ossa] @content_of_arg_to_return : $@convention(thin) (@owned Z) -> @owned Y -sil [ossa] [escapes !%0.**] @non_escaping_in_arg : $@convention(thin) (@in Z) -> () -sil [escapes %0 -> %r, %0.c*.v** -> %r.c*.v**] @load_from_inout : $@convention(thin) (@inout Y) -> @owned Y -sil [escapes %0 => %r, %0.c*.v** => %r.c*.v**] @forward_from_inout : $@convention(thin) (@inout Z, @guaranteed Y) -> Z -sil [escapes %0 -> %r, %0.c*.v** -> %r.c*.v**] @load_and_store_to_inout : $@convention(thin) (@inout Z, @guaranteed Y) -> Z -sil [escapes %0 => %r, %0.c1.v** => %r.c1.v**] @exclusive2 : $@convention(thin) (@owned Z) -> @owned Z -sil [escapes %0 -> %r, %0.c1.v** -> %r.c1.v**] @nonexclusive2 : $@convention(thin) (@owned Z) -> @owned Z +sil @not_escaping_argument : $@convention(thin) (@guaranteed Z) -> () { +[%0: noescape **] +} +sil [ossa] @not_escaping_second_argument : $@convention(thin) (@owned X, @owned Y) -> () { +[%1: noescape **] +} +sil @exclusive_arg_to_return : $@convention(thin) (@owned Z) -> @owned Z { +[%0: escape => %r, escape c*.c*.v** => %r.c*.c*.v**] +} +sil @nonexclusive_arg_to_return : $@convention(thin) (@owned Z) -> @owned Z { +[%0: escape => %r, escape c*.c*.v** -> %r.c*.c*.v**] +} +sil [ossa] @arg_to_return : $@convention(thin) (@owned X) -> @owned Str { +[%0: escape => %r.s0] +} +sil [ossa] @arg_to_arg : $@convention(thin) (@owned X) -> @out Str { +[%0: noescape] +[%1: escape => %0.s0] +} +sil [ossa] @content_of_arg_to_return : $@convention(thin) (@owned Z) -> @owned Y { +[%0: noescape, escape c* => %r] +} +sil [ossa] @non_escaping_in_arg : $@convention(thin) (@in Z) -> () { +[%0: noescape **] +} +sil @load_from_inout : $@convention(thin) (@inout Y) -> @owned Y { +[%0: escape -> %r, escape c*.v** -> %r.c*.v**] +} +sil @forward_from_inout : $@convention(thin) (@inout Z, @guaranteed Y) -> Z { +[%0: escape => %r, escape c*.v** => %r.c*.v**] +} +sil @load_and_store_to_inout : $@convention(thin) (@inout Z, @guaranteed Y) -> Z { +[%0: escape -> %r, escape c*.v** -> %r.c*.v**] +} +sil @exclusive2 : $@convention(thin) (@owned Z) -> @owned Z { +[%0: escape => %r, escape c1.v** => %r.c1.v**] +} +sil @nonexclusive2 : $@convention(thin) (@owned Z) -> @owned Z { +[%0: escape -> %r, escape c1.v** -> %r.c1.v**] +} // CHECK-LABEL: Escape information for test_simple: // CHECK: return[]: %1 = alloc_ref $X @@ -791,7 +818,9 @@ bb0: return %6 : $X } -sil [escapes !%0.**] @$s4test1FCfD : $@convention(method) (@owned F) -> () +sil @$s4test1FCfD : $@convention(method) (@owned F) -> () { +[%0: noescape **] +} // CHECK-LABEL: Escape information for destructure_struct_walkup: // CHECK: arg0[c0]: %2 = alloc_ref $Y // user: %9 @@ -965,11 +994,22 @@ bb0(%0 : @owned $Z): return %11 : $() } -sil [ossa] [escapes !%1] @closure1 : $@convention(thin) (@guaranteed Y, @guaranteed Y) -> () -sil [ossa] [escapes !%0] @closure2 : $@convention(thin) (@guaranteed Y, @guaranteed Y) -> () -sil [escapes !%0.**] @closure3 : $@convention(thin) (@guaranteed Z) -> () -sil [ossa] [escapes %0 => %1, !%1] @closure4 : $@convention(thin) (@guaranteed X, @guaranteed Y) -> () -sil [ossa] [escapes %1 => %0, !%1] @closure5 : $@convention(thin) (@guaranteed Y, @guaranteed X) -> () +sil [ossa] @closure1 : $@convention(thin) (@guaranteed Y, @guaranteed Y) -> () { +[%1: noescape] +} +sil [ossa] @closure2 : $@convention(thin) (@guaranteed Y, @guaranteed Y) -> () { +[%0: noescape] +} +sil @closure3 : $@convention(thin) (@guaranteed Z) -> () { +[%0: noescape **] +} +sil [ossa] @closure4 : $@convention(thin) (@guaranteed X, @guaranteed Y) -> () { +[%0: escape => %1] +[%1: noescape] +} +sil [ossa] @closure5 : $@convention(thin) (@guaranteed Y, @guaranteed X) -> () { +[%1: escape => %0, noescape] +} // CHECK-LABEL: Escape information for callClosure1: // CHECK: - : %0 = alloc_ref $Y @@ -1140,8 +1180,12 @@ bb0(%0 : $Y): return %10 : $() } -sil [escapes !%0.c1.**] @$s4test1ZCfD : $@convention(method) (@owned Z) -> () -sil [escapes !%0.c0.**] @$s4test8DerivedZCfD : $@convention(method) (@owned DerivedZ) -> () +sil @$s4test1ZCfD : $@convention(method) (@owned Z) -> () { +[%0: noescape c1.**] +} +sil @$s4test8DerivedZCfD : $@convention(method) (@owned DerivedZ) -> () { +[%0: noescape c0.**] +} // CHECK-LABEL: Escape information for known_type: // CHECK: global: %0 = alloc_ref $Y diff --git a/test/SILOptimizer/escape_info_objc.sil b/test/SILOptimizer/escape_info_objc.sil index 667c420f9c868..6f19616eed441 100644 --- a/test/SILOptimizer/escape_info_objc.sil +++ b/test/SILOptimizer/escape_info_objc.sil @@ -15,11 +15,18 @@ import SwiftShims class X {} -sil [escapes !%0, !%0.c*.v**] @$ss23_ContiguousArrayStorageCfD : $@convention(method) (@owned _ContiguousArrayStorage) -> () +sil @$ss23_ContiguousArrayStorageCfD : $@convention(method) (@owned _ContiguousArrayStorage) -> () { +[%0: noescape, noescape c*.v**] +} + +sil @array_adopt_storage_class : $@convention(method) (@owned _ContiguousArrayStorage, Int, @thin Array.Type) -> (@owned Array, UnsafeMutablePointer) { +[%0!: escape => %r.0.v**, escape c*.v** => %r.0.v**.c*.v**, escape c*.v** => %r.1.v**] +} -sil [defined_escapes %0 => %r.0.v**, %0.c*.v** => %r.0.v**.c*.v**, %0.c*.v** => %r.1.v**] @array_adopt_storage_class : $@convention(method) (@owned _ContiguousArrayStorage, Int, @thin Array.Type) -> (@owned Array, UnsafeMutablePointer) +sil @array_adopt_storage : $@convention(thin) (@owned _ContiguousArrayStorage) -> (@owned Array, UnsafeMutablePointer) { +[%0: escape => %r.0.s0.**] +} -sil [escapes %0 => %r.0.s0.**] @array_adopt_storage : $@convention(thin) (@owned _ContiguousArrayStorage) -> (@owned Array, UnsafeMutablePointer) sil @take_ptr : $@convention(thin) (UnsafeMutablePointer) -> () // CHECK-LABEL: Escape information for call_array_adopt_storage: diff --git a/test/SILOptimizer/existential_specializer_indirect_class.sil b/test/SILOptimizer/existential_specializer_indirect_class.sil index cfdf1362db960..db1db39e9136d 100644 --- a/test/SILOptimizer/existential_specializer_indirect_class.sil +++ b/test/SILOptimizer/existential_specializer_indirect_class.sil @@ -8,10 +8,9 @@ protocol ClassProtocol: AnyObject { func method() } class C: ClassProtocol { func method() {} } -// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] {{.*}}@test_indirect_class_protocol : $@convention(thin) (@in any ClassProtocol) -> () +// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @test_indirect_class_protocol : $@convention(thin) (@in any ClassProtocol) -> () sil @test_indirect_class_protocol : $@convention(thin) (@in ClassProtocol) -> () { -// CHECK-NEXT: // -// CHECK-NEXT: bb0(%0 : $*any ClassProtocol): +// CHECK: bb0(%0 : $*any ClassProtocol): bb0(%0 : $*ClassProtocol): // CHECK-NEXT: %1 = load %0 // CHECK-NEXT: strong_release %1 @@ -41,8 +40,7 @@ bb0(%0 : $*ClassProtocol): // Check that a specialization of test_indirect_class_protocol is created. // CHECK-LABEL: sil shared [signature_optimized_thunk] [always_inline] {{.*}}@$s28test_indirect_class_protocolTf4e_n4main1CC_Tg5 : $@convention(thin) (@owned C) -> () -// CHECK-NEXT: // -// CHECK-NEXT: bb0(%0 : $C): +// CHECK: bb0(%0 : $C): // CHECK-NEXT: strong_release %0 // CHECK-NEXT: return undef // CHECK-LABEL: end sil function '$s28test_indirect_class_protocolTf4e_n4main1CC_Tg5' diff --git a/test/SILOptimizer/function_effects.sil b/test/SILOptimizer/function_effects.sil index 958bdd21b8b3d..073bd718979e0 100644 --- a/test/SILOptimizer/function_effects.sil +++ b/test/SILOptimizer/function_effects.sil @@ -56,7 +56,11 @@ sil_global @globalY : $Y sil [ossa] @unknown_function : $@convention(thin) (@owned X) -> () sil [ossa] @unknown_function_y : $@convention(thin) (@owned Y) -> () -// CHECK-LABEL: sil [escapes %1.v** => %3.v**, !%2, %2.c*.v** -> %r.v**, !%3] [ossa] @test_basic +// CHECK-LABEL: sil [ossa] @test_basic +// CHECK-NEXT: [%1: escape v** => %3.v**] +// CHECK-NEXT: [%2: noescape, escape c*.v** -> %r.v**] +// CHECK-NEXT: [%3: noescape] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @test_basic : $@convention(thin) (@owned X, @guaranteed Str, @guaranteed Z, @inout X) -> @owned Y { bb0(%0 : @owned $X, %1 : @guaranteed $Str, %2 : @guaranteed $Z, %3 : $*X): // %0 escapes to global @@ -75,20 +79,28 @@ bb0(%0 : @owned $X, %1 : @guaranteed $Str, %2 : @guaranteed $Z, %3 : $*X): return %9 : $Y } -// CHECK-LABEL: sil [escapes %0 => %r, %0.c*.v** => %r.c*.v**] @simple_forwarding +// CHECK-LABEL: sil @simple_forwarding +// CHECK-NEXT: [%0: escape => %r, escape c*.v** => %r.c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @simple_forwarding : $@convention(thin) (@owned Z) -> @owned Z { bb0(%0 : $Z): return %0 : $Z } -// CHECK-LABEL: sil [escapes %0.v** => %r.v**, %0.v**.c*.v** => %r.v**.c*.v**] @forwarding_struct_element +// CHECK-LABEL: sil @forwarding_struct_element +// CHECK-NEXT: [%0: escape v** => %r.v**, escape v**.c*.v** => %r.v**.c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @forwarding_struct_element : $@convention(thin) (@owned Str) -> @owned X { bb0(%0 : $Str): %1 = struct_extract %0 : $Str, #Str.a return %1 : $X } -// CHECK-LABEL: sil [escapes %0 => %r.s1.0, %0.c*.v** => %r.s1.0.c*.v**, %1 => %r.s1.1, %1.c*.v** => %r.s1.1.c*.v**, %2 => %r.s0, %2.c*.v** => %r.s0.c*.v**] @forwarding_in_struct +// CHECK-LABEL: sil @forwarding_in_struct +// CHECK-NEXT: [%0: escape => %r.s1.0, escape c*.v** => %r.s1.0.c*.v**] +// CHECK-NEXT: [%1: escape => %r.s1.1, escape c*.v** => %r.s1.1.c*.v**] +// CHECK-NEXT: [%2: escape => %r.s0, escape c*.v** => %r.s0.c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @forwarding_in_struct : $@convention(thin) (@owned X, @owned X, @owned X) -> @owned Str { bb0(%0 : $X, %1 : $X, %2: $X): %3 = tuple (%0 : $X, %1 : $X) @@ -96,7 +108,9 @@ bb0(%0 : $X, %1 : $X, %2: $X): return %4 : $Str } -// CHECK-LABEL: sil [escapes %0 -> %r.v**, %0.c*.v** -> %r.v**.c*.v**] @forwarding_same_arg_in_struct +// CHECK-LABEL: sil @forwarding_same_arg_in_struct +// CHECK-NEXT: [%0: escape -> %r.v**, escape c*.v** -> %r.v**.c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @forwarding_same_arg_in_struct : $@convention(thin) (@owned X) -> @owned Str { bb0(%0 : $X): %1 = tuple (%0 : $X, %0 : $X) @@ -104,7 +118,9 @@ bb0(%0 : $X): return %2 : $Str } -// CHECK-LABEL: sil [escapes !%0] [ossa] @test_escaping_content +// CHECK-LABEL: sil [ossa] @test_escaping_content +// CHECK-NEXT: [%0: noescape] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @test_escaping_content : $@convention(thin) (@guaranteed Z) -> () { bb0(%0 : @guaranteed $Z): %1 = ref_element_addr %0 : $Z, #Z.y @@ -115,8 +131,11 @@ bb0(%0 : @guaranteed $Z): return %5 : $() } -// CHECK-LABEL: sil [escapes !%0.**] @not_escaping_argument +// CHECK-LABEL: sil @not_escaping_argument +// CHECK-NEXT: [%0!: noescape **] +// CHECK-NEXT: {{^[^[]}} sil @not_escaping_argument : $@convention(thin) (@guaranteed Z) -> () { +[%0!: noescape **] bb0(%0 : $Z): %1 = ref_element_addr %0 : $Z, #Z.y %2 = global_addr @globalY : $*Y @@ -126,14 +145,19 @@ bb0(%0 : $Z): return %4 : $() } -// CHECK-LABEL: sil [escapes %0 -> %r, %0.c*.v** -> %r.c*.v**] @load_from_inout +// CHECK-LABEL: sil @load_from_inout +// CHECK-NEXT: [%0: escape -> %r, escape c*.v** -> %r.c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @load_from_inout : $@convention(thin) (@inout Y) -> @owned Y { bb0(%0 : $*Y): %2 = load %0 : $*Y return %2 : $Y } -// CHECK-LABEL: sil [escapes !%0, %0.c*.v** -> %r.v**, !%1] [ossa] @followstore_implies_non_exclusive +// CHECK-LABEL: sil [ossa] @followstore_implies_non_exclusive +// CHECK-NEXT: [%0: noescape, escape c*.v** -> %r.v**] +// CHECK-NEXT: [%1: noescape] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @followstore_implies_non_exclusive : $@convention(thin) (@guaranteed Z, @owned Y) -> @owned Y { bb0(%0 : @guaranteed $Z, %1 : @owned $Y): %2 = ref_element_addr %0 : $Z, #Z.y @@ -142,7 +166,10 @@ bb0(%0 : @guaranteed $Z, %1 : @owned $Y): return %3 : $Y } -// CHECK-LABEL: sil [escapes !%0, %0.c*.v** -> %r.v**, %1 -> %r] [ossa] @test_not_exclusive_escaping_through_phi +// CHECK-LABEL: sil [ossa] @test_not_exclusive_escaping_through_phi +// CHECK-NEXT: [%0: noescape, escape c*.v** -> %r.v**] +// CHECK-NEXT: [%1: escape -> %r] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @test_not_exclusive_escaping_through_phi : $@convention(thin) (@guaranteed Z, @owned Y) -> @owned Y { bb0(%0 : @guaranteed $Z, %1 : @owned $Y): cond_br undef, bb1, bb2 @@ -158,7 +185,9 @@ bb3(%7: @owned $Y): return %7 : $Y } -// CHECK-LABEL: sil [escapes !%0, %0.c*.v** -> %r.v**] [ossa] @test_not_exclusive_through_store +// CHECK-LABEL: sil [ossa] @test_not_exclusive_through_store +// CHECK-NEXT: [%0: noescape, escape c*.v** -> %r.v**] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @test_not_exclusive_through_store : $@convention(thin) (@guaranteed Z, @owned Y) -> @owned Y { bb0(%0 : @guaranteed $Z, %1 : @owned $Y): %2 = ref_element_addr %0 : $Z, #Z.y @@ -167,9 +196,13 @@ bb0(%0 : @guaranteed $Z, %1 : @owned $Y): return %3 : $Y } -sil [escapes %0.v** => %0.v**] [ossa] @self_arg_callee : $@convention(thin) (@inout Str) -> () +sil [ossa] @self_arg_callee : $@convention(thin) (@inout Str) -> () { +[%0: escape v** => %0.v**] +} -// CHECK-LABEL: sil [escapes %0.v** => %0.v**] [ossa] @test_self_arg_escape +// CHECK-LABEL: sil [ossa] @test_self_arg_escape +// CHECK-NEXT: [%0: escape v** => %0.v**] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @test_self_arg_escape : $@convention(thin) (@inout Str) -> () { bb0(%0 : $*Str): %1 = function_ref @self_arg_callee : $@convention(thin) (@inout Str) -> () @@ -178,7 +211,9 @@ bb0(%0 : $*Str): return %3 : $() } -// CHECK-LABEL: sil [escapes %0.v** => %0.s1.0.v**] [ossa] @test_self_arg_escape_with_copy +// CHECK-LABEL: sil [ossa] @test_self_arg_escape_with_copy +// CHECK-NEXT: [%0: escape v** => %0.s1.0.v**] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @test_self_arg_escape_with_copy : $@convention(thin) (@inout Str) -> () { bb0(%0 : $*Str): %1 = struct_element_addr %0 : $*Str, #Str.a @@ -189,7 +224,9 @@ bb0(%0 : $*Str): return %5 : $() } -// CHECK-LABEL: sil [escapes %0.v** => %0.v**] [ossa] @test_self_arg_escape_with_bidirectional_copy +// CHECK-LABEL: sil [ossa] @test_self_arg_escape_with_bidirectional_copy +// CHECK-NEXT: [%0: escape v** => %0.v**] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @test_self_arg_escape_with_bidirectional_copy : $@convention(thin) (@inout Str) -> () { bb0(%0 : $*Str): %1 = struct_element_addr %0 : $*Str, #Str.a @@ -201,7 +238,10 @@ bb0(%0 : $*Str): return %5 : $() } -// CHECK-LABEL: sil [escapes !%0.**, !%1, %1.c*.v** => %0.v**] [ossa] @test_content_to_arg_addr +// CHECK-LABEL: sil [ossa] @test_content_to_arg_addr +// CHECK-NEXT: [%0: noescape **] +// CHECK-NEXT: [%1: noescape, escape c*.v** => %0.v**] +// CHECK-NEXT: {{^[^[]}} sil [ossa] @test_content_to_arg_addr : $@convention(thin) (@guaranteed Y) -> @out Str { bb0(%0 : $*Str, %1 : @guaranteed $Y): %2 = ref_element_addr %1 : $Y, #Y.s @@ -212,21 +252,27 @@ bb0(%0 : $*Str, %1 : @guaranteed $Y): return %6 : $() } -// CHECK-LABEL: sil [escapes %0.v** => %r.v**, %0.v**.c*.v** => %r.v**.c*.v**] @escaping_pointer +// CHECK-LABEL: sil @escaping_pointer +// CHECK-NEXT: [%0: escape v** => %r.v**, escape v**.c*.v** => %r.v**.c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @escaping_pointer : $@convention(thin) (PointerAndInt) -> UnsafeMutablePointer { bb0(%0 : $PointerAndInt): %1 = struct_extract %0 : $PointerAndInt, #PointerAndInt.p return %1 : $UnsafeMutablePointer } -// CHECK-LABEL: sil [escapes !%0.**] @not_escaping_int +// CHECK-LABEL: sil @not_escaping_int +// CHECK-NEXT: [%0: noescape **] +// CHECK-NEXT: {{^[^[]}} sil @not_escaping_int : $@convention(thin) (PointerAndInt) -> Int { bb0(%0 : $PointerAndInt): %1 = struct_extract %0 : $PointerAndInt, #PointerAndInt.i return %1 : $Int } -// CHECK-LABEL: sil [escapes !%0.**] @test_simple_recursion +// CHECK-LABEL: sil @test_simple_recursion +// CHECK-NEXT: [%0: noescape **] +// CHECK-NEXT: {{^[^[]}} sil @test_simple_recursion : $@convention(thin) (@guaranteed Y) -> () { bb0(%0 : $Y): cond_br undef, bb1, bb2 @@ -244,7 +290,8 @@ bb3: return %5 : $() } -// CHECK-LABEL: sil @test_recursion_with_escaping_param +// CHECK-LABEL: sil @test_recursion_with_escaping_param : $@convention(thin) (@guaranteed Y, @guaranteed Y) -> Y { +// CHECK-NEXT: {{^[^[]}} sil @test_recursion_with_escaping_param : $@convention(thin) (@guaranteed Y, @guaranteed Y) -> Y { bb0(%0 : $Y, %1 : $Y): cond_br undef, bb1, bb2 @@ -261,7 +308,9 @@ bb3(%5 : $Y): return %5 : $Y } -// CHECK-LABEL: [escapes !%0.**] @inout_class_argument +// CHECK-LABEL: sil @inout_class_argument +// CHECK-NEXT: [%0: noescape **] +// CHECK-NEXT: {{^[^[]}} sil @inout_class_argument : $@convention(thin) (@inout X) -> () { bb0(%0 : $*X): %1 = alloc_ref $X @@ -270,7 +319,9 @@ bb0(%0 : $*X): return %3 : $() } -// CHECK-LABEL: sil [escapes %0 -> %r, %0.c*.v** -> %r.c*.v**] @load_and_store_to_inout +// CHECK-LABEL: sil @load_and_store_to_inout +// CHECK-NEXT: [%0: escape -> %r, escape c*.v** -> %r.c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @load_and_store_to_inout : $@convention(thin) (@inout Z, @guaranteed Y) -> Z { bb0(%0 : $*Z, %1 : $Y): %2 = load %0 : $*Z @@ -279,7 +330,9 @@ bb0(%0 : $*Z, %1 : $Y): return %2 : $Z } -// CHECK-LABEL: sil [escapes %0 => %r, %0.c*.v** => %r.c*.v**] @store_to_content +// CHECK-LABEL: sil @store_to_content +// CHECK-NEXT: [%0: escape => %r, escape c*.v** => %r.c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @store_to_content : $@convention(thin) (@owned Z, @guaranteed Y) -> @owned Z { bb0(%0 : $Z, %1 : $Y): %2 = ref_element_addr %0 : $Z, #Z.y @@ -287,14 +340,18 @@ bb0(%0 : $Z, %1 : $Y): return %0 : $Z } -// CHECK-LABEL: sil [escapes !%1.**] @non_returning_function +// CHECK-LABEL: sil @non_returning_function +// CHECK-NEXT: [%1: noescape **] +// CHECK-NEXT: {{^[^[]}} sil @non_returning_function : $@convention(thin) (Str, @inout Str) -> () { bb0(%0 : $Str, %1 : $*Str): store %0 to %1 : $*Str unreachable } -// CHECK-LABEL: sil [escapes !%0] @followStore_introducing1 +// CHECK-LABEL: sil @followStore_introducing1 +// CHECK-NEXT: [%0: noescape] +// CHECK-NEXT: {{^[^[]}} sil @followStore_introducing1 : $@convention(thin) (@guaranteed Z) -> @owned Y { bb0(%0 : $Z): %1 = ref_element_addr %0 : $Z, #Z.y @@ -304,7 +361,9 @@ bb0(%0 : $Z): return %4 : $Y } -// CHECK-LABEL: sil [escapes !%0.**] @followStore_introducing2 +// CHECK-LABEL: sil @followStore_introducing2 +// CHECK-NEXT: [%0: noescape **] +// CHECK-NEXT: {{^[^[]}} sil @followStore_introducing2 : $@convention(thin) (@inout Z, @guaranteed Y) -> () { bb0(%0 : $*Z, %1 : $Y): %2 = load %0 : $*Z diff --git a/test/SILOptimizer/function_effects_objc.sil b/test/SILOptimizer/function_effects_objc.sil index 291651321e98a..2e3f56e0aab76 100644 --- a/test/SILOptimizer/function_effects_objc.sil +++ b/test/SILOptimizer/function_effects_objc.sil @@ -14,7 +14,9 @@ class Y { @_hasStorage var i: Int } -// CHECK-LABEL: sil [escapes %0 => %r.0.s0.s0.s0, %0.c*.v** -> %r.**] @array_adopt_storage +// CHECK-LABEL: sil @array_adopt_storage +// CHECK-NEXT: [%0: escape => %r.0.s0.s0.s0, escape c*.v** -> %r.**] +// CHECK-NEXT: {{^[^[]}} sil @array_adopt_storage : $@convention(thin) (@owned _ContiguousArrayStorage) -> (@owned Array, UnsafeMutablePointer) { bb0(%0 : $_ContiguousArrayStorage): %3 = upcast %0 : $_ContiguousArrayStorage to $__ContiguousArrayStorageBase @@ -29,7 +31,9 @@ bb0(%0 : $_ContiguousArrayStorage): return %19 : $(Array, UnsafeMutablePointer) } -// CHECK-LABEL: sil [escapes !%0, !%0.c*.v**] @array_destructor +// CHECK-LABEL: sil @array_destructor +// CHECK-NEXT: [%0: noescape, noescape c*.v**] +// CHECK-NEXT: {{^[^[]}} sil @array_destructor : $@convention(method) (@owned _ContiguousArrayStorage) -> () { bb0(%0 : $_ContiguousArrayStorage): %1 = ref_tail_addr %0 : $_ContiguousArrayStorage, $Element diff --git a/test/SILOptimizer/specialize_reabstraction.sil b/test/SILOptimizer/specialize_reabstraction.sil index f2ade8c254766..67fe831873807 100644 --- a/test/SILOptimizer/specialize_reabstraction.sil +++ b/test/SILOptimizer/specialize_reabstraction.sil @@ -135,16 +135,22 @@ bb0(%0 : $Bool): return %rv : $() } -// CHECK-LABEL: sil shared [escapes %0 => %r] @$s21arg_escapes_to_return4main1XC_Tg5 -sil [escapes %1 => %0] @arg_escapes_to_return : $@convention(thin) (@in_guaranteed T) -> @out T { +// CHECK-LABEL: sil shared @$s21arg_escapes_to_return4main1XC_Tg5 +// CHECK-NEXT: [%0: escape => %r] +// CHECK-NEXT: {{^[^[]}} +sil @arg_escapes_to_return : $@convention(thin) (@in_guaranteed T) -> @out T { +[%1: escape => %0] bb0(%0 : $*T, %1 : $*T): copy_addr %1 to [initialization] %0 : $*T %4 = tuple () return %4 : $() } -// CHECK-LABEL: sil shared [escapes %1 => %0] @$s015arg_escapes_to_A04main1XC_Tg5 -sil [escapes %1 => %0] @arg_escapes_to_arg : $@convention(thin) (@inout T, @in_guaranteed T) -> () { +// CHECK-LABEL: sil shared @$s015arg_escapes_to_A04main1XC_Tg5 +// CHECK-NEXT: [%1: escape => %0] +// CHECK-NEXT: {{^[^[]}} +sil @arg_escapes_to_arg : $@convention(thin) (@inout T, @in_guaranteed T) -> () { +[%1: escape => %0] bb0(%0 : $*T, %1 : $*T): copy_addr %1 to %0 : $*T %4 = tuple () diff --git a/test/SILOptimizer/stack_promotion.sil b/test/SILOptimizer/stack_promotion.sil index 0bebf5b6e5424..8b244c57c70cd 100644 --- a/test/SILOptimizer/stack_promotion.sil +++ b/test/SILOptimizer/stack_promotion.sil @@ -847,8 +847,13 @@ bb0(%0 : $Int): return %11 : $Array } -sil [escapes %0.v** => %r.0] [_semantics "array.uninitialized"] @init_array_with_buffer : $@convention(thin) (@owned DummyArrayStorage, Int32, @thin Array.Type) -> @owned (Array, UnsafeMutablePointer) -sil [escapes !%2.v**] [_semantics "array.withUnsafeMutableBufferPointer"] @withUnsafeMutableBufferPointer : $@convention(method) (@owned @callee_owned (@inout Int) -> (@out (), @error Error), @inout Array) -> (@out (), @error Error) +sil [_semantics "array.uninitialized"] @init_array_with_buffer : $@convention(thin) (@owned DummyArrayStorage, Int32, @thin Array.Type) -> @owned (Array, UnsafeMutablePointer) { +[%0: escape v** => %r.0] +} + +sil [_semantics "array.withUnsafeMutableBufferPointer"] @withUnsafeMutableBufferPointer : $@convention(method) (@owned @callee_owned (@inout Int) -> (@out (), @error Error), @inout Array) -> (@out (), @error Error) { +[%2: noescape v**] +} sil @take_array : $@convention(thin) (@owned Array) -> () { bb0(%0 : $Array): From 0db14574c18ae024ad84216274dff53599390d46 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 12 Sep 2022 07:07:24 -0700 Subject: [PATCH 28/37] [interop][SwiftToCxx] pass C++ value types into Swift correctly --- lib/PrintAsClang/PrintClangFunction.cpp | 26 ++++++++-- ...ridge-cxx-struct-back-to-cxx-execution.cpp | 52 ++++++++++++++++++- .../bridge-cxx-struct-back-to-cxx.swift | 20 +++++++ 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/lib/PrintAsClang/PrintClangFunction.cpp b/lib/PrintAsClang/PrintClangFunction.cpp index 06bab8bb5a2e5..1a78215dfc28f 100644 --- a/lib/PrintAsClang/PrintClangFunction.cpp +++ b/lib/PrintAsClang/PrintClangFunction.cpp @@ -310,7 +310,12 @@ class CFunctionSignatureTypePrinter ClangTypeHandler handler(decl->getClangDecl()); if (!handler.isRepresentable()) return ClangRepresentation::unsupported; + if (typeUseKind == FunctionSignatureTypeUse::ParamType && + !isInOutParam) + os << "const "; handler.printTypeName(os); + if (typeUseKind == FunctionSignatureTypeUse::ParamType) + os << '&'; return ClangRepresentation::representable; } @@ -859,11 +864,22 @@ void DeclAndTypeClangFunctionPrinter::printCxxToCFunctionParameterUse( if (!directTypeEncoding.empty()) os << cxx_synthesis::getCxxImplNamespaceName() << "::swift_interop_passDirect_" << directTypeEncoding << '('; - ClangValueTypePrinter(os, cPrologueOS, interopContext) - .printParameterCxxToCUseScaffold( - moduleContext, - [&]() { printTypeImplTypeSpecifier(type, moduleContext); }, - namePrinter, isSelf); + if (decl->hasClangNode()) { + if (!directTypeEncoding.empty()) + os << "reinterpret_cast("; + os << "swift::" << cxx_synthesis::getCxxImplNamespaceName() + << "::getOpaquePointer("; + namePrinter(); + os << ')'; + if (!directTypeEncoding.empty()) + os << ')'; + } else { + ClangValueTypePrinter(os, cPrologueOS, interopContext) + .printParameterCxxToCUseScaffold( + moduleContext, + [&]() { printTypeImplTypeSpecifier(type, moduleContext); }, + namePrinter, isSelf); + } if (!directTypeEncoding.empty()) os << ')'; return; diff --git a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp index ff38652e818be..7b2666def33ce 100644 --- a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp +++ b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp @@ -28,7 +28,9 @@ struct NonTrivialTemplate { inline NonTrivialTemplate(T x) : x(x) { puts("create NonTrivialTemplate"); } - inline NonTrivialTemplate(const NonTrivialTemplate &) = default; + inline NonTrivialTemplate(const NonTrivialTemplate &other) : x(other.x) { + puts("copy NonTrivialTemplate"); + } inline NonTrivialTemplate(NonTrivialTemplate &&other) : x(static_cast(other.x)) { puts("move NonTrivialTemplate"); } @@ -50,10 +52,34 @@ public func retNonTrivial(y: CInt) -> NonTrivialTemplate { return NonTrivialTemplate(Trivial(42, y)) } +public func takeNonTrivial(_ x: NonTrivialTemplate) { + print(x) +} + +public func passThroughNonTrivial(_ x: NonTrivialTemplate) -> NonTrivialTemplate{ + return x +} + +public func inoutNonTrivial(_ x: inout NonTrivialTemplate) { + x.x.y *= 2 +} + public func retTrivial(_ x: CInt) -> Trivial { return Trivial(x, -x) } +public func takeTrivial(_ x: Trivial) { + print(x) +} + +public func passThroughTrivial(_ x: Trivial) -> Trivial { + return x +} + +public func inoutTrivial(_ x: inout Trivial) { + x.x = x.y + x.x - 11 +} + //--- use-swift-cxx-types.cpp #include "header.h" @@ -65,15 +91,37 @@ int main() { auto x = UseCxx::retTrivial(423421); assert(x.x == 423421); assert(x.y == -423421); + UseCxx::takeTrivial(UseCxx::passThroughTrivial(x)); + assert(x.x == 423421); + assert(x.y == -423421); + UseCxx::inoutTrivial(x); + assert(x.x == -11); + assert(x.y == -423421); + UseCxx::takeTrivial(x); } +// CHECK: Trivial(x: 423421, y: -423421) +// CHECK-NEXT: Trivial(x: -11, y: -423421) { auto x = UseCxx::retNonTrivial(-942); assert(x.x.y == -942); assert(x.x.x == 42); + UseCxx::takeNonTrivial(UseCxx::passThroughNonTrivial(x)); + puts("done non trivial"); + UseCxx::inoutNonTrivial(x); + assert(x.x.y == -1884); + assert(x.x.x == 42); } -// CHECK: create NonTrivialTemplate +// CHECK-NEXT: create NonTrivialTemplate +// CHECK-NEXT: move NonTrivialTemplate +// CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: copy NonTrivialTemplate // CHECK-NEXT: move NonTrivialTemplate // CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: copy NonTrivialTemplate +// CHECK-NEXT: __CxxTemplateInst18NonTrivialTemplateI7TrivialE(x: __C.Trivial(x: 42, y: -942)) +// CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: done non trivial // CHECK-NEXT: ~NonTrivialTemplate return 0; } diff --git a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift index 30d667e60c5f3..454d41362311a 100644 --- a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift +++ b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift @@ -71,6 +71,15 @@ public func retTrivial() -> Trivial { return Trivial() } +public func takeNonTrivial2(_ x: ns.NonTrivialTemplate) { +} + +public func takeTrivial(_ x: Trivial) { +} + +public func takeTrivialInout(_ x: inout Trivial) { +} + // CHECK: #if __has_feature(objc_modules) // CHECK: #if __has_feature(objc_modules) // CHECK-NEXT: #if __has_warning("-Watimport-in-framework-header") @@ -120,3 +129,14 @@ public func retTrivial() -> Trivial { // CHECK-NEXT: return *storageObjectPtr; // CHECK-NEXT: } +// CHECK: inline void takeNonTrivial2(const ns::NonTrivialTemplate& x) noexcept { +// CHECK-NEXT: return _impl::$s8UseCxxTy15takeNonTrivial2yySo2nsO02__b18TemplateInstN2ns18e7TrivialH20INS_11TrivialinNSEEEVF(swift::_impl::getOpaquePointer(x)); +// CHECK-NEXT: } + +// CHECK: inline void takeTrivial(const Trivial& x) noexcept { +// CHECK-NEXT: return _impl::$s8UseCxxTy11takeTrivialyySo0E0VF(_impl::swift_interop_passDirect_UseCxxTy_uint32_t_0_4(reinterpret_cast(swift::_impl::getOpaquePointer(x)))); +// CHECK-NEXT: } + +// CHECK: inline void takeTrivialInout(Trivial& x) noexcept { +// CHECK-NEXT: return _impl::$s8UseCxxTy16takeTrivialInoutyySo0E0VzF(swift::_impl::getOpaquePointer(x)); +// CHECK-NEXT: } From f53e88b25b0444757b53d602eb19f3baf9228959 Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Mon, 12 Sep 2022 07:35:56 -0700 Subject: [PATCH 29/37] Workaround MSVC more. --- include/swift/SIL/TypeLowering.h | 4 +- lib/SIL/IR/TypeLowering.cpp | 104 +++++++++++++------------------ 2 files changed, 47 insertions(+), 61 deletions(-) diff --git a/include/swift/SIL/TypeLowering.h b/include/swift/SIL/TypeLowering.h index c388a61a21fa2..5b568275f258a 100644 --- a/include/swift/SIL/TypeLowering.h +++ b/include/swift/SIL/TypeLowering.h @@ -1254,10 +1254,10 @@ class TypeConverter { visitAggregateLeaves(Lowering::AbstractionPattern origType, Type substType, TypeExpansionContext context, std::function)> + ValueDecl *, Optional)> isLeafAggregate, std::function)> + ValueDecl *, Optional)> visit); #endif }; diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index 0106b3719200f..1964392d8a44f 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2439,83 +2439,74 @@ TypeConverter::getTypeLowering(AbstractionPattern origType, bool TypeConverter::visitAggregateLeaves( Lowering::AbstractionPattern origType, Type substType, TypeExpansionContext context, - std::function)> + std::function)> isLeafAggregate, - std::function)> + std::function)> visit) { llvm::SmallSet, 16> visited; llvm::SmallVector< std::tuple, 16> worklist; - auto insertIntoWorklist = - [&visited, &worklist](Type substTy, AbstractionPattern origTy, - TaggedUnion either) -> bool { - ValueDecl *field; - unsigned index; - if (either.isa()) { - field = either.get(); - index = UINT_MAX; - } else { - field = nullptr; - index = either.get(); - } + auto insertIntoWorklist = [&visited, + &worklist](Type substTy, AbstractionPattern origTy, + ValueDecl *field, + Optional maybeIndex) -> bool { + unsigned index = maybeIndex.getValueOr(UINT_MAX); if (!visited.insert({substTy.getPointer(), field, index}).second) return false; worklist.push_back({substTy.getPointer(), origTy, field, index}); return true; }; - auto popFromWorklist = - [&worklist]() -> std::tuple> { + auto popFromWorklist = [&worklist]() + -> std::tuple> { TypeBase *ty; AbstractionPattern origTy = AbstractionPattern::getOpaque(); - TaggedUnion either((ValueDecl *)nullptr); ValueDecl *field; - unsigned index = 0; + unsigned index; std::tie(ty, origTy, field, index) = worklist.pop_back_val(); - if (index != UINT_MAX) { - either = TaggedUnion(index); - } else { - either = TaggedUnion(field); - } - return {ty->getCanonicalType(), origTy, either}; + Optional maybeIndex; + if (index != UINT_MAX) + maybeIndex = {index}; + return {ty->getCanonicalType(), origTy, field, index}; }; auto isAggregate = [](Type ty) { return ty->is() || ty->getEnumOrBoundGenericEnum() || ty->getStructOrBoundGenericStruct(); }; - insertIntoWorklist(substType, origType, (ValueDecl *)nullptr); + insertIntoWorklist(substType, origType, nullptr, llvm::None); while (!worklist.empty()) { Type ty; AbstractionPattern origTy = AbstractionPattern::getOpaque(); - TaggedUnion path((ValueDecl *)nullptr); - std::tie(ty, origTy, path) = popFromWorklist(); - if (isAggregate(ty) && !isLeafAggregate(ty, origTy, path)) { + ValueDecl *field; + Optional index; + std::tie(ty, origTy, field, index) = popFromWorklist(); + if (isAggregate(ty) && !isLeafAggregate(ty, origTy, field, index)) { if (auto tupleTy = ty->getAs()) { - for (unsigned index = 0, num = tupleTy->getNumElements(); index < num; - ++index) { - auto origElementTy = origTy.getTupleElementType(index); + for (unsigned tupleIndex = 0, num = tupleTy->getNumElements(); + tupleIndex < num; ++tupleIndex) { + auto origElementTy = origTy.getTupleElementType(tupleIndex); auto substElementTy = - tupleTy->getElementType(index)->getCanonicalType(); + tupleTy->getElementType(tupleIndex)->getCanonicalType(); substElementTy = computeLoweredRValueType(context, origElementTy, substElementTy); - insertIntoWorklist(substElementTy, origElementTy, - TaggedUnion(index)); + insertIntoWorklist(substElementTy, origElementTy, nullptr, + tupleIndex); } } else if (auto *decl = ty->getStructOrBoundGenericStruct()) { - for (auto *field : decl->getStoredProperties()) { + for (auto *structField : decl->getStoredProperties()) { auto subMap = ty->getContextSubstitutionMap(&M, decl); auto substFieldTy = - field->getInterfaceType().subst(subMap)->getCanonicalType(); - auto sig = field->getDeclContext()->getGenericSignatureOfContext(); - auto interfaceTy = field->getInterfaceType()->getReducedType(sig); + structField->getInterfaceType().subst(subMap)->getCanonicalType(); + auto sig = + structField->getDeclContext()->getGenericSignatureOfContext(); + auto interfaceTy = + structField->getInterfaceType()->getReducedType(sig); auto origFieldType = - origTy.unsafeGetSubstFieldType(field, interfaceTy); - insertIntoWorklist(substFieldTy, origFieldType, - TaggedUnion( - static_cast(field))); + origTy.unsafeGetSubstFieldType(structField, interfaceTy); + insertIntoWorklist(substFieldTy, origFieldType, structField, + llvm::None); } } else if (auto *decl = ty->getEnumOrBoundGenericEnum()) { auto subMap = ty->getContextSubstitutionMap(&M, decl); @@ -2532,9 +2523,8 @@ bool TypeConverter::visitAggregateLeaves( element, element->getArgumentInterfaceType()->getReducedType( decl->getGenericSignature())); - insertIntoWorklist(substElementType, origElementTy, - TaggedUnion( - static_cast(element))); + insertIntoWorklist(substElementType, origElementTy, element, + llvm::None); } } else { llvm_unreachable("unknown aggregate kind!"); @@ -2543,7 +2533,7 @@ bool TypeConverter::visitAggregateLeaves( } // This type is a leaf. Visit it. - auto success = visit(ty, origTy, path); + auto success = visit(ty, origTy, field, index); if (!success) return false; } @@ -2567,26 +2557,23 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, bool hasNoNontrivialLexicalLeaf = visitAggregateLeaves( origType, substType, forExpansion, /*isLeaf=*/ - [&](auto ty, auto origTy, auto either) -> bool { + [&](auto ty, auto origTy, auto *field, auto index) -> bool { // The field's type is an aggregate. Treat it as a leaf if it // has a lifetime annotation. // If it's a field of a tuple or the top-level type, there's no value // decl on which to look for an attribute. It's a leaf iff the type // has a lifetime annotation. - if (either.template isa() || - either.template get() == nullptr) + if (index || !field) return getLifetimeAnnotation(ty).isSome(); // It's a field of a struct or an enum. It's a leaf if the type // or the var decl has a lifetime annotation. - return either.template get() - ->getLifetimeAnnotation() - .isSome() || + return field->getLifetimeAnnotation().isSome() || getLifetimeAnnotation(ty); }, /*visit=*/ - [&](auto ty, auto origTy, auto either) -> bool { + [&](auto ty, auto origTy, auto *field, auto index) -> bool { // Look at each leaf: if it is non-trivial, verify that it is // attributed @_eagerMove. @@ -2605,8 +2592,7 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, // not lexical. The leaf must be annotated @_eagerMove. // Otherwise, the whole type would be lexical. - if (either.template isa() || - either.template get() == nullptr) { + if (index || !field) { // There is no field decl that might be annotated @_eagerMove. The // field is @_eagerMove iff its type is annotated @_eagerMove. return getLifetimeAnnotation(ty) == LifetimeAnnotation::EagerMove; @@ -2615,7 +2601,7 @@ void TypeConverter::verifyLowering(const TypeLowering &lowering, // The field is non-trivial and the whole type is non-lexical. // That's fine as long as the field or its type is annotated // @_eagerMove. - return either.template get()->getLifetimeAnnotation() == + return field->getLifetimeAnnotation() == LifetimeAnnotation::EagerMove || getLifetimeAnnotation(ty) == LifetimeAnnotation::EagerMove; }); From 5642a463784007cb6a47c5603c17d4219fe483ae Mon Sep 17 00:00:00 2001 From: Nate Chandler Date: Mon, 12 Sep 2022 08:17:49 -0700 Subject: [PATCH 30/37] Renamed _noEagerMove attribute. Avoid introducing extra terminology via an underscored attribute. rdar://99723104 --- docs/ReferenceGuides/UnderscoredAttributes.md | 6 +++--- include/swift/AST/Attr.def | 2 +- include/swift/AST/Decl.h | 2 +- include/swift/AST/DiagnosticsParse.def | 2 +- include/swift/AST/DiagnosticsSema.def | 2 +- include/swift/AST/LifetimeAnnotation.h | 2 +- lib/SILGen/SILGenProlog.cpp | 2 +- lib/Sema/TypeCheckAttr.cpp | 6 +++--- lib/Sema/TypeCheckDeclOverride.cpp | 2 +- test/SILGen/lexical_lifetime.swift | 2 +- test/SILOptimizer/inline_lifetime.sil | 6 +++--- test/attr/lexical.swift | 14 +++++++------- 12 files changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/ReferenceGuides/UnderscoredAttributes.md b/docs/ReferenceGuides/UnderscoredAttributes.md index 2aeef35a480b6..a81dfe5af29ca 100644 --- a/docs/ReferenceGuides/UnderscoredAttributes.md +++ b/docs/ReferenceGuides/UnderscoredAttributes.md @@ -159,7 +159,7 @@ that releases of the value may be hoisted without respect to deinit barriers. When applied to a type, indicates that all values which are _statically_ instances of that type are themselves `@_eagerMove` as above, unless overridden -with `@_lexical`. +with `@_noEagerMove`. Aggregates all of whose fields are `@_eagerMove` or trivial are inferred to be `@_eagerMove`. @@ -534,7 +534,7 @@ initializers from its superclass. This implies that all designated initializers overridden. This attribute is often printed alongside `@_hasMissingDesignatedInitializers` in this case. -## `@_lexical` +## `@_noEagerMove` When applied to a value, indicates that the value's lifetime is lexical, that releases of the value may not be hoisted over deinit barriers. @@ -544,7 +544,7 @@ This is the default behavior, unless the value's type is annotated annotation. When applied to a type, indicates that all values which are instances of that -type are themselves `@_lexical` as above. +type are themselves `@_noEagerMove` as above. This is the default behavior, unless the type annotated is an aggregate that consists entirely of `@_eagerMove` or trivial values, in which case the diff --git a/include/swift/AST/Attr.def b/include/swift/AST/Attr.def index 716b6a52c9a0c..602a5629cfc99 100644 --- a/include/swift/AST/Attr.def +++ b/include/swift/AST/Attr.def @@ -676,7 +676,7 @@ CONTEXTUAL_SIMPLE_DECL_ATTR(distributed, DistributedActor, APIBreakingToAdd | APIBreakingToRemove, 118) -SIMPLE_DECL_ATTR(_lexical, Lexical, +SIMPLE_DECL_ATTR(_noEagerMove, NoEagerMove, UserInaccessible | ABIStableToAdd | ABIStableToRemove | APIStableToAdd | APIStableToRemove | diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index d243ca4a94222..eb860c306449d 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -990,7 +990,7 @@ class alignas(1 << DeclAlignInBits) Decl : public ASTAllocated { auto &attrs = getAttrs(); if (attrs.hasAttribute()) return LifetimeAnnotation::EagerMove; - if (attrs.hasAttribute()) + if (attrs.hasAttribute()) return LifetimeAnnotation::Lexical; return LifetimeAnnotation::None; } diff --git a/include/swift/AST/DiagnosticsParse.def b/include/swift/AST/DiagnosticsParse.def index 2d92a2c56511e..d9c355095c0bc 100644 --- a/include/swift/AST/DiagnosticsParse.def +++ b/include/swift/AST/DiagnosticsParse.def @@ -516,7 +516,7 @@ ERROR(silfunc_and_silarg_have_incompatible_sil_value_ownership,none, "Function type specifies: '@%0'. SIL argument specifies: '@%1'.", (StringRef, StringRef)) ERROR(sil_arg_both_lexical_and_eagerMove,none, - "Function argument is annotated both @_eagerMove and @_lexical, " + "Function argument is annotated both @_eagerMove and @_noEagerMove, " "but these are incompatible alternatives", ()) ERROR(expected_sil_colon,none, "expected ':' before %0", (StringRef)) diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 879c2a20b20c2..0e068fb0c5a1c 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -3240,7 +3240,7 @@ ERROR(inherits_executor_without_async,none, ERROR(lifetime_invalid_global_scope,none, "%0 is only valid on methods", (DeclAttribute)) ERROR(eagermove_and_lexical_combined,none, - "@_eagerMove and @_lexical attributes are alternate styles of lifetimes " + "@_eagerMove and @_noEagerMove attributes are alternate styles of lifetimes " "and can't be combined", ()) ERROR(autoclosure_function_type,none, diff --git a/include/swift/AST/LifetimeAnnotation.h b/include/swift/AST/LifetimeAnnotation.h index 866703d96ef4b..34b1bced86d93 100644 --- a/include/swift/AST/LifetimeAnnotation.h +++ b/include/swift/AST/LifetimeAnnotation.h @@ -29,7 +29,7 @@ namespace swift { /// /// 1) None: No annotation has been applied. /// 2) EagerMove: The @_eagerMove attribute has been applied. -/// 3) Lexical: The @_lexical attribute has been applied. +/// 3) NoEagerMove: The @_noEagerMove attribute has been applied. struct LifetimeAnnotation { enum Case : uint8_t { None, diff --git a/lib/SILGen/SILGenProlog.cpp b/lib/SILGen/SILGenProlog.cpp index 70d5fd7853e7a..d4c34f4782870 100644 --- a/lib/SILGen/SILGenProlog.cpp +++ b/lib/SILGen/SILGenProlog.cpp @@ -272,7 +272,7 @@ struct ArgumentInitHelper { // Look for the following annotations on the function argument: // - @noImplicitCopy // - @_eagerMove - // - @_lexical + // - @_noEagerMove auto isNoImplicitCopy = pd->isNoImplicitCopy(); auto lifetime = SGF.F.getLifetime(pd, value->getType()); diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 25ba5958cab6b..ca1ca211e62b4 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -324,7 +324,7 @@ class AttributeChecker : public AttributeVisitor { bool visitLifetimeAttr(DeclAttribute *attr); void visitEagerMoveAttr(EagerMoveAttr *attr); - void visitLexicalAttr(LexicalAttr *attr); + void visitNoEagerMoveAttr(NoEagerMoveAttr *attr); void visitCompilerInitializedAttr(CompilerInitializedAttr *attr); @@ -6519,10 +6519,10 @@ void AttributeChecker::visitEagerMoveAttr(EagerMoveAttr *attr) { return; } -void AttributeChecker::visitLexicalAttr(LexicalAttr *attr) { +void AttributeChecker::visitNoEagerMoveAttr(NoEagerMoveAttr *attr) { if (visitLifetimeAttr(attr)) return; - // @_lexical and @_eagerMove are opposites and can't be combined. + // @_noEagerMove and @_eagerMove are opposites and can't be combined. if (D->getAttrs().hasAttribute()) { diagnoseAndRemoveAttr(attr, diag::eagermove_and_lexical_combined); return; diff --git a/lib/Sema/TypeCheckDeclOverride.cpp b/lib/Sema/TypeCheckDeclOverride.cpp index 481ca440d5ac6..5412ba654be88 100644 --- a/lib/Sema/TypeCheckDeclOverride.cpp +++ b/lib/Sema/TypeCheckDeclOverride.cpp @@ -1620,7 +1620,7 @@ namespace { UNINTERESTING_ATTR(AlwaysEmitConformanceMetadata) UNINTERESTING_ATTR(EagerMove) - UNINTERESTING_ATTR(Lexical) + UNINTERESTING_ATTR(NoEagerMove) #undef UNINTERESTING_ATTR void visitAvailableAttr(AvailableAttr *attr) { diff --git a/test/SILGen/lexical_lifetime.swift b/test/SILGen/lexical_lifetime.swift index d9637cb11deff..990bd0d0a4ab2 100644 --- a/test/SILGen/lexical_lifetime.swift +++ b/test/SILGen/lexical_lifetime.swift @@ -134,7 +134,7 @@ extension C { // CHECK: {{bb[0-9]+}}({{%[^,]+}} : @_lexical @owned $C): // CHECK-LABEL: } // end sil function 'lexical_method_attr' @_silgen_name("lexical_method_attr") - @_lexical + @_noEagerMove __consuming func lexical_method_attr() {} diff --git a/test/SILOptimizer/inline_lifetime.sil b/test/SILOptimizer/inline_lifetime.sil index 298eeebd6089e..f3686c70540c2 100644 --- a/test/SILOptimizer/inline_lifetime.sil +++ b/test/SILOptimizer/inline_lifetime.sil @@ -34,7 +34,7 @@ struct InferredEagerD2S1 { var i4: InferredEagerD1S4 } -@_lexical +@_noEagerMove struct LexicalS { var i: InferredEagerD2S1 } @@ -47,11 +47,11 @@ struct InferredLexicalD1S1 { } struct InferredLexicalD1S2 { - @_lexical var i: InferredEagerD2S1 + @_noEagerMove var i: InferredEagerD2S1 } struct InferredLexicalD1S3 { - @_lexical var i: InferredEagerD2S1 + @_noEagerMove var i: InferredEagerD2S1 var j: InferredEagerD2S1 } diff --git a/test/attr/lexical.swift b/test/attr/lexical.swift index 51eabb5023df4..57d0b6bfa647c 100644 --- a/test/attr/lexical.swift +++ b/test/attr/lexical.swift @@ -9,35 +9,35 @@ enum E {} @_eagerMove // expected-error {{'@_eagerMove' attribute cannot be applied to this declaration}} typealias EagerTuple = (C, C) -func foo(@_lexical @_eagerMove _ e: E) {} // expected-error {{@_eagerMove and @_lexical attributes are alternate styles of lifetimes and can't be combined}} +func foo(@_noEagerMove @_eagerMove _ e: E) {} // expected-error {{@_eagerMove and @_noEagerMove attributes are alternate styles of lifetimes and can't be combined}} -func bar(@_lexical _ s: S) {} // okay +func bar(@_noEagerMove _ s: S) {} // okay func baz(@_eagerMove _ c: C) {} // okay @_eagerMove // expected-error {{@_eagerMove is only valid on methods}} func bazzoo(_ c: C) {} -@_lexical // expected-error {{@_lexical is only valid on methods}} +@_noEagerMove // expected-error {{@_noEagerMove is only valid on methods}} func bazzoozoo(_ c: C) {} extension C { @_eagerMove func pazzoo() {} - @_lexical + @_noEagerMove func pazzoozoo() {} } struct S2 { @_eagerMove let c: C // okay - @_lexical let e: E // okay + @_noEagerMove let e: E // okay } func foo() { - @_lexical let s1 = S() + @_noEagerMove let s1 = S() @_eagerMove var s2 = S() - @_lexical @_eagerMove let s3 = S() // expected-error {{@_eagerMove and @_lexical attributes are alternate styles of lifetimes and can't be combined}} + @_noEagerMove @_eagerMove let s3 = S() // expected-error {{@_eagerMove and @_noEagerMove attributes are alternate styles of lifetimes and can't be combined}} _ = s1 s2 = S() _ = s2 From d8f77c601b9ce71c6b4f76dbd05626a71e84cc75 Mon Sep 17 00:00:00 2001 From: Artem Chikin Date: Fri, 26 Aug 2022 13:44:25 -0700 Subject: [PATCH 31/37] [Dependency Scanner] Use Clang scanner's module name query API instead of creating a dummy file --- .../ClangModuleDependencyScanner.cpp | 75 +++---------------- 1 file changed, 10 insertions(+), 65 deletions(-) diff --git a/lib/ClangImporter/ClangModuleDependencyScanner.cpp b/lib/ClangImporter/ClangModuleDependencyScanner.cpp index f9d77ae848131..ae904a59444c2 100644 --- a/lib/ClangImporter/ClangModuleDependencyScanner.cpp +++ b/lib/ClangImporter/ClangModuleDependencyScanner.cpp @@ -29,12 +29,6 @@ using namespace clang::tooling; using namespace clang::tooling::dependencies; class swift::ClangModuleDependenciesCacheImpl { - /// Cache the names of the files used for the "import hack" to compute module - /// dependencies. - /// FIXME: This should go away once Clang's dependency scanning library - /// can scan by module name. - llvm::StringMap importHackFileCache; - public: /// Set containing all of the Clang modules that have already been seen. llvm::StringSet<> alreadySeen; @@ -44,44 +38,11 @@ class swift::ClangModuleDependenciesCacheImpl { DependencyScanningTool tool; ClangModuleDependenciesCacheImpl() - : importHackFileCache(), service(ScanningMode::DependencyDirectivesScan, - ScanningOutputFormat::Full, - clang::CASOptions(), nullptr, nullptr), + : service(ScanningMode::DependencyDirectivesScan, + ScanningOutputFormat::Full, clang::CASOptions(), nullptr, nullptr), tool(service) {} - ~ClangModuleDependenciesCacheImpl(); - - /// Retrieve the name of the file used for the "import hack" that is - /// used to scan the dependencies of a Clang module. - llvm::ErrorOr getImportHackFile(StringRef moduleName); }; -ClangModuleDependenciesCacheImpl::~ClangModuleDependenciesCacheImpl() { - if (!importHackFileCache.empty()) { - for (auto& it: importHackFileCache) { - llvm::sys::fs::remove(it.second); - } - } -} - -llvm::ErrorOr ClangModuleDependenciesCacheImpl::getImportHackFile(StringRef moduleName) { - auto cacheIt = importHackFileCache.find(moduleName.str()); - if (cacheIt != importHackFileCache.end()) - return cacheIt->second; - - // Create a temporary file. - int resultFD; - SmallString<128> resultPath; - if (auto error = llvm::sys::fs::createTemporaryFile( - "import-hack-" + moduleName.str(), "c", resultFD, resultPath)) - return error; - - llvm::raw_fd_ostream out(resultFD, /*shouldClose=*/true); - out << "#pragma clang module import " << moduleName.str() << ";\n"; - llvm::sys::RemoveFileOnSignal(resultPath); - importHackFileCache.insert(std::make_pair(moduleName, resultPath.str().str())); - return importHackFileCache[moduleName]; -} - // Add search paths. // Note: This is handled differently for the Clang importer itself, which // adds search paths to Clang's data structures rather than to its @@ -103,7 +64,7 @@ static void addSearchPathInvocationArguments( /// Create the command line for Clang dependency scanning. static std::vector getClangDepScanningInvocationArguments( ASTContext &ctx, - StringRef sourceFileName) { + Optional sourceFileName = None) { std::vector commandLineArgs; // Form the basic command line. @@ -116,7 +77,10 @@ static std::vector getClangDepScanningInvocationArguments( commandLineArgs.begin(), commandLineArgs.end(), ""); assert(sourceFilePos != commandLineArgs.end()); - *sourceFilePos = sourceFileName.str(); + if (sourceFileName.hasValue()) + *sourceFilePos = sourceFileName->str(); + else + commandLineArgs.erase(sourceFilePos); // HACK! Drop the -fmodule-format= argument and the one that // precedes it. @@ -140,18 +104,6 @@ static std::vector getClangDepScanningInvocationArguments( *syntaxOnlyPos = "-c"; } - // HACK: Stolen from ClangScanDeps.cpp - commandLineArgs.push_back("-o"); - commandLineArgs.push_back("/dev/null"); - commandLineArgs.push_back("-M"); - commandLineArgs.push_back("-MT"); - commandLineArgs.push_back("import-hack.o"); - commandLineArgs.push_back("-Xclang"); - commandLineArgs.push_back("-Eonly"); - commandLineArgs.push_back("-Xclang"); - commandLineArgs.push_back("-sys-header-deps"); - commandLineArgs.push_back("-Wno-error"); - return commandLineArgs; } @@ -323,16 +275,9 @@ Optional ClangImporter::getModuleDependencies( // Retrieve or create the shared state. auto clangImpl = getOrCreateClangImpl(cache); - // HACK! Replace the module import buffer name with the source file hack. - auto importHackFile = clangImpl->getImportHackFile(moduleName); - if (!importHackFile) { - // FIXME: Emit a diagnostic here. - return None; - } - // Determine the command-line arguments for dependency scanning. std::vector commandLineArgs = - getClangDepScanningInvocationArguments(ctx, *importHackFile); + getClangDepScanningInvocationArguments(ctx); // The Swift compiler does not have a concept of a working directory. // It is instead handled by the Swift driver by resolving relative paths // according to the driver's notion of a working directory. On the other hand, @@ -354,7 +299,7 @@ Optional ClangImporter::getModuleDependencies( } auto clangDependencies = clangImpl->tool.getFullDependencies( - commandLineArgs, workingDir, clangImpl->alreadySeen); + commandLineArgs, workingDir, clangImpl->alreadySeen, moduleName); if (!clangDependencies) { auto errorStr = toString(clangDependencies.takeError()); // We ignore the "module 'foo' not found" error, the Swift dependency @@ -407,7 +352,7 @@ bool ClangImporter::addBridgingHeaderDependencies( // Determine the command-line arguments for dependency scanning. std::vector commandLineArgs = - getClangDepScanningInvocationArguments(ctx, bridgingHeader); + getClangDepScanningInvocationArguments(ctx, StringRef(bridgingHeader)); std::string workingDir = ctx.SourceMgr.getFileSystem()->getCurrentWorkingDirectory().get(); From d5f6b128304e534d7f946ea9689be2f3c5dd2b16 Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Mon, 12 Sep 2022 11:06:57 -0700 Subject: [PATCH 32/37] Avoid generating address phis in LoopUnroll --- lib/SIL/Utils/LoopInfo.cpp | 12 +++ .../LoopTransforms/LoopUnroll.cpp | 26 ++++++ test/SILOptimizer/loop_unroll_ossa.sil | 85 +++++++++++++++++-- 3 files changed, 115 insertions(+), 8 deletions(-) diff --git a/lib/SIL/Utils/LoopInfo.cpp b/lib/SIL/Utils/LoopInfo.cpp index e0aadd90535f5..3ae5990719d25 100644 --- a/lib/SIL/Utils/LoopInfo.cpp +++ b/lib/SIL/Utils/LoopInfo.cpp @@ -15,6 +15,7 @@ #include "swift/SIL/Dominance.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/CFG.h" +#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" #include "llvm/Analysis/LoopInfoImpl.h" #include "llvm/Support/Debug.h" @@ -36,6 +37,17 @@ SILLoopInfo::SILLoopInfo(SILFunction *F, DominanceInfo *DT) : Dominance(DT) { } bool SILLoop::canDuplicate(SILInstruction *I) const { + SinkAddressProjections sinkProj; + for (auto res : I->getResults()) { + if (!res->getType().isAddress()) { + continue; + } + auto canSink = sinkProj.analyzeAddressProjections(I); + if (!canSink) { + return false; + } + } + // The deallocation of a stack allocation must be in the loop, otherwise the // deallocation will be fed by a phi node of two allocations. if (I->isAllocatingStack()) { diff --git a/lib/SILOptimizer/LoopTransforms/LoopUnroll.cpp b/lib/SILOptimizer/LoopTransforms/LoopUnroll.cpp index e95881f07a715..2311f0a55339d 100644 --- a/lib/SILOptimizer/LoopTransforms/LoopUnroll.cpp +++ b/lib/SILOptimizer/LoopTransforms/LoopUnroll.cpp @@ -19,6 +19,7 @@ #include "swift/SILOptimizer/Analysis/LoopAnalysis.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" #include "swift/SILOptimizer/Utils/PerformanceInlinerUtils.h" #include "swift/SILOptimizer/Utils/SILInliner.h" #include "swift/SILOptimizer/Utils/SILSSAUpdater.h" @@ -48,6 +49,8 @@ class LoopCloner : public SILCloner { /// Clone the basic blocks in the loop. void cloneLoop(); + void sinkAddressProjections(); + // Update SSA helper. void collectLoopLiveOutValues( DenseMap> &LoopLiveOutValues); @@ -69,10 +72,33 @@ class LoopCloner : public SILCloner { } // end anonymous namespace +void LoopCloner::sinkAddressProjections() { + SinkAddressProjections sinkProj; + for (auto *bb : Loop->getBlocks()) { + for (auto &inst : *bb) { + for (auto res : inst.getResults()) { + if (!res->getType().isAddress()) { + continue; + } + for (auto use : res->getUses()) { + auto *user = use->getUser(); + if (Loop->contains(user)) { + continue; + } + bool canSink = sinkProj.analyzeAddressProjections(&inst); + assert(canSink); + sinkProj.cloneProjections(); + } + } + } + } +} + void LoopCloner::cloneLoop() { SmallVector ExitBlocks; Loop->getExitBlocks(ExitBlocks); + sinkAddressProjections(); // Clone the entire loop. cloneReachableBlocks(Loop->getHeader(), ExitBlocks, /*insertAfter*/Loop->getLoopLatch()); diff --git a/test/SILOptimizer/loop_unroll_ossa.sil b/test/SILOptimizer/loop_unroll_ossa.sil index eaeaf9fee93b7..2fe6f6e628a06 100644 --- a/test/SILOptimizer/loop_unroll_ossa.sil +++ b/test/SILOptimizer/loop_unroll_ossa.sil @@ -21,6 +21,22 @@ struct UInt64 { var value: Builtin.Int64 } +class Klass { + var id: Int64 +} + +struct WrapperStruct { + var val: Klass +} + +sil [_semantics "array.check_subscript"] @checkbounds : $@convention(method) (Int64, Bool, @guaranteed Array) -> _DependenceToken + +sil [_semantics "array.get_element"] @getElement : $@convention(method) (Int64, Bool, _DependenceToken, @guaranteed Array) -> @owned Klass + +sil @use_inguaranteed : $@convention(thin) (@in_guaranteed Klass) -> () + +sil @get_wrapper_struct : $@convention(thin) (@inout WrapperStruct) -> () + // CHECK-LABEL: sil [ossa] @loop_unroll_1 : // CHECK: bb0 // CHECK: br bb1 @@ -495,14 +511,6 @@ bb4: return %8 : $() } -class Klass { - var id: Int64 -} - -sil [_semantics "array.check_subscript"] @checkbounds : $@convention(method) (Int64, Bool, @guaranteed Array) -> _DependenceToken - -sil [_semantics "array.get_element"] @getElement : $@convention(method) (Int64, Bool, _DependenceToken, @guaranteed Array) -> @owned Klass - // CHECK-LABEL: sil [ossa] @sumKlassId : // CHECK: [[FUNC:%.*]] = function_ref @getElement : // CHECK: apply [[FUNC]] @@ -724,3 +732,64 @@ bb3: return %tup : $() } +// Ensure we don't trigger presence of address phis verification +sil [ossa] @unroll_with_addr_proj1 : $@convention(thin) (@in_guaranteed WrapperStruct) -> () { +bb0(%0 : $*WrapperStruct): + %1 = integer_literal $Builtin.Int64, 1 + %2 = integer_literal $Builtin.Int64, 4 + %3 = integer_literal $Builtin.Int1, -1 + br bb1(%1 : $Builtin.Int64, %2 : $Builtin.Int64) + +bb1(%5 : $Builtin.Int64, %6 : $Builtin.Int64): + %7 = builtin "sadd_with_overflow_Int64"(%5 : $Builtin.Int64, %1 : $Builtin.Int64, %3 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) + %8 = tuple_extract %7 : $(Builtin.Int64, Builtin.Int1), 0 + %9 = builtin "smul_with_overflow_Int64"(%6 : $Builtin.Int64, %2 : $Builtin.Int64, %3 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) + %10 = tuple_extract %9 : $(Builtin.Int64, Builtin.Int1), 0 + %11 = builtin "cmp_slt_Int64"(%8 : $Builtin.Int64, %2 : $Builtin.Int64) : $Builtin.Int1 + %12 = struct_element_addr %0 : $*WrapperStruct, #WrapperStruct.val + cond_br %11, bb2, bb3 + +bb2: + br bb1(%8 : $Builtin.Int64, %10 : $Builtin.Int64) + +bb3: + %15 = function_ref @use_inguaranteed : $@convention(thin) (@in_guaranteed Klass) -> () + %16 = apply %15(%12) : $@convention(thin) (@in_guaranteed Klass) -> () + %17 = tuple () + return %17 : $() +} + +// Ensure we don't trigger presence of address phis verification +sil [ossa] @unroll_with_addr_proj2 : $@convention(thin) (@in_guaranteed WrapperStruct) -> () { +bb0(%0 : $*WrapperStruct): + %1 = alloc_stack $WrapperStruct + %2 = integer_literal $Builtin.Int64, 1 + %3 = integer_literal $Builtin.Int64, 4 + %4 = integer_literal $Builtin.Int1, -1 + %5 = load [copy] %0 : $*WrapperStruct + store %5 to [init] %1 : $*WrapperStruct + br bb1(%2 : $Builtin.Int64, %3 : $Builtin.Int64) + +bb1(%8 : $Builtin.Int64, %9 : $Builtin.Int64): + %10 = builtin "sadd_with_overflow_Int64"(%8 : $Builtin.Int64, %2 : $Builtin.Int64, %4 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) + %11 = tuple_extract %10 : $(Builtin.Int64, Builtin.Int1), 0 + %12 = builtin "smul_with_overflow_Int64"(%9 : $Builtin.Int64, %3 : $Builtin.Int64, %4 : $Builtin.Int1) : $(Builtin.Int64, Builtin.Int1) + %13 = tuple_extract %12 : $(Builtin.Int64, Builtin.Int1), 0 + %14 = builtin "cmp_slt_Int64"(%11 : $Builtin.Int64, %3 : $Builtin.Int64) : $Builtin.Int1 + %15 = function_ref @get_wrapper_struct : $@convention(thin) (@inout WrapperStruct) -> () + %16 = apply %15(%1) : $@convention(thin) (@inout WrapperStruct) -> () + %17 = struct_element_addr %1 : $*WrapperStruct, #WrapperStruct.val + cond_br %14, bb2, bb3 + +bb2: + br bb1(%11 : $Builtin.Int64, %13 : $Builtin.Int64) + +bb3: + %20 = function_ref @use_inguaranteed : $@convention(thin) (@in_guaranteed Klass) -> () + %21 = apply %20(%17) : $@convention(thin) (@in_guaranteed Klass) -> () + destroy_addr %1 : $*WrapperStruct + dealloc_stack %1 : $*WrapperStruct + %24 = tuple () + return %24 : $() +} + From 035f062e6961e8e4cf8abb60041518a264b12ccc Mon Sep 17 00:00:00 2001 From: Meghana Gupta Date: Mon, 12 Sep 2022 11:12:16 -0700 Subject: [PATCH 33/37] Ban address phis in non-OSSA SIL --- lib/SIL/Verifier/SILVerifier.cpp | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/lib/SIL/Verifier/SILVerifier.cpp b/lib/SIL/Verifier/SILVerifier.cpp index 0e79471846d0c..db3990d1f2ad9 100644 --- a/lib/SIL/Verifier/SILVerifier.cpp +++ b/lib/SIL/Verifier/SILVerifier.cpp @@ -985,24 +985,6 @@ class SILVerifier : public SILVerifierBase { return InstNumbers[a] < InstNumbers[b]; } - // FIXME: For sanity, address-type phis should be prohibited at all SIL - // stages. However, the optimizer currently breaks the invariant in three - // places: - // 1. Normal Simplify CFG during conditional branch simplification - // (sneaky jump threading). - // 2. Simplify CFG via Jump Threading. - // 3. Loop Rotation. - // - // BasicBlockCloner::canCloneInstruction and sinkAddressProjections is - // designed to avoid this issue, we just need to make sure all passes use it - // correctly. - // - // Minimally, we must prevent address-type phis as long as access markers are - // preserved. A goal is to preserve access markers in OSSA. - bool prohibitAddressPhis() { - return F.hasOwnership(); - } - void visitSILPhiArgument(SILPhiArgument *arg) { // Verify that the `isPhiArgument` property is sound: // - Phi arguments come from branches. @@ -1026,7 +1008,7 @@ class SILVerifier : public SILVerifierBase { "All phi argument inputs must be from branches."); } } - if (arg->isPhi() && prohibitAddressPhis()) { + if (arg->isPhi()) { // As a property of well-formed SIL, we disallow address-type // phis. Supporting them would prevent reliably reasoning about the // underlying storage of memory access. This reasoning is important for From 26e5742fe7f56c870c3bc67cb59f8ec6fc4b5f4b Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 12 Sep 2022 12:39:22 -0700 Subject: [PATCH 34/37] [interop][SwiftToCxx] emit generic type traits for C++ types bridged into Swift and then back to C++ --- lib/PrintAsClang/ClangSyntaxPrinter.cpp | 30 ++++++++++ lib/PrintAsClang/ClangSyntaxPrinter.h | 3 + lib/PrintAsClang/DeclAndTypePrinter.h | 2 + lib/PrintAsClang/ModuleContentsWriter.cpp | 7 +++ lib/PrintAsClang/PrintClangClassType.cpp | 3 +- lib/PrintAsClang/PrintClangFunction.cpp | 24 +------- lib/PrintAsClang/PrintClangValueType.cpp | 55 +++++++++++++++---- lib/PrintAsClang/PrintClangValueType.h | 9 ++- .../SwiftToClangInteropContext.cpp | 7 +++ lib/PrintAsClang/SwiftToClangInteropContext.h | 13 +++++ .../SwiftShims/_SwiftCxxInteroperability.h | 5 ++ .../bridge-cxx-struct-back-to-cxx.swift | 30 ++++++++++ 12 files changed, 154 insertions(+), 34 deletions(-) diff --git a/lib/PrintAsClang/ClangSyntaxPrinter.cpp b/lib/PrintAsClang/ClangSyntaxPrinter.cpp index 21dd043bc2bd7..b4d72599a8afb 100644 --- a/lib/PrintAsClang/ClangSyntaxPrinter.cpp +++ b/lib/PrintAsClang/ClangSyntaxPrinter.cpp @@ -15,6 +15,9 @@ #include "swift/AST/Decl.h" #include "swift/AST/Module.h" #include "swift/AST/SwiftNameTranslation.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/NestedNameSpecifier.h" using namespace swift; using namespace cxx_synthesis; @@ -80,8 +83,35 @@ bool ClangSyntaxPrinter::printNominalTypeOutsideMemberDeclTemplateSpecifiers( return false; } +void ClangSyntaxPrinter::printNominalClangTypeReference( + const clang::Decl *typeDecl) { + auto &clangCtx = typeDecl->getASTContext(); + clang::PrintingPolicy pp(clangCtx.getLangOpts()); + const auto *NS = clang::NestedNameSpecifier::getRequiredQualification( + clangCtx, clangCtx.getTranslationUnitDecl(), + typeDecl->getLexicalDeclContext()); + if (NS) + NS->print(os, pp); + assert(cast(typeDecl)->getDeclName().isIdentifier()); + os << cast(typeDecl)->getName(); + if (auto *ctd = dyn_cast(typeDecl)) { + if (ctd->getTemplateArgs().size()) { + os << '<'; + llvm::interleaveComma(ctd->getTemplateArgs().asArray(), os, + [&](const clang::TemplateArgument &arg) { + arg.print(pp, os, /*IncludeType=*/true); + }); + os << '>'; + } + } +} + void ClangSyntaxPrinter::printNominalTypeReference( const NominalTypeDecl *typeDecl, const ModuleDecl *moduleContext) { + if (typeDecl->hasClangNode()) { + printNominalClangTypeReference(typeDecl->getClangDecl()); + return; + } printModuleNamespaceQualifiersIfNeeded(typeDecl->getModuleContext(), moduleContext); // FIXME: Full qualifiers for nested types? diff --git a/lib/PrintAsClang/ClangSyntaxPrinter.h b/lib/PrintAsClang/ClangSyntaxPrinter.h index 238339702d0ca..c33dc0fddc4c0 100644 --- a/lib/PrintAsClang/ClangSyntaxPrinter.h +++ b/lib/PrintAsClang/ClangSyntaxPrinter.h @@ -90,6 +90,9 @@ class ClangSyntaxPrinter { void printNominalTypeReference(const NominalTypeDecl *typeDecl, const ModuleDecl *moduleContext); + /// Print out the C++ record qualifier for the given C++ record. + void printNominalClangTypeReference(const clang::Decl *typeDecl); + /// Print out the C++ class access qualifier for the given Swift type /// declaration. /// diff --git a/lib/PrintAsClang/DeclAndTypePrinter.h b/lib/PrintAsClang/DeclAndTypePrinter.h index 8c4c89a68455d..41a59af07a93c 100644 --- a/lib/PrintAsClang/DeclAndTypePrinter.h +++ b/lib/PrintAsClang/DeclAndTypePrinter.h @@ -72,6 +72,8 @@ class DeclAndTypePrinter { requiresExposedAttribute(requiresExposedAttribute), outputLang(outputLang) {} + SwiftToClangInteropContext &getInteropContext() { return interopContext; } + /// Returns true if \p VD should be included in a compatibility header for /// the options the printer was constructed with. bool shouldInclude(const ValueDecl *VD); diff --git a/lib/PrintAsClang/ModuleContentsWriter.cpp b/lib/PrintAsClang/ModuleContentsWriter.cpp index 48547450d4da3..bf145b4fd2034 100644 --- a/lib/PrintAsClang/ModuleContentsWriter.cpp +++ b/lib/PrintAsClang/ModuleContentsWriter.cpp @@ -18,6 +18,7 @@ #include "PrimitiveTypeMapping.h" #include "PrintClangValueType.h" #include "PrintSwiftToClangCoreScaffold.h" +#include "SwiftToClangInteropContext.h" #include "swift/AST/ExistentialLayout.h" #include "swift/AST/Module.h" @@ -677,8 +678,14 @@ class ModuleWriter { } printer.printAdHocCategory(make_range(groupBegin, delayedMembers.end())); } + // Print any out of line definitions. os << outOfLineDefinitionsOS.str(); + + // Print any additional metadata for referenced C++ types. + for (const auto *typeDecl : + printer.getInteropContext().getEmittedClangTypeDecls()) + ClangValueTypePrinter::printClangTypeSwiftGenericTraits(os, typeDecl, &M); } }; } // end anonymous namespace diff --git a/lib/PrintAsClang/PrintClangClassType.cpp b/lib/PrintAsClang/PrintClangClassType.cpp index 7d09c31a404cc..e9e18c1054756 100644 --- a/lib/PrintAsClang/PrintClangClassType.cpp +++ b/lib/PrintAsClang/PrintClangClassType.cpp @@ -95,7 +95,8 @@ void ClangClassTypePrinter::printClassTypeDecl( }); ClangValueTypePrinter::printTypeGenericTraits( - os, typeDecl, typeMetadataFuncName, /*genericRequirements=*/{}); + os, typeDecl, typeMetadataFuncName, /*genericRequirements=*/{}, + typeDecl->getModuleContext()); } void ClangClassTypePrinter::printClassTypeReturnScaffold( diff --git a/lib/PrintAsClang/PrintClangFunction.cpp b/lib/PrintAsClang/PrintClangFunction.cpp index 1a78215dfc28f..dbfec1cae287b 100644 --- a/lib/PrintAsClang/PrintClangFunction.cpp +++ b/lib/PrintAsClang/PrintClangFunction.cpp @@ -28,8 +28,6 @@ #include "swift/ClangImporter/ClangImporter.h" #include "swift/IRGen/IRABIDetailsProvider.h" #include "clang/AST/ASTContext.h" -#include "clang/AST/DeclTemplate.h" -#include "clang/AST/NestedNameSpecifier.h" #include "llvm/ADT/STLExtras.h" using namespace swift; @@ -108,26 +106,7 @@ class ClangTypeHandler { } void printTypeName(raw_ostream &os) const { - auto &clangCtx = typeDecl->getASTContext(); - clang::PrintingPolicy pp(clangCtx.getLangOpts()); - const auto *NS = clang::NestedNameSpecifier::getRequiredQualification( - clangCtx, clangCtx.getTranslationUnitDecl(), - typeDecl->getLexicalDeclContext()); - if (NS) - NS->print(os, pp); - assert(cast(typeDecl)->getDeclName().isIdentifier()); - os << cast(typeDecl)->getName(); - if (auto *ctd = - dyn_cast(typeDecl)) { - if (ctd->getTemplateArgs().size()) { - os << '<'; - llvm::interleaveComma(ctd->getTemplateArgs().asArray(), os, - [&](const clang::TemplateArgument &arg) { - arg.print(pp, os, /*IncludeType=*/true); - }); - os << '>'; - } - } + ClangSyntaxPrinter(os).printNominalClangTypeReference(typeDecl); } void printReturnScaffold(raw_ostream &os, @@ -316,6 +295,7 @@ class CFunctionSignatureTypePrinter handler.printTypeName(os); if (typeUseKind == FunctionSignatureTypeUse::ParamType) os << '&'; + interopContext.recordEmittedClangTypeDecl(decl); return ClangRepresentation::representable; } diff --git a/lib/PrintAsClang/PrintClangValueType.cpp b/lib/PrintAsClang/PrintClangValueType.cpp index c66be135ed39f..a9660557b93ba 100644 --- a/lib/PrintAsClang/PrintClangValueType.cpp +++ b/lib/PrintAsClang/PrintClangValueType.cpp @@ -406,7 +406,8 @@ void ClangValueTypePrinter::printValueTypeDecl( printCValueTypeStorageStruct(cPrologueOS, typeDecl, *typeSizeAlign); printTypeGenericTraits(os, typeDecl, typeMetadataFuncName, - typeMetadataFuncGenericParams); + typeMetadataFuncGenericParams, + typeDecl->getModuleContext()); } void ClangValueTypePrinter::printParameterCxxToCUseScaffold( @@ -456,24 +457,47 @@ void ClangValueTypePrinter::printValueTypeReturnScaffold( os << " });\n"; } +void ClangValueTypePrinter::printClangTypeSwiftGenericTraits( + raw_ostream &os, const NominalTypeDecl *typeDecl, + const ModuleDecl *moduleContext) { + assert(typeDecl->hasClangNode()); + // Do not reference unspecialized templates. + if (isa(typeDecl->getClangDecl())) + return; + auto typeMetadataFunc = irgen::LinkEntity::forTypeMetadataAccessFunction( + typeDecl->getDeclaredType()->getCanonicalType()); + std::string typeMetadataFuncName = typeMetadataFunc.mangleAsString(); + printTypeGenericTraits(os, typeDecl, typeMetadataFuncName, + /*typeMetadataFuncRequirements=*/{}, moduleContext); +} + void ClangValueTypePrinter::printTypeGenericTraits( raw_ostream &os, const NominalTypeDecl *typeDecl, StringRef typeMetadataFuncName, - ArrayRef typeMetadataFuncRequirements) { + ArrayRef typeMetadataFuncRequirements, + const ModuleDecl *moduleContext) { ClangSyntaxPrinter printer(os); // FIXME: avoid popping out of the module's namespace here. os << "} // end namespace \n\n"; os << "namespace swift {\n"; + if (typeDecl->hasClangNode()) { + /// Print a reference to the type metadata fucntion for a C++ type. + ClangSyntaxPrinter(os).printNamespace( + cxx_synthesis::getCxxImplNamespaceName(), [&](raw_ostream &os) { + ClangSyntaxPrinter(os).printCTypeMetadataTypeFunction( + typeDecl, typeMetadataFuncName, typeMetadataFuncRequirements); + }); + } + os << "#pragma clang diagnostic push\n"; os << "#pragma clang diagnostic ignored \"-Wc++17-extensions\"\n"; if (typeMetadataFuncRequirements.empty()) { // FIXME: generic type support. os << "template<>\n"; os << "static inline const constexpr bool isUsableInGenericContext<"; - printer.printBaseName(typeDecl->getModuleContext()); - os << "::"; - printer.printBaseName(typeDecl); + printer.printNominalTypeReference(typeDecl, + /*moduleContext=*/nullptr); os << "> = true;\n"; } if (printer.printNominalTypeOutsideMemberDeclTemplateSpecifiers(typeDecl)) @@ -484,8 +508,11 @@ void ClangValueTypePrinter::printTypeGenericTraits( os << "> {\n"; os << " static inline void * _Nonnull getTypeMetadata() {\n"; os << " return "; - printer.printBaseName(typeDecl->getModuleContext()); - os << "::" << cxx_synthesis::getCxxImplNamespaceName() << "::"; + if (!typeDecl->hasClangNode()) { + printer.printBaseName(typeDecl->getModuleContext()); + os << "::"; + } + os << cxx_synthesis::getCxxImplNamespaceName() << "::"; ClangSyntaxPrinter(os).printSwiftTypeMetadataAccessFunctionCall( typeMetadataFuncName, typeMetadataFuncRequirements); os << "._0;\n"; @@ -493,7 +520,15 @@ void ClangValueTypePrinter::printTypeGenericTraits( os << "namespace " << cxx_synthesis::getCxxImplNamespaceName() << "{\n"; - if (!isa(typeDecl) && typeMetadataFuncRequirements.empty()) { + if (typeDecl->hasClangNode()) { + os << "template<>\n"; + os << "static inline const constexpr bool isSwiftBridgedCxxRecord<"; + printer.printNominalClangTypeReference(typeDecl->getClangDecl()); + os << "> = true;\n"; + } + + if (!isa(typeDecl) && !typeDecl->hasClangNode() && + typeMetadataFuncRequirements.empty()) { // FIXME: generic support. os << "template<>\n"; os << "static inline const constexpr bool isValueType<"; @@ -512,7 +547,7 @@ void ClangValueTypePrinter::printTypeGenericTraits( } // FIXME: generic support. - if (typeMetadataFuncRequirements.empty()) { + if (!typeDecl->hasClangNode() && typeMetadataFuncRequirements.empty()) { os << "template<>\n"; os << "struct implClassFor<"; printer.printBaseName(typeDecl->getModuleContext()); @@ -528,6 +563,6 @@ void ClangValueTypePrinter::printTypeGenericTraits( os << "#pragma clang diagnostic pop\n"; os << "} // namespace swift\n"; os << "\nnamespace "; - printer.printBaseName(typeDecl->getModuleContext()); + printer.printBaseName(moduleContext); os << " {\n"; } diff --git a/lib/PrintAsClang/PrintClangValueType.h b/lib/PrintAsClang/PrintClangValueType.h index b7b9d611de817..d8e2249ff17f6 100644 --- a/lib/PrintAsClang/PrintClangValueType.h +++ b/lib/PrintAsClang/PrintClangValueType.h @@ -93,10 +93,17 @@ class ClangValueTypePrinter { static void printTypeGenericTraits( raw_ostream &os, const NominalTypeDecl *typeDecl, StringRef typeMetadataFuncName, - ArrayRef typeMetadataFuncRequirements); + ArrayRef typeMetadataFuncRequirements, + const ModuleDecl *moduleContext); static void forwardDeclType(raw_ostream &os, const NominalTypeDecl *typeDecl); + /// Print out the type traits that allow a C++ type be used a Swift generic + /// context. + static void printClangTypeSwiftGenericTraits(raw_ostream &os, + const NominalTypeDecl *typeDecl, + const ModuleDecl *moduleContext); + private: raw_ostream &os; raw_ostream &cPrologueOS; diff --git a/lib/PrintAsClang/SwiftToClangInteropContext.cpp b/lib/PrintAsClang/SwiftToClangInteropContext.cpp index c04955b35c9d1..86fbd0df2c884 100644 --- a/lib/PrintAsClang/SwiftToClangInteropContext.cpp +++ b/lib/PrintAsClang/SwiftToClangInteropContext.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "SwiftToClangInteropContext.h" +#include "swift/AST/Decl.h" #include "swift/IRGen/IRABIDetailsProvider.h" using namespace swift; @@ -33,3 +34,9 @@ void SwiftToClangInteropContext::runIfStubForDeclNotEmitted( if (result.second) function(); } + +void SwiftToClangInteropContext::recordEmittedClangTypeDecl( + const NominalTypeDecl *typeDecl) { + assert(typeDecl->hasClangNode()); + referencedClangTypeDecls.insert(typeDecl); +} diff --git a/lib/PrintAsClang/SwiftToClangInteropContext.h b/lib/PrintAsClang/SwiftToClangInteropContext.h index 58b1f231af8e5..5a9e7750160a0 100644 --- a/lib/PrintAsClang/SwiftToClangInteropContext.h +++ b/lib/PrintAsClang/SwiftToClangInteropContext.h @@ -13,7 +13,9 @@ #ifndef SWIFT_PRINTASCLANG_SWIFTTOCLANGINTEROPCONTEXT_H #define SWIFT_PRINTASCLANG_SWIFTTOCLANGINTEROPCONTEXT_H +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SetVector.h" #include "llvm/ADT/StringSet.h" #include @@ -23,6 +25,7 @@ class Decl; class IRABIDetailsProvider; class IRGenOptions; class ModuleDecl; +class NominalTypeDecl; /// The \c SwiftToClangInteropContext class is responsible for providing /// access to the other required subsystems of the compiler during the emission @@ -40,11 +43,21 @@ class SwiftToClangInteropContext { void runIfStubForDeclNotEmitted(llvm::StringRef stubName, llvm::function_ref function); + /// Records that the given nominal type decl that has a clang declaration was + /// emitted in the generated header. + void recordEmittedClangTypeDecl(const NominalTypeDecl *typeDecl); + + inline const llvm::SetVector & + getEmittedClangTypeDecls() const { + return referencedClangTypeDecls; + } + private: ModuleDecl &mod; const IRGenOptions &irGenOpts; std::unique_ptr irABIDetails; llvm::StringSet<> emittedStubs; + llvm::SetVector referencedClangTypeDecls; }; } // end namespace swift diff --git a/stdlib/public/SwiftShims/_SwiftCxxInteroperability.h b/stdlib/public/SwiftShims/_SwiftCxxInteroperability.h index a596c2928a1b6..a36c17fee30ef 100644 --- a/stdlib/public/SwiftShims/_SwiftCxxInteroperability.h +++ b/stdlib/public/SwiftShims/_SwiftCxxInteroperability.h @@ -160,6 +160,11 @@ template static inline const constexpr bool isValueType = false; /// boxed. template static inline const constexpr bool isOpaqueLayout = false; +/// True if the given type is a C++ record that was bridged to Swift, giving +/// Swift ability to work with it in a generic context. +template +static inline const constexpr bool isSwiftBridgedCxxRecord = false; + /// Returns the opaque pointer to the given value. template inline const void *_Nonnull getOpaquePointer(const T &value) { diff --git a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift index 454d41362311a..308336cc9afe2 100644 --- a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift +++ b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx.swift @@ -140,3 +140,33 @@ public func takeTrivialInout(_ x: inout Trivial) { // CHECK: inline void takeTrivialInout(Trivial& x) noexcept { // CHECK-NEXT: return _impl::$s8UseCxxTy16takeTrivialInoutyySo0E0VzF(swift::_impl::getOpaquePointer(x)); // CHECK-NEXT: } + +// CHECK: } // end namespace +// CHECK-EMPTY: +// CHECK-NEXT: namespace swift { +// CHECK-NEXT: namespace _impl { +// CHECK-EMPTY: +// CHECK-NEXT: // Type metadata accessor for __CxxTemplateInstN2ns18NonTrivialTemplateIiEE +// CHECK-NEXT: SWIFT_EXTERN swift::_impl::MetadataResponseTy $sSo2nsO033__CxxTemplateInstN2ns18NonTrivialC4IiEEVMa(swift::_impl::MetadataRequestTy) SWIFT_NOEXCEPT SWIFT_CALL; +// CHECK-EMPTY: +// CHECK-EMPTY: +// CHECK-NEXT: } // namespace _impl +// CHECK-EMPTY: +// CHECK-NEXT: #pragma clang diagnostic push +// CHECK-NEXT: #pragma clang diagnostic ignored "-Wc++17-extensions" +// CHECK-NEXT: template<> +// CHECK-NEXT: static inline const constexpr bool isUsableInGenericContext> = true; +// CHECK-NEXT: template<> +// CHECK-NEXT: struct TypeMetadataTrait> { +// CHECK-NEXT: static inline void * _Nonnull getTypeMetadata() { +// CHECK-NEXT: return _impl::$sSo2nsO033__CxxTemplateInstN2ns18NonTrivialC4IiEEVMa(0)._0; +// CHECK-NEXT: } +// CHECK-NEXT: }; +// CHECK-NEXT: namespace _impl{ +// CHECK-NEXT: template<> +// CHECK-NEXT: static inline const constexpr bool isSwiftBridgedCxxRecord> = true; +// CHECK-NEXT: } // namespace +// CHECK-NEXT: #pragma clang diagnostic pop +// CHECK-NEXT: } // namespace swift +// CHECK-EMPTY: +// CHECK-NEXT: namespace UseCxxTy { From 0dcb8b58e58c07d5c8798a0dc264f350ae15aec5 Mon Sep 17 00:00:00 2001 From: Alex Lorenz Date: Mon, 12 Sep 2022 13:01:29 -0700 Subject: [PATCH 35/37] =?UTF-8?q?[interop][SwiftToCxx]=20use=20C++=20types?= =?UTF-8?q?=20bridged=20to=20Swift=20in=20Swift=20generic=20context=20from?= =?UTF-8?q?=20C++=20=E2=99=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/PrintAsClang/PrintClangFunction.cpp | 25 ++++++++++-- ...ridge-cxx-struct-back-to-cxx-execution.cpp | 38 ++++++++++++++++++- .../generics/generic-function-in-cxx.swift | 14 +++++++ .../generics/generic-struct-in-cxx.swift | 6 ++- 4 files changed, 77 insertions(+), 6 deletions(-) diff --git a/lib/PrintAsClang/PrintClangFunction.cpp b/lib/PrintAsClang/PrintClangFunction.cpp index dbfec1cae287b..b6a3a051b1088 100644 --- a/lib/PrintAsClang/PrintClangFunction.cpp +++ b/lib/PrintAsClang/PrintClangFunction.cpp @@ -109,6 +109,13 @@ class ClangTypeHandler { ClangSyntaxPrinter(os).printNominalClangTypeReference(typeDecl); } + static void + printGenericReturnScaffold(raw_ostream &os, StringRef templateParamName, + llvm::function_ref bodyOfReturn) { + printReturnScaffold(nullptr, os, templateParamName, templateParamName, + bodyOfReturn); + } + void printReturnScaffold(raw_ostream &os, llvm::function_ref bodyOfReturn) { std::string fullQualifiedType; @@ -119,14 +126,22 @@ class ClangTypeHandler { llvm::raw_string_ostream unqualTypeNameOS(typeName); unqualTypeNameOS << cast(typeDecl)->getName(); } + printReturnScaffold(typeDecl, os, fullQualifiedType, typeName, + bodyOfReturn); + } + +private: + static void + printReturnScaffold(const clang::Decl *typeDecl, raw_ostream &os, + StringRef fullQualifiedType, StringRef typeName, + llvm::function_ref bodyOfReturn) { os << "alignas(alignof(" << fullQualifiedType << ")) char storage[sizeof(" << fullQualifiedType << ")];\n"; os << "auto * _Nonnull storageObjectPtr = reinterpret_cast<" << fullQualifiedType << " *>(storage);\n"; bodyOfReturn("storage"); os << ";\n"; - auto *cxxRecord = cast(typeDecl); - if (cxxRecord->isTrivial()) { + if (typeDecl && cast(typeDecl)->isTrivial()) { // Trivial object can be just copied and not destroyed. os << "return *storageObjectPtr;\n"; return; @@ -136,7 +151,6 @@ class ClangTypeHandler { os << "return result;\n"; } -private: const clang::Decl *typeDecl; }; @@ -999,6 +1013,11 @@ void DeclAndTypeClangFunctionPrinter::printCxxThunkBody( << ">::type::returnNewValue([&](void * _Nonnull returnValue) {\n"; printCallToCFunc(/*additionalParam=*/StringRef("returnValue")); os << ";\n });\n"; + os << " } else if constexpr (::swift::" + << cxx_synthesis::getCxxImplNamespaceName() + << "::isSwiftBridgedCxxRecord<" << resultTyName << ">) {\n"; + ClangTypeHandler::printGenericReturnScaffold(os, resultTyName, + printCallToCFunc); os << " } else {\n"; os << " " << resultTyName << " returnValue;\n"; printCallToCFunc(/*additionalParam=*/StringRef(ros.str())); diff --git a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp index 7b2666def33ce..11fa06d8fb93d 100644 --- a/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp +++ b/test/Interop/CxxToSwiftToCxx/bridge-cxx-struct-back-to-cxx-execution.cpp @@ -3,7 +3,7 @@ // RUN: %target-swift-frontend -typecheck %t/use-cxx-types.swift -typecheck -module-name UseCxx -emit-clang-header-path %t/UseCxx.h -I %t -enable-experimental-cxx-interop -clang-header-expose-public-decls -// RUN: %target-interop-build-clangxx -c %t/use-swift-cxx-types.cpp -I %t -o %t/swift-cxx-execution.o -g +// RUN: %target-interop-build-clangxx -std=c++20 -c %t/use-swift-cxx-types.cpp -I %t -o %t/swift-cxx-execution.o -g // RUN: %target-interop-build-swift %t/use-cxx-types.swift -o %t/swift-cxx-execution -Xlinker %t/swift-cxx-execution.o -module-name UseCxx -Xfrontend -entry-point-function-name -Xfrontend swiftMain -I %t -g // RUN: %target-codesign %t/swift-cxx-execution @@ -80,6 +80,14 @@ public func inoutTrivial(_ x: inout Trivial) { x.x = x.y + x.x - 11 } +public func takeGeneric(_ x: T) { + print("GENERIC", x) +} + +public func retPassThroughGeneric(_ x: T) -> T { + return x +} + //--- use-swift-cxx-types.cpp #include "header.h" @@ -98,8 +106,15 @@ int main() { assert(x.x == -11); assert(x.y == -423421); UseCxx::takeTrivial(x); + UseCxx::takeGeneric(x); + auto xPrime = UseCxx::retPassThroughGeneric(x); + assert(xPrime.x == -11); + assert(xPrime.y == -423421); + UseCxx::takeTrivial(xPrime); } // CHECK: Trivial(x: 423421, y: -423421) +// CHECK-NEXT: Trivial(x: -11, y: -423421) +// CHECK-NEXT: GENERIC Trivial(x: -11, y: -423421) // CHECK-NEXT: Trivial(x: -11, y: -423421) { auto x = UseCxx::retNonTrivial(-942); @@ -110,7 +125,16 @@ int main() { UseCxx::inoutNonTrivial(x); assert(x.x.y == -1884); assert(x.x.x == 42); + UseCxx::takeGeneric(x); + { + auto xPrime = UseCxx::retPassThroughGeneric(x); + assert(xPrime.x.y == -1884); + assert(xPrime.x.x == 42); + UseCxx::takeNonTrivial(xPrime); + } + puts("secondon non trivial"); } + puts("EndOfTest"); // CHECK-NEXT: create NonTrivialTemplate // CHECK-NEXT: move NonTrivialTemplate // CHECK-NEXT: ~NonTrivialTemplate @@ -122,6 +146,18 @@ int main() { // CHECK-NEXT: ~NonTrivialTemplate // CHECK-NEXT: ~NonTrivialTemplate // CHECK-NEXT: done non trivial +// CHECK-NEXT: copy NonTrivialTemplate +// CHECK-NEXT: GENERIC __CxxTemplateInst18NonTrivialTemplateI7TrivialE(x: __C.Trivial(x: 42, y: -1884)) +// CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: copy NonTrivialTemplate +// CHECK-NEXT: move NonTrivialTemplate +// CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: copy NonTrivialTemplate +// CHECK-NEXT: __CxxTemplateInst18NonTrivialTemplateI7TrivialE(x: __C.Trivial(x: 42, y: -1884)) +// CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: secondon non trivial // CHECK-NEXT: ~NonTrivialTemplate +// CHECK-NEXT: EndOfTest return 0; } diff --git a/test/Interop/SwiftToCxx/generics/generic-function-in-cxx.swift b/test/Interop/SwiftToCxx/generics/generic-function-in-cxx.swift index 9a8b27bc61eba..07333d8dd98bf 100644 --- a/test/Interop/SwiftToCxx/generics/generic-function-in-cxx.swift +++ b/test/Interop/SwiftToCxx/generics/generic-function-in-cxx.swift @@ -150,6 +150,13 @@ public func createTestSmallStruct(_ x: UInt32) -> TestSmallStruct { // CHECK-NEXT: return ::swift::_impl::implClassFor::type::returnNewValue([&](void * _Nonnull returnValue) { // CHECK-NEXT: _impl::$s9Functions10genericRetyxxlF(returnValue, swift::_impl::getOpaquePointer(x), swift::TypeMetadataTrait::getTypeMetadata()); // CHECK-NEXT: }); +// CHECK-NEXT: } else if constexpr (::swift::_impl::isSwiftBridgedCxxRecord) { +// CHECK-NEXT: alignas(alignof(T_0_0)) char storage[sizeof(T_0_0)]; +// CHECK-NEXT: auto * _Nonnull storageObjectPtr = reinterpret_cast(storage); +// CHECK-NEXT: _impl::$s9Functions10genericRetyxxlF(storage, swift::_impl::getOpaquePointer(x), swift::TypeMetadataTrait::getTypeMetadata()); +// CHECK-NEXT: T_0_0 result(std::move(*storageObjectPtr)); +// CHECK-NEXT: storageObjectPtr->~T_0_0(); +// CHECK-NEXT: return result; // CHECK-NEXT: } else { // CHECK-NEXT: T_0_0 returnValue; // CHECK-NEXT: _impl::$s9Functions10genericRetyxxlF(reinterpret_cast(&returnValue), swift::_impl::getOpaquePointer(x), swift::TypeMetadataTrait::getTypeMetadata()); @@ -174,6 +181,13 @@ public func createTestSmallStruct(_ x: UInt32) -> TestSmallStruct { // CHECK-NEXT: return ::swift::_impl::implClassFor::type::returnNewValue([&](void * _Nonnull returnValue) { // CHECK-NEXT: _impl::$s9Functions15TestSmallStructV24genericMethodPassThroughyxxlF(returnValue, swift::_impl::getOpaquePointer(x), _impl::swift_interop_passDirect_Functions_uint32_t_0_4(_getOpaquePointer()), swift::TypeMetadataTrait::getTypeMetadata()); // CHECK-NEXT: }); +// CHECK-NEXT: } else if constexpr (::swift::_impl::isSwiftBridgedCxxRecord) { +// CHECK-NEXT: alignas(alignof(T_0_0)) char storage[sizeof(T_0_0)]; +// CHECK-NEXT: auto * _Nonnull storageObjectPtr = reinterpret_cast(storage); +// CHECK-NEXT: _impl::$s9Functions15TestSmallStructV24genericMethodPassThroughyxxlF(storage, swift::_impl::getOpaquePointer(x), _impl::swift_interop_passDirect_Functions_uint32_t_0_4(_getOpaquePointer()), swift::TypeMetadataTrait::getTypeMetadata()) +// CHECK-NEXT: T_0_0 result(std::move(*storageObjectPtr)); +// CHECK-NEXT: storageObjectPtr->~T_0_0(); +// CHECK-NEXT: return result; // CHECK-NEXT: } else { // CHECK-NEXT: T_0_0 returnValue; // CHECK-NEXT: _impl::$s9Functions15TestSmallStructV24genericMethodPassThroughyxxlF(reinterpret_cast(&returnValue), swift::_impl::getOpaquePointer(x), _impl::swift_interop_passDirect_Functions_uint32_t_0_4(_getOpaquePointer()), swift::TypeMetadataTrait::getTypeMetadata()); diff --git a/test/Interop/SwiftToCxx/generics/generic-struct-in-cxx.swift b/test/Interop/SwiftToCxx/generics/generic-struct-in-cxx.swift index 947df4903f15d..643aedda0336c 100644 --- a/test/Interop/SwiftToCxx/generics/generic-struct-in-cxx.swift +++ b/test/Interop/SwiftToCxx/generics/generic-struct-in-cxx.swift @@ -304,7 +304,8 @@ public func inoutConcretePair(_ x: UInt16, _ y: inout GenericPair::type::returnNewValue([&](void * _Nonnull returnValue) { // CHECK-NEXT: _impl::$s8Generics11GenericPairV1yq_vg(returnValue, swift::TypeMetadataTrait>::getTypeMetadata(), _getOpaquePointer()); // CHECK-NEXT: }); -// CHECK-NEXT: } else { +// CHECK-NEXT: } else if constexpr (::swift::_impl::isSwiftBridgedCxxRecord) { +// CHECK: } else { // CHECK-NEXT: T_0_1 returnValue; // CHECK-NEXT: _impl::$s8Generics11GenericPairV1yq_vg(reinterpret_cast(&returnValue), swift::TypeMetadataTrait>::getTypeMetadata(), _getOpaquePointer()); // CHECK-NEXT: return returnValue; @@ -345,7 +346,8 @@ public func inoutConcretePair(_ x: UInt16, _ y: inout GenericPair::type::returnNewValue([&](void * _Nonnull returnValue) { // CHECK-NEXT: _impl::$s8Generics11GenericPairV13genericMethodyqd__qd___q_tlF(returnValue, swift::_impl::getOpaquePointer(x), swift::_impl::getOpaquePointer(y), swift::TypeMetadataTrait>::getTypeMetadata(), swift::TypeMetadataTrait::getTypeMetadata(), _getOpaquePointer()); // CHECK-NEXT: }); -// CHECK-NEXT: } else { +// CHECK-NEXT: } else if constexpr (::swift::_impl::isSwiftBridgedCxxRecord) { +// CHECK: } else { // CHECK-NEXT: T_1_0 returnValue; // CHECK-NEXT: _impl::$s8Generics11GenericPairV13genericMethodyqd__qd___q_tlF(reinterpret_cast(&returnValue), swift::_impl::getOpaquePointer(x), swift::_impl::getOpaquePointer(y), swift::TypeMetadataTrait>::getTypeMetadata(), swift::TypeMetadataTrait::getTypeMetadata(), _getOpaquePointer()); // CHECK-NEXT: return returnValue; From d20e00e53168623d62501f06c20e3c00d473a71e Mon Sep 17 00:00:00 2001 From: Becca Royal-Gordon Date: Mon, 12 Sep 2022 13:35:59 -0700 Subject: [PATCH 36/37] [NFC] Improve deserialization errors for wrong record kind MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are a bunch of places where we expect to find a record with a particular record ID/kind in the decls_block and have to bail out if we see something we don’t expect. Add an `InvalidRecordKindError` we can use in this situation to produce a useful error message and adopt it in various places. This change also makes deserialization errors print the path to the invalid file. --- lib/Serialization/Deserialization.cpp | 50 ++++++++++++---------- lib/Serialization/DeserializationErrors.h | 32 ++++++++++++++ lib/Serialization/ModuleFileSharedCore.cpp | 2 + 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/lib/Serialization/Deserialization.cpp b/lib/Serialization/Deserialization.cpp index 1c894d3ab275b..2dc5433a63444 100644 --- a/lib/Serialization/Deserialization.cpp +++ b/lib/Serialization/Deserialization.cpp @@ -149,6 +149,8 @@ const char ExtensionError::ID = '\0'; void ExtensionError::anchor() {} const char DeclAttributesDidNotMatch::ID = '\0'; void DeclAttributesDidNotMatch::anchor() {} +const char InvalidRecordKindError::ID = '\0'; +void InvalidRecordKindError::anchor() {} /// Skips a single record in the bitstream. /// @@ -239,8 +241,8 @@ ParameterList *ModuleFile::readParameterList() { fatalIfUnexpected(DeclTypeCursor.advance(AF_DontPopBlockAtEnd)); unsigned recordID = fatalIfUnexpected(DeclTypeCursor.readRecord(entry.ID, scratch)); - assert(recordID == PARAMETERLIST); - (void) recordID; + if (recordID != PARAMETERLIST) + fatal(llvm::make_error(recordID)); ArrayRef rawMemberIDs; decls_block::ParameterListLayout::readRecord(scratch, rawMemberIDs); @@ -312,7 +314,8 @@ Expected ModuleFile::readPattern(DeclContext *owningDC) { assert(next.Kind == llvm::BitstreamEntry::Record); kind = fatalIfUnexpected(DeclTypeCursor.readRecord(next.ID, scratch)); - assert(kind == decls_block::TUPLE_PATTERN_ELT); + if (kind != decls_block::TUPLE_PATTERN_ELT) + fatal(llvm::make_error(kind)); // FIXME: Add something for this record or remove it. IdentifierID labelID; @@ -393,7 +396,7 @@ Expected ModuleFile::readPattern(DeclContext *owningDC) { } default: - return nullptr; + return llvm::make_error(kind); } } @@ -432,7 +435,7 @@ SILLayout *ModuleFile::readSILLayout(llvm::BitstreamCursor &Cursor) { return SILLayout::get(getContext(), canSig, fields, capturesGenerics); } default: - fatal(); + fatal(llvm::make_error(kind)); } } @@ -503,7 +506,7 @@ ProtocolConformanceDeserializer::read( // Not a protocol conformance. default: - MF.fatal(); + MF.fatal(llvm::make_error(kind)); } } @@ -1172,7 +1175,7 @@ ModuleFile::getGenericSignatureChecked(serialization::GenericSignatureID ID) { } default: // Not a generic signature; no way to recover. - fatal(); + fatal(llvm::make_error(recordID)); } // If we've already deserialized this generic signature, start over to return @@ -1214,7 +1217,7 @@ ModuleFile::getGenericEnvironmentChecked(serialization::GenericEnvironmentID ID) unsigned recordID = fatalIfUnexpected( DeclTypeCursor.readRecord(entry.ID, scratch, &blobData)); if (recordID != GENERIC_ENVIRONMENT) - fatal(); + fatal(llvm::make_error(recordID)); GenericSignatureID parentSigID; TypeID existentialID; @@ -1273,7 +1276,7 @@ ModuleFile::getSubstitutionMapChecked(serialization::SubstitutionMapID id) { unsigned recordID = fatalIfUnexpected( DeclTypeCursor.readRecord(entry.ID, scratch, &blobData)); if (recordID != SUBSTITUTION_MAP) - fatal(); + fatal(llvm::make_error(recordID)); GenericSignatureID genericSigID; uint64_t numReplacementIDs; @@ -1629,7 +1632,7 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) { default: // Unknown xref kind. pathTrace.addUnknown(recordID); - fatal(); + fatal(llvm::make_error(recordID)); } auto getXRefDeclNameForError = [&]() -> DeclName { @@ -1894,7 +1897,8 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) { } default: - llvm_unreachable("Unhandled path piece"); + fatal(llvm::make_error(recordID, + "Unhandled path piece")); } pathTrace.addValue(memberName); @@ -2100,7 +2104,7 @@ ModuleFile::resolveCrossReference(ModuleID MID, uint32_t pathLen) { default: // Unknown xref path piece. pathTrace.addUnknown(recordID); - fatal(); + fatal(llvm::make_error(recordID)); } Optional traceMsg; @@ -2273,7 +2277,8 @@ DeclContext *ModuleFile::getLocalDeclContext(LocalDeclContextID DCID) { } default: - llvm_unreachable("Unknown record ID found when reading local DeclContext."); + fatal(llvm::make_error(recordID, + "Unknown record ID found when reading local DeclContext.")); } return declContextOrOffset; } @@ -5085,7 +5090,7 @@ llvm::Error DeclDeserializer::deserializeDeclCommon() { default: // We don't know how to deserialize this kind of attribute. - MF.fatal(); + MF.fatal(llvm::make_error(recordID)); } if (!skipAttr) { @@ -5211,7 +5216,7 @@ DeclDeserializer::getDeclCheckedImpl( default: // We don't know how to deserialize this kind of decl. - MF.fatal(); + MF.fatal(llvm::make_error(recordID)); } } @@ -6431,7 +6436,7 @@ Expected ModuleFile::getTypeChecked(TypeID TID) { #undef TYPE default: // We don't know how to deserialize this kind of type. - fatal(); + fatal(llvm::make_error(recordID)); } } @@ -6550,7 +6555,7 @@ ModuleFile::getClangType(ClangTypeID TID) { DeclTypeCursor.readRecord(entry.ID, scratch, &blobData)); if (recordID != decls_block::CLANG_TYPE) - fatal(); + fatal(llvm::make_error(recordID)); auto &clangLoader = *getContext().getClangModuleLoader(); auto clangType = @@ -6640,8 +6645,8 @@ void ModuleFile::loadAllMembers(Decl *container, uint64_t contextData) { unsigned kind = fatalIfUnexpected(DeclTypeCursor.readRecord(entry.ID, memberIDBuffer)); - assert(kind == decls_block::MEMBERS); - (void)kind; + if (kind != decls_block::MEMBERS) + fatal(llvm::make_error(kind)); ArrayRef rawMemberIDs; decls_block::MembersLayout::readRecord(memberIDBuffer, rawMemberIDs); @@ -6845,9 +6850,10 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance, unsigned kind = fatalIfUnexpected(DeclTypeCursor.readRecord(entry.ID, scratch)); - (void) kind; - assert(kind == NORMAL_PROTOCOL_CONFORMANCE && - "registered lazy loader incorrectly"); + if (kind != NORMAL_PROTOCOL_CONFORMANCE) + fatal(llvm::make_error(kind, + "registered lazy loader incorrectly")); + NormalProtocolConformanceLayout::readRecord(scratch, protoID, contextID, typeCount, valueCount, conformanceCount, diff --git a/lib/Serialization/DeserializationErrors.h b/lib/Serialization/DeserializationErrors.h index 91f077916ae69..7432798bf744a 100644 --- a/lib/Serialization/DeserializationErrors.h +++ b/lib/Serialization/DeserializationErrors.h @@ -445,6 +445,38 @@ class DeclAttributesDidNotMatch : public llvm::ErrorInfo { + friend ErrorInfo; + static const char ID; + void anchor() override; + + unsigned recordKind; + const char *extraText; + +public: + explicit InvalidRecordKindError(unsigned kind, + const char *extraText = nullptr) { + this->recordKind = kind; + this->extraText = extraText; + } + + void log(raw_ostream &OS) const override { + OS << "don't know how to deserialize record with code " << recordKind; + if (recordKind >= decls_block::SILGenName_DECL_ATTR) + OS << " (attribute kind " + << recordKind - decls_block::SILGenName_DECL_ATTR << ")"; + if (extraText) + OS << ": " << extraText; + OS << "; this may be a compiler bug, or a file on disk may have changed " + "during compilation"; + } + + std::error_code convertToErrorCode() const override { + return llvm::inconvertibleErrorCode(); + } +}; + LLVM_NODISCARD static inline std::unique_ptr takeErrorInfo(llvm::Error error) { diff --git a/lib/Serialization/ModuleFileSharedCore.cpp b/lib/Serialization/ModuleFileSharedCore.cpp index d01aafb04671c..01fa62f41c7fe 100644 --- a/lib/Serialization/ModuleFileSharedCore.cpp +++ b/lib/Serialization/ModuleFileSharedCore.cpp @@ -585,6 +585,8 @@ void ModuleFileSharedCore::outputDiagnosticInfo(llvm::raw_ostream &os) const { << "'"; if (Bits.IsAllowModuleWithCompilerErrorsEnabled) os << " (built with -experimental-allow-module-with-compiler-errors)"; + if (ModuleInputBuffer) + os << " at '" << ModuleInputBuffer->getBufferIdentifier() << "'"; } ModuleFileSharedCore::~ModuleFileSharedCore() { } From fd8d7afc5b90348a11524db100d53e63dc7b3a8a Mon Sep 17 00:00:00 2001 From: Suyash Srijan Date: Mon, 12 Sep 2022 23:05:20 +0100 Subject: [PATCH 37/37] [Parse] Fix variable identifier parsing including code on newline as part of the identifier (#61036) --- lib/Parse/ParsePattern.cpp | 3 ++- test/Parse/identifiers.swift | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/Parse/ParsePattern.cpp b/lib/Parse/ParsePattern.cpp index 735c3aec96e18..b14a150e10eab 100644 --- a/lib/Parse/ParsePattern.cpp +++ b/lib/Parse/ParsePattern.cpp @@ -1112,7 +1112,8 @@ ParserResult Parser::parsePattern() { PatternCtx.setCreateSyntax(SyntaxKind::IdentifierPattern); Identifier name; SourceLoc loc = consumeIdentifier(name, /*diagnoseDollarPrefix=*/true); - if (Tok.isIdentifierOrUnderscore() && !Tok.isContextualDeclKeyword()) + if (Tok.isIdentifierOrUnderscore() && !Tok.isContextualDeclKeyword() && + !Tok.isAtStartOfLine()) diagnoseConsecutiveIDs(name.str(), loc, introducer == VarDecl::Introducer::Let ? "constant" : "variable"); diff --git a/test/Parse/identifiers.swift b/test/Parse/identifiers.swift index af28823c44049..7971687dc4f9a 100644 --- a/test/Parse/identifiers.swift +++ b/test/Parse/identifiers.swift @@ -47,3 +47,16 @@ _ = sil_witness_table // expected-error {{cannot find 'sil_witness_table' in sco _ = sil_default_witness_table // expected-error {{cannot find 'sil_default_witness_table' in scope}} _ = sil_coverage_map // expected-error {{cannot find 'sil_coverage_map' in scope}} _ = sil_scope // expected-error {{cannot find 'sil_scope' in scope}} + +// https://github.com/apple/swift/issues/57542 +// Make sure we do not parse the '_' on the newline as being part of the 'variable' identifier on the line before. + +@propertyWrapper +struct Wrapper { + var wrappedValue = 0 +} + +func localScope() { + @Wrapper var variable + _ = 0 +}