Skip to content

Commit fa19cc9

Browse files
committedFeb 5, 2021
[capture-promotion] Emit a warning when the pass fails to promote a capture of a concurrent function to a by value capture instead of by ref capture.
I also added a SILVerifier check that once we are in canonical SIL all concurrent functions that are partial applied are banned from having Box arguments. I have not added support yet for this for address only types, so we do not crash on that yet.

9 files changed

+642
-16
lines changed
 

‎include/swift/AST/DiagnosticsSIL.def

+10
Original file line numberDiff line numberDiff line change
@@ -645,5 +645,15 @@ NOTE(box_to_stack_cannot_promote_box_to_stack_due_to_escape_location, none,
645645

646646
WARNING(semantic_function_improper_nesting, none, "'@_semantics' function calls non-'@_semantics' function with nested '@_semantics' calls", ())
647647

648+
// Capture promotion diagnostics
649+
WARNING(capturepromotion_concurrentcapture_mutation, none,
650+
"'%0' mutated after capture by concurrent closure", (StringRef))
651+
NOTE(capturepromotion_concurrentcapture_closure_here, none,
652+
"variable captured by concurrent closure", ())
653+
NOTE(capturepromotion_concurrentcapture_capturinguse_here, none,
654+
"capturing use", ())
655+
NOTE(capturepromotion_variable_defined_here,none,
656+
"variable defined here", ())
657+
648658
#define UNDEFINE_DIAGNOSTIC_MACROS
649659
#include "DefineDiagnosticMacros.h"

‎include/swift/SIL/SILType.h

+7
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,13 @@ class SILType {
565565
/// Returns true if this SILType is a differentiable type.
566566
bool isDifferentiable(SILModule &M) const;
567567

568+
/// If this is a SILBoxType, return getSILBoxFieldType(). Otherwise, return
569+
/// SILType().
570+
///
571+
/// \p field Return the type of the ith field of the box. Default set to 0
572+
/// since we only support one field today. This is just future proofing.
573+
SILType getSILBoxFieldType(const SILFunction *f, unsigned field = 0);
574+
568575
/// Returns the hash code for the SILType.
569576
llvm::hash_code getHashCode() const {
570577
return llvm::hash_combine(*this);

‎lib/SIL/IR/SILType.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -699,3 +699,11 @@ bool SILType::isEffectivelyExhaustiveEnumType(SILFunction *f) {
699699
return decl->isEffectivelyExhaustive(f->getModule().getSwiftModule(),
700700
f->getResilienceExpansion());
701701
}
702+
703+
SILType SILType::getSILBoxFieldType(const SILFunction *f, unsigned field) {
704+
auto *boxTy = getASTType()->getAs<SILBoxType>();
705+
if (!boxTy)
706+
return SILType();
707+
return ::getSILBoxFieldType(f->getTypeExpansionContext(), boxTy,
708+
f->getModule().Types, field);
709+
}

‎lib/SIL/Verifier/SILVerifier.cpp

+14-3
Original file line numberDiff line numberDiff line change
@@ -1671,13 +1671,24 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
16711671
SILFunctionConventions substConv(substTy, F.getModule());
16721672
unsigned appliedArgStartIdx =
16731673
substConv.getNumSILArguments() - PAI->getNumArguments();
1674-
for (unsigned i = 0, size = PAI->getArguments().size(); i < size; ++i) {
1674+
bool isConcurrentAndStageIsCanonical =
1675+
PAI->getFunctionType()->isConcurrent() &&
1676+
F.getModule().getStage() >= SILStage::Canonical;
1677+
for (auto p : llvm::enumerate(PAI->getArguments())) {
16751678
requireSameType(
1676-
PAI->getArguments()[i]->getType(),
1677-
substConv.getSILArgumentType(appliedArgStartIdx + i,
1679+
p.value()->getType(),
1680+
substConv.getSILArgumentType(appliedArgStartIdx + p.index(),
16781681
F.getTypeExpansionContext()),
16791682
"applied argument types do not match suffix of function type's "
16801683
"inputs");
1684+
1685+
// TODO: Expand this to also be true for address only types.
1686+
if (isConcurrentAndStageIsCanonical)
1687+
require(
1688+
!p.value()->getType().getASTType()->is<SILBoxType>() ||
1689+
p.value()->getType().getSILBoxFieldType(&F).isAddressOnly(F),
1690+
"Concurrent partial apply in canonical SIL with a loadable box "
1691+
"type argument?!");
16811692
}
16821693

16831694
// The arguments to the result function type must match the prefix of the

‎lib/SILOptimizer/Mandatory/CapturePromotion.cpp

+98-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the Swift.org open source project
44
//
5-
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
66
// Licensed under Apache License v2.0 with Runtime Library Exception
77
//
88
// See https://swift.org/LICENSE.txt for license information
@@ -44,7 +44,9 @@
4444

4545
#define DEBUG_TYPE "sil-capture-promotion"
4646

47+
#include "swift/AST/DiagnosticsSIL.h"
4748
#include "swift/AST/GenericEnvironment.h"
49+
#include "swift/Basic/FrozenMultiMap.h"
4850
#include "swift/SIL/SILCloner.h"
4951
#include "swift/SIL/SILInstruction.h"
5052
#include "swift/SIL/TypeSubstCloner.h"
@@ -56,6 +58,7 @@
5658
#include "llvm/ADT/SmallSet.h"
5759
#include "llvm/ADT/Statistic.h"
5860
#include "llvm/Support/Debug.h"
61+
#include "llvm/Support/ErrorHandling.h"
5962
#include <tuple>
6063

6164
using namespace swift;
@@ -765,6 +768,15 @@ struct EscapeMutationScanningState {
765768
/// found.
766769
SmallVector<Operand *, 8> accumulatedEscapes;
767770

771+
/// A multimap that maps partial applies to the set of operands in the partial
772+
/// applies referenced function that the pass has identified as being the use
773+
/// that caused the partial apply to capture our box.
774+
///
775+
/// We use a frozen multi-map since our algorithm first accumulates this info
776+
/// and then wants to use it, perfect for the 2-stage frozen multi map.
777+
SmallFrozenMultiMap<PartialApplyInst *, Operand *, 16>
778+
accumulatedCaptureCausingUses;
779+
768780
/// A flag that we use to ensure that we only ever see 1 project_box on an
769781
/// alloc_box.
770782
bool sawProjectBoxInst;
@@ -797,15 +809,17 @@ static bool isNonMutatingLoad(SILInstruction *inst) {
797809
/// address of the box's contents), return true if this box has mutating
798810
/// captures. Return false otherwise. All of the mutating captures that we find
799811
/// are placed into \p accumulatedMutatingUses.
800-
static bool getPartialApplyArgMutationsAndEscapes(
801-
SILArgument *boxArg, SmallVectorImpl<Operand *> &accumulatedMutatingUses,
802-
SmallVectorImpl<Operand *> &accumulatedEscapes) {
812+
static bool
813+
getPartialApplyArgMutationsAndEscapes(PartialApplyInst *pai,
814+
SILArgument *boxArg,
815+
EscapeMutationScanningState &state) {
803816
SmallVector<ProjectBoxInst *, 2> projectBoxInsts;
804817

805818
// Conservatively do not allow any use of the box argument other than a
806819
// strong_release or projection, since this is the pattern expected from
807820
// SILGen.
808821
SmallVector<Operand *, 32> incrementalEscapes;
822+
SmallVector<Operand *, 32> incrementalCaptureCausingUses;
809823
for (auto *use : boxArg->getUses()) {
810824
if (isa<StrongReleaseInst>(use->getUser()) ||
811825
isa<DestroyValueInst>(use->getUser()))
@@ -827,18 +841,25 @@ static bool getPartialApplyArgMutationsAndEscapes(
827841
// function that mirrors isNonEscapingUse.
828842
auto checkIfAddrUseMutating = [&](Operand *addrUse) -> bool {
829843
unsigned initSize = incrementalEscapes.size();
830-
auto *addrInst = addrUse->getUser();
831-
if (auto *seai = dyn_cast<StructElementAddrInst>(addrInst)) {
844+
auto *addrUser = addrUse->getUser();
845+
if (auto *seai = dyn_cast<StructElementAddrInst>(addrUser)) {
832846
for (auto *seaiUse : seai->getUses()) {
833-
if (!isNonMutatingLoad(seaiUse->getUser())) {
847+
if (isNonMutatingLoad(seaiUse->getUser())) {
848+
incrementalCaptureCausingUses.push_back(seaiUse);
849+
} else {
834850
incrementalEscapes.push_back(seaiUse);
835851
}
836852
}
837853
return incrementalEscapes.size() != initSize;
838854
}
839855

840-
if (isNonMutatingLoad(addrInst) || isa<DebugValueAddrInst>(addrInst) ||
841-
isa<MarkFunctionEscapeInst>(addrInst) || isa<EndAccessInst>(addrInst)) {
856+
if (isNonMutatingLoad(addrUser)) {
857+
incrementalCaptureCausingUses.push_back(addrUse);
858+
return false;
859+
}
860+
861+
if (isa<DebugValueAddrInst>(addrUser) ||
862+
isa<MarkFunctionEscapeInst>(addrUser) || isa<EndAccessInst>(addrUser)) {
842863
return false;
843864
}
844865

@@ -859,10 +880,15 @@ static bool getPartialApplyArgMutationsAndEscapes(
859880
}
860881
}
861882

883+
auto &accCaptureCausingUses = state.accumulatedCaptureCausingUses;
884+
while (!incrementalCaptureCausingUses.empty())
885+
accCaptureCausingUses.insert(pai,
886+
incrementalCaptureCausingUses.pop_back_val());
887+
862888
if (incrementalEscapes.empty())
863889
return false;
864890
while (!incrementalEscapes.empty())
865-
accumulatedEscapes.push_back(incrementalEscapes.pop_back_val());
891+
state.accumulatedEscapes.push_back(incrementalEscapes.pop_back_val());
866892
return true;
867893
}
868894

@@ -925,8 +951,7 @@ bool isPartialApplyNonEscapingUser(Operand *currentOp, PartialApplyInst *pai,
925951
// Verify that this closure is known not to mutate the captured value; if
926952
// it does, then conservatively refuse to promote any captures of this
927953
// value.
928-
if (getPartialApplyArgMutationsAndEscapes(boxArg, state.accumulatedMutations,
929-
state.accumulatedEscapes)) {
954+
if (getPartialApplyArgMutationsAndEscapes(pai, boxArg, state)) {
930955
LLVM_DEBUG(llvm::dbgs() << " FAIL: Have a mutation or escape of a "
931956
"partial apply arg?!\n");
932957
return false;
@@ -1195,6 +1220,57 @@ static bool findEscapeOrMutationUses(Operand *op,
11951220
return isNonEscapingUse(op, state);
11961221
}
11971222

1223+
/// We found a capture of \p abi in concurrent closure \p pai that we can not
1224+
/// promote to a by value capture. Emit a nice warning (FIXME: error) to warn
1225+
/// the user and provide the following information in the compiler feedback:
1226+
///
1227+
/// 1. The source loc where the variable's box is written to.
1228+
///
1229+
/// 2. The source loc of the captured variable's declaration.
1230+
///
1231+
/// 3. The source loc of the start of the concurrent closure that caused the
1232+
/// variable to be captured.
1233+
///
1234+
/// 4. All places in the concurrent closure that triggered the box's
1235+
/// capture. NOTE: For objects these are load points. For address only things
1236+
/// it is still open for debate at this point.
1237+
static void diagnoseInvalidCaptureByConcurrentClosure(
1238+
AllocBoxInst *abi, PartialApplyInst *pai,
1239+
const EscapeMutationScanningState &state, SILInstruction *mutatingUser) {
1240+
auto captureCausingUses = state.accumulatedCaptureCausingUses.find(pai);
1241+
if (!captureCausingUses) {
1242+
llvm::errs() << "Didn't find capture causing use of partial apply: "
1243+
<< *pai;
1244+
llvm::errs() << "Original Func: " << pai->getFunction()->getName() << '\n';
1245+
llvm::errs() << "Partial Applied Func: "
1246+
<< pai->getReferencedFunctionOrNull()->getName() << '\n';
1247+
llvm::report_fatal_error("standard compiler error");
1248+
}
1249+
1250+
auto &astCtx = pai->getFunction()->getASTContext();
1251+
auto &de = astCtx.Diags;
1252+
auto varInfo = abi->getVarInfo();
1253+
StringRef name = "<unknown>";
1254+
if (varInfo) {
1255+
name = varInfo->Name;
1256+
}
1257+
1258+
de.diagnoseWithNotes(
1259+
de.diagnose(mutatingUser->getLoc().getSourceLoc(),
1260+
diag::capturepromotion_concurrentcapture_mutation, name),
1261+
[&]() {
1262+
de.diagnose(abi->getLoc().getSourceLoc(),
1263+
diag::capturepromotion_variable_defined_here);
1264+
de.diagnose(pai->getLoc().getSourceLoc(),
1265+
diag::capturepromotion_concurrentcapture_closure_here);
1266+
for (auto *use : *captureCausingUses) {
1267+
de.diagnose(
1268+
use->getUser()->getLoc().getSourceLoc(),
1269+
diag::capturepromotion_concurrentcapture_capturinguse_here);
1270+
}
1271+
});
1272+
}
1273+
11981274
/// Examine an alloc_box instruction, returning true if at least one
11991275
/// capture of the boxed variable is promotable. If so, then the pair of the
12001276
/// partial_apply instruction and the index of the box argument in the closure's
@@ -1203,7 +1279,7 @@ static bool
12031279
examineAllocBoxInst(AllocBoxInst *abi, ReachabilityInfo &ri,
12041280
llvm::DenseMap<PartialApplyInst *, unsigned> &im) {
12051281
LLVM_DEBUG(llvm::dbgs() << "Visiting alloc box: " << *abi);
1206-
EscapeMutationScanningState state{{}, {}, false, im};
1282+
EscapeMutationScanningState state{{}, {}, {}, false, im};
12071283

12081284
// Scan the box for escaping or mutating uses.
12091285
for (auto *use : abi->getUses()) {
@@ -1220,6 +1296,7 @@ examineAllocBoxInst(AllocBoxInst *abi, ReachabilityInfo &ri,
12201296
return false;
12211297
}
12221298

1299+
state.accumulatedCaptureCausingUses.setFrozen();
12231300
LLVM_DEBUG(llvm::dbgs() << "We can optimize this alloc box!\n");
12241301

12251302
// Helper lambda function to determine if instruction b is strictly after
@@ -1249,6 +1326,13 @@ examineAllocBoxInst(AllocBoxInst *abi, ReachabilityInfo &ri,
12491326
// block is after the partial_apply.
12501327
if (ri.isReachable(pai->getParent(), user->getParent()) ||
12511328
(pai->getParent() == user->getParent() && isAfter(pai, user))) {
1329+
// If our partial apply is concurrent and we can not promote this, emit
1330+
// a warning that shows the variable, where the variable is captured,
1331+
// and the mutation that we found.
1332+
if (pai->getFunctionType()->isConcurrent()) {
1333+
diagnoseInvalidCaptureByConcurrentClosure(abi, pai, state, user);
1334+
}
1335+
12521336
LLVM_DEBUG(llvm::dbgs() << " Invalidating: " << *pai);
12531337
LLVM_DEBUG(llvm::dbgs() << " Because of user: " << *user);
12541338
auto prev = iter++;
@@ -1257,6 +1341,7 @@ examineAllocBoxInst(AllocBoxInst *abi, ReachabilityInfo &ri,
12571341
}
12581342
++iter;
12591343
}
1344+
12601345
// If there are no valid captures left, then stop.
12611346
if (im.empty()) {
12621347
LLVM_DEBUG(llvm::dbgs() << " Ran out of valid captures... bailing!\n");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
// RUN: %target-swift-frontend -enable-experimental-concurrency -verify -emit-sil %s -o - >/dev/null
2+
3+
// REQUIRES: concurrency
4+
5+
func f(_: @escaping @concurrent () -> Void) { }
6+
open class F {
7+
func useConcurrent(_: @escaping @concurrent () -> Void) { }
8+
}
9+
10+
extension Int {
11+
mutating func addOne() {
12+
self += 1
13+
}
14+
}
15+
16+
func inoutUserInt(_ t: inout Int) {}
17+
18+
func testCaseTrivialValue() {
19+
var i = 17
20+
f {
21+
print(i + 17)
22+
print(i + 18)
23+
print(i + 19)
24+
}
25+
26+
i = 20
27+
i += 21
28+
29+
// We only emit a warning here since we use the last write.
30+
//
31+
// TODO: Should we emit for all writes?
32+
i.addOne() // expected-warning {{'i' mutated after capture by concurrent closure}}
33+
// expected-note @-14 {{variable defined here}}
34+
// expected-note @-14 {{variable captured by concurrent closure}}
35+
// expected-note @-14 {{capturing use}}
36+
// expected-note @-14 {{capturing use}}
37+
// expected-note @-14 {{capturing use}}
38+
}
39+
40+
func testCaseTrivialValue2() {
41+
var i2 = 17
42+
f {
43+
print(i2 + 17)
44+
print(i2 + 18)
45+
print(i2 + 19)
46+
}
47+
48+
i2 = 20
49+
i2 += 21 // expected-warning {{'i2' mutated after capture by concurrent closure}}
50+
// expected-note @-9 {{variable defined here}}
51+
// expected-note @-9 {{variable captured by concurrent closure}}
52+
// expected-note @-9 {{capturing use}}
53+
// expected-note @-9 {{capturing use}}
54+
// expected-note @-9 {{capturing use}}
55+
}
56+
57+
func testCaseTrivialValue3() {
58+
var i3 = 17
59+
f {
60+
print(i3 + 17)
61+
print(i3 + 18)
62+
print(i3 + 19)
63+
}
64+
65+
i3 = 20 // expected-warning {{'i3' mutated after capture by concurrent closure}}
66+
// expected-note @-8 {{variable defined here}}
67+
// expected-note @-8 {{variable captured by concurrent closure}}
68+
// expected-note @-8 {{capturing use}}
69+
// expected-note @-8 {{capturing use}}
70+
// expected-note @-8 {{capturing use}}
71+
}
72+
73+
func testCaseTrivialValue4() {
74+
var i4 = 17
75+
f {
76+
print(i4 + 17)
77+
print(i4 + 18)
78+
print(i4 + 19)
79+
}
80+
81+
inoutUserInt(&i4) // expected-warning {{'i4' mutated after capture by concurrent closure}}
82+
// expected-note @-8 {{variable defined here}}
83+
// expected-note @-8 {{variable captured by concurrent closure}}
84+
// expected-note @-8 {{capturing use}}
85+
// expected-note @-8 {{capturing use}}
86+
// expected-note @-8 {{capturing use}}
87+
}
88+
89+
class Klass {
90+
var next: Klass? = nil
91+
}
92+
func inoutUserKlass(_ k: inout Klass) {}
93+
func inoutUserOptKlass(_ k: inout Klass?) {}
94+
95+
func testCaseClass() {
96+
var i = Klass()
97+
f {
98+
print(i)
99+
}
100+
101+
i = Klass() // expected-warning {{'i' mutated after capture by concurrent closure}}
102+
// expected-note @-6 {{variable defined here}}
103+
// expected-note @-6 {{variable captured by concurrent closure}}
104+
// expected-note @-6 {{capturing use}}
105+
}
106+
107+
func testCaseClassInout() {
108+
var i2 = Klass()
109+
f {
110+
print(i2)
111+
}
112+
inoutUserKlass(&i2) // expected-warning {{'i2' mutated after capture by concurrent closure}}
113+
// expected-note @-5 {{variable defined here}}
114+
// expected-note @-5 {{variable captured by concurrent closure}}
115+
// expected-note @-5 {{capturing use}}
116+
}
117+
118+
func testCaseClassInoutField() {
119+
var i2 = Klass()
120+
i2 = Klass()
121+
f {
122+
print(i2)
123+
}
124+
// Since we are passing in .next inout and we are using a class this isn't a
125+
// violation.
126+
inoutUserOptKlass(&i2.next)
127+
}
128+
129+
////////////////////////////
130+
// Non Trivial Value Type //
131+
////////////////////////////
132+
133+
struct NonTrivialValueType {
134+
var i: Int
135+
var k: Klass? = nil
136+
137+
init(_ inputI: Int, _ inputKlass: Klass) {
138+
self.i = inputI
139+
self.k = inputKlass
140+
}
141+
}
142+
143+
func testCaseNonTrivialValue() {
144+
var i = NonTrivialValueType(17, Klass())
145+
f {
146+
// Currently emits a typechecker level error due to some sort of bug in the type checker.
147+
// print(i.i + 17)
148+
// print(i.i + 18)
149+
// print(i.i + 19)
150+
}
151+
152+
i.i = 20
153+
i.i += 21
154+
155+
// We only emit a warning here since we use the last write.
156+
//
157+
// TODO: Should we emit for all writes?
158+
i.i.addOne() // xpected-warning {{'i' mutated after capture by concurrent closure}}
159+
// xpected-note @-14 {{variable defined here}}
160+
// xpected-note @-14 {{variable captured by concurrent closure}}
161+
// xpected-note @-14 {{capturing use}}
162+
// xpected-note @-14 {{capturing use}}
163+
// xpected-note @-14 {{capturing use}}
164+
}
165+
166+
func testCaseNonTrivialValueInout() {
167+
var i = NonTrivialValueType(17, Klass())
168+
f {
169+
// Currently emits a typechecker level error due to some sort of bug in the type checker.
170+
// print(i.i + 17)
171+
// print(i.k ?? "none")
172+
}
173+
174+
// We only emit a warning here since we use the last write.
175+
inoutUserOptKlass(&i.k) // xpected-warning {{'i' mutated after capture by concurrent closure}}
176+
// xpected-note @-8 {{variable defined here}}
177+
// xpected-note @-8 {{variable captured by concurrent closure}}
178+
// xpected-note @-8 {{capturing use}}
179+
// xpected-note @-8 {{capturing use}}
180+
}
181+
182+
protocol MyProt {
183+
var i: Int { get set }
184+
var k: Klass? { get set }
185+
}
186+
187+
func testCaseAddressOnlyAllocBoxToStackable<T : MyProt>(i : T) {
188+
var i2 = i
189+
f {
190+
// Currently emits an error due to some sort of bug in the type checker.
191+
// print(i2.i + 17)
192+
// print(i2.k ?? "none")
193+
}
194+
195+
// TODO: Make sure we emit these once we support address only types!
196+
inoutUserOptKlass(&i2.k) // xpected-warning {{'i2' mutated after capture by concurrent closure}}
197+
// xpected-note @-8 {{variable defined here}}
198+
// xpected-note @-8 {{variable captured by concurrent closure}}
199+
// xpected-note @-8 {{capturing use}}
200+
// xpected-note @-8 {{capturing use}}
201+
}
202+
203+
// Alloc box to stack can't handle this test case, so show off a bit and make
204+
// sure we can emit a great diagnostic here!
205+
func testCaseAddressOnlyNoAllocBoxToStackable<T : MyProt>(i : T) {
206+
let f2 = F()
207+
var i2 = i
208+
f2.useConcurrent {
209+
// Currently emits a typechecker level error due to some sort of bug in the type checker.
210+
// print(i2.i + 17)
211+
// print(i2.k ?? "none")
212+
}
213+
214+
// TODO: Make sure we emit these once we support address only types!
215+
inoutUserOptKlass(&i2.k) // xpected-warning {{'i2' mutated after capture by concurrent closure}}
216+
// xpected-note @-8 {{variable defined here}}
217+
// xpected-note @-8 {{variable captured by concurrent closure}}
218+
// xpected-note @-8 {{capturing use}}
219+
// xpected-note @-8 {{capturing use}}
220+
}
221+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// RUN: %target-sil-opt -enable-experimental-concurrency %s
2+
3+
// REQUIRES: concurrency
4+
5+
// This test ensures that the SILVerifier does not enforce (yet) that concurrent
6+
// functions must have non-box parameters in canonical SIL for address only
7+
// types.
8+
9+
sil_stage canonical
10+
11+
import Builtin
12+
13+
func f(_: @escaping @concurrent () -> ())
14+
15+
@_hasMissingDesignatedInitializers
16+
class F {
17+
func useConcurrent(_: @escaping @concurrent () -> ())
18+
init()
19+
}
20+
21+
enum FakeOptional<T> {
22+
case none
23+
case some(T)
24+
}
25+
26+
struct Int {
27+
var _value: Builtin.Int64
28+
}
29+
30+
class Klass {}
31+
32+
/////////////////////////////////////////
33+
// Address Only Type Success (For Now) //
34+
/////////////////////////////////////////
35+
36+
protocol MyProt {
37+
var i: Int { get set }
38+
var k: FakeOptional<Klass> { get set }
39+
}
40+
41+
sil @$s37concurrentfunction_capturediagnostics1FCACycfC : $@convention(method) (@thick F.Type) -> @owned F // user: %5
42+
sil @$s37concurrentfunction_capturediagnostics20testCaseAddressOnly21iyx_tAA6MyProtRzlFyyJcfU_ : $@convention(thin) @concurrent <τ_0_0 where τ_0_0 : MyProt> (@guaranteed <τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <τ_0_0>) -> () // user: %15
43+
sil @$s37concurrentfunction_capturediagnostics1FC13useConcurrentyyyyJcF : $@convention(method) (@guaranteed @concurrent @callee_guaranteed () -> (), @guaranteed F) -> () // user: %19
44+
sil @$s37concurrentfunction_capturediagnostics17inoutUserOptKlassyyAA0F0CSgzF : $@convention(thin) (@inout FakeOptional<Klass>) -> () // user: %29
45+
sil @$s37concurrentfunction_capturediagnostics1FCfD : $@convention(method) (@owned F) -> ()
46+
47+
// This is address only so we shouldn't crash.
48+
sil hidden [ossa] @$s37concurrentfunction_capturediagnostics20testCaseAddressOnly21iyx_tAA6MyProtRzlF : $@convention(thin) <T where T : MyProt> (@in_guaranteed T) -> () {
49+
bb0(%0 : $*T):
50+
debug_value_addr %0 : $*T, let, name "i", argno 1 // id: %1
51+
%2 = alloc_stack $F, var, name "f2" // users: %34, %33, %7
52+
%3 = metatype $@thick F.Type // user: %5
53+
// function_ref F.__allocating_init()
54+
%4 = function_ref @$s37concurrentfunction_capturediagnostics1FCACycfC : $@convention(method) (@thick F.Type) -> @owned F // user: %5
55+
%5 = apply %4(%3) : $@convention(method) (@thick F.Type) -> @owned F // users: %6, %7
56+
%6 = copy_value %5 : $F // users: %11, %12
57+
store %5 to [init] %2 : $*F // id: %7
58+
%8 = alloc_box $<τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <T>, var, name "i2" // users: %32, %14, %9
59+
%9 = project_box %8 : $<τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <T>, 0 // users: %24, %10
60+
copy_addr %0 to [initialization] %9 : $*T // id: %10
61+
%11 = copy_value %6 : $F // users: %23, %18
62+
destroy_value %6 : $F // id: %12
63+
// function_ref closure #1 in testCaseAddressOnly2<A>(i:)
64+
%13 = function_ref @$s37concurrentfunction_capturediagnostics20testCaseAddressOnly21iyx_tAA6MyProtRzlFyyJcfU_ : $@convention(thin) @concurrent <τ_0_0 where τ_0_0 : MyProt> (@guaranteed <τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <τ_0_0>) -> () // user: %15
65+
%14 = copy_value %8 : $<τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <T> // user: %15
66+
%15 = partial_apply [callee_guaranteed] %13<T>(%14) : $@convention(thin) @concurrent <τ_0_0 where τ_0_0 : MyProt> (@guaranteed <τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <τ_0_0>) -> () // users: %17, %22
67+
// function_ref F.useConcurrent(_:)
68+
%16 = function_ref @$s37concurrentfunction_capturediagnostics1FC13useConcurrentyyyyJcF : $@convention(method) (@guaranteed @concurrent @callee_guaranteed () -> (), @guaranteed F) -> () // user: %19
69+
%17 = begin_borrow %15 : $@concurrent @callee_guaranteed () -> () // users: %20, %19
70+
%18 = begin_borrow %11 : $F // users: %21, %19
71+
%19 = apply %16(%17, %18) : $@convention(method) (@guaranteed @concurrent @callee_guaranteed () -> (), @guaranteed F) -> ()
72+
end_borrow %17 : $@concurrent @callee_guaranteed () -> () // id: %20
73+
end_borrow %18 : $F // id: %21
74+
destroy_value %15 : $@concurrent @callee_guaranteed () -> () // id: %22
75+
destroy_value %11 : $F // id: %23
76+
%24 = begin_access [modify] [dynamic] %9 : $*T // users: %31, %26
77+
%25 = witness_method $T, #MyProt.k!modify : <Self where Self : MyProt> (inout Self) -> () -> () : $@yield_once @convention(witness_method: MyProt) <τ_0_0 where τ_0_0 : MyProt> (@inout τ_0_0) -> @yields @inout FakeOptional<Klass> // user: %26
78+
(%26, %27) = begin_apply %25<T>(%24) : $@yield_once @convention(witness_method: MyProt) <τ_0_0 where τ_0_0 : MyProt> (@inout τ_0_0) -> @yields @inout FakeOptional<Klass> // users: %29, %30
79+
// function_ref inoutUserOptKlass(_:)
80+
%28 = function_ref @$s37concurrentfunction_capturediagnostics17inoutUserOptKlassyyAA0F0CSgzF : $@convention(thin) (@inout FakeOptional<Klass>) -> () // user: %29
81+
%29 = apply %28(%26) : $@convention(thin) (@inout FakeOptional<Klass>) -> ()
82+
end_apply %27 // id: %30
83+
end_access %24 : $*T // id: %31
84+
destroy_value %8 : $<τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <T> // id: %32
85+
destroy_addr %2 : $*F // id: %33
86+
dealloc_stack %2 : $*F // id: %34
87+
%35 = tuple () // user: %36
88+
return %35 : $() // id: %36
89+
} // end sil function '$s37concurrentfunction_capturediagnostics20testCaseAddressOnly21iyx_tAA6MyProtRzlF'
90+
91+
sil_vtable [serialized] F {
92+
#F.useConcurrent: (F) -> (@escaping @concurrent () -> ()) -> () : @$s37concurrentfunction_capturediagnostics1FC13useConcurrentyyyyJcF // F.useConcurrent(_:)
93+
#F.init!allocator: (F.Type) -> () -> F : @$s37concurrentfunction_capturediagnostics1FCACycfC // F.__allocating_init()
94+
#F.deinit!deallocator: @$s37concurrentfunction_capturediagnostics1FCfD // F.__deallocating_deinit
95+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// RUN: not --crash %target-sil-opt -enable-experimental-concurrency %s
2+
3+
// REQUIRES: concurrency
4+
5+
// This test ensures that the SILVerifier does enforce that concurrent functions
6+
// must have non-box parameters in canonical SIL for loadable types.
7+
8+
sil_stage canonical
9+
10+
import Builtin
11+
12+
func f(_: @escaping @concurrent () -> ())
13+
14+
@_hasMissingDesignatedInitializers
15+
class F {
16+
func useConcurrent(_: @escaping @concurrent () -> ())
17+
init()
18+
}
19+
20+
enum FakeOptional<T> {
21+
case none
22+
case some(T)
23+
}
24+
25+
struct Int {
26+
var _value: Builtin.Int64
27+
}
28+
29+
class Klass {}
30+
31+
////////////////////
32+
// Loadable Type //
33+
////////////////////
34+
35+
sil @$s37concurrentfunction_capturediagnostics1fyyyyJcF : $@convention(thin) (@guaranteed @concurrent @callee_guaranteed () -> ()) -> ()
36+
sil @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %5
37+
sil @$s37concurrentfunction_capturediagnostics20testCaseTrivialValueyyFyyJcfU_ : $@convention(thin) @concurrent (@guaranteed { var Int }) -> () // user: %10
38+
39+
// testCaseTrivialValue()
40+
sil hidden [ossa] @$s37concurrentfunction_capturediagnostics20testCaseTrivialValueyyF : $@convention(thin) () -> () {
41+
bb0:
42+
%0 = alloc_box ${ var Int }, var, name "i" // users: %34, %8, %1
43+
%1 = project_box %0 : ${ var Int }, 0 // users: %30, %26, %18, %9, %6
44+
%2 = integer_literal $Builtin.IntLiteral, 17 // user: %5
45+
%3 = metatype $@thin Int.Type // user: %5
46+
// function_ref Int.init(_builtinIntegerLiteral:)
47+
%4 = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %5
48+
%5 = apply %4(%2, %3) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %6
49+
store %5 to [trivial] %1 : $*Int // id: %6
50+
// function_ref closure #1 in testCaseTrivialValue()
51+
%7 = function_ref @$s37concurrentfunction_capturediagnostics20testCaseTrivialValueyyFyyJcfU_ : $@convention(thin) @concurrent (@guaranteed { var Int }) -> () // user: %10
52+
%8 = copy_value %0 : ${ var Int } // user: %10
53+
%10 = partial_apply [callee_guaranteed] %7(%8) : $@convention(thin) @concurrent (@guaranteed { var Int }) -> () // users: %13, %12
54+
// function_ref f(_:)
55+
%11 = function_ref @$s37concurrentfunction_capturediagnostics1fyyyyJcF : $@convention(thin) (@guaranteed @concurrent @callee_guaranteed () -> ()) -> () // user: %12
56+
%12 = apply %11(%10) : $@convention(thin) (@guaranteed @concurrent @callee_guaranteed () -> ()) -> ()
57+
destroy_value %10 : $@concurrent @callee_guaranteed () -> () // id: %13
58+
destroy_value %0 : ${ var Int }
59+
%35 = tuple () // user: %36
60+
return %35 : $() // id: %36
61+
} // end sil function '$s37concurrentfunction_capturediagnostics20testCaseTrivialValueyyF'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// RUN: %target-sil-opt -enable-experimental-concurrency %s
2+
3+
// REQUIRES: concurrency
4+
5+
// This test ensures that the SILVerifier does not enforce that concurrent
6+
// functions must have non-box parameters in raw SIL.
7+
8+
sil_stage raw
9+
10+
import Builtin
11+
12+
func f(_: @escaping @concurrent () -> ())
13+
14+
@_hasMissingDesignatedInitializers
15+
class F {
16+
func useConcurrent(_: @escaping @concurrent () -> ())
17+
init()
18+
}
19+
20+
enum FakeOptional<T> {
21+
case none
22+
case some(T)
23+
}
24+
25+
struct Int {
26+
var _value: Builtin.Int64
27+
}
28+
29+
class Klass {}
30+
31+
////////////////////
32+
// Loadable Type //
33+
////////////////////
34+
35+
sil @$s37concurrentfunction_capturediagnostics1fyyyyJcF : $@convention(thin) (@guaranteed @concurrent @callee_guaranteed () -> ()) -> ()
36+
sil @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %5
37+
sil @$s37concurrentfunction_capturediagnostics20testCaseTrivialValueyyFyyJcfU_ : $@convention(thin) @concurrent (@guaranteed { var Int }) -> () // user: %10
38+
39+
// testCaseTrivialValue()
40+
sil hidden [ossa] @$s37concurrentfunction_capturediagnostics20testCaseTrivialValueyyF : $@convention(thin) () -> () {
41+
bb0:
42+
%0 = alloc_box ${ var Int }, var, name "i" // users: %34, %8, %1
43+
%1 = project_box %0 : ${ var Int }, 0 // users: %30, %26, %18, %9, %6
44+
%2 = integer_literal $Builtin.IntLiteral, 17 // user: %5
45+
%3 = metatype $@thin Int.Type // user: %5
46+
// function_ref Int.init(_builtinIntegerLiteral:)
47+
%4 = function_ref @$sSi22_builtinIntegerLiteralSiBI_tcfC : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %5
48+
%5 = apply %4(%2, %3) : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int // user: %6
49+
store %5 to [trivial] %1 : $*Int // id: %6
50+
// function_ref closure #1 in testCaseTrivialValue()
51+
%7 = function_ref @$s37concurrentfunction_capturediagnostics20testCaseTrivialValueyyFyyJcfU_ : $@convention(thin) @concurrent (@guaranteed { var Int }) -> () // user: %10
52+
%8 = copy_value %0 : ${ var Int } // user: %10
53+
mark_function_escape %1 : $*Int // id: %9
54+
%10 = partial_apply [callee_guaranteed] %7(%8) : $@convention(thin) @concurrent (@guaranteed { var Int }) -> () // users: %13, %12
55+
// function_ref f(_:)
56+
%11 = function_ref @$s37concurrentfunction_capturediagnostics1fyyyyJcF : $@convention(thin) (@guaranteed @concurrent @callee_guaranteed () -> ()) -> () // user: %12
57+
%12 = apply %11(%10) : $@convention(thin) (@guaranteed @concurrent @callee_guaranteed () -> ()) -> ()
58+
destroy_value %10 : $@concurrent @callee_guaranteed () -> () // id: %13
59+
destroy_value %0 : ${ var Int }
60+
%35 = tuple () // user: %36
61+
return %35 : $() // id: %36
62+
} // end sil function '$s37concurrentfunction_capturediagnostics20testCaseTrivialValueyyF'
63+
64+
65+
/////////////////////////////////////////
66+
// Address Only Type Success (For Now) //
67+
/////////////////////////////////////////
68+
69+
protocol MyProt {
70+
var i: Int { get set }
71+
var k: FakeOptional<Klass> { get set }
72+
}
73+
74+
sil @$s37concurrentfunction_capturediagnostics1FCACycfC : $@convention(method) (@thick F.Type) -> @owned F // user: %5
75+
sil @$s37concurrentfunction_capturediagnostics20testCaseAddressOnly21iyx_tAA6MyProtRzlFyyJcfU_ : $@convention(thin) @concurrent <τ_0_0 where τ_0_0 : MyProt> (@guaranteed <τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <τ_0_0>) -> () // user: %15
76+
sil @$s37concurrentfunction_capturediagnostics1FC13useConcurrentyyyyJcF : $@convention(method) (@guaranteed @concurrent @callee_guaranteed () -> (), @guaranteed F) -> () // user: %19
77+
sil @$s37concurrentfunction_capturediagnostics17inoutUserOptKlassyyAA0F0CSgzF : $@convention(thin) (@inout FakeOptional<Klass>) -> () // user: %29
78+
sil @$s37concurrentfunction_capturediagnostics1FCfD : $@convention(method) (@owned F) -> ()
79+
80+
// This is address only so we shouldn't crash.
81+
sil hidden [ossa] @$s37concurrentfunction_capturediagnostics20testCaseAddressOnly21iyx_tAA6MyProtRzlF : $@convention(thin) <T where T : MyProt> (@in_guaranteed T) -> () {
82+
bb0(%0 : $*T):
83+
debug_value_addr %0 : $*T, let, name "i", argno 1 // id: %1
84+
%2 = alloc_stack $F, var, name "f2" // users: %34, %33, %7
85+
%3 = metatype $@thick F.Type // user: %5
86+
// function_ref F.__allocating_init()
87+
%4 = function_ref @$s37concurrentfunction_capturediagnostics1FCACycfC : $@convention(method) (@thick F.Type) -> @owned F // user: %5
88+
%5 = apply %4(%3) : $@convention(method) (@thick F.Type) -> @owned F // users: %6, %7
89+
%6 = copy_value %5 : $F // users: %11, %12
90+
store %5 to [init] %2 : $*F // id: %7
91+
%8 = alloc_box $<τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <T>, var, name "i2" // users: %32, %14, %9
92+
%9 = project_box %8 : $<τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <T>, 0 // users: %24, %10
93+
copy_addr %0 to [initialization] %9 : $*T // id: %10
94+
%11 = copy_value %6 : $F // users: %23, %18
95+
destroy_value %6 : $F // id: %12
96+
// function_ref closure #1 in testCaseAddressOnly2<A>(i:)
97+
%13 = function_ref @$s37concurrentfunction_capturediagnostics20testCaseAddressOnly21iyx_tAA6MyProtRzlFyyJcfU_ : $@convention(thin) @concurrent <τ_0_0 where τ_0_0 : MyProt> (@guaranteed <τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <τ_0_0>) -> () // user: %15
98+
%14 = copy_value %8 : $<τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <T> // user: %15
99+
%15 = partial_apply [callee_guaranteed] %13<T>(%14) : $@convention(thin) @concurrent <τ_0_0 where τ_0_0 : MyProt> (@guaranteed <τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <τ_0_0>) -> () // users: %17, %22
100+
// function_ref F.useConcurrent(_:)
101+
%16 = function_ref @$s37concurrentfunction_capturediagnostics1FC13useConcurrentyyyyJcF : $@convention(method) (@guaranteed @concurrent @callee_guaranteed () -> (), @guaranteed F) -> () // user: %19
102+
%17 = begin_borrow %15 : $@concurrent @callee_guaranteed () -> () // users: %20, %19
103+
%18 = begin_borrow %11 : $F // users: %21, %19
104+
%19 = apply %16(%17, %18) : $@convention(method) (@guaranteed @concurrent @callee_guaranteed () -> (), @guaranteed F) -> ()
105+
end_borrow %17 : $@concurrent @callee_guaranteed () -> () // id: %20
106+
end_borrow %18 : $F // id: %21
107+
destroy_value %15 : $@concurrent @callee_guaranteed () -> () // id: %22
108+
destroy_value %11 : $F // id: %23
109+
%24 = begin_access [modify] [dynamic] %9 : $*T // users: %31, %26
110+
%25 = witness_method $T, #MyProt.k!modify : <Self where Self : MyProt> (inout Self) -> () -> () : $@yield_once @convention(witness_method: MyProt) <τ_0_0 where τ_0_0 : MyProt> (@inout τ_0_0) -> @yields @inout FakeOptional<Klass> // user: %26
111+
(%26, %27) = begin_apply %25<T>(%24) : $@yield_once @convention(witness_method: MyProt) <τ_0_0 where τ_0_0 : MyProt> (@inout τ_0_0) -> @yields @inout FakeOptional<Klass> // users: %29, %30
112+
// function_ref inoutUserOptKlass(_:)
113+
%28 = function_ref @$s37concurrentfunction_capturediagnostics17inoutUserOptKlassyyAA0F0CSgzF : $@convention(thin) (@inout FakeOptional<Klass>) -> () // user: %29
114+
%29 = apply %28(%26) : $@convention(thin) (@inout FakeOptional<Klass>) -> ()
115+
end_apply %27 // id: %30
116+
end_access %24 : $*T // id: %31
117+
destroy_value %8 : $<τ_0_0 where τ_0_0 : MyProt> { var τ_0_0 } <T> // id: %32
118+
destroy_addr %2 : $*F // id: %33
119+
dealloc_stack %2 : $*F // id: %34
120+
%35 = tuple () // user: %36
121+
return %35 : $() // id: %36
122+
} // end sil function '$s37concurrentfunction_capturediagnostics20testCaseAddressOnly21iyx_tAA6MyProtRzlF'
123+
124+
sil_vtable [serialized] F {
125+
#F.useConcurrent: (F) -> (@escaping @concurrent () -> ()) -> () : @$s37concurrentfunction_capturediagnostics1FC13useConcurrentyyyyJcF // F.useConcurrent(_:)
126+
#F.init!allocator: (F.Type) -> () -> F : @$s37concurrentfunction_capturediagnostics1FCACycfC // F.__allocating_init()
127+
#F.deinit!deallocator: @$s37concurrentfunction_capturediagnostics1FCfD // F.__deallocating_deinit
128+
}

0 commit comments

Comments
 (0)
Please sign in to comment.