Skip to content

Commit b972a1d

Browse files
committed
Drop <new> requirement for JitCall wrappers.
The JitCall wrapper generator used to emit plain `new (ret) (T) (call())`, `new (ptr) T(args...)`, and `new (p) T[n]` for its placement-new forms, binding against the standard placement operators declared in <new>. That forced every user of JitCall to pull <new> into the interpreter's TU and every ctor-using test to pass `-include new` on the command line. Append `, __ci_newtag` to the two scalar placement-new sites -- the return-value branch of make_narg_call_with_return and the scalar-ctor branch of make_narg_ctor_with_return -- so they resolve against clang-repl's preloaded tagged overload `operator new(size_t, void*, __clang_Interpreter_NewTag)`. That overload is declared by the Runtimes string introduced in llvm/llvm-project@1566f1ffc6b5 and first released in LLVM 18. CppInterOp already registers `__ci_newtag` as an absolute symbol in the JIT dylib, so no new runtime machinery is needed. The default-ctor wrapper's `nary > 1 && is_arena` branch is rewritten as a loop of scalar tagged placement news rather than `new (p) T[n]`. Two alternatives were considered and rejected: (a) A tagged `operator new[](size_t, void*, Tag)` would give the minimum per-wrapper emission, but clang does not recognise a custom-signature array allocator as the standard placement form and inserts an array cookie for types with non-trivial destructors (Itanium C++ ABI §2.7), breaking the contract that `Cpp::Construct(scope, arena, n)` returns `arena`. (b) Forward-declaring the STANDARD-signature `operator new[](size_t, void*)` would satisfy clang's placement recognition, but the declaration is not portably replicable: libstdc++, libc++, and the MSVC STL decorate it with different noexcept macros, calling conventions, and [[nodiscard]] attributes. A user-supplied `#include <new>` after interpreter creation would clash with ours and crash the parse. Cling is unaffected: it pre-includes <new> at IncrementalParser init, so the empty tag on that backend is equivalent to plain scalar placement new. With the wrapper no longer needing <new>, the JitCall-adjacent tests drop their `#include <new>` declares and `-include new` compiler flags. Sites that were cargo pulling in <new> for unrelated reflection tests (no JitCall use, no direct `<new>` dependency) drop the flag too -- where the test already pulls in `<memory>`, `<string>`, or `<vector>`, the `<new>` declarations arrive transitively. Two regression guards pin the contract: * FunctionReflection_JitCallNoNewHeader asserts the no-<new> premise via a non-noexcept redeclaration of `operator new(size_t, void*)` (per [new.delete.placement] the standard placement forms are noexcept, so the redeclaration parses cleanly only when <new> is not in scope) and then exercises a scalar-return JitCall. * FunctionReflection_ArrayConstructNoCookie constructs an array of a class with a user-provided destructor, asserts `Cpp::Construct(scope, arena, n) == arena`, and cross-checks each element's value at `arena + i*sizeof(T)`. A future revert to a tagged `operator new[]` or any non-standard-signature placement allocator fires this test immediately.
1 parent a727d4f commit b972a1d

7 files changed

Lines changed: 176 additions & 72 deletions

File tree

lib/CppInterOp/CppInterOp.cpp

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2767,6 +2767,27 @@ void make_narg_call(const FunctionDecl* FD, const std::string& return_type,
27672767
callbuf << ")";
27682768
}
27692769

