Skip to content

Commit 56ec625

Browse files
authored
Merge pull request swiftlang#77308 from rjmccall/optional-isolation-check
Fix a crash when emitting isolation checks in a function with optional isolation
2 parents 5a5b1ef + b603c3c commit 56ec625

File tree

7 files changed

+150
-12
lines changed

7 files changed

+150
-12
lines changed

include/swift/SIL/SILBuilder.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2711,6 +2711,17 @@ class SILBuilder {
27112711
CaseCounts, DefaultCount, forwardingOwnershipKind));
27122712
}
27132713

2714+
/// A convenience function to decompose a scalar optional value with a
2715+
/// switch_enum. Returns the object value, which will only be valid
2716+
/// in `someBB`. Don't forget to switch insertion blocks after
2717+
/// calling this.
2718+
SILPhiArgument *createSwitchOptional(
2719+
SILLocation loc, SILValue operand,
2720+
SILBasicBlock *someBB, SILBasicBlock *noneBB,
2721+
ValueOwnershipKind forwardingOwnershipKind,
2722+
ProfileCounter someCount = ProfileCounter(),
2723+
ProfileCounter noneCount = ProfileCounter());
2724+
27142725
SwitchEnumAddrInst *createSwitchEnumAddr(
27152726
SILLocation Loc, SILValue Operand, SILBasicBlock *DefaultBB,
27162727
ArrayRef<std::pair<EnumElementDecl *, SILBasicBlock *>> CaseBBs,

lib/SIL/IR/SILBuilder.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,25 @@ void SILBuilder::emitScopedBorrowOperation(SILLocation loc, SILValue original,
681681
createEndBorrow(loc, value);
682682
}
683683

684+
SILPhiArgument *SILBuilder::createSwitchOptional(
685+
SILLocation loc, SILValue operand,
686+
SILBasicBlock *someBB, SILBasicBlock *noneBB,
687+
ValueOwnershipKind forwardingOwnershipKind,
688+
ProfileCounter someCount,
689+
ProfileCounter noneCount) {
690+
ProfileCounter counts[] = {someCount, noneCount};
691+
std::optional<ArrayRef<ProfileCounter>> countsArg = std::nullopt;
692+
if (someCount || noneCount) countsArg = counts;
693+
694+
auto &ctx = getASTContext();
695+
auto sei = createSwitchEnum(loc, operand, /*default*/ nullptr,
696+
{{ctx.getOptionalSomeDecl(), someBB},
697+
{ctx.getOptionalNoneDecl(), noneBB}},
698+
countsArg, /*default*/ProfileCounter(),
699+
forwardingOwnershipKind);
700+
return sei->createResult(someBB, operand->getType().unwrapOptionalType());
701+
}
702+
684703
/// Attempt to propagate ownership from \p operand to the returned forwarding
685704
/// ownership where the forwarded value has type \p targetType. If this fails,
686705
/// return Owned forwarding ownership instead.

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,24 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
10691069
what + " must be Optional<Builtin.Executor>");
10701070
}
10711071

