Skip to content

Dyno: cache function signature instantiations, reduce cache impact of some queries #27082

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
Apr 13, 2025
8 changes: 8 additions & 0 deletions frontend/include/chpl/resolution/resolution-queries.h
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,14 @@ types::QualifiedType yieldType(ResolutionContext* rc,
const TypedFnSignature* sig,
const PoiScope* poiScope);

/* Returns a pair of (yieldType(), returnType()) for the function.
These can differ if the function has ITER kind, which may
have an 'int' return type but creates an iterable yielding 'int' when called. */
const std::pair<types::QualifiedType, types::QualifiedType>&
returnTypes(ResolutionContext* rc,
const TypedFnSignature* sig,
const PoiScope* poiScope);

/**
Compute the types for any generic 'out' formal types after instantiation
of any other generic arguments.
Expand Down
17 changes: 17 additions & 0 deletions frontend/include/chpl/resolution/resolution-types.h
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,9 @@ class CallInfo {
public:
using CallInfoActualIterable = Iterable<std::vector<CallInfoActual>>;

/** default constructor for the query system */
CallInfo() : name_(), calledType_() {}

/** Construct a CallInfo that contains QualifiedTypes for actuals */
CallInfo(UniqueString name, types::QualifiedType calledType,
bool isMethodCall,
Expand Down Expand Up @@ -732,6 +735,20 @@ class CallInfo {
hasQuestionArg_, isParenless_,
actuals_);
}
static bool update(CallInfo& keep,
CallInfo& addin) {
return defaultUpdate(keep, addin);
}

void swap(CallInfo& other) {
std::swap(name_, other.name_);
std::swap(calledType_, other.calledType_);
std::swap(isMethodCall_, other.isMethodCall_);
std::swap(isOpCall_, other.isOpCall_);
std::swap(hasQuestionArg_, other.hasQuestionArg_);
std::swap(isParenless_, other.isParenless_);
actuals_.swap(other.actuals_);
}

void stringify(std::ostream& ss, chpl::StringifyKind stringKind) const;

Expand Down
8 changes: 5 additions & 3 deletions frontend/include/chpl/util/assertions.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ void chpl_unimpl(const char* filename, const char* func, int lineno,
// release mode
#define CHPL_ASSERT(expr__) \
do { \
bool ignore = expr__; \
(void) ignore; \
} while (0)
if constexpr (false) { \
auto res = expr__; \
(void) res; \
} \
} while (0)

#else
// debug mode
Expand Down
10 changes: 6 additions & 4 deletions frontend/lib/framework/Context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1281,22 +1281,24 @@ void Context::saveDependencyInParent(const QueryMapResultBase* resultEntry) {
// Should only happen if recursion occurred, in which case do not add it
// to dependencies, in which case code below will skip it.
CHPL_ASSERT(resultEntry->recursionErrors.count(resultEntry) > 0);
} else if (resultEntry->recursionErrors.count(resultEntry) > 0) {
} else if (resultEntry->recursionErrors.contains(resultEntry)) {
// Skip adding recursion error triggers to the dependency graph
// to avoid creating cycles.
} else {
bool errorCollectionRoot = !errorCollectionStack.empty() &&
errorCollectionStack.back().collectingQuery() == parentQuery;
parentQuery->dependencies.push_back(QueryDependency(resultEntry, errorCollectionRoot));
parentQuery->dependencies.emplace_back(resultEntry, errorCollectionRoot);
if (!errorCollectionRoot) {
parentQuery->errorsPresentInSelfOrDependencies |=
resultEntry->errorsPresentInSelfOrDependencies;
}
}

// Propagate query errors that occurred in the child query to the parent
parentQuery->recursionErrors.insert(resultEntry->recursionErrors.begin(),
resultEntry->recursionErrors.end());
if (!resultEntry->recursionErrors.empty()) {
parentQuery->recursionErrors.insert(resultEntry->recursionErrors.begin(),
resultEntry->recursionErrors.end());
}
}

