From b91276595165f457ec53e9cfa8bb0c15c5db0d5f Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 1 Oct 2025 13:01:39 +0200 Subject: [PATCH 1/7] [interp] Register runtime symbols for clang-repl --- lib/CppInterOp/CppInterOp.cpp | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index f1527b4a8..fbeda4560 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -90,6 +90,9 @@ #include #endif // WIN32 +extern "C" void __clang_Interpreter_SetValueNoAlloc(void* This, void* OutVal, + void* OpaqueType, ...); + namespace Cpp { using namespace clang; @@ -3330,6 +3333,37 @@ CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func) { } namespace { +#ifndef CPPINTEROP_USE_CLING +static bool DefineAbsoluteSymbol(compat::Interpreter& I, + const char* linker_mangled_name, + uint64_t address) { + using namespace llvm; + using namespace llvm::orc; + + llvm::orc::LLJIT& Jit = *compat::getExecutionEngine(I); + llvm::orc::ExecutionSession& ES = Jit.getExecutionSession(); + JITDylib& DyLib = *Jit.getProcessSymbolsJITDylib().get(); + + llvm::orc::SymbolMap InjectedSymbols; + auto& DL = compat::getExecutionEngine(I)->getDataLayout(); + char GlobalPrefix = DL.getGlobalPrefix(); + std::string tmp(linker_mangled_name); + if (GlobalPrefix != '\0') { + tmp = std::string(1, GlobalPrefix) + tmp; + } + auto Name = ES.intern(tmp); + InjectedSymbols[Name] = + ExecutorSymbolDef(ExecutorAddr(address), JITSymbolFlags::Exported); + + if (Error Err = DyLib.define(absoluteSymbols(InjectedSymbols))) { + logAllUnhandledErrors(std::move(Err), errs(), + "DefineAbsoluteSymbol error: "); + return true; + } + return false; +} +#endif + static std::string MakeResourcesPath() { StringRef Dir; #ifdef LLVM_BINARY_DIR @@ -3442,6 +3476,11 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, sInterpreters->back().get()}); assert(sInterpreters->size() == sInterpreterASTMap->size()); +// define __clang_Interpreter_SetValueNoAlloc in the JIT dylib for clang-repl +#ifndef CPPINTEROP_USE_CLING + DefineAbsoluteSymbol(*I, "__clang_Interpreter_SetValueNoAlloc", + (uint64_t)&__clang_Interpreter_SetValueNoAlloc); +#endif return I; } From 9c6be696e9dc2baa2638478954a7d4fcd21a6faf Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 1 Oct 2025 14:41:10 +0200 Subject: [PATCH 2/7] Register `__clang_Interpreter_SetValueWithAlloc`, mangle name if LLVM<22 --- lib/CppInterOp/CppInterOp.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index fbeda4560..236f10901 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -90,8 +90,17 @@ #include #endif // WIN32 -extern "C" void __clang_Interpreter_SetValueNoAlloc(void* This, void* OutVal, - void* OpaqueType, ...); +#if CLANG_VERSION_MAJOR > 22 +extern "C" void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, + void* OpaqueType) +#else +void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, + void* OpaqueType); +#endif + + extern "C" void __clang_Interpreter_SetValueNoAlloc(void* This, + void* OutVal, + void* OpaqueType, ...); namespace Cpp { @@ -3476,8 +3485,23 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, sInterpreters->back().get()}); assert(sInterpreters->size() == sInterpreterASTMap->size()); -// define __clang_Interpreter_SetValueNoAlloc in the JIT dylib for clang-repl + +// Define runtime symbols in the JIT dylib for clang-repl #ifndef CPPINTEROP_USE_CLING +#if CLANG_VERSION_MAJOR > 22 + DefineAbsoluteSymbol(*I, "__clang_Interpreter_SetValueWithAlloc", + (uint64_t)&__clang_Interpreter_SetValueNoAlloc); +#else + auto* D = static_cast( + Cpp::GetNamed("__clang_Interpreter_SetValueWithAlloc")); + if (auto* FD = llvm::dyn_cast(D)) { + auto GD = GlobalDecl(FD); + std::string mangledName; + compat::maybeMangleDeclName(GD, mangledName); + DefineAbsoluteSymbol(*I, mangledName.c_str(), + (uint64_t)&__clang_Interpreter_SetValueWithAlloc); + } +#endif DefineAbsoluteSymbol(*I, "__clang_Interpreter_SetValueNoAlloc", (uint64_t)&__clang_Interpreter_SetValueNoAlloc); #endif From e8a6b27e5e7349ada05a19741957f04a56fe3c1d Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 3 Oct 2025 15:26:01 +0200 Subject: [PATCH 3/7] Overload resolution for __clang_Interpreter_SetValueNoAlloc symbols to be defined with LLVM <19 --- lib/CppInterOp/CppInterOp.cpp | 78 +++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 236f10901..e8ce72aa7 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -90,7 +90,10 @@ #include #endif // WIN32 -#if CLANG_VERSION_MAJOR > 22 +// Runtime symbols required if the library using JIT (Cpp::Evaluate) does not +// link to llvm +#ifndef CPPINTEROP_USE_CLING +#if CLANG_VERSION_MAJOR >= 22 extern "C" void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, void* OpaqueType) #else @@ -98,9 +101,20 @@ void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, void* OpaqueType); #endif +#if CLANG_VERSION_MAJOR >= 19 extern "C" void __clang_Interpreter_SetValueNoAlloc(void* This, void* OutVal, void* OpaqueType, ...); +#elif CLANG_VERSION_MAJOR == 18 +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, void*); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, float); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, double); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, long double); +void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, + unsigned long long); +#endif +#endif // CPPINTEROP_USE_CLING namespace Cpp { @@ -3488,10 +3502,12 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, // Define runtime symbols in the JIT dylib for clang-repl #ifndef CPPINTEROP_USE_CLING -#if CLANG_VERSION_MAJOR > 22 +// llvm > 22 has this defined as a C symbol that does not require mangling +#if CLANG_VERSION_MAJOR >= 22 DefineAbsoluteSymbol(*I, "__clang_Interpreter_SetValueWithAlloc", - (uint64_t)&__clang_Interpreter_SetValueNoAlloc); + (uint64_t)&__clang_Interpreter_SetValueWithAlloc); #else + // obtain mangled name auto* D = static_cast( Cpp::GetNamed("__clang_Interpreter_SetValueWithAlloc")); if (auto* FD = llvm::dyn_cast(D)) { @@ -3502,8 +3518,64 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, (uint64_t)&__clang_Interpreter_SetValueWithAlloc); } #endif +// llvm < 19 has multiple overloads of __clang_Interpreter_SetValueNoAlloc +#if CLANG_VERSION_MAJOR < 19 + // obtain all 6 candidates, and obtain the correct Decl for each overload + // using BestOverloadFunctionMatch. We then map the decl to the correct + // function pointer (force the compiler to find the right declarion by casting + // to the corresponding function pointer signature) and then register it. + const std::vector Methods = Cpp::GetFunctionsUsingName( + Cpp::GetGlobalScope(), "__clang_Interpreter_SetValueNoAlloc"); + std::string mangledName; + ASTContext& Ctxt = I->getSema().getASTContext(); + auto* TAI = Ctxt.VoidPtrTy.getAsOpaquePtr(); + + // possible parameter lists for __clang_Interpreter_SetValueNoAlloc overloads + // in LLVM 18 + const std::vector> a_params = { + {TAI, TAI, TAI}, + {TAI, TAI, TAI, TAI}, + {TAI, TAI, TAI, Ctxt.FloatTy.getAsOpaquePtr()}, + {TAI, TAI, TAI, Ctxt.DoubleTy.getAsOpaquePtr()}, + {TAI, TAI, TAI, Ctxt.LongDoubleTy.getAsOpaquePtr()}, + {TAI, TAI, TAI, Ctxt.UnsignedLongLongTy.getAsOpaquePtr()}}; + + using FP0 = void (*)(void*, void*, void*); + using FP1 = void (*)(void*, void*, void*, void*); + using FP2 = void (*)(void*, void*, void*, float); + using FP3 = void (*)(void*, void*, void*, double); + using FP4 = void (*)(void*, void*, void*, long double); + using FP5 = void (*)(void*, void*, void*, unsigned long long); + + const std::vector func_pointers = { + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc)), + reinterpret_cast( + static_cast(&__clang_Interpreter_SetValueNoAlloc))}; + + // these symbols are not externed, so we need to mangle their names + for (size_t i = 0; i < a_params.size(); ++i) { + auto* decl = static_cast( + Cpp::BestOverloadFunctionMatch(Methods, {}, a_params[i])); + if (auto* fd = llvm::dyn_cast(decl)) { + auto gd = clang::GlobalDecl(fd); + compat::maybeMangleDeclName(gd, mangledName); + DefineAbsoluteSymbol(*I, mangledName.c_str(), + reinterpret_cast(func_pointers[i])); + } + } +#else DefineAbsoluteSymbol(*I, "__clang_Interpreter_SetValueNoAlloc", (uint64_t)&__clang_Interpreter_SetValueNoAlloc); +#endif #endif return I; } From 740bcb3379546ebc4d55388db6d850ba674c1f44 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 3 Oct 2025 16:10:12 +0200 Subject: [PATCH 4/7] Add tests for Cpp::Evaluate that use more runtime symbols --- unittests/CppInterOp/InterpreterTest.cpp | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/unittests/CppInterOp/InterpreterTest.cpp b/unittests/CppInterOp/InterpreterTest.cpp index 5800fe64e..b1713cc51 100644 --- a/unittests/CppInterOp/InterpreterTest.cpp +++ b/unittests/CppInterOp/InterpreterTest.cpp @@ -84,6 +84,38 @@ TEST(InterpreterTest, Evaluate) { EXPECT_FALSE(HadError) ; } +TEST(InterpreterTest, EvaluateExtensive) { +#ifdef EMSCRIPTEN + GTEST_SKIP() << "Test fails for Emscipten builds"; +#endif +#ifdef _WIN32 + GTEST_SKIP() << "Disabled on Windows. Needs fixing."; +#endif + if (llvm::sys::RunningOnValgrind()) + GTEST_SKIP() << "XFAIL due to Valgrind report"; + Cpp::CreateInterpreter(); + EXPECT_TRUE(Cpp::Evaluate("__cplusplus") == 201402); + + bool HadError; + EXPECT_TRUE(Cpp::Evaluate("#error", &HadError) == (intptr_t)~0UL); + EXPECT_TRUE(HadError); +// for llvm < 19 this tests all different overloads of __clang_Interpreter_SetValueNoAlloc + EXPECT_EQ(Cpp::Evaluate("int i = 11; ++i", &HadError), 12); + EXPECT_FALSE(HadError) ; + EXPECT_EQ(Cpp::Evaluate("double a = 12.; a", &HadError), 12.); + EXPECT_FALSE(HadError) ; + EXPECT_EQ(Cpp::Evaluate("float b = 13.; b", &HadError), 13.); + EXPECT_FALSE(HadError) ; + EXPECT_EQ(Cpp::Evaluate("long double c = 14.; c", &HadError), 14.); + EXPECT_FALSE(HadError) ; + EXPECT_EQ(Cpp::Evaluate("long double d = 15.; d", &HadError), 15.); + EXPECT_FALSE(HadError); + EXPECT_EQ(Cpp::Evaluate("unsigned long long e = 16; e", &HadError), 16); + EXPECT_FALSE(HadError) ; + EXPECT_NE(Cpp::Evaluate("struct S{} s; s", &HadError), (intptr_t)~0UL); + EXPECT_FALSE(HadError) ; +} + TEST(InterpreterTest, DeleteInterpreter) { auto* I1 = Cpp::CreateInterpreter(); auto* I2 = Cpp::CreateInterpreter(); From 4ef7df928a1fd0d05f9a3aeeae46b97ac576353b Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 3 Oct 2025 16:37:42 +0200 Subject: [PATCH 5/7] do not define and register symbols if emscripten --- lib/CppInterOp/CppInterOp.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index e8ce72aa7..071664578 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -92,7 +92,7 @@ // Runtime symbols required if the library using JIT (Cpp::Evaluate) does not // link to llvm -#ifndef CPPINTEROP_USE_CLING +#if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) #if CLANG_VERSION_MAJOR >= 22 extern "C" void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, void* OpaqueType) @@ -3356,7 +3356,7 @@ CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func) { } namespace { -#ifndef CPPINTEROP_USE_CLING +#if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) static bool DefineAbsoluteSymbol(compat::Interpreter& I, const char* linker_mangled_name, uint64_t address) { @@ -3501,7 +3501,7 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, assert(sInterpreters->size() == sInterpreterASTMap->size()); // Define runtime symbols in the JIT dylib for clang-repl -#ifndef CPPINTEROP_USE_CLING +#if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) // llvm > 22 has this defined as a C symbol that does not require mangling #if CLANG_VERSION_MAJOR >= 22 DefineAbsoluteSymbol(*I, "__clang_Interpreter_SetValueWithAlloc", From 8d91bcd99eb3f39425c661b72807273e57d5ccba Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 3 Oct 2025 17:54:15 +0200 Subject: [PATCH 6/7] Add struct `__clang_Interpreter_NewTag` --- lib/CppInterOp/CppInterOp.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 071664578..b2428de59 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -93,6 +93,8 @@ // Runtime symbols required if the library using JIT (Cpp::Evaluate) does not // link to llvm #if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) +struct __clang_Interpreter_NewTag { +} __ci_newtag; #if CLANG_VERSION_MAJOR >= 22 extern "C" void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, void* OpaqueType) @@ -3502,6 +3504,7 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, // Define runtime symbols in the JIT dylib for clang-repl #if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) + DefineAbsoluteSymbol(*I, "__ci_newtag", (uint64_t)&__ci_newtag); // llvm > 22 has this defined as a C symbol that does not require mangling #if CLANG_VERSION_MAJOR >= 22 DefineAbsoluteSymbol(*I, "__clang_Interpreter_SetValueWithAlloc", From 3c17a65bc1fcb1e53485f1c7f48d24f311343147 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 6 Oct 2025 10:43:48 +0200 Subject: [PATCH 7/7] review comments --- lib/CppInterOp/CppInterOp.cpp | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index b2428de59..e08992565 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -95,7 +95,7 @@ #if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) struct __clang_Interpreter_NewTag { } __ci_newtag; -#if CLANG_VERSION_MAJOR >= 22 +#if CLANG_VERSION_MAJOR > 21 extern "C" void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, void* OpaqueType) #else @@ -103,11 +103,11 @@ void* __clang_Interpreter_SetValueWithAlloc(void* This, void* OutVal, void* OpaqueType); #endif -#if CLANG_VERSION_MAJOR >= 19 +#if CLANG_VERSION_MAJOR > 18 extern "C" void __clang_Interpreter_SetValueNoAlloc(void* This, void* OutVal, void* OpaqueType, ...); -#elif CLANG_VERSION_MAJOR == 18 +#else void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*); void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, void*); void __clang_Interpreter_SetValueNoAlloc(void*, void*, void*, float); @@ -3359,9 +3359,8 @@ CPPINTEROP_API JitCall MakeFunctionCallable(TCppConstFunction_t func) { namespace { #if !defined(CPPINTEROP_USE_CLING) && !defined(EMSCRIPTEN) -static bool DefineAbsoluteSymbol(compat::Interpreter& I, - const char* linker_mangled_name, - uint64_t address) { +bool DefineAbsoluteSymbol(compat::Interpreter& I, + const char* linker_mangled_name, uint64_t address) { using namespace llvm; using namespace llvm::orc; @@ -3369,16 +3368,10 @@ static bool DefineAbsoluteSymbol(compat::Interpreter& I, llvm::orc::ExecutionSession& ES = Jit.getExecutionSession(); JITDylib& DyLib = *Jit.getProcessSymbolsJITDylib().get(); - llvm::orc::SymbolMap InjectedSymbols; - auto& DL = compat::getExecutionEngine(I)->getDataLayout(); - char GlobalPrefix = DL.getGlobalPrefix(); - std::string tmp(linker_mangled_name); - if (GlobalPrefix != '\0') { - tmp = std::string(1, GlobalPrefix) + tmp; - } - auto Name = ES.intern(tmp); - InjectedSymbols[Name] = - ExecutorSymbolDef(ExecutorAddr(address), JITSymbolFlags::Exported); + llvm::orc::SymbolMap InjectedSymbols{ + {ES.intern(linker_mangled_name), ExecutorSymbolDef(ExecutorAddr(address), + JITSymbolFlags::Exported)} + }; if (Error Err = DyLib.define(absoluteSymbols(InjectedSymbols))) { logAllUnhandledErrors(std::move(Err), errs(),