Sometimes, I use the call stack as a data structure. I use local variables as elements on the stack and use the data member of a class to store the top element. When I use this pattern, I feel like I'm doing something a little naughty!
Here's an example of the pattern. The pattern ensures that break and continue statements jump to the right place even in the presence of nested while, for and switch statements.
class StatementVisitor final : public ast::Visitor {
public:
void visitFlow(ast::Statement *body, llvm::BasicBlock *brake, llvm::BasicBlock *continoo) {
// Store the old blocks in local variables and push the new blocks.
llvm::BasicBlock *oldBreak = std::exchange(breakBlock, brake);
llvm::BasicBlock *oldContinue = std::exchange(continueBlock, continoo);
// Traverse a statement that might be a block that might have some
// break or continue statements in it.
body->accept(*this);
// Pop the top blocks and restore the previous blocks
continueBlock = oldContinue;
breakBlock = oldBreak;
}
void visit(ast::For &four) override {
// ...
visitFlow(four.body.get(), doneBlock, incrBlock);
// ...
}
void visit(ast::Break &) override {
// ...
funcBdr.ir.CreateBr(breakBlock);
// ...
}
void visit(ast::Continue &) override {
// ...
funcBdr.ir.CreateBr(continueBlock);
// ...
}
private:
// the blocks that continue and break statements should jump to
// these are on the top of the stack
llvm::BasicBlock *continueBlock;
llvm::BasicBlock *breakBlock;
FuncBuilder funcBdr;
};
I could use a std::stack<llvm::BasicBlock *> and I end up with roughly the same amount of code. This pattern probably saves memory and a few heap allocations in std::stack. It's so ingrained in my mind (after using it in two other places) that I think of using this before I even realize that I could use a std::stack.
Should I avoid using this pattern?