Skip to content

Add CppInterOp API Dispatch mechanism #780

Merged
aaronj0 merged 5 commits intocompiler-research:mainfrom
aaronj0:api-dispatch-mechanism
Jan 24, 2026
Merged

Add CppInterOp API Dispatch mechanism #780
aaronj0 merged 5 commits intocompiler-research:mainfrom
aaronj0:api-dispatch-mechanism

Conversation

@aaronj0
Copy link
Copy Markdown
Collaborator

@aaronj0 aaronj0 commented Jan 17, 2026

(fresh PR on top of #730, see #730 (comment))

This PR defines the mechanism which enables dispatching CppInterOp's API without linking to it, preventing any LLVM or Clang symbols from being leaked into the client application.

This allows us to run cppyy without linking to CppInterOp motivated by the use case in ROOT. Can be used to deploy CppInterOp in an environment where dynamic linking is not favourable and the only option is dlopen'ing libClangCppInterOp.so with RTLD_LOCAL

TODO:

@aaronj0 aaronj0 requested a review from vgvassilev January 17, 2026 11:55
@codecov
Copy link
Copy Markdown

codecov bot commented Jan 17, 2026

Codecov Report

❌ Patch coverage is 81.81818% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.32%. Comparing base (9d2f4c5) to head (dd612b1).
⚠️ Report is 5 commits behind head on main.

Files with missing lines Patch % Lines
include/CppInterOp/Dispatch.h 77.77% 6 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #780      +/-   ##
==========================================
+ Coverage   79.30%   79.32%   +0.02%     
==========================================
  Files           9       11       +2     
  Lines        3933     3966      +33     
==========================================
+ Hits         3119     3146      +27     
- Misses        814      820       +6     
Files with missing lines Coverage Δ
include/CppInterOp/CppInterOp.h 95.12% <ø> (ø)
lib/CppInterOp/CXCppInterOp.cpp 49.56% <ø> (ø)
lib/CppInterOp/CppInterOp.cpp 87.73% <ø> (ø)
lib/CppInterOp/Dispatch.cpp 100.00% <100.00%> (ø)
include/CppInterOp/Dispatch.h 77.77% <77.77%> (ø)
Files with missing lines Coverage Δ
include/CppInterOp/CppInterOp.h 95.12% <ø> (ø)
lib/CppInterOp/CXCppInterOp.cpp 49.56% <ø> (ø)
lib/CppInterOp/CppInterOp.cpp 87.73% <ø> (ø)
lib/CppInterOp/Dispatch.cpp 100.00% <100.00%> (ø)
include/CppInterOp/Dispatch.h 77.77% <77.77%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 126. Check the log or trigger a new build to see more.

TYPED_TEST(CppInterOpTest, FunctionReflectionTestGetClassMethods) {
// Reusable empty template args vector. In the dispatch mode, passing an empty initializer list {}
// does not work since the compiler cannot deduce the type for a function pointer
std::vector<Cpp::TemplateArgInfo> empty_templ_args = {};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable 'empty_templ_args' can be made static or moved into an anonymous namespace to enforce internal linkage [misc-use-internal-linkage]

Suggested change
std::vector<Cpp::TemplateArgInfo> empty_templ_args = {};
static std::vector<Cpp::TemplateArgInfo> empty_templ_args = {};

std::vector<Cpp::TemplateArgInfo> args1 = {C.IntTy.getAsOpaquePtr()};
std::vector<Cpp::TemplateArgInfo> args2 = {
Cpp::GetVariableType(Cpp::GetNamed("a"))};
Cpp::GetVariableType(Cpp::GetNamed("a", 0))};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetVariableType(Cpp::GetNamed("a", 0))};
Cpp::GetVariableType(Cpp::GetNamed("a", nullptr))};

std::vector<Cpp::TemplateArgInfo> args4 = {
Cpp::GetVariableType(Cpp::GetNamed("a")),
Cpp::GetVariableType(Cpp::GetNamed("a"))};
Cpp::GetVariableType(Cpp::GetNamed("a", 0)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetVariableType(Cpp::GetNamed("a", 0)),
Cpp::GetVariableType(Cpp::GetNamed("a", nullptr)),

Cpp::GetVariableType(Cpp::GetNamed("a")),
Cpp::GetVariableType(Cpp::GetNamed("a"))};
Cpp::GetVariableType(Cpp::GetNamed("a", 0)),
Cpp::GetVariableType(Cpp::GetNamed("a", 0))};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetVariableType(Cpp::GetNamed("a", 0))};
Cpp::GetVariableType(Cpp::GetNamed("a", nullptr))};

