================
@@ -2000,6 +2004,80 @@ void CodeGenFunction::EmitDefaultStmt(const DefaultStmt
&S,
EmitStmt(S.getSubStmt());
}
+namespace {
+struct EmitDeferredStatement final : EHScopeStack::Cleanup {
+ const DeferStmt &Stmt;
+ EmitDeferredStatement(const DeferStmt *Stmt) : Stmt(*Stmt) {}
+
+ void Emit(CodeGenFunction &CGF, Flags) override {
+ // Take care that any cleanups pushed by the body of the 'defer' don't
+ // clobber the current cleanup slot value.
+ //
+ // This situation warrants some explanation: Assume we have a scope that
+ // pushes a cleanup; when that scope is exited, we need to run that
cleanup;
+ // this is accomplished by emitting the cleanup into a separate block and
+ // then branching to that block at scope exit.
+ //
+ // Where this gets complicated is if we exit the scope in multiple
different
+ // ways; e.g. in a 'for' loop, we may exit the scope of its body by falling
+ // off the end (in which case we need to run the cleanup and then branch to
+ // the increment), or by 'break'ing out of the loop (in which case we need
+ // to run the cleanup and then branch to the loop exit block); in both
cases
+ // we first branch to the cleanup block to run the cleanup, but the block
we
+ // need to jump to *after* running the cleanup is different.
+ //
+ // This is accomplished using a local integer variable called the 'cleanup
+ // slot': before branching to the cleanup block, we store a value into that
+ // slot. Then, in the cleanup block, after running the cleanup, we load the
+ // value of that variable and 'switch' on it to branch to the appropriate
+ // continuation block.
+ //
+ // The problem that arises once 'defer' statements are involved is that the
+ // body of a 'defer' is an arbitrary statement which itself can create more
+ // cleanups. This means we may end up overwriting the cleanup slot before
we
+ // ever have a chance to 'switch' on it, which means that once we *do* get
+ // to the 'switch', we end up in whatever block the cleanup code happened
to
+ // pick as the default 'switch' exit label!
+ //
+ // That is, what is normally supposed to happen is something like:
+ //
+ // 1. Store 'X' to cleanup slot.
+ // 2. Branch to cleanup block.
+ // 3. Execute cleanup.
+ // 4. Read value from cleanup slot.
+ // 5. Branch to the block associated with 'X'.
+ //
+ // But if we encounter a 'defer' statement that contains a cleanup, then
+ // what might instead happen is:
+ //
+ // 1. Store 'X' to cleanup slot.
+ // 2. Branch to cleanup block.
+ // 3. Execute cleanup; this ends up pushing another cleanup, so:
+ // 3a. Store 'Y' to cleanup slot.
+ // 3b. Run steps 2–5 recursively.
+ // 4. Read value from cleanup slot, which is now 'Y' instead of 'X'.
+ // 5. Branch to the block associated with 'Y'... which doesn't even
+ // exist because the value 'Y' is only meaningful for the inner
+ // cleanup. The result is we just branch 'somewhere random'.
+ //
+ // The rest of the cleanup code simply isn't prepared to handle this case
+ // because there are no other cleanups that can push more cleanups, and
+ // thus, emitting any other cleanup cannot clobber the cleanup slot.
+ //
+ // To prevent this from happening, simply force the allocation of a new
+ // cleanup slot for the body of the defer statement by temporarily clearing
+ // out any existing slot.
+ SaveAndRestore PreserveCleanupSlot{CGF.NormalCleanupDest,
+ RawAddress::invalid()};
+ CGF.EmitStmt(Stmt.getBody());
----------------
cor3ntin wrote:
@erichkeane can you have a look at that bit?
https://github.com/llvm/llvm-project/pull/162848
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits