Skip to content

Commit 4d2f337

Browse files
committed
[region-isolation] Do not ignore non-trivial results that are Sendable to be more permissive in the face of lazy typechecker issues.
We have found certain cases due to the requestified typechecker, a type is initially Sendable and then is later non-Sendable. This can be seen by the attached test case where the first time one calls isNonSendableType on the test value, one would get that it is Sendable and then the second time one would get it was non-Sendable. The result of this is that the pass gets into an inconsistent state. This patch is a small patch that makes the pass more permissive in the face of such an error by making it so that we do not ignore Sendable results of instructions (that is we make sure to track a value for them), so we do not break invariants. The longer term better fix is to make it so that we have a cache in the pass for this query that way we just always use the first answer returned from the typechecker and cache that. If the typechecker has such a bug, we may get bogus results, but we at least do not break invariants. As an example of this type of behavior, in the test case in this patch, we first find the Sendable conformance of MySubClass and then the typechecker after doing some more type checking while performing that query, the second time finds the inherited non-Sendable conformance of MyParentClass causing MySubClass to be considered to be non-Sendable. rdar://132347404 (cherry picked from commit 8604480)
1 parent 7cf82b7 commit 4d2f337

File tree

3 files changed

+102
-13
lines changed

3 files changed

+102
-13
lines changed

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2888,9 +2888,6 @@ CONSTANT_TRANSLATION(VectorInst, Asserting)
28882888

28892889
#define IGNORE_IF_SENDABLE_RESULT_ASSIGN_OTHERWISE(INST) \
28902890
TranslationSemantics PartitionOpTranslator::visit##INST(INST *inst) { \
2891-
if (!isNonSendableType(inst->getType())) { \
2892-
return TranslationSemantics::Ignored; \
2893-
} \
28942891
return TranslationSemantics::Assign; \
28952892
}
28962893

@@ -2919,10 +2916,18 @@ IGNORE_IF_SENDABLE_RESULT_ASSIGN_OTHERWISE(StructExtractInst)
29192916
return TranslationSemantics::Require; \
29202917
} \
29212918
\
2922-
if (isResultNonSendable) \
2923-
return TranslationSemantics::AssignFresh; \
2924-
\
2925-
return TranslationSemantics::Ignored; \
2919+
/* We always use assign fresh here regardless of whether or not a */ \
2920+
/* type is sendable. */ \
2921+
/* */ \
2922+
/* DISCUSSION: Since the typechecker is lazy, if there is a bug */ \
2923+
/* that causes us to not look up enough type information it is */ \
2924+
/* possible for a type to move from being Sendable to being */ \
2925+
/* non-Sendable. For example, we could call isNonSendableType and */ \
2926+
/* get that the type is non-Sendable, but as a result of calling */ \
2927+
/* that API, the type checker could get more information that the */ \
2928+
/* next time we call isNonSendableType, we will get that the type */ \
2929+
/* is non-Sendable. */ \
2930+
return TranslationSemantics::AssignFresh; \
29262931
}
29272932

29282933
LOOKTHROUGH_IF_NONSENDABLE_RESULT_AND_OPERAND(UncheckedTrivialBitCastInst)

test/Concurrency/transfernonsendable.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -872,8 +872,8 @@ func letSendableTrivialLetStructFieldTest() async {
872872
await transferToMain(test) // expected-tns-warning {{sending 'test' risks causing data races}}
873873
// expected-tns-note @-1 {{sending 'test' to main actor-isolated global function 'transferToMain' risks causing data races between main actor-isolated and local nonisolated uses}}
874874
// expected-complete-warning @-2 {{passing argument of non-sendable type 'StructFieldTests' into main actor-isolated context may introduce data races}}
875-
_ = test.letSendableTrivial
876-
useValue(test) // expected-tns-note {{access can happen concurrently}}
875+
_ = test.letSendableTrivial // expected-tns-note {{access can happen concurrently}}
876+
useValue(test)
877877
}
878878