std::vector<Cpp::TemplateArgInfo> args1 = {
Cpp::GetVariableType(Cpp::GetNamed("a")),
Cpp::GetVariableType(Cpp::GetNamed("a"))};
Cpp::GetVariableType(Cpp::GetNamed("a", 0)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetVariableType(Cpp::GetNamed("a", 0)),
Cpp::GetVariableType(Cpp::GetNamed("a", nullptr)),


CppFnPtrTy CppGetProcAddress(const char* funcName) {
auto it = DispatchMap.find(funcName);
if (it == DispatchMap.end())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this code is reachable we should provide some diagnostic message and a test.


Cpp::CreateInterpreter({}, {"--cuda"});
bool success = !Cpp::Declare("#include <cuda.h>");
bool success = !Cpp::Declare("#include <cuda.h>", false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spell out the default argument names in /**/

@vgvassilev vgvassilev mentioned this pull request Jan 20, 2026
10 tasks
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 116. Check the log or trigger a new build to see more.

@aaronj0 aaronj0 force-pushed the api-dispatch-mechanism branch from b6db417 to 7fa58c8 Compare January 20, 2026 11:29
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 106. Check the log or trigger a new build to see more.

auto S = clang_getDefaultConstructor(make_scope(Decls[0], I));
void* object_c = nullptr;
clang_invoke(S, &object_c, nullptr, 0, nullptr);
clang_invoke(S, &object_c, nullptr, 0, 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: multilevel pointer conversion from 'void **' to 'void *', please use explicit cast [bugprone-multi-level-implicit-pointer-conversion]

  clang_invoke(S, &object_c, nullptr, 0, 0);
                  ^

auto S = clang_getDefaultConstructor(make_scope(Decls[0], I));
void* object_c = nullptr;
clang_invoke(S, &object_c, nullptr, 0, nullptr);
clang_invoke(S, &object_c, nullptr, 0, 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
clang_invoke(S, &object_c, nullptr, 0, 0);
clang_invoke(S, &object_c, nullptr, 0, nullptr);

EXPECT_TRUE(FCI1.getKind() == Cpp::JitCall::kGenericCall);
Cpp::JitCall FCI2 =
Cpp::MakeFunctionCallable(Cpp::GetNamed("f2"));
Cpp::MakeFunctionCallable(Cpp::GetNamed("f2", 0));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::MakeFunctionCallable(Cpp::GetNamed("f2", 0));
Cpp::MakeFunctionCallable(Cpp::GetNamed("f2", nullptr));

EXPECT_TRUE(FCI2.getKind() == Cpp::JitCall::kGenericCall);
Cpp::JitCall FCI3 =
Cpp::MakeFunctionCallable(Cpp::GetNamed("f3", Cpp::GetNamed("NS")));
Cpp::MakeFunctionCallable(Cpp::GetNamed("f3", Cpp::GetNamed("NS", 0)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::MakeFunctionCallable(Cpp::GetNamed("f3", Cpp::GetNamed("NS", 0)));
Cpp::MakeFunctionCallable(Cpp::GetNamed("f3", Cpp::GetNamed("NS", nullptr)));

EXPECT_TRUE(FCI3.getKind() == Cpp::JitCall::kGenericCall);
Cpp::JitCall FCI4 =
Cpp::MakeFunctionCallable(Cpp::GetNamed("f4", Cpp::GetNamed("NS")));
Cpp::MakeFunctionCallable(Cpp::GetNamed("f4", Cpp::GetNamed("NS", 0)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::MakeFunctionCallable(Cpp::GetNamed("f4", Cpp::GetNamed("NS", 0)));
Cpp::MakeFunctionCallable(Cpp::GetNamed("f4", Cpp::GetNamed("NS", nullptr)));


Cpp::JitCall FCI5 =
Cpp::MakeFunctionCallable(Cpp::GetNamed("f5", Cpp::GetNamed("NS")));
Cpp::MakeFunctionCallable(Cpp::GetNamed("f5", Cpp::GetNamed("NS", 0)));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::MakeFunctionCallable(Cpp::GetNamed("f5", Cpp::GetNamed("NS", 0)));
Cpp::MakeFunctionCallable(Cpp::GetNamed("f5", Cpp::GetNamed("NS", nullptr)));

)");

clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C");
clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]

  clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C", 0);
                             ^

)");

clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C");
clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: no header providing "clang::NamedDecl" is directly included [misc-include-cleaner]

  clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C", 0);
         ^

)");

clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C");
clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C", 0);
clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C", nullptr);

)");

Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5");
Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5", 0);
Cpp::TCppScope_t set_5 = Cpp::GetNamed("set_5", nullptr);

@aaronj0 aaronj0 force-pushed the api-dispatch-mechanism branch 3 times, most recently from 1584a54 to 161da95 Compare January 21, 2026 13:35
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 96. Check the log or trigger a new build to see more.


Cpp::TCppScope_t TypedefToPrivateClass =
Cpp::GetNamed("TypedefToPrivateClass");
Cpp::GetNamed("TypedefToPrivateClass", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetNamed("TypedefToPrivateClass", 0);
Cpp::GetNamed("TypedefToPrivateClass", nullptr);

};
)");
Cpp::TCppScope_t TOperator = Cpp::GetNamed("TOperator");
Cpp::TCppScope_t TOperator = Cpp::GetNamed("TOperator", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::TCppScope_t TOperator = Cpp::GetNamed("TOperator", 0);
Cpp::TCppScope_t TOperator = Cpp::GetNamed("TOperator", nullptr);


Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1"));
Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2"));
Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1", 0));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1", 0));
Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1", nullptr));

Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1"));
Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2"));
Cpp::TCppType_t K1 = Cpp::GetTypeFromScope(Cpp::GetNamed("K1", 0));
Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2", 0));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2", 0));
Cpp::TCppType_t K2 = Cpp::GetTypeFromScope(Cpp::GetNamed("K2", nullptr));