2770+
// Tag appended inside `new (ptr<tag>) T(...)` when emitting a scalar
2771+
// placement new in a JitCall wrapper.
2772+
//
2773+
// clang-repl's Runtimes string declares the scalar tagged overload
2774+
// `operator new(size_t, void*, __clang_Interpreter_NewTag)` (introduced
2775+
// in llvm/llvm-project@1566f1ffc6b5, LLVM 18), so the spelling
2776+
// `new (p, __ci_newtag) T(...)` binds without the user's TU having
2777+
// `#include <new>` in scope. Array placement in
2778+
// `make_narg_ctor_with_return` is implemented as a loop of scalar tagged
2779+
// placements for the same reason.
2780+
//
2781+
// Cling has no such tag; its runtime makes `<new>` available by default,
2782+
// so the empty tag suffices there (plain scalar placement new).
2783+
inline const char* PlacementTag() {
2784+
#ifdef CPPINTEROP_USE_CLING
2785+
return "";
2786+
#else
2787+
return ", __ci_newtag";
2788+
#endif
2789+
}
2790+
27702791
void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N,
27712792
const std::string& class_name,
27722793
std::ostringstream& buf, int indent_level) {
@@ -2790,41 +2811,59 @@ void make_narg_ctor_with_return(const FunctionDecl* FD, const unsigned N,
27902811
indent(callbuf, indent_level);
27912812
const auto* CD = dyn_cast<CXXConstructorDecl>(FD);
27922813

2793-
// Activate this block only if array new is possible
2794-
// if (nary) {
2795-
// (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName[nary]
2796-
// : new ClassName[nary];
2797-
// }
2798-
// else {
2814+
// Array branch. The is_arena side emits a loop of scalar placement
2815+
// calls rather than `new (p) T[n]`. Two alternatives were considered
2816+
// and rejected:
2817+
//
2818+
// (a) Forward-declare a tagged
2819+
// `operator new[](size_t, void*, __clang_Interpreter_NewTag)`
2820+
// and emit `new (p, __ci_newtag) T[n]`. Cheapest in per-wrapper
2821+
// emission, but clang does not recognise a custom-signature
2822+
// array allocator as the standard placement form and inserts
2823+
// an array cookie for types with non-trivial destructors
2824+
// (Itanium C++ ABI §2.7), breaking the
2825+
// `Cpp::Construct(scope, arena, n) == arena` contract.
2826+
//
2827+
// (b) Forward-declare the STANDARD-signature
2828+
// `operator new[](size_t, void*)`. Clang would signature-match
2829+
// this as the placement form (no cookie), but the declaration
2830+
// is not portably replicable: libstdc++, libc++, and the MSVC
2831+
// STL decorate it with different noexcept macros, calling
2832+
// conventions, and `[[nodiscard]]` attributes. A
2833+
// user-supplied `#include <new>` after interpreter creation
2834+
// would clash with our declaration and crash the parse.
2835+
//
2836+
// The loop binds against the already-declared scalar tagged
2837+
// placement operator (PlacementTag()), adds only O(6) lines per
2838+
// wrapper, and works on cling too (`<new>` is pre-included there,
2839+
// so the empty tag is equivalent to plain scalar placement new).
27992840
if (CD->isDefaultConstructor()) {
28002841
callbuf << "if (nary > 1) {\n";
28012842
indent(callbuf, indent_level);
2802-
callbuf << "(*(" << class_name << "**)ret) = ";
2803-
callbuf << "(is_arena) ? new (*(" << class_name << "**)ret) ";
2804-
make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level,
2805-
true);
2806-
2807-
callbuf << ": new ";
2808-
//
2809-
// Write the actual expression.
2810-
//
2843+
callbuf << "if (is_arena)\n";
2844+
indent(callbuf, indent_level + 1);
2845+
callbuf << "for (unsigned long __i = 0; __i < nary; ++__i)\n";
2846+
indent(callbuf, indent_level + 2);
2847+
callbuf << "new ((void*)(*(" << class_name << "**)ret + __i)"
2848+
<< PlacementTag() << ") " << class_name << "();\n";
2849+
indent(callbuf, indent_level);
2850+
callbuf << "else (*(" << class_name << "**)ret) = new ";
28112851
make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level,
28122852
true);
2813-
//
2814-
// End the new expression statement.
2815-
//
28162853
callbuf << ";\n";
28172854
indent(callbuf, indent_level);
28182855
callbuf << "}\n";
28192856
callbuf << "else {\n";
28202857
}
28212858

