| //===-- JSON serialization routines ---------------------------------------===// |
| // |
| // 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 "JSON.h" |
| #include "LibcBenchmark.h" |
| #include "llvm/ADT/DenseSet.h" |
| #include "llvm/ADT/SmallVector.h" |
| #include "llvm/ADT/StringRef.h" |
| #include "llvm/ADT/StringSwitch.h" |
| #include "llvm/Support/Errc.h" |
| #include "llvm/Support/Error.h" |
| #include "llvm/Support/ErrorHandling.h" |
| #include "llvm/Support/JSON.h" |
| #include "llvm/Support/MathExtras.h" |
| |
| #include <chrono> |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| namespace llvm { |
| namespace libc_benchmarks { |
| |
| template <typename T> |
| static Error intFromJsonTemplate(const json::Value &V, T &Out) { |
| if (const auto &MaybeInt64 = V.getAsInteger()) { |
| int64_t Value = *MaybeInt64; |
| if (Value < std::numeric_limits<T>::min() || |
| Value > std::numeric_limits<T>::max()) |
| return createStringError(errc::io_error, "Out of bound Integer"); |
| Out = Value; |
| return Error::success(); |
| } |
| return createStringError(errc::io_error, "Can't parse Integer"); |
| } |
| |
| static Error fromJson(const json::Value &V, bool &Out) { |
| if (auto B = V.getAsBoolean()) { |
| Out = *B; |
| return Error::success(); |
| } |
| return createStringError(errc::io_error, "Can't parse Boolean"); |
| } |
| |
| static Error fromJson(const json::Value &V, double &Out) { |
| if (auto S = V.getAsNumber()) { |
| Out = *S; |
| return Error::success(); |
| } |
| return createStringError(errc::io_error, "Can't parse Double"); |
| } |
| |
| static Error fromJson(const json::Value &V, std::string &Out) { |
| if (auto S = V.getAsString()) { |
| Out = std::string(*S); |
| return Error::success(); |
| } |
| return createStringError(errc::io_error, "Can't parse String"); |
| } |
| |
| static Error fromJson(const json::Value &V, uint32_t &Out) { |
| return intFromJsonTemplate(V, Out); |
| } |
| |
| static Error fromJson(const json::Value &V, int &Out) { |
| return intFromJsonTemplate(V, Out); |
| } |
| |
| static Error fromJson(const json::Value &V, libc_benchmarks::Duration &D) { |
| if (V.kind() != json::Value::Kind::Number) |
| return createStringError(errc::io_error, "Can't parse Duration"); |
| D = libc_benchmarks::Duration(*V.getAsNumber()); |
| return Error::success(); |
| } |
| |
| static Error fromJson(const json::Value &V, MaybeAlign &Out) { |
| const auto MaybeInt = V.getAsInteger(); |
| if (!MaybeInt) |
| return createStringError(errc::io_error, |
| "Can't parse Align, not an Integer"); |
| const int64_t Value = *MaybeInt; |
| if (!Value) { |
| Out = None; |
| return Error::success(); |
| } |
| if (isPowerOf2_64(Value)) { |
| Out = Align(Value); |
| return Error::success(); |
| } |
| return createStringError(errc::io_error, |
| "Can't parse Align, not a power of two"); |
| } |
| |
| static Error fromJson(const json::Value &V, |
| libc_benchmarks::BenchmarkLog &Out) { |
| if (V.kind() != json::Value::Kind::String) |
| return createStringError(errc::io_error, |
| "Can't parse BenchmarkLog, not a String"); |
| const auto String = *V.getAsString(); |
| auto Parsed = |
| llvm::StringSwitch<Optional<libc_benchmarks::BenchmarkLog>>(String) |
| .Case("None", libc_benchmarks::BenchmarkLog::None) |
| .Case("Last", libc_benchmarks::BenchmarkLog::Last) |
| .Case("Full", libc_benchmarks::BenchmarkLog::Full) |
| .Default(None); |
| if (!Parsed) |
| return createStringError(errc::io_error, |
| Twine("Can't parse BenchmarkLog, invalid value '") |
| .concat(String) |
| .concat("'")); |
| Out = *Parsed; |
| return Error::success(); |
| } |
| |
| template <typename C> |
| Error vectorFromJsonTemplate(const json::Value &V, C &Out) { |
| auto *A = V.getAsArray(); |
| if (!A) |
| return createStringError(errc::io_error, "Can't parse Array"); |
| Out.clear(); |
| Out.resize(A->size()); |
| for (auto InOutPair : llvm::zip(*A, Out)) |
| if (auto E = fromJson(std::get<0>(InOutPair), std::get<1>(InOutPair))) |
| return std::move(E); |
| return Error::success(); |
| } |
| |
| template <typename T> |
| static Error fromJson(const json::Value &V, std::vector<T> &Out) { |
| return vectorFromJsonTemplate(V, Out); |
| } |
| |
| // Same as llvm::json::ObjectMapper but adds a finer error reporting mechanism. |
| class JsonObjectMapper { |
| const json::Object *O; |
| Error E; |
| SmallDenseSet<StringRef> SeenFields; |
| |
| public: |
| explicit JsonObjectMapper(const json::Value &V) |
| : O(V.getAsObject()), |
| E(O ? Error::success() |
| : createStringError(errc::io_error, "Expected JSON Object")) {} |
| |
| Error takeError() { |
| if (E) |
| return std::move(E); |
| for (const auto &Itr : *O) { |
| const StringRef Key = Itr.getFirst(); |
| if (!SeenFields.count(Key)) |
| E = createStringError(errc::io_error, |
| Twine("Unknown field: ").concat(Key)); |
| } |
| return std::move(E); |
| } |
| |
| template <typename T> void map(StringRef Key, T &Out) { |
| if (E) |
| return; |
| if (const json::Value *Value = O->get(Key)) { |
| SeenFields.insert(Key); |
| E = fromJson(*Value, Out); |
| } |
| } |
| }; |
| |
| static Error fromJson(const json::Value &V, |
| libc_benchmarks::BenchmarkOptions &Out) { |
| JsonObjectMapper O(V); |
| O.map("MinDuration", Out.MinDuration); |
| O.map("MaxDuration", Out.MaxDuration); |
| O.map("InitialIterations", Out.InitialIterations); |
| O.map("MaxIterations", Out.MaxIterations); |
| O.map("MinSamples", Out.MinSamples); |
| O.map("MaxSamples", Out.MaxSamples); |
| O.map("Epsilon", Out.Epsilon); |
| O.map("ScalingFactor", Out.ScalingFactor); |
| O.map("Log", Out.Log); |
| return O.takeError(); |
| } |
| |
| static Error fromJson(const json::Value &V, |
| libc_benchmarks::StudyConfiguration &Out) { |
| JsonObjectMapper O(V); |
| O.map("Function", Out.Function); |
| O.map("NumTrials", Out.NumTrials); |
| O.map("IsSweepMode", Out.IsSweepMode); |
| O.map("SweepModeMaxSize", Out.SweepModeMaxSize); |
| O.map("SizeDistributionName", Out.SizeDistributionName); |
| O.map("AccessAlignment", Out.AccessAlignment); |
| O.map("MemcmpMismatchAt", Out.MemcmpMismatchAt); |
| return O.takeError(); |
| } |
| |
| static Error fromJson(const json::Value &V, libc_benchmarks::CacheInfo &Out) { |
| JsonObjectMapper O(V); |
| O.map("Type", Out.Type); |
| O.map("Level", Out.Level); |
| O.map("Size", Out.Size); |
| O.map("NumSharing", Out.NumSharing); |
| return O.takeError(); |
| } |
| |
| static Error fromJson(const json::Value &V, libc_benchmarks::HostState &Out) { |
| JsonObjectMapper O(V); |
| O.map("CpuName", Out.CpuName); |
| O.map("CpuFrequency", Out.CpuFrequency); |
| O.map("Caches", Out.Caches); |
| return O.takeError(); |
| } |
| |
| static Error fromJson(const json::Value &V, libc_benchmarks::Runtime &Out) { |
| JsonObjectMapper O(V); |
| O.map("Host", Out.Host); |
| O.map("BufferSize", Out.BufferSize); |
| O.map("BatchParameterCount", Out.BatchParameterCount); |
| O.map("BenchmarkOptions", Out.BenchmarkOptions); |
| return O.takeError(); |
| } |
| |
| static Error fromJson(const json::Value &V, libc_benchmarks::Study &Out) { |
| JsonObjectMapper O(V); |
| O.map("StudyName", Out.StudyName); |
| O.map("Runtime", Out.Runtime); |
| O.map("Configuration", Out.Configuration); |
| O.map("Measurements", Out.Measurements); |
| return O.takeError(); |
| } |
| |
| static double seconds(const Duration &D) { |
| return std::chrono::duration<double>(D).count(); |
| } |
| |
| Expected<Study> parseJsonStudy(StringRef Content) { |
| Expected<json::Value> EV = json::parse(Content); |
| if (!EV) |
| return EV.takeError(); |
| Study S; |
| if (Error E = fromJson(*EV, S)) |
| return std::move(E); |
| return S; |
| } |
| |
| static StringRef serialize(const BenchmarkLog &L) { |
| switch (L) { |
| case BenchmarkLog::None: |
| return "None"; |
| case BenchmarkLog::Last: |
| return "Last"; |
| case BenchmarkLog::Full: |
| return "Full"; |
| } |
| llvm_unreachable("Unhandled BenchmarkLog value"); |
| } |
| |
| static void serialize(const BenchmarkOptions &BO, json::OStream &JOS) { |
| JOS.attribute("MinDuration", seconds(BO.MinDuration)); |
| JOS.attribute("MaxDuration", seconds(BO.MaxDuration)); |
| JOS.attribute("InitialIterations", BO.InitialIterations); |
| JOS.attribute("MaxIterations", BO.MaxIterations); |
| JOS.attribute("MinSamples", BO.MinSamples); |
| JOS.attribute("MaxSamples", BO.MaxSamples); |
| JOS.attribute("Epsilon", BO.Epsilon); |
| JOS.attribute("ScalingFactor", BO.ScalingFactor); |
| JOS.attribute("Log", serialize(BO.Log)); |
| } |
| |
| static void serialize(const CacheInfo &CI, json::OStream &JOS) { |
| JOS.attribute("Type", CI.Type); |
| JOS.attribute("Level", CI.Level); |
| JOS.attribute("Size", CI.Size); |
| JOS.attribute("NumSharing", CI.NumSharing); |
| } |
| |
| static void serialize(const StudyConfiguration &SC, json::OStream &JOS) { |
| JOS.attribute("Function", SC.Function); |
| JOS.attribute("NumTrials", SC.NumTrials); |
| JOS.attribute("IsSweepMode", SC.IsSweepMode); |
| JOS.attribute("SweepModeMaxSize", SC.SweepModeMaxSize); |
| JOS.attribute("SizeDistributionName", SC.SizeDistributionName); |
| JOS.attribute("AccessAlignment", |
| static_cast<int64_t>(SC.AccessAlignment->value())); |
| JOS.attribute("MemcmpMismatchAt", SC.MemcmpMismatchAt); |
| } |
| |
| static void serialize(const HostState &HS, json::OStream &JOS) { |
| JOS.attribute("CpuName", HS.CpuName); |
| JOS.attribute("CpuFrequency", HS.CpuFrequency); |
| JOS.attributeArray("Caches", [&]() { |
| for (const auto &CI : HS.Caches) |
| JOS.object([&]() { serialize(CI, JOS); }); |
| }); |
| } |
| |
| static void serialize(const Runtime &RI, json::OStream &JOS) { |
| JOS.attributeObject("Host", [&]() { serialize(RI.Host, JOS); }); |
| JOS.attribute("BufferSize", RI.BufferSize); |
| JOS.attribute("BatchParameterCount", RI.BatchParameterCount); |
| JOS.attributeObject("BenchmarkOptions", |
| [&]() { serialize(RI.BenchmarkOptions, JOS); }); |
| } |
| |
| void serializeToJson(const Study &S, json::OStream &JOS) { |
| JOS.object([&]() { |
| JOS.attribute("StudyName", S.StudyName); |
| JOS.attributeObject("Runtime", [&]() { serialize(S.Runtime, JOS); }); |
| JOS.attributeObject("Configuration", |
| [&]() { serialize(S.Configuration, JOS); }); |
| if (!S.Measurements.empty()) { |
| JOS.attributeArray("Measurements", [&]() { |
| for (const auto &M : S.Measurements) |
| JOS.value(seconds(M)); |
| }); |
| } |
| }); |
| } |
| |
| } // namespace libc_benchmarks |
| } // namespace llvm |