operators.clear();
Cpp::GetOperator(Cpp::GetScope("N2", Cpp::GetScope("N1")), Cpp::OP_Plus,
operators);
Cpp::GetOperator(Cpp::GetScope("N2", Cpp::GetScope("N1", 0)), Cpp::Operator::OP_Plus,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetOperator(Cpp::GetScope("N2", Cpp::GetScope("N1", 0)), Cpp::Operator::OP_Plus,
Cpp::GetOperator(Cpp::GetScope("N2", Cpp::GetScope("N1", nullptr)), Cpp::Operator::OP_Plus,


std::vector<Cpp::TCppFunction_t> unresolved_candidate_methods;
Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std"),
Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std", 0),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std", 0),
Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std", nullptr),

Cpp::GetClassTemplatedMethods("get", Cpp::GetScope("my_std", 0),
unresolved_candidate_methods);
Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p"));
Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p", 0));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p", 0));
Cpp::TCppType_t p = Cpp::GetTypeFromScope(Cpp::GetNamed("p", nullptr));

unresolved_candidate_methods, {},
{Cpp::GetVariableType(Cpp::GetNamed("tuple_one")),
Cpp::GetVariableType(Cpp::GetNamed("tuple_two"))});
{Cpp::GetVariableType(Cpp::GetNamed("tuple_one", 0)),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
{Cpp::GetVariableType(Cpp::GetNamed("tuple_one", 0)),
{Cpp::GetVariableType(Cpp::GetNamed("tuple_one", nullptr)),

{Cpp::GetVariableType(Cpp::GetNamed("tuple_one")),
Cpp::GetVariableType(Cpp::GetNamed("tuple_two"))});
{Cpp::GetVariableType(Cpp::GetNamed("tuple_one", 0)),
Cpp::GetVariableType(Cpp::GetNamed("tuple_two", 0))});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetVariableType(Cpp::GetNamed("tuple_two", 0))});
Cpp::GetVariableType(Cpp::GetNamed("tuple_two", nullptr))});


Cpp::TCppScope_t bar =
Cpp::GetNamed("bar", Cpp::GetScope("EnumFunctionSameName"));
Cpp::GetNamed("bar", Cpp::GetScope("EnumFunctionSameName", 0));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use nullptr [modernize-use-nullptr]

Suggested change
Cpp::GetNamed("bar", Cpp::GetScope("EnumFunctionSameName", 0));
Cpp::GetNamed("bar", Cpp::GetScope("EnumFunctionSameName", nullptr));

