From 442f2d7bc0bc4f19ce056018231fbed9166c9b08 Mon Sep 17 00:00:00 2001 From: Lang Hames Date: Fri, 28 May 2021 17:08:23 -0700 Subject: [PATCH] [ORC-RT] Add OrcRTCWrapperFunctionResult. OrcRTCWrapperFunctionResult is a C struct that can be used to return serialized results from "wrapper functions" -- functions that deserialize an argument buffer, call through to an actual implementation function, then serialize and return the result of that function. Wrapper functions allow calls between ORC and the ORC Runtime to be written using a single signature, WrapperFunctionResult(const char *ArgData, size_t ArgSize), and without coupling either side to a particular transport mechanism (in-memory, TCP, IPC, ... the actual mechanism will be determined by the TargetProcessControl implementation). OrcRTCWrapperFunctionResult is designed to allow small serialized buffers to be returned by value, with larger serialized results stored on the heap. They also provide an error state to report failures in serialization/deserialization. --- compiler-rt/lib/orc/CMakeLists.txt | 1 + compiler-rt/lib/orc/c_api.h | 207 +++++++++++++++++++++++++++ compiler-rt/lib/orc/unittests/CMakeLists.txt | 1 + compiler-rt/lib/orc/unittests/c_api_test.cpp | 200 ++++++++++++++++++++++++++ 4 files changed, 409 insertions(+) create mode 100644 compiler-rt/lib/orc/c_api.h create mode 100644 compiler-rt/lib/orc/unittests/c_api_test.cpp diff --git a/compiler-rt/lib/orc/CMakeLists.txt b/compiler-rt/lib/orc/CMakeLists.txt index 37ca504..fb45f28 100644 --- a/compiler-rt/lib/orc/CMakeLists.txt +++ b/compiler-rt/lib/orc/CMakeLists.txt @@ -13,6 +13,7 @@ set(x86_64_SOURCES set(ORC_IMPL_HEADERS # Implementation headers will go here. adt.h + c_api.h compiler.h endian.h error.h diff --git a/compiler-rt/lib/orc/c_api.h b/compiler-rt/lib/orc/c_api.h new file mode 100644 index 0000000..ac8d344 --- /dev/null +++ b/compiler-rt/lib/orc/c_api.h @@ -0,0 +1,207 @@ +/*===- c_api.h - C API 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 file defines the C API for the ORC runtime *| +|* *| +|* NOTE: The OrtRTWrapperFunctionResult type must be kept in sync with the *| +|* definition in llvm/include/llvm-c/OrcShared.h. *| +|* *| +\*===----------------------------------------------------------------------===*/ + +#ifndef ORC_RT_C_API_H +#define ORC_RT_C_API_H + +#include +#include +#include + +/* Helper to suppress strict prototype warnings. */ +#ifdef __clang__ +#define ORC_RT_C_STRICT_PROTOTYPES_BEGIN \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic error \"-Wstrict-prototypes\"") +#define ORC_RT_C_STRICT_PROTOTYPES_END _Pragma("clang diagnostic pop") +#else +#define ORC_RT_C_STRICT_PROTOTYPES_BEGIN +#define ORC_RT_C_STRICT_PROTOTYPES_END +#endif + +/* Helper to wrap C code for C++ */ +#ifdef __cplusplus +#define ORC_RT_C_EXTERN_C_BEGIN \ + extern "C" { \ + ORC_RT_C_STRICT_PROTOTYPES_BEGIN +#define ORC_RT_C_EXTERN_C_END \ + ORC_RT_C_STRICT_PROTOTYPES_END \ + } +#else +#define ORC_RT_C_EXTERN_C_BEGIN ORC_RT_C_STRICT_PROTOTYPES_BEGIN +#define ORC_RT_C_EXTERN_C_END ORC_RT_C_STRICT_PROTOTYPES_END +#endif + +ORC_RT_C_EXTERN_C_BEGIN + +typedef union { + const char *ValuePtr; + char Value[sizeof(ValuePtr)]; +} OrcRTCWrapperFunctionResultDataUnion; + +/** + * OrcRTCWrapperFunctionResult is a kind of C-SmallVector with an out-of-band + * error state. + * + * If Size == 0 and Data.ValuePtr is non-zero then the value is in the + * 'out-of-band error' state, and Data.ValuePtr points at a malloc-allocated, + * null-terminated string error message. + * + * If Size <= sizeof(OrcRTCWrapperFunctionResultData) then the value is in the + * 'small' state and the content is held in the first Size bytes of Data.Value. + * + * If Size > sizeof(OrtRTCWrapperFunctionResultData) then the value is in the + * 'large' state and the content is held in the first Size bytes of the + * memory pointed to by Data.ValuePtr. This memory must have been allocated by + * malloc, and will be freed with free when this value is destroyed. + */ +typedef struct { + OrcRTCWrapperFunctionResultDataUnion Data; + size_t Size; +} OrcRTCWrapperFunctionResult; + +typedef struct OrcRTCSharedOpaqueJITProcessControl + *OrcRTSharedJITProcessControlRef; + +/** + * Zero-initialize an OrcRTCWrapperFunctionResult. + */ +static inline void +OrcRTCWrapperFunctionResultInit(OrcRTCWrapperFunctionResult *R) { + R->Size = 0; + R->Data.ValuePtr = 0; +} + +/** + * Create an OrcRTCWrapperFunctionResult with an uninitialized buffer of size + * Size. The buffer is returned via the DataPtr argument. + */ +static inline char * +OrcRTCWrapperFunctionResultAllocate(OrcRTCWrapperFunctionResult *R, + size_t Size) { + char *DataPtr; + R->Size = Size; + if (Size > sizeof(R->Data.Value)) { + DataPtr = (char *)malloc(Size); + R->Data.ValuePtr = DataPtr; + } else + DataPtr = R->Data.Value; + return DataPtr; +} + +/** + * Create an OrcRTWrapperFunctionResult from the given data range. + */ +static inline OrcRTCWrapperFunctionResult +OrcRTCreateCWrapperFunctionResultFromRange(const char *Data, size_t Size) { + OrcRTCWrapperFunctionResult R; + R.Size = Size; + if (R.Size > sizeof(R.Data.Value)) { + char *Tmp = (char *)malloc(Size); + memcpy(Tmp, Data, Size); + R.Data.ValuePtr = Tmp; + } else + memcpy(R.Data.Value, Data, Size); + return R; +} + +/** + * Create an OrcRTCWrapperFunctionResult by copying the given string, including + * the null-terminator. + * + * This function copies the input string. The client is responsible for freeing + * the ErrMsg arg. + */ +static inline OrcRTCWrapperFunctionResult +OrcRTCreateCWrapperFunctionResultFromString(const char *Source) { + return OrcRTCreateCWrapperFunctionResultFromRange(Source, strlen(Source) + 1); +} + +/** + * Create an OrcRTCWrapperFunctionResult representing an out-of-band + * error. + * + * This function takes ownership of the string argument which must have been + * allocated with malloc. + */ +static inline OrcRTCWrapperFunctionResult +OrcRTCreateCWrapperFunctionResultFromOutOfBandError(const char *ErrMsg) { + OrcRTCWrapperFunctionResult R; + R.Size = 0; + char *Tmp = (char *)malloc(strlen(ErrMsg) + 1); + strcpy(Tmp, ErrMsg); + R.Data.ValuePtr = Tmp; + return R; +} + +/** + * This should be called to destroy OrcRTCWrapperFunctionResult values + * regardless of their state. + */ +static inline void +OrcRTDisposeCWrapperFunctionResult(OrcRTCWrapperFunctionResult *R) { + if (R->Size > sizeof(R->Data.Value) || + (R->Size == 0 && R->Data.ValuePtr)) + free((void *)R->Data.ValuePtr); +} + +/** + * Get a pointer to the data contained in the given + * OrcRTCWrapperFunctionResult. + */ +static inline const char * +OrcRTCWrapperFunctionResultData(const OrcRTCWrapperFunctionResult *R) { + assert((R->Size != 0 || R->Data.ValuePtr == nullptr) && + "Cannot get data for out-of-band error value"); + return R->Size > sizeof(R->Data.Value) ? R->Data.ValuePtr : R->Data.Value; +} + +/** + * Safely get the size of the given OrcRTCWrapperFunctionResult. + * + * Asserts that we're not trying to access the size of an error value. + */ +static inline size_t +OrcRTCWrapperFunctionResultSize(const OrcRTCWrapperFunctionResult *R) { + assert((R->Size != 0 || R->Data.ValuePtr == nullptr) && + "Cannot get size for out-of-band error value"); + return R->Size; +} + +/** + * Returns 1 if this value is equivalent to a value just initialized by + * OrcRTCWrapperFunctionResultInit, 0 otherwise. + */ +static inline size_t +OrcRTCWrapperFunctionResultEmpty(const OrcRTCWrapperFunctionResult *R) { + return R->Size == 0 && R->Data.ValuePtr == 0; +} + +/** + * Returns a pointer to the out-of-band error string for this + * OrcRTCWrapperFunctionResult, or null if there is no error. + * + * The OrcRTCWrapperFunctionResult retains ownership of the error + * string, so it should be copied if the caller wishes to preserve it. + */ +static inline const char *OrcRTCWrapperFunctionResultGetOutOfBandError( + const OrcRTCWrapperFunctionResult *R) { + return R->Size == 0 ? R->Data.ValuePtr : 0; +} + +ORC_RT_C_EXTERN_C_END + +#endif /* ORC_RT_C_API_H */ diff --git a/compiler-rt/lib/orc/unittests/CMakeLists.txt b/compiler-rt/lib/orc/unittests/CMakeLists.txt index 6ed0f30..b784f22 100644 --- a/compiler-rt/lib/orc/unittests/CMakeLists.txt +++ b/compiler-rt/lib/orc/unittests/CMakeLists.txt @@ -81,6 +81,7 @@ endmacro() set(UNITTEST_SOURCES adt_test.cpp + c_api_test.cpp endian_test.cpp error_test.cpp extensible_rtti_test.cpp diff --git a/compiler-rt/lib/orc/unittests/c_api_test.cpp b/compiler-rt/lib/orc/unittests/c_api_test.cpp new file mode 100644 index 0000000..1400981 --- /dev/null +++ b/compiler-rt/lib/orc/unittests/c_api_test.cpp @@ -0,0 +1,200 @@ +//===-- c_api_test.cpp ----------------------------------------------------===// +// +// 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 is a part of the ORC runtime. +// +//===----------------------------------------------------------------------===// + +#include "c_api.h" +#include "gtest/gtest.h" + +TEST(CAPITest, CWrapperFunctionResultInit) { + OrcRTCWrapperFunctionResult R; + OrcRTCWrapperFunctionResultInit(&R); + + EXPECT_EQ(R.Size, 0U); + EXPECT_EQ(R.Data.ValuePtr, nullptr); + + // Check that this value isn't treated as an out-of-band error. + EXPECT_EQ(OrcRTCWrapperFunctionResultGetOutOfBandError(&R), nullptr); + + // Check that we can dispose of the value. + OrcRTDisposeCWrapperFunctionResult(&R); +} + +TEST(CAPITest, CWrapperFunctionResultAllocSmall) { + constexpr size_t SmallAllocSize = sizeof(const char *); + + OrcRTCWrapperFunctionResult R; + char *DataPtr = OrcRTCWrapperFunctionResultAllocate(&R, SmallAllocSize); + + for (size_t I = 0; I != SmallAllocSize; ++I) + DataPtr[I] = 0x55 + I; + + // Check that the inline storage in R.Data.Value contains the expected + // sequence. + EXPECT_EQ(R.Size, SmallAllocSize); + for (size_t I = 0; I != SmallAllocSize; ++I) + EXPECT_EQ(R.Data.Value[I], (char)(0x55 + I)) + << "Unexpected value at index " << I; + + // Check that this value isn't treated as an out-of-band error. + EXPECT_EQ(OrcRTCWrapperFunctionResultGetOutOfBandError(&R), nullptr); + + // Check that OrcRTCWrapperFunctionResult(Data|Result|Size) and + // OrcRTCWrapperFunctionResultGetOutOfBandError behave as expected. + EXPECT_EQ(OrcRTCWrapperFunctionResultData(&R), R.Data.Value); + EXPECT_EQ(OrcRTCWrapperFunctionResultSize(&R), SmallAllocSize); + EXPECT_FALSE(OrcRTCWrapperFunctionResultEmpty(&R)); + EXPECT_EQ(OrcRTCWrapperFunctionResultGetOutOfBandError(&R), nullptr); + + // Check that we can dispose of the value. + OrcRTDisposeCWrapperFunctionResult(&R); +} + +TEST(CAPITest, CWrapperFunctionResultAllocLarge) { + constexpr size_t LargeAllocSize = sizeof(const char *) + 1; + + OrcRTCWrapperFunctionResult R; + char *DataPtr = OrcRTCWrapperFunctionResultAllocate(&R, LargeAllocSize); + + for (size_t I = 0; I != LargeAllocSize; ++I) + DataPtr[I] = 0x55 + I; + + // Check that the inline storage in R.Data.Value contains the expected + // sequence. + EXPECT_EQ(R.Size, LargeAllocSize); + EXPECT_EQ(R.Data.ValuePtr, DataPtr); + for (size_t I = 0; I != LargeAllocSize; ++I) + EXPECT_EQ(R.Data.ValuePtr[I], (char)(0x55 + I)) + << "Unexpected value at index " << I; + + // Check that this value isn't treated as an out-of-band error. + EXPECT_EQ(OrcRTCWrapperFunctionResultGetOutOfBandError(&R), nullptr); + + // Check that OrcRTCWrapperFunctionResult(Data|Result|Size) and + // OrcRTCWrapperFunctionResultGetOutOfBandError behave as expected. + EXPECT_EQ(OrcRTCWrapperFunctionResultData(&R), R.Data.ValuePtr); + EXPECT_EQ(OrcRTCWrapperFunctionResultSize(&R), LargeAllocSize); + EXPECT_FALSE(OrcRTCWrapperFunctionResultEmpty(&R)); + EXPECT_EQ(OrcRTCWrapperFunctionResultGetOutOfBandError(&R), nullptr); + + // Check that we can dispose of the value. + OrcRTDisposeCWrapperFunctionResult(&R); +} + +TEST(CAPITest, CWrapperFunctionResultFromRangeSmall) { + constexpr size_t SmallAllocSize = sizeof(const char *); + + char Source[SmallAllocSize]; + for (size_t I = 0; I != SmallAllocSize; ++I) + Source[I] = 0x55 + I; + + OrcRTCWrapperFunctionResult R = + OrcRTCreateCWrapperFunctionResultFromRange(Source, SmallAllocSize); + + // Check that the inline storage in R.Data.Value contains the expected + // sequence. + EXPECT_EQ(R.Size, SmallAllocSize); + for (size_t I = 0; I != SmallAllocSize; ++I) + EXPECT_EQ(R.Data.Value[I], (char)(0x55 + I)) + << "Unexpected value at index " << I; + + // Check that we can dispose of the value. + OrcRTDisposeCWrapperFunctionResult(&R); +} + +TEST(CAPITest, CWrapperFunctionResultFromRangeLarge) { + constexpr size_t LargeAllocSize = sizeof(const char *) + 1; + + char Source[LargeAllocSize]; + for (size_t I = 0; I != LargeAllocSize; ++I) + Source[I] = 0x55 + I; + + OrcRTCWrapperFunctionResult R = + OrcRTCreateCWrapperFunctionResultFromRange(Source, LargeAllocSize); + + // Check that the inline storage in R.Data.Value contains the expected + // sequence. + EXPECT_EQ(R.Size, LargeAllocSize); + for (size_t I = 0; I != LargeAllocSize; ++I) + EXPECT_EQ(R.Data.ValuePtr[I], (char)(0x55 + I)) + << "Unexpected value at index " << I; + + // Check that we can dispose of the value. + OrcRTDisposeCWrapperFunctionResult(&R); +} + +TEST(CAPITest, CWrapperFunctionResultFromStringSmall) { + constexpr size_t SmallAllocSize = sizeof(const char *); + + char Source[SmallAllocSize]; + for (size_t I = 0; I != SmallAllocSize - 1; ++I) + Source[I] = 'a' + I; + Source[SmallAllocSize - 1] = '\0'; + + OrcRTCWrapperFunctionResult R = + OrcRTCreateCWrapperFunctionResultFromString(Source); + + // Check that the inline storage in R.Data.Value contains the expected + // sequence. + EXPECT_EQ(R.Size, SmallAllocSize); + for (size_t I = 0; I != SmallAllocSize - 1; ++I) + EXPECT_EQ(R.Data.Value[I], (char)('a' + I)) + << "Unexpected value at index " << I; + EXPECT_EQ(R.Data.Value[SmallAllocSize - 1], '\0') + << "Unexpected value at index " << (SmallAllocSize - 1); + + // Check that we can dispose of the value. + OrcRTDisposeCWrapperFunctionResult(&R); +} + +TEST(CAPITest, CWrapperFunctionResultFromStringLarge) { + constexpr size_t LargeAllocSize = sizeof(const char *) + 1; + + char Source[LargeAllocSize]; + for (size_t I = 0; I != LargeAllocSize - 1; ++I) + Source[I] = 'a' + I; + Source[LargeAllocSize - 1] = '\0'; + + OrcRTCWrapperFunctionResult R = + OrcRTCreateCWrapperFunctionResultFromString(Source); + + // Check that the inline storage in R.Data.Value contains the expected + // sequence. + EXPECT_EQ(R.Size, LargeAllocSize); + for (size_t I = 0; I != LargeAllocSize - 1; ++I) + EXPECT_EQ(R.Data.ValuePtr[I], (char)('a' + I)) + << "Unexpected value at index " << I; + EXPECT_EQ(R.Data.ValuePtr[LargeAllocSize - 1], '\0') + << "Unexpected value at index " << (LargeAllocSize - 1); + + // Check that we can dispose of the value. + OrcRTDisposeCWrapperFunctionResult(&R); +} + +TEST(CAPITest, CWrapperFunctionResultFromOutOfBandError) { + constexpr const char *ErrMsg = "test error message"; + OrcRTCWrapperFunctionResult R = + OrcRTCreateCWrapperFunctionResultFromOutOfBandError(ErrMsg); + +#ifndef NDEBUG + EXPECT_DEATH({ OrcRTCWrapperFunctionResultData(&R); }, + "Cannot get data for out-of-band error value"); + EXPECT_DEATH({ OrcRTCWrapperFunctionResultSize(&R); }, + "Cannot get size for out-of-band error value"); +#endif + + EXPECT_FALSE(OrcRTCWrapperFunctionResultEmpty(&R)); + const char *OOBErrMsg = OrcRTCWrapperFunctionResultGetOutOfBandError(&R); + EXPECT_NE(OOBErrMsg, nullptr); + EXPECT_NE(OOBErrMsg, ErrMsg); + EXPECT_TRUE(strcmp(OOBErrMsg, ErrMsg) == 0); + + OrcRTDisposeCWrapperFunctionResult(&R); +} -- 2.7.4