Skip to content

[region-isolation] Only make partial_apply actor derived if we capture an isolated parameter... not just any actor. #72004

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Mar 3, 2024
6 changes: 6 additions & 0 deletions include/swift/AST/CaptureInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,12 @@ class CapturedValue {
return Value.getPointer().is<OpaqueValueExpr *>();
}

/// Returns true if this captured value is a local capture.
///
/// NOTE: This implies that the value is not dynamic self metadata, since
/// values with decls are the only values that are able to be local captures.
bool isLocalCapture() const;

CapturedValue mergeFlags(CapturedValue cv) {
assert(Value.getPointer() == cv.Value.getPointer() &&
"merging flags on two different value decls");
Expand Down
19 changes: 10 additions & 9 deletions include/swift/SIL/ApplySite.h
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,16 @@ class ApplySite {
return getApplyOptions().contains(ApplyFlags::DoesNotAwait);
}

/// Return the SILParameterInfo for this operand in the callee function.
SILParameterInfo getArgumentParameterInfo(const Operand &oper) const {
assert(!getArgumentConvention(oper).isIndirectOutParameter() &&
"Can only be applied to non-out parameters");

// The ParameterInfo is going to be the parameter in the caller.
unsigned calleeArgIndex = getCalleeArgIndex(oper);
return getSubstCalleeConv().getParamInfoForSILArg(calleeArgIndex);
}

static ApplySite getFromOpaqueValue(void *p) { return ApplySite(p); }

friend bool operator==(ApplySite lhs, ApplySite rhs) {
Expand Down Expand Up @@ -801,15 +811,6 @@ class FullApplySite : public ApplySite {
}
}

SILParameterInfo getArgumentParameterInfo(const Operand &oper) const {
assert(!getArgumentConvention(oper).isIndirectOutParameter() &&
"Can only be applied to non-out parameters");

// The ParameterInfo is going to be the parameter in the caller.
unsigned calleeArgIndex = getCalleeArgIndex(oper);
return getSubstCalleeConv().getParamInfoForSILArg(calleeArgIndex);
}

static FullApplySite getFromOpaqueValue(void *p) { return FullApplySite(p); }

static bool classof(const SILInstruction *inst) {
Expand Down
27 changes: 20 additions & 7 deletions lib/AST/CaptureInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@

using namespace swift;

//===----------------------------------------------------------------------===//
// MARK: CaptureInfo
//===----------------------------------------------------------------------===//

CaptureInfo::CaptureInfo(ASTContext &ctx, ArrayRef<CapturedValue> captures,
DynamicSelfType *dynamicSelf,
OpaqueValueExpr *opaqueValue,
Expand Down Expand Up @@ -56,9 +60,10 @@ CaptureInfo CaptureInfo::empty() {
}

bool CaptureInfo::hasLocalCaptures() const {
for (auto capture : getCaptures())
if (capture.getDecl()->isLocalCapture())
for (auto capture : getCaptures()) {
if (capture.isLocalCapture())
return true;
}
return false;
}

Expand All @@ -71,7 +76,7 @@ getLocalCaptures(SmallVectorImpl<CapturedValue> &Result) const {

// Filter out global variables.
for (auto capture : getCaptures()) {
if (!capture.getDecl()->isLocalCapture())
if (!capture.isLocalCapture())
continue;

Result.push_back(capture);
Expand All @@ -83,10 +88,10 @@ VarDecl *CaptureInfo::getIsolatedParamCapture() const {
return nullptr;

for (const auto &capture : getCaptures()) {
if (!capture.getDecl()->isLocalCapture())
continue;

if (capture.isDynamicSelfMetadata())
// NOTE: isLocalCapture() returns false if we have dynamic self metadata
// since dynamic self metadata is never an isolated capture. So we can just
// call isLocalCapture without checking for dynamic self metadata.
if (!capture.isLocalCapture())
continue;

// If we captured an isolated parameter, return it.
Expand Down Expand Up @@ -134,3 +139,11 @@ void CaptureInfo::print(raw_ostream &OS) const {
OS << ')';
}

//===----------------------------------------------------------------------===//
// MARK: CapturedValue
//===----------------------------------------------------------------------===//

bool CapturedValue::isLocalCapture() const {
auto *decl = Value.getPointer().dyn_cast<ValueDecl *>();
return decl && decl->isLocalCapture();
}
42 changes: 26 additions & 16 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1917,6 +1917,7 @@ lowerCaptureContextParameters(TypeConverter &TC, SILDeclRef function,
// signature from the AST for that.
auto origGenericSig = function.getAnyFunctionRef()->getGenericSignature();
auto loweredCaptures = TC.getLoweredLocalCaptures(function);
auto *isolatedParam = loweredCaptures.getIsolatedParamCapture();

for (auto capture : loweredCaptures.getCaptures()) {
if (capture.isDynamicSelfMetadata()) {
Expand Down Expand Up @@ -1955,13 +1956,19 @@ lowerCaptureContextParameters(TypeConverter &TC, SILDeclRef function,
continue;
}

auto *VD = capture.getDecl();
auto type = VD->getInterfaceType();
auto *varDecl = capture.getDecl();
auto type = varDecl->getInterfaceType();
auto canType = type->getReducedType(origGenericSig);

auto options = SILParameterInfo::Options();
if (isolatedParam == varDecl) {
options |= SILParameterInfo::Isolated;
isolatedParam = nullptr;
}

// If we're capturing a parameter pack, wrap it in a tuple.
if (isa<PackExpansionType>(canType)) {
assert(!cast<ParamDecl>(VD)->supportsMutation() &&
assert(!cast<ParamDecl>(varDecl)->supportsMutation() &&
"Cannot capture a pack as an lvalue");

SmallVector<TupleTypeElt, 1> elts;
Expand All @@ -1983,7 +1990,7 @@ lowerCaptureContextParameters(TypeConverter &TC, SILDeclRef function,
} else {
convention = ParameterConvention::Direct_Guaranteed;
}
SILParameterInfo param(loweredTy.getASTType(), convention);
SILParameterInfo param(loweredTy.getASTType(), convention, options);
inputs.push_back(param);
break;
}
Expand All @@ -1995,10 +2002,10 @@ lowerCaptureContextParameters(TypeConverter &TC, SILDeclRef function,
.getLoweredType();
// Lvalues are captured as a box that owns the captured value.
auto boxTy = TC.getInterfaceBoxTypeForCapture(
VD, minimalLoweredTy.getASTType(),
varDecl, minimalLoweredTy.getASTType(),
/*mutable*/ true);
auto convention = ParameterConvention::Direct_Guaranteed;
auto param = SILParameterInfo(boxTy, convention);
auto param = SILParameterInfo(boxTy, convention, options);
inputs.push_back(param);
break;
}
Expand All @@ -2009,35 +2016,38 @@ lowerCaptureContextParameters(TypeConverter &TC, SILDeclRef function,
TypeExpansionContext::minimal())
.getLoweredType();
// Lvalues are captured as a box that owns the captured value.
auto boxTy =
TC.getInterfaceBoxTypeForCapture(VD, minimalLoweredTy.getASTType(),
/*mutable*/ false);
auto boxTy = TC.getInterfaceBoxTypeForCapture(
varDecl, minimalLoweredTy.getASTType(),
/*mutable*/ false);
auto convention = ParameterConvention::Direct_Guaranteed;
auto param = SILParameterInfo(boxTy, convention);
auto param = SILParameterInfo(boxTy, convention, options);
inputs.push_back(param);
break;
}
case CaptureKind::StorageAddress: {
// Non-escaping lvalues are captured as the address of the value.
SILType ty = loweredTy.getAddressType();
auto param =
SILParameterInfo(ty.getASTType(),
ParameterConvention::Indirect_InoutAliasable);
auto param = SILParameterInfo(
ty.getASTType(), ParameterConvention::Indirect_InoutAliasable,
options);
inputs.push_back(param);
break;
}
case CaptureKind::Immutable: {
// 'let' constants that are address-only are captured as the address of
// the value and will be consumed by the closure.
SILType ty = loweredTy.getAddressType();
auto param =
SILParameterInfo(ty.getASTType(),
ParameterConvention::Indirect_In_Guaranteed);
auto param = SILParameterInfo(ty.getASTType(),
ParameterConvention::Indirect_In_Guaranteed,
options);
inputs.push_back(param);
break;
}
}
}
assert(!isolatedParam &&
"If we had an isolated capture, we should have visited it when "
"iterating over loweredCaptures.getCaptures().");
}

static AccessorDecl *
Expand Down
15 changes: 8 additions & 7 deletions lib/SILOptimizer/Analysis/RegionAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1693,18 +1693,19 @@ class PartitionOpTranslator {
}

SILMultiAssignOptions options;
for (auto arg : pai->getOperandValues()) {
if (auto value = tryToTrackValue(arg)) {
for (auto &op : pai->getAllOperands()) {
if (auto value = tryToTrackValue(op.get())) {
if (value->isActorDerived()) {
options |= SILMultiAssignFlags::PropagatesActorSelf;
}
} else {
// NOTE: One may think that only sendable things can enter
// here... but we treat things like function_ref/class_method which
// are non-Sendable as sendable for our purposes.
if (arg->getType().isActor()) {
// We only treat Sendable values as propagating actor self if the
// partial apply has operand as an sil_isolated parameter.
ApplySite applySite(pai);
if (applySite.isArgumentOperand(op) &&
ApplySite(pai).getArgumentParameterInfo(op).hasOption(
SILParameterInfo::Isolated))
options |= SILMultiAssignFlags::PropagatesActorSelf;
}
}
}

Expand Down
8 changes: 0 additions & 8 deletions test/Concurrency/transfernonsendable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,10 @@ func transferNonIsolatedNonAsyncClosureTwice() async {
var actorCaptureClosure = { print(a) }
await transferToMain(actorCaptureClosure) // expected-complete-warning {{passing argument of non-sendable type '() -> ()' into main actor-isolated context may introduce data races}}
// expected-complete-note @-1 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
// expected-tns-warning @-2 {{transferring 'actorCaptureClosure' may cause a race}}
// expected-tns-note @-3 {{transferring nonisolated 'actorCaptureClosure' to main actor-isolated callee could cause races between main actor-isolated and nonisolated uses}}

actorCaptureClosure = { print(a) }
await transferToMain(actorCaptureClosure) // expected-complete-warning {{passing argument of non-sendable type '() -> ()' into main actor-isolated context may introduce data races}}
// expected-complete-note @-1 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
// expected-tns-warning @-2 {{transferring 'actorCaptureClosure' may cause a race}}
// expected-tns-note @-3 {{transferring nonisolated 'actorCaptureClosure' to main actor-isolated callee could cause races between main actor-isolated and nonisolated uses}}
}

extension Actor {
Expand Down Expand Up @@ -403,8 +399,6 @@ func testSimpleLetClosureCaptureActor() async {
let closure = { print(a) }
await transferToMain(closure) // expected-complete-warning {{passing argument of non-sendable type '() -> ()' into main actor-isolated context may introduce data races}}
// expected-complete-note @-1 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
// expected-tns-warning @-2 {{transferring 'closure' may cause a race}}
// expected-tns-note @-3 {{transferring nonisolated 'closure' to main actor-isolated callee could cause races between main actor-isolated and nonisolated uses}}
}

#if TYPECHECKER_ONLY
Expand Down Expand Up @@ -441,8 +435,6 @@ func testSimpleVarClosureCaptureActor() async {
closure = { print(a) }
await transferToMain(closure) // expected-complete-warning {{passing argument of non-sendable type '() -> ()' into main actor-isolated context may introduce data races}}
// expected-complete-note @-1 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
// expected-tns-warning @-2 {{transferring 'closure' may cause a race}}
// expected-tns-note @-3 {{transferring nonisolated 'closure' to main actor-isolated callee could cause races between main actor-isolated and nonisolated uses}}
}

#if TYPECHECKER_ONLY
Expand Down
21 changes: 21 additions & 0 deletions test/SILGen/capture_info_invariants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %target-swift-frontend -o - -emit-silgen %s

// REQUIRES: concurrency

// READ THIS! This test is meant to test invariants around CaptureInfo usage in
// SILGen. Only add examples to this if we hit a crasher in capture info and the
// example makes sure we do not crash again.

class GetIsolatedParamTest {
public static var rootType: Any.Type? { nil }
}

extension GetIsolatedParamTest : CustomDebugStringConvertible {
public var debugDescription: String {
let description = "\\\(String(describing: Self.rootType!))"
let x: Any.Type? = nil
// The error is triggered by the ?? autoclosure.
var valueType: Any.Type? = x ?? Self.rootType
return description
}
}
2 changes: 1 addition & 1 deletion test/SILGen/check_executor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public actor MyActor {
}
}

// CHECK-CANONICAL-LABEL: sil private [ossa] @$s4test7MyActorC0A13LocalFunctionyyF5localL_SiyF : $@convention(thin) (@guaranteed MyActor) -> Int
// CHECK-CANONICAL-LABEL: sil private [ossa] @$s4test7MyActorC0A13LocalFunctionyyF5localL_SiyF : $@convention(thin) (@sil_isolated @guaranteed MyActor) -> Int
// CHECK-CANONICAL: [[CAPTURE:%.*]] = copy_value %0 : $MyActor
// CHECK-CANONICAL-NEXT: [[BORROWED_CAPTURE:%.*]] = begin_borrow [[CAPTURE]] : $MyActor
// CHECK-CANONICAL-NEXT: [[EXECUTOR:%.*]] = builtin "buildDefaultActorExecutorRef"<MyActor>([[BORROWED_CAPTURE]] : $MyActor) : $Builtin.Executor
Expand Down
2 changes: 1 addition & 1 deletion test/SILGen/hop_to_executor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ actor MyActor {
// CHECK: } // end sil function '$s4test7MyActorC0A7ClosureSiyYaF'

///// closure #1 in MyActor.testClosure()
// CHECK-LABEL: sil private [ossa] @$s4test7MyActorC0A7ClosureSiyYaFSiyYaXEfU_ : $@convention(thin) @async (@guaranteed MyActor) -> Int {
// CHECK-LABEL: sil private [ossa] @$s4test7MyActorC0A7ClosureSiyYaFSiyYaXEfU_ : $@convention(thin) @async (@sil_isolated @guaranteed MyActor) -> Int {
// CHECK: [[COPIED_SELF:%[0-9]+]] = copy_value %0 : $MyActor
// CHECK: [[BORROWED_SELF:%[0-9]+]] = begin_borrow [[COPIED_SELF]] : $MyActor
// CHECK: hop_to_executor [[BORROWED_SELF]] : $MyActor
Expand Down
6 changes: 3 additions & 3 deletions test/SILGen/isolated_any.swift
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ func testEraseInheritingSyncMainActorClosure() {

// CHECK-LABEL: sil hidden [ossa] @$s4test7MyActorC0a19EraseInheritingSyncC7ClosureyyF
// CHECK: // function_ref closure #1
// CHECK-NEXT: [[CLOSURE_FN:%.*]] = function_ref @$s4test7MyActorC0a19EraseInheritingSyncC7ClosureyyFyycfU_ : $@convention(thin) (@guaranteed Optional<any Actor>, @guaranteed MyActor) -> ()
// CHECK-NEXT: [[CLOSURE_FN:%.*]] = function_ref @$s4test7MyActorC0a19EraseInheritingSyncC7ClosureyyFyycfU_ : $@convention(thin) (@guaranteed Optional<any Actor>, @sil_isolated @guaranteed MyActor) -> ()
// CHECK-NEXT: [[CAPTURE:%.*]] = copy_value %0 : $MyActor
// CHECK-NEXT: [[CAPTURE_FOR_ISOLATION:%.*]] = copy_value [[CAPTURE]] : $MyActor
// CHECK-NEXT: [[ISOLATION_OBJECT:%.*]] = init_existential_ref [[CAPTURE_FOR_ISOLATION]] : $MyActor : $MyActor, $any Actor
Expand Down Expand Up @@ -332,7 +332,7 @@ func testEraseInheritingAsyncMainActorClosure() {

// CHECK-LABEL: sil hidden [ossa] @$s4test7MyActorC0a20EraseInheritingAsyncC7ClosureyyF
// CHECK: // function_ref closure #1
// CHECK-NEXT: [[CLOSURE_FN:%.*]] = function_ref @$s4test7MyActorC0a20EraseInheritingAsyncC7ClosureyyFyyYacfU_ : $@convention(thin) @async (@guaranteed Optional<any Actor>, @guaranteed MyActor) -> ()
// CHECK-NEXT: [[CLOSURE_FN:%.*]] = function_ref @$s4test7MyActorC0a20EraseInheritingAsyncC7ClosureyyFyyYacfU_ : $@convention(thin) @async (@guaranteed Optional<any Actor>, @sil_isolated @guaranteed MyActor) -> ()
// CHECK-NEXT: [[CAPTURE:%.*]] = copy_value %0 : $MyActor
// CHECK-NEXT: [[CAPTURE_FOR_ISOLATION:%.*]] = copy_value [[CAPTURE]] : $MyActor
// CHECK-NEXT: [[ISOLATION_OBJECT:%.*]] = init_existential_ref [[CAPTURE_FOR_ISOLATION]] : $MyActor : $MyActor, $any Actor
Expand Down Expand Up @@ -362,7 +362,7 @@ func takeInheritingAsyncIsolatedAny_optionalResult(@_inheritActorContext fn: @es
// CHECK-LABEL: sil hidden [ossa] @$s4test7MyActorC0a20EraseInheritingAsyncC18Closure_noPeepholeyyF
// We emit the closure itself with just the erasure conversion.
// CHECK: // function_ref closure #1
// CHECK-NEXT: [[CLOSURE_FN:%.*]] = function_ref @$s4test7MyActorC0a20EraseInheritingAsyncC18Closure_noPeepholeyyFSiyYacfU_ : $@convention(thin) @async (@guaranteed Optional<any Actor>, @guaranteed MyActor) -> Int
// CHECK-NEXT: [[CLOSURE_FN:%.*]] = function_ref @$s4test7MyActorC0a20EraseInheritingAsyncC18Closure_noPeepholeyyFSiyYacfU_ : $@convention(thin) @async (@guaranteed Optional<any Actor>, @sil_isolated @guaranteed MyActor) -> Int
// CHECK-NEXT: [[CAPTURE:%.*]] = copy_value %0 : $MyActor
// CHECK-NEXT: [[CAPTURE_FOR_ISOLATION:%.*]] = copy_value [[CAPTURE]] : $MyActor
// CHECK-NEXT: [[ISOLATION_OBJECT:%.*]] = init_existential_ref [[CAPTURE_FOR_ISOLATION]] : $MyActor : $MyActor, $any Actor
Expand Down
23 changes: 23 additions & 0 deletions test/SILGen/isolated_self_capture.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -disable-availability-checking -sil-verify-all -swift-version 5 | %FileCheck %s

// REQUIRES: concurrency

class NonSendableKlass {}

func useValue<T>(_ t: T) {}

@MainActor func transferToMain<T>(_ t: T) async {}

actor MyActor {
var ns = NonSendableKlass()

// CHECK-LABEL: sil hidden [ossa] @$s4test7MyActorC19passSelfIntoClosureyyYaF : $@convention(method) @async (@sil_isolated @guaranteed MyActor) -> () {
// CHECK: bb0([[ARG:%.*]] : @guaranteed $
// CHECK: [[COPY:%.*]] = copy_value [[ARG]]
// CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] {{.*}}([[COPY]]) : $@convention(thin) (@sil_isolated @guaranteed MyActor) -> ()
// CHECK: } // end sil function '$s4test7MyActorC19passSelfIntoClosureyyYaF'
func passSelfIntoClosure() async {
let closure = { useValue(self.ns) }
await transferToMain(closure)
}
}