@aaronj0 aaronj0 force-pushed the api-dispatch-mechanism branch from 161da95 to b131bff Compare January 21, 2026 15:32
@aaronj0 aaronj0 force-pushed the api-dispatch-mechanism branch from 671e59b to faea19b Compare January 21, 2026 16:31
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 85. Check the log or trigger a new build to see more.


// Sanity check to verify that critical (and consequently all) functions are loaded
if (!GetInterpreter || !CreateInterpreter) {
std::cerr << "[CppInterOp] Failed to load critical functions\n";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::cerr << "[CppInterOp] Failed to load critical functions\n";
std::cerr << "[CppInterOp Dispatch] Failed to load critical functions\n";


Interp->declare(code);
Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1");
Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1", 0);
Cpp::TCppScope_t ns_N1 = Cpp::GetNamed("N1", DFLT_0);

EXPECT_EQ(get_base_class_name(Decls[10], 0), "<unnamed>");

auto *VD = Cpp::GetNamed("var");
auto *VD = Cpp::GetNamed("var", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto *VD = Cpp::GetNamed("var", 0);
auto *VD = Cpp::GetNamed("var", DFLT_0);

EXPECT_EQ(Cpp::GetCompleteName(TC1_A_Decl), "TC1<A>");

auto* VD1 = Cpp::GetNamed("var1");
auto* VD1 = Cpp::GetNamed("var1", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto* VD1 = Cpp::GetNamed("var1", 0);
auto* VD1 = Cpp::GetNamed("var1", DFLT_0);

std::vector<Cpp::TemplateArgInfo> args1 = {C.IntTy.getAsOpaquePtr()};
auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(),
/*type_size*/ args1.size());
/*type_size*/ args1.size(), false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*type_size*/ args1.size(), false);
/*type_size*/ args1.size() DFLT_FALSE);

std::vector<Cpp::TemplateArgInfo> args1 = {C.IntTy.getAsOpaquePtr()};
auto Instance1 = Cpp::InstantiateTemplate(Decls[0], args1.data(),
/*type_size*/ args1.size());
/*type_size*/ args1.size(), false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*type_size*/ args1.size(), false);
/*type_size*/ args1.size() DFLT_FALSE);


auto Instance2 = Cpp::InstantiateTemplate(Decls[1], nullptr,
/*type_size*/ 0);
/*type_size*/ 0, false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*type_size*/ 0, false);
/*type_size*/ 0 DFLT_FALSE);

std::vector<Cpp::TemplateArgInfo> args3 = {C.IntTy.getAsOpaquePtr()};
auto Instance3 = Cpp::InstantiateTemplate(Decls[2], args3.data(),
/*type_size*/ args3.size());
/*type_size*/ args3.size(), false);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/*type_size*/ args3.size(), false);
/*type_size*/ args3.size() DFLT_FALSE);

Comment on lines +1076 to +1078
auto *v1 = Cpp::GetNamed("v1", 0);
auto *v2 = Cpp::GetNamed("v2", 0);
auto *v3 = Cpp::GetNamed("v3", 0);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto *v1 = Cpp::GetNamed("v1", 0);
auto *v2 = Cpp::GetNamed("v2", 0);
auto *v3 = Cpp::GetNamed("v3", 0);
auto *v1 = Cpp::GetNamed("v1", DFLT_0);
auto *v2 = Cpp::GetNamed("v2", DFLT_0);
auto *v3 = Cpp::GetNamed("v3", DFLT_0);

@vgvassilev vgvassilev force-pushed the api-dispatch-mechanism branch 2 times, most recently from f1a7de9 to c223566 Compare January 23, 2026 15:26
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

There were too many comments to post at once. Showing the first 10 out of 27. Check the log or trigger a new build to see more.

)");

clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C");
clang::NamedDecl* ClassC = (clang::NamedDecl*)Cpp::GetNamed("C" DFLT_NULLPTR);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not use C-style cast to convert between unrelated types [cppcoreguidelines-pro-type-cstyle-cast]

  clang::NamedDecl* ClassC = (clang::NamedDecl*)Cpp::GetNamed("C" DFLT_NULLPTR);
                             ^

)");

