[orc-rt] Add C API for Errors, plus ORC_RT_C_ABI macro. (#178123)
This commit introduces a C interface for the ORC runtime's Error
handling system, enabling C clients and language bindings to work with
ORC errors.
The ORC_RT_C_ABI macro applies __attribute__((visibility("default")))
(on platforms that support it), ensuring C API symbols are exported when
building the ORC runtime as a shared library. In the future I expect
that this will be extended to support other platforms (e.g. dllexport on
Windows).
This commit is contained in:
parent
ddecdcc85c
commit
a95a9465bf
@ -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
|
||||
|
||||
@ -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.
|
||||
*/
|
||||
|
||||
80
orc-rt/include/orc-rt-c/Error.h
Normal file
80
orc-rt/include/orc-rt-c/Error.h
Normal file
@ -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
|
||||
30
orc-rt/include/orc-rt-c/Visibility.h
Normal file
30
orc-rt/include/orc-rt-c/Visibility.h
Normal file
@ -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 */
|
||||
@ -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 <typename... HandlerTs>
|
||||
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 <typename ErrT, typename... ArgTs> Error make_error(ArgTs &&...Args) {
|
||||
return make_error(std::make_unique<ErrT>(std::forward<ArgTs>(Args)...));
|
||||
}
|
||||
|
||||
inline orc_rt_ErrorRef wrap(Error Err) noexcept {
|
||||
return reinterpret_cast<orc_rt_ErrorRef>(Err.takePayload().release());
|
||||
}
|
||||
|
||||
inline Error unwrap(orc_rt_ErrorRef ErrRef) noexcept {
|
||||
return make_error(std::unique_ptr<ErrorInfoBase>(
|
||||
reinterpret_cast<ErrorInfoBase *>(ErrRef)));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename RetT, typename ArgT> struct ErrorHandlerTraitsImpl;
|
||||
|
||||
@ -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 <system_error>
|
||||
|
||||
@ -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<ErrorInfoBase *>(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<StringError>(ErrMsg));
|
||||
}
|
||||
|
||||
} // namespace orc_rt
|
||||
|
||||
@ -16,6 +16,7 @@ add_orc_rt_unittest(CoreTests
|
||||
BitmaskEnumTest.cpp
|
||||
CallableTraitsHelperTest.cpp
|
||||
EndianTest.cpp
|
||||
ErrorCAPITest.cpp
|
||||
ErrorTest.cpp
|
||||
ErrorExceptionInteropTest.cpp
|
||||
ExecutorAddressTest.cpp
|
||||
|
||||
176
orc-rt/unittests/ErrorCAPITest.cpp
Normal file
176
orc-rt/unittests/ErrorCAPITest.cpp
Normal file
@ -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 <cstring>
|
||||
|
||||
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<StringError>("test error");
|
||||
orc_rt_ErrorRef ErrRef = wrap(std::move(Original));
|
||||
|
||||
EXPECT_NE(ErrRef, orc_rt_ErrorSuccess);
|
||||
|
||||
Error Restored = unwrap(ErrRef);
|
||||
EXPECT_TRUE(Restored.isA<StringError>());
|
||||
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<CustomCAPITestError, ErrorInfoBase> {
|
||||
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<CustomCAPITestError>(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
|
||||
Loading…
x
Reference in New Issue
Block a user