| //===-- 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 <optional> | |
| #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 = std::nullopt; | |
| 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<std::optional<libc_benchmarks::BenchmarkLog>>(String) | |
| .Case("None", libc_benchmarks::BenchmarkLog::None) | |
| .Case("Last", libc_benchmarks::BenchmarkLog::Last) | |
| .Case("Full", libc_benchmarks::BenchmarkLog::Full) | |
| .Default(std::nullopt); | |
| 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.min_duration); | |
| O.map("MaxDuration", Out.max_duration); | |
| O.map("InitialIterations", Out.initial_iterations); | |
| O.map("MaxIterations", Out.max_iterations); | |
| O.map("MinSamples", Out.min_samples); | |
| O.map("MaxSamples", Out.max_samples); | |
| O.map("Epsilon", Out.epsilon); | |
| O.map("ScalingFactor", Out.scaling_factor); | |
| 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.num_trials); | |
| O.map("IsSweepMode", Out.is_sweep_mode); | |
| O.map("SweepModeMaxSize", Out.sweep_mode_max_size); | |
| O.map("SizeDistributionName", Out.size_distribution_name); | |
| O.map("AccessAlignment", Out.access_alignment); | |
| O.map("MemcmpMismatchAt", Out.memcmp_mismatch_at); | |
| 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.num_sharing); | |
| return O.takeError(); | |
| } | |
| static Error fromJson(const json::Value &V, libc_benchmarks::HostState &Out) { | |
| JsonObjectMapper O(V); | |
| O.map("CpuName", Out.cpu_name); | |
| O.map("CpuFrequency", Out.cpu_frequency); | |
| 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.buffer_size); | |
| O.map("BatchParameterCount", Out.batch_parameter_count); | |
| O.map("BenchmarkOptions", Out.benchmark_options); | |
| return O.takeError(); | |
| } | |
| static Error fromJson(const json::Value &V, libc_benchmarks::Study &Out) { | |
| JsonObjectMapper O(V); | |
| O.map("StudyName", Out.study_name); | |
| 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.min_duration)); | |
| JOS.attribute("MaxDuration", seconds(BO.max_duration)); | |
| JOS.attribute("InitialIterations", BO.initial_iterations); | |
| JOS.attribute("MaxIterations", BO.max_iterations); | |
| JOS.attribute("MinSamples", BO.min_samples); | |
| JOS.attribute("MaxSamples", BO.max_samples); | |
| JOS.attribute("Epsilon", BO.epsilon); | |
| JOS.attribute("ScalingFactor", BO.scaling_factor); | |
| 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.num_sharing); | |
| } | |
| static void serialize(const StudyConfiguration &SC, json::OStream &JOS) { | |
| JOS.attribute("Function", SC.function); | |
| JOS.attribute("NumTrials", SC.num_trials); | |
| JOS.attribute("IsSweepMode", SC.is_sweep_mode); | |
| JOS.attribute("SweepModeMaxSize", SC.sweep_mode_max_size); | |
| JOS.attribute("SizeDistributionName", SC.size_distribution_name); | |
| JOS.attribute("AccessAlignment", | |
| static_cast<int64_t>(SC.access_alignment->value())); | |
| JOS.attribute("MemcmpMismatchAt", SC.memcmp_mismatch_at); | |
| } | |
| static void serialize(const HostState &HS, json::OStream &JOS) { | |
| JOS.attribute("CpuName", HS.cpu_name); | |
| JOS.attribute("CpuFrequency", HS.cpu_frequency); | |
| 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.buffer_size); | |
| JOS.attribute("BatchParameterCount", RI.batch_parameter_count); | |
| JOS.attributeObject("BenchmarkOptions", | |
| [&]() { serialize(RI.benchmark_options, JOS); }); | |
| } | |
| void serializeToJson(const Study &S, json::OStream &JOS) { | |
| JOS.object([&]() { | |
| JOS.attribute("StudyName", S.study_name); | |
| 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 |