diff --git a/llvm/include/llvm/IR/OptBisect.h b/llvm/include/llvm/IR/OptBisect.h index a8cd56f0b4b0..77c866ab76d2 100644 --- a/llvm/include/llvm/IR/OptBisect.h +++ b/llvm/include/llvm/IR/OptBisect.h @@ -17,7 +17,7 @@ #include "llvm/ADT/StringRef.h" #include "llvm/ADT/StringSet.h" #include "llvm/Support/Compiler.h" -#include +#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::max(); - private: - int BisectLimit = Disabled; mutable int LastBisectNum = 0; + IntegerInclusiveIntervalUtils::IntervalList BisectIntervals; }; /// This class implements a mechanism to disable passes and individual diff --git a/llvm/include/llvm/Support/DebugCounter.h b/llvm/include/llvm/Support/DebugCounter.h index 979d6b8e62f2..7e83fc8ff876 100644 --- a/llvm/include/llvm/Support/DebugCounter.h +++ b/llvm/include/llvm/Support/DebugCounter.h @@ -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 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 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); + LLVM_ABI static void + printChunks(raw_ostream &OS, ArrayRef Intervals); /// Return true on parsing error and print the error message on the /// llvm::errs() - LLVM_ABI static bool parseChunks(StringRef Str, SmallVector &Res); + LLVM_ABI static bool + parseChunks(StringRef Str, IntegerInclusiveIntervalUtils::IntervalList &Res); /// Returns a reference to the singleton instance. LLVM_ABI static DebugCounter &instance(); diff --git a/llvm/include/llvm/Support/IntegerInclusiveInterval.h b/llvm/include/llvm/Support/IntegerInclusiveInterval.h new file mode 100644 index 000000000000..f5472b9122af --- /dev/null +++ b/llvm/include/llvm/Support/IntegerInclusiveInterval.h @@ -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 +#include + +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; + +/// 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 containing the parsed intervals on success, +/// or an Error on failure. +Expected parseIntervals(StringRef IntervalStr, + char Separator = ','); + +/// Check if a value is contained in any of the intervals. +bool contains(ArrayRef 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 Intervals, + char Separator = ','); + +/// Merge adjacent/consecutive intervals into single intervals. +/// Example: [1-3, 4-6, 8-10] -> [1-6, 8-10]. +IntervalList +mergeAdjacentIntervals(ArrayRef Intervals); + +} // end namespace IntegerInclusiveIntervalUtils + +} // end namespace llvm + +#endif // LLVM_SUPPORT_INTEGER_INCLUSIVE_INTERVAL_H diff --git a/llvm/lib/IR/OptBisect.cpp b/llvm/lib/IR/OptBisect.cpp index 29ca26840826..bb9fba66ddf9 100644 --- a/llvm/lib/IR/OptBisect.cpp +++ b/llvm/lib/IR/OptBisect.cpp @@ -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 +#include using namespace llvm; @@ -30,12 +33,50 @@ static OptDisable &getOptDisabler() { return OptDisabler; } -static cl::opt OptBisectLimit("opt-bisect-limit", cl::Hidden, - cl::init(OptBisect::Disabled), cl::Optional, - cl::cb([](int Limit) { - getOptBisector().setLimit(Limit); - }), - cl::desc("Maximum optimization to perform")); +static cl::opt OptBisectLimit( + "opt-bisect-limit", cl::Hidden, cl::init(-1), cl::Optional, + cl::cb([](int Limit) { + if (Limit == -1) + // -1 means run all passes. + getOptBisector().setIntervals({{1, std::numeric_limits::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 OptBisectIntervals( + "opt-bisect", cl::Hidden, cl::Optional, + cl::cb([](const std::string &IntervalStr) { + if (IntervalStr == "-1") { + // -1 means run all passes. + getOptBisector().setIntervals({{1, std::numeric_limits::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 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; diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index 1c397e8c0b76..099b0e55579b 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -236,6 +236,7 @@ add_llvm_component_library(LLVMSupport PluginLoader.cpp PrettyStackTrace.cpp RandomNumberGenerator.cpp + IntegerInclusiveInterval.cpp Regex.cpp RewriteBuffer.cpp RewriteRope.cpp diff --git a/llvm/lib/Support/DebugCounter.cpp b/llvm/lib/Support/DebugCounter.cpp index 6c69c884a684..ae388474a792 100644 --- a/llvm/lib/Support/DebugCounter.cpp +++ b/llvm/lib/Support/DebugCounter.cpp @@ -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 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 &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 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 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; diff --git a/llvm/lib/Support/IntegerInclusiveInterval.cpp b/llvm/lib/Support/IntegerInclusiveInterval.cpp new file mode 100644 index 000000000000..c38e32b8a5ed --- /dev/null +++ b/llvm/lib/Support/IntegerInclusiveInterval.cpp @@ -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 + +using namespace llvm; + +namespace llvm::IntegerInclusiveIntervalUtils { + +Expected 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 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 Intervals, int64_t Value) { + for (const IntegerInclusiveInterval &It : Intervals) { + if (It.contains(Value)) + return true; + } + return false; +} + +void printIntervals(raw_ostream &OS, + ArrayRef 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 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 diff --git a/llvm/test/Other/debugcounter-multi-intervals.ll b/llvm/test/Other/debugcounter-multi-intervals.ll new file mode 100644 index 000000000000..d5b3c7ed314f --- /dev/null +++ b/llvm/test/Other/debugcounter-multi-intervals.ll @@ -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 diff --git a/llvm/test/Other/opt-bisect-ranges.ll b/llvm/test/Other/opt-bisect-ranges.ll new file mode 100644 index 000000000000..0558bb67c799 --- /dev/null +++ b/llvm/test/Other/opt-bisect-ranges.ll @@ -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 +} + + diff --git a/llvm/tools/reduce-chunk-list/reduce-chunk-list.cpp b/llvm/tools/reduce-chunk-list/reduce-chunk-list.cpp index dc99859b516d..eaf173d4a88a 100644 --- a/llvm/tools/reduce-chunk-list/reduce-chunk-list.cpp +++ b/llvm/tools/reduce-chunk-list/reduce-chunk-list.cpp @@ -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 StartChunks(cl::Positional, cl::Required); static cl::opt Pessimist("pessimist", cl::init(false)); -using Chunk = DebugCounter::Chunk; - namespace { -SmallVector simplifyChunksList(ArrayRef Chunks) { - SmallVector 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 Chunks) { - SmallVector SimpleChunks = simplifyChunksList(Chunks); +bool isStillInteresting(ArrayRef 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 Chunks) { return Res; } -bool increaseGranularity(SmallVector &Chunks) { +bool increaseGranularity(IntegerInclusiveIntervalUtils::IntervalList &Chunks) { errs() << "Increasing granularity\n"; - SmallVector 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 &Chunks) { int main(int argc, char **argv) { cl::ParseCommandLineOptions(argc, argv); - SmallVector 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"; } diff --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt index 21f10eb610f1..a8bf96bbe096 100644 --- a/llvm/unittests/Support/CMakeLists.txt +++ b/llvm/unittests/Support/CMakeLists.txt @@ -76,6 +76,7 @@ add_llvm_unittest(SupportTests ProcessTest.cpp ProgramTest.cpp ProgramStackTest.cpp + IntegerInclusiveIntervalTest.cpp RecyclerTest.cpp RegexTest.cpp ReverseIterationTest.cpp diff --git a/llvm/unittests/Support/IntegerInclusiveIntervalTest.cpp b/llvm/unittests/Support/IntegerInclusiveIntervalTest.cpp new file mode 100644 index 000000000000..9e80dc8242a4 --- /dev/null +++ b/llvm/unittests/Support/IntegerInclusiveIntervalTest.cpp @@ -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); +}