//===-- 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 #include #include #include #include namespace llvm { namespace libc_benchmarks { template static Error intFromJsonTemplate(const json::Value &V, T &Out) { if (const auto &MaybeInt64 = V.getAsInteger()) { int64_t Value = *MaybeInt64; if (Value < std::numeric_limits::min() || Value > std::numeric_limits::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, 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, uint8_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>(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 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 static Error fromJson(const json::Value &V, std::vector &Out) { return vectorFromJsonTemplate(V, Out); } template static Error fromJson(const json::Value &V, SmallVectorImpl &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 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 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::SizeRange &Out) { JsonObjectMapper O(V); O.map("From", Out.From); O.map("To", Out.To); O.map("Step", Out.Step); return O.takeError(); } static Error fromJson(const json::Value &V, libc_benchmarks::StudyConfiguration &Out) { JsonObjectMapper O(V); O.map("Runs", Out.Runs); O.map("BufferSize", Out.BufferSize); O.map("Size", Out.Size); O.map("AddressAlignment", Out.AddressAlignment); O.map("MemsetValue", Out.MemsetValue); 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::FunctionMeasurements &Out) { JsonObjectMapper O(V); O.map("Name", Out.Name); std::vector Sizes; O.map("Sizes", Sizes); std::vector Runtimes; O.map("Runtimes", Runtimes); if (Sizes.size() != Runtimes.size()) return createStringError(errc::io_error, "Measurement Size and Runtime mistmatch"); Out.Measurements.resize(Sizes.size()); for (size_t I = 0; I < Sizes.size(); ++I) { Out.Measurements[I].Size = Sizes[I]; Out.Measurements[I].Runtime = Runtimes[I]; } return O.takeError(); } static Error fromJson(const json::Value &V, libc_benchmarks::Study &Out) { JsonObjectMapper O(V); O.map("Host", Out.Host); O.map("Options", Out.Options); O.map("Configuration", Out.Configuration); O.map("Functions", Out.Functions); return O.takeError(); } static double Seconds(const Duration &D) { return std::chrono::duration(D).count(); } Expected ParseJsonStudy(StringRef Content) { Expected 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.object([&]() { 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.object([&]() { JOS.attribute("Type", CI.Type); JOS.attribute("Level", CI.Level); JOS.attribute("Size", CI.Size); JOS.attribute("NumSharing", CI.NumSharing); }); } static void Serialize(const HostState &HS, json::OStream &JOS) { JOS.object([&]() { JOS.attribute("CpuName", HS.CpuName); JOS.attribute("CpuFrequency", HS.CpuFrequency); JOS.attributeArray("Caches", [&]() { for (const auto &CI : HS.Caches) Serialize(CI, JOS); }); }); } static void Serialize(const StudyConfiguration &SC, json::OStream &JOS) { JOS.object([&]() { JOS.attribute("Runs", SC.Runs); JOS.attribute("BufferSize", SC.BufferSize); JOS.attributeObject("Size", [&]() { JOS.attribute("From", SC.Size.From); JOS.attribute("To", SC.Size.To); JOS.attribute("Step", SC.Size.Step); }); if (SC.AddressAlignment) JOS.attribute("AddressAlignment", static_cast(SC.AddressAlignment->value())); JOS.attribute("MemsetValue", SC.MemsetValue); JOS.attribute("MemcmpMismatchAt", SC.MemcmpMismatchAt); }); } static void Serialize(const FunctionMeasurements &FM, json::OStream &JOS) { JOS.object([&]() { JOS.attribute("Name", FM.Name); JOS.attributeArray("Sizes", [&]() { for (const auto &M : FM.Measurements) JOS.value(M.Size); }); JOS.attributeArray("Runtimes", [&]() { for (const auto &M : FM.Measurements) JOS.value(Seconds(M.Runtime)); }); }); } void SerializeToJson(const Study &S, json::OStream &JOS) { JOS.object([&]() { JOS.attributeBegin("Host"); Serialize(S.Host, JOS); JOS.attributeEnd(); JOS.attributeBegin("Options"); Serialize(S.Options, JOS); JOS.attributeEnd(); JOS.attributeBegin("Configuration"); Serialize(S.Configuration, JOS); JOS.attributeEnd(); if (!S.Functions.empty()) { JOS.attributeArray("Functions", [&]() { for (const auto &FM : S.Functions) Serialize(FM, JOS); }); } }); } } // namespace libc_benchmarks } // namespace llvm