Commit 87f80c12 by Andrew Scull

Introduction of improved switch lowering.

This includes the high level analysis of switches, the x86 lowering, the repointing of targets in jump tables and ASM emission of jump tables. The technique uses jump tables, range test and binary search with worst case O(lg n) which improves the previous worst case of O(n) from a sequential search. Use is hidden by the --adv-switch flag as the IAS emission still needs to be implemented. BUG=None R=jvoung@chromium.org, stichnot@chromium.org Review URL: https://codereview.chromium.org/1234803007.
parent 53483691
...@@ -196,6 +196,7 @@ SRCS = \ ...@@ -196,6 +196,7 @@ SRCS = \
IceOperand.cpp \ IceOperand.cpp \
IceRegAlloc.cpp \ IceRegAlloc.cpp \
IceRNG.cpp \ IceRNG.cpp \
IceSwitchLowering.cpp \
IceTargetLowering.cpp \ IceTargetLowering.cpp \
IceTargetLoweringARM32.cpp \ IceTargetLoweringARM32.cpp \
IceTargetLoweringMIPS32.cpp \ IceTargetLoweringMIPS32.cpp \
......
...@@ -213,15 +213,9 @@ void CfgNode::deletePhis() { ...@@ -213,15 +213,9 @@ void CfgNode::deletePhis() {
// parameter is only used to make the new node's name unique when // parameter is only used to make the new node's name unique when
// there are multiple edges between the same pair of nodes.) The new // there are multiple edges between the same pair of nodes.) The new
// node's instruction list is initialized to the empty list, with no // node's instruction list is initialized to the empty list, with no
// terminator instruction. If there are multiple edges from Pred to // terminator instruction. There must not be multiple edges from Pred
// this node, only one edge is split, and the particular choice of // to this node so all Inst::getTerminatorEdges implementations must
// edge is undefined. This could happen with a switch instruction, or // not contain duplicates.
// a conditional branch that weirdly has both branches to the same
// place. TODO(stichnot,kschimpf): Figure out whether this is legal
// in the LLVM IR or the PNaCl bitcode, and if so, we need to
// establish a strong relationship among the ordering of Pred's
// out-edge list, this node's in-edge list, and the Phi instruction's
// operand list.
CfgNode *CfgNode::splitIncomingEdge(CfgNode *Pred, SizeT EdgeIndex) { CfgNode *CfgNode::splitIncomingEdge(CfgNode *Pred, SizeT EdgeIndex) {
CfgNode *NewNode = Func->makeNode(); CfgNode *NewNode = Func->makeNode();
if (BuildDefs::dump()) if (BuildDefs::dump())
...@@ -232,32 +226,31 @@ CfgNode *CfgNode::splitIncomingEdge(CfgNode *Pred, SizeT EdgeIndex) { ...@@ -232,32 +226,31 @@ CfgNode *CfgNode::splitIncomingEdge(CfgNode *Pred, SizeT EdgeIndex) {
NewNode->setNeedsPlacement(true); NewNode->setNeedsPlacement(true);
// Repoint Pred's out-edge. // Repoint Pred's out-edge.
bool Found = false; bool Found = false;
for (auto I = Pred->OutEdges.begin(), E = Pred->OutEdges.end(); for (CfgNode *&I : Pred->OutEdges) {
!Found && I != E; ++I) { if (I == this) {
if (*I == this) { I = NewNode;
*I = NewNode;
NewNode->InEdges.push_back(Pred); NewNode->InEdges.push_back(Pred);
Found = true; Found = true;
break;
} }
} }
assert(Found); assert(Found);
// Repoint this node's in-edge. // Repoint this node's in-edge.
Found = false; Found = false;
for (auto I = InEdges.begin(), E = InEdges.end(); !Found && I != E; ++I) { for (CfgNode *&I : InEdges) {
if (*I == Pred) { if (I == Pred) {
*I = NewNode; I = NewNode;
NewNode->OutEdges.push_back(this); NewNode->OutEdges.push_back(this);
Found = true; Found = true;
break;
} }
} }
assert(Found); assert(Found);
// Repoint a suitable branch instruction's target and return. // Repoint all suitable branch instructions' target and return.
Found = false; Found = false;
for (Inst &I : reverse_range(Pred->getInsts())) { for (Inst &I : Pred->getInsts())
if (!I.isDeleted() && I.repointEdge(this, NewNode)) if (!I.isDeleted() && I.repointEdges(this, NewNode))
return NewNode; Found = true;
}
// This should be unreachable, so the assert will fail.
assert(Found); assert(Found);
return NewNode; return NewNode;
} }
...@@ -752,7 +745,7 @@ void CfgNode::contractIfEmpty() { ...@@ -752,7 +745,7 @@ void CfgNode::contractIfEmpty() {
} }
for (Inst &I : Pred->getInsts()) { for (Inst &I : Pred->getInsts()) {
if (!I.isDeleted()) if (!I.isDeleted())
I.repointEdge(this, OutEdges.front()); I.repointEdges(this, OutEdges.front());
} }
} }
} }
......
...@@ -176,6 +176,10 @@ cl::opt<std::string> ...@@ -176,6 +176,10 @@ cl::opt<std::string>
TranslateOnly("translate-only", TranslateOnly("translate-only",
cl::desc("Translate only the given function"), cl::init("")); cl::desc("Translate only the given function"), cl::init(""));
cl::opt<bool>
UseAdvancedSwitchLowering("adv-switch",
cl::desc("Use advanced switch lowering"));
cl::opt<bool> UseSandboxing("sandbox", cl::desc("Use sandboxing")); cl::opt<bool> UseSandboxing("sandbox", cl::desc("Use sandboxing"));
cl::opt<std::string> VerboseFocusOn( cl::opt<std::string> VerboseFocusOn(
...@@ -341,6 +345,7 @@ void ClFlags::resetClFlags(ClFlags &OutFlags) { ...@@ -341,6 +345,7 @@ void ClFlags::resetClFlags(ClFlags &OutFlags) {
OutFlags.SkipUnimplemented = false; OutFlags.SkipUnimplemented = false;
OutFlags.SubzeroTimingEnabled = false; OutFlags.SubzeroTimingEnabled = false;
OutFlags.TimeEachFunction = false; OutFlags.TimeEachFunction = false;
OutFlags.UseAdvancedSwitchLowering = false;
OutFlags.UseSandboxing = false; OutFlags.UseSandboxing = false;
// Enum and integer fields. // Enum and integer fields.
OutFlags.Opt = Opt_m1; OutFlags.Opt = Opt_m1;
...@@ -410,6 +415,7 @@ void ClFlags::getParsedClFlags(ClFlags &OutFlags) { ...@@ -410,6 +415,7 @@ void ClFlags::getParsedClFlags(ClFlags &OutFlags) {
OutFlags.setTimeEachFunction(::TimeEachFunction); OutFlags.setTimeEachFunction(::TimeEachFunction);
OutFlags.setTimingFocusOn(::TimingFocusOn); OutFlags.setTimingFocusOn(::TimingFocusOn);
OutFlags.setTranslateOnly(::TranslateOnly); OutFlags.setTranslateOnly(::TranslateOnly);
OutFlags.setUseAdvancedSwitchLowering(::UseAdvancedSwitchLowering);
OutFlags.setUseSandboxing(::UseSandboxing); OutFlags.setUseSandboxing(::UseSandboxing);
OutFlags.setVerboseFocusOn(::VerboseFocusOn); OutFlags.setVerboseFocusOn(::VerboseFocusOn);
OutFlags.setOutFileType(::OutFileType); OutFlags.setOutFileType(::OutFileType);
......
...@@ -103,6 +103,13 @@ public: ...@@ -103,6 +103,13 @@ public:
} }
void setTimeEachFunction(bool NewValue) { TimeEachFunction = NewValue; } void setTimeEachFunction(bool NewValue) { TimeEachFunction = NewValue; }
bool getUseAdvancedSwitchLowering() const {
return UseAdvancedSwitchLowering;
}
void setUseAdvancedSwitchLowering(bool NewValue) {
UseAdvancedSwitchLowering = NewValue;
}
bool getUseSandboxing() const { return UseSandboxing; } bool getUseSandboxing() const { return UseSandboxing; }
void setUseSandboxing(bool NewValue) { UseSandboxing = NewValue; } void setUseSandboxing(bool NewValue) { UseSandboxing = NewValue; }
...@@ -235,6 +242,7 @@ private: ...@@ -235,6 +242,7 @@ private:
bool SkipUnimplemented; bool SkipUnimplemented;
bool SubzeroTimingEnabled; bool SubzeroTimingEnabled;
bool TimeEachFunction; bool TimeEachFunction;
bool UseAdvancedSwitchLowering;
bool UseSandboxing; bool UseSandboxing;
OptLevel Opt; OptLevel Opt;
......
...@@ -146,7 +146,8 @@ private: ...@@ -146,7 +146,8 @@ private:
void assignRelLinkNum(SizeT SymTabNumber, RelSectionList &RelSections); void assignRelLinkNum(SizeT SymTabNumber, RelSectionList &RelSections);
/// Helper function for writeDataSection. Writes a data section of type /// Helper function for writeDataSection. Writes a data section of type
/// SectionType, given the global variables Vars belonging to that SectionType. /// SectionType, given the global variables Vars belonging to that
/// SectionType.
void writeDataOfType(SectionType SectionType, void writeDataOfType(SectionType SectionType,
const VariableDeclarationList &Vars, const VariableDeclarationList &Vars,
FixupKind RelocationKind, FixupKind RelocationKind,
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "IceCfgNode.h" #include "IceCfgNode.h"
#include "IceLiveness.h" #include "IceLiveness.h"
#include "IceOperand.h" #include "IceOperand.h"
#include "IceTargetLowering.h"
namespace Ice { namespace Ice {
...@@ -295,15 +296,17 @@ NodeList InstBr::getTerminatorEdges() const { ...@@ -295,15 +296,17 @@ NodeList InstBr::getTerminatorEdges() const {
return OutEdges; return OutEdges;
} }
bool InstBr::repointEdge(CfgNode *OldNode, CfgNode *NewNode) { bool InstBr::repointEdges(CfgNode *OldNode, CfgNode *NewNode) {
bool Found = false;
if (TargetFalse == OldNode) { if (TargetFalse == OldNode) {
TargetFalse = NewNode; TargetFalse = NewNode;
return true; Found = true;
} else if (TargetTrue == OldNode) { }
if (TargetTrue == OldNode) {
TargetTrue = NewNode; TargetTrue = NewNode;
return true; Found = true;
} }
return false; return Found;
} }
InstCast::InstCast(Cfg *Func, OpKind CastKind, Variable *Dest, Operand *Source) InstCast::InstCast(Cfg *Func, OpKind CastKind, Variable *Dest, Operand *Source)
...@@ -461,21 +464,28 @@ NodeList InstSwitch::getTerminatorEdges() const { ...@@ -461,21 +464,28 @@ NodeList InstSwitch::getTerminatorEdges() const {
for (SizeT I = 0; I < NumCases; ++I) { for (SizeT I = 0; I < NumCases; ++I) {
OutEdges.push_back(Labels[I]); OutEdges.push_back(Labels[I]);
} }
std::sort(OutEdges.begin(), OutEdges.end(),
[](const CfgNode *x, const CfgNode *y) {
return x->getIndex() < y->getIndex();
});
auto Last = std::unique(OutEdges.begin(), OutEdges.end());
OutEdges.erase(Last, OutEdges.end());
return OutEdges; return OutEdges;
} }
bool InstSwitch::repointEdge(CfgNode *OldNode, CfgNode *NewNode) { bool InstSwitch::repointEdges(CfgNode *OldNode, CfgNode *NewNode) {
bool Found = false;
if (LabelDefault == OldNode) { if (LabelDefault == OldNode) {
LabelDefault = NewNode; LabelDefault = NewNode;
return true; Found = true;
} }
for (SizeT I = 0; I < NumCases; ++I) { for (SizeT I = 0; I < NumCases; ++I) {
if (Labels[I] == OldNode) { if (Labels[I] == OldNode) {
Labels[I] = NewNode; Labels[I] = NewNode;
return true; Found = true;
} }
} }
return false; return Found;
} }
InstUnreachable::InstUnreachable(Cfg *Func) InstUnreachable::InstUnreachable(Cfg *Func)
...@@ -504,6 +514,31 @@ InstFakeUse::InstFakeUse(Cfg *Func, Variable *Src) ...@@ -504,6 +514,31 @@ InstFakeUse::InstFakeUse(Cfg *Func, Variable *Src)
InstFakeKill::InstFakeKill(Cfg *Func, const Inst *Linked) InstFakeKill::InstFakeKill(Cfg *Func, const Inst *Linked)
: InstHighLevel(Func, Inst::FakeKill, 0, nullptr), Linked(Linked) {} : InstHighLevel(Func, Inst::FakeKill, 0, nullptr), Linked(Linked) {}
InstJumpTable::InstJumpTable(Cfg *Func, SizeT NumTargets, CfgNode *Default)
: InstHighLevel(Func, Inst::JumpTable, 1, nullptr),
LabelNumber(Func->getTarget()->makeNextLabelNumber()),
NumTargets(NumTargets) {
Targets = Func->allocateArrayOf<CfgNode *>(NumTargets);
for (SizeT I = 0; I < NumTargets; ++I)
Targets[I] = Default;
}
bool InstJumpTable::repointEdges(CfgNode *OldNode, CfgNode *NewNode) {
bool Found = false;
for (SizeT I = 0; I < NumTargets; ++I) {
if (Targets[I] == OldNode) {
Targets[I] = NewNode;
Found = true;
}
}
return Found;
}
IceString InstJumpTable::getName(const Cfg *Func) const {
return ".L" + Func->getFunctionName() + "$jumptable$__" +
std::to_string(LabelNumber);
}
Type InstCall::getReturnType() const { Type InstCall::getReturnType() const {
if (Dest == nullptr) if (Dest == nullptr)
return IceType_void; return IceType_void;
...@@ -917,6 +952,39 @@ void InstFakeKill::dump(const Cfg *Func) const { ...@@ -917,6 +952,39 @@ void InstFakeKill::dump(const Cfg *Func) const {
Str << "kill.pseudo scratch_regs"; Str << "kill.pseudo scratch_regs";
} }
void InstJumpTable::emit(const Cfg *Func) const {
// TODO(ascull): should this be a target specific lowering (with access built
// in?) and just have InstJumpTable as a high level, similar to br? or should
// this follow the same path as emitIAS i.e. put it in global context and
// produce this code later?
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrEmit();
// TODO(ascull): softcode pointer size of 4
// TODO(ascull): is .long portable?
Str << "\n\t.section\t.rodata." << Func->getFunctionName()
<< "$jumptable,\"a\",@progbits\n"
<< "\t.align 4\n" << getName(Func) << ":";
for (SizeT I = 0; I < NumTargets; ++I)
Str << "\n\t.long\t" << Targets[I]->getAsmName();
Str << "\n\n\t.text";
}
void InstJumpTable::emitIAS(const Cfg *Func) const {
// TODO(ascull): put jump table in the global context for emission later
(void)Func;
}
void InstJumpTable::dump(const Cfg *Func) const {
if (!BuildDefs::dump())
return;
Ostream &Str = Func->getContext()->getStrDump();
Str << "jump table [";
for (SizeT I = 0; I < NumTargets; ++I)
Str << "\n " << Targets[I]->getName();
Str << "\n ]";
}
void InstTarget::dump(const Cfg *Func) const { void InstTarget::dump(const Cfg *Func) const {
if (!BuildDefs::dump()) if (!BuildDefs::dump())
return; return;
......
...@@ -67,6 +67,7 @@ public: ...@@ -67,6 +67,7 @@ public:
FakeDef, // not part of LLVM/PNaCl bitcode FakeDef, // not part of LLVM/PNaCl bitcode
FakeUse, // not part of LLVM/PNaCl bitcode FakeUse, // not part of LLVM/PNaCl bitcode
FakeKill, // not part of LLVM/PNaCl bitcode FakeKill, // not part of LLVM/PNaCl bitcode
JumpTable, // not part of LLVM/PNaCl bitcode
Target // target-specific low-level ICE Target // target-specific low-level ICE
// Anything >= Target is an InstTarget subclass. // Anything >= Target is an InstTarget subclass.
// Note that the value-spaces are shared across targets. // Note that the value-spaces are shared across targets.
...@@ -108,6 +109,7 @@ public: ...@@ -108,6 +109,7 @@ public:
/// Returns a list of out-edges corresponding to a terminator /// Returns a list of out-edges corresponding to a terminator
/// instruction, which is the last instruction of the block. /// instruction, which is the last instruction of the block.
/// The list must not contain duplicates.
virtual NodeList getTerminatorEdges() const { virtual NodeList getTerminatorEdges() const {
// All valid terminator instructions override this method. For // All valid terminator instructions override this method. For
// the default implementation, we assert in case some CfgNode // the default implementation, we assert in case some CfgNode
...@@ -119,9 +121,8 @@ public: ...@@ -119,9 +121,8 @@ public:
virtual bool isUnconditionalBranch() const { return false; } virtual bool isUnconditionalBranch() const { return false; }
/// If the instruction is a branch-type instruction with OldNode as a /// If the instruction is a branch-type instruction with OldNode as a
/// target, repoint it to NewNode and return true, otherwise return /// target, repoint it to NewNode and return true, otherwise return
/// false. Only repoint one instance, even if the instruction has /// false. Repoint all instances of OldNode as a target.
/// multiple instances of OldNode as a target. virtual bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) {
virtual bool repointEdge(CfgNode *OldNode, CfgNode *NewNode) {
(void)OldNode; (void)OldNode;
(void)NewNode; (void)NewNode;
return false; return false;
...@@ -352,7 +353,7 @@ public: ...@@ -352,7 +353,7 @@ public:
} }
NodeList getTerminatorEdges() const override; NodeList getTerminatorEdges() const override;
bool isUnconditionalBranch() const override { return isUnconditional(); } bool isUnconditionalBranch() const override { return isUnconditional(); }
bool repointEdge(CfgNode *OldNode, CfgNode *NewNode) override; bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) override;
void dump(const Cfg *Func) const override; void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return Inst->getKind() == Br; } static bool classof(const Inst *Inst) { return Inst->getKind() == Br; }
...@@ -727,7 +728,7 @@ public: ...@@ -727,7 +728,7 @@ public:
} }
void addBranch(SizeT CaseIndex, uint64_t Value, CfgNode *Label); void addBranch(SizeT CaseIndex, uint64_t Value, CfgNode *Label);
NodeList getTerminatorEdges() const override; NodeList getTerminatorEdges() const override;
bool repointEdge(CfgNode *OldNode, CfgNode *NewNode) override; bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) override;
void dump(const Cfg *Func) const override; void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return Inst->getKind() == Switch; } static bool classof(const Inst *Inst) { return Inst->getKind() == Switch; }
...@@ -899,6 +900,43 @@ private: ...@@ -899,6 +900,43 @@ private:
const Inst *Linked; const Inst *Linked;
}; };
/// JumpTable instruction. This represents a jump table that will be
/// stored in the .rodata section. This is used to track and repoint
/// the target CfgNodes which may change, for example due to
/// splitting for phi lowering.
class InstJumpTable : public InstHighLevel {
InstJumpTable() = delete;
InstJumpTable(const InstJumpTable &) = delete;
InstJumpTable &operator=(const InstJumpTable &) = delete;
public:
static InstJumpTable *create(Cfg *Func, SizeT NumTargets, CfgNode *Default) {
return new (Func->allocate<InstJumpTable>())
InstJumpTable(Func, NumTargets, Default);
}
void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override;
void addTarget(SizeT TargetIndex, CfgNode *Target) {
assert(TargetIndex < NumTargets);
Targets[TargetIndex] = Target;
}
bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) override;
IceString getName(const Cfg *Func) const;
void dump(const Cfg *Func) const override;
static bool classof(const Inst *Inst) { return Inst->getKind() == JumpTable; }
private:
InstJumpTable(Cfg *Func, SizeT NumTargets, CfgNode *Default);
void destroy(Cfg *Func) override {
Func->deallocateArrayOf<CfgNode *>(Targets);
Inst::destroy(Func);
}
const SizeT LabelNumber;
const SizeT NumTargets;
CfgNode **Targets;
};
/// The Target instruction is the base class for all target-specific /// The Target instruction is the base class for all target-specific
/// instructions. /// instructions.
class InstTarget : public Inst { class InstTarget : public Inst {
......
...@@ -277,15 +277,17 @@ bool InstARM32Br::optimizeBranch(const CfgNode *NextNode) { ...@@ -277,15 +277,17 @@ bool InstARM32Br::optimizeBranch(const CfgNode *NextNode) {
return false; return false;
} }
bool InstARM32Br::repointEdge(CfgNode *OldNode, CfgNode *NewNode) { bool InstARM32Br::repointEdges(CfgNode *OldNode, CfgNode *NewNode) {
bool Found = false;
if (TargetFalse == OldNode) { if (TargetFalse == OldNode) {
TargetFalse = NewNode; TargetFalse = NewNode;
return true; Found = true;
} else if (TargetTrue == OldNode) { }
if (TargetTrue == OldNode) {
TargetTrue = NewNode; TargetTrue = NewNode;
return true; Found = true;
} }
return false; return Found;
} }
InstARM32Call::InstARM32Call(Cfg *Func, Variable *Dest, Operand *CallTarget) InstARM32Call::InstARM32Call(Cfg *Func, Variable *Dest, Operand *CallTarget)
......
...@@ -742,7 +742,7 @@ public: ...@@ -742,7 +742,7 @@ public:
bool isUnconditionalBranch() const override { bool isUnconditionalBranch() const override {
return getPredicate() == CondARM32::AL; return getPredicate() == CondARM32::AL;
} }
bool repointEdge(CfgNode *OldNode, CfgNode *NewNode) override; bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) override;
void emit(const Cfg *Func) const override; void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override; void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override; void dump(const Cfg *Func) const override;
......
...@@ -349,7 +349,7 @@ public: ...@@ -349,7 +349,7 @@ public:
bool isUnconditionalBranch() const override { bool isUnconditionalBranch() const override {
return !Label && Condition == InstX86Base<Machine>::Traits::Cond::Br_None; return !Label && Condition == InstX86Base<Machine>::Traits::Cond::Br_None;
} }
bool repointEdge(CfgNode *OldNode, CfgNode *NewNode) override; bool repointEdges(CfgNode *OldNode, CfgNode *NewNode) override;
void emit(const Cfg *Func) const override; void emit(const Cfg *Func) const override;
void emitIAS(const Cfg *Func) const override; void emitIAS(const Cfg *Func) const override;
void dump(const Cfg *Func) const override; void dump(const Cfg *Func) const override;
......
...@@ -152,15 +152,17 @@ bool InstX86Br<Machine>::optimizeBranch(const CfgNode *NextNode) { ...@@ -152,15 +152,17 @@ bool InstX86Br<Machine>::optimizeBranch(const CfgNode *NextNode) {
} }
template <class Machine> template <class Machine>
bool InstX86Br<Machine>::repointEdge(CfgNode *OldNode, CfgNode *NewNode) { bool InstX86Br<Machine>::repointEdges(CfgNode *OldNode, CfgNode *NewNode) {
bool Found = false;
if (TargetFalse == OldNode) { if (TargetFalse == OldNode) {
TargetFalse = NewNode; TargetFalse = NewNode;
return true; Found = true;
} else if (TargetTrue == OldNode) { }
if (TargetTrue == OldNode) {
TargetTrue = NewNode; TargetTrue = NewNode;
return true; Found = true;
} }
return false; return Found;
} }
template <class Machine> template <class Machine>
......
...@@ -68,8 +68,8 @@ public: ...@@ -68,8 +68,8 @@ public:
Encoded_Not_ByteReg = -1 Encoded_Not_ByteReg = -1
}; };
/// An enum of X87 Stack Registers. The enum value does match the encoding used /// An enum of X87 Stack Registers. The enum value does match the encoding
/// to binary encode register operands in instructions. /// used to binary encode register operands in instructions.
enum X87STRegister { enum X87STRegister {
#define X(val, encode, name) Encoded_##val encode, #define X(val, encode, name) Encoded_##val encode,
X87ST_REGX8632_TABLE X87ST_REGX8632_TABLE
......
//===- subzero/src/IceSwitchLowering.cpp - Switch lowering -----------------==//
//
// The Subzero Code Generator
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// This file implements platform independent analysis of switch cases to
/// improve the generated code.
///
//===----------------------------------------------------------------------===//
#include "IceSwitchLowering.h"
#include "IceTargetLowering.h"
#include <algorithm>
namespace Ice {
CaseClusterArray CaseCluster::clusterizeSwitch(Cfg *Func,
const InstSwitch *Inst) {
CaseClusterArray CaseClusters;
// Load the cases
SizeT NumCases = Inst->getNumCases();
CaseClusters.reserve(NumCases);
for (SizeT I = 0; I < NumCases; ++I)
CaseClusters.emplace_back(Inst->getValue(I), Inst->getLabel(I));
// Sort the cases
std::sort(CaseClusters.begin(), CaseClusters.end(),
[](const CaseCluster &x, const CaseCluster &y) {
return x.High < y.Low;
});
// Merge adjacent case ranges
auto Active = CaseClusters.begin();
std::for_each(Active + 1, CaseClusters.end(),
[&Active](const CaseCluster &x) {
if (!Active->tryAppend(x))
*(++Active) = x;
});
CaseClusters.erase(Active + 1, CaseClusters.end());
// TODO(ascull): Merge in a cycle i.e. -1(=UINTXX_MAX) to 0. This depends on
// the types for correct wrap around behavior.
// A small number of cases is more efficient without a jump table
if (CaseClusters.size() < Func->getTarget()->getMinJumpTableSize())
return CaseClusters;
// Test for a single jump table. This can be done in constant time whereas
// finding the best set of jump table would be quadratic, too slow(?). If
// jump tables were included in the search tree we'd first have to traverse to
// them. Ideally we would have an unbalanced tree which is biased towards
// frequently executed code but we can't do this well without profiling data.
// So, this single jump table is a good starting point where you can get to
// the jump table quickly without figuring out how to unbalance the tree.
uint64_t MaxValue = CaseClusters.back().High;
uint64_t MinValue = CaseClusters.front().Low;
// Don't +1 yet to avoid (INT64_MAX-0)+1 overflow
uint64_t TotalRange = MaxValue - MinValue;
// Might be too sparse for the jump table
if (NumCases * 2 <= TotalRange)
return CaseClusters;
// Unlikely. Would mean can't store size of jump table.
if (TotalRange == UINT64_MAX)
return CaseClusters;
++TotalRange;
// Replace everything with a jump table
InstJumpTable *JumpTable =
InstJumpTable::create(Func, TotalRange, Inst->getLabelDefault());
for (const CaseCluster &Case : CaseClusters)
for (uint64_t I = Case.Low; I <= Case.High; ++I)
JumpTable->addTarget(I - MinValue, Case.Label);
CaseClusters.clear();
CaseClusters.emplace_back(MinValue, MaxValue, JumpTable);
return CaseClusters;
}
bool CaseCluster::tryAppend(const CaseCluster &New) {
// Can only append ranges with the same target and are adjacent
bool CanAppend = this->Label == New.Label && this->High + 1 == New.Low;
if (CanAppend)
this->High = New.High;
return CanAppend;
}
} // end of namespace Ice
//===- subzero/src/IceSwitchLowering.h - Switch lowering --------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief The file contains helpers for switch lowering.
//===----------------------------------------------------------------------===//
#ifndef SUBZERO_SRC_ICESWITCHLOWERING_H
#define SUBZERO_SRC_ICESWITCHLOWERING_H
#include "IceCfgNode.h"
#include "IceInst.h"
namespace Ice {
class CaseCluster;
typedef std::vector<CaseCluster, CfgLocalAllocator<CaseCluster>>
CaseClusterArray;
/// A cluster of cases can be tested by a common method during switch lowering.
class CaseCluster {
CaseCluster() = delete;
public:
enum CaseClusterKind {
Range, /// Numerically adjacent case values with same target.
JumpTable, /// Different targets and possibly sparse.
};
CaseCluster(const CaseCluster &) = default;
CaseCluster &operator=(const CaseCluster &) = default;
/// Create a cluster of a single case represented by a unitary range.
CaseCluster(uint64_t Value, CfgNode *Label)
: Kind(Range), Low(Value), High(Value), Label(Label) {}
/// Create a case consisting of a jump table.
CaseCluster(uint64_t Low, uint64_t High, InstJumpTable *JT)
: Kind(JumpTable), Low(Low), High(High), JT(JT) {}
CaseClusterKind getKind() const { return Kind; }
uint64_t getLow() const { return Low; }
uint64_t getHigh() const { return High; }
CfgNode *getLabel() const {
assert(Kind == Range);
return Label;
}
InstJumpTable *getJumpTable() const {
assert(Kind == JumpTable);
return JT;
}
/// Discover cases which can be clustered together and return the clusters
/// ordered by case value.
static CaseClusterArray clusterizeSwitch(Cfg *Func, const InstSwitch *Inst);
private:
CaseClusterKind Kind;
uint64_t Low;
uint64_t High;
union {
CfgNode *Label; /// Target for a range.
InstJumpTable *JT; /// Jump table targets.
};
/// Try and append a cluster returning whether or not it was successful.
bool tryAppend(const CaseCluster &New);
};
} // end of namespace Ice
#endif // SUBZERO_SRC_ICESWITCHLOWERING_H
...@@ -208,6 +208,10 @@ public: ...@@ -208,6 +208,10 @@ public:
StackAdjustment = SnapshotStackAdjustment; StackAdjustment = SnapshotStackAdjustment;
} }
/// Get the minimum number of clusters required for a jump table to be
/// considered.
virtual SizeT getMinJumpTableSize() const = 0;
virtual void emitVariable(const Variable *Var) const = 0; virtual void emitVariable(const Variable *Var) const = 0;
void emitWithoutPrefix(const ConstantRelocatable *CR) const; void emitWithoutPrefix(const ConstantRelocatable *CR) const;
......
...@@ -78,6 +78,9 @@ public: ...@@ -78,6 +78,9 @@ public:
return (typeWidthInBytes(Ty) + 3) & ~3; return (typeWidthInBytes(Ty) + 3) & ~3;
} }
// TODO(ascull): what size is best for ARM?
SizeT getMinJumpTableSize() const override { return 3; }
void emitVariable(const Variable *Var) const override; void emitVariable(const Variable *Var) const override;
const char *getConstantPrefix() const final { return "#"; } const char *getConstantPrefix() const final { return "#"; }
......
...@@ -53,6 +53,10 @@ public: ...@@ -53,6 +53,10 @@ public:
// i8, and i16 are rounded up to 4 bytes. // i8, and i16 are rounded up to 4 bytes.
return (typeWidthInBytes(Ty) + 3) & ~3; return (typeWidthInBytes(Ty) + 3) & ~3;
} }
// TODO(ascull): what is the best size of MIPS?
SizeT getMinJumpTableSize() const override { return 3; }
void emitVariable(const Variable *Var) const override; void emitVariable(const Variable *Var) const override;
const char *getConstantPrefix() const final { return ""; } const char *getConstantPrefix() const final { return ""; }
......
...@@ -335,7 +335,7 @@ enum _tmp_enum { ...@@ -335,7 +335,7 @@ enum _tmp_enum {
_num _num
}; };
// Define a set of constants based on high-level table entries. // Define a set of constants based on high-level table entries.
#define X(tag, size, align, elts, elty, str) \ #define X(tag, sizeLog2, align, elts, elty, str) \
static const int _table1_##tag = tag; static const int _table1_##tag = tag;
ICETYPE_TABLE ICETYPE_TABLE
#undef X #undef X
...@@ -349,7 +349,7 @@ ICETYPEX8632_TABLE ...@@ -349,7 +349,7 @@ ICETYPEX8632_TABLE
#undef X #undef X
// Repeat the static asserts with respect to the high-level table // Repeat the static asserts with respect to the high-level table
// entries in case the high-level table has extra entries. // entries in case the high-level table has extra entries.
#define X(tag, size, align, elts, elty, str) \ #define X(tag, sizeLog2, align, elts, elty, str) \
static_assert(_table1_##tag == _table2_##tag, \ static_assert(_table1_##tag == _table2_##tag, \
"Inconsistency between ICETYPEX8632_TABLE and ICETYPE_TABLE"); "Inconsistency between ICETYPEX8632_TABLE and ICETYPE_TABLE");
ICETYPE_TABLE ICETYPE_TABLE
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "IceDefs.h" #include "IceDefs.h"
#include "IceInst.h" #include "IceInst.h"
#include "IceSwitchLowering.h"
#include "IceTargetLowering.h" #include "IceTargetLowering.h"
#include <type_traits> #include <type_traits>
...@@ -130,6 +131,8 @@ public: ...@@ -130,6 +131,8 @@ public:
return (typeWidthInBytes(Ty) + 3) & ~3; return (typeWidthInBytes(Ty) + 3) & ~3;
} }
SizeT getMinJumpTableSize() const override { return 4; }
void emitVariable(const Variable *Var) const override; void emitVariable(const Variable *Var) const override;
const char *getConstantPrefix() const final { return "$"; } const char *getConstantPrefix() const final { return "$"; }
...@@ -204,6 +207,19 @@ protected: ...@@ -204,6 +207,19 @@ protected:
void lowerCountZeros(bool Cttz, Type Ty, Variable *Dest, Operand *FirstVal, void lowerCountZeros(bool Cttz, Type Ty, Variable *Dest, Operand *FirstVal,
Operand *SecondVal); Operand *SecondVal);
/// Check the comparison is in [Min,Max]. The flags register will be modified
/// with:
/// - below equal, if in range
/// - above, set if not in range
/// The index into the range is returned.
Operand *lowerCmpRange(Operand *Comparison, uint64_t Min, uint64_t Max);
/// Lowering of a cluster of switch cases. If the case is not matched control
/// will pass to the default label provided. If the default label is nullptr
/// then control will fall through to the next instruction. DoneCmp should be
/// true if the flags contain the result of a comparison with the Comparison.
void lowerCaseCluster(const CaseCluster &Case, Operand *Src0, bool DoneCmp,
CfgNode *DefaultLabel = nullptr);
typedef void (TargetX86Base::*LowerBinOp)(Variable *, Operand *); typedef void (TargetX86Base::*LowerBinOp)(Variable *, Operand *);
void expandAtomicRMWAsCmpxchg(LowerBinOp op_lo, LowerBinOp op_hi, void expandAtomicRMWAsCmpxchg(LowerBinOp op_lo, LowerBinOp op_hi,
Variable *Dest, Operand *Ptr, Operand *Val); Variable *Dest, Operand *Ptr, Operand *Val);
......
...@@ -30,7 +30,7 @@ const char *TargetArchName[] = { ...@@ -30,7 +30,7 @@ const char *TargetArchName[] = {
// Define a temporary set of enum values based on ICETYPE_TABLE // Define a temporary set of enum values based on ICETYPE_TABLE
enum { enum {
#define X(tag, size, align, elts, elty, str) _table_tag_##tag, #define X(tag, sizeLog2, align, elts, elty, str) _table_tag_##tag,
ICETYPE_TABLE ICETYPE_TABLE
#undef X #undef X
_enum_table_tag_Names _enum_table_tag_Names
...@@ -44,7 +44,7 @@ enum { ...@@ -44,7 +44,7 @@ enum {
_enum_props_table_tag_Names _enum_props_table_tag_Names
}; };
// Assert that tags in ICETYPE_TABLE are also in ICETYPE_PROPS_TABLE. // Assert that tags in ICETYPE_TABLE are also in ICETYPE_PROPS_TABLE.
#define X(tag, size, align, elts, elty, str) \ #define X(tag, sizeLog2, align, elts, elty, str) \
static_assert( \ static_assert( \
(unsigned)_table_tag_##tag == (unsigned)_props_table_tag_##tag, \ (unsigned)_table_tag_##tag == (unsigned)_props_table_tag_##tag, \
"Inconsistency between ICETYPE_PROPS_TABLE and ICETYPE_TABLE"); "Inconsistency between ICETYPE_PROPS_TABLE and ICETYPE_TABLE");
...@@ -63,7 +63,7 @@ ICETYPE_PROPS_TABLE ...@@ -63,7 +63,7 @@ ICETYPE_PROPS_TABLE
// Define constants for each element size in ICETYPE_TABLE. // Define constants for each element size in ICETYPE_TABLE.
enum { enum {
#define X(tag, size, align, elts, elty, str) _table_elts_##tag = elts, #define X(tag, sizeLog2, align, elts, elty, str) _table_elts_##tag = elts,
ICETYPE_TABLE ICETYPE_TABLE
#undef X #undef X
_enum_table_elts_Elements = 0 _enum_table_elts_Elements = 0
...@@ -83,7 +83,7 @@ ICETYPE_PROPS_TABLE ...@@ -83,7 +83,7 @@ ICETYPE_PROPS_TABLE
#undef X #undef X
struct TypeAttributeFields { struct TypeAttributeFields {
size_t TypeWidthInBytes; int8_t TypeWidthInBytesLog2;
size_t TypeAlignInBytes; size_t TypeAlignInBytes;
size_t TypeNumElements; size_t TypeNumElements;
Type TypeElementType; Type TypeElementType;
...@@ -91,8 +91,8 @@ struct TypeAttributeFields { ...@@ -91,8 +91,8 @@ struct TypeAttributeFields {
}; };
const struct TypeAttributeFields TypeAttributes[] = { const struct TypeAttributeFields TypeAttributes[] = {
#define X(tag, size, align, elts, elty, str) \ #define X(tag, sizeLog2, align, elts, elty, str) \
{ size, align, elts, elty, str } \ { sizeLog2, align, elts, elty, str } \
, ,
ICETYPE_TABLE ICETYPE_TABLE
#undef X #undef X
...@@ -133,10 +133,15 @@ const char *targetArchString(const TargetArch Arch) { ...@@ -133,10 +133,15 @@ const char *targetArchString(const TargetArch Arch) {
} }
size_t typeWidthInBytes(Type Ty) { size_t typeWidthInBytes(Type Ty) {
int8_t Shift = typeWidthInBytesLog2(Ty);
return (Shift < 0) ? 0 : 1 << Shift;
}
int8_t typeWidthInBytesLog2(Type Ty) {
size_t Index = static_cast<size_t>(Ty); size_t Index = static_cast<size_t>(Ty);
if (Index < IceType_NUM) if (Index < IceType_NUM)
return TypeAttributes[Index].TypeWidthInBytes; return TypeAttributes[Index].TypeWidthInBytesLog2;
llvm_unreachable("Invalid type for typeWidthInBytes()"); llvm_unreachable("Invalid type for typeWidthInBytesLog2()");
return 0; return 0;
} }
......
...@@ -30,24 +30,24 @@ ...@@ -30,24 +30,24 @@
//#define X(tag, str, is_elf64, e_machine, e_flags) //#define X(tag, str, is_elf64, e_machine, e_flags)
#define ICETYPE_TABLE \ #define ICETYPE_TABLE \
/* enum value, size, align, # elts, element type, printable string */ \ /* enum value, log_2(size), align, # elts, element type, printable */ \
/* (size and alignment in bytes) */ \ /* string (size and alignment in bytes) */ \
X(IceType_void, 0, 0, 1, IceType_void, "void") \ X(IceType_void, -1, 0, 1, IceType_void, "void") \
X(IceType_i1, 1, 1, 1, IceType_i1, "i1") \ X(IceType_i1, 0, 1, 1, IceType_i1, "i1") \
X(IceType_i8, 1, 1, 1, IceType_i8, "i8") \ X(IceType_i8, 0, 1, 1, IceType_i8, "i8") \
X(IceType_i16, 2, 1, 1, IceType_i16, "i16") \ X(IceType_i16, 1, 1, 1, IceType_i16, "i16") \
X(IceType_i32, 4, 1, 1, IceType_i32, "i32") \ X(IceType_i32, 2, 1, 1, IceType_i32, "i32") \
X(IceType_i64, 8, 1, 1, IceType_i64, "i64") \ X(IceType_i64, 3, 1, 1, IceType_i64, "i64") \
X(IceType_f32, 4, 4, 1, IceType_f32, "float") \ X(IceType_f32, 2, 4, 1, IceType_f32, "float") \
X(IceType_f64, 8, 8, 1, IceType_f64, "double") \ X(IceType_f64, 3, 8, 1, IceType_f64, "double") \
X(IceType_v4i1, 16, 1, 4, IceType_i1, "<4 x i1>") \ X(IceType_v4i1, 4, 1, 4, IceType_i1, "<4 x i1>") \
X(IceType_v8i1, 16, 1, 8, IceType_i1, "<8 x i1>") \ X(IceType_v8i1, 4, 1, 8, IceType_i1, "<8 x i1>") \
X(IceType_v16i1, 16, 1, 16, IceType_i1, "<16 x i1>") \ X(IceType_v16i1, 4, 1, 16, IceType_i1, "<16 x i1>") \
X(IceType_v16i8, 16, 1, 16, IceType_i8, "<16 x i8>") \ X(IceType_v16i8, 4, 1, 16, IceType_i8, "<16 x i8>") \
X(IceType_v8i16, 16, 2, 8, IceType_i16, "<8 x i16>") \ X(IceType_v8i16, 4, 2, 8, IceType_i16, "<8 x i16>") \
X(IceType_v4i32, 16, 4, 4, IceType_i32, "<4 x i32>") \ X(IceType_v4i32, 4, 4, 4, IceType_i32, "<4 x i32>") \
X(IceType_v4f32, 16, 4, 4, IceType_f32, "<4 x float>") \ X(IceType_v4f32, 4, 4, 4, IceType_f32, "<4 x float>") \
//#define X(tag, size, align, elts, elty, str) //#define X(tag, sizeLog2, align, elts, elty, str)
// Dictionary: // Dictionary:
// V - Is vector type. // V - Is vector type.
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
namespace Ice { namespace Ice {
enum Type { enum Type {
#define X(tag, size, align, elts, elty, str) tag, #define X(tag, sizeLog2, align, elts, elty, str) tag,
ICETYPE_TABLE ICETYPE_TABLE
#undef X #undef X
IceType_NUM IceType_NUM
...@@ -60,6 +60,7 @@ enum TargetInstructionSet { ...@@ -60,6 +60,7 @@ enum TargetInstructionSet {
enum OptLevel { Opt_m1, Opt_0, Opt_1, Opt_2 }; enum OptLevel { Opt_m1, Opt_0, Opt_1, Opt_2 };
size_t typeWidthInBytes(Type Ty); size_t typeWidthInBytes(Type Ty);
int8_t typeWidthInBytesLog2(Type Ty);
size_t typeAlignInBytes(Type Ty); size_t typeAlignInBytes(Type Ty);
size_t typeNumElements(Type Ty); size_t typeNumElements(Type Ty);
Type typeElementType(Type Ty); Type typeElementType(Type Ty);
......
; This tests the advanced lowering of switch statements. The advanced lowering
; uses jump tables, range tests and binary search.
; RUN: %p2i -i %s --filetype=asm --assemble --disassemble --args --adv-switch \
; RUN: -O2 | FileCheck %s
; Dense but non-continuous ranges should be converted into a jump table.
define internal i32 @testJumpTable(i32 %a) {
entry:
switch i32 %a, label %sw.default [
i32 91, label %sw.default
i32 92, label %sw.bb1
i32 93, label %sw.default
i32 99, label %sw.bb1
i32 98, label %sw.default
i32 96, label %sw.bb1
i32 97, label %sw.epilog
]
sw.default:
%add = add i32 %a, 27
br label %sw.epilog
sw.bb1:
%tmp = add i32 %a, 16
br label %sw.epilog
sw.epilog:
%result.1 = phi i32 [ %add, %sw.default ], [ %tmp, %sw.bb1 ], [ 17, %entry ]
ret i32 %result.1
}
; CHECK-LABEL: testJumpTable
; CHECK: sub [[IND:[^,]+]],0x5b
; CHECK-NEXT: cmp [[IND]],0x8
; CHECK-NEXT: ja
; CHECK-NEXT: mov [[BASE:[^,]+]],0x0 {{[0-9a-f]+}}: R_386_32 .rodata.testJumpTable$jumptable
; CHECK-NEXT: mov {{.*}},DWORD PTR {{\[}}[[BASE]]+[[IND]]*4]
; CHECK-NEXT: jmp
; Continuous ranges which map to the same target should be grouped and
; efficiently tested.
define internal i32 @testRangeTest() {
entry:
switch i32 10, label %sw.default [
i32 0, label %sw.epilog
i32 1, label %sw.epilog
i32 2, label %sw.epilog
i32 3, label %sw.epilog
i32 10, label %sw.bb1
i32 11, label %sw.bb1
i32 12, label %sw.bb1
i32 13, label %sw.bb1
]
sw.default:
br label %sw.epilog
sw.bb1:
br label %sw.epilog
sw.epilog:
%result.1 = phi i32 [ 23, %sw.default ], [ 42, %sw.bb1 ], [ 17, %entry ], [ 17, %entry ], [ 17, %entry ], [ 17, %entry ]
ret i32 %result.1
}
; CHECK-LABEL: testRangeTest
; CHECK: cmp {{.*}},0x3
; CHECK-NEXT: jbe
; CHECK: sub [[REG:[^,]*]],0xa
; CHECK-NEXT: cmp [[REG]],0x3
; CHECK-NEXT: jbe
; CHECK-NEXT: jmp
; Sparse cases should be searched with a binary search.
define internal i32 @testBinarySearch() {
entry:
switch i32 10, label %sw.default [
i32 0, label %sw.epilog
i32 10, label %sw.epilog
i32 20, label %sw.bb1
i32 30, label %sw.bb1
]
sw.default:
br label %sw.epilog
sw.bb1:
br label %sw.epilog
sw.epilog:
%result.1 = phi i32 [ 23, %sw.default ], [ 42, %sw.bb1 ], [ 17, %entry ], [ 17, %entry ]
ret i32 %result.1
}
; CHECK-LABEL: testBinarySearch
; CHECK: cmp {{.*}},0x14
; CHECK-NEXT: jb
; CHECK-NEXT: je
; CHECK-NEXT: cmp {{.*}},0x1e
; CHECK-NEXT: je
; CHECK-NEXT: jmp
; CHECK-NEXT: cmp {{.*}},0x0
; CHECK-NEXT: je
; CHECK-NEXT: cmp {{.*}},0xa
; CHECK-NEXT: je
; CHECK-NEXT: jmp
; 64-bit switches where the cases are all 32-bit values should be reduced to a
; 32-bit switch after checking the top byte is 0.
define internal i32 @testSwitchSmall64(i64 %a) {
entry:
switch i64 %a, label %sw.default [
i64 123, label %return
i64 234, label %sw.bb1
i64 345, label %sw.bb2
i64 456, label %sw.bb3
]
sw.bb1:
br label %return
sw.bb2:
br label %return
sw.bb3:
br label %return
sw.default:
br label %return
return:
%retval.0 = phi i32 [ 5, %sw.default ], [ 4, %sw.bb3 ], [ 3, %sw.bb2 ], [ 2, %sw.bb1 ], [ 1, %entry ]
ret i32 %retval.0
}
; CHECK-LABEL: testSwitchSmall64
; CHECK: cmp {{.*}},0x0
; CHECK-NEXT: jne
; CHECK-NEXT: cmp {{.*}},0x159
; CHECK-NEXT: jb
; CHECK-NEXT: je
; CHECK-NEXT: cmp {{.*}},0x1c8
; CHECK-NEXT: je
; CHECK-NEXT: jmp
; CHECK-NEXT: cmp {{.*}},0x7b
; CHECK-NEXT: je
; CHECK-NEXT: cmp {{.*}},0xea
; CHECK-NEXT: je
; CHECK-NEXT: jmp
; Test for correct 64-bit lowering.
; TODO(ascull): this should generate better code like the 32-bit version
define internal i32 @testSwitch64(i64 %a) {
entry:
switch i64 %a, label %sw.default [
i64 123, label %return
i64 234, label %sw.bb1
i64 345, label %sw.bb2
i64 78187493520, label %sw.bb3
]
sw.bb1:
br label %return
sw.bb2:
br label %return
sw.bb3:
br label %return
sw.default:
br label %return
return:
%retval.0 = phi i32 [ 5, %sw.default ], [ 4, %sw.bb3 ], [ 3, %sw.bb2 ], [ 2, %sw.bb1 ], [ 1, %entry ]
ret i32 %retval.0
}
; CHECK-LABEL: testSwitch64
; CHECK: cmp {{.*}},0x7b
; CHECK-NEXT: jne
; CHECK-NEXT: cmp {{.*}},0x0
; CHECK-NEXT: je
; CHECK: cmp {{.*}},0xea
; CHECK-NEXT: jne
; CHECK-NEXT: cmp {{.*}},0x0
; CHECK-NEXT: je
; CHECK: cmp {{.*}},0x159
; CHECK-NEXT: jne
; CHECK-NEXT: cmp {{.*}},0x0
; CHECK-NEXT: je
; CHECK: cmp {{.*}},0x34567890
; CHECK-NEXT: jne
; CHECK-NEXT: cmp {{.*}},0x12
; CHECK-NEXT: je
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment