Switch containing holes via table lookup

Hans Wennborg hans at chromium.org
Thu Feb 13 11:01:04 PST 2014


On Wed, Feb 12, 2014 at 3:40 PM, Jasper Neumann <jn at sirrida.de> wrote:
> But, as I wrote, my first attempt almost but unfortunately not always works.
> When I try to compile LLVM with modified compiler it crashes at
> lib/AsmParser/LLParser.cpp after successfully converting several switches to
> table lookups from other files.
> The error is as follows:
> clang: /home/jane/wrk/llvm-trunk/include/llvm/IR/Instructions.h:2155:
> llvm::Value* llvm::PHINode::getIncomingValueForBlock(const
> llvm::BasicBlock*) const: Assertion `Idx >= 0 && "Invalid basic block
> argument!"' failed.
> [...]
> The statistics I got thus far tells me that those fixable holes occur quite
> often.

Cool!

> I probably have missed to insert a connection between BasicBlocks.
> There should be a way to apply the methods described in
> http://llvm.cs.uiuc.edu/releases/2.3/docs/tutorial/LangImpl5.html.
> Please help to fix this.

I haven't tried to debug your patch, but I've taken a closer look and
made some comments. Hopefully we can make the code a little more
straight-forward and then figure out where the bug is.

> +static cl::opt<bool> CheckHoles(
> +    "simplifycfg-check-holes", cl::Hidden, cl::init(true),
> +    cl::desc("Allow holes in a value table by testing a mask"));

Having this command-line option is just for testing, right?

> +  unsigned NeededMaskSize = 0;
> +  if (CheckHoles) {
> +    if (TableSize <= 32 && TD->fitsInLegalInteger(32))
> +      NeededMaskSize = 32;
> +    else if (TableSize <= 64 && TD->fitsInLegalInteger(64))
> +      NeededMaskSize = 64;

What if the table is of size 12? We don't want to have to use a 32-bit
immediate for the mask in that case. I think we should generate the
bitmask of the same size as the table, as long as it fits in a legal
register. This is what the current bitmap-building code for lookup
tables does (see the SwitchLookupTable constructor above).

> +  // If the table has holes, we need a constant result for the default case...
>    SmallVector<std::pair<PHINode*, Constant*>, 4> DefaultResultsList;
> -  if (TableHasHoles && !GetCaseResults(SI, 0, SI->getDefaultDest(), &CommonDest,
> -                                       DefaultResultsList, TD))
> +  bool NeedMask = (TableHasHoles &&
> +                  !GetCaseResults(SI, 0, SI->getDefaultDest(), &CommonDest,
> +                                  DefaultResultsList, TD));
> +  // ...or need to check for valid values with a sufficiently small bit set.
> +  if (NeedMask && NeededMaskSize == 0)
>      return false;

I wonder if we could simplify the code here. What if we start with

bool HasDefaultResults = false;
if (TableHasHoles)
  // If the table has holes, check if we can fill them with a default result.
  HasDefaultResults = GetCaseResults(SI, 0, SI->getDefaultDest(),
&CommonDest, DefaultResultsList, TD);

ConstantInt *TableMask = 0;
if (TableHasHoles && !HasDefaultResults && TD->fitsInLegalInteger(TableSize)) {
  // Build a bitmask to check for valid table indices.
  APInt MaskInt(TableSize, 0);
  APInt One(TableSize, 1);
  const ResultListTy &ResultList = ResultLists[PHIs[0]];
  for (size_t I = 0, E = ResultList.size(); I != E; ++I) {
    uint64_t Idx = (ResultList[I].first->getValue() -
MinCaseVal->getValue()).getLimitedValue();
    MaskInt |= One << Idx;
  }
  TableMask = ConstantInt::get(M.getContext(), MaskInt);
}


Now we can take all these things together and decide if we want to
bail out or not:

if (TableHasHoles && !HasDefaultResults && !TableMask)
  return false;

> +    // As an extra penalty for the validity test we require more cases.
> +    if (SI->getNumCases() < 4)  // TODO: Make threshold configurable.
> +      return false;

Hmm, yeah I guess this extra penalty might be necessary :/

> +    // We need any value to fill the table; the first one suffices.
> +    SwitchInst::CaseIt CI = SI->case_begin();
> +    GetCaseResults(SI, CI.getCaseValue(), CI.getCaseSuccessor(),
> +                   &CommonDest, DefaultResultsList, TD);

Ouch, calling GetCaseResults more than necessary is something I'd like
to avoid. We already have all the results in ResultLists, so let's use
that.

>    // Populate the BB that does the lookups.
>    Builder.SetInsertPoint(LookupBB);
> +
> +  if (NeedMask) {
> +    uint64_t UseMask = 0;
> +    for (SwitchInst::CaseIt CI = SI->case_begin(), E = SI->case_end();
> +         CI != E; ++CI) {
> +      ConstantInt *CaseVal = CI.getCaseValue();
> +      UseMask |= 1ULL <<
> +        (CaseVal->getValue() - MinCaseVal->getValue()).getLimitedValue();
> +    }
> +
> +    BasicBlock *MaskBB = BasicBlock::Create(Mod.getContext(),
> +                                            "switch.lookup2",
> +                                            CommonDest->getParent(),
> +                                            CommonDest);

I think we should create this BB earlier (and with a better name!), so
that we can use it in the conditional branch we create after checking
that Index < TableSize above. I also like to have the mask built
before we start generating IR.

> +    ConstantInt *Zero;
> +    ConstantInt *One;
> +    ConstantInt *Mask;
> +    Value *CmpVal;
> +    if (NeededMaskSize == 32) {
> +      Zero = Builder.getInt32(0);
> +      One = Builder.getInt32(1);
> +      Mask = Builder.getInt32(UseMask);
> +      CmpVal = Builder.CreateZExtOrTrunc(TableIndex, Builder.getInt32Ty());
> +    } else {
> +      Zero = Builder.getInt64(0);
> +      One = Builder.getInt64(1);
> +      Mask = Builder.getInt64(UseMask);
> +      CmpVal = Builder.CreateZExtOrTrunc(TableIndex, Builder.getInt64Ty());
> +    }
> +    Value *Shr = Builder.CreateLShr(Mask, CmpVal);
> +    Value *And = Builder.CreateAnd(Shr, One);
> +    Value *CmpZ = Builder.CreateICmpNE(And, Zero);
> +    Builder.CreateCondBr(CmpZ, MaskBB, SI->getDefaultDest());
> +    Builder.SetInsertPoint(MaskBB);
> +    LookupBB = MaskBB;

I wonder if this mask checking code could be simpler. Look at
SwitchLookupTable::BuildLookup for the BitMapKind case.


You'll also need to update the table building code in
SwitchLookupTable::SwitchLookupTable. (I assume you did this locally,
otherwise it will assert when not having a default value to fill the
holes?)

 - Hans



More information about the llvm-commits mailing list