//===- subzero/src/IceInstVarIter.h - Iterate over inst vars ----*- C++ -*-===// // // The Subzero Code Generator // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// /// /// \file /// \brief Defines a common pattern for iterating over the variables of an /// instruction. /// //===----------------------------------------------------------------------===// #ifndef SUBZERO_SRC_ICEINSTVARITER_H #define SUBZERO_SRC_ICEINSTVARITER_H /// In Subzero, an Instr may have multiple Ice::Operands, and each Operand can /// have zero, one, or more Variables. /// /// We found that a common pattern in Subzero is to iterate over all the /// Variables in an Instruction. This led to the following pattern being /// repeated multiple times across the codebase: /// /// for (Operand Op : Instr.Operands()) /// for (Variable Var : Op.Vars()) /// do_my_thing(Var, Instr) /// /// /// This code is straightforward, but one may take a couple of seconds to /// identify what it is doing. We therefore introduce a macroized iterator for /// hiding this common idiom behind a more explicit interface. /// /// FOREACH_VAR_IN_INST(Var, Instr) provides this interface. Its first argument /// needs to be a valid C++ identifier currently undeclared in the current /// scope; Instr can be any expression yielding a Ice::Inst&&. Even though its /// definition is ugly, awful, painful-to-read, using it is fairly simple: /// /// FOREACH_VAR_IN_INST(Var, Instr) /// do_my_thing(Var, Instr) /// /// If your loop body contains more than one statement, you can wrap it with a /// {}, just like any other C++ statement. Note that doing /// /// FOREACH_VAR_IN_INST(Var0, Instr0) /// FOREACH_VAR_IN_INST(Var1, Instr1) /// /// is perfectly safe and legal -- as long as Var0 and Var1 are different /// identifiers. /// /// It is sometimes useful to know Var's index in Instr, which can be obtained /// with /// /// IndexOfVarInInst(Var) /// /// Similarly, the current Variable's Operand index can be obtained with /// /// IndexOfVarOperandInInst(Var). /// /// And that's pretty much it. Now, if you really hate yourself, keep reading, /// but beware! The iterator implementation abuses comma operators, for /// statements, variable initialization and expression evaluations. You have /// been warned. /// /// **Implementation details** /// /// First, let's "break" the two loops into multiple parts: /// /// for ( Init1; Cond1; Step1 ) /// if ( CondIf ) /// UnreachableThenBody /// else /// for ( Init2; Cond2; Step2 ) /// /// The hairiest, scariest, most confusing parts here are Init2 and Cond2, so /// let's save them for later. /// /// 1) Init1 declares five integer variables: /// * i --> outer loop control variable; /// * Var##Index --> the current variable index /// * SrcSize --> how many operands does Instr have? /// * j --> the inner loop control variable /// * NumVars --> how many variables does the current operand have? /// /// 2) Cond1 and Step1 are your typical for condition and step expressions. /// /// 3) CondIf is where the voodoo starts. We abuse CondIf to declare a local /// Operand * variable to hold the current operand being evaluated to avoid /// invoking an Instr::getOperand for each outter loop iteration -- which /// caused a small performance regression. We initialize the Operand * /// variable with nullptr, so UnreachableThenBody is trully unreachable, and /// use the else statement to declare the inner loop. We want to use an else /// here to prevent code like /// /// FOREACH_VAR_IN_INST(Var, Instr) { /// } else { /// } /// /// from being legal. We also want to avoid warnings about "dangling else"s. /// /// 4) Init2 is where the voodoo starts. It declares a Variable * local /// variable name 'Var' (i.e., whatever identifier the first parameter to /// FOREACH_VAR_IN_INST is), and initializes it with nullptr. Why nullptr? /// Because as stated above, some operands have zero Variables, and therefore /// initializing Var = CurrentOperand->Variable(0) would lead to an assertion. /// Init2 is also required to initialize the control variables used in Cond2, /// as well as the current Operand * holder, Therefore, we use the obscure /// comma operator to initialize Var, and the control variables. The /// declaration /// /// Variable *Var = (j = 0, CurrentOperand = Instr.Operand[i], /// NumVars = CurrentOperand.NumVars, nullptr) /// /// achieves that. /// /// 5) Cond2 is where we lose all hopes of having a self-documenting /// implementation. The stop condition for the inner loop is simply /// /// j < NumVars /// /// But there is one more thing we need to do before jumping to the iterator's /// body: we need to initialize Var with the current variable, but only if the /// loop has not terminated. So we implemented Cond2 in a way that it would /// make Var point to the current Variable, but only if there were more /// variables. So Cond2 became: /// /// j < NumVars && (Var = CurrentOperand.Var[j]) /// /// which is not quite right. Cond2 would evaluate to false if /// CurrentOperand.Var[j] == nullptr. Even though that should never happen in /// Subzero, assuming this is always true is dangerous and could lead to /// problems in the future. So we abused the comma operator one more time here: /// /// j < NumVars && ((Var = CurrentOperand.Var[j]), true) /// /// this expression will evaluate to true if, and only if, j < NumVars. /// /// 6) Step2 increments the inner loop's control variable, as well as the /// current variable index. /// /// We use Var -- which should be a valid C++ identifier -- to uniquify names /// -- e.g., i##Var instead of simply i because we want users to be able to use /// the iterator for cross-products involving instructions' variables. #define FOREACH_VAR_IN_INST(Var, Instr) \ for (SizeT Sz_I##Var##_ = 0, Sz_##Var##Index_ = 0, \ Sz_SrcSize##Var##_ = (Instr).getSrcSize(), Sz_J##Var##_ = 0, \ Sz_NumVars##Var##_ = 0, Sz_Foreach_Break = 0; \ !Sz_Foreach_Break && Sz_I##Var##_ < Sz_SrcSize##Var##_; ++Sz_I##Var##_) \ if (Operand *Sz_Op##Var##_ = nullptr) \ /*nothing*/; \ else \ for (Variable *Var = \ (Sz_J##Var##_ = 0, \ Sz_Op##Var##_ = (Instr).getSrc(Sz_I##Var##_), \ Sz_NumVars##Var##_ = Sz_Op##Var##_->getNumVars(), nullptr); \ !Sz_Foreach_Break && Sz_J##Var##_ < Sz_NumVars##Var##_ && \ ((Var = Sz_Op##Var##_->getVar(Sz_J##Var##_)), true); \ ++Sz_J##Var##_, ++Sz_##Var##Index_) #define IsOnlyValidInFOREACH_VAR_IN_INST(V) \ (static_cast<const SizeT>(Sz_##V##_)) #define IndexOfVarInInst(Var) IsOnlyValidInFOREACH_VAR_IN_INST(Var##Index) #define IndexOfVarOperandInInst(Var) IsOnlyValidInFOREACH_VAR_IN_INST(I##Var) #define FOREACH_VAR_IN_INST_BREAK \ if (true) { \ Sz_Foreach_Break = 1; \ continue; \ } else { \ } #undef OnlyValidIn_FOREACH_VAR_IN_INSTS #endif // SUBZERO_SRC_ICEINSTVARITER_H