#pragma once // // This file is distributed under the MIT License. See LICENSE.md for details. // #include #include "llvm/IR/DiagnosticHandler.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" #include "revng/ADT/KeyedObjectContainer.h" #include "revng/Support/Error.h" #include "revng/TupleTree/TupleTreeCompatible.h" template concept HasScalarTraits = llvm::yaml::has_ScalarTraits::value; template concept HasScalarEnumTraits = llvm::yaml::has_ScalarEnumerationTraits::value; template concept HasScalarOrEnumTraits = HasScalarTraits or HasScalarEnumTraits; template inline llvm::StringRef getNameFromYAMLEnumScalar(T V) { using namespace llvm::yaml; struct GetScalarIO { llvm::StringRef Result; void enumCase(const T &V, llvm::StringRef Name, const T &M, llvm::yaml::QuotingType = llvm::yaml::QuotingType::None) { if (V == M) { Result = Name; } } }; GetScalarIO ExtractName; llvm::yaml::ScalarEnumerationTraits::enumeration(ExtractName, V); return ExtractName.Result; } template inline std::string getNameFromYAMLScalar(T V) { using namespace llvm::yaml; if constexpr (has_ScalarTraits::value) { std::string Buffer; llvm::raw_string_ostream Stream(Buffer); llvm::yaml::ScalarTraits::output(V, nullptr, Stream); return Buffer; } else { return getNameFromYAMLEnumScalar(V).str(); } } template T getInvalidValueFromYAMLScalar() { // Default action: return Invalid. Users can override this behavior. return T::Invalid; } template inline T getValueFromYAMLScalar(llvm::StringRef Name) { using namespace llvm::yaml; T Result; if constexpr (has_ScalarTraits::value) { llvm::yaml::ScalarTraits::input(Name, nullptr, Result); } else { struct GetScalarIO { bool Found = false; llvm::StringRef TargetName; void enumCase(T &V, llvm::StringRef Name, const T &M, llvm::yaml::QuotingType = llvm::yaml::QuotingType::None) { if (TargetName == Name) { revng_assert(not Found); Found = true; V = M; } } }; GetScalarIO ExtractValue{ false, Name }; llvm::yaml::ScalarEnumerationTraits::enumeration(ExtractValue, Result); if (not ExtractValue.Found) Result = getInvalidValueFromYAMLScalar(); } return Result; } template struct CompositeScalar { static_assert(std::tuple_size_v >= 0); template static void output(const T &Value, void *Context, llvm::raw_ostream &Output) { if constexpr (I < std::tuple_size_v) { if constexpr (I != 0) { Output << Separator; } using element = std::tuple_element_t; Output << getNameFromYAMLScalar(get(Value)); CompositeScalar::output(Value, Context, Output); } } template static llvm::StringRef input(llvm::StringRef Scalar, void *Context, T &Value) { if constexpr (I < std::tuple_size_v) { auto &&[Before, After] = Scalar.split(Separator); using element = std::tuple_element_t; get(Value) = getValueFromYAMLScalar(Before); return CompositeScalar::input(After, Context, Value); } else { revng_assert(Scalar.size() == 0); return Scalar; } } static llvm::yaml::QuotingType mustQuote(llvm::StringRef) { return llvm::yaml::QuotingType::Double; } }; namespace revng::detail { using EC = llvm::yaml::EmptyContext; template concept MappableWithEmptyContext = llvm::yaml::has_MappingTraits::value; } // namespace revng::detail template concept Yamlizable = llvm::yaml::has_DocumentListTraits::value or revng::detail::MappableWithEmptyContext or llvm::yaml::has_SequenceTraits::value or llvm::yaml::has_BlockScalarTraits::value or llvm::yaml::has_CustomMappingTraits::value or llvm::yaml::has_PolymorphicTraits::value or llvm::yaml::has_ScalarTraits::value or llvm::yaml::has_ScalarEnumerationTraits::value; namespace revng::detail { struct NoYaml {}; static_assert(not Yamlizable); } // end namespace revng::detail static_assert(Yamlizable); static_assert(Yamlizable>); constexpr inline auto IsYamlizable = [](auto *K) { return Yamlizable>; }; // How to improve performance without losing safety of a `TupleTree`: // // * `TupleTreeReference` must contain a `std::variant` between what they // have right now and a naked pointer. // * The `operator* const` of `UpcastablePointer` (which should be // renamed to *Variant*) should return a constant reference. Same // for `TupleTreeReference`. // * `TupleTree` should have: // * `const TupleTree freeze()`: `std::move` itself in the `const` // result and transforms all the `TupleTreeReference`s in direct // pointers. // * `TupleTree unfreeze()`: `std::move` itself in the `const` // result and transforms all the `TupleTreeReference`s in root + // key. // * Alternatively, we could push the functionality of `ModelWrapper` // into `TupleTree`. In this way, the default behavior would be to // be frozen. A RAII wrapper could take care of unfreeze and // refreeze the TupleTree. // TODO: `const` stuff is not YAML-serializable template void serialize(S &Stream, T &Element) { if constexpr (std::is_base_of_v) { if constexpr (HasScalarOrEnumTraits) { Stream << llvm::StringRef(getNameFromYAMLScalar(Element)); } else { llvm::yaml::Output YAMLOutput(Stream); YAMLOutput << Element; } } else { std::string Buffer; if constexpr (HasScalarOrEnumTraits) { Buffer = getNameFromYAMLScalar(Element); } else { llvm::raw_string_ostream StringStream(Buffer); llvm::yaml::Output YAMLOutput(StringStream); YAMLOutput << Element; } Stream << Buffer; } } template void serialize(S &Stream, const T &Element) { serialize(Stream, const_cast(Element)); } template llvm::Error serializeToFile(const T &ToWrite, const llvm::StringRef &Path) { std::error_code ErrorCode; llvm::raw_fd_ostream OutFile(Path, ErrorCode, llvm::sys::fs::CD_CreateAlways); if (!!ErrorCode) { return llvm::make_error("Could not open file " + Path.str(), ErrorCode); } serialize(OutFile, ToWrite); return llvm::Error::success(); } template std::string toString(const T &ToDump) { std::string Buffer; { llvm::raw_string_ostream StringStream(Buffer); serialize(StringStream, ToDump); } return Buffer; } namespace revng::detail { inline void yamlDignosticHandler(const llvm::SMDiagnostic &Diagnostic, void *Context) { *static_cast(Context) = Diagnostic.getMessage(); } template llvm::Expected fromStringImpl(llvm::StringRef YAMLString, void *Context = nullptr) { if constexpr (HasScalarOrEnumTraits) { return getValueFromYAMLScalar(YAMLString); } else { T Result; // YAMLInput takes liberty to print to the stderr upon parsing errors, we // instead prefer to wrap the error string in the returned error. This // DiagnosticHandler takes care of that. std::string ErrorMessage; llvm::yaml::Input YAMLInput(YAMLString, Context, yamlDignosticHandler, &ErrorMessage); YAMLInput >> Result; std::error_code EC = YAMLInput.error(); if (EC) return llvm::createStringError(EC, ErrorMessage); return Result; } } } // namespace revng::detail template struct TupleTreeVisitor; // The "natural" deserialization of tuple tree roots is forbidden (as // it does not initialize references), use `TupleTree::deserialize` instead. template concept NotTupleTreeRoot = !requires(T &&) { { TupleTreeVisitor{} }; }; template llvm::Expected fromString(llvm::StringRef YAMLString, void *Context = nullptr) { return revng::detail::fromStringImpl(YAMLString, Context); } template llvm::Expected fromFileOrSTDIN(const llvm::StringRef &Path, void *Context = nullptr) { auto MaybeBuffer = llvm::MemoryBuffer::getFileOrSTDIN(Path); if (not MaybeBuffer) return llvm::errorCodeToError(MaybeBuffer.getError()); return fromString((*MaybeBuffer)->getBuffer(), Context); } template struct llvm::yaml::ScalarTraits> { using ValueType = std::tuple; using ValueTrait = llvm::yaml::ScalarTraits; static void output(const ValueType &Value, void *Context, llvm::raw_ostream &Output) { ValueTrait().output(std::get<0>(Value), Context, Output); } static llvm::StringRef input(llvm::StringRef Scalar, void *Context, ValueType &Value) { return ValueTrait().input(Scalar, Context, std::get<0>(Value)); } static llvm::yaml::QuotingType mustQuote(llvm::StringRef String) { return ValueTrait().mustQuote(String); } }; template<> struct llvm::yaml::ScalarTraits { static_assert(HasScalarTraits); static_assert(sizeof(std::byte) == sizeof(uint8_t)); static void output(const std::byte &Value, void *, llvm::raw_ostream &Out) { Out << std::bit_cast(Value); } static StringRef input(StringRef Scalar, void *Ptr, std::byte &Value) { uint8_t Temporary; auto Err = llvm::yaml::ScalarTraits::input(Scalar, Ptr, Temporary); if (Err.empty()) Value = std::bit_cast(Temporary); return Err; } static QuotingType mustQuote(StringRef Scalar) { return llvm::yaml::ScalarTraits::mustQuote(Scalar); } }; LLVM_YAML_IS_FLOW_SEQUENCE_VECTOR(std::byte);