1072+
/// Require the operand to be an object of some type that conforms to
1073+
/// Actor or DistributedActor.
1074+
void requireAnyActorType(SILValue value, bool allowOptional,
1075+
bool allowExecutor, const Twine &what) {
1076+
auto type = value->getType();
1077+
require(type.isObject(), what + " must be an object type");
1078+
1079+
auto actorType = type.getASTType();
1080+
if (allowOptional) {
1081+
if (auto objectType = actorType.getOptionalObjectType())
1082+
actorType = objectType;
1083+
}
1084+
if (allowExecutor && isa<BuiltinExecutorType>(actorType))
1085+
return;
1086+
require(actorType->isAnyActorType(),
1087+
what + " must be some kind of actor type");
1088+
}
1089+
10721090
/// Assert that two types are equal.
10731091
void requireSameType(Type type1, Type type2, const Twine &complaint) {
10741092
_require(type1->isEqual(type2), complaint,
@@ -5806,10 +5824,21 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
58065824
if (HI->getModule().getStage() == SILStage::Lowered) {
58075825
requireOptionalExecutorType(executor,
58085826
"hop_to_executor operand in lowered SIL");
5827+
} else {
5828+
requireAnyActorType(executor,
5829+
/*allow optional*/ true,
5830+
/*allow executor*/ true,
5831+
"hop_to_executor operand");
58095832
}
58105833
}
58115834

58125835
void checkExtractExecutorInst(ExtractExecutorInst *EEI) {
5836+
requireObjectType(BuiltinExecutorType, EEI,
5837+
"extract_executor result");
5838+
requireAnyActorType(EEI->getExpectedExecutor(),
5839+
/*allow optional*/ false,
5840+
/*allow executor*/ false,
5841+
"extract_executor operand");
58135842
if (EEI->getModule().getStage() == SILStage::Lowered) {
58145843
require(false,
58155844
"extract_executor instruction should have been lowered away");

lib/SILGen/SILGenConcurrency.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,23 @@ void SILGenFunction::emitPreconditionCheckExpectedExecutor(
736736
// We don't want the debugger to step into these.
737737
loc.markAutoGenerated();
738738

739+
// If the function is isolated to an optional actor reference,
740+
// check dynamically whether it's non-null. We don't need to
741+
// do an assertion if the expected expected is nil.
742+
SILBasicBlock *noneBB = nullptr;
743+
bool isOptional = (bool) executorOrActor->getType().getOptionalObjectType();
744+
if (isOptional) {
745+
// Start by emiting the .some path.
746+
noneBB = createBasicBlock();
747+
auto someBB = createBasicBlockBefore(noneBB);
748+
749+
executorOrActor =
750+
B.createSwitchOptional(loc, executorOrActor, someBB, noneBB,
751+
executorOrActor->getOwnershipKind());
752+
753+
B.emitBlock(someBB);
754+
}
755+
739756
// Get the executor.
740757
SILValue executor = B.createExtractExecutor(loc, executorOrActor);
741758

@@ -747,6 +764,20 @@ void SILGenFunction::emitPreconditionCheckExpectedExecutor(
747764
{args.filenameStartPointer, args.filenameLength, args.filenameIsAscii,
748765
args.line, ManagedValue::forObjectRValueWithoutOwnership(executor)},
749766
SGFContext());
767+
768+
// Complete the optional control flow if we had an optional value.
769+
if (isOptional) {
770+
assert(noneBB);
771+
// Finish the .some path by branching to the continuation block.
772+
auto contBB = createBasicBlockAfter(noneBB);
773+
B.createBranch(loc, contBB);
774+
775+
// The .none path is trivial.
776+
B.emitBlock(noneBB);
777+
B.createBranch(loc, contBB);
778+
779+
B.emitBlock(contBB);
780+
}
750781
}
751782

752783
bool SILGenFunction::unsafelyInheritsExecutor() {

lib/SILOptimizer/Mandatory/LowerHopToActor.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,21 +112,19 @@ static bool isOptionalBuiltinExecutor(SILType type) {
112112
void LowerHopToActor::recordDominatingInstFor(SILInstruction *inst) {
113113
SILValue actor;
114114
if (auto *hop = dyn_cast<HopToExecutorInst>(inst)) {
115+
// hop_to_executor can take optional and non-optional Builtin.Executor
116+
// values directly. If we see Optional<Builtin.Executor>, there's
117+
// nothing to do.
115118
actor = hop->getTargetExecutor();
116-
// If hop_to_executor was emitted with an optional executor operand,
117-
// there's nothing to derive.
118-
if (isOptionalBuiltinExecutor(actor->getType())) {
119+
if (isOptionalBuiltinExecutor(actor->getType()))
119120
return;
120-
}
121121
} else if (auto *extract = dyn_cast<ExtractExecutorInst>(inst)) {
122+
// extract_executor can only take non-optional actor values.
122123
actor = extract->getExpectedExecutor();
123124
} else {
124125
return;
125126
}
126127

127-
if (isOptionalBuiltinExecutor(actor->getType()))
128-
return;
129-
130128
auto *dominatingInst = ExecutorDerivationForActor.lookup(actor);
131129
if (dominatingInst) {
132130
DominatingActorHops.insert(dominatingInst, inst);
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// RUN: %empty-directory(%t/src)
2+
// RUN: split-file %s %t/src
3+
4+
/// Build the library A
5+
// RUN: %target-swift-frontend -emit-module %t/src/Preconcurrency.swift \
6+
// RUN: -module-name Preconcurrency -swift-version 5 -enable-library-evolution \
7+
// RUN: -emit-module-path %t/Preconcurrency.swiftmodule
8+
9+
// RUN: %target-swift-emit-silgen -swift-version 6 -disable-availability-checking -I %t %t/src/test.swift -o - -verify | %FileCheck %s
10+
11+
// REQUIRES: concurrency
12+
13+
//--- Preconcurrency.swift
14+
15+
public func takeNonSendableClosure_preconcurrency(_ fn: @escaping () -> Int) {}
16+
public func takeNonSendableClosure_preconcurrency_generic<T>(_ fn: @escaping () -> T) {}
17+
18+
//--- test.swift
19+
20+
import Preconcurrency
21+
22+
func takeNonSendableClosure_strict(_ fn: @escaping () -> Int) { }
23+
@preconcurrency
24+
25+
actor MyActor {
26+
var counter = 0
27+
}
28+
func forceIsolation(isolation: isolated MyActor?) {}
29+
30+
// rdar://132478429
31+
//
32+
// We were trying to emit dynamic isolation checks in functions that are
33+
// isolated to optional actor references by just passing that reference
34+
// directly to extract_executor, which is incorrect --- we need to skip
35+
// the check when the reference is nil.
36+
37+
// CHECK-LABEL: sil private [ossa] @$s4test0A25OptionalIsolation_checked9isolationyAA7MyActorCSgYi_tFSiycfU_ :
38+
// CHECK: [[T0:%.*]] = copy_value %0 : $Optional<MyActor>
39+
// CHECK-NEXT: [[BORROW:%.*]] = begin_borrow [[T0]] :
40+
// CHECK-NEXT: switch_enum [[BORROW]] : $Optional<MyActor>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2
41+
// CHECK: bb1([[T0:%.*]] : @guaranteed $MyActor):
42+
// CHECK-NEXT: extract_executor [[T0]] : $MyActor
43+
// CHECK: // function_ref _checkExpectedExecutor
44+
// CHECK: br bb3
45+
// CHECK: bb2:
46+
// CHECK-NEXT: br bb3
47+
func testOptionalIsolation_checked(isolation: isolated MyActor?) {
48+
takeNonSendableClosure_preconcurrency {
49+
// This closure inherits isolation because it's non-Sendable, and
50+
// it requires a dynamic check because we're passing it to a
51+
// preconcurrency function.
52+
forceIsolation(isolation: isolation)
53+
return 0
54+
}
55+
}

test/SILOptimizer/consume_operator_kills_addresses_dbginfo.sil

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ public protocol P {
1919
sil @forceSplit : $@convention(thin) @async () -> ()
2020
sil @genericUse : $@convention(thin) <τ_0_0> (@in_guaranteed τ_0_0) -> ()
2121

22-
enum Optional<T> {
23-
case some(T)
24-
case none
25-
}
26-
2722
///////////
2823
// Tests //
2924
///////////

0 commit comments

Comments
 (0)