[OptBisect][ADT] Add support for running ranges of passes and introduce IntegerInclusiveInterval ADT (#152393)

This PR does two things:
1. Introduce `-opt-bisect=<inclusive_integer_interval>` for running
ranges of passes, where `inclusive_integer_interval` could be:
- `-1` to run all pases
- `0` to run no passes
- `10-12` to run passes 10 through 12
- `10-12,15-18 `to run passes 10 through 10 and 15 through 18 

`-opt-bisect-limit=N` now maps to `-opt-bisect=1-N`

2. Introduces `IntegerInclusiveInterval` in ADT to represent these
intervals and the ability to parse string ranges (from the command
line). The motivation to move this into ADT is that both `opt-bisect`
and `debug-counter` need to parse intervals from the command line.
`reduce-chunk-list` also relied on a interval object, so I refactored
that to use `IntegerInclusiveInterval` as well.
This commit is contained in:
Yonah Goldberg 2026-01-05 11:56:40 -08:00 committed by GitHub
parent c371721586
commit 7f9a00e59c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 774 additions and 141 deletions

View File

@ -17,7 +17,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/Support/Compiler.h"
#include <limits>
#include "llvm/Support/IntegerInclusiveInterval.h"
namespace llvm {
@ -40,7 +40,7 @@ public:
/// This class implements a mechanism to disable passes and individual
/// optimizations at compile time based on a command line option
/// (-opt-bisect-limit) in order to perform a bisecting search for
/// (-opt-bisect) in order to perform a bisecting search for
/// optimization-related problems.
class LLVM_ABI OptBisect : public OptPassGate {
public:
@ -53,12 +53,12 @@ public:
~OptBisect() override = default;
/// Checks the bisect limit to determine if the specified pass should run.
/// Checks the bisect intervals to determine if the specified pass should run.
///
/// The method prints the name of the pass, its assigned bisect number, and
/// whether or not the pass will be executed. It returns true if the pass
/// should run, i.e. if the bisect limit is set to -1 or has not yet been
/// exceeded.
/// should run, i.e. if no intervals are specified or the current pass number
/// falls within one of the specified intervals.
///
/// Most passes should not call this routine directly. Instead, it is called
/// through helper routines provided by the base classes of the pass. For
@ -67,20 +67,22 @@ public:
StringRef IRDescription) const override;
/// isEnabled() should return true before calling shouldRunPass().
bool isEnabled() const override { return BisectLimit != Disabled; }
bool isEnabled() const override { return !BisectIntervals.empty(); }
/// Set the new optimization limit and reset the counter. Passing
/// OptBisect::Disabled disables the limiting.
void setLimit(int Limit) {
BisectLimit = Limit;
/// Set intervals directly from an IntervalList.
void setIntervals(IntegerInclusiveIntervalUtils::IntervalList Intervals) {
BisectIntervals = std::move(Intervals);
}
/// Clear all intervals, effectively disabling bisection.
void clearIntervals() {
BisectIntervals.clear();
LastBisectNum = 0;
}
static constexpr int Disabled = std::numeric_limits<int>::max();
private:
int BisectLimit = Disabled;
mutable int LastBisectNum = 0;
IntegerInclusiveIntervalUtils::IntervalList BisectIntervals;
};
/// This class implements a mechanism to disable passes and individual

View File

@ -17,7 +17,7 @@
/// debug. That is where debug counting steps in. You can instrument the pass
/// with a debug counter before it does a certain thing, and depending on the
/// counts, it will either execute that thing or not. The debug counter itself
/// consists of a list of chunks (inclusive numeric ranges). `shouldExecute`
/// consists of a list of chunks (inclusive numeric intervals). `shouldExecute`
/// returns true iff the list is empty or the current count is in one of the
/// chunks.
///
@ -48,6 +48,7 @@
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/IntegerInclusiveInterval.h"
#include <string>
namespace llvm {
@ -56,13 +57,6 @@ class raw_ostream;
class DebugCounter {
public:
struct Chunk {
int64_t Begin;
int64_t End;
LLVM_ABI void print(llvm::raw_ostream &OS);
bool contains(int64_t Idx) const { return Idx >= Begin && Idx <= End; }
};
/// Struct to store counter info.
class CounterInfo {
friend class DebugCounter;
@ -78,7 +72,7 @@ public:
uint64_t CurrChunkIdx = 0;
StringRef Name;
StringRef Desc;
SmallVector<Chunk> Chunks;
IntegerInclusiveIntervalUtils::IntervalList Chunks;
public:
CounterInfo(StringRef Name, StringRef Desc) : Name(Name), Desc(Desc) {
@ -86,11 +80,13 @@ public:
}
};
LLVM_ABI static void printChunks(raw_ostream &OS, ArrayRef<Chunk>);
LLVM_ABI static void
printChunks(raw_ostream &OS, ArrayRef<IntegerInclusiveInterval> Intervals);
/// Return true on parsing error and print the error message on the
/// llvm::errs()
LLVM_ABI static bool parseChunks(StringRef Str, SmallVector<Chunk> &Res);
LLVM_ABI static bool
parseChunks(StringRef Str, IntegerInclusiveIntervalUtils::IntervalList &Res);
/// Returns a reference to the singleton instance.
LLVM_ABI static DebugCounter &instance();

View File

@ -0,0 +1,115 @@
//===- IntegerInclusiveInterval.h -------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file defines the IntegerInclusiveInterval class and utilities for
// handling lists of inclusive integer intervals, such as parsing interval
// strings like "1-10,20-30,45", which are used in debugging and bisection
// tools.
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_SUPPORT_INTEGER_INCLUSIVE_INTERVAL_H
#define LLVM_SUPPORT_INTEGER_INCLUSIVE_INTERVAL_H
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Error.h"
#include <cassert>
#include <cstdint>
namespace llvm {
class raw_ostream;
} // end namespace llvm
namespace llvm {
/// Represents an inclusive integer interval [Begin, End] where Begin <= End.
class IntegerInclusiveInterval {
int64_t Begin;
int64_t End;
public:
/// Create an interval [Begin, End].
IntegerInclusiveInterval(int64_t Begin, int64_t End)
: Begin(Begin), End(End) {
assert(Begin <= End && "Interval Begin must be <= End");
}
/// Create a singleton interval [Single, Single].
IntegerInclusiveInterval(int64_t Single) : Begin(Single), End(Single) {}
int64_t getBegin() const { return Begin; }
int64_t getEnd() const { return End; }
void setBegin(int64_t NewBegin) {
assert(NewBegin <= End && "Interval Begin must be <= End");
Begin = NewBegin;
}
void setEnd(int64_t NewEnd) {
assert(Begin <= NewEnd && "Interval Begin must be <= End");
End = NewEnd;
}
/// Check if the given value is within this interval (inclusive).
bool contains(int64_t Value) const { return Value >= Begin && Value <= End; }
/// Check if this interval overlaps with another interval.
bool overlaps(const IntegerInclusiveInterval &Other) const {
return Begin <= Other.End && End >= Other.Begin;
}
/// Print the interval to the output stream.
void print(raw_ostream &OS) const {
if (Begin == End)
OS << Begin;
else
OS << Begin << "-" << End;
}
bool operator==(const IntegerInclusiveInterval &Other) const {
return Begin == Other.Begin && End == Other.End;
}
};
namespace IntegerInclusiveIntervalUtils {
/// A list of integer intervals.
using IntervalList = SmallVector<IntegerInclusiveInterval, 8>;
/// Parse a interval specification string like "1-10,20-30,45" or
/// "1-10:20-30:45". Intervals must be in increasing order and non-overlapping.
/// \param IntervalStr The string to parse.
/// \param Separator The separator character to use (',' or ':').
/// \returns Expected<IntervalList> containing the parsed intervals on success,
/// or an Error on failure.
Expected<IntervalList> parseIntervals(StringRef IntervalStr,
char Separator = ',');
/// Check if a value is contained in any of the intervals.
bool contains(ArrayRef<IntegerInclusiveInterval> Intervals, int64_t Value);
/// Print intervals to output stream.
/// \param OS The output stream to print to.
/// \param Intervals The intervals to print.
/// \param Separator The separator character to use between intervals (i.e. ','
/// or
/// ':').
void printIntervals(raw_ostream &OS,
ArrayRef<IntegerInclusiveInterval> Intervals,
char Separator = ',');
/// Merge adjacent/consecutive intervals into single intervals.
/// Example: [1-3, 4-6, 8-10] -> [1-6, 8-10].
IntervalList
mergeAdjacentIntervals(ArrayRef<IntegerInclusiveInterval> Intervals);
} // end namespace IntegerInclusiveIntervalUtils
} // end namespace llvm
#endif // LLVM_SUPPORT_INTEGER_INCLUSIVE_INTERVAL_H

View File

@ -13,10 +13,13 @@
//===----------------------------------------------------------------------===//
#include "llvm/IR/OptBisect.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/IntegerInclusiveInterval.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <cstdlib>
using namespace llvm;
@ -30,12 +33,50 @@ static OptDisable &getOptDisabler() {
return OptDisabler;
}
static cl::opt<int> OptBisectLimit("opt-bisect-limit", cl::Hidden,
cl::init(OptBisect::Disabled), cl::Optional,
cl::cb<void, int>([](int Limit) {
getOptBisector().setLimit(Limit);
}),
cl::desc("Maximum optimization to perform"));
static cl::opt<int> OptBisectLimit(
"opt-bisect-limit", cl::Hidden, cl::init(-1), cl::Optional,
cl::cb<void, int>([](int Limit) {
if (Limit == -1)
// -1 means run all passes.
getOptBisector().setIntervals({{1, std::numeric_limits<int>::max()}});
else if (Limit == 0)
// 0 means run no passes.
getOptBisector().setIntervals({{0, 0}});
else if (Limit > 0)
// Convert limit to interval 1-Limit.
getOptBisector().setIntervals({{1, Limit}});
else
llvm_unreachable(
("Invalid limit for -opt-bisect-limit: " + llvm::utostr(Limit))
.c_str());
}),
cl::desc(
"Maximum optimization to perform (equivalent to -opt-bisect=1-N)"));
static cl::opt<std::string> OptBisectIntervals(
"opt-bisect", cl::Hidden, cl::Optional,
cl::cb<void, const std::string &>([](const std::string &IntervalStr) {
if (IntervalStr == "-1") {
// -1 means run all passes.
getOptBisector().setIntervals({{1, std::numeric_limits<int>::max()}});
return;
}
auto Intervals =
IntegerInclusiveIntervalUtils::parseIntervals(IntervalStr);
if (!Intervals) {
handleAllErrors(Intervals.takeError(), [&](const StringError &E) {
errs() << "Error: Invalid interval specification for -opt-bisect: "
<< IntervalStr << " (" << E.getMessage() << ")\n";
});
exit(1);
}
getOptBisector().setIntervals(std::move(*Intervals));
}),
cl::desc("Run optimization passes only for the specified intervals. "
"Format: '1-10,20-30,45' runs passes 1-10, 20-30, and 45, where "
"index 1 is the first pass. Supply '0' to run no passes and -1 to "
"run all passes."));
static cl::opt<bool> OptBisectVerbose(
"opt-bisect-verbose",
@ -66,7 +107,11 @@ bool OptBisect::shouldRunPass(StringRef PassName,
assert(isEnabled());
int CurBisectNum = ++LastBisectNum;
bool ShouldRun = (BisectLimit == -1 || CurBisectNum <= BisectLimit);
// Check if current pass number falls within any of the specified intervals.
bool ShouldRun =
IntegerInclusiveIntervalUtils::contains(BisectIntervals, CurBisectNum);
if (OptBisectVerbose)
printPassMessage(PassName, CurBisectNum, IRDescription, ShouldRun);
return ShouldRun;

View File

@ -236,6 +236,7 @@ add_llvm_component_library(LLVMSupport
PluginLoader.cpp
PrettyStackTrace.cpp
RandomNumberGenerator.cpp
IntegerInclusiveInterval.cpp
Regex.cpp
RewriteBuffer.cpp
RewriteRope.cpp

View File

@ -10,77 +10,9 @@ using namespace llvm;
namespace llvm {
void DebugCounter::Chunk::print(llvm::raw_ostream &OS) {
if (Begin == End)
OS << Begin;
else
OS << Begin << "-" << End;
}
void DebugCounter::printChunks(raw_ostream &OS, ArrayRef<Chunk> Chunks) {
if (Chunks.empty()) {
OS << "empty";
} else {
bool IsFirst = true;
for (auto E : Chunks) {
if (!IsFirst)
OS << ':';
else
IsFirst = false;
E.print(OS);
}
}
}
bool DebugCounter::parseChunks(StringRef Str, SmallVector<Chunk> &Chunks) {
StringRef Remaining = Str;
auto ConsumeInt = [&]() -> int64_t {
StringRef Number =
Remaining.take_until([](char c) { return c < '0' || c > '9'; });
int64_t Res;
if (Number.getAsInteger(10, Res)) {
errs() << "Failed to parse int at : " << Remaining << "\n";
return -1;
}
Remaining = Remaining.drop_front(Number.size());
return Res;
};
while (1) {
int64_t Num = ConsumeInt();
if (Num == -1)
return true;
if (!Chunks.empty() && Num <= Chunks[Chunks.size() - 1].End) {
errs() << "Expected Chunks to be in increasing order " << Num
<< " <= " << Chunks[Chunks.size() - 1].End << "\n";
return true;
}
if (Remaining.starts_with("-")) {
Remaining = Remaining.drop_front();
int64_t Num2 = ConsumeInt();
if (Num2 == -1)
return true;
if (Num >= Num2) {
errs() << "Expected " << Num << " < " << Num2 << " in " << Num << "-"
<< Num2 << "\n";
return true;
}
Chunks.push_back({Num, Num2});
} else {
Chunks.push_back({Num, Num});
}
if (Remaining.starts_with(":")) {
Remaining = Remaining.drop_front();
continue;
}
if (Remaining.empty())
break;
errs() << "Failed to parse at : " << Remaining;
return true;
}
return false;
void DebugCounter::printChunks(raw_ostream &OS,
ArrayRef<IntegerInclusiveInterval> Chunks) {
IntegerInclusiveIntervalUtils::printIntervals(OS, Chunks, ':');
}
} // namespace llvm
@ -190,14 +122,9 @@ void DebugCounter::push_back(const std::string &Val) {
auto CounterPair = StringRef(Val).split('=');
if (CounterPair.second.empty()) {
errs() << "DebugCounter Error: " << Val << " does not have an = in it\n";
return;
exit(1);
}
StringRef CounterName = CounterPair.first;
SmallVector<Chunk> Chunks;
if (parseChunks(CounterPair.second, Chunks)) {
return;
}
CounterInfo *Counter = getCounterInfo(CounterName);
if (!Counter) {
@ -206,8 +133,16 @@ void DebugCounter::push_back(const std::string &Val) {
return;
}
auto ExpectedChunks =
IntegerInclusiveIntervalUtils::parseIntervals(CounterPair.second, ':');
if (!ExpectedChunks) {
handleAllErrors(ExpectedChunks.takeError(), [&](const StringError &E) {
errs() << "DebugCounter Error: " << E.getMessage() << "\n";
});
exit(1);
}
Counter->Chunks = std::move(*ExpectedChunks);
Counter->Active = Counter->IsSet = true;
Counter->Chunks = std::move(Chunks);
}
void DebugCounter::print(raw_ostream &OS) const {
@ -234,15 +169,15 @@ bool DebugCounter::handleCounterIncrement(CounterInfo &Info) {
bool Res = Info.Chunks[CurrIdx].contains(CurrCount);
if (BreakOnLast && CurrIdx == (Info.Chunks.size() - 1) &&
CurrCount == Info.Chunks[CurrIdx].End) {
CurrCount == Info.Chunks[CurrIdx].getEnd()) {
LLVM_BUILTIN_DEBUGTRAP;
}
if (CurrCount > Info.Chunks[CurrIdx].End) {
if (CurrCount > Info.Chunks[CurrIdx].getEnd()) {
Info.CurrChunkIdx++;
/// Handle consecutive blocks.
if (Info.CurrChunkIdx < Info.Chunks.size() &&
CurrCount == Info.Chunks[Info.CurrChunkIdx].Begin)
CurrCount == Info.Chunks[Info.CurrChunkIdx].getBegin())
return true;
}
return Res;

View File

@ -0,0 +1,125 @@
//===- IntegerInclusiveInterval.cpp -----------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements utilities for handling lists of inclusive integer
// intervals, such as parsing interval strings like "1-10,20-30,45", which are
// used in debugging and bisection tools.
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/IntegerInclusiveInterval.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
using namespace llvm;
namespace llvm::IntegerInclusiveIntervalUtils {
Expected<IntervalList> parseIntervals(StringRef Str, char Separator) {
IntervalList Intervals;
if (Str.empty())
return std::move(Intervals);
// Regex to match either single number or interval "num1-num2".
const Regex IntervalRegex("^([0-9]+)(-([0-9]+))?$");
for (StringRef Part : llvm::split(Str, Separator)) {
Part = Part.trim();
if (Part.empty())
continue;
SmallVector<StringRef, 4> Matches;
if (!IntervalRegex.match(Part, &Matches))
return createStringError(std::errc::invalid_argument,
"Invalid interval format: '%s'",
Part.str().c_str());
int64_t Begin, End;
if (Matches[1].getAsInteger(10, Begin))
return createStringError(std::errc::invalid_argument,
"Failed to parse number: '%s'",
Matches[1].str().c_str());
if (!Matches[3].empty()) {
// Interval format "begin-end".
if (Matches[3].getAsInteger(10, End))
return createStringError(std::errc::invalid_argument,
"Failed to parse number: '%s'",
Matches[3].str().c_str());
if (Begin >= End)
return createStringError(std::errc::invalid_argument,
"Invalid interval: %lld >= %lld", Begin, End);
} else
// Single number.
End = Begin;
// Check ordering constraint (intervals must be in increasing order).
if (!Intervals.empty() && Begin <= Intervals.back().getEnd())
return createStringError(
std::errc::invalid_argument,
"Expected intervals to be in increasing order: %lld <= %lld", Begin,
Intervals.back().getEnd());
Intervals.push_back(IntegerInclusiveInterval(Begin, End));
}
return Intervals;
}
bool contains(ArrayRef<IntegerInclusiveInterval> Intervals, int64_t Value) {
for (const IntegerInclusiveInterval &It : Intervals) {
if (It.contains(Value))
return true;
}
return false;
}
void printIntervals(raw_ostream &OS,
ArrayRef<IntegerInclusiveInterval> Intervals,
char Separator) {
if (Intervals.empty()) {
OS << "empty";
return;
}
std::string Sep(1, Separator);
ListSeparator LS(Sep);
for (const IntegerInclusiveInterval &It : Intervals) {
OS << LS;
It.print(OS);
}
}
IntervalList
mergeAdjacentIntervals(ArrayRef<IntegerInclusiveInterval> Intervals) {
if (Intervals.empty())
return {};
IntervalList Result;
Result.push_back(Intervals[0]);
for (const IntegerInclusiveInterval &Current : Intervals.drop_front()) {
IntegerInclusiveInterval &Last = Result.back();
// Check if current interval is adjacent to the last merged interval.
if (Current.getBegin() == Last.getEnd() + 1) {
// Merge by extending the end of the last interval.
Last.setEnd(Current.getEnd());
} else {
// Not adjacent, add as separate interval.
Result.push_back(Current);
}
}
return Result;
}
} // end namespace llvm::IntegerInclusiveIntervalUtils

View File

@ -0,0 +1,116 @@
; REQUIRES: asserts
; Test debug counter with multiple intervals
; RUN: opt -passes=dce -S -debug-counter=dce-transform=0 < %s | FileCheck %s --check-prefix=CHECK-ZERO
; RUN: opt -passes=dce -S -debug-counter=dce-transform=1:3:5 < %s | FileCheck %s --check-prefix=CHECK-SINGLE
; RUN: opt -passes=dce -S -debug-counter=dce-transform=1-2:4:6-7 < %s | FileCheck %s --check-prefix=CHECK-MIXED
; RUN: opt -passes=dce -S -debug-counter=dce-transform=1-7 < %s | FileCheck %s --check-prefix=CHECK-ALL
; RUN: opt -passes=dce -S -debug-counter=dce-transform=100 < %s | FileCheck %s --check-prefix=CHECK-NONE
; RUN: opt -passes=dce -S -debug-counter=dce-transform=7 < %s | FileCheck %s --check-prefix=CHECK-LAST
; RUN: opt -passes=dce -S -debug-counter=dce-transform=1 < %s | FileCheck %s --check-prefix=CHECK-FIRST
; Test error cases - these should produce error messages but not crash
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=invalid 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-INVALID
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=5-2 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-BACKWARDS
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=1:3:2 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-UNORDERED
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=abc-def 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-NON-NUMERIC
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=1-abc 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-MIXED
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=1:2:3:2:4 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-COMPLEX-UNORDERED
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=1--5 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-DOUBLE-DASH
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=-5 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-NEGATIVE
; RUN: not opt -passes=dce -S -debug-counter=dce-transform= 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-EMPTY
; RUN: not opt -passes=dce -S -debug-counter=dce-transform=1:1:1 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR-DUPLICATE
; Test that with debug counters on, we can selectively apply transformations
; using different interval specifications. Also check that we catch errors during parsing.
; Original function has 8 dead instructions that DCE can eliminate
define void @test() {
%dead1 = add i32 1, 2
%dead2 = add i32 3, 4
%dead3 = add i32 5, 6
%dead4 = add i32 7, 8
%dead5 = add i32 9, 10
%dead6 = add i32 11, 12
%dead7 = add i32 13, 14
%dead8 = add i32 15, 16
ret void
}
; Test zero: eliminate transformation 0
; CHECK-ZERO-LABEL: @test
; CHECK-ZERO-NEXT: %dead2 = add i32 3, 4
; CHECK-ZERO-NEXT: %dead3 = add i32 5, 6
; CHECK-ZERO-NEXT: %dead4 = add i32 7, 8
; CHECK-ZERO-NEXT: %dead5 = add i32 9, 10
; CHECK-ZERO-NEXT: %dead6 = add i32 11, 12
; CHECK-ZERO-NEXT: %dead7 = add i32 13, 14
; CHECK-ZERO-NEXT: %dead8 = add i32 15, 16
; CHECK-ZERO-NEXT: ret void
; Test single values: apply transformations 1, 3, 5 (eliminate dead2, dead4, dead6)
; CHECK-SINGLE-LABEL: @test
; CHECK-SINGLE-NEXT: %dead1 = add i32 1, 2
; CHECK-SINGLE-NEXT: %dead3 = add i32 5, 6
; CHECK-SINGLE-NEXT: %dead5 = add i32 9, 10
; CHECK-SINGLE-NEXT: %dead7 = add i32 13, 14
; CHECK-SINGLE-NEXT: %dead8 = add i32 15, 16
; CHECK-SINGLE-NEXT: ret void
; Test mixed intervals: apply transformations 1-2, 4, 6-7 (eliminate dead2, dead3, dead5, dead7, dead8)
; CHECK-MIXED-LABEL: @test
; CHECK-MIXED-NEXT: %dead1 = add i32 1, 2
; CHECK-MIXED-NEXT: %dead4 = add i32 7, 8
; CHECK-MIXED-NEXT: %dead6 = add i32 11, 12
; CHECK-MIXED-NEXT: ret void
; Test all interval: apply transformations 1-7 (eliminate all dead instructions except dead1)
; CHECK-ALL-LABEL: @test
; CHECK-ALL-NEXT: %dead1 = add i32 1, 2
; CHECK-ALL-NEXT: ret void
; Test out of interval: apply transformation 100 (eliminate nothing, counter too high)
; CHECK-NONE-LABEL: @test
; CHECK-NONE-NEXT: %dead1 = add i32 1, 2
; CHECK-NONE-NEXT: %dead2 = add i32 3, 4
; CHECK-NONE-NEXT: %dead3 = add i32 5, 6
; CHECK-NONE-NEXT: %dead4 = add i32 7, 8
; CHECK-NONE-NEXT: %dead5 = add i32 9, 10
; CHECK-NONE-NEXT: %dead6 = add i32 11, 12
; CHECK-NONE-NEXT: %dead7 = add i32 13, 14
; CHECK-NONE-NEXT: %dead8 = add i32 15, 16
; CHECK-NONE-NEXT: ret void
; Test last transformation: apply transformation 7 (eliminate dead8)
; CHECK-LAST-LABEL: @test
; CHECK-LAST-NEXT: %dead1 = add i32 1, 2
; CHECK-LAST-NEXT: %dead2 = add i32 3, 4
; CHECK-LAST-NEXT: %dead3 = add i32 5, 6
; CHECK-LAST-NEXT: %dead4 = add i32 7, 8
; CHECK-LAST-NEXT: %dead5 = add i32 9, 10
; CHECK-LAST-NEXT: %dead6 = add i32 11, 12
; CHECK-LAST-NEXT: %dead7 = add i32 13, 14
; CHECK-LAST-NEXT: ret void
; Test first transformation: apply transformation 1 (eliminate dead2)
; CHECK-FIRST-LABEL: @test
; CHECK-FIRST-NEXT: %dead1 = add i32 1, 2
; CHECK-FIRST-NEXT: %dead3 = add i32 5, 6
; CHECK-FIRST-NEXT: %dead4 = add i32 7, 8
; CHECK-FIRST-NEXT: %dead5 = add i32 9, 10
; CHECK-FIRST-NEXT: %dead6 = add i32 11, 12
; CHECK-FIRST-NEXT: %dead7 = add i32 13, 14
; CHECK-FIRST-NEXT: %dead8 = add i32 15, 16
; CHECK-FIRST-NEXT: ret void
; Error case checks - test comprehensive error handling
; CHECK-ERROR-INVALID: DebugCounter Error: Invalid interval format: 'invalid'
; CHECK-ERROR-BACKWARDS: DebugCounter Error: Invalid interval: 5 >= 2
; CHECK-ERROR-UNORDERED: DebugCounter Error: Expected intervals to be in increasing order: 2 <= 3
; CHECK-ERROR-NON-NUMERIC: DebugCounter Error: Invalid interval format: 'abc-def'
; CHECK-ERROR-MIXED: DebugCounter Error: Invalid interval format: '1-abc'
; CHECK-ERROR-COMPLEX-UNORDERED: DebugCounter Error: Expected intervals to be in increasing order: 2 <= 3
; CHECK-ERROR-DOUBLE-DASH: DebugCounter Error: Invalid interval format: '1--5'
; CHECK-ERROR-NEGATIVE: DebugCounter Error: Invalid interval format: '-5'
; CHECK-ERROR-EMPTY: DebugCounter Error: dce-transform= does not have an = in it
; CHECK-ERROR-DUPLICATE: DebugCounter Error: Expected intervals to be in increasing order: 1 <= 1

View File

@ -0,0 +1,58 @@
; Test that verifies functionality for -opt-bisect with interval specifications
; Test basic interval functionality: run passes 1-3 and 7-8
; RUN: opt -passes='annotation2metadata,forceattrs,inferattrs,function(lower-expect),function(simplifycfg),function(sroa),function(early-cse),openmp-opt' \
; RUN: -opt-bisect=1-3,7-8 %s 2>&1 | FileCheck %s --check-prefix=CHECK-INTERVALS
; CHECK-INTERVALS: BISECT: running pass (1) annotation2metadata on [module]
; CHECK-INTERVALS: BISECT: running pass (2) forceattrs on [module]
; CHECK-INTERVALS: BISECT: running pass (3) inferattrs on [module]
; CHECK-INTERVALS: BISECT: NOT running pass (4) lower-expect on foo
; CHECK-INTERVALS: BISECT: NOT running pass (5) simplifycfg on foo
; CHECK-INTERVALS: BISECT: NOT running pass (6) sroa on foo
; CHECK-INTERVALS: BISECT: running pass (7) early-cse on foo
; CHECK-INTERVALS: BISECT: running pass (8) openmp-opt on [module]
; Test single pass selection: run only pass 5
; RUN: opt -passes='annotation2metadata,forceattrs,inferattrs,function(lower-expect),function(simplifycfg),function(sroa),function(early-cse),openmp-opt' \
; RUN: -opt-bisect=5 %s 2>&1 | FileCheck %s --check-prefix=CHECK-SINGLE-INTERVAL
; CHECK-SINGLE-INTERVAL: BISECT: NOT running pass (1) annotation2metadata on [module]
; CHECK-SINGLE-INTERVAL: BISECT: NOT running pass (2) forceattrs on [module]
; CHECK-SINGLE-INTERVAL: BISECT: NOT running pass (3) inferattrs on [module]
; CHECK-SINGLE-INTERVAL: BISECT: NOT running pass (4) lower-expect on foo
; CHECK-SINGLE-INTERVAL: BISECT: running pass (5) simplifycfg on foo
; CHECK-SINGLE-INTERVAL: BISECT: NOT running pass (6) sroa on foo
; Test running no passes
; RUN: opt -passes='annotation2metadata,forceattrs,inferattrs,function(lower-expect),function(simplifycfg),function(sroa),function(early-cse),openmp-opt' \
; RUN: -opt-bisect=0 %s 2>&1 | FileCheck %s --check-prefix=CHECK-NONE
; CHECK-NONE: BISECT: NOT running pass (1) annotation2metadata on [module]
; CHECK-NONE: BISECT: NOT running pass (2) forceattrs on [module]
; CHECK-NONE: BISECT: NOT running pass (3) inferattrs on [module]
; CHECK-NONE: BISECT: NOT running pass (4) lower-expect on foo
; CHECK-NONE: BISECT: NOT running pass (5) simplifycfg on foo
; CHECK-NONE: BISECT: NOT running pass (6) sroa on foo
; Test running all passes
; RUN: opt -passes='annotation2metadata,forceattrs,inferattrs,function(lower-expect),function(simplifycfg),function(sroa),function(early-cse),openmp-opt' \
; RUN: -opt-bisect=-1 %s 2>&1 | FileCheck %s --check-prefix=CHECK-ALL
; CHECK-ALL: BISECT: running pass (1) annotation2metadata on [module]
; CHECK-ALL: BISECT: running pass (2) forceattrs on [module]
; CHECK-ALL: BISECT: running pass (3) inferattrs on [module]
; CHECK-ALL: BISECT: running pass (4) lower-expect on foo
; CHECK-ALL: BISECT: running pass (5) simplifycfg on foo
; CHECK-ALL: BISECT: running pass (6) sroa on foo
; Test backward compatibility: -opt-bisect-limit=3 should be equivalent to -opt-bisect=1-3
; RUN: opt -passes='annotation2metadata,forceattrs,inferattrs,function(lower-expect),function(simplifycfg),function(sroa),function(early-cse),openmp-opt' \
; RUN: -opt-bisect-limit=3 %s 2>&1 | FileCheck %s --check-prefix=CHECK-LIMIT
; CHECK-LIMIT: BISECT: running pass (1) annotation2metadata on [module]
; CHECK-LIMIT: BISECT: running pass (2) forceattrs on [module]
; CHECK-LIMIT: BISECT: running pass (3) inferattrs on [module]
; CHECK-LIMIT: BISECT: NOT running pass (4) lower-expect on foo
; CHECK-LIMIT: BISECT: NOT running pass (5) simplifycfg on foo
define void @foo() {
ret void
}

View File

@ -11,9 +11,8 @@
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/DenseSet.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/DebugCounter.h"
#include "llvm/Support/IntegerInclusiveInterval.h"
#include "llvm/Support/Program.h"
using namespace llvm;
@ -24,29 +23,16 @@ static cl::opt<std::string> StartChunks(cl::Positional, cl::Required);
static cl::opt<bool> Pessimist("pessimist", cl::init(false));
using Chunk = DebugCounter::Chunk;
namespace {
SmallVector<Chunk> simplifyChunksList(ArrayRef<Chunk> Chunks) {
SmallVector<Chunk> Res;
Res.push_back(Chunks.front());
for (unsigned Idx = 1; Idx < Chunks.size(); Idx++) {
if (Chunks[Idx].Begin == Res.back().End + 1)
Res.back().End = Chunks[Idx].End;
else
Res.push_back(Chunks[Idx]);
}
return Res;
}
bool isStillInteresting(ArrayRef<Chunk> Chunks) {
SmallVector<Chunk> SimpleChunks = simplifyChunksList(Chunks);
bool isStillInteresting(ArrayRef<IntegerInclusiveInterval> Chunks) {
IntegerInclusiveIntervalUtils::IntervalList SimpleChunks =
IntegerInclusiveIntervalUtils::mergeAdjacentIntervals(Chunks);
std::string ChunkStr;
{
raw_string_ostream OS(ChunkStr);
DebugCounter::printChunks(OS, SimpleChunks);
IntegerInclusiveIntervalUtils::printIntervals(OS, SimpleChunks);
}
errs() << "Checking with: " << ChunkStr << "\n";
@ -73,18 +59,18 @@ bool isStillInteresting(ArrayRef<Chunk> Chunks) {
return Res;
}
bool increaseGranularity(SmallVector<Chunk> &Chunks) {
bool increaseGranularity(IntegerInclusiveIntervalUtils::IntervalList &Chunks) {
errs() << "Increasing granularity\n";
SmallVector<Chunk> NewChunks;
IntegerInclusiveIntervalUtils::IntervalList NewChunks;
bool SplitOne = false;
for (auto &C : Chunks) {
if (C.Begin == C.End) {
if (C.getBegin() == C.getEnd()) {
NewChunks.push_back(C);
} else {
int Half = (C.Begin + C.End) / 2;
NewChunks.push_back({C.Begin, Half});
NewChunks.push_back({Half + 1, C.End});
int64_t Half = (C.getBegin() + C.getEnd()) / 2;
NewChunks.push_back(IntegerInclusiveInterval(C.getBegin(), Half));
NewChunks.push_back(IntegerInclusiveInterval(Half + 1, C.getEnd()));
SplitOne = true;
}
}
@ -99,10 +85,16 @@ bool increaseGranularity(SmallVector<Chunk> &Chunks) {
int main(int argc, char **argv) {
cl::ParseCommandLineOptions(argc, argv);
SmallVector<Chunk> CurrChunks;
if (DebugCounter::parseChunks(StartChunks, CurrChunks)) {
auto ExpectedChunks =
IntegerInclusiveIntervalUtils::parseIntervals(StartChunks, ',');
if (!ExpectedChunks) {
handleAllErrors(ExpectedChunks.takeError(), [](const StringError &E) {
errs() << "Error parsing chunks: " << E.getMessage() << "\n";
});
return 1;
}
IntegerInclusiveIntervalUtils::IntervalList CurrChunks =
std::move(*ExpectedChunks);
auto Program = sys::findProgramByName(ReproductionCmd);
if (!Program) {
@ -126,7 +118,7 @@ int main(int argc, char **argv) {
if (CurrChunks.size() == 1)
break;
Chunk Testing = CurrChunks[Idx];
IntegerInclusiveInterval Testing = CurrChunks[Idx];
errs() << "Trying to remove : ";
Testing.print(errs());
errs() << "\n";
@ -142,6 +134,8 @@ int main(int argc, char **argv) {
}
errs() << "Minimal Chunks = ";
DebugCounter::printChunks(llvm::errs(), simplifyChunksList(CurrChunks));
IntegerInclusiveIntervalUtils::printIntervals(
llvm::errs(),
IntegerInclusiveIntervalUtils::mergeAdjacentIntervals(CurrChunks));
errs() << "\n";
}

View File

@ -76,6 +76,7 @@ add_llvm_unittest(SupportTests
ProcessTest.cpp
ProgramTest.cpp
ProgramStackTest.cpp
IntegerInclusiveIntervalTest.cpp
RecyclerTest.cpp
RegexTest.cpp
ReverseIterationTest.cpp

View File

@ -0,0 +1,245 @@
//===- llvm/unittest/Support/IntegerInclusiveIntervalTest.cpp -------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "llvm/Support/IntegerInclusiveInterval.h"
#include "llvm/Testing/Support/Error.h"
#include "gtest/gtest.h"
using namespace llvm;
TEST(IntegerInclusiveIntervalTest, BasicInterval) {
IntegerInclusiveInterval I(5, 10);
EXPECT_EQ(I.getBegin(), 5);
EXPECT_EQ(I.getEnd(), 10);
EXPECT_TRUE(I.contains(5));
EXPECT_TRUE(I.contains(7));
EXPECT_TRUE(I.contains(10));
EXPECT_FALSE(I.contains(4));
EXPECT_FALSE(I.contains(11));
}
TEST(IntegerInclusiveIntervalTest, SingleValueInterval) {
IntegerInclusiveInterval I(42);
EXPECT_EQ(I.getBegin(), 42);
EXPECT_EQ(I.getEnd(), 42);
EXPECT_TRUE(I.contains(42));
EXPECT_FALSE(I.contains(41));
EXPECT_FALSE(I.contains(43));
}
TEST(IntegerInclusiveIntervalTest, IntervalOverlaps) {
IntegerInclusiveInterval I1(1, 5);
IntegerInclusiveInterval I2(3, 8);
IntegerInclusiveInterval I3(6, 10);
IntegerInclusiveInterval I4(11, 15);
EXPECT_TRUE(I1.overlaps(I2));
EXPECT_TRUE(I2.overlaps(I1));
EXPECT_TRUE(I2.overlaps(I3));
EXPECT_FALSE(I1.overlaps(I3));
EXPECT_FALSE(I1.overlaps(I4));
EXPECT_FALSE(I3.overlaps(I4));
}
TEST(IntegerInclusiveIntervalUtilsTest, ParseSingleNumber) {
auto ER = IntegerInclusiveIntervalUtils::parseIntervals("42");
ASSERT_THAT_EXPECTED(ER, Succeeded());
auto Intervals = std::move(*ER);
EXPECT_EQ(Intervals.size(), 1U);
EXPECT_EQ(Intervals[0].getBegin(), 42);
EXPECT_EQ(Intervals[0].getEnd(), 42);
}
TEST(IntegerInclusiveIntervalUtilsTest, ParseSingleInterval) {
auto ER = IntegerInclusiveIntervalUtils::parseIntervals("10-20");
ASSERT_THAT_EXPECTED(ER, Succeeded());
auto Intervals = std::move(*ER);
EXPECT_EQ(Intervals.size(), 1U);
EXPECT_EQ(Intervals[0].getBegin(), 10);
EXPECT_EQ(Intervals[0].getEnd(), 20);
}
TEST(IntegerInclusiveIntervalUtilsTest, ParseMultipleIntervals) {
auto ER = IntegerInclusiveIntervalUtils::parseIntervals("1-5,10,15-20");
ASSERT_THAT_EXPECTED(ER, Succeeded());
auto Intervals = std::move(*ER);
EXPECT_EQ(Intervals.size(), 3U);
// Intervals are in input order (DebugCounter style).
EXPECT_EQ(Intervals[0].getBegin(), 1);
EXPECT_EQ(Intervals[0].getEnd(), 5);
EXPECT_EQ(Intervals[1].getBegin(), 10);
EXPECT_EQ(Intervals[1].getEnd(), 10);
EXPECT_EQ(Intervals[2].getBegin(), 15);
EXPECT_EQ(Intervals[2].getEnd(), 20);
}
TEST(IntegerInclusiveIntervalUtilsTest, ParseColonSeparatedIntervals) {
auto ER = IntegerInclusiveIntervalUtils::parseIntervals("1-5:10:15-20", ':');
ASSERT_THAT_EXPECTED(ER, Succeeded());
auto Intervals = std::move(*ER);
EXPECT_EQ(Intervals.size(), 3U);
EXPECT_EQ(Intervals[0].getBegin(), 1);
EXPECT_EQ(Intervals[0].getEnd(), 5);
EXPECT_EQ(Intervals[1].getBegin(), 10);
EXPECT_EQ(Intervals[1].getEnd(), 10);
EXPECT_EQ(Intervals[2].getBegin(), 15);
EXPECT_EQ(Intervals[2].getEnd(), 20);
}
TEST(IntegerInclusiveIntervalUtilsTest, ParseEmptyString) {
auto ER = IntegerInclusiveIntervalUtils::parseIntervals("");
ASSERT_THAT_EXPECTED(ER, Succeeded());
auto Intervals = std::move(*ER);
EXPECT_TRUE(Intervals.empty());
}
TEST(IntegerInclusiveIntervalUtilsTest, ParseInvalidIntervals) {
// Invalid number.
auto ER1 = IntegerInclusiveIntervalUtils::parseIntervals("abc");
EXPECT_THAT_EXPECTED(ER1, Failed());
consumeError(ER1.takeError());
// Invalid interval (begin > end).
auto ER2 = IntegerInclusiveIntervalUtils::parseIntervals("10-5");
EXPECT_THAT_EXPECTED(ER2, Failed());
consumeError(ER2.takeError());
// Out of order intervals (DebugCounter constraint and overlap).
auto ER3 = IntegerInclusiveIntervalUtils::parseIntervals("10,5");
EXPECT_THAT_EXPECTED(ER3, Failed());
consumeError(ER3.takeError());
auto ER4 = IntegerInclusiveIntervalUtils::parseIntervals("1-5,3-7");
EXPECT_THAT_EXPECTED(ER4, Failed());
consumeError(ER4.takeError());
}
TEST(IntegerInclusiveIntervalUtilsTest, Contains) {
auto ER = IntegerInclusiveIntervalUtils::parseIntervals("1-5,10,15-20");
ASSERT_THAT_EXPECTED(ER, Succeeded());
auto Intervals = std::move(*ER);
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(Intervals, 1));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(Intervals, 3));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(Intervals, 5));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(Intervals, 10));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(Intervals, 15));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(Intervals, 18));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(Intervals, 20));
EXPECT_FALSE(IntegerInclusiveIntervalUtils::contains(Intervals, 6));
EXPECT_FALSE(IntegerInclusiveIntervalUtils::contains(Intervals, 9));
EXPECT_FALSE(IntegerInclusiveIntervalUtils::contains(Intervals, 11));
EXPECT_FALSE(IntegerInclusiveIntervalUtils::contains(Intervals, 14));
EXPECT_FALSE(IntegerInclusiveIntervalUtils::contains(Intervals, 21));
}
TEST(IntegerInclusiveIntervalUtilsTest, SeparatorParameter) {
IntegerInclusiveIntervalUtils::IntervalList ColonIntervals, CommaIntervals;
// Test explicit separator parameters.
auto ERC = IntegerInclusiveIntervalUtils::parseIntervals("1-5:10:15-20", ':');
ASSERT_THAT_EXPECTED(ERC, Succeeded());
ColonIntervals = std::move(*ERC);
auto ERM = IntegerInclusiveIntervalUtils::parseIntervals("1-5,10,15-20", ',');
ASSERT_THAT_EXPECTED(ERM, Succeeded());
CommaIntervals = std::move(*ERM);
EXPECT_EQ(ColonIntervals.size(), CommaIntervals.size());
for (size_t I = 0; I < ColonIntervals.size(); ++I) {
EXPECT_EQ(ColonIntervals[I].getBegin(), CommaIntervals[I].getBegin());
EXPECT_EQ(ColonIntervals[I].getEnd(), CommaIntervals[I].getEnd());
}
// Test that both work with contains().
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(ColonIntervals, 3));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(CommaIntervals, 3));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(ColonIntervals, 10));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(CommaIntervals, 10));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(ColonIntervals, 18));
EXPECT_TRUE(IntegerInclusiveIntervalUtils::contains(CommaIntervals, 18));
EXPECT_FALSE(IntegerInclusiveIntervalUtils::contains(ColonIntervals, 8));
EXPECT_FALSE(IntegerInclusiveIntervalUtils::contains(CommaIntervals, 8));
}
TEST(IntegerInclusiveIntervalUtilsTest, DefaultCommaSeparator) {
// Test that comma is the default separator.
auto ER = IntegerInclusiveIntervalUtils::parseIntervals("1-5,10,15-20");
ASSERT_THAT_EXPECTED(ER, Succeeded());
auto Intervals = std::move(*ER);
EXPECT_EQ(Intervals.size(), 3U);
EXPECT_EQ(Intervals[0].getBegin(), 1);
EXPECT_EQ(Intervals[0].getEnd(), 5);
EXPECT_EQ(Intervals[1].getBegin(), 10);
EXPECT_EQ(Intervals[1].getEnd(), 10);
EXPECT_EQ(Intervals[2].getBegin(), 15);
EXPECT_EQ(Intervals[2].getEnd(), 20);
}
TEST(IntegerInclusiveIntervalTest, MergeAdjacentIntervals) {
IntegerInclusiveIntervalUtils::IntervalList Input, Expected, Result;
// Empty input
Result = IntegerInclusiveIntervalUtils::mergeAdjacentIntervals(Input);
EXPECT_TRUE(Result.empty());
// Single interval - no change.
Input.push_back(IntegerInclusiveInterval(5, 10));
Expected.push_back(IntegerInclusiveInterval(5, 10));
Result = IntegerInclusiveIntervalUtils::mergeAdjacentIntervals(Input);
EXPECT_EQ(Expected, Result);
// Adjacent intervals should merge.
Input.clear();
Expected.clear();
Input.push_back(IntegerInclusiveInterval(1, 3));
Input.push_back(IntegerInclusiveInterval(4, 6));
Input.push_back(IntegerInclusiveInterval(7, 9));
Expected.push_back(IntegerInclusiveInterval(1, 9));
Result = IntegerInclusiveIntervalUtils::mergeAdjacentIntervals(Input);
EXPECT_EQ(Expected, Result);
// Non-adjacent intervals should not merge.
Input.clear();
Expected.clear();
Input.push_back(IntegerInclusiveInterval(1, 3));
Input.push_back(IntegerInclusiveInterval(5, 7)); // Gap between 3 and 5.
Input.push_back(IntegerInclusiveInterval(10, 12)); // Gap between 7 and 10.
Expected.push_back(IntegerInclusiveInterval(1, 3));
Expected.push_back(IntegerInclusiveInterval(5, 7));
Expected.push_back(IntegerInclusiveInterval(10, 12));
Result = IntegerInclusiveIntervalUtils::mergeAdjacentIntervals(Input);
EXPECT_EQ(Expected, Result);
// Mixed adjacent and non-adjacent intervals.
Input.clear();
Expected.clear();
Input.push_back(IntegerInclusiveInterval(1, 3));
Input.push_back(IntegerInclusiveInterval(4, 6)); // Adjacent to first.
Input.push_back(IntegerInclusiveInterval(8, 10)); // Gap.
Input.push_back(IntegerInclusiveInterval(11, 13)); // Adjacent to third.
Input.push_back(IntegerInclusiveInterval(14, 16)); // Adjacent to fourth.
Expected.push_back(IntegerInclusiveInterval(1, 6)); // Merged 1-3 and 4-6.
Expected.push_back(
IntegerInclusiveInterval(8, 16)); // Merged 8-10, 11-13, 14-16.
Result = IntegerInclusiveIntervalUtils::mergeAdjacentIntervals(Input);
EXPECT_EQ(Expected, Result);
// Single numbers that are adjacent.
Input.clear();
Expected.clear();
Input.push_back(IntegerInclusiveInterval(5));
Input.push_back(IntegerInclusiveInterval(6));
Input.push_back(IntegerInclusiveInterval(7));
Expected.push_back(IntegerInclusiveInterval(5, 7));
Result = IntegerInclusiveIntervalUtils::mergeAdjacentIntervals(Input);
EXPECT_EQ(Expected, Result);
}