Skip to content

[concurrency] Wire up execution(concurrent)/execution(caller) #78997

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 7 commits into from
Jan 30, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -8093,6 +8093,8 @@ class AbstractFunctionDecl : public GenericContext, public ValueDecl {
return cast_or_null<AbstractFunctionDecl>(ValueDecl::getOverriddenDecl());
}

std::optional<ExecutionKind> getExecutionBehavior() const;

/// Whether the declaration is later overridden in the module
///
/// Overrides are resolved during type checking; only query this field after
Expand Down
8 changes: 4 additions & 4 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -5966,6 +5966,9 @@ ERROR(actor_isolation_multiple_attr_2,none,
ERROR(actor_isolation_multiple_attr_3,none,
"%0 %1 has multiple actor-isolation attributes ('%2', '%3' and '%4')",
(const Decl *, StringRef, StringRef, StringRef))
ERROR(actor_isolation_multiple_attr_4,none,
"%0 %1 has multiple actor-isolation attributes ('%2', '%3', '%4', and '%5')",
(const Decl *, StringRef, StringRef, StringRef, StringRef))
ERROR(actor_isolation_override_mismatch,none,
"%0 %kind1 has different actor isolation from %2 overridden declaration",
(ActorIsolation, const ValueDecl *, ActorIsolation))
Expand Down Expand Up @@ -8228,14 +8231,11 @@ ERROR(attr_abi_incompatible_with_silgen_name,none,
//===----------------------------------------------------------------------===//
// MARK: @execution Attribute
//===----------------------------------------------------------------------===//

ERROR(attr_execution_concurrent_only_on_async,none,
"cannot use '@execution(concurrent)' on non-async %kind0",
(ValueDecl *))

ERROR(attr_execution_concurrent_incompatible_with_global_actor,none,
"cannot use '@execution(concurrent)' on %kind0 isolated to global actor %1",
(ValueDecl *, Type))

ERROR(attr_execution_concurrent_incompatible_isolated_parameter,none,
"cannot use '@execution(concurrent)' on %kind0 because it has "
"an isolated parameter: %1",
Expand Down
8 changes: 8 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8471,6 +8471,14 @@ void VarDecl::emitLetToVarNoteIfSimple(DeclContext *UseDC) const {
}
}

std::optional<ExecutionKind>
AbstractFunctionDecl::getExecutionBehavior() const {
auto *attr = getAttrs().getAttribute<ExecutionAttr>();
if (!attr)
return {};
return attr->getBehavior();
}

clang::PointerAuthQualifier VarDecl::getPointerAuthQualifier() const {
if (auto *clangDecl = getClangDecl()) {
if (auto *valueDecl = dyn_cast<clang::ValueDecl>(clangDecl)) {
Expand Down
10 changes: 10 additions & 0 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2532,6 +2532,16 @@ static CanSILFunctionType getSILFunctionType(
if (constant) {
if (constant->kind == SILDeclRef::Kind::Deallocator) {
actorIsolation = ActorIsolation::forNonisolated(false);
} else if (auto *decl = constant->getAbstractFunctionDecl();
decl && decl->getExecutionBehavior().has_value()) {
switch (*decl->getExecutionBehavior()) {
case ExecutionKind::Concurrent:
actorIsolation = ActorIsolation::forNonisolated(false /*unsafe*/);
break;
case ExecutionKind::Caller:
actorIsolation = ActorIsolation::forCallerIsolationInheriting();
break;
}
} else {
actorIsolation =
getActorIsolationOfContext(constant->getInnermostDeclContext());
Expand Down
90 changes: 76 additions & 14 deletions lib/Sema/TypeCheckAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,18 +306,6 @@ class AttributeChecker : public AttributeVisitor<AttributeChecker> {
}
}

// We need isolation check here because global actor isolation
// could be inferred.

auto isolation = getActorIsolation(F);
if (isolation.isGlobalActor()) {
diagnoseAndRemoveAttr(
attr,
diag::attr_execution_concurrent_incompatible_with_global_actor, F,
isolation.getGlobalActor());
return;
}

break;
}

Expand Down Expand Up @@ -4418,6 +4406,77 @@ void AttributeChecker::visitFrozenAttr(FrozenAttr *attr) {
}
}

static void checkGlobalActorAttr(
const Decl *decl,
std::pair<CustomAttr *, NominalTypeDecl *> &globalActorAttr) {
auto isolatedAttr = decl->getAttrs().getAttribute<IsolatedAttr>();
auto nonisolatedAttr = decl->getAttrs().getAttribute<NonisolatedAttr>();
auto executionAttr = decl->getAttrs().getAttribute<ExecutionAttr>();
struct NameAndRange {
StringRef name;
SourceRange range;

NameAndRange(StringRef _name, SourceRange _range)
: name(_name), range(_range) {}
};

llvm::SmallVector<NameAndRange, 4> attributes;

attributes.push_back(NameAndRange(globalActorAttr.second->getName().str(),
globalActorAttr.first->getRangeWithAt()));

if (isolatedAttr) {
attributes.push_back(NameAndRange(isolatedAttr->getAttrName(),
isolatedAttr->getRangeWithAt()));
}
if (nonisolatedAttr) {
attributes.push_back(NameAndRange(nonisolatedAttr->getAttrName(),
nonisolatedAttr->getRangeWithAt()));
}
if (executionAttr) {
attributes.push_back(NameAndRange(executionAttr->getAttrName(),
executionAttr->getRangeWithAt()));
}

if (attributes.size() == 1)
return;

if (attributes.size() == 2) {
decl->diagnose(diag::actor_isolation_multiple_attr_2, decl,
attributes[0].name, attributes[1].name)
.highlight(attributes[0].range)
.highlight(attributes[1].range)
.warnUntilSwiftVersion(6)
.fixItRemove(attributes[1].range);
return;
}

if (attributes.size() == 3) {
decl->diagnose(diag::actor_isolation_multiple_attr_3, decl,
attributes[0].name, attributes[1].name, attributes[2].name)
.highlight(attributes[0].range)
.highlight(attributes[1].range)
.highlight(attributes[2].range)
.warnUntilSwiftVersion(6)
.fixItRemove(attributes[1].range)
.fixItRemove(attributes[2].range);
return;
}

assert(attributes.size() == 4);
decl->diagnose(diag::actor_isolation_multiple_attr_4, decl,
attributes[0].name, attributes[1].name, attributes[2].name,
attributes[3].name)
.highlight(attributes[0].range)
.highlight(attributes[1].range)
.highlight(attributes[2].range)
.highlight(attributes[3].range)
.warnUntilSwiftVersion(6)
.fixItRemove(attributes[1].range)
.fixItRemove(attributes[2].range)
.fixItRemove(attributes[3].range);
}

void AttributeChecker::visitCustomAttr(CustomAttr *attr) {
auto dc = D->getDeclContext();

Expand Down Expand Up @@ -4602,7 +4661,10 @@ void AttributeChecker::visitCustomAttr(CustomAttr *attr) {
// retrieval request perform checking for us.
if (nominal->isGlobalActor()) {
diagnoseIsolatedDeinitInValueTypes(attr);
(void)D->getGlobalActorAttr();
if (auto g = D->getGlobalActorAttr()) {
checkGlobalActorAttr(D, *g);
}

if (auto value = dyn_cast<ValueDecl>(D)) {
(void)getActorIsolation(value);
} else {
Expand Down Expand Up @@ -8062,7 +8124,7 @@ class ClosureAttributeChecker
ctx.evaluator, GlobalActorAttributeRequest{closure}, std::nullopt);

if (globalActorAttr && globalActorAttr->first == attr) {
// if there is an `isolated` parameter, then this global-actor attribute
// If there is an `isolated` parameter, then this global-actor attribute
// is invalid.
for (auto param : *closure->getParameters()) {
if (param->isIsolated()) {
Expand Down
80 changes: 28 additions & 52 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4688,6 +4688,7 @@ getIsolationFromAttributes(const Decl *decl, bool shouldDiagnose = true,
auto isolatedAttr = decl->getAttrs().getAttribute<IsolatedAttr>();
auto nonisolatedAttr = decl->getAttrs().getAttribute<NonisolatedAttr>();
auto globalActorAttr = decl->getGlobalActorAttr();
auto concurrentExecutionAttr = decl->getAttrs().getAttribute<ExecutionAttr>();

// Remove implicit attributes if we only care about explicit ones.
if (onlyExplicit) {
Expand All @@ -4697,61 +4698,34 @@ getIsolationFromAttributes(const Decl *decl, bool shouldDiagnose = true,
isolatedAttr = nullptr;
if (globalActorAttr && globalActorAttr->first->isImplicit())
globalActorAttr = std::nullopt;
if (concurrentExecutionAttr && concurrentExecutionAttr->isImplicit())
concurrentExecutionAttr = nullptr;
}

unsigned numIsolationAttrs = (isolatedAttr ? 1 : 0) +
(nonisolatedAttr ? 1 : 0) +
(globalActorAttr ? 1 : 0);
unsigned numIsolationAttrs =
(isolatedAttr ? 1 : 0) + (nonisolatedAttr ? 1 : 0) +
(globalActorAttr ? 1 : 0) + (concurrentExecutionAttr ? 1 : 0);
if (numIsolationAttrs == 0) {
if (isa<DestructorDecl>(decl) && !decl->isImplicit()) {
return ActorIsolation::forNonisolated(false);
}
return std::nullopt;
}

// Only one such attribute is valid, but we only actually care of one of
// them is a global actor.
if (numIsolationAttrs > 1 && globalActorAttr && shouldDiagnose) {
struct NameAndRange {
StringRef name;
SourceRange range;

NameAndRange(StringRef _name, SourceRange _range)
: name(_name), range(_range) {}
};

llvm::SmallVector<NameAndRange, 3> attributes;
if (isolatedAttr) {
attributes.push_back(NameAndRange(isolatedAttr->getAttrName(),
isolatedAttr->getRangeWithAt()));
}
if (nonisolatedAttr) {
attributes.push_back(NameAndRange(nonisolatedAttr->getAttrName(),
nonisolatedAttr->getRangeWithAt()));
}
if (globalActorAttr) {
attributes.push_back(
NameAndRange(globalActorAttr->second->getName().str(),
globalActorAttr->first->getRangeWithAt()));
}
if (attributes.size() == 3) {
decl->diagnose(diag::actor_isolation_multiple_attr_3, decl,
attributes[0].name, attributes[1].name, attributes[2].name)
.highlight(attributes[0].range)
.highlight(attributes[1].range)
.highlight(attributes[2].range);
} else {
assert(attributes.size() == 2);
decl->diagnose(diag::actor_isolation_multiple_attr_2, decl,
attributes[0].name, attributes[1].name)
.highlight(attributes[0].range)
.highlight(attributes[1].range);
}
}

// If the declaration is explicitly marked 'nonisolated', report it as
// independent.
if (nonisolatedAttr) {
// If the nonisolated async inherits isolation from context is set, return
// caller isolation inheriting.
if (decl->getASTContext().LangOpts.hasFeature(
Feature::NonIsolatedAsyncInheritsIsolationFromContext)) {
if (auto *func = dyn_cast<AbstractFunctionDecl>(decl);
func && func->hasAsync() &&
func->getModuleContext() == decl->getASTContext().MainModule) {
return ActorIsolation::forCallerIsolationInheriting();
}
}

return ActorIsolation::forNonisolated(nonisolatedAttr->isUnsafe());
}

Expand Down Expand Up @@ -4844,6 +4818,17 @@ getIsolationFromAttributes(const Decl *decl, bool shouldDiagnose = true,
.withPreconcurrency(decl->preconcurrency() || isUnsafe);
}

// If the declaration is explicitly marked with 'execution', return the
// appropriate isolation.
if (concurrentExecutionAttr) {
switch (concurrentExecutionAttr->getBehavior()) {
case ExecutionKind::Concurrent:
return ActorIsolation::forNonisolated(false /*is unsafe*/);
case ExecutionKind::Caller:
return ActorIsolation::forCallerIsolationInheriting();
}
}

llvm_unreachable("Forgot about an attribute?");
}

Expand Down Expand Up @@ -5616,15 +5601,6 @@ InferredActorIsolation ActorIsolationRequest::evaluate(
checkClassGlobalActorIsolation(classDecl, *isolationFromAttr);
}

if (ctx.LangOpts.hasFeature(
Feature::NonIsolatedAsyncInheritsIsolationFromContext)) {
if (auto *func = dyn_cast<AbstractFunctionDecl>(value);
func && func->hasAsync() && isolationFromAttr->isNonisolated() &&
func->getModuleContext() == ctx.MainModule) {
return {ActorIsolation::forCallerIsolationInheriting(), {}};
}
}

return {*isolationFromAttr,
IsolationSource(/*source*/ nullptr, IsolationSource::Explicit)};
}
Expand Down
30 changes: 30 additions & 0 deletions test/Concurrency/Runtime/nonisolated_inherits_isolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ struct CustomActor {
}
}

@execution(caller)
func executionCallerIsolation() async {
checkIfOnMainQueue()
}

// Expected to always crash
@execution(concurrent)
func executionConcurrentIsolation() async {
checkIfOnMainQueue()
}

let tests = TestSuite("NonIsolatedInheritsIsolation")

tests.test("checkIfOnMainQueue does not crash on the main queue") { @MainActor () -> () in
Expand Down Expand Up @@ -87,6 +98,25 @@ tests.test("Check if nonisolated inheriting nonisolated crashes") { () async ->
sleep(5)
}

tests.test("Check if execution concurrent isolation crashes (main actor)") { @MainActor () async -> () in
expectCrashLater()
await executionConcurrentIsolation()
}

tests.test("Check if execution concurrent isolation crashes (custom actor)") { @CustomActor () async -> () in
expectCrashLater()
await executionConcurrentIsolation()
}

tests.test("Check if execution concurrent isolation does not crash (main actor)") { @MainActor () async -> () in
await executionCallerIsolation()
}

tests.test("Check if execution concurrent isolation does crash (custom actor)") { @CustomActor () async -> () in
expectCrashLater()
await executionCallerIsolation()
}

@MainActor func run() async {
await runAllTestsAsync()
}
Expand Down
17 changes: 17 additions & 0 deletions test/Concurrency/attr_execution.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// RUN: %target-swift-emit-silgen -enable-experimental-feature NonIsolatedAsyncInheritsIsolationFromContext %s | %FileCheck %s

// REQUIRES: asserts
// REQUIRES: swift_feature_NonIsolatedAsyncInheritsIsolationFromContext


// CHECK-LABEL: // concurrentTest()
// CHECK: // Isolation: nonisolated
// CHECK: sil hidden [ossa] @$s14attr_execution14concurrentTestyyYaF : $@convention(thin) @async () -> () {
@execution(concurrent)
func concurrentTest() async {}

// CHECK-LABEL: // callerTest()
// CHECK: // Isolation: caller_isolation_inheriting
// CHECK: sil hidden [ossa] @$s14attr_execution10callerTestyyYaF : $@convention(thin) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>) -> () {
@execution(caller)
func callerTest() async {}
1 change: 1 addition & 0 deletions test/Concurrency/isolated_parameters.swift
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ func isolatedClosures() {
}
}

// expected-warning@+3 {{global function 'allOfEm' has multiple actor-isolation attributes ('MainActor' and 'nonisolated')}}
// expected-warning@+2 {{global function with 'isolated' parameter cannot be 'nonisolated'; this is an error in the Swift 6 language mode}}{{12-24=}}
// expected-warning@+1 {{global function with 'isolated' parameter cannot have a global actor; this is an error in the Swift 6 language mode}}{{1-12=}}
@MainActor nonisolated func allOfEm(_ a: isolated A) {
Expand Down
2 changes: 1 addition & 1 deletion test/Concurrency/nonisolated_rules.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ class KlassA {

@MainActor
nonisolated struct Conflict {}
// expected-error@-1 {{struct 'Conflict' has multiple actor-isolation attributes ('nonisolated' and 'MainActor')}}
// expected-error@-1 {{struct 'Conflict' has multiple actor-isolation attributes ('MainActor' and 'nonisolated')}}

struct B: Sendable {
// expected-error@+1 {{'nonisolated' can not be applied to variable with non-'Sendable' type 'NonSendable}}
Expand Down
Loading