Skip to content
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

dyno: Initial resolution of zip() expressions and parallel iterators #24915

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

dlongnecke-cray
Copy link
Contributor

@dlongnecke-cray dlongnecke-cray commented Apr 23, 2024

This PR introduces resolution for zip() expressions as well as resolution of parallel iterators for forall and [] loops.

For zip() expressions that appear as the iterand of a serial loop, the strategy is to resolve only serial iterators for each actual in the zip().

For zip() expressions that appear as the iterand of a forall loop, the strategy is to:

  • For the first actual (known as the leader), attempt to resolve a suitable leader iterator or error
  • For the leader and all remaining actuals (known as followers), attempt to resolve a suitable follower iterator or error

For [] loops, the strategy is similar, except serial iterators may be used as a substitute for the leader and followers IFF the leader iterator could not be resolved for the first actual. If the leader iterator could be resolved, but e.g., its return type could not, then serial iterators will not be considered as fallbacks.

For iterands that are not zip() expressions, the "standalone" parallel iterator is preferred for parallel loops before attempting to resolve a leader/follower combo. As with zip(), forall loops will emit an error if no form of parallel iterator could be resolved. All other loops will fall back to serial iterators.

Thanks to @vasslitvinov for walking me through the semantics of zip() and parallel iterator resolution.

FUTURE WORK

  • Iterator forwarding for iterators in the internal modules (e.g., tag=tag, followThis=followThis)
  • Expand tests, handle edge cases, attempt to resolve leader/follower/standalone iterators for standard/internal types

Reviewed by @DanilaFe. Thanks!

Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
@dlongnecke-cray
Copy link
Contributor Author

I'll fix the CI check failures tomorrow, have to head out for the night.

Copy link
Contributor

@DanilaFe DanilaFe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm halfway through, a few comments, mostly minor changes within individual lines.

Comment on lines 71 to 77
/** Given an enum type 'et', get a map from the name of each constant
in 'et' to each constant represented as a param value.
If there are multiple enum constants with the same name (which
means the AST is semantically incorrect), then only the first
constant is added to the map. */
static const std::map<UniqueString, QualifiedType>*
getParamConstantsMapOrNull(Context* context, const EnumType* et);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before I read the implementation, my question is: why would it be nullable?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answering my own question, probably if we built without the standard modules or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just like the property of returning nullptr if the AST for the thing could not be found.

Comment on lines 3662 to 3664
if (!p || !p->isIndexableLoop()) {
context->error(zip, "zip expressions may only appear as a loop's iterand");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can make this an assertion because we simply can't parse zip any other way.

auto actual = zip->actual(i);

if (actual->isZip()) {
context->error(actual, "nested 'zip' expressions are not supported");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here. According to the grammar, 'zip' can only occur after foreach or in (see zippered_iterator in chpl.ypp). So you can remove this check.

// Try to resolve the parallel leader using the first actual.
if (isFirstActual && prefersParallel) {
const bool emitErrors = requiresParallel;
leaderType = resolveParallelLeaderIterType(*this, actual, actual,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'd rather have resolveParallelLeaderIterType return a boolean for whether or not the call resolution works, and separately check leaderType for errors. The reason for that is the following: if the user has a leader iterator that matches, but we can't infer its return type. The user also has follower iterators that work, and a serial iterator that's specifically disabled via compilerError or something. Since we know there exists a leader iterator, we should skip resolving the serial loops (via the "RFINOS" principle, or "resolution error is not overload selection"). On the other hand, depending on whether we could find a valid leader iterator, we should try or give up on resolving followers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to change the structure of the underlying implementation so that the resolved iterator can be gotten through an optional out formal. Then you can see inspect it to see the details you're describing.

MSC.numBest() == 1 &&
MSC.only().fn()->untyped()->kind() == uast::Function::Kind::ITER;
auto iterKindFormal = getIterKindConstantOrUnknown(rv, iterKindStr);
bool needFollower = iterKindStr ? !strcmp(iterKindStr, "follower") : false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This tells me you should use std::string or UniqueString. Probably the latter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's wrong with a strcmp?! Lol, back in my day...

Comment on lines 4138 to 4146
if (iterand->isCall()) {
auto runResult = context->runAndTrackErrors([&](Context* context) {
iterand->traverse(rv);
return true;
});
std::swap(runResult.errors(), traversalErrors);
} else {
iterand->traverse(rv);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why "is call" is the line between speculative and non-speculative resolution -- would you mind giving your rationale?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My rationale is, what "call-like" things will actually resolve to an iterator call? I am not even sure if we allow parenless methods to resolve to iterator calls anymore (if it does then sure we can always introspect).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what if it's a call-like thing that returns, say, an array? like getMyArray()? I think it being a call expression is not a particularly useful metric. I suspect we never want to silence errors except when we're trying to invoke the 'leader' iterator on a value, which i believe is handled below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I can go ahead and just always run the speculation for now.


QualifiedType idxType;
// Inspect the resolution result to determine what should be done next.
ResolvedExpression& iterandRE = rv.byPostorder.byAst(iterand);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

consider auto& here, too.

// Inspect the resolution result to determine what should be done next.
ResolvedExpression& iterandRE = rv.byPostorder.byAst(iterand);
auto& MSC = iterandRE.mostSpecific();
auto fn = !MSC.isEmpty() && MSC.numBest() == 1 ? MSC.only().fn() : nullptr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe you can just use MostSpecificCandidate's operator bool here and write this as

auto fn = MSC.only() ? MSC.only().fn() : nullptr;

That's because .only() returns an empty candidate (without runtime overhead if that worries you) if there are more or less than 1 candidates.

Comment on lines +4158 to +4164
if (!isIter && e->type() == NoMatchingCandidates) {
auto nmc = static_cast<ErrorNoMatchingCandidates*>(e.get());
if (std::get<0>(nmc->info()) == iterand) {
std::swap(noCandidatesError, e);
continue;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woah, a use of error introspection. I love it.

@dlongnecke-cray
Copy link
Contributor Author

Adding isSerialIterator isLeaderIterator isFollowerIterator and isStandaloneIterator as per your suggestion.

Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Signed-off-by: David Longnecker <dlongnecke-cray@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants