When -enable-swifttailcc-musttail-check is passed (off by default), we will
check that all swiftasync->swiftasync tail calls are marked musttail.
This can be used to catch codegen bugs with a missing musttail.
Differential D103275
Update musttail verification to check all swiftasync->swiftasync tail calls. varungandhi-apple on May 27 2021, 11:59 AM. Authored by
Details When -enable-swifttailcc-musttail-check is passed (off by default), we will This can be used to catch codegen bugs with a missing musttail.
Diff Detail
Event Timeline
Comment Actions This doesn't look like a particularly expensive verifier check (although maybe I've missed something). What's the motivation for hiding it behind EnableSwiftTailCCMustTailCheck? Have you considered using bitcode AutoUpgrade to fix old IR?
Comment Actions I used Assert for two reasons: (1) the surrounding code uses Assert, so it seemed like the right thing to use and (2) in case there's a failure, it will display the instruction missing the musttail. I'm using NDEBUG because we would like this check to not be turned on for release builds. Happy to change the code to something different/more idiomatic so long as it has the same effect.
Comment Actions
Having a flag makes it easier to turn off from Swift if we need to turn it off for some reason. Missing the musttail is technically not a fatal error, but it increases the risk of stack overflow if Swift doesn't generate the right IR.
Comment Actions Absolutely, no debate here!
As @aschwaighofer pointed out, release builds can just avoid setting the enable flag. I guess I'm wondering what the trajectory is - if the goal is for this to eventually be an IR invariant for the calling convention, I think a verifier check makes sense; once the codegen bugs are sorted out, and the bitcode upgrade is done, the flag can be switched to on-by-default and eventually removed. But if this will never be an invariant of the IR, I'm not sure the LLVM IR verifier is really the right place for the check.
Comment Actions Ah, I see you've already answered that:
In that case, should this patch also document the intended invariant (maybe with a caveat that it's not enforced yet?) at the swifttailcc entry in LangRef? (https://llvm.org/docs/LangRef.html#calling-conventions) @rjmccall , I'd be curious about your thoughts. (Just to explain the other option on my mind -- if this isn't an IR invariant for the calling convention, just a linting check for IR with bad style, maybe there should be a separate pass for linting, and we keep the verifier focused on whether IR is valid.) Comment Actions The whole point behind the swiftasynccc convention (swifttailcc in llvm; whose clang support we still have to upstream I think) is to use only constant stack space for an unbounded number of calls in tail call position. So this is an invariant in my opinion. If a frontend wants to opt out of that invariant in can annotate the call with nottail. The c frontend marks calls in tail position immediately followed by a return as musttail. The swift frontend uses musttail wherever appropriate. Comment Actions
Comment Actions I would be uncomfortable with an invariant that all calls from swifttailcc to swifttailcc must be musttail because we might well want to make non-tail calls in some circumstances. However, I think it would be fine to annotate all such calls as either musttail or a hypothetical notail — at least, I think we could achieve that coming out of the frontend, and we ought to be able to maintain it in IR transformations. To do that, you'd need to actually add a notail, though.
Comment Actions @rjmccall The invariant is not (edit: s/is/should not be imo/g) all swiftailcc calls need to be annotate but only calls in tail position - call followed by return. Comment Actions I guess that the 'in tail position' restriction cannot really be used by frontends because it has to predict that otherStuff() cannot be optimized away. So it would not buy us much other than a loss of clarity ... if (cond) { swifttailcc(); otherStuff(); ret; } Comment Actions
Comment Actions Yeah, I think that's right. The check for ReturnInst seems quite fragile. Replacing it with a restriction that frontends need to explicitly say notail or musttail seems much easier to reason about. To that end, a bunch of more specific feedback inline.
Comment Actions If a swifttailcc function calls another swifttailcc in a non-tail position, the proposed rule is that it has to be marked notail? Not sure I understand the justification here. Is there some reason we need to forbid sibcall optimization of swifttailcc functions? This might just be one of those unfortunate cases where we need to accept suspicious IR to avoid imposing awkward restrictions on optimizations. If you're just trying to audit IR for suspicious constructs, we do have a separate "lint" pass. Comment Actions @efriedma It is critical for Swift that ‘swifttailcc’ function calls in tail position be executed in constant stack space. Guaranteed tail call opt was the reason why we added this convention. We want to guarantee that such calls coming out of the front end be and stay optimizable hence we want to enforce that a call in tail cal position is annotated with musttail coming out of the frontends and that this property is maintained throughout optimization. |
I think this is a good place to document that all calls to swifttailcc from swifttailcc must be marked with either notail or musttail.