Skip to content

Commit 8604480

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
1 parent 111bc46 commit 8604480

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
@@ -2891,9 +2891,6 @@ CONSTANT_TRANSLATION(VectorInst, Asserting)
28912891

28922892
#define IGNORE_IF_SENDABLE_RESULT_ASSIGN_OTHERWISE(INST) \
28932893
TranslationSemantics PartitionOpTranslator::visit##INST(INST *inst) { \
2894-
if (!isNonSendableType(inst->getType())) { \
2895-
return TranslationSemantics::Ignored; \
2896-
} \
28972894
return TranslationSemantics::Assign; \
28982895
}
28992896

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

29312936
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
@@ -878,8 +878,8 @@ func letSendableTrivialLetStructFieldTest() async {
878878
await transferToMain(test) // expected-tns-warning {{sending 'test' risks causing data races}}
879879
// 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}}
880880
// expected-complete-warning @-2 {{passing argument of non-sendable type 'StructFieldTests' into main actor-isolated context may introduce data races}}
881-
_ = test.letSendableTrivial
882-
useValue(test) // expected-tns-note {{access can happen concurrently}}
881+
_ = test.letSendableTrivial // expected-tns-note {{access can happen concurrently}}
882+
useValue(test)
883883
}
884884

885885
func letSendableNonTrivialLetStructFieldTest() async {
@@ -947,20 +947,20 @@ func letNonSendableNonTrivialLetStructFieldClosureTest() async {
947947
await transferToMain(test) // expected-tns-warning {{sending 'test' risks causing data races}}
948948
// 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}}
949949
// expected-complete-warning @-2 {{passing argument of non-sendable type 'StructFieldTests' into main actor-isolated context may introduce data races}}
950-
let z = test.letSendableNonTrivial
950+
let z = test.letSendableNonTrivial // expected-tns-note {{access can happen concurrently}}
951951
_ = z
952952
let z2 = test.varSendableNonTrivial
953953
_ = z2
954-
useValue(test) // expected-tns-note {{access can happen concurrently}}
954+
useValue(test)
955955
}
956956

957957
func varSendableTrivialLetStructFieldTest() async {
958958
let test = StructFieldTests()
959959
await transferToMain(test) // expected-tns-warning {{sending 'test' risks causing data races}}
960960
// 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}}
961961
// expected-complete-warning @-2 {{passing argument of non-sendable type 'StructFieldTests' into main actor-isolated context may introduce data races}}
962-
_ = test.varSendableTrivial
963-
useValue(test) // expected-tns-note {{access can happen concurrently}}
962+
_ = test.varSendableTrivial // expected-tns-note {{access can happen concurrently}}
963+
useValue(test)
964964
}
965965

966966
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)