-
Notifications
You must be signed in to change notification settings - Fork 10.3k
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
[Distributed] Implement whenLocal properly #59598
base: main
Are you sure you want to change the base?
Conversation
include/swift/AST/Decl.h
Outdated
}; | ||
|
||
/// The default value, if any, along with flags. | ||
llvm::PointerIntPair<StoredDefaultArgument *, 3, OptionSet<Flags>> | ||
DefaultValueAndFlags; | ||
|
||
// FIXME: can we pack this somewhere as flags, rather than ad hoc bool? | ||
bool isDistributedKnownLocal = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You already have it packed in the flags via IsDistributedKnownToBeLocal
, so you can remove this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok cool, I was very iffy about how I packed it into the flags but seems that's fine -- removing the bool then, thanks!
include/swift/AST/Decl.h
Outdated
/// Whether or not this parameter is marked with 'isolated'. | ||
bool isDistributedKnownToBeLocal() const { | ||
auto yes = getAttrs().hasAttribute<DistributedKnownToBeLocalAttr>() || | ||
ArgumentNameAndFlags.getInt().contains(ArgumentNameFlags::IsDistributedKnownToBeLocal); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you just want to use ArgumentNameAndFlags.getInt().contains(ArgumentNameFlags::IsDistributedKnownToBeLocal)
, and set it elsewhere based on the attribute. Let isolated
be your guide here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done and cleaned up!
// TODO(distributed): more diagnosis here, prevent from use in props, enums etc | ||
|
||
// isolated parameters must be of 'distributed actor' type | ||
if (!type->hasTypeParameter() && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably it's okay for it to have a type parameter, but then the archetype would have to be a distributed actor (i.e., conform to the DistributedActor
protocol).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, I think that's right... will think about it and try
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, moving this logic into TypeResolver
, as I suggested above, means that it'll be checked on the archetype automatically for you, rendering my comment above uninteresting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok awesome :)
@@ -140,8 +140,18 @@ extension DistributedActor { | |||
/// state. | |||
/// | |||
/// When the actor is remote, the closure won't be executed and this function will return nil. | |||
// public nonisolated func whenLocal<T: Sendable>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you want to remove this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have to figure out how to introduce the "good ones" without breaking existing code; this one has unfortunate semantics that it does "await x.whenLocal { isolated X in .... }whereas the other one this
_localenables allows us to not hop unless we have to:
x.whenLocal { _local X in xx.someConstant }` so there's no hop and no async there...
We also want to allow await x.whenLocal { _local X in await xx.something }
though; so we can either do this by keeping the old version which does the HOP once (could be good?), or making some new overload.
So in other words... perhaps keeping the "old" signature is good because:
- won't break API/ABI
- is could be okey...? we do one hop when the function is async and run on the actor...?
If we could get away with removing this function though I think we would want to -- can we though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Open question what to do here, the "I'd love those" signatures are:
public nonisolated func whenLocal<T: Sendable>( // FIXME: do we need sendable req here?
_ body: @Sendable (_local Self) async throws -> T
) async rethrows -> T? {
if __isLocalActor(self) {
return try await body(self)
} else {
return nil
}
}
public nonisolated func whenLocal<T: Sendable>(
_ body: (_local Self) throws -> T
) rethrows -> T? {
if __isLocalActor(self) {
return try body(self)
} else {
return nil
}
}
so we can avoid the "one hop" at the beginning, but I guess we COULD keep the isolated one as well hmm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These signatures look fine to me, but I don't think you need the Sendable constraints because you're never actually crossing the actor boundary. However, you do need to decide to either have the _local
versions, or the isolated
versions, but if you want both you're going to need to give them different names.
9527413
to
8af1975
Compare
distributedKnownLocalParams.insert(paramDecl); | ||
} | ||
if (contextualParam->isDistributedKnownToBeLocal() && | ||
!flags.isDistributedKnownToBeLocal() && paramDecl) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: It’s not necessary to check paramDecl
here since there is a access on it earlier which would crash if it was nullptr.
8af1975
to
8c368c5
Compare
I think I managed to implement this for methods -- properties are hard... I'll need to consult a bit with you @DougGregor but they can also come later I think. |
@swift-ci please smoke test |
FYI, Windows is telling us there's a real issue here:
I don't think we need |
@@ -5906,6 +5900,10 @@ class ParamDecl : public VarDecl { | |||
|
|||
void setDistributedKnownToBeLocal(bool value = true) { | |||
auto flags = ArgumentNameAndFlags.getInt(); | |||
if (value && !getAttrs().hasAttribute<DistributedKnownToBeLocalAttr>()) { | |||
getAttrs().add(new (getASTContext()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You shouldn't need to add an attribute here, and please don't because it's an additional unnecessary mutation on the AST. Nobody should be looking at the attribute except code that validates it when present, and sets this flag when needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, thank you!
isolatedParams.insert(paramDecl); | ||
|
||
// an 'isolated' param is also implicitly '_local', | ||
// since in order to run "on" an actor, it must be a real local instance. | ||
distributedKnownLocalParams.insert(paramDecl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do we check distributedKnownLocalParams
to determine whether it's known-to-be-local? Those places should check both the isolatedParams
and distributedKnownLocalParams
to answer the question.
if (!D->isImplicit()) { | ||
if (D->isImplicit()) { | ||
// Implicit code is fine, this is how we use '_local' in synthesized thunks. | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implicit code should always follow the rules below, right? And if they don't, it's a bug in either the generation of synthesized thunks or in the check below. I recommend removing this early return. You could add an assert(!D->isImplicit());
along the error paths below if you want to get earlier warning when the synthesis and the check are out of sync.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... I think that means we'll have to change synthesis of thunks to call whenLocal
, because the thunks do "if local ... call the real func" (without using the whenLocal -- at least today), and they do so from nonisolated
func -- so we'll have to change thunk synthesis to use whenLocal to get the _local self
in order to do this 🤔
That probably makes sense though, so thunks should become to use the normal function from here I suppose 🤔
// param, since we're not able to safely carry it around and propagate the | ||
// "local-ness" just yet. | ||
// TODO(distributed): unlock this once '_local' is also implemented in properties | ||
if (auto closureExpr = dyn_cast<AbstractClosureExpr>(D->getDeclContext())) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand this restriction. Why should _local
not be permitted anywhere that isolated
is?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per my comment about this attribute probably not being needed, this validation could occur in TypeResolver, like TypeResolver::resolveIsolatedTypeRepr
does for isolated
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why should _local not be permitted anywhere that isolated is?
Just because I don't think the assignments are handled properly, but maybe it'll just work when implemented in TypeResolver::resolveIsolatedTypeRepr hmmm
param->setIsolated(true); | ||
} else if (isa<DistributedKnownToBeLocalTypeRepr>(STR)) { | ||
param->setDistributedKnownToBeLocal(true); | ||
auto localAttr = new (parser.Context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You know, part of me wonders why we even have DistributedKnownToBeLocalAttr
anymore. We have a bit on ParamDecl
that provides this information. We have the source location in DistributedKnownToBeLocalTypeRepr
that we can use for diagnostics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that may be true -- perhaps it is indeed much more like isolated
than the const
attribute I based a lot of this work on... I'll try if we can get rid of the attr
Ctx.Diags.diagnose(E->getAwaitLoc(), diag::no_async_in_await); | ||
else | ||
E->dump(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extraneous debug dumps.
// TODO(distributed): more diagnosis here, prevent from use in props, enums etc | ||
|
||
// isolated parameters must be of 'distributed actor' type | ||
if (!type->hasTypeParameter() && |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, moving this logic into TypeResolver
, as I suggested above, means that it'll be checked on the archetype automatically for you, rendering my comment above uninteresting.
/// | ||
/// When the actor is remote, the closure won't be executed and this function will return nil. | ||
public nonisolated func whenLocal<T: Sendable>( | ||
_ body: @Sendable (isolated Self) async throws -> T | ||
public nonisolated func whenLocal<T: Sendable>( // FIXME: do we need sendable req here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As noted elsewhere, I don't think we need a Sendable requirements here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these be @_alwaysEmitIntoClient
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably good idea, these impls will never change 👍
Yeah I think you're right, we do it in one centralized spot anyway, will remove this. |
This will need to be redone since we're changing back how thunks work. |
Resolves rdar://89082275
This aims to properly implement
whenLocal
so that it works properly in the type system, not by side stepping it. Almost there but need to consult a few pieces.Replaces #42098