clang::NamedDecl *ClassC = (clang::NamedDecl*)Cpp::GetNamed("C");
clang::NamedDecl* ClassC = (clang::NamedDecl*)Cpp::GetNamed("C" DFLT_NULLPTR);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: no header providing "clang::NamedDecl" is directly included [misc-include-cleaner]

  clang::NamedDecl* ClassC = (clang::NamedDecl*)Cpp::GetNamed("C" DFLT_NULLPTR);
         ^

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions


std::string mangled_add_int;
std::string mangled_add_double;
compat::maybeMangleDeclName(Add_int, mangled_add_int);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: no header providing "compat::maybeMangleDeclName" is directly included [misc-include-cleaner]

unittests/CppInterOp/DispatchTest.cpp:6:

- #include "gtest/gtest.h"
+ #include "/github/workspace/lib/CppInterOp/Compatibility.h"
+ #include "gtest/gtest.h"

@vgvassilev vgvassilev force-pushed the api-dispatch-mechanism branch from a72727d to c0d72f7 Compare January 23, 2026 17:08
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions


std::string mangled_add_int;
std::string mangled_add_double;
compat::maybeMangleDeclName(Add_int, mangled_add_int);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: no header providing "compat::maybeMangleDeclName" is directly included [misc-include-cleaner]

unittests/CppInterOp/DispatchTest.cpp:7:

- #include "gtest/gtest.h"
+ #include "/github/workspace/lib/CppInterOp/Compatibility.h"
+ #include "gtest/gtest.h"

DispatchInitializer& operator=(DispatchInitializer&&) noexcept = default;
};
// FIXME: Make this threadsafe by moving it as a function static.
DispatchInitializer g_dispatch_init;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: variable 'g_dispatch_init' is non-const and globally accessible, consider making it const [cppcoreguidelines-avoid-non-const-global-variables]

DispatchInitializer g_dispatch_init;
                    ^

Copy link
Copy Markdown
Contributor

@vgvassilev vgvassilev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm!

Defines the mechanism which enables dispatching of the CppInterOp API
without linking, preventing any LLVM or Clang symbols from being leaked
into the client application. This is achieved using a symbol-address table
and an address lookup through a C symbol allowing clients to dlopen
CppInterOp with RTLD_LOCAL, and automatically assign the API during runtime.

This alternate approach is particularly useful in scenarios where CppInterOp is loaded dynamically alongside other tools/libraries/packages that also
rely on LLVM. With cppyy, importing tools like `numba` or `pygame` that come bundled with a statically linked copy of LLVM,
leads to symbol interposition and crashes. The dispatch mechanism solves this by solely relying on a C interface, which returns a pointer to the address of a requested function, which can be cast to the appropriate API type and
invoked as needed. An example can be found in `unittests/CppInterOp/DispatchTest.cpp`. For a user, this system is completely automated via the `Dispatch.h` header file, which maintains a symbol-address map for a fast look-up through a C symbol (`CppGetProcAddress`) which is fetched via `dlsym`. The provided macro system then handles all the declarations on the client end
with the inclusion of the header:

```cpp
```

At this point, the expansions already provide the same API surface as the standard CppInterOp library, except that the declarations require to be added to a translation unit for the client application.
This can be done by adding the following preprocessor directive in one of the source files:

```cpp
CPPINTEROP_API_TABLE
```

This creates the necessary function pointers in the `Cpp` namespace, which can then be used as normal CppInterOp API functions.
To load the API, invoke:

```cpp
Cpp::LoadDispatchAPI("path/to/libclangCppInterOp.so");
```

Now your application can use the CppInterOp API via the `Cpp::` namespace, with all functions being dispatched through the function pointers.
This is a one-to-one replacement for linking and using <CppInterOp/CppInterOp.h> directly, with a few drawbacks:
- This system currently does not support overloaded public API
- The function pointers are not initialized until `LoadDispatchAPI` is called, which means that any attempt to use the API before loading will result in a null pointer dereference.
- You lose the ability to assume default arguments in API calls, which must now be provided explicitly since we rely on function pointers.
…te CppInterOpTestsDispatch

The unittests now dynamically change behaviours from a standard library usage of CppInterOp to utilising the dispatch mechanism based on the the presence of the `ENABLE_DISPATCH_TESTS` flag
@aaronj0 aaronj0 force-pushed the api-dispatch-mechanism branch from 1e5775a to dd612b1 Compare January 24, 2026 03:45
@aaronj0 aaronj0 merged commit dea9b71 into compiler-research:main Jan 24, 2026
30 of 67 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants