diff options
Diffstat (limited to 'contrib/llvm-project/compiler-rt/lib/orc/error.h')
-rw-r--r-- | contrib/llvm-project/compiler-rt/lib/orc/error.h | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/contrib/llvm-project/compiler-rt/lib/orc/error.h b/contrib/llvm-project/compiler-rt/lib/orc/error.h new file mode 100644 index 000000000000..b5da0769c637 --- /dev/null +++ b/contrib/llvm-project/compiler-rt/lib/orc/error.h @@ -0,0 +1,426 @@ +//===-------- error.h - Enforced error checking for ORC RT ------*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef ORC_RT_ERROR_H +#define ORC_RT_ERROR_H + +#include "compiler.h" +#include "extensible_rtti.h" +#include "stl_extras.h" + +#include <cassert> +#include <memory> +#include <string> +#include <type_traits> + +namespace __orc_rt { + +/// Base class for all errors. +class ErrorInfoBase : public RTTIExtends<ErrorInfoBase, RTTIRoot> { +public: + virtual std::string toString() const = 0; +}; + +/// Represents an environmental error. +class ORC_RT_NODISCARD Error { + + template <typename ErrT, typename... ArgTs> + friend Error make_error(ArgTs &&...Args); + + friend Error repackage_error(std::unique_ptr<ErrorInfoBase>); + + template <typename ErrT> friend std::unique_ptr<ErrT> error_cast(Error &); + + template <typename T> friend class Expected; + +public: + /// Destroy this error. Aborts if error was not checked, or was checked but + /// not handled. + ~Error() { assertIsChecked(); } + + Error(const Error &) = delete; + Error &operator=(const Error &) = delete; + + /// Move-construct an error. The newly constructed error is considered + /// unchecked, even if the source error had been checked. The original error + /// becomes a checked success value. + Error(Error &&Other) { + setChecked(true); + *this = std::move(Other); + } + + /// Move-assign an error value. The current error must represent success, you + /// you cannot overwrite an unhandled error. The current error is then + /// considered unchecked. The source error becomes a checked success value, + /// regardless of its original state. + Error &operator=(Error &&Other) { + // Don't allow overwriting of unchecked values. + assertIsChecked(); + setPtr(Other.getPtr()); + + // This Error is unchecked, even if the source error was checked. + setChecked(false); + + // Null out Other's payload and set its checked bit. + Other.setPtr(nullptr); + Other.setChecked(true); + + return *this; + } + + /// Create a success value. + static Error success() { return Error(); } + + /// Error values convert to true for failure values, false otherwise. + explicit operator bool() { + setChecked(getPtr() == nullptr); + return getPtr() != nullptr; + } + + /// Return true if this Error contains a failure value of the given type. + template <typename ErrT> bool isA() const { + return getPtr() && getPtr()->isA<ErrT>(); + } + +private: + Error() = default; + + Error(std::unique_ptr<ErrorInfoBase> ErrInfo) { + auto RawErrPtr = reinterpret_cast<uintptr_t>(ErrInfo.release()); + assert((RawErrPtr & 0x1) == 0 && "ErrorInfo is insufficiently aligned"); + ErrPtr = RawErrPtr | 0x1; + } + + void assertIsChecked() { + if (ORC_RT_UNLIKELY(!isChecked() || getPtr())) { + fprintf(stderr, "Error must be checked prior to destruction.\n"); + abort(); // Some sort of JIT program abort? + } + } + + template <typename ErrT = ErrorInfoBase> ErrT *getPtr() const { + return reinterpret_cast<ErrT *>(ErrPtr & ~uintptr_t(1)); + } + + void setPtr(ErrorInfoBase *Ptr) { + ErrPtr = (reinterpret_cast<uintptr_t>(Ptr) & ~uintptr_t(1)) | (ErrPtr & 1); + } + + bool isChecked() const { return ErrPtr & 0x1; } + + void setChecked(bool Checked) { ErrPtr = (ErrPtr & ~uintptr_t(1)) | Checked; } + + template <typename ErrT = ErrorInfoBase> std::unique_ptr<ErrT> takePayload() { + static_assert(std::is_base_of<ErrorInfoBase, ErrT>::value, + "ErrT is not an ErrorInfoBase subclass"); + std::unique_ptr<ErrT> Tmp(getPtr<ErrT>()); + setPtr(nullptr); + setChecked(true); + return Tmp; + } + + uintptr_t ErrPtr = 0; +}; + +/// Construct an error of ErrT with the given arguments. +template <typename ErrT, typename... ArgTs> Error make_error(ArgTs &&...Args) { + static_assert(std::is_base_of<ErrorInfoBase, ErrT>::value, + "ErrT is not an ErrorInfoBase subclass"); + return Error(std::make_unique<ErrT>(std::forward<ArgTs>(Args)...)); +} + +/// Construct an error of ErrT using a std::unique_ptr<ErrorInfoBase>. The +/// primary use-case for this is 're-packaging' errors after inspecting them +/// using error_cast, hence the name. +inline Error repackage_error(std::unique_ptr<ErrorInfoBase> EIB) { + return Error(std::move(EIB)); +} + +/// If the argument is an error of type ErrT then this function unpacks it +/// and returns a std::unique_ptr<ErrT>. Otherwise returns a nullptr and +/// leaves the error untouched. Common usage looks like: +/// +/// \code{.cpp} +/// if (Error E = foo()) { +/// if (auto EV1 = error_cast<ErrorType1>(E)) { +/// // use unwrapped EV1 value. +/// } else if (EV2 = error_cast<ErrorType2>(E)) { +/// // use unwrapped EV2 value. +/// } ... +/// } +/// \endcode +template <typename ErrT> std::unique_ptr<ErrT> error_cast(Error &Err) { + static_assert(std::is_base_of<ErrorInfoBase, ErrT>::value, + "ErrT is not an ErrorInfoBase subclass"); + if (Err.isA<ErrT>()) + return Err.takePayload<ErrT>(); + return nullptr; +} + +/// Helper for Errors used as out-parameters. +/// Sets the 'checked' flag on construction, resets it on destruction. +class ErrorAsOutParameter { +public: + ErrorAsOutParameter(Error *Err) : Err(Err) { + // Raise the checked bit if Err is success. + if (Err) + (void)!!*Err; + } + + ~ErrorAsOutParameter() { + // Clear the checked bit. + if (Err && !*Err) + *Err = Error::success(); + } + +private: + Error *Err; +}; + +template <typename T> class ORC_RT_NODISCARD Expected { + + template <class OtherT> friend class Expected; + + static constexpr bool IsRef = std::is_reference<T>::value; + using wrap = std::reference_wrapper<std::remove_reference_t<T>>; + using error_type = std::unique_ptr<ErrorInfoBase>; + using storage_type = std::conditional_t<IsRef, wrap, T>; + using value_type = T; + + using reference = std::remove_reference_t<T> &; + using const_reference = const std::remove_reference_t<T> &; + using pointer = std::remove_reference_t<T> *; + using const_pointer = const std::remove_reference_t<T> *; + +public: + /// Create an Expected from a failure value. + Expected(Error Err) : HasError(true), Unchecked(true) { + assert(Err && "Cannot create Expected<T> from Error success value"); + new (getErrorStorage()) error_type(Err.takePayload()); + } + + /// Create an Expected from a T value. + template <typename OtherT> + Expected(OtherT &&Val, + std::enable_if_t<std::is_convertible<OtherT, T>::value> * = nullptr) + : HasError(false), Unchecked(true) { + new (getStorage()) storage_type(std::forward<OtherT>(Val)); + } + + /// Move-construct an Expected<T> from an Expected<OtherT>. + Expected(Expected &&Other) { moveConstruct(std::move(Other)); } + + /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT + /// must be convertible to T. + template <class OtherT> + Expected( + Expected<OtherT> &&Other, + std::enable_if_t<std::is_convertible<OtherT, T>::value> * = nullptr) { + moveConstruct(std::move(Other)); + } + + /// Move construct an Expected<T> value from an Expected<OtherT>, where OtherT + /// isn't convertible to T. + template <class OtherT> + explicit Expected( + Expected<OtherT> &&Other, + std::enable_if_t<!std::is_convertible<OtherT, T>::value> * = nullptr) { + moveConstruct(std::move(Other)); + } + + /// Move-assign from another Expected<T>. + Expected &operator=(Expected &&Other) { + moveAssign(std::move(Other)); + return *this; + } + + /// Destroy an Expected<T>. + ~Expected() { + assertIsChecked(); + if (!HasError) + getStorage()->~storage_type(); + else + getErrorStorage()->~error_type(); + } + + /// Returns true if this Expected value is in a success state (holding a T), + /// and false if this Expected value is in a failure state. + explicit operator bool() { + Unchecked = HasError; + return !HasError; + } + + /// Returns true if this Expected value holds an Error of type error_type. + template <typename ErrT> bool isFailureOfType() const { + return HasError && (*getErrorStorage())->template isFailureOfType<ErrT>(); + } + + /// Take ownership of the stored error. + /// + /// If this Expected value is in a success state (holding a T) then this + /// method is a no-op and returns Error::success. + /// + /// If thsi Expected value is in a failure state (holding an Error) then this + /// method returns the contained error and leaves this Expected in an + /// 'empty' state from which it may be safely destructed but not otherwise + /// accessed. + Error takeError() { + Unchecked = false; + return HasError ? Error(std::move(*getErrorStorage())) : Error::success(); + } + + /// Returns a pointer to the stored T value. + pointer operator->() { + assertIsChecked(); + return toPointer(getStorage()); + } + + /// Returns a pointer to the stored T value. + const_pointer operator->() const { + assertIsChecked(); + return toPointer(getStorage()); + } + + /// Returns a reference to the stored T value. + reference operator*() { + assertIsChecked(); + return *getStorage(); + } + + /// Returns a reference to the stored T value. + const_reference operator*() const { + assertIsChecked(); + return *getStorage(); + } + +private: + template <class T1> + static bool compareThisIfSameType(const T1 &a, const T1 &b) { + return &a == &b; + } + + template <class T1, class T2> + static bool compareThisIfSameType(const T1 &a, const T2 &b) { + return false; + } + + template <class OtherT> void moveConstruct(Expected<OtherT> &&Other) { + HasError = Other.HasError; + Unchecked = true; + Other.Unchecked = false; + + if (!HasError) + new (getStorage()) storage_type(std::move(*Other.getStorage())); + else + new (getErrorStorage()) error_type(std::move(*Other.getErrorStorage())); + } + + template <class OtherT> void moveAssign(Expected<OtherT> &&Other) { + assertIsChecked(); + + if (compareThisIfSameType(*this, Other)) + return; + + this->~Expected(); + new (this) Expected(std::move(Other)); + } + + pointer toPointer(pointer Val) { return Val; } + + const_pointer toPointer(const_pointer Val) const { return Val; } + + pointer toPointer(wrap *Val) { return &Val->get(); } + + const_pointer toPointer(const wrap *Val) const { return &Val->get(); } + + storage_type *getStorage() { + assert(!HasError && "Cannot get value when an error exists!"); + return reinterpret_cast<storage_type *>(&TStorage); + } + + const storage_type *getStorage() const { + assert(!HasError && "Cannot get value when an error exists!"); + return reinterpret_cast<const storage_type *>(&TStorage); + } + + error_type *getErrorStorage() { + assert(HasError && "Cannot get error when a value exists!"); + return reinterpret_cast<error_type *>(&ErrorStorage); + } + + const error_type *getErrorStorage() const { + assert(HasError && "Cannot get error when a value exists!"); + return reinterpret_cast<const error_type *>(&ErrorStorage); + } + + void assertIsChecked() { + if (ORC_RT_UNLIKELY(Unchecked)) { + fprintf(stderr, + "Expected<T> must be checked before access or destruction.\n"); + abort(); + } + } + + union { + std::aligned_union_t<1, storage_type> TStorage; + std::aligned_union_t<1, error_type> ErrorStorage; + }; + + bool HasError : 1; + bool Unchecked : 1; +}; + +/// Consume an error without doing anything. +inline void consumeError(Error Err) { + if (Err) + (void)error_cast<ErrorInfoBase>(Err); +} + +/// Consumes success values. It is a programmatic error to call this function +/// on a failure value. +inline void cantFail(Error Err) { + assert(!Err && "cantFail called on failure value"); + consumeError(std::move(Err)); +} + +/// Auto-unwrap an Expected<T> value in the success state. It is a programmatic +/// error to call this function on a failure value. +template <typename T> T cantFail(Expected<T> E) { + assert(E && "cantFail called on failure value"); + consumeError(E.takeError()); + return std::move(*E); +} + +/// Auto-unwrap an Expected<T> value in the success state. It is a programmatic +/// error to call this function on a failure value. +template <typename T> T &cantFail(Expected<T &> E) { + assert(E && "cantFail called on failure value"); + consumeError(E.takeError()); + return *E; +} + +/// Convert the given error to a string. The error value is consumed in the +/// process. +inline std::string toString(Error Err) { + if (auto EIB = error_cast<ErrorInfoBase>(Err)) + return EIB->toString(); + return {}; +} + +class StringError : public RTTIExtends<StringError, ErrorInfoBase> { +public: + StringError(std::string ErrMsg) : ErrMsg(std::move(ErrMsg)) {} + std::string toString() const override { return ErrMsg; } + +private: + std::string ErrMsg; +}; + +} // end namespace __orc_rt + +#endif // ORC_RT_ERROR_H |