From af3c606c232a84986e669343ed9e58bf3f67aaed 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 | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index b6cf555e8..05cf88188 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -86,6 +86,9 @@ #include #endif // WIN32 +extern "C" void __clang_Interpreter_SetValueNoAlloc(void* This, void* OutVal, + void* OpaqueType, ...); + namespace Cpp { using namespace clang; @@ -3169,6 +3172,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 @@ -3273,6 +3307,12 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, sInterpreters->emplace_back(I, /*Owned=*/true); +// 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 376cc2baa6ad9827c49b7c68a485fa33f9aa1257 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 | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 05cf88188..71c34b827 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -86,8 +86,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 { @@ -3307,8 +3316,22 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, sInterpreters->emplace_back(I, /*Owned=*/true); -// 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 adfd4a4c38e23dd848182d86665ae239bac57b6f 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 | 79 +++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 4 deletions(-) diff --git a/lib/CppInterOp/CppInterOp.cpp b/lib/CppInterOp/CppInterOp.cpp index 71c34b827..0cf002df2 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -86,7 +86,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 @@ -94,9 +97,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 { @@ -3318,10 +3332,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)) { @@ -3332,10 +3348,65 @@ 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 15d86007fc22dd2060d948fc2bd9f950a993ddf7 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 e9b82ea1d..c5e869d0f 100644 --- a/unittests/CppInterOp/InterpreterTest.cpp +++ b/unittests/CppInterOp/InterpreterTest.cpp @@ -80,6 +80,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 262979309b9efe2707d5f52ba9dbb4d57d8a41e9 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 0cf002df2..2eab2c0f2 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -88,7 +88,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) @@ -3195,7 +3195,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) { @@ -3331,7 +3331,7 @@ TInterp_t CreateInterpreter(const std::vector& Args /*={}*/, sInterpreters->emplace_back(I, /*Owned=*/true); // 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 ebb4e16ee73f6c6a3975fa3ddb9f02a111f26da3 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 2eab2c0f2..bf1b09618 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -89,6 +89,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) @@ -3332,6 +3334,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 708d0dd2391e8b2469ae1d048591b2f244585408 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 bf1b09618..d0af87246 100755 --- a/lib/CppInterOp/CppInterOp.cpp +++ b/lib/CppInterOp/CppInterOp.cpp @@ -91,7 +91,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 @@ -99,11 +99,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); @@ -3198,9 +3198,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; @@ -3208,16 +3207,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(),