2822-
// Standard branch:
2823-
// (*(ClassName**)ret) = (obj) ? new (*(ClassName**)ret) ClassName(args...)
2824-
// : new ClassName(args...);
2859+
// Standard (scalar) branch:
2860+
// (*(ClassName**)ret) = (is_arena)
2861+
// ? new (*(ClassName**)ret[, __ci_newtag]) ClassName(args...)
2862+
// : new ClassName(args...);
28252863
indent(callbuf, indent_level);
28262864
callbuf << "(*(" << class_name << "**)ret) = ";
2827-
callbuf << "(is_arena) ? new (*(" << class_name << "**)ret) ";
2865+
callbuf << "(is_arena) ? new (*(" << class_name << "**)ret"
2866+
<< PlacementTag() << ") ";
28282867
make_narg_ctor(FD, N, typedefbuf, callbuf, class_name, indent_level);
28292868

28302869
callbuf << ": new ";
@@ -2908,7 +2947,8 @@ void make_narg_call_with_return(compat::Interpreter& I, const FunctionDecl* FD,
29082947
// Write the placement part of the placement new.
29092948
//
29102949
indent(callbuf, indent_level);
2911-
callbuf << "new (ret) ";
2950+
// See PlacementTag for the rationale of the tag.
2951+
callbuf << "new (ret" << PlacementTag() << ") ";
29122952
//
29132953
// Write the type part of the placement new.
29142954
//

unittests/CppInterOp/FunctionReflectionTest.cpp

Lines changed: 105 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -705,8 +705,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE,
705705
FunctionReflection_InstantiateTemplateFunctionFromString) {
706706
if (llvm::sys::RunningOnValgrind())
707707
GTEST_SKIP() << "XFAIL due to Valgrind report";
708-
std::vector<const char*> interpreter_args = { "-include", "new" };
709-
TestFixture::CreateInterpreter(interpreter_args);
708+
TestFixture::CreateInterpreter();
710709
std::string code = R"(#include <memory>)";
711710
Interp->process(code);
712711
const char* str = "std::make_unique<int,int>";
@@ -1560,7 +1559,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionAddress) {
15601559

15611560
std::vector<Decl*> Decls;
15621561
std::string code = "int f1(int i) { return i * i; }";
1563-
std::vector<const char*> interpreter_args = {"-include", "new", "-Xclang", "-iwithsysroot/include/compat"};
1562+
std::vector<const char*> interpreter_args = {"-Xclang", "-iwithsysroot/include/compat"};
15641563

15651564
GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false,
15661565
interpreter_args);
@@ -1617,6 +1616,37 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsVirtualMethod) {
16171616
EXPECT_FALSE(Cpp::IsVirtualMethod(Decls[0]));
16181617
}
16191618

