-
Notifications
You must be signed in to change notification settings - Fork 10.5k
[caller-analysis] Implement findLocalApplySites and reimplement caller analysis on top of it #17971
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
[caller-analysis] Implement findLocalApplySites and reimplement caller analysis on top of it #17971
Conversation
@swift-ci smoke test |
Made a mistake while splicing things... hmmm... |
Yea... its some sort of memory error... Hmm... Compiling locally with asan... |
Just discovered that we are not being complete with our notifyAdd/Delete notifications as I was tracking down the memory issue. |
I am going to fix that in a different PR 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.
This looks great. Most of my comments are about clearly communicating the intent and semantics and sticking with certain conventions so we can easily refer to the API in the future without deriving its meaning from the code.
include/swift/SIL/InstructionUtils.h
Outdated
/// NOTE: We want to return true if escapes has any value. We want to return | ||
/// true if we have any non-conservative information that the user should | ||
/// process. | ||
operator bool() const { |
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.
An operator bool
overload should only be used for a result that has a completely obvious "has a valid value" or "has no value". A false result should mean that the analysis failed, so no information is available. i.e. it found an unrecognized SIL pattern. That seems to be the intention based on the comments, although that could be stated directly as: "Returns false for an invalid result. No assumptions can be made based an an invalid result".
However, the implementation seems to do something different. It apparently returns 'true' if an unknown use was found and 'false' if no uses were found. If that's really the desired semantics, then the comment needs to be rewritten and the name should be explicit, like foundUse
or hasUseWithSideEffects
(I started to say "hasNonTrivialUse", then realized we restrict the term "trivial" to ownership).
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.
The reason that I put this is in was so that I could use an if scope, e.x.:
if (auto result = findLocalApplySites(fri)) {
Looks nice, right? But I get your point.
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 get your point. I'm saying you should be able to do this:
if (auto result = findLocalApplySites(fri)) {
As long as your intention is that the if
statement executes whenever the result
is valid.
include/swift/SIL/InstructionUtils.h
Outdated
/// | ||
/// The none case is so that we can distinguish in between saying that a value | ||
/// did escape and saying that we did not find any conservative information. | ||
Optional<bool> escapes; |
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.
If there are no (nontrivial) uses of the function_ref
, then I would expect this to contain a definite negative result (false). A missing result should indicate that the analysis cannot determine the result.
llvm::DenseMap<SILFunction *, FunctionInfo> FuncInfos; | ||
/// | ||
/// We use a map vector to ensure that when we dump the state of the caller | ||
/// analysis, |
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.
Comment trails off.
/// indirectly. That is a separate query that is type system specific. | ||
bool isDirectCallerSetComplete : 1; | ||
|
||
CallerInfo() |
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.
A default ctor isn't necessary.
|
||
/// The number of partial applied arguments of this function. | ||
/// This is a special set vector that is an abuse as a performance |
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.
This comment appears to be a commentary on some other code. Is there some abuse happening? Do you mean optimization passes are using/abusing this information? Or does this just make the CallerAnalysis invalidation more efficient?
The comment should first state what calleeStates
actually represents. I think this is simply the set of callees computed during analysis. If so, we should just say that. Maybe you're saying that this set should not be used for any purpose other than recomputing CallerInfo because it may be incomplete or out of date? If so, we should say that.
|
||
/// Internal only way for getting a caller info. Will insert f if needed and | ||
/// _WILL NOT_ preform any recomputation of the callgraph. | ||
FunctionInfo &getOrInsertCallerInfo(SILFunction *f); |
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 keep getting confused by the name getOrInsertCallerInfo
. I'm naturally expecting it to return a CallerInfo
. I guess this should actually be getOrInsertFunctioInfo
.
// TODO: Make this more aggressive by considering | ||
// final/visibility/etc. | ||
calleeInfo.mayHaveIndirectCallers = | ||
canBeCalledIndirectly(calleeFn->getRepresentation()); |
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 find it confusing that this is handled while analyzing the call site. Shouldn't calleeInfo.mayHaveIndirectCallers
be part of FunctionInfo initialization?
include/swift/SIL/InstructionUtils.h
Outdated
/// Treat this function ref as escaping only if we found an actual user we | ||
/// didn't understand. Do not treat it as escaping if we did not find any | ||
/// users at all. | ||
bool isEscaping() const { return escapes.getValueOr(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.
The name mayEscape
would match the semantics you have: null or false.
/// Returns true if this function has at least one caller. | ||
bool hasCaller() const { return !Callers.empty(); } | ||
bool hasCaller() const { |
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 this be called hasDirectCaller
?
FunctionInfo &CalleeInfo = FuncInfos[Callee]; | ||
CalleeInfo.Callers.erase(F); | ||
CalleeInfo.PartialAppliers.erase(F); | ||
void CallerAnalysis::invalidateExistingCalleeRelation(SILFunction *caller) { |
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 know what Existing
implies here. If you don't want to refer to the computed callees simply as callees
, then I would use the term "known callees" consistently:
calleeStates
-> knownCallees
invalidateExistingCalleeRelation
-> invalidateKnownCallees
Got in the functionality I needed to make sure that we get all callee relations in the short term (need to do a bit more work, but I can at least get htis in and get it out of the way). |
744fa4f
to
6ec9dad
Compare
@swift-ci smoke test macOS platform |
@swift-ci smoke test osx platform |
1 similar comment
@swift-ci smoke test osx platform |
@swift-ci smoke test OS X platform |
@swift-ci asan test |
Reproduced the crasher with ASAN. |
6ec9dad
to
285e301
Compare
@swift-ci test |
@swift-ci test asan |
Build failed |
@swift-ci test |
@swift-ci test asan |
@swift-ci smoke benchmark |
Build failed |
Build comment file:Compilation-performance test failed |
Build failed |
Build comment file:Build failed before running benchmark. |
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.
The code looks great. Please have a look at the comments below before merging.
/// an ask for information. | ||
/// | ||
/// This laziness is implemented by the pass invalidating its internal state for | ||
/// a function f when receiving an invalidation message for f and then adding f |
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.
a function f when
I thought this was a typo. Use F like you do in other comments.
/// | ||
/// By storing the minimum number of partial applied arguments, we are able | ||
/// to decide quickly if we are able to eliminate dead captured arguments. | ||
Optional<unsigned> numPartialAppliedArguments; |
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.
Sorry to micro-optimize, but this is quadratic in the number of functions (x2 for MapVectors), so...
This creates a lot of fragmentation within the callerStates map, and the "Optional" doesn't help with clarity. I would use a bitfield for numPartialAppliedArguments initialized to zero, a separate bit for "seen partial apply", and assert that it doesn't overflow, which is good to check anyway.
if (auto *fri = dyn_cast<FunctionRefInst>(&i)) { | ||
// If we found any interesting information, update our internal state. | ||
if (auto result = findLocalApplySites(fri)) { | ||
findApplySites(callSitesThatMustBeVisited, callerFn, callerInfo, fri, |
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 think this code will build in Release mode. It looks like you might want to define a class/struct to hold the state required to process a caller function. That would solve the NDEBUG problem and address the findApplySite
code smell where you're passing in a bunch of context that isn't specific to this invocation.
This utility works by taking in a function_ref and then traverses the transitive uses of the function_ref until it finds either a use it does not understand "escape" or an "apply" instruction. It returns a result structure that contains the final found applications and more importantly a bool telling the caller if we found any "escaping" uses. This is intended to be an inverse operation to ApplySite::getCalleeOrigin(). As such it has a bunch of assertions in it that check that the two stay in sync. rdar://41146023
1f7d373
to
858fa7c
Compare
@swift-ci test |
@swift-ci asan test |
@swift-ci test |
1 similar comment
@swift-ci test |
Build failed |
858fa7c
to
71f6d68
Compare
@swift-ci test linux platform |
3 similar comments
@swift-ci test linux platform |
@swift-ci test linux platform |
@swift-ci test linux platform |
@swift-ci test Linux platform |
Build failed |
…ites. Now the caller analysis can tell callers if it was able to find /all/ callers of a callee. NOTE: This does not change FSO itself yet. rdar://41146023
71f6d68
to
03afdc5
Compare
@swift-ci test |
Build failed |
The original linux failure was due to an issue with my result type being a move only type and not getting the NRVO behavior I wanted on Linux due to a conversion. I fixed it by making the code "more NRVO"-able by eliminating the conversion and just working with an optional. The second failure was b/c I forgot to do an emplace with that optional. = (. But at least on Linux it compiled! I think this run will work. |
Build failed |
This is a reimplementation of caller analysis on top of a new utility called findLocalApplySites. This utility works in spirit as an inverse operation to ApplySite::getCalleeOrigin(). At a high level starting at a specific function_ref, the utility traverses through the transitive use edges from the function_ref, looking through specific conversion insts and uses that it understands. All of the apply sites found this way are returned to the user and more importantly if there is an unknown use, a bool is set. As a result, the caller analysis (although not used anywhere beyond tests in this commit), can not be used to determine if the analysis can guarantee that all callers for a specific callee have been found.
rdar://41146023