// The resultEntry might have been a query that silences errors. However,
Expand Down
120 changes: 70 additions & 50 deletions frontend/lib/resolution/resolution-queries.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2160,10 +2160,11 @@ static bool instantiateAcrossManagerRecordConversion(Context* context,

// TODO: We could remove the 'ResolutionContext' argument if we figure out
// a different way/decide not to resolve initializer bodies down below.
ApplicabilityResult instantiateSignature(ResolutionContext* rc,
const TypedFnSignature* sig,
const CallInfo& call,
const PoiScope* poiScope) {
static ApplicabilityResult
instantiateSignatureImpl(ResolutionContext* rc,
const TypedFnSignature* sig,
const CallInfo& call,
const PoiScope* poiScope) {
// Performance: Should this query use a similar approach to
// resolveFunctionByInfoQuery, where the PoiInfo and visibility
// are consulted?
Expand Down Expand Up @@ -2636,6 +2637,23 @@ ApplicabilityResult instantiateSignature(ResolutionContext* rc,
return ApplicabilityResult::success(result);
}

static ApplicabilityResult const&
instantiateSignatureQuery(ResolutionContext* rc,
const TypedFnSignature* sig,
const CallInfo& call,
const PoiScope* poiScope) {
CHPL_RESOLUTION_QUERY_BEGIN(instantiateSignatureQuery, rc, sig, call, poiScope);
auto ret = instantiateSignatureImpl(rc, sig, call, poiScope);
return CHPL_RESOLUTION_QUERY_END(ret);
}

ApplicabilityResult instantiateSignature(ResolutionContext* rc,
const TypedFnSignature* sig,
const CallInfo& call,
const PoiScope* poiScope) {
return instantiateSignatureQuery(rc, sig, call, poiScope);
}

// This implements the body of 'resolveFunctionByInfo'. It returns a new
// 'ResolvedFunction' and does not directly set any queries.
//
Expand Down Expand Up @@ -5270,7 +5288,8 @@ resolutionResultFromMostSpecificCandidate(ResolutionContext* rc,
QualifiedType exprType;
QualifiedType yieldedType;
if (msc.fn()) {
exprType = returnType(rc, msc.fn(), instantiationPoiScope);
auto& yieldAndRet = returnTypes(rc, msc.fn(), instantiationPoiScope);
exprType = yieldAndRet.second;

if (!msc.promotedFormals().empty()) {
yieldedType = exprType;
Expand All @@ -5283,7 +5302,7 @@ resolutionResultFromMostSpecificCandidate(ResolutionContext* rc,
}

if (msc.fn()->isIterator() && yieldedType.isUnknown()) {
yieldedType = yieldType(rc, msc.fn(), instantiationPoiScope);
yieldedType = yieldAndRet.first;
}
}

Expand Down Expand Up @@ -5391,56 +5410,57 @@ resolveFnCall(ResolutionContext* rc,
optional<QualifiedType> retType;
optional<QualifiedType> yieldedType;
for (const MostSpecificCandidate& candidate : mostSpecific) {
if (candidate.fn() != nullptr) {
bool isIterator = candidate.fn()->isIterator();

QualifiedType rt;
// TODO: Ideally we'd refactor things such that we instantiate some kind
// of hidden argument to these functions that is then returned. Alas, we
// still need this stuff to work with production, so we create this hack.
if (handleReflectionFunction(rc, candidate.fn(), call, rt)) {
CHPL_ASSERT(rt.isParam() && rt.hasParamPtr());
} else {
rt = returnType(rc, candidate.fn(),
instantiationPoiScope);
}
if (candidate.fn() == nullptr) continue;

QualifiedType rt;
if (handleReflectionFunction(rc, candidate.fn(), call, rt)) {
CHPL_ASSERT(rt.isParam() && rt.hasParamPtr());
CHPL_ASSERT(mostSpecific.numBest() == 1);
CHPL_ASSERT(candidate.fn()->numFormals() == 0);
retType = rt;
break;
}

QualifiedType yt;
bool isIterator = candidate.fn()->isIterator();
auto& yieldAndRet =
returnTypes(rc, candidate.fn(), instantiationPoiScope);
rt = yieldAndRet.second;

if (!candidate.promotedFormals().empty()) {
// this is actually a promotion; construct a promotion type instead.
yt = rt;
rt = QualifiedType(rt.kind(), PromotionIteratorType::get(context, instantiationPoiScope, candidate.fn(), candidate.promotedFormals()));
}
QualifiedType yt;

if (retType && retType->type() != rt.type()) {
// The actual iterator type for each overload will be different
// since it includes the TypedFnSignature, and different overloads
// are different TypedFnSignatures. Don't error, though.
//
// TODO: how do iterators + iterator groups work with return intent
// overloading?
if (!isIterator) {
context->error(candidate.fn(),
nullptr,
"return intent overload type does not match");
}
} else if (!retType) {
retType = rt;
if (!candidate.promotedFormals().empty()) {
// this is actually a promotion; construct a promotion type instead.
yt = rt;
rt = QualifiedType(rt.kind(), PromotionIteratorType::get(context, instantiationPoiScope, candidate.fn(), candidate.promotedFormals()));
}

if (retType && retType->type() != rt.type()) {
// The actual iterator type for each overload will be different
// since it includes the TypedFnSignature, and different overloads
// are different TypedFnSignatures. Don't error, though.
//
// TODO: how do iterators + iterator groups work with return intent
// overloading?
if (!isIterator) {
context->error(candidate.fn(),
nullptr,
"return intent overload type does not match");
}
} else if (!retType) {
retType = rt;
}

if (isIterator || !yt.isUnknown()) {
if (yt.isUnknown()) {
yt = yieldType(rc, candidate.fn(), instantiationPoiScope);
}
if (isIterator || !yt.isUnknown()) {
if (yt.isUnknown()) {
yt = yieldAndRet.first;
}

if (yieldedType && yieldedType->type() != yt.type()) {
context->error(candidate.fn(),
nullptr,
"return intent overload type does not match");
} else if (!yieldedType) {
yieldedType = yt;
}
if (yieldedType && yieldedType->type() != yt.type()) {
context->error(candidate.fn(),
nullptr,
"return intent overload type does not match");
} else if (!yieldedType) {
yieldedType = yt;
}
}
}
Expand Down
39 changes: 14 additions & 25 deletions frontend/lib/resolution/return-type-inference.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1140,17 +1140,17 @@ static bool helpComputeReturnType(ResolutionContext* rc,
return false;
}

static const QualifiedType&
returnTypeWithoutIterableQuery(ResolutionContext* rc,
const TypedFnSignature* sig,
const PoiScope* poiScope) {
CHPL_RESOLUTION_QUERY_BEGIN(returnTypeWithoutIterableQuery, rc, sig, poiScope);
const std::pair<QualifiedType, QualifiedType>&
returnTypes(ResolutionContext* rc,
const TypedFnSignature* sig,
const PoiScope* poiScope) {
CHPL_RESOLUTION_QUERY_BEGIN(returnTypes, rc, sig, poiScope);

Context* context = rc->context();
const UntypedFnSignature* untyped = sig->untyped();
QualifiedType result;
std::pair<QualifiedType, QualifiedType> result;

bool computed = helpComputeReturnType(rc, sig, poiScope, result);
bool computed = helpComputeReturnType(rc, sig, poiScope, result.first);
if (!computed) {
const AstNode* ast = parsing::idToAst(context, untyped->id());
const Function* fn = ast->toFunction();
Expand All @@ -1160,25 +1160,14 @@ returnTypeWithoutIterableQuery(ResolutionContext* rc,
// resolveFunction will arrange to call computeReturnType
// and store the return type in the result.
if (auto rFn = resolveFunction(rc, sig, poiScope)) {
result = rFn->returnType();
result.first = rFn->returnType();
}
}

return CHPL_RESOLUTION_QUERY_END(result);
}

static const QualifiedType& returnTypeQuery(ResolutionContext* rc,
const TypedFnSignature* sig,
const PoiScope* poiScope) {
CHPL_RESOLUTION_QUERY_BEGIN(returnTypeQuery, rc, sig, poiScope);

Context* context = rc->context();
auto result = returnTypeWithoutIterableQuery(rc, sig, poiScope);

if (sig->isIterator() && !result.isUnknownOrErroneous()) {
result = QualifiedType(result.kind(),
FnIteratorType::get(context, poiScope, sig));

result.second = result.first;
if (sig->isIterator() && !result.second.isUnknownOrErroneous()) {
result.second = QualifiedType(result.second.kind(),
FnIteratorType::get(context, poiScope, sig));
}

return CHPL_RESOLUTION_QUERY_END(result);
Expand All @@ -1187,14 +1176,14 @@ static const QualifiedType& returnTypeQuery(ResolutionContext* rc,
QualifiedType returnType(ResolutionContext* rc,
const TypedFnSignature* sig,
const PoiScope* poiScope) {
return returnTypeQuery(rc, sig, poiScope);
return returnTypes(rc, sig, poiScope).second;
}

QualifiedType yieldType(ResolutionContext* rc,
const TypedFnSignature* sig,
const PoiScope* poiScope) {
CHPL_ASSERT(sig->isIterator());
return returnTypeWithoutIterableQuery(rc, sig, poiScope);
return returnTypes(rc, sig, poiScope).first;
}

static const TypedFnSignature* const&
Expand Down
Loading