//===-------- 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 #include #include #include namespace __orc_rt { /// Base class for all errors. class ErrorInfoBase : public RTTIExtends { public: virtual std::string toString() const = 0; }; /// Represents an environmental error. class ORC_RT_NODISCARD Error { template friend Error make_error(ArgTs &&...Args); friend Error repackage_error(std::unique_ptr); template friend std::unique_ptr error_cast(Error &); template 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 bool isA() const { return getPtr() && getPtr()->isA(); } private: Error() = default; Error(std::unique_ptr ErrInfo) { auto RawErrPtr = reinterpret_cast(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 ErrT *getPtr() const { return reinterpret_cast(ErrPtr & ~uintptr_t(1)); } void setPtr(ErrorInfoBase *Ptr) { ErrPtr = (reinterpret_cast(Ptr) & ~uintptr_t(1)) | (ErrPtr & 1); } bool isChecked() const { return ErrPtr & 0x1; } void setChecked(bool Checked) { ErrPtr = (ErrPtr & ~uintptr_t(1)) | Checked; } template std::unique_ptr takePayload() { static_assert(std::is_base_of::value, "ErrT is not an ErrorInfoBase subclass"); std::unique_ptr Tmp(getPtr()); setPtr(nullptr); setChecked(true); return Tmp; } uintptr_t ErrPtr = 0; }; /// Construct an error of ErrT with the given arguments. template Error make_error(ArgTs &&...Args) { static_assert(std::is_base_of::value, "ErrT is not an ErrorInfoBase subclass"); return Error(std::make_unique(std::forward(Args)...)); } /// Construct an error of ErrT using a std::unique_ptr. 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 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. Otherwise returns a nullptr and /// leaves the error untouched. Common usage looks like: /// /// \code{.cpp} /// if (Error E = foo()) { /// if (auto EV1 = error_cast(E)) { /// // use unwrapped EV1 value. /// } else if (EV2 = error_cast(E)) { /// // use unwrapped EV2 value. /// } ... /// } /// \endcode template std::unique_ptr error_cast(Error &Err) { static_assert(std::is_base_of::value, "ErrT is not an ErrorInfoBase subclass"); if (Err.isA()) return Err.takePayload(); 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 class ORC_RT_NODISCARD Expected { template friend class Expected; static constexpr bool IsRef = std::is_reference::value; using wrap = std::reference_wrapper>; using error_type = std::unique_ptr; using storage_type = std::conditional_t; using value_type = T; using reference = std::remove_reference_t &; using const_reference = const std::remove_reference_t &; using pointer = std::remove_reference_t *; using const_pointer = const std::remove_reference_t *; public: /// Create an Expected from a failure value. Expected(Error Err) : HasError(true), Unchecked(true) { assert(Err && "Cannot create Expected from Error success value"); new (getErrorStorage()) error_type(Err.takePayload()); } /// Create an Expected from a T value. template Expected(OtherT &&Val, std::enable_if_t::value> * = nullptr) : HasError(false), Unchecked(true) { new (getStorage()) storage_type(std::forward(Val)); } /// Move-construct an Expected from an Expected. Expected(Expected &&Other) { moveConstruct(std::move(Other)); } /// Move construct an Expected value from an Expected, where OtherT /// must be convertible to T. template Expected( Expected &&Other, std::enable_if_t::value> * = nullptr) { moveConstruct(std::move(Other)); } /// Move construct an Expected value from an Expected, where OtherT /// isn't convertible to T. template explicit Expected( Expected &&Other, std::enable_if_t::value> * = nullptr) { moveConstruct(std::move(Other)); } /// Move-assign from another Expected. Expected &operator=(Expected &&Other) { moveAssign(std::move(Other)); return *this; } /// Destroy an Expected. ~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 bool isFailureOfType() const { return HasError && (*getErrorStorage())->template isFailureOfType(); } /// 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 static bool compareThisIfSameType(const T1 &a, const T1 &b) { return &a == &b; } template static bool compareThisIfSameType(const T1 &a, const T2 &b) { return false; } template void moveConstruct(Expected &&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 void moveAssign(Expected &&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(&TStorage); } const storage_type *getStorage() const { assert(!HasError && "Cannot get value when an error exists!"); return reinterpret_cast(&TStorage); } error_type *getErrorStorage() { assert(HasError && "Cannot get error when a value exists!"); return reinterpret_cast(&ErrorStorage); } const error_type *getErrorStorage() const { assert(HasError && "Cannot get error when a value exists!"); return reinterpret_cast(&ErrorStorage); } void assertIsChecked() { if (ORC_RT_UNLIKELY(Unchecked)) { fprintf(stderr, "Expected 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(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 value in the success state. It is a programmatic /// error to call this function on a failure value. template T cantFail(Expected E) { assert(E && "cantFail called on failure value"); consumeError(E.takeError()); return std::move(*E); } /// Auto-unwrap an Expected value in the success state. It is a programmatic /// error to call this function on a failure value. template T &cantFail(Expected 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(Err)) return EIB->toString(); return {}; } class StringError : public RTTIExtends { 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