diff --git a/orc-rt/include/CMakeLists.txt b/orc-rt/include/CMakeLists.txt index 8d78eb1bae61..8e68e8b04ae3 100644 --- a/orc-rt/include/CMakeLists.txt +++ b/orc-rt/include/CMakeLists.txt @@ -1,5 +1,6 @@ set(ORC_RT_HEADERS orc-rt-c/CoreTyspe.h + orc-rt-c/Error.h orc-rt-c/ExternC.h orc-rt-c/WrapperFunction.h orc-rt-c/orc-rt.h diff --git a/orc-rt/include/orc-rt-c/CoreTypes.h b/orc-rt/include/orc-rt-c/CoreTypes.h index 9b3fdbea4149..a0f80b3bfc21 100644 --- a/orc-rt/include/orc-rt-c/CoreTypes.h +++ b/orc-rt/include/orc-rt-c/CoreTypes.h @@ -18,6 +18,11 @@ ORC_RT_C_EXTERN_C_BEGIN +/** + * Opaque reference to an error instance. Null serves as the 'success' value. + */ +typedef struct orc_rt_OpaqueError *orc_rt_ErrorRef; + /** * A reference to an orc_rt::Session instance. */ diff --git a/orc-rt/include/orc-rt-c/Error.h b/orc-rt/include/orc-rt-c/Error.h new file mode 100644 index 000000000000..81f088214dd0 --- /dev/null +++ b/orc-rt/include/orc-rt-c/Error.h @@ -0,0 +1,80 @@ +/*===----------- Error.h - C API for ORC Runtime Errors -----------*- 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 *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This file defines the C interface to LLVM's Error class. *| +|* *| +|* TODO: Explain ownership model. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef ORC_RT_C_ERROR_H +#define ORC_RT_C_ERROR_H + +#include "orc-rt-c/CoreTypes.h" +#include "orc-rt-c/ExternC.h" +#include "orc-rt-c/Visibility.h" + +ORC_RT_C_EXTERN_C_BEGIN + +#define orc_rt_ErrorSuccess ((orc_rt_ErrorRef)0) + +/** + * Error type identifier. + */ +typedef const void *orc_rt_Error_TypeId; + +/** + * Returns the type id for the given error instance, which must be a failure + * value (i.e. non-null). + */ +ORC_RT_C_ABI orc_rt_Error_TypeId orc_rt_Error_getTypeId(orc_rt_ErrorRef Err); + +/** + * Dispose of the given error without handling it. This operation consumes the + * error, and the given orc_rt_ErrorRef value is not usable once this call + * returns. + * Note: This method *only* needs to be called if the error is not being passed + * to some other consuming operation, e.g. LLVMGetErrorMessage. + */ +ORC_RT_C_ABI void orc_rt_Error_consume(orc_rt_ErrorRef Err); + +/** + * Report a fatal error if Err is a failure value. + * + * This function can be used to wrap calls to fallible functions ONLY when it is + * known that the Error will always be a success value. + */ +ORC_RT_C_ABI void orc_rt_Error_cantFail(orc_rt_ErrorRef Err); + +/** + * Returns the given string's error message. This operation consumes the error, + * and the given orc_rt_ErrorRef value is not usable once this call returns. + * The caller is responsible for disposing of the string by calling + * LLVMDisposeErrorMessage. + */ +ORC_RT_C_ABI char *orc_rt_Error_toString(orc_rt_ErrorRef Err); + +/** + * Dispose of the given error message. + */ +ORC_RT_C_ABI void orc_rt_Error_freeErrorMessage(char *ErrMsg); + +/** + * Returns the type id for llvm StringError. + */ +ORC_RT_C_ABI orc_rt_Error_TypeId orc_rt_StringError_getTypeId(void); + +/** + * Create a StringError. + */ +ORC_RT_C_ABI orc_rt_ErrorRef orc_rt_StringError_create(const char *ErrMsg); + +ORC_RT_C_EXTERN_C_END + +#endif // ORC_RT_C_ERROR_H diff --git a/orc-rt/include/orc-rt-c/Visibility.h b/orc-rt/include/orc-rt-c/Visibility.h new file mode 100644 index 000000000000..76e1a4706c2a --- /dev/null +++ b/orc-rt/include/orc-rt-c/Visibility.h @@ -0,0 +1,30 @@ +/*===--- Visibility.h - Visibility macros for the ORC runtime ---*- 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 *| +|* *| +|*===----------------------------------------------------------------------===*| +|* *| +|* This header defines visibility macros used for the ORC runtime C interface.*| +|* These macros are used to annotate C functions that should be exported as *| +|* part of a shared library or DLL. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef ORC_RT_C_VISIBILITY_H +#define ORC_RT_C_VISIBILITY_H + +/* ORC_RT_C_ABI is the export/visibility macro used to mark symbols declared + in orc-rt-c as exported when built as a shared library. */ + +#if defined(__has_attribute) && __has_attribute(visibility) +#define ORC_RT_C_ABI __attribute__((visibility("default"))) +#endif + +#if !defined(ORC_RT_C_ABI) +#define ORC_RT_C_ABI +#endif + +#endif /* ORC_RT_C_VISIBILITY_H */ diff --git a/orc-rt/include/orc-rt/Error.h b/orc-rt/include/orc-rt/Error.h index 07a3be98fd21..3ad277594336 100644 --- a/orc-rt/include/orc-rt/Error.h +++ b/orc-rt/include/orc-rt/Error.h @@ -9,6 +9,7 @@ #ifndef ORC_RT_ERROR_H #define ORC_RT_ERROR_H +#include "orc-rt-c/CoreTypes.h" #include "orc-rt-c/config.h" #include "orc-rt/CallableTraitsHelper.h" #include "orc-rt/Compiler.h" @@ -89,6 +90,8 @@ class ORC_RT_NODISCARD Error { template friend Error handleErrors(Error E, HandlerTs &&...Hs); + friend orc_rt_ErrorRef wrap(Error Err) noexcept; + public: /// Destroy this error. Aborts if error was not checked, or was checked but /// not handled. @@ -212,6 +215,15 @@ template Error make_error(ArgTs &&...Args) { return make_error(std::make_unique(std::forward(Args)...)); } +inline orc_rt_ErrorRef wrap(Error Err) noexcept { + return reinterpret_cast(Err.takePayload().release()); +} + +inline Error unwrap(orc_rt_ErrorRef ErrRef) noexcept { + return make_error(std::unique_ptr( + reinterpret_cast(ErrRef))); +} + namespace detail { template struct ErrorHandlerTraitsImpl; diff --git a/orc-rt/lib/executor/Error.cpp b/orc-rt/lib/executor/Error.cpp index 6cf85f47eddf..495df9c104af 100644 --- a/orc-rt/lib/executor/Error.cpp +++ b/orc-rt/lib/executor/Error.cpp @@ -6,11 +6,13 @@ // //===----------------------------------------------------------------------===// // -// Contains the implementation of APIs in the orc-rt/Error.h header. +// Contains the implementation of APIs in the orc-rt/Error.h and +// orc-rt-c/Error.h headers. // //===----------------------------------------------------------------------===// #include "orc-rt/Error.h" +#include "orc-rt-c/Error.h" #include @@ -45,4 +47,31 @@ std::string ExceptionError::toString() const noexcept { #endif // ORC_RT_ENABLE_EXCEPTIONS +extern "C" orc_rt_Error_TypeId orc_rt_Error_getTypeId(orc_rt_ErrorRef Err) { + assert(Err && "Err must not be null"); + return reinterpret_cast(Err)->dynamicClassID(); +} + +extern "C" void orc_rt_Error_consume(orc_rt_ErrorRef Err) { + consumeError(unwrap(Err)); +} + +extern "C" void orc_rt_Error_cantFail(orc_rt_ErrorRef Err) { + cantFail(unwrap(Err)); +} + +extern "C" char *orc_rt_Error_toString(orc_rt_ErrorRef Err) { + return strdup(toString(unwrap(Err)).c_str()); +} + +extern "C" void orc_rt_Error_freeErrorMessage(char *ErrMsg) { free(ErrMsg); } + +extern "C" orc_rt_Error_TypeId orc_rt_StringError_getTypeId(void) { + return StringError::classID(); +} + +extern "C" orc_rt_ErrorRef orc_rt_StringError_create(const char *ErrMsg) { + return wrap(make_error(ErrMsg)); +} + } // namespace orc_rt diff --git a/orc-rt/unittests/CMakeLists.txt b/orc-rt/unittests/CMakeLists.txt index 9edff560cd45..963e4da71a20 100644 --- a/orc-rt/unittests/CMakeLists.txt +++ b/orc-rt/unittests/CMakeLists.txt @@ -16,6 +16,7 @@ add_orc_rt_unittest(CoreTests BitmaskEnumTest.cpp CallableTraitsHelperTest.cpp EndianTest.cpp + ErrorCAPITest.cpp ErrorTest.cpp ErrorExceptionInteropTest.cpp ExecutorAddressTest.cpp diff --git a/orc-rt/unittests/ErrorCAPITest.cpp b/orc-rt/unittests/ErrorCAPITest.cpp new file mode 100644 index 000000000000..5ba9299f57b6 --- /dev/null +++ b/orc-rt/unittests/ErrorCAPITest.cpp @@ -0,0 +1,176 @@ +//===- ErrorCAPITest.cpp - Tests for Error C API --------------------------===// +// +// 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 +// +//===----------------------------------------------------------------------===// +// +// This file tests the C API for ORC runtime errors defined in orc-rt-c/Error.h. +// +//===----------------------------------------------------------------------===// + +#include "orc-rt-c/Error.h" +#include "orc-rt/Error.h" +#include "gtest/gtest.h" + +#include + +using namespace orc_rt; + +namespace { + +// Test that wrapping a success value produces null. +TEST(ErrorCAPITest, WrapSuccess) { + orc_rt_ErrorRef ErrRef = wrap(Error::success()); + EXPECT_EQ(ErrRef, orc_rt_ErrorSuccess); +} + +// Test that wrap/unwrap round-trips correctly for error values. +TEST(ErrorCAPITest, WrapUnwrapRoundTrip) { + Error Original = make_error("test error"); + orc_rt_ErrorRef ErrRef = wrap(std::move(Original)); + + EXPECT_NE(ErrRef, orc_rt_ErrorSuccess); + + Error Restored = unwrap(ErrRef); + EXPECT_TRUE(Restored.isA()); + EXPECT_EQ(toString(std::move(Restored)), "test error"); +} + +// Test that unwrapping null produces a success value. +TEST(ErrorCAPITest, UnwrapSuccess) { + Error E = unwrap(orc_rt_ErrorSuccess); + EXPECT_FALSE(E) << "Unwrapping null should produce success"; +} + +// Test orc_rt_Error_getTypeId returns the correct type ID. +TEST(ErrorCAPITest, GetTypeId) { + orc_rt_ErrorRef ErrRef = orc_rt_StringError_create("test"); + orc_rt_Error_TypeId TypeId = orc_rt_Error_getTypeId(ErrRef); + + EXPECT_EQ(TypeId, orc_rt_StringError_getTypeId()); + + orc_rt_Error_consume(ErrRef); +} + +// Test orc_rt_Error_consume properly disposes of an error. +TEST(ErrorCAPITest, Consume) { + orc_rt_ErrorRef ErrRef = orc_rt_StringError_create("test"); + EXPECT_NE(ErrRef, orc_rt_ErrorSuccess); + + // Should not crash or leak. + orc_rt_Error_consume(ErrRef); +} + +// Test orc_rt_Error_cantFail with success value. +TEST(ErrorCAPITest, CantFailSuccess) { + // Should not crash. + orc_rt_Error_cantFail(orc_rt_ErrorSuccess); +} + +// Test orc_rt_Error_cantFail aborts on failure value. +TEST(ErrorCAPITest, CantFailFailure) { + EXPECT_DEATH( + { orc_rt_Error_cantFail(orc_rt_StringError_create("test")); }, "") + << "orc_rt_Error_cantFail did not abort on failure value"; +} + +// Test orc_rt_Error_toString returns the error message and consumes the error. +TEST(ErrorCAPITest, ToString) { + orc_rt_ErrorRef ErrRef = orc_rt_StringError_create("hello world"); + char *Msg = orc_rt_Error_toString(ErrRef); + + EXPECT_STREQ(Msg, "hello world"); + + orc_rt_Error_freeErrorMessage(Msg); +} + +// Test orc_rt_StringError_create creates an error with the correct message. +TEST(ErrorCAPITest, StringErrorCreate) { + const char *TestMsg = "custom error message"; + orc_rt_ErrorRef ErrRef = orc_rt_StringError_create(TestMsg); + + EXPECT_NE(ErrRef, orc_rt_ErrorSuccess); + + // Verify it's a StringError. + EXPECT_EQ(orc_rt_Error_getTypeId(ErrRef), orc_rt_StringError_getTypeId()); + + // Verify the message. + char *Msg = orc_rt_Error_toString(ErrRef); + EXPECT_STREQ(Msg, TestMsg); + orc_rt_Error_freeErrorMessage(Msg); +} + +// Test orc_rt_StringError_getTypeId returns a consistent value. +TEST(ErrorCAPITest, StringErrorTypeIdConsistent) { + orc_rt_Error_TypeId TypeId1 = orc_rt_StringError_getTypeId(); + orc_rt_Error_TypeId TypeId2 = orc_rt_StringError_getTypeId(); + + EXPECT_EQ(TypeId1, TypeId2); + EXPECT_NE(TypeId1, nullptr); +} + +// Test that C API type ID matches C++ StringError class ID. +TEST(ErrorCAPITest, StringErrorTypeIdMatchesCpp) { + orc_rt_Error_TypeId CTypeId = orc_rt_StringError_getTypeId(); + const void *CppTypeId = StringError::classID(); + + EXPECT_EQ(CTypeId, CppTypeId); +} + +// Test creating and consuming multiple errors. +TEST(ErrorCAPITest, MultipleErrors) { + orc_rt_ErrorRef Err1 = orc_rt_StringError_create("error 1"); + orc_rt_ErrorRef Err2 = orc_rt_StringError_create("error 2"); + orc_rt_ErrorRef Err3 = orc_rt_StringError_create("error 3"); + + EXPECT_NE(Err1, orc_rt_ErrorSuccess); + EXPECT_NE(Err2, orc_rt_ErrorSuccess); + EXPECT_NE(Err3, orc_rt_ErrorSuccess); + + char *Msg1 = orc_rt_Error_toString(Err1); + char *Msg2 = orc_rt_Error_toString(Err2); + char *Msg3 = orc_rt_Error_toString(Err3); + + EXPECT_STREQ(Msg1, "error 1"); + EXPECT_STREQ(Msg2, "error 2"); + EXPECT_STREQ(Msg3, "error 3"); + + orc_rt_Error_freeErrorMessage(Msg1); + orc_rt_Error_freeErrorMessage(Msg2); + orc_rt_Error_freeErrorMessage(Msg3); +} + +// Test wrapping a custom C++ error type and checking its type via C API. +class CustomCAPITestError + : public ErrorExtends { +public: + CustomCAPITestError(int Code) : Code(Code) {} + std::string toString() const noexcept override { + return "CustomCAPITestError: " + std::to_string(Code); + } + int getCode() const { return Code; } + +private: + int Code; +}; + +TEST(ErrorCAPITest, CustomErrorTypeId) { + Error CppError = make_error(42); + orc_rt_ErrorRef ErrRef = wrap(std::move(CppError)); + + orc_rt_Error_TypeId TypeId = orc_rt_Error_getTypeId(ErrRef); + + // Should not be a StringError. + EXPECT_NE(TypeId, orc_rt_StringError_getTypeId()); + + // Should match the C++ class ID. + EXPECT_EQ(TypeId, CustomCAPITestError::classID()); + + char *Msg = orc_rt_Error_toString(ErrRef); + EXPECT_STREQ(Msg, "CustomCAPITestError: 42"); + orc_rt_Error_freeErrorMessage(Msg); +} + +} // end anonymous namespace