1619+
// Regression guard: JitCall wrappers must not require the user's TU to
1620+
// pull in <new>. This test adds an explicit premise check and one
1621+
// scalar-return JitCall so a refactor that accidentally makes <new>
1622+
// reachable cannot silently mask a future revert of the wrapper contract.
1623+
TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallNoNewHeader) {
1624+
#ifdef CPPINTEROP_USE_CLING
1625+
GTEST_SKIP() << "Cling pre-includes <new>.";
1626+
#endif
1627+
if (TypeParam::isOutOfProcess)
1628+
GTEST_SKIP() << "Test fails for OOP JIT builds";
1629+
1630+
TestFixture::CreateInterpreter();
1631+
1632+
// Per [new.delete.placement] the standard placement overloads are
1633+
// declared `noexcept`; this non-noexcept redeclaration only parses
1634+
// if <new> is NOT in scope.
1635+
ASSERT_EQ(0,
1636+
Cpp::Declare("void* operator new(__SIZE_TYPE__, void*);"
1637+
DFLT_FALSE));
1638+
1639+
// One JitCall is enough — if the wrapper regresses to plain placement
1640+
// new, MakeFunctionCallable fails to compile without <new>.
1641+
Cpp::Declare("int jc_sq(int x) { return x * x; }" DFLT_FALSE);
1642+
auto JC = Cpp::MakeFunctionCallable(Cpp::GetNamed("jc_sq" DFLT_NULLPTR));
1643+
ASSERT_TRUE(JC.isValid());
1644+
int arg = 5, ret = 0;
1645+
void* args[] = {&arg};
1646+
JC.Invoke(&ret, {args, 1});
1647+
EXPECT_EQ(ret, 25);
1648+
}
1649+
16201650
TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallAdvanced) {
16211651
#if CLANG_VERSION_MAJOR == 20 && defined(CPPINTEROP_USE_CLING) && defined(_WIN32)
16221652
GTEST_SKIP() << "Test fails with Cling on Windows";
@@ -1638,10 +1668,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallAdvanced) {
16381668
} name;
16391669
)";
16401670

1641-
std::vector<const char*> interpreter_args = {"-include", "new"};
1642-
1643-
GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false,
1644-
interpreter_args);
1671+
GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false);
16451672
auto *CtorD
16461673
= (clang::CXXConstructorDecl*)Cpp::GetDefaultConstructor(Decls[0]);
16471674
auto Ctor = Cpp::MakeFunctionCallable(CtorD);
@@ -1682,8 +1709,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_JitCallDebug) {
16821709
}
16831710
};)";
16841711

1685-
std::vector<const char*> interpreter_args = {"-include", "new",
1686-
"-debug-only=jitcall"};
1712+
std::vector<const char*> interpreter_args = {"-debug-only=jitcall"};
16871713
GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false,
16881714
interpreter_args);
16891715

@@ -1785,8 +1811,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_GetFunctionCallWrapper) {
17851811
int f1(int i) { return i * i; }
17861812
)";
17871813

1788-
std::vector<const char*> interpreter_args = {"-include", "new"};
1789-
1814+
std::vector<const char*> interpreter_args;
17901815
GetAllTopLevelDecls(code, Decls, /*filter_implicitGenerated=*/false,
17911816
interpreter_args);
17921817

