-
Notifications
You must be signed in to change notification settings - Fork 50
[hls-fuzzer] Implement statistics for II #991
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
zero9178
wants to merge
3
commits into
main
Choose a base branch
from
users/zero9179/ii-statistic
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| #ifndef DYNAMATIC_HLS_FUZZER_STATISTICS_HISTOGRAM | ||
| #define DYNAMATIC_HLS_FUZZER_STATISTICS_HISTOGRAM | ||
|
|
||
| #include "llvm/ADT/STLExtras.h" | ||
| #include "llvm/Support/JSON.h" | ||
|
|
||
| #include <cstddef> | ||
| #include <cstdint> | ||
| #include <map> | ||
| #include <type_traits> | ||
|
|
||
| namespace dynamatic { | ||
|
|
||
| /// A frequency histogram mapping observed values of type 'T' to the number of | ||
| /// times they were observed. Values are kept ordered so the median and any | ||
| /// rendering come out sorted. Formatting is intentionally left to callers (it | ||
| /// differs per statistic); iterate the histogram with 'begin()'/'end()' to | ||
| /// print it. | ||
| template <typename T> | ||
| class Histogram { | ||
| public: | ||
| /// Records 'count' additional observations of 'value'. | ||
| void add(T value, std::size_t count = 1) { counts[value] += count; } | ||
|
|
||
| /// Merges the observations of 'rhs' into this histogram. | ||
| void merge(const Histogram &rhs) { | ||
| for (const auto &[value, count] : rhs.counts) | ||
| counts[value] += count; | ||
| } | ||
|
|
||
| /// Total number of observations across all values. | ||
| std::size_t total() const { | ||
| std::size_t sum = 0; | ||
| for (const auto &[value, count] : counts) | ||
| sum += count; | ||
| return sum; | ||
| } | ||
|
|
||
| bool empty() const { return counts.empty(); } | ||
|
|
||
| /// The median of all observations. For an even number of observations the | ||
| /// mean of the two central values is returned; an empty histogram has median | ||
| /// 0. | ||
| double median() const { | ||
| std::size_t n = total(); | ||
| if (n == 0) | ||
| return 0.0; | ||
|
|
||
| double median = 0.0; | ||
| std::size_t seen = 0; | ||
| std::size_t lowerHalf = n / 2; | ||
| for (const auto &[value, count] : counts) { | ||
| // The median sits at index 'lowerHalf' (or, for an even count, between | ||
| // 'lowerHalf - 1' and 'lowerHalf'). Capture both samples as they are | ||
| // crossed. | ||
| if (n % 2 == 0 && seen <= lowerHalf - 1 && seen + count > lowerHalf - 1) | ||
| median += static_cast<double>(value) / 2.0; | ||
| if (seen <= lowerHalf && seen + count > lowerHalf) | ||
| median += n % 2 == 0 ? static_cast<double>(value) / 2.0 | ||
| : static_cast<double>(value); | ||
| seen += count; | ||
| } | ||
| return median; | ||
| } | ||
|
|
||
| /// Ordered iteration over '{value, count}' pairs, for rendering. | ||
| auto begin() const { return counts.begin(); } | ||
| auto end() const { return counts.end(); } | ||
|
|
||
| private: | ||
| std::map<T, std::size_t> counts; | ||
| }; | ||
|
|
||
| /// The type a histogram value of type 'T' is parsed as before being narrowed | ||
| /// back to 'T'. 'llvm::json' only parses the widest integer and floating-point | ||
| /// types, so a 'Histogram<unsigned>' has to go through 'uint64_t'. | ||
| template <typename T> | ||
| using HistogramParseType = std::conditional_t< | ||
| std::is_floating_point_v<T>, double, | ||
| std::conditional_t<std::is_signed_v<T>, int64_t, uint64_t>>; | ||
|
|
||
| /// Serializes 'histogram' as an array of '{"value": ..., "count": ...}' | ||
| /// objects, ordered by value. | ||
| /// | ||
| /// Counts are written raw rather than as a share of the total, so that a | ||
| /// histogram read back by 'fromJSON' is indistinguishable from the original and | ||
| /// can still be merged into another one. | ||
| template <typename T> | ||
| llvm::json::Value toJSON(const Histogram<T> &histogram) { | ||
| llvm::json::Array entries; | ||
| for (const auto &[value, count] : histogram) | ||
| entries.push_back(llvm::json::Object{ | ||
| {"value", value}, | ||
| {"count", static_cast<uint64_t>(count)}, | ||
| }); | ||
| return entries; | ||
| } | ||
|
|
||
| /// Parses a histogram written by 'toJSON', replacing the contents of | ||
| /// 'histogram'. Returns false if 'value' is not a valid representation, | ||
| /// reporting why to 'path'. | ||
| template <typename T> | ||
| bool fromJSON(const llvm::json::Value &value, Histogram<T> &histogram, | ||
| llvm::json::Path path) { | ||
| const llvm::json::Array *entries = value.getAsArray(); | ||
| if (!entries) { | ||
| path.report("expected array"); | ||
| return false; | ||
| } | ||
|
|
||
| histogram = Histogram<T>(); | ||
| for (const auto &[index, entry] : llvm::enumerate(*entries)) { | ||
| HistogramParseType<T> entryValue; | ||
| uint64_t count; | ||
| llvm::json::ObjectMapper mapper(entry, path.index(index)); | ||
| if (!mapper || !mapper.map("value", entryValue) || | ||
| !mapper.map("count", count)) | ||
| return false; | ||
|
|
||
| histogram.add(static_cast<T>(entryValue), static_cast<std::size_t>(count)); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
| } // namespace dynamatic | ||
| #endif |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| #include "IIReport.h" | ||
|
|
||
| #include "llvm/ADT/StringMap.h" | ||
| #include "llvm/ADT/StringRef.h" | ||
| #include "llvm/Support/Error.h" | ||
| #include "llvm/Support/JSON.h" | ||
| #include "llvm/Support/LineIterator.h" | ||
| #include "llvm/Support/MemoryBuffer.h" | ||
|
|
||
| using namespace dynamatic; | ||
|
|
||
| std::optional<double> LoopIIReport::getMedianII() const { | ||
| if (intervals.empty()) | ||
| return std::nullopt; | ||
| return intervals.median(); | ||
| } | ||
|
|
||
| llvm::SmallVector<LoopIIReport> | ||
| dynamatic::parseIIReport(const std::filesystem::path &outputDir) { | ||
| llvm::SmallVector<LoopIIReport> reports; | ||
| llvm::ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> buffer = | ||
| llvm::MemoryBuffer::getFile((outputDir / "sim" / "report.txt").string()); | ||
| if (!buffer) | ||
| return reports; | ||
|
|
||
| // Index into 'reports', so that a loop's lines -- which the simulator | ||
| // interleaves with every other loop's -- all land in the same entry while the | ||
| // loops keep the order they were first seen in. | ||
| llvm::StringMap<unsigned> indexByLoop; | ||
| for (llvm::line_iterator it(**buffer, /*SkipBlanks=*/true); !it.is_at_eof(); | ||
| ++it) { | ||
| llvm::StringRef ref = *it; | ||
| size_t pos = ref.find("II_INSTRUMENT:"); | ||
| if (pos == llvm::StringRef::npos) | ||
| continue; | ||
|
|
||
| // The simulator surrounds the report with a severity prefix and, depending | ||
| // on the backend, a trailing time stamp; the object itself is what lies | ||
| // between the outermost braces of the line. | ||
| ref = ref.drop_front(pos); | ||
| size_t objStart = ref.find('{'), objEnd = ref.rfind('}'); | ||
| if (objStart == llvm::StringRef::npos || objEnd == llvm::StringRef::npos || | ||
| objEnd < objStart) | ||
| continue; | ||
|
|
||
| llvm::Expected<llvm::json::Value> value = | ||
| llvm::json::parse(ref.slice(objStart, objEnd + 1)); | ||
| if (!value) { | ||
| llvm::consumeError(value.takeError()); | ||
| continue; | ||
| } | ||
| const llvm::json::Object *report = value->getAsObject(); | ||
| if (!report) | ||
| continue; | ||
|
|
||
| std::optional<llvm::StringRef> loop = report->getString("loop"); | ||
| std::optional<int64_t> depth = report->getInteger("depth"); | ||
| std::optional<int64_t> maxDepth = report->getInteger("max_depth"); | ||
| std::optional<int64_t> iter = report->getInteger("iter"); | ||
| if (!loop || !depth || !maxDepth || !iter) | ||
| continue; | ||
|
|
||
| auto [entry, inserted] = indexByLoop.try_emplace(*loop, reports.size()); | ||
| if (inserted) { | ||
| LoopIIReport &newReport = reports.emplace_back(); | ||
| newReport.loop = loop->str(); | ||
| newReport.depth = static_cast<unsigned>(*depth); | ||
| newReport.maxDepth = static_cast<unsigned>(*maxDepth); | ||
| } | ||
| LoopIIReport &loopReport = reports[entry->second]; | ||
|
|
||
| if (*iter == 0) { | ||
| // A fresh activation of the loop. Its interval, if any, spans the gap | ||
| // since the previous activation and is dropped. | ||
| loopReport.iterationsPerActivation.push_back(1); | ||
| continue; | ||
| } | ||
| if (loopReport.iterationsPerActivation.empty()) | ||
| // An iteration of an activation the simulation did not record the start | ||
| // of, which cannot happen but would corrupt the iteration counts. | ||
| continue; | ||
|
|
||
| ++loopReport.iterationsPerActivation.back(); | ||
| // 'null' on the very first line of the whole run, which has no previous | ||
| // iteration to measure against. | ||
| if (std::optional<int64_t> interval = report->getInteger("interval")) | ||
| loopReport.intervals.add(static_cast<unsigned>(*interval)); | ||
| } | ||
| return reports; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| #ifndef DYNAMATIC_HLS_FUZZER_STATISTICS_IIREPORT | ||
| #define DYNAMATIC_HLS_FUZZER_STATISTICS_IIREPORT | ||
|
|
||
| #include "Histogram.h" | ||
|
|
||
| #include "llvm/ADT/SmallVector.h" | ||
|
|
||
| #include <filesystem> | ||
| #include <optional> | ||
| #include <string> | ||
|
|
||
| namespace dynamatic { | ||
|
|
||
| /// Everything one II monitor reported about its loop over a simulation, | ||
| /// gathered from the per-iteration lines it prints (see 'parseIIReport'). | ||
| struct LoopIIReport { | ||
| /// Hierarchical instance path of the monitor, which identifies the loop | ||
| /// within the program. | ||
| std::string loop; | ||
| /// Nesting depth of the loop (1 for a top-level loop) and the deepest depth | ||
| /// reachable in its nest. The loop is innermost exactly when the two are | ||
| /// equal. | ||
| unsigned depth = 0; | ||
| unsigned maxDepth = 0; | ||
|
|
||
| /// Intervals, in cycles, between an iteration and the one before it within | ||
| /// the same activation. | ||
| Histogram<unsigned> intervals; | ||
|
|
||
| /// Sequential history of number of iterations. | ||
| /// Every fresh activation adds a new entry which contains the number of | ||
| /// iterations that activation lasted. | ||
| llvm::SmallVector<unsigned> iterationsPerActivation; | ||
|
|
||
| /// Whether the loop is an innermost one, i.e. has no loop nested inside it. | ||
| bool isInnermost() const { return depth == maxDepth; } | ||
|
|
||
| /// The loop's depth measured from the innermost loop of its nest outwards: 0 | ||
| /// for an innermost loop, 1 for a loop directly enclosing one, and so on. | ||
| unsigned depthFromInnermost() const { return maxDepth - depth; } | ||
|
|
||
| /// The loop's achieved median II over all intervals. | ||
| /// Empty if the loop never ran more than one iteration per activation. | ||
| std::optional<double> getMedianII() const; | ||
| }; | ||
|
|
||
| /// Parses the II instrumentation's reports out of the simulation log in | ||
| /// 'outputDir' (the output directory dynamatic was run with, compiled with | ||
| /// '--instrument-ii') and returns one entry per loop. Returns an empty vector | ||
| /// if the log is missing or holds no report. | ||
| /// | ||
| /// Every monitor prints one line per iteration of its loop, as the loop takes | ||
| /// that iteration in: | ||
| /// 'II_INSTRUMENT: {"loop": <path>, "depth": <d>, "max_depth": <m>, | ||
| /// "iter": <i>, "interval": <n>}' | ||
| /// where 'iter' is the iteration's index within its activation (so 'iter == 0' | ||
| /// opens a fresh activation) and 'interval' the number of cycles since the | ||
| /// previous iteration was taken in ('null' on the very first line of the run). | ||
| /// The monitor deliberately leaves how these are aggregated to its consumers; | ||
| /// this is where the fuzzer's statistics decide on it. | ||
| llvm::SmallVector<LoopIIReport> | ||
| parseIIReport(const std::filesystem::path &outputDir); | ||
|
|
||
| } // namespace dynamatic | ||
| #endif |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you pls add an example of the JSON being parsed here?