[llvm] [IR] Don't store switch case values as operands (PR #170984)
Alexis Engelke via llvm-commits
llvm-commits at lists.llvm.org
Thu Dec 11 08:01:37 PST 2025
https://github.com/aengelke updated https://github.com/llvm/llvm-project/pull/170984
>From 4b1113a8a760373aa8867dbdb2000a63e93cacf5 Mon Sep 17 00:00:00 2001
From: Alexis Engelke <engelke at in.tum.de>
Date: Sat, 6 Dec 2025 12:36:53 +0000
Subject: [PATCH 1/3] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20in?=
=?UTF-8?q?itial=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Created using spr 1.3.5-bogner
---
llvm/docs/ReleaseNotes.md | 2 +
llvm/include/llvm-c/Core.h | 24 ++++++
llvm/include/llvm/IR/Instructions.h | 35 +++++----
llvm/include/llvm/IR/User.h | 8 +-
llvm/lib/Bitcode/Writer/ValueEnumerator.cpp | 8 ++
llvm/lib/CodeGen/TypePromotion.cpp | 8 ++
llvm/lib/IR/Core.cpp | 13 ++++
llvm/lib/IR/Instruction.cpp | 8 ++
llvm/lib/IR/Instructions.cpp | 33 ++++----
llvm/lib/IR/User.cpp | 20 ++---
llvm/lib/IR/Verifier.cpp | 2 +-
llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp | 17 ++---
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 15 +++-
.../Transforms/SimplifyCFG/switch-dedup.ll | 76 +++++++++++++++++++
llvm/unittests/IR/InstructionsTest.cpp | 23 ++++++
llvm/unittests/IR/VerifierTest.cpp | 3 -
16 files changed, 236 insertions(+), 59 deletions(-)
create mode 100644 llvm/test/Transforms/SimplifyCFG/switch-dedup.ll
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index dc0cec6122cdf..5ffad71abaf35 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -74,6 +74,7 @@ Changes to the LLVM IR
format string function implementations from statically-linked libc's based on
the requirements of each call. Currently only `float` is supported; this can
keep floating point support out of printf if it can be proven unused.
+* Case values are no longer operands of `SwitchInst`.
Changes to LLVM infrastructure
------------------------------
@@ -178,6 +179,7 @@ Changes to the C API
* Add `LLVMGetOrInsertFunction` to get or insert a function, replacing the combination of `LLVMGetNamedFunction` and `LLVMAddFunction`.
* Allow `LLVMGetVolatile` to work with any kind of Instruction.
* Add `LLVMConstFPFromBits` to get a constant floating-point value from an array of 64 bit values.
+* Add `LLVMGetSwitchCaseValue` and `LLVMSetSwitchCaseValue` to get and set switch case values; switch case values are no longer operands of the instruction.
Changes to the CodeGen infrastructure
-------------------------------------
diff --git a/llvm/include/llvm-c/Core.h b/llvm/include/llvm-c/Core.h
index fc41b5835d6eb..0074f1aad5a3c 100644
--- a/llvm/include/llvm-c/Core.h
+++ b/llvm/include/llvm-c/Core.h
@@ -4213,6 +4213,30 @@ LLVM_C_ABI void LLVMSetCondition(LLVMValueRef Branch, LLVMValueRef Cond);
*/
LLVM_C_ABI LLVMBasicBlockRef LLVMGetSwitchDefaultDest(LLVMValueRef SwitchInstr);
+/**
+ * Obtain the case value for a successor of a switch instruction. i corresponds
+ * to the successor index. The first successor is the default destination, so i
+ * must be greater than zero.
+ *
+ * This only works on llvm::SwitchInst instructions.
+ *
+ * @see llvm::SwitchInst::CaseHandle::getCaseValue()
+ */
+LLVM_C_ABI LLVMValueRef LLVMGetSwitchCaseValue(LLVMValueRef SwitchInstr,
+ unsigned i);
+
+/**
+ * Set the case value for a successor of a switch instruction. i corresponds to
+ * the successor index. The first successor is the default destination, so i
+ * must be greater than zero.
+ *
+ * This only works on llvm::SwitchInst instructions.
+ *
+ * @see llvm::SwitchInst::CaseHandle::setValue()
+ */
+LLVM_C_ABI void LLVMSetSwitchCaseValue(LLVMValueRef SwitchInstr, unsigned i,
+ LLVMValueRef CaseValue);
+
/**
* @}
*/
diff --git a/llvm/include/llvm/IR/Instructions.h b/llvm/include/llvm/IR/Instructions.h
index 8bd060ae8f485..80e9cb5bd806b 100644
--- a/llvm/include/llvm/IR/Instructions.h
+++ b/llvm/include/llvm/IR/Instructions.h
@@ -2665,7 +2665,7 @@ class PHINode : public Instruction {
// User::allocHungoffUses, because we have to allocate Uses for the incoming
// values and pointers to the incoming blocks, all in one allocation.
void allocHungoffUses(unsigned N) {
- User::allocHungoffUses(N, /* IsPhi */ true);
+ User::allocHungoffUses(N, /*WithExtraValues=*/true);
}
public:
@@ -3198,10 +3198,10 @@ class SwitchInst : public Instruction {
unsigned ReservedSpace;
- // Operand[0] = Value to switch on
- // Operand[1] = Default basic block destination
- // Operand[2n ] = Value to match
- // Operand[2n+1] = BasicBlock to go to on match
+ // Operand[0] = Value to switch on
+ // Operand[1] = Default basic block destination
+ // Operand[n] = BasicBlock to go to on match
+ // Values are stored after the Uses similar to PHINode's basic blocks.
SwitchInst(const SwitchInst &SI);
/// Create a new switch instruction, specifying a value to switch on and a
@@ -3223,6 +3223,17 @@ class SwitchInst : public Instruction {
LLVM_ABI SwitchInst *cloneImpl() const;
+ void allocHungoffUses(unsigned N) {
+ User::allocHungoffUses(N, /*WithExtraValues=*/true);
+ }
+
+ ConstantInt *const *case_values() const {
+ return reinterpret_cast<ConstantInt *const *>(op_begin() + ReservedSpace);
+ }
+ ConstantInt **case_values() {
+ return reinterpret_cast<ConstantInt **>(op_begin() + ReservedSpace);
+ }
+
public:
void operator delete(void *Ptr) { User::operator delete(Ptr); }
@@ -3257,7 +3268,7 @@ class SwitchInst : public Instruction {
ConstantIntT *getCaseValue() const {
assert((unsigned)Index < SI->getNumCases() &&
"Index out the number of cases.");
- return reinterpret_cast<ConstantIntT *>(SI->getOperand(2 + Index * 2));
+ return SI->case_values()[Index];
}
/// Resolves successor for current case.
@@ -3299,7 +3310,7 @@ class SwitchInst : public Instruction {
void setValue(ConstantInt *V) const {
assert((unsigned)Index < SI->getNumCases() &&
"Index out the number of cases.");
- SI->setOperand(2 + Index*2, reinterpret_cast<Value*>(V));
+ SI->case_values()[Index] = V;
}
/// Sets the new successor for current case.
@@ -3406,9 +3417,7 @@ class SwitchInst : public Instruction {
/// Return the number of 'cases' in this switch instruction, excluding the
/// default case.
- unsigned getNumCases() const {
- return getNumOperands()/2 - 1;
- }
+ unsigned getNumCases() const { return getNumOperands() - 2; }
/// Returns a read/write iterator that points to the first case in the
/// SwitchInst.
@@ -3510,14 +3519,14 @@ class SwitchInst : public Instruction {
/// case.
LLVM_ABI CaseIt removeCase(CaseIt I);
- unsigned getNumSuccessors() const { return getNumOperands()/2; }
+ unsigned getNumSuccessors() const { return getNumOperands() - 1; }
BasicBlock *getSuccessor(unsigned idx) const {
assert(idx < getNumSuccessors() &&"Successor idx out of range for switch!");
- return cast<BasicBlock>(getOperand(idx*2+1));
+ return cast<BasicBlock>(getOperand(idx + 1));
}
void setSuccessor(unsigned idx, BasicBlock *NewSucc) {
assert(idx < getNumSuccessors() && "Successor # out of range for switch!");
- setOperand(idx * 2 + 1, NewSucc);
+ setOperand(idx + 1, NewSucc);
}
// Methods for support type inquiry through isa, cast, and dyn_cast:
diff --git a/llvm/include/llvm/IR/User.h b/llvm/include/llvm/IR/User.h
index cbb4379b68c41..394ea70d6637e 100644
--- a/llvm/include/llvm/IR/User.h
+++ b/llvm/include/llvm/IR/User.h
@@ -132,13 +132,13 @@ class User : public Value {
/// Allocate the array of Uses, followed by a pointer
/// (with bottom bit set) to the User.
- /// \param IsPhi identifies callers which are phi nodes and which need
- /// N BasicBlock* allocated along with N
- LLVM_ABI void allocHungoffUses(unsigned N, bool IsPhi = false);
+ /// \param WithExtraValues identifies callers which need N Value* allocated
+ /// along the N operands.
+ LLVM_ABI void allocHungoffUses(unsigned N, bool WithExtraValues = false);
/// Grow the number of hung off uses. Note that allocHungoffUses
/// should be called if there are no uses.
- LLVM_ABI void growHungoffUses(unsigned N, bool IsPhi = false);
+ LLVM_ABI void growHungoffUses(unsigned N, bool WithExtraValues = false);
protected:
~User() = default; // Use deleteValue() to delete a generic Instruction.
diff --git a/llvm/lib/Bitcode/Writer/ValueEnumerator.cpp b/llvm/lib/Bitcode/Writer/ValueEnumerator.cpp
index 36d0d35d024cc..4d5188cf7a0ce 100644
--- a/llvm/lib/Bitcode/Writer/ValueEnumerator.cpp
+++ b/llvm/lib/Bitcode/Writer/ValueEnumerator.cpp
@@ -164,6 +164,10 @@ static OrderMap orderModule(const Module &M) {
orderConstantValue(Op);
if (auto *SVI = dyn_cast<ShuffleVectorInst>(&I))
orderValue(SVI->getShuffleMaskForBitcode(), OM);
+ if (auto *SI = dyn_cast<SwitchInst>(&I)) {
+ for (const auto &Case : SI->cases())
+ orderValue(Case.getCaseValue(), OM);
+ }
orderValue(&I, OM);
}
}
@@ -1092,6 +1096,10 @@ void ValueEnumerator::incorporateFunction(const Function &F) {
}
if (auto *SVI = dyn_cast<ShuffleVectorInst>(&I))
EnumerateValue(SVI->getShuffleMaskForBitcode());
+ if (auto *SI = dyn_cast<SwitchInst>(&I)) {
+ for (const auto &Case : SI->cases())
+ EnumerateValue(Case.getCaseValue());
+ }
}
BasicBlocks.push_back(&BB);
ValueMap[&BB] = BasicBlocks.size();
diff --git a/llvm/lib/CodeGen/TypePromotion.cpp b/llvm/lib/CodeGen/TypePromotion.cpp
index e9fa78eabff7c..0865597dadcd6 100644
--- a/llvm/lib/CodeGen/TypePromotion.cpp
+++ b/llvm/lib/CodeGen/TypePromotion.cpp
@@ -512,6 +512,14 @@ void IRPromoter::PromoteTree() {
I->setOperand(i, ConstantInt::get(ExtTy, 0));
}
+ // For switch, also mutate case values, which are not operands.
+ if (auto *SI = dyn_cast<SwitchInst>(I)) {
+ for (auto Case : SI->cases()) {
+ APInt NewConst = Case.getCaseValue()->getValue().zext(PromotedWidth);
+ Case.setValue(ConstantInt::get(SI->getContext(), NewConst));
+ }
+ }
+
// Mutate the result type, unless this is an icmp or switch.
if (!isa<ICmpInst>(I) && !isa<SwitchInst>(I)) {
I->mutateType(ExtTy);
diff --git a/llvm/lib/IR/Core.cpp b/llvm/lib/IR/Core.cpp
index bea30649947c7..e0427e9c3d8b1 100644
--- a/llvm/lib/IR/Core.cpp
+++ b/llvm/lib/IR/Core.cpp
@@ -3257,6 +3257,19 @@ LLVMBasicBlockRef LLVMGetSwitchDefaultDest(LLVMValueRef Switch) {
return wrap(unwrap<SwitchInst>(Switch)->getDefaultDest());
}
+LLVMValueRef LLVMGetSwitchCaseValue(LLVMValueRef Switch, unsigned i) {
+ assert(i > 0 && i <= unwrap<SwitchInst>(Switch)->getNumCases());
+ auto It = unwrap<SwitchInst>(Switch)->case_begin() + (i - 1);
+ return wrap(It->getCaseValue());
+}
+
+void LLVMSetSwitchCaseValue(LLVMValueRef Switch, unsigned i,
+ LLVMValueRef CaseValue) {
+ assert(i > 0 && i <= unwrap<SwitchInst>(Switch)->getNumCases());
+ auto It = unwrap<SwitchInst>(Switch)->case_begin() + (i - 1);
+ It->setValue(unwrap<ConstantInt>(CaseValue));
+}
+
/*--.. Operations on alloca instructions (only) ............................--*/
LLVMTypeRef LLVMGetAllocatedType(LLVMValueRef Alloca) {
diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp
index 33ca46ca1c2c6..b1fa86aff239e 100644
--- a/llvm/lib/IR/Instruction.cpp
+++ b/llvm/lib/IR/Instruction.cpp
@@ -975,6 +975,14 @@ bool Instruction::isIdenticalToWhenDefined(const Instruction *I,
return equal(Phi->blocks(), OtherPhi->blocks());
}
+ if (const SwitchInst *SI = dyn_cast<SwitchInst>(this)) {
+ const SwitchInst *OtherSI = cast<SwitchInst>(I);
+ for (auto [Case, OtherCase] : zip(SI->cases(), OtherSI->cases()))
+ if (Case.getCaseValue() != OtherCase.getCaseValue())
+ return false;
+ return true;
+ }
+
return this->hasSameSpecialState(I, /*IgnoreAlignment=*/false,
IntersectAttrs);
}
diff --git a/llvm/lib/IR/Instructions.cpp b/llvm/lib/IR/Instructions.cpp
index 85d3690dd8306..db0d5af83655f 100644
--- a/llvm/lib/IR/Instructions.cpp
+++ b/llvm/lib/IR/Instructions.cpp
@@ -202,7 +202,7 @@ void PHINode::growOperands() {
if (NumOps < 2) NumOps = 2; // 2 op PHI nodes are VERY common.
ReservedSpace = NumOps;
- growHungoffUses(ReservedSpace, /* IsPhi */ true);
+ growHungoffUses(ReservedSpace, /*WithExtraValues=*/true);
}
/// hasConstantValue - If the specified PHI node always merges together the same
@@ -4076,7 +4076,7 @@ SwitchInst::SwitchInst(Value *Value, BasicBlock *Default, unsigned NumCases,
InsertPosition InsertBefore)
: Instruction(Type::getVoidTy(Value->getContext()), Instruction::Switch,
AllocMarker, InsertBefore) {
- init(Value, Default, 2+NumCases*2);
+ init(Value, Default, 2 + NumCases);
}
SwitchInst::SwitchInst(const SwitchInst &SI)
@@ -4084,10 +4084,12 @@ SwitchInst::SwitchInst(const SwitchInst &SI)
init(SI.getCondition(), SI.getDefaultDest(), SI.getNumOperands());
setNumHungOffUseOperands(SI.getNumOperands());
Use *OL = getOperandList();
+ ConstantInt **VL = case_values();
const Use *InOL = SI.getOperandList();
- for (unsigned i = 2, E = SI.getNumOperands(); i != E; i += 2) {
+ ConstantInt *const *InVL = SI.case_values();
+ for (unsigned i = 2, E = SI.getNumOperands(); i != E; ++i) {
OL[i] = InOL[i];
- OL[i+1] = InOL[i+1];
+ VL[i - 2] = InVL[i - 2];
}
SubclassOptionalData = SI.SubclassOptionalData;
}
@@ -4097,11 +4099,11 @@ SwitchInst::SwitchInst(const SwitchInst &SI)
void SwitchInst::addCase(ConstantInt *OnVal, BasicBlock *Dest) {
unsigned NewCaseIdx = getNumCases();
unsigned OpNo = getNumOperands();
- if (OpNo+2 > ReservedSpace)
+ if (OpNo + 1 > ReservedSpace)
growOperands(); // Get more space!
// Initialize some new operands.
- assert(OpNo+1 < ReservedSpace && "Growing didn't work!");
- setNumHungOffUseOperands(OpNo+2);
+ assert(OpNo < ReservedSpace && "Growing didn't work!");
+ setNumHungOffUseOperands(OpNo + 1);
CaseHandle Case(this, NewCaseIdx);
Case.setValue(OnVal);
Case.setSuccessor(Dest);
@@ -4112,21 +4114,22 @@ void SwitchInst::addCase(ConstantInt *OnVal, BasicBlock *Dest) {
SwitchInst::CaseIt SwitchInst::removeCase(CaseIt I) {
unsigned idx = I->getCaseIndex();
- assert(2 + idx*2 < getNumOperands() && "Case index out of range!!!");
+ assert(2 + idx < getNumOperands() && "Case index out of range!!!");
unsigned NumOps = getNumOperands();
Use *OL = getOperandList();
+ ConstantInt **VL = case_values();
// Overwrite this case with the end of the list.
- if (2 + (idx + 1) * 2 != NumOps) {
- OL[2 + idx * 2] = OL[NumOps - 2];
- OL[2 + idx * 2 + 1] = OL[NumOps - 1];
+ if (2 + idx + 1 != NumOps) {
+ OL[2 + idx] = OL[NumOps - 1];
+ VL[idx] = VL[NumOps - 2 - 1];
}
// Nuke the last value.
- OL[NumOps-2].set(nullptr);
- OL[NumOps-2+1].set(nullptr);
- setNumHungOffUseOperands(NumOps-2);
+ OL[NumOps - 1].set(nullptr);
+ VL[NumOps - 2 - 1] = nullptr;
+ setNumHungOffUseOperands(NumOps - 1);
return CaseIt(this, idx);
}
@@ -4139,7 +4142,7 @@ void SwitchInst::growOperands() {
unsigned NumOps = e*3;
ReservedSpace = NumOps;
- growHungoffUses(ReservedSpace);
+ growHungoffUses(ReservedSpace, /*WithExtraValues=*/true);
}
void SwitchInstProfUpdateWrapper::init() {
diff --git a/llvm/lib/IR/User.cpp b/llvm/lib/IR/User.cpp
index 9bb7c1298593a..1847c29d9ea4f 100644
--- a/llvm/lib/IR/User.cpp
+++ b/llvm/lib/IR/User.cpp
@@ -50,16 +50,16 @@ bool User::replaceUsesOfWith(Value *From, Value *To) {
// User allocHungoffUses Implementation
//===----------------------------------------------------------------------===//
-void User::allocHungoffUses(unsigned N, bool IsPhi) {
+void User::allocHungoffUses(unsigned N, bool WithExtraValues) {
assert(HasHungOffUses && "alloc must have hung off uses");
- static_assert(alignof(Use) >= alignof(BasicBlock *),
+ static_assert(alignof(Use) >= alignof(Value *),
"Alignment is insufficient for 'hung-off-uses' pieces");
// Allocate the array of Uses
size_t size = N * sizeof(Use);
- if (IsPhi)
- size += N * sizeof(BasicBlock *);
+ if (WithExtraValues)
+ size += N * sizeof(Value *);
Use *Begin = static_cast<Use*>(::operator new(size));
Use *End = Begin + N;
setOperandList(Begin);
@@ -67,7 +67,7 @@ void User::allocHungoffUses(unsigned N, bool IsPhi) {
new (Begin) Use(this);
}
-void User::growHungoffUses(unsigned NewNumUses, bool IsPhi) {
+void User::growHungoffUses(unsigned NewNumUses, bool WithExtraValues) {
assert(HasHungOffUses && "realloc must have hung off uses");
unsigned OldNumUses = getNumOperands();
@@ -77,22 +77,22 @@ void User::growHungoffUses(unsigned NewNumUses, bool IsPhi) {
assert(NewNumUses > OldNumUses && "realloc must grow num uses");
Use *OldOps = getOperandList();
- allocHungoffUses(NewNumUses, IsPhi);
+ allocHungoffUses(NewNumUses, WithExtraValues);
Use *NewOps = getOperandList();
// Now copy from the old operands list to the new one.
std::copy(OldOps, OldOps + OldNumUses, NewOps);
- // If this is a Phi, then we need to copy the BB pointers too.
- if (IsPhi) {
+ // If the User has extra values (phi basic blocks, switch case values), then
+ // we need to copy these, too.
+ if (WithExtraValues) {
auto *OldPtr = reinterpret_cast<char *>(OldOps + OldNumUses);
auto *NewPtr = reinterpret_cast<char *>(NewOps + NewNumUses);
- std::copy(OldPtr, OldPtr + (OldNumUses * sizeof(BasicBlock *)), NewPtr);
+ std::copy(OldPtr, OldPtr + (OldNumUses * sizeof(Value *)), NewPtr);
}
Use::zap(OldOps, OldOps + OldNumUses, true);
}
-
// This is a private struct used by `User` to track the co-allocated descriptor
// section.
struct DescriptorInfo {
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index 439b3859fd3ac..778feec6a2da0 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -3424,7 +3424,7 @@ void Verifier::visitSwitchInst(SwitchInst &SI) {
Type *SwitchTy = SI.getCondition()->getType();
SmallPtrSet<ConstantInt*, 32> Constants;
for (auto &Case : SI.cases()) {
- Check(isa<ConstantInt>(SI.getOperand(Case.getCaseIndex() * 2 + 2)),
+ Check(isa<ConstantInt>(Case.getCaseValue()),
"Case value is not a constant integer.", &SI);
Check(Case.getCaseValue()->getType() == SwitchTy,
"Switch constants must all be same type as switch value!", &SI);
diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
index eea49bfdaf04b..4081b6b526771 100644
--- a/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
+++ b/llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
@@ -1539,19 +1539,18 @@ void SPIRVEmitIntrinsics::useRoundingMode(ConstrainedFPIntrinsic *FPI,
Instruction *SPIRVEmitIntrinsics::visitSwitchInst(SwitchInst &I) {
BasicBlock *ParentBB = I.getParent();
+ Function *F = ParentBB->getParent();
IRBuilder<> B(ParentBB);
B.SetInsertPoint(&I);
SmallVector<Value *, 4> Args;
SmallVector<BasicBlock *> BBCases;
- for (auto &Op : I.operands()) {
- if (Op.get()->getType()->isSized()) {
- Args.push_back(Op);
- } else if (BasicBlock *BB = dyn_cast<BasicBlock>(Op.get())) {
- BBCases.push_back(BB);
- Args.push_back(BlockAddress::get(BB->getParent(), BB));
- } else {
- report_fatal_error("Unexpected switch operand");
- }
+ Args.push_back(I.getCondition());
+ BBCases.push_back(I.getDefaultDest());
+ Args.push_back(BlockAddress::get(F, I.getDefaultDest()));
+ for (auto &Case : I.cases()) {
+ Args.push_back(Case.getCaseValue());
+ BBCases.push_back(Case.getCaseSuccessor());
+ Args.push_back(BlockAddress::get(F, Case.getCaseSuccessor()));
}
CallInst *NewI = B.CreateIntrinsic(Intrinsic::spv_switch,
{I.getOperand(0)->getType()}, {Args});
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index ed2a5c292fa54..a91c2e2119e56 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1900,12 +1900,19 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
// so does not add any new instructions.
SmallVector<BasicBlock *> Succs = to_vector(successors(BB));
// Check if sizes and terminators of all successors match.
- bool AllSame = none_of(Succs, [&Succs](BasicBlock *Succ) {
+ bool AllSame = all_of(drop_begin(Succs), [&Succs](BasicBlock *Succ) {
Instruction *Term0 = Succs[0]->getTerminator();
Instruction *Term = Succ->getTerminator();
- return !Term->isSameOperationAs(Term0) ||
- !equal(Term->operands(), Term0->operands()) ||
- Succs[0]->size() != Succ->size();
+ if (!Term->isSameOperationAs(Term0) || Succs[0]->size() != Succ->size())
+ return false;
+ if (auto *SI = dyn_cast<SwitchInst>(Term)) {
+ // Switch case values also need to be equal.
+ auto *SI0 = cast<SwitchInst>(Term0);
+ for (auto [Case, Case0] : zip(SI->cases(), SI0->cases()))
+ if (Case.getCaseValue() != Case0.getCaseValue())
+ return false;
+ }
+ return equal(Term->operands(), Term0->operands());
});
if (!AllSame)
return false;
diff --git a/llvm/test/Transforms/SimplifyCFG/switch-dedup.ll b/llvm/test/Transforms/SimplifyCFG/switch-dedup.ll
new file mode 100644
index 0000000000000..1a8115e3ca386
--- /dev/null
+++ b/llvm/test/Transforms/SimplifyCFG/switch-dedup.ll
@@ -0,0 +1,76 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
+; RUN: opt < %s -passes=simplifycfg -S | FileCheck %s
+
+define i32 @nodedup(i1 %c, i16 %v) {
+; CHECK-LABEL: define i32 @nodedup(
+; CHECK-SAME: i1 [[C:%.*]], i16 [[V:%.*]]) {
+; CHECK-NEXT: br i1 [[C]], label %[[B1:.*]], label %[[B2:.*]]
+; CHECK: [[B1]]:
+; CHECK-NEXT: switch i16 [[V]], label %[[RET2:.*]] [
+; CHECK-NEXT: i16 1962, label %[[COMMON_RET:.*]]
+; CHECK-NEXT: i16 2000, label %[[COMMON_RET]]
+; CHECK-NEXT: i16 1963, label %[[COMMON_RET]]
+; CHECK-NEXT: ]
+; CHECK: [[B2]]:
+; CHECK-NEXT: switch i16 [[V]], label %[[RET2]] [
+; CHECK-NEXT: i16 2766, label %[[COMMON_RET]]
+; CHECK-NEXT: i16 2798, label %[[COMMON_RET]]
+; CHECK-NEXT: i16 2767, label %[[COMMON_RET]]
+; CHECK-NEXT: ]
+; CHECK: [[COMMON_RET]]:
+; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi i32 [ 3, %[[RET2]] ], [ 1, %[[B2]] ], [ 1, %[[B2]] ], [ 1, %[[B2]] ], [ 1, %[[B1]] ], [ 1, %[[B1]] ], [ 1, %[[B1]] ]
+; CHECK-NEXT: ret i32 [[COMMON_RET_OP]]
+; CHECK: [[RET2]]:
+; CHECK-NEXT: br label %[[COMMON_RET]]
+;
+ br i1 %c, label %b1, label %b2
+b1:
+ switch i16 %v, label %ret2 [
+ i16 1962, label %ret1
+ i16 2000, label %ret1
+ i16 1963, label %ret1
+ ]
+b2:
+ switch i16 %v, label %ret2 [
+ i16 2766, label %ret1
+ i16 2798, label %ret1
+ i16 2767, label %ret1
+ ]
+ret1:
+ ret i32 1
+ret2:
+ ret i32 3
+}
+
+define i32 @dedup(i1 %c, i16 %v) {
+; CHECK-LABEL: define i32 @dedup(
+; CHECK-SAME: i1 [[C:%.*]], i16 [[V:%.*]]) {
+; CHECK-NEXT: switch i16 [[V]], label %[[RET2:.*]] [
+; CHECK-NEXT: i16 2766, label %[[COMMON_RET:.*]]
+; CHECK-NEXT: i16 2798, label %[[COMMON_RET]]
+; CHECK-NEXT: i16 2767, label %[[COMMON_RET]]
+; CHECK-NEXT: ]
+; CHECK: [[COMMON_RET]]:
+; CHECK-NEXT: [[COMMON_RET_OP:%.*]] = phi i32 [ 3, %[[RET2]] ], [ 1, [[TMP0:%.*]] ], [ 1, [[TMP0]] ], [ 1, [[TMP0]] ]
+; CHECK-NEXT: ret i32 [[COMMON_RET_OP]]
+; CHECK: [[RET2]]:
+; CHECK-NEXT: br label %[[COMMON_RET]]
+;
+ br i1 %c, label %b1, label %b2
+b1:
+ switch i16 %v, label %ret2 [
+ i16 2766, label %ret1
+ i16 2798, label %ret1
+ i16 2767, label %ret1
+ ]
+b2:
+ switch i16 %v, label %ret2 [
+ i16 2766, label %ret1
+ i16 2798, label %ret1
+ i16 2767, label %ret1
+ ]
+ret1:
+ ret i32 1
+ret2:
+ ret i32 3
+}
diff --git a/llvm/unittests/IR/InstructionsTest.cpp b/llvm/unittests/IR/InstructionsTest.cpp
index f4693bfb1a4d1..f7517a732e7f6 100644
--- a/llvm/unittests/IR/InstructionsTest.cpp
+++ b/llvm/unittests/IR/InstructionsTest.cpp
@@ -968,6 +968,29 @@ TEST(InstructionsTest, SwitchInst) {
const auto &Handle = *CCI;
EXPECT_EQ(1, Handle.getCaseValue()->getSExtValue());
EXPECT_EQ(BB1.get(), Handle.getCaseSuccessor());
+
+ // C API tests.
+ EXPECT_EQ(BB0.get(), unwrap(LLVMGetSwitchDefaultDest(wrap(SI))));
+ EXPECT_EQ(BB0.get(), unwrap(LLVMGetSuccessor(wrap(SI), 0)));
+ EXPECT_EQ(BB1.get(), unwrap(LLVMGetSuccessor(wrap(SI), 1)));
+ EXPECT_EQ(
+ 1,
+ unwrap<ConstantInt>(LLVMGetSwitchCaseValue(wrap(SI), 1))->getSExtValue());
+ EXPECT_EQ(BB2.get(), unwrap(LLVMGetSuccessor(wrap(SI), 2)));
+ EXPECT_EQ(
+ 2,
+ unwrap<ConstantInt>(LLVMGetSwitchCaseValue(wrap(SI), 2))->getSExtValue());
+ EXPECT_EQ(BB3.get(), unwrap(LLVMGetSuccessor(wrap(SI), 3)));
+ EXPECT_EQ(
+ 3,
+ unwrap<ConstantInt>(LLVMGetSwitchCaseValue(wrap(SI), 3))->getSExtValue());
+ // Test case value modification. The C API provides case value indices
+ // matching the successor indices.
+ LLVMSetSwitchCaseValue(wrap(SI), 2, wrap(ConstantInt::get(Int32Ty, 12)));
+ EXPECT_EQ(12, (SI->case_begin() + 1)->getCaseValue()->getSExtValue());
+ EXPECT_EQ(
+ 12,
+ unwrap<ConstantInt>(LLVMGetSwitchCaseValue(wrap(SI), 2))->getSExtValue());
}
TEST(InstructionsTest, SwitchInstProfUpdateWrapper) {
diff --git a/llvm/unittests/IR/VerifierTest.cpp b/llvm/unittests/IR/VerifierTest.cpp
index 440db1216edc9..49fad197d12d7 100644
--- a/llvm/unittests/IR/VerifierTest.cpp
+++ b/llvm/unittests/IR/VerifierTest.cpp
@@ -329,9 +329,6 @@ TEST(VerifierTest, SwitchInst) {
Switch->addCase(ConstantInt::get(Int32Ty, 2), OnTwo);
EXPECT_FALSE(verifyFunction(*F));
- // set one case value to function argument.
- Switch->setOperand(2, F->getArg(1));
- EXPECT_TRUE(verifyFunction(*F));
}
TEST(VerifierTest, CrossFunctionRef) {
>From 4f5b0f81333acf4c9ad637a3d207cf0e6d0f19e8 Mon Sep 17 00:00:00 2001
From: Alexis Engelke <engelke at in.tum.de>
Date: Thu, 11 Dec 2025 15:24:27 +0000
Subject: [PATCH 2/3] move to hasSameSpecialState and adjust FunctionComparator
Created using spr 1.3.5-bogner
---
llvm/lib/IR/Instruction.cpp | 16 ++---
.../Transforms/Utils/FunctionComparator.cpp | 6 ++
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 17 +----
.../Transforms/IPO/MergeFunctionsTest.cpp | 72 ++++++++++++++++++-
4 files changed, 87 insertions(+), 24 deletions(-)
diff --git a/llvm/lib/IR/Instruction.cpp b/llvm/lib/IR/Instruction.cpp
index b1fa86aff239e..8f66c6c3a4fc8 100644
--- a/llvm/lib/IR/Instruction.cpp
+++ b/llvm/lib/IR/Instruction.cpp
@@ -865,7 +865,7 @@ const char *Instruction::getOpcodeName(unsigned OpCode) {
}
/// This must be kept in sync with FunctionComparator::cmpOperations in
-/// lib/Transforms/IPO/MergeFunctions.cpp.
+/// lib/Transforms/Utils/FunctionComparator.cpp.
bool Instruction::hasSameSpecialState(const Instruction *I2,
bool IgnoreAlignment,
bool IntersectAttrs) const {
@@ -913,6 +913,12 @@ bool Instruction::hasSameSpecialState(const Instruction *I2,
return CI->getCallingConv() == cast<CallBrInst>(I2)->getCallingConv() &&
CheckAttrsSame(CI, cast<CallBrInst>(I2)) &&
CI->hasIdenticalOperandBundleSchema(*cast<CallBrInst>(I2));
+ if (const SwitchInst *SI = dyn_cast<SwitchInst>(I1)) {
+ for (auto [Case1, Case2] : zip(SI->cases(), cast<SwitchInst>(I2)->cases()))
+ if (Case1.getCaseValue() != Case2.getCaseValue())
+ return false;
+ return true;
+ }
if (const InsertValueInst *IVI = dyn_cast<InsertValueInst>(I1))
return IVI->getIndices() == cast<InsertValueInst>(I2)->getIndices();
if (const ExtractValueInst *EVI = dyn_cast<ExtractValueInst>(I1))
@@ -975,14 +981,6 @@ bool Instruction::isIdenticalToWhenDefined(const Instruction *I,
return equal(Phi->blocks(), OtherPhi->blocks());
}
- if (const SwitchInst *SI = dyn_cast<SwitchInst>(this)) {
- const SwitchInst *OtherSI = cast<SwitchInst>(I);
- for (auto [Case, OtherCase] : zip(SI->cases(), OtherSI->cases()))
- if (Case.getCaseValue() != OtherCase.getCaseValue())
- return false;
- return true;
- }
-
return this->hasSameSpecialState(I, /*IgnoreAlignment=*/false,
IntersectAttrs);
}
diff --git a/llvm/lib/Transforms/Utils/FunctionComparator.cpp b/llvm/lib/Transforms/Utils/FunctionComparator.cpp
index 6d4026e8209de..7cf19177db545 100644
--- a/llvm/lib/Transforms/Utils/FunctionComparator.cpp
+++ b/llvm/lib/Transforms/Utils/FunctionComparator.cpp
@@ -724,6 +724,12 @@ int FunctionComparator::cmpOperations(const Instruction *L,
return cmpMDNode(L->getMetadata(LLVMContext::MD_range),
R->getMetadata(LLVMContext::MD_range));
}
+ if (const SwitchInst *SI = dyn_cast<SwitchInst>(L)) {
+ for (auto [LCase, RCase] : zip(SI->cases(), cast<SwitchInst>(R)->cases()))
+ if (int Res = cmpConstants(LCase.getCaseValue(), RCase.getCaseValue()))
+ return Res;
+ return 0;
+ }
if (const InsertValueInst *IVI = dyn_cast<InsertValueInst>(L)) {
ArrayRef<unsigned> LIndices = IVI->getIndices();
ArrayRef<unsigned> RIndices = cast<InsertValueInst>(R)->getIndices();
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index 0389a0444a63c..ad707423f0810 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1904,21 +1904,10 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
// so does not add any new instructions.
// Check if sizes and terminators of all successors match.
+ Instruction *Term0 = UniqueSuccessors[0]->getTerminator();
bool AllSame =
- all_of(UniqueSuccessors, [&UniqueSuccessors](BasicBlock *Succ) {
- Instruction *Term0 = UniqueSuccessors[0]->getTerminator();
- Instruction *Term = Succ->getTerminator();
- if (!Term->isSameOperationAs(Term0) ||
- UniqueSuccessors[0]->size() != Succ->size())
- return false;
- if (auto *SI = dyn_cast<SwitchInst>(Term)) {
- // Switch case values also need to be equal.
- auto *SI0 = cast<SwitchInst>(Term0);
- for (auto [Case, Case0] : zip(SI->cases(), SI0->cases()))
- if (Case.getCaseValue() != Case0.getCaseValue())
- return false;
- }
- return equal(Term->operands(), Term0->operands());
+ all_of(drop_begin(UniqueSuccessors), [Term0](BasicBlock *Succ) {
+ return Succ->getTerminator()->isIdenticalTo(Term0);
});
if (!AllSame)
return false;
diff --git a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
index 56d119878a9ab..bce7d64fe8d3b 100644
--- a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
+++ b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
@@ -243,4 +243,74 @@ TEST(MergeFunctions, FalseOutputFunctionsTest) {
EXPECT_EQ(MergeResult.size(), 0u);
}
-} // namespace
\ No newline at end of file
+TEST(MergeFunctions, SwitchMergeTrue) {
+ LLVMContext Ctx;
+ SMDiagnostic Err;
+ std::unique_ptr<Module> M1(parseAssemblyString(R"invalid(
+ @exp = global [2 x ptr] [ptr @fn1, ptr @fn2]
+ define internal i32 @fn1(i32 %a) unnamed_addr {
+ switch i32 %a, label %b1 [
+ i32 1, label %b2
+ i32 2, label %b3
+ ]
+ b1:
+ ret i32 1
+ b2:
+ ret i32 2
+ b3:
+ ret i32 3
+ }
+ define internal i32 @fn2(i32 %a) unnamed_addr {
+ switch i32 %a, label %b1 [
+ i32 1, label %b2
+ i32 2, label %b3
+ ]
+ b1:
+ ret i32 1
+ b2:
+ ret i32 2
+ b3:
+ ret i32 3
+ }
+ )invalid",
+ Err, Ctx));
+
+ EXPECT_TRUE(MergeFunctionsPass::runOnModule(*M1)); // Merged fn1 and fn2.
+}
+
+TEST(MergeFunctions, SwitchMergeFalse) {
+ LLVMContext Ctx;
+ SMDiagnostic Err;
+ std::unique_ptr<Module> M1(parseAssemblyString(R"invalid(
+ @exp = global [2 x ptr] [ptr @fn1, ptr @fn2]
+ define internal i32 @fn1(i32 %a) unnamed_addr {
+ switch i32 %a, label %b1 [
+ i32 1, label %b2
+ i32 2, label %b3
+ ]
+ b1:
+ ret i32 1
+ b2:
+ ret i32 2
+ b3:
+ ret i32 3
+ }
+ define internal i32 @fn2(i32 %a) unnamed_addr {
+ switch i32 %a, label %b1 [
+ i32 1000, label %b2
+ i32 2, label %b3
+ ]
+ b1:
+ ret i32 1
+ b2:
+ ret i32 2
+ b3:
+ ret i32 3
+ }
+ )invalid",
+ Err, Ctx));
+
+ EXPECT_FALSE(MergeFunctionsPass::runOnModule(*M1)); // No merge.
+}
+
+} // namespace
>From dc1efde7ef147a425426ab0d0b054169ae434780 Mon Sep 17 00:00:00 2001
From: Alexis Engelke <engelke at in.tum.de>
Date: Thu, 11 Dec 2025 16:01:21 +0000
Subject: [PATCH 3/3] IR test + fix SimplifyCFG size comparison
Created using spr 1.3.5-bogner
---
llvm/lib/Transforms/Utils/SimplifyCFG.cpp | 6 +-
llvm/test/Transforms/MergeFunc/switch.ll | 79 +++++++++++++++++++
.../Transforms/IPO/MergeFunctionsTest.cpp | 72 +----------------
3 files changed, 84 insertions(+), 73 deletions(-)
create mode 100644 llvm/test/Transforms/MergeFunc/switch.ll
diff --git a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
index ad707423f0810..cecbd8072a40a 100644
--- a/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
+++ b/llvm/lib/Transforms/Utils/SimplifyCFG.cpp
@@ -1904,10 +1904,12 @@ bool SimplifyCFGOpt::hoistCommonCodeFromSuccessors(Instruction *TI,
// so does not add any new instructions.
// Check if sizes and terminators of all successors match.
+ unsigned Size0 = UniqueSuccessors[0]->size();
Instruction *Term0 = UniqueSuccessors[0]->getTerminator();
bool AllSame =
- all_of(drop_begin(UniqueSuccessors), [Term0](BasicBlock *Succ) {
- return Succ->getTerminator()->isIdenticalTo(Term0);
+ all_of(drop_begin(UniqueSuccessors), [Term0, Size0](BasicBlock *Succ) {
+ return Succ->getTerminator()->isIdenticalTo(Term0) &&
+ Succ->size() == Size0;
});
if (!AllSame)
return false;
diff --git a/llvm/test/Transforms/MergeFunc/switch.ll b/llvm/test/Transforms/MergeFunc/switch.ll
new file mode 100644
index 0000000000000..9175d9d829d59
--- /dev/null
+++ b/llvm/test/Transforms/MergeFunc/switch.ll
@@ -0,0 +1,79 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -S -passes=mergefunc < %s | FileCheck %s
+
+define internal i32 @fn1(i32 %a) {
+; CHECK-LABEL: @fn1(
+; CHECK-NEXT: switch i32 [[A:%.*]], label [[B1:%.*]] [
+; CHECK-NEXT: i32 1, label [[B2:%.*]]
+; CHECK-NEXT: i32 2, label [[B3:%.*]]
+; CHECK-NEXT: ]
+; CHECK: b1:
+; CHECK-NEXT: ret i32 1
+; CHECK: b2:
+; CHECK-NEXT: ret i32 2
+; CHECK: b3:
+; CHECK-NEXT: ret i32 3
+;
+ switch i32 %a, label %b1 [
+ i32 1, label %b2
+ i32 2, label %b3
+ ]
+b1:
+ ret i32 1
+b2:
+ ret i32 2
+b3:
+ ret i32 3
+}
+
+define internal i32 @fn2(i32 %a) {
+ switch i32 %a, label %b1 [
+ i32 1, label %b2
+ i32 2, label %b3
+ ]
+b1:
+ ret i32 1
+b2:
+ ret i32 2
+b3:
+ ret i32 3
+}
+
+; Different switch case value, don't merge.
+define internal i32 @fn3(i32 %a) {
+; CHECK-LABEL: @fn3(
+; CHECK-NEXT: switch i32 [[A:%.*]], label [[B1:%.*]] [
+; CHECK-NEXT: i32 1000, label [[B2:%.*]]
+; CHECK-NEXT: i32 2, label [[B3:%.*]]
+; CHECK-NEXT: ]
+; CHECK: b1:
+; CHECK-NEXT: ret i32 1
+; CHECK: b2:
+; CHECK-NEXT: ret i32 2
+; CHECK: b3:
+; CHECK-NEXT: ret i32 3
+;
+ switch i32 %a, label %b1 [
+ i32 1000, label %b2
+ i32 2, label %b3
+ ]
+b1:
+ ret i32 1
+b2:
+ ret i32 2
+b3:
+ ret i32 3
+}
+
+define void @caller(i32 %a) {
+; CHECK-LABEL: @caller(
+; CHECK-NEXT: [[R1:%.*]] = call i32 @fn1(i32 [[A:%.*]])
+; CHECK-NEXT: [[R2:%.*]] = call i32 @fn1(i32 [[A]])
+; CHECK-NEXT: [[R3:%.*]] = call i32 @fn3(i32 [[A]])
+; CHECK-NEXT: ret void
+;
+ %r1 = call i32 @fn1(i32 %a)
+ %r2 = call i32 @fn2(i32 %a)
+ %r3 = call i32 @fn3(i32 %a)
+ ret void
+}
diff --git a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
index bce7d64fe8d3b..56d119878a9ab 100644
--- a/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
+++ b/llvm/unittests/Transforms/IPO/MergeFunctionsTest.cpp
@@ -243,74 +243,4 @@ TEST(MergeFunctions, FalseOutputFunctionsTest) {
EXPECT_EQ(MergeResult.size(), 0u);
}
-TEST(MergeFunctions, SwitchMergeTrue) {
- LLVMContext Ctx;
- SMDiagnostic Err;
- std::unique_ptr<Module> M1(parseAssemblyString(R"invalid(
- @exp = global [2 x ptr] [ptr @fn1, ptr @fn2]
- define internal i32 @fn1(i32 %a) unnamed_addr {
- switch i32 %a, label %b1 [
- i32 1, label %b2
- i32 2, label %b3
- ]
- b1:
- ret i32 1
- b2:
- ret i32 2
- b3:
- ret i32 3
- }
- define internal i32 @fn2(i32 %a) unnamed_addr {
- switch i32 %a, label %b1 [
- i32 1, label %b2
- i32 2, label %b3
- ]
- b1:
- ret i32 1
- b2:
- ret i32 2
- b3:
- ret i32 3
- }
- )invalid",
- Err, Ctx));
-
- EXPECT_TRUE(MergeFunctionsPass::runOnModule(*M1)); // Merged fn1 and fn2.
-}
-
-TEST(MergeFunctions, SwitchMergeFalse) {
- LLVMContext Ctx;
- SMDiagnostic Err;
- std::unique_ptr<Module> M1(parseAssemblyString(R"invalid(
- @exp = global [2 x ptr] [ptr @fn1, ptr @fn2]
- define internal i32 @fn1(i32 %a) unnamed_addr {
- switch i32 %a, label %b1 [
- i32 1, label %b2
- i32 2, label %b3
- ]
- b1:
- ret i32 1
- b2:
- ret i32 2
- b3:
- ret i32 3
- }
- define internal i32 @fn2(i32 %a) unnamed_addr {
- switch i32 %a, label %b1 [
- i32 1000, label %b2
- i32 2, label %b3
- ]
- b1:
- ret i32 1
- b2:
- ret i32 2
- b3:
- ret i32 3
- }
- )invalid",
- Err, Ctx));
-
- EXPECT_FALSE(MergeFunctionsPass::runOnModule(*M1)); // No merge.
-}
-
-} // namespace
+} // namespace
\ No newline at end of file
More information about the llvm-commits
mailing list