GP-2980 Analysis for unrolled switch guards

This commit is contained in:
caheckman 2023-01-03 19:56:49 -05:00
parent e100c84085
commit 6f49dc939e
4 changed files with 196 additions and 18 deletions

View file

@ -2616,6 +2616,70 @@ bool BlockBasic::noInterveningStatement(PcodeOp *first,int4 path,PcodeOp *last)
return false; return false;
} }
/// If there exists a CPUI_MULTIEQUAL PcodeOp in the given basic block that takes this exact list of Varnodes
/// as its inputs, return that PcodeOp. Otherwise return null.
/// \param varArray is the exact list of Varnodes
/// \param bl is the basic block
/// \return the MULTIEQUAL or null
PcodeOp *BlockBasic::findMultiequal(const vector<Varnode *> &varArray)
{
Varnode *vn = varArray[0];
PcodeOp *op;
list<PcodeOp *>::const_iterator iter = vn->beginDescend();
for(;;) {
op = *iter;
if (op->code() == CPUI_MULTIEQUAL && op->getParent() == this)
break;
++iter;
if (iter == vn->endDescend())
return (PcodeOp *)0;
}
for(int4 i=0;i<op->numInput();++i) {
if (op->getIn(i) != varArray[i])
return (PcodeOp *)0;
}
return op;
}
/// Each Varnode must be defined by a PcodeOp with the same OpCode. The Varnode, within the array, is replaced
/// with the input Varnode in the indicated slot.
/// \param varArray is the given array of Varnodes
/// \param slot is the indicated slot
/// \return \true if all the Varnodes are defined in the same way
bool BlockBasic::liftVerifyUnroll(vector<Varnode *> &varArray,int4 slot)
{
OpCode opc;
Varnode *cvn;
Varnode *vn = varArray[0];
if (!vn->isWritten()) return false;
PcodeOp *op = vn->getDef();
opc = op->code();
if (op->numInput() == 2) {
cvn = op->getIn(1-slot);
if (!cvn->isConstant()) return false;
}
else
cvn = (Varnode *)0;
varArray[0] = op->getIn(slot);
for(int4 i=1;i<varArray.size();++i) {
vn = varArray[i];
if (!vn->isWritten()) return false;
op = vn->getDef();
if (op->code() != opc) return false;
if (cvn != (Varnode *)0) {
Varnode *cvn2 = op->getIn(1-slot);
if (!cvn2->isConstant()) return false;
if (cvn->getSize() != cvn2->getSize()) return false;
if (cvn->getOffset() != cvn2->getOffset()) return false;
}
varArray[i] = op->getIn(slot);
}
return true;
}
void BlockCopy::printHeader(ostream &s) const void BlockCopy::printHeader(ostream &s) const
{ {

View file

@ -413,6 +413,8 @@ public:
list<PcodeOp *>::const_iterator endOp(void) const { return op.end(); } ///< Return an iterator to the end of the PcodeOps list<PcodeOp *>::const_iterator endOp(void) const { return op.end(); } ///< Return an iterator to the end of the PcodeOps
bool emptyOp(void) const { return op.empty(); } ///< Return \b true if \b block contains no operations bool emptyOp(void) const { return op.empty(); } ///< Return \b true if \b block contains no operations
static bool noInterveningStatement(PcodeOp *first,int4 path,PcodeOp *last); static bool noInterveningStatement(PcodeOp *first,int4 path,PcodeOp *last);
PcodeOp *findMultiequal(const vector<Varnode *> &varArray); ///< Find MULTIEQUAL with given inputs
static bool liftVerifyUnroll(vector<Varnode *> &varArray,int4 slot); ///< Verify given Varnodes are defined with same PcodeOp
}; };
/// \brief This class is used to mirror the BlockBasic objects in the fixed control-flow graph for a function /// \brief This class is used to mirror the BlockBasic objects in the fixed control-flow graph for a function

View file

@ -502,6 +502,46 @@ uintb JumpBasic::backup2Switch(Funcdata *fd,uintb output,Varnode *outvn,Varnode
return output; return output;
} }
/// If the Varnode has a restricted range due to masking via INT_AND, the maximum value of this range is returned.
/// Otherwise, 0 is returned, indicating that the Varnode can take all possible values.
/// \param vn is the given Varnode
/// \return the maximum value or 0
uintb JumpBasic::getMaxValue(Varnode *vn)
{
uintb maxValue = 0; // 0 indicates maximum possible value
if (!vn->isWritten())
return maxValue;
PcodeOp *op = vn->getDef();
if (op->code() == CPUI_INT_AND) {
Varnode *constvn = op->getIn(1);
if (constvn->isConstant()) {
maxValue = coveringmask( constvn->getOffset() );
maxValue = (maxValue + 1) & calc_mask(vn->getSize());
}
}
else if (op->code() == CPUI_MULTIEQUAL) { // Its possible the AND is duplicated across multiple blocks
int4 i;
for(i=0;i<op->numInput();++i) {
Varnode *subvn = op->getIn(i);
if (!subvn->isWritten()) break;
PcodeOp *andOp = subvn->getDef();
if (andOp->code() != CPUI_INT_AND) break;
Varnode *constvn = andOp->getIn(1);
if (!constvn->isConstant()) break;
if (maxValue < constvn->getOffset())
maxValue = constvn->getOffset();
}
if (i == op->numInput()) {
maxValue = coveringmask( maxValue );
maxValue = (maxValue + 1) & calc_mask(vn->getSize());
}
else
maxValue = 0;
}
return maxValue;
}
/// \brief Calculate the initial set of Varnodes that might be switch variables /// \brief Calculate the initial set of Varnodes that might be switch variables
/// ///
/// Paths that terminate at the given PcodeOp are calculated and organized /// Paths that terminate at the given PcodeOp are calculated and organized
@ -566,7 +606,8 @@ static bool matching_constants(Varnode *vn1,Varnode *vn2)
/// \param path is the specific branch to take from the CBRANCH to reach the switch /// \param path is the specific branch to take from the CBRANCH to reach the switch
/// \param rng is the range of values causing the switch path to be taken /// \param rng is the range of values causing the switch path to be taken
/// \param v is the Varnode holding the value controlling the CBRANCH /// \param v is the Varnode holding the value controlling the CBRANCH
GuardRecord::GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &rng,Varnode *v) /// \param unroll is \b true if the guard is duplicated across multiple blocks
GuardRecord::GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &rng,Varnode *v,bool unr)
{ {
cbranch = bOp; cbranch = bOp;
@ -575,6 +616,7 @@ GuardRecord::GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &
range = rng; range = rng;
vn = v; vn = v;
baseVn = quasiCopy(v,bitsPreserved); // Look for varnode whose bits are copied baseVn = quasiCopy(v,bitsPreserved); // Look for varnode whose bits are copied
unrolled = unr;
} }
/// \brief Determine if \b this guard applies to the given Varnode /// \brief Determine if \b this guard applies to the given Varnode
@ -1020,7 +1062,12 @@ void JumpBasic::analyzeGuards(BlockBasic *bl,int4 pathout)
else { else {
pathout = -1; // Make sure not to use pathout next time around pathout = -1; // Make sure not to use pathout next time around
for(;;) { for(;;) {
if (bl->sizeIn() != 1) return; // Assume only 1 path to switch if (bl->sizeIn() != 1) {
if (bl->sizeIn() > 1)
checkUnrolledGuard(bl, maxpullback, usenzmask);
return;
}
// Only 1 flow path to the switch
prevbl = (BlockBasic *)bl->getIn(0); prevbl = (BlockBasic *)bl->getIn(0);
if (prevbl->sizeOut() != 1) break; // Is it possible to deviate from switch path in this block if (prevbl->sizeOut() != 1) break; // Is it possible to deviate from switch path in this block
bl = prevbl; // If not, back up to next block bl = prevbl; // If not, back up to next block
@ -1077,17 +1124,7 @@ void JumpBasic::calcRange(Varnode *vn,CircleRange &rng) const
else if (vn->isWritten() && vn->getDef()->isBoolOutput()) else if (vn->isWritten() && vn->getDef()->isBoolOutput())
rng = CircleRange(0,2,1,1); // Only 0 or 1 possible rng = CircleRange(0,2,1,1); // Only 0 or 1 possible
else { // Should we go ahead and use nzmask in all cases? else { // Should we go ahead and use nzmask in all cases?
uintb maxValue = 0; // Every possible value uintb maxValue = getMaxValue(vn);
if (vn->isWritten()) {
PcodeOp *andop = vn->getDef();
if (andop->code() == CPUI_INT_AND) {
Varnode *constvn = andop->getIn(1);
if (constvn->isConstant()) {
maxValue = coveringmask( constvn->getOffset() );
maxValue = (maxValue + 1) & calc_mask(vn->getSize());
}
}
}
stride = getStride(vn); stride = getStride(vn);
rng = CircleRange(0,maxValue,vn->getSize(),stride); rng = CircleRange(0,maxValue,vn->getSize(),stride);
} }
@ -1203,8 +1240,9 @@ void JumpBasic::markFoldableGuards(void)
int4 bitsPreserved; int4 bitsPreserved;
Varnode *baseVn = GuardRecord::quasiCopy(vn, bitsPreserved); Varnode *baseVn = GuardRecord::quasiCopy(vn, bitsPreserved);
for(int4 i=0;i<selectguards.size();++i) { for(int4 i=0;i<selectguards.size();++i) {
if (selectguards[i].valueMatch(vn,baseVn,bitsPreserved)==0) { GuardRecord &guardRecord(selectguards[i]);
selectguards[i].clear(); // Indicate this is not a true guard if (guardRecord.valueMatch(vn,baseVn,bitsPreserved)==0 || guardRecord.isUnrolled()) {
guardRecord.clear(); // Indicate this guard was not used or should not be folded
} }
} }
} }
@ -1243,6 +1281,75 @@ bool JumpBasic::flowsOnlyToModel(Varnode *vn,PcodeOp *trailOp)
return true; return true;
} }
/// All CBRANCHs in addition to flowing to the given block, must also flow to another common block,
/// and each boolean value must select between the given block and the common block in the same way.
/// If this flow exists, \b true is returned and the boolean Varnode inputs to each CBRANCH are passed back.
/// \param varArray will hold the input Varnodes being passed back
/// \param bl is the given block
/// \return \b true if the common CBRANCH flow exists across all incoming blocks
bool JumpBasic::checkCommonCbranch(vector<Varnode *> &varArray,BlockBasic *bl)
{
BlockBasic *curBlock = (BlockBasic *)bl->getIn(0);
PcodeOp *op = curBlock->lastOp();
if (op == (PcodeOp *)0 || op->code() != CPUI_CBRANCH)
return false;
int4 outslot = bl->getInRevIndex(0);
bool isOpFlip = op->isBooleanFlip();
varArray.push_back(op->getIn(1)); // Pass back boolean input to CBRANCH
for(int4 i=1;i<bl->sizeIn();++i) {
curBlock = (BlockBasic *)bl->getIn(i);
op = curBlock->lastOp();
if (op == (PcodeOp *)0 || op->code() != CPUI_CBRANCH)
return false; // All blocks must end with CBRANCH
if (op->isBooleanFlip() != isOpFlip)
return false;
if (outslot != bl->getInRevIndex(i))
return false; // Boolean value must have some meaning
varArray.push_back(op->getIn(1)); // Pass back boolean input to CBRANCH
}
return true;
}
/// \brief Check for a guard that has been unrolled across multiple blocks
///
/// A guard calculation can be duplicated across multiple blocks that all branch to the basic block
/// performing the final BRANCHIND. In this case, the switch variable is also duplicated across multiple Varnodes
/// that are all inputs to a MULTIEQUAL whose output is used for the final BRANCHIND calculation. This method
/// looks for this situation and creates a GuardRecord associated with this MULTIEQUAL output.
/// \param bl is the basic block on the path to the switch with multiple incoming flows
/// \param maxpullback is the maximum number of times to pull back from the guard CBRANCH to the putative switch variable
/// \param usenzmask is \b true if the NZMASK should be used as part of the pull-back operation
void JumpBasic::checkUnrolledGuard(BlockBasic *bl,int4 maxpullback,bool usenzmask)
{
vector<Varnode *> varArray;
if (!checkCommonCbranch(varArray,bl))
return;
int4 indpath = bl->getInRevIndex(0);
bool toswitchval = (indpath == 1);
PcodeOp *cbranch = ((BlockBasic *)bl->getIn(0))->lastOp();
if (cbranch->isBooleanFlip())
toswitchval = !toswitchval;
CircleRange rng(toswitchval);
int4 indpathstore = bl->getIn(0)->getFlipPath() ? 1-indpath : indpath;
PcodeOp *readOp = cbranch;
for(int4 j=0;j<maxpullback;++j) {
PcodeOp *multiOp = bl->findMultiequal(varArray);
if (multiOp != (PcodeOp *)0) {
selectguards.push_back(GuardRecord(cbranch,readOp,indpathstore,rng,multiOp->getOut(),true));
}
Varnode *markup; // Throw away markup information
Varnode *vn = varArray[0];
if (!vn->isWritten()) break;
PcodeOp *readOp = vn->getDef();
vn = rng.pullBack(readOp,&markup,usenzmask);
if (vn == (Varnode *)0) break;
if (rng.isEmpty()) break;
if (!BlockBasic::liftVerifyUnroll(varArray, readOp->getSlot(vn))) break;
}
}
bool JumpBasic::foldInOneGuard(Funcdata *fd,GuardRecord &guard,JumpTable *jump) bool JumpBasic::foldInOneGuard(Funcdata *fd,GuardRecord &guard,JumpTable *jump)
{ {

View file

@ -141,13 +141,15 @@ class JumpTable;
class GuardRecord { class GuardRecord {
PcodeOp *cbranch; ///< PcodeOp CBRANCH the branches around the switch PcodeOp *cbranch; ///< PcodeOp CBRANCH the branches around the switch
PcodeOp *readOp; ///< The immediate PcodeOp causing the restriction PcodeOp *readOp; ///< The immediate PcodeOp causing the restriction
int4 indpath; ///< Specific CBRANCH path going to the switch
CircleRange range; ///< Range of values causing the CBRANCH to take the path to the switch
Varnode *vn; ///< The Varnode being restricted Varnode *vn; ///< The Varnode being restricted
Varnode *baseVn; ///< Value being (quasi)copied to the Varnode Varnode *baseVn; ///< Value being (quasi)copied to the Varnode
int4 indpath; ///< Specific CBRANCH path going to the switch
int4 bitsPreserved; ///< Number of bits copied (all other bits are zero) int4 bitsPreserved; ///< Number of bits copied (all other bits are zero)
CircleRange range; ///< Range of values causing the CBRANCH to take the path to the switch
bool unrolled; ///< \b true if guarding CBRANCH is duplicated across multiple blocks
public: public:
GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &rng,Varnode *v); ///< Constructor GuardRecord(PcodeOp *bOp,PcodeOp *rOp,int4 path,const CircleRange &rng,Varnode *v,bool unr=false); ///< Constructor
bool isUnrolled(void) const { return unrolled; } ///< Is \b this guard duplicated across multiple blocks
PcodeOp *getBranch(void) const { return cbranch; } ///< Get the CBRANCH associated with \b this guard PcodeOp *getBranch(void) const { return cbranch; } ///< Get the CBRANCH associated with \b this guard
PcodeOp *getReadOp(void) const { return readOp; } ///< Get the PcodeOp immediately causing the restriction PcodeOp *getReadOp(void) const { return readOp; } ///< Get the PcodeOp immediately causing the restriction
int4 getPath(void) const { return indpath; } ///< Get the specific path index going towards the switch int4 getPath(void) const { return indpath; } ///< Get the specific path index going towards the switch
@ -364,6 +366,7 @@ protected:
static bool ispoint(Varnode *vn); ///< Is it possible for the given Varnode to be a switch variable? static bool ispoint(Varnode *vn); ///< Is it possible for the given Varnode to be a switch variable?
static int4 getStride(Varnode *vn); ///< Get the step/stride associated with the Varnode static int4 getStride(Varnode *vn); ///< Get the step/stride associated with the Varnode
static uintb backup2Switch(Funcdata *fd,uintb output,Varnode *outvn,Varnode *invn); static uintb backup2Switch(Funcdata *fd,uintb output,Varnode *outvn,Varnode *invn);
static uintb getMaxValue(Varnode *vn); ///< Get maximum value associated with the given Varnode
void findDeterminingVarnodes(PcodeOp *op,int4 slot); void findDeterminingVarnodes(PcodeOp *op,int4 slot);
void analyzeGuards(BlockBasic *bl,int4 pathout); void analyzeGuards(BlockBasic *bl,int4 pathout);
void calcRange(Varnode *vn,CircleRange &rng) const; void calcRange(Varnode *vn,CircleRange &rng) const;
@ -372,6 +375,8 @@ protected:
void markFoldableGuards(); void markFoldableGuards();
void markModel(bool val); ///< Mark (or unmark) all PcodeOps involved in the model void markModel(bool val); ///< Mark (or unmark) all PcodeOps involved in the model
bool flowsOnlyToModel(Varnode *vn,PcodeOp *trailOp); ///< Check if the given Varnode flows to anything other than \b this model bool flowsOnlyToModel(Varnode *vn,PcodeOp *trailOp); ///< Check if the given Varnode flows to anything other than \b this model
bool checkCommonCbranch(vector<Varnode *> &varArray,BlockBasic *bl); ///< Check that all incoming blocks end with a CBRANCH
void checkUnrolledGuard(BlockBasic *bl,int4 maxpullback,bool usenzmask);
/// \brief Eliminate the given guard to \b this switch /// \brief Eliminate the given guard to \b this switch
/// ///