@@ -2446,11 +2471,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Construct) {
24462471
#endif
24472472
if (TypeParam::isOutOfProcess)
24482473
GTEST_SKIP() << "Test fails for OOP JIT builds";
2449-
std::vector<const char*> interpreter_args = {"-include", "new"};
24502474
std::vector<Decl*> Decls, SubDecls;
24512475

24522476
std::string code = R"(
2453-
#include <new>
24542477
extern "C" int printf(const char*,...);
24552478
class C {
24562479
public:
@@ -2463,7 +2486,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Construct) {
24632486
void construct() { return; }
24642487
)";
24652488

2466-
GetAllTopLevelDecls(code, Decls, false, interpreter_args);
2489+
GetAllTopLevelDecls(code, Decls, false);
24672490
GetAllSubDecls(Decls[1], SubDecls);
24682491
testing::internal::CaptureStdout();
24692492
Cpp::TCppScope_t scope = Cpp::GetNamed("C" DFLT_NULLPTR);
@@ -2534,8 +2557,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructPOD) {
25342557
#endif
25352558
if (TypeParam::isOutOfProcess)
25362559
GTEST_SKIP() << "Test fails for OOP JIT builds";
2537-
std::vector<const char*> interpreter_args = {"-include", "new"};
2538-
TestFixture::CreateInterpreter(interpreter_args);
2560+
TestFixture::CreateInterpreter();
25392561

25402562
Interp->declare(R"(
25412563
namespace PODS {
@@ -2577,11 +2599,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructNested) {
25772599
if (TypeParam::isOutOfProcess)
25782600
GTEST_SKIP() << "Test fails for OOP JIT builds";
25792601

2580-
std::vector<const char*> interpreter_args = {"-include", "new"};
2581-
TestFixture::CreateInterpreter(interpreter_args);
2602+
TestFixture::CreateInterpreter();
25822603

25832604
Interp->declare(R"(
2584-
#include <new>
25852605
extern "C" int printf(const char*,...);
25862606
class A {
25872607
public:
@@ -2643,7 +2663,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ConstructArray) {
26432663
TestFixture::CreateInterpreter();
26442664

26452665
Interp->declare(R"(
2646-
#include <new>
26472666
extern "C" int printf(const char*,...);
26482667
class C {
26492668
int x;
@@ -2695,11 +2714,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_Destruct) {
26952714
if (TypeParam::isOutOfProcess)
26962715
GTEST_SKIP() << "Test fails for OOP JIT builds";
26972716

2698-
std::vector<const char*> interpreter_args = {"-include", "new"};
2699-
TestFixture::CreateInterpreter(interpreter_args);
2717+
TestFixture::CreateInterpreter();
27002718

27012719
Interp->declare(R"(
2702-
#include <new>
27032720
extern "C" int printf(const char*,...);
27042721
class C {
27052722
C() {}
@@ -2768,11 +2785,9 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_DestructArray) {
27682785
if (TypeParam::isOutOfProcess)
27692786
GTEST_SKIP() << "Test fails for OOP JIT builds";
27702787

2771-
std::vector<const char*> interpreter_args = {"-include", "new"};
2772-
TestFixture::CreateInterpreter(interpreter_args);
2788+
TestFixture::CreateInterpreter();
27732789

27742790
Interp->declare(R"(
2775-
#include <new>
27762791
extern "C" int printf(const char*,...);
27772792
class C {
27782793
int x;
@@ -2834,6 +2849,69 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_DestructArray) {
28342849
output.clear();
28352850
}
28362851

2852+
// Regression guard for Itanium C++ ABI §2.7 array cookies. Any
2853+
// placement `operator new[]` that clang does not recognise as the
2854+
// standard placement form (standard signature, declared in <new>)
2855+
// causes clang to insert an array cookie before the storage when the
2856+
// element type has a non-trivial destructor. That would shift the
2857+
// pointer returned by `Cpp::Construct(scope, arena, n)` past the
2858+
// cookie, breaking the documented contract that the return equals
2859+
// `arena`. The JitCall ctor wrapper works around this by emitting a
2860+
// loop of scalar placement news rather than a single
2861+
// `new (p) T[n]` with a custom-signature allocator — see the comment
2862+
// on `make_narg_ctor_with_return` in lib/CppInterOp/CppInterOp.cpp.
2863+
// This test fails immediately if that loop is ever replaced with a
2864+
// tagged `operator new[]` or a non-standard forward declaration.
2865+
TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_ArrayConstructNoCookie) {
2866+
#ifdef EMSCRIPTEN
2867+
GTEST_SKIP() << "Test fails for Emscripten builds";
2868+
#endif
2869+
if (llvm::sys::RunningOnValgrind())
2870+
GTEST_SKIP() << "XFAIL due to Valgrind report";
2871+
#ifdef _WIN32
2872+
GTEST_SKIP() << "Disabled on Windows. Needs fixing.";
2873+
#endif
2874+
if (TypeParam::isOutOfProcess)
2875+
GTEST_SKIP() << "Test fails for OOP JIT builds";
2876+
2877+
TestFixture::CreateInterpreter();
2878+
2879+
// CookieProbe has a user-provided destructor, which makes it
2880+
// non-trivially destructible. Any non-placement-form `operator new[]`
2881+
// must reserve space for an array cookie in front of the storage.
2882+
Interp->declare(R"(
2883+
struct CookieProbe {
2884+
int v;
2885+
CookieProbe() : v(0xC0DE) {}
2886+
~CookieProbe() {}
2887+
};
2888+
)");
2889+
2890+
auto* scope = Cpp::GetNamed("CookieProbe" DFLT_NULLPTR);
2891+
ASSERT_NE(scope, nullptr);
2892+
2893+
constexpr size_t kN = 4;
2894+
void* arena = Cpp::Allocate(scope, kN);
2895+
ASSERT_NE(arena, nullptr);
2896+
2897+
// Placement array construction must return the arena as-is.
2898+
EXPECT_EQ(Cpp::Construct(scope, arena, kN), arena)
2899+
<< "Construct returned a pointer offset from arena; an array "
2900+
"cookie has been inserted. Placement-new signature regressed.";
2901+
2902+
// Cross-check: the i-th element's field lives at offset i*sizeof(T)
2903+
// from the arena base (not past a cookie header).
2904+
const size_t T = Cpp::SizeOf(scope);
2905+
for (size_t i = 0; i < kN; ++i) {
2906+
int* slot = reinterpret_cast<int*>(
2907+
reinterpret_cast<char*>(arena) + i * T);
2908+
EXPECT_EQ(*slot, 0xC0DE) << "element " << i << " not at expected offset";
2909+
}
2910+
2911+
Cpp::Destruct(arena, scope, /*withFree=*/false, kN);
2912+
Cpp::Deallocate(scope, arena, kN);
2913+
}
2914+
28372915
TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_UndoTest) {
28382916
#ifdef _WIN32
28392917
GTEST_SKIP() << "Disabled on Windows. Needs fixing.";
@@ -3003,7 +3081,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsExplicitTemplated) {
30033081

30043082
TYPED_TEST(CPPINTEROP_TEST_MODE, FunctionReflection_IsExplicitDeductionGuide) {
30053083
// Deduction guides are a C++17 feature
3006-
std::vector<const char*> interpreter_args = {"-include", "new", "-std=c++17"};
3084+
std::vector<const char*> interpreter_args = {"-std=c++17"};
30073085
Cpp::CreateInterpreter(interpreter_args, {});
30083086

30093087
Interp->declare(R"(

unittests/CppInterOp/InterpreterTest.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_Process) {
158158
GTEST_SKIP() << "Test fails for OOP JIT builds";
159159
if (llvm::sys::RunningOnValgrind())
160160
GTEST_SKIP() << "XFAIL due to Valgrind report";
161-
std::vector<const char*> interpreter_args = { "-include", "new", "-Xclang", "-iwithsysroot/include/compat" };
161+
std::vector<const char*> interpreter_args = { "-Xclang", "-iwithsysroot/include/compat" };
162162
auto* I = TestFixture::CreateInterpreter(interpreter_args);
163163
EXPECT_TRUE(Cpp::Process("") == 0);
164164
EXPECT_TRUE(Cpp::Process("int a = 12;") == 0);
@@ -538,7 +538,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_WrapperCacheIsPerInterpreter) {
538538
auto* AddDecl1 = Cpp::GetNamed("add" DFLT_NULLPTR);
539539
ASSERT_NE(AddDecl1, nullptr);
540540

541-
Cpp::Declare("#include <new>" DFLT_FALSE); // Needed by JitCall
542541
auto JC1 = Cpp::MakeFunctionCallable(AddDecl1);
543542
ASSERT_TRUE(JC1.isValid());
544543

@@ -555,7 +554,6 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_WrapperCacheIsPerInterpreter) {
555554
auto* AddDecl2 = Cpp::GetNamed("add" DFLT_NULLPTR);
556555
ASSERT_NE(AddDecl2, nullptr);
557556

558-
Cpp::Declare("#include <new>" DFLT_FALSE); // Needed by JitCall
559557
auto JC2 = Cpp::MakeFunctionCallable(AddDecl2);
560558
ASSERT_TRUE(JC2.isValid());
561559

0 commit comments

Comments
 (0)