Skip to content

Commit fb745ea

Browse files
authored
Replace duplicated dispatch test suite with focused smoke tests. (#918)
The dispatch variant re-compiled all 174 tests through dlopen function pointers, doubling CI time without proportional coverage. The inline wrappers are trivial forwards — if one works, they all work. Replace with 14 targeted tests in a standalone DispatchTests binary that does not link libclangCppInterOp, verifying true RTLD_LOCAL isolation: 3 mechanism tests (dlGetProcAddress lookup, missing symbols, load/unload cycle) and 11 smoke tests covering interpreter, scopes, types, functions, variables, templates, construct/destruct, enums, demangle, and version. A ctest guard (ldd/otool) verifies the binary is not directly linked against the library, catching regressions in dispatch isolation. Split CppInterOp.h into CppInterOpTypes.h (types, enums, JitCall) and CppInterOp.h (types + generated declarations). Dispatch.h now includes only CppInterOpTypes.h, making CppInterOp.h and Dispatch.h mutually exclusive with a compile-time #error guard.
1 parent 5d159fb commit fb745ea

15 files changed

Lines changed: 615 additions & 457 deletions

.readthedocs.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ build:
1111
apt_packages:
1212
- clang-20
1313
- cmake
14+
- doxygen
1415
- libclang-20-dev
1516
- llvm-20-dev
1617
- llvm-20-tools

docs/conf.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,6 @@
5454
-DLLVM_DIR=/usr/lib/llvm-20/build/lib/cmake/llvm -DCPPINTEROP_ENABLE_DOXYGEN=ON\
5555
-DCPPINTEROP_INCLUDE_DOCS=ON'.format(CPPINTEROP_ROOT)
5656
subprocess.call(command, shell=True)
57+
# Generate .inc files so doxygen can find the API declarations in CppInterOp.h.
58+
subprocess.call('cmake --build {0}/build --target CppInterOpTableGen'.format(CPPINTEROP_ROOT), shell=True)
5759
subprocess.call('doxygen {0}/build/docs/doxygen.cfg'.format(CPPINTEROP_ROOT), shell=True)

docs/doxygen.cfg.in

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1617,7 +1617,8 @@ SEARCH_INCLUDES = YES
16171617
# contain include files that are not input files but should be processed by
16181618
# the preprocessor.
16191619

1620-
INCLUDE_PATH = @CMAKE_SOURCE_DIR@/include
1620+
INCLUDE_PATH = @CMAKE_SOURCE_DIR@/include \
1621+
@CMAKE_BINARY_DIR@/include
16211622

16221623
# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard
16231624
# patterns (like *.h and *.hpp) to filter out the header-files in the

include/CppInterOp/CppInterOp.h

Lines changed: 5 additions & 333 deletions
Original file line numberDiff line numberDiff line change
@@ -14,341 +14,15 @@
1414
#ifndef CPPINTEROP_CPPINTEROP_H
1515
#define CPPINTEROP_CPPINTEROP_H
1616

17-
#include <cassert>
18-
#include <cstddef>
19-
#include <cstdint>
20-
#include <set>
21-
#include <string>
22-
#include <sys/types.h>
23-
#include <vector>
24-
25-
// The cross-platform CPPINTEROP_API macro definition
26-
#if defined _WIN32 || defined __CYGWIN__
27-
#define CPPINTEROP_API __declspec(dllexport)
28-
#else
29-
#ifdef __GNUC__
30-
#define CPPINTEROP_API __attribute__((__visibility__("default")))
31-
#else
32-
#define CPPINTEROP_API
17+
#ifdef CPPINTEROP_DISPATCH_H
18+
#error "CppInterOp.h and Dispatch.h are mutually exclusive — include only one."
3319
#endif
34-
#endif
35-
36-
namespace CppImpl {
37-
using TCppIndex_t = size_t;
38-
using TCppScope_t = void*;
39-
using TCppConstScope_t = const void*;
40-
using TCppType_t = void*;
41-
using TCppConstType_t = const void*;
42-
using TCppFunction_t = void*;
43-
using TCppConstFunction_t = const void*;
44-
using TCppFuncAddr_t = void*;
45-
using TInterp_t = void*;
46-
using TCppObject_t = void*;
47-
48-
enum Operator : unsigned char {
49-
OP_None,
50-
OP_New,
51-
OP_Delete,
52-
OP_Array_New,
53-
OP_Array_Delete,
54-
OP_Plus,
55-
OP_Minus,
56-
OP_Star,
57-
OP_Slash,
58-
OP_Percent,
59-
OP_Caret,
60-
OP_Amp,
61-
OP_Pipe,
62-
OP_Tilde,
63-
OP_Exclaim,
64-
OP_Equal,
65-
OP_Less,
66-
OP_Greater,
67-
OP_PlusEqual,
68-
OP_MinusEqual,
69-
OP_StarEqual,
70-
OP_SlashEqual,
71-
OP_PercentEqual,
72-
OP_CaretEqual,
73-
OP_AmpEqual,
74-
OP_PipeEqual,
75-
OP_LessLess,
76-
OP_GreaterGreater,
77-
OP_LessLessEqual,
78-
OP_GreaterGreaterEqual,
79-
OP_EqualEqual,
80-
OP_ExclaimEqual,
81-
OP_LessEqual,
82-
OP_GreaterEqual,
83-
OP_Spaceship,
84-
OP_AmpAmp,
85-
OP_PipePipe,
86-
OP_PlusPlus,
87-
OP_MinusMinus,
88-
OP_Comma,
89-
OP_ArrowStar,
90-
OP_Arrow,
91-
OP_Call,
92-
OP_Subscript,
93-
OP_Conditional,
94-
OP_Coawait,
95-
};
96-
97-
enum OperatorArity : unsigned char { kUnary = 1, kBinary, kBoth };
98-
enum Signedness : unsigned char { kSigned = 1, kUnsigned };
99-
100-
/// Enum modelling CVR qualifiers.
101-
enum QualKind : unsigned char {
102-
Const = 1 << 0,
103-
Volatile = 1 << 1,
104-
Restrict = 1 << 2,
105-
All = Const | Volatile | Restrict
106-
};
107-
108-
/// Enum modelling programming languages.
109-
enum class InterpreterLanguage : unsigned char {
110-
Unknown,
111-
Asm,
112-
CIR,
113-
LLVM_IR,
114-
C,
115-
CPlusPlus,
116-
ObjC,
117-
ObjCPlusPlus,
118-
OpenCL,
119-
OpenCLCXX,
120-
CUDA,
121-
HIP,
122-
HLSL
123-
};
124-
125-
/// Enum modelling language standards.
126-
enum class InterpreterLanguageStandard : unsigned char {
127-
c89,
128-
c94,
129-
gnu89,
130-
c99,
131-
gnu99,
132-
c11,
133-
gnu11,
134-
c17,
135-
gnu17,
136-
c23,
137-
gnu23,
138-
c2y,
139-
gnu2y,
140-
cxx98,
141-
gnucxx98,
142-
cxx11,
143-
gnucxx11,
144-
cxx14,
145-
gnucxx14,
146-
cxx17,
147-
gnucxx17,
148-
cxx20,
149-
gnucxx20,
150-
cxx23,
151-
gnucxx23,
152-
cxx26,
153-
gnucxx26,
154-
opencl10,
155-
opencl11,
156-
opencl12,
157-
opencl20,
158-
opencl30,
159-
openclcpp10,
160-
openclcpp2021,
161-
hlsl,
162-
hlsl2015,
163-
hlsl2016,
164-
hlsl2017,
165-
hlsl2018,
166-
hlsl2021,
167-
hlsl202x,
168-
hlsl202y,
169-
lang_unspecified
170-
};
171-
inline QualKind operator|(QualKind a, QualKind b) {
172-
return static_cast<QualKind>(static_cast<unsigned char>(a) |
173-
static_cast<unsigned char>(b));
174-
}
175-
176-
enum class ValueKind : std::uint8_t {
177-
None,
178-
LValue,
179-
RValue,
180-
};
181-
182-
/// A class modeling function calls for functions produced by the interpreter
183-
/// in compiled code. It provides an information if we are calling a standard
184-
/// function, constructor or destructor.
185-
class JitCall {
186-
public:
187-
friend CPPINTEROP_API JitCall MakeFunctionCallable(TInterp_t I,
188-
TCppConstFunction_t func);
189-
enum Kind : char {
190-
kUnknown = 0,
191-
kGenericCall,
192-
kConstructorCall,
193-
kDestructorCall,
194-
};
195-
struct ArgList {
196-
void** m_Args = nullptr;
197-
size_t m_ArgSize = 0;
198-
// Clang struggles with =default...
199-
ArgList() {}
200-
ArgList(void** Args, size_t ArgSize) : m_Args(Args), m_ArgSize(ArgSize) {}
201-
};
202-
// FIXME: Figure out how to unify the wrapper signatures.
203-
// FIXME: Hide these implementation details by moving wrapper generation in
204-
// this class.
205-
// (self, nargs, args, result, nary)
206-
using GenericCall = void (*)(void*, size_t, void**, void*);
207-
// (result, nary, nargs, args, is_arena)
208-
using ConstructorCall = void (*)(void*, size_t, size_t, void**, void*);
209-
// (self, nary, withFree)
210-
using DestructorCall = void (*)(void*, size_t, int);
211-
212-
private:
213-
union {
214-
GenericCall m_GenericCall;
215-
ConstructorCall m_ConstructorCall;
216-
DestructorCall m_DestructorCall;
217-
};
218-
Kind m_Kind;
219-
TCppConstFunction_t m_FD;
220-
JitCall() : m_GenericCall(nullptr), m_Kind(kUnknown), m_FD(nullptr) {}
221-
JitCall(Kind K, GenericCall C, TCppConstFunction_t FD)
222-
: m_GenericCall(C), m_Kind(K), m_FD(FD) {}
223-
JitCall(Kind K, ConstructorCall C, TCppConstFunction_t Ctor)
224-
: m_ConstructorCall(C), m_Kind(K), m_FD(Ctor) {}
225-
JitCall(Kind K, DestructorCall C, TCppConstFunction_t Dtor)
226-
: m_DestructorCall(C), m_Kind(K), m_FD(Dtor) {}
227-
228-
/// Checks if the passed arguments are valid for the given function.
229-
CPPINTEROP_API bool AreArgumentsValid(void* result, ArgList args, void* self,
230-
size_t nary) const;
23120

232-
/// This function is used for debugging, it reports when the function was
233-
/// called.
234-
CPPINTEROP_API void ReportInvokeStart(void* result, ArgList args,
235-
void* self) const;
236-
CPPINTEROP_API void ReportInvokeStart(void* object, unsigned long nary,
237-
int withFree) const;
238-
void ReportInvokeEnd() const;
21+
#include "CppInterOp/CppInterOpTypes.h"
23922

240-
public:
241-
Kind getKind() const { return m_Kind; }
242-
bool isValid() const { return getKind() != kUnknown; }
243-
bool isInvalid() const { return !isValid(); }
244-
explicit operator bool() const { return isValid(); }
245-
246-
// Specialized for calling void functions.
247-
void Invoke(ArgList args = {}, void* self = nullptr) const {
248-
Invoke(/*result=*/nullptr, args, self);
249-
}
250-
251-
/// Makes a call to a generic function or method.
252-
///\param[in] result - the location where the return result will be placed.
253-
///\param[in] args - a pointer to a argument list and argument size.
254-
///\param[in] self - the 'this pointer' of the object.
255-
// FIXME: Adjust the arguments and their types: args_size can be unsigned;
256-
// self can go in the end and be nullptr by default; result can be a nullptr
257-
// by default. These changes should be synchronized with the wrapper if we
258-
// decide to directly.
259-
void Invoke(void* result, ArgList args = {}, void* self = nullptr) const {
260-
// NOLINTBEGIN(*-type-union-access)
261-
// Its possible the JitCall object deals with structor decls but went
262-
// through Invoke
263-
264-
switch (m_Kind) {
265-
case kUnknown:
266-
assert(0 && "Attempted to call an invalid function declaration");
267-
break;
268-
269-
case kGenericCall:
270-
#ifndef NDEBUG
271-
// We pass 1UL to nary which is only relevant for structors
272-
assert(AreArgumentsValid(result, args, self, 1UL) && "Invalid args!");
273-
ReportInvokeStart(result, args, self);
274-
#endif // NDEBUG
275-
m_GenericCall(self, args.m_ArgSize, args.m_Args, result);
276-
break;
277-
278-
case kConstructorCall:
279-
// Forward if we intended to call a constructor (nary cannot be inferred,
280-
// so we stick to constructing a single object)
281-
InvokeConstructor(result, /*nary=*/1UL, args, self);
282-
break;
283-
case kDestructorCall:
284-
// Forward if we intended to call a dtor with only 1 parameter.
285-
assert(!args.m_Args && "Destructor called with arguments");
286-
InvokeDestructor(result, /*nary=*/0UL, /*withFree=*/true);
287-
break;
288-
}
289-
// NOLINTEND(*-type-union-access)
290-
}
291-
/// Makes a call to a destructor.
292-
///\param[in] object - the pointer of the object whose destructor we call.
293-
///\param[in] nary - the count of the objects we destruct if we deal with an
294-
/// array of objects.
295-
///\param[in] withFree - true if we should call operator delete or false if
296-
/// we should call only the destructor.
297-
// FIXME: Change the type of withFree from int to bool in the wrapper code.
298-
void InvokeDestructor(void* object, unsigned long nary = 0,
299-
int withFree = true) const {
300-
assert(m_Kind == kDestructorCall && "Wrong overload!");
301-
#ifndef NDEBUG
302-
ReportInvokeStart(object, nary, withFree);
303-
#endif // NDEBUG
304-
m_DestructorCall(object, nary, withFree);
305-
}
306-
307-
/// Makes a call to a constructor.
308-
///\param[in] result - the memory address at which we construct the object
309-
/// (placement new).
310-
///\param[in] nary - Use array new if we have to construct an array of
311-
/// objects (nary > 1).
312-
///\param[in] args - a pointer to a argument list and argument size.
313-
///\param[in] is_arena - a pointer that indicates if placement new is to be
314-
/// used
315-
// FIXME: Change the type of withFree from int to bool in the wrapper code.
316-
void InvokeConstructor(void* result, unsigned long nary = 1,
317-
ArgList args = {}, void* is_arena = nullptr) const {
318-
assert(m_Kind == kConstructorCall && "Wrong overload!");
319-
#ifndef NDEBUG
320-
assert(AreArgumentsValid(result, args, /*self=*/nullptr, nary) &&
321-
"Invalid args!");
322-
ReportInvokeStart(result, args, nullptr);
323-
#endif // NDEBUG
324-
m_ConstructorCall(result, nary, args.m_ArgSize, args.m_Args, is_arena);
325-
}
326-
};
327-
328-
/// Holds information for instantiating a template.
329-
struct TemplateArgInfo {
330-
TCppType_t m_Type;
331-
const char* m_IntegralValue;
332-
TemplateArgInfo(TCppScope_t type, const char* integral_value = nullptr)
333-
: m_Type(type), m_IntegralValue(integral_value) {}
334-
};
335-
336-
// FIXME: Rework GetDimensions to make this enum redundant.
337-
namespace DimensionValue {
338-
enum : long int {
339-
UNKNOWN_SIZE = -1,
340-
};
341-
} // namespace DimensionValue
342-
343-
/// @name Stream Redirection
344-
///@{
345-
346-
enum CaptureStreamKind : char {
347-
kStdOut = 1, ///< stdout
348-
kStdErr, ///< stderr
349-
};
23+
#include <sys/types.h>
35024

351-
///@}
25+
namespace CppImpl {
35226

35327
// Public API function declarations. Generated by cppinterop-tblgen
35428
// from CppInterOp.td. Do not edit this section by hand.
@@ -362,8 +36,6 @@ CPPINTEROP_API pid_t GetExecutorPID();
36236

36337
} // namespace CppImpl
36438

365-
#ifndef CPPINTEROP_DISPATCH_H
36639
// NOLINTNEXTLINE(misc-unused-alias-decls)
36740
namespace Cpp = CppImpl;
368-
#endif
36941
#endif // CPPINTEROP_CPPINTEROP_H

0 commit comments

Comments
 (0)