879879
func letSendableNonTrivialLetStructFieldTest() async {
@@ -941,20 +941,20 @@ func letNonSendableNonTrivialLetStructFieldClosureTest() async {
941941
await transferToMain(test) // expected-tns-warning {{sending 'test' risks causing data races}}
942942
// expected-tns-note @-1 {{sending 'test' to main actor-isolated global function 'transferToMain' risks causing data races between main actor-isolated and local nonisolated uses}}
943943
// expected-complete-warning @-2 {{passing argument of non-sendable type 'StructFieldTests' into main actor-isolated context may introduce data races}}
944-
let z = test.letSendableNonTrivial
944+
let z = test.letSendableNonTrivial // expected-tns-note {{access can happen concurrently}}
945945
_ = z
946946
let z2 = test.varSendableNonTrivial
947947
_ = z2
948-
useValue(test) // expected-tns-note {{access can happen concurrently}}
948+
useValue(test)
949949
}
950950

951951
func varSendableTrivialLetStructFieldTest() async {
952952
let test = StructFieldTests()
953953
await transferToMain(test) // expected-tns-warning {{sending 'test' risks causing data races}}
954954
// expected-tns-note @-1 {{sending 'test' to main actor-isolated global function 'transferToMain' risks causing data races between main actor-isolated and local nonisolated uses}}
955955
// expected-complete-warning @-2 {{passing argument of non-sendable type 'StructFieldTests' into main actor-isolated context may introduce data races}}
956-
_ = test.varSendableTrivial
957-
useValue(test) // expected-tns-note {{access can happen concurrently}}
956+
_ = test.varSendableTrivial // expected-tns-note {{access can happen concurrently}}
957+
useValue(test)
958958
}
959959

960960
func varSendableNonTrivialLetStructFieldTest() async {
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %empty-directory(%t/src)
3+
// RUN: %empty-directory(%t/sdk)
4+
// RUN: %empty-directory(%t/sdk/ObjCAPI)
5+
// RUN: %empty-directory(%t/sdk/SwiftAPI)
6+
// RUN: %empty-directory(%t/compile)
7+
// RUN: split-file %s %t/src
8+
9+
// Build Objective-C lib
10+
// RUN: %target-clang -dynamiclib %t/src/ObjCAPI.m -I %t/src -o %t/sdk/ObjCAPI/libObjCAPI.dylib -lobjc
11+
// RUN: cp %t/src/ObjCAPI.modulemap %t/sdk/ObjCAPI/module.modulemap
12+
// RUN: cp %t/src/ObjCAPI.h %t/sdk/ObjCAPI/ObjCAPI.h
13+
14+
// Build the swift part of API
15+
// RUN: %target-swiftc_driver -emit-library -emit-module-path %t/sdk/SwiftAPI/SwiftAPI.swiftmodule %t/src/SwiftAPI.swift -parse-as-library -I %t/sdk -swift-version 6 -module-name SwiftAPI -enable-library-evolution
16+
17+
// Now compile against the API.
18+
// RUN: %target-swift-frontend -emit-sil -o /dev/null -I %t/sdk/SwiftAPI -I %t/sdk/ObjCAPI %t/src/main.swift -swift-version 6
19+
20+
// REQUIRES: asserts
21+
// REQUIRES: concurrency
22+
// REQUIRES: objc_interop
23+
24+
// Test
25+
26+
//--- ObjCAPI.modulemap
27+
28+
module ObjCAPI {
29+
header "ObjCAPI.h"
30+
}
31+
32+
//--- ObjCAPI.h
33+
34+
#define NS_SWIFT_SENDABLE __attribute__((swift_attr("@Sendable")))
35+
#define NS_SWIFT_NONSENDABLE __attribute__((swift_attr("@_nonSendable")))
36+
37+
NS_SWIFT_NONSENDABLE
38+
@interface MyParentClass
39+
@end
40+
41+
NS_SWIFT_SENDABLE
42+
@interface MySubClass : MyParentClass
43+
@end
44+
45+
//--- ObjCAPI.m
46+
47+
#include "ObjCAPI.h"
48+
49+
@implementation MyParentClass
50+
@end
51+
52+
@implementation MySubClass
53+
@end
54+
55+
//--- SwiftAPI.swift
56+
57+
@_exported import ObjCAPI // Clang module
58+
59+
public struct Measurement<T : MyParentClass> {
60+
/// The unit component of the `Measurement`.
61+
public let unit: T
62+
63+
public var count: Int { 0 }
64+
}
65+
66+
extension Measurement : Sendable where T : Sendable {}
67+
68+
//--- main.swift
69+
70+
public import SwiftAPI
71+
72+
public enum MarketplaceKitError : Sendable {
73+
case first
74+
case second(Measurement<MySubClass>)
75+
76+
public var description: String {
77+
switch self {
78+
case .first:
79+
return "first"
80+
case .second(let value):
81+
return "\(value.count)"
82+
}
83+
}
84+
}

0 commit comments

Comments
 (0)