From 37becec2079139aa15811beb8467df322988ffde Mon Sep 17 00:00:00 2001 From: Juan Miguel Carceller Date: Thu, 8 Oct 2026 16:45:20 +0200 Subject: [PATCH 1/3] [interop] Use the object for offsets through virtual bases --- src/interop/interop_wrapper.cxx | 46 ++++++++++++++++++++++++++++++++- test/test_advancedcpp.py | 36 ++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index d26697c8..1c58abc1 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -225,6 +225,17 @@ static void defineRuntimeHelpers() { // helper for multiple inheritance Cpp::Declare("namespace __cppjit_internal { struct Sep; }", /*silent=*/false); + + Cpp::Declare("namespace __cppjit_internal { template" + " bool base_offset(void* addr, bool up, long long* offset) {" + " D* d = nullptr;" + " if (up) d = (D*)addr;" + " else if constexpr (__is_polymorphic(B))" + " d = dynamic_cast((B*)addr);" + " if (!d) return false;" + " *offset = (long long)((char*)static_cast(d) - (char*)d);" + " return true; } }", + /*silent=*/false); } } // unnamed namespace @@ -1195,8 +1206,26 @@ bool interop::GetSmartPtrInfo(TCppScope_t klass, TCppScope_t* raw, } // type offsets -------------------------------------------------------------- +using BaseOffsetFunc_t = bool (*)(void*, bool, long long*); + +static BaseOffsetFunc_t make_base_offset_func(interop::TCppScope_t derived, + interop::TCppScope_t base) { + static unsigned long long func_count = 0; + const std::string name = + "__cppjit_base_offset_" + std::to_string(func_count++); + const std::string code = "extern \"C\" bool " + name + + "(void* addr, bool up, long long* offset) {" + " return __cppjit_internal::base_offset<" + + Cpp::GetQualifiedCompleteName(derived) + ", " + + Cpp::GetQualifiedCompleteName(base) + + ">(addr, up, offset); }"; + if (Cpp::Declare(code.c_str(), /*silent=*/true)) + return nullptr; + return (BaseOffsetFunc_t)Cpp::GetFunctionAddress(name.c_str()); +} + ptrdiff_t interop::GetBaseOffset(TCppScope_t derived, TCppScope_t base, - TCppObject_t /*address*/, int direction, + TCppObject_t address, int direction, bool rerror) { std::lock_guard Lock(InterOpMutex); intptr_t offset = Cpp::GetBaseClassOffset(derived, base); @@ -1204,6 +1233,21 @@ ptrdiff_t interop::GetBaseOffset(TCppScope_t derived, TCppScope_t base, if (offset == -1) // Cling error, treat silently return rerror ? (ptrdiff_t)offset : 0; + static std::map, BaseOffsetFunc_t> + s_vbase_offset_funcs; + if (address && Cpp::IsBaseReachedVirtually(derived, base)) { + const auto key = std::make_pair(derived.data, base.data); + auto func = s_vbase_offset_funcs.find(key); + if (func == s_vbase_offset_funcs.end()) + func = s_vbase_offset_funcs + .emplace(key, make_base_offset_func(derived, base)) + .first; + long long dynamic_offset = 0; + if (func->second && + func->second(address.data, direction > 0, &dynamic_offset)) + offset = (intptr_t)dynamic_offset; + } + return (ptrdiff_t)(direction < 0 ? -offset : offset); } diff --git a/test/test_advancedcpp.py b/test/test_advancedcpp.py index 39f6ab70..e64d7ef5 100644 --- a/test/test_advancedcpp.py +++ b/test/test_advancedcpp.py @@ -1030,3 +1030,39 @@ def __cast_cpp__(self): for norm in [ns.norm_cr, ns.norm_m, ns.norm_v]: assert round(norm(p3) - pynorm, 8) == 0 + + def test30_virtual_base_of_hidden_type(self): + """Offset to a virtual base taken from the actual object""" + + import cppjit + + cppjit.cppdef("""\ + namespace vbase_hidden { + struct IB { virtual ~IB() {} virtual long id() const { return 42; } }; + struct ID1 : virtual IB {}; + struct ID2 : virtual IB {}; + struct IC { virtual ~IC() {} long z = 7; }; + struct M : IC {}; + struct ID3 : virtual M {}; + long call(IB* p) { return p->id(); } + long call_c(IC* p) { return p->z; } + } + namespace { + struct Other { virtual ~Other() {} long x[100] = {}; }; + struct Hidden : Other, vbase_hidden::ID1, vbase_hidden::ID2 {}; + struct Hidden3 : Other, vbase_hidden::ID1, vbase_hidden::ID3 { long y = 3; }; + } + namespace vbase_hidden { + void* make2() { return static_cast(new Hidden); } + void* make3() { return static_cast(new Hidden3); } + }""") + + ns = cppjit.gbl.vbase_hidden + + d2 = cppjit.bind_object(ns.make2(), ns.ID2) + assert ns.call(d2) == 42 + assert d2.id() == 42 + + d3 = cppjit.bind_object(ns.make3(), ns.ID3) + assert ns.call_c(d3) == 7 + assert d3.z == 7 From 6d8956310ec2c863522ea7dbfdad2a4816ec2dcd Mon Sep 17 00:00:00 2001 From: Juan Miguel Carceller Date: Sun, 11 Oct 2026 09:32:17 +0200 Subject: [PATCH 2/3] Update GetBaseOffset to use extended GetBaseClassOffset API Use the new GetBaseClassOffset(derived, base, address) API for up-casts, which reads virtual base offsets from the object's vtable/vbtable when needed. This eliminates the need for IsBaseReachedVirtually checks and the per-pair JIT-compiled helper for up-casts. Down-casts still use the generated helper as they require dynamic_cast semantics that GetBaseClassOffset does not provide. Co-Authored-By: Claude Opus 5.5 --- src/interop/interop_wrapper.cxx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index 1c58abc1..9e70be0a 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -1228,14 +1228,25 @@ ptrdiff_t interop::GetBaseOffset(TCppScope_t derived, TCppScope_t base, TCppObject_t address, int direction, bool rerror) { std::lock_guard Lock(InterOpMutex); - intptr_t offset = Cpp::GetBaseClassOffset(derived, base); + + // For up-casts, pass the address to GetBaseClassOffset which will read from + // the vtable/vbtable when the path goes through a virtual base. + // For down-casts (direction < 0), the address points to the base and we need + // dynamic_cast behavior, which the generated helper provides. + intptr_t offset; + if (direction > 0 && address) { + offset = Cpp::GetBaseClassOffset(derived, base, address.data); + } else { + offset = Cpp::GetBaseClassOffset(derived, base); + } if (offset == -1) // Cling error, treat silently return rerror ? (ptrdiff_t)offset : 0; + // For down-casts we still need the generated helper to perform dynamic_cast static std::map, BaseOffsetFunc_t> s_vbase_offset_funcs; - if (address && Cpp::IsBaseReachedVirtually(derived, base)) { + if (address && direction < 0) { const auto key = std::make_pair(derived.data, base.data); auto func = s_vbase_offset_funcs.find(key); if (func == s_vbase_offset_funcs.end()) From 5794891e3fb5ebb775829c1ac8b544b2c5cc225a Mon Sep 17 00:00:00 2001 From: Juan Miguel Carceller Date: Sun, 11 Oct 2026 11:36:18 +0200 Subject: [PATCH 3/3] Use class layout offsets for virtual base downcasts --- src/interop/interop_wrapper.cxx | 60 +++------------------------------ test/test_advancedcpp.py | 36 ++++++++++++++++++-- 2 files changed, 39 insertions(+), 57 deletions(-) diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index 9e70be0a..699d3b39 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -225,17 +225,6 @@ static void defineRuntimeHelpers() { // helper for multiple inheritance Cpp::Declare("namespace __cppjit_internal { struct Sep; }", /*silent=*/false); - - Cpp::Declare("namespace __cppjit_internal { template" - " bool base_offset(void* addr, bool up, long long* offset) {" - " D* d = nullptr;" - " if (up) d = (D*)addr;" - " else if constexpr (__is_polymorphic(B))" - " d = dynamic_cast((B*)addr);" - " if (!d) return false;" - " *offset = (long long)((char*)static_cast(d) - (char*)d);" - " return true; } }", - /*silent=*/false); } } // unnamed namespace @@ -1206,59 +1195,20 @@ bool interop::GetSmartPtrInfo(TCppScope_t klass, TCppScope_t* raw, } // type offsets -------------------------------------------------------------- -using BaseOffsetFunc_t = bool (*)(void*, bool, long long*); - -static BaseOffsetFunc_t make_base_offset_func(interop::TCppScope_t derived, - interop::TCppScope_t base) { - static unsigned long long func_count = 0; - const std::string name = - "__cppjit_base_offset_" + std::to_string(func_count++); - const std::string code = "extern \"C\" bool " + name + - "(void* addr, bool up, long long* offset) {" - " return __cppjit_internal::base_offset<" + - Cpp::GetQualifiedCompleteName(derived) + ", " + - Cpp::GetQualifiedCompleteName(base) + - ">(addr, up, offset); }"; - if (Cpp::Declare(code.c_str(), /*silent=*/true)) - return nullptr; - return (BaseOffsetFunc_t)Cpp::GetFunctionAddress(name.c_str()); -} - ptrdiff_t interop::GetBaseOffset(TCppScope_t derived, TCppScope_t base, TCppObject_t address, int direction, bool rerror) { std::lock_guard Lock(InterOpMutex); - // For up-casts, pass the address to GetBaseClassOffset which will read from - // the vtable/vbtable when the path goes through a virtual base. - // For down-casts (direction < 0), the address points to the base and we need - // dynamic_cast behavior, which the generated helper provides. - intptr_t offset; - if (direction > 0 && address) { - offset = Cpp::GetBaseClassOffset(derived, base, address.data); - } else { - offset = Cpp::GetBaseClassOffset(derived, base); - } + // Up-casts may start from a subobject whose complete type is unknown. For + // down-casts, callers obtain the complete type with GetActualClass, so use + // its static layout; address points to the base rather than the derived type. + intptr_t offset = Cpp::GetBaseClassOffset( + derived, base, direction > 0 ? address.data : nullptr); if (offset == -1) // Cling error, treat silently return rerror ? (ptrdiff_t)offset : 0; - // For down-casts we still need the generated helper to perform dynamic_cast - static std::map, BaseOffsetFunc_t> - s_vbase_offset_funcs; - if (address && direction < 0) { - const auto key = std::make_pair(derived.data, base.data); - auto func = s_vbase_offset_funcs.find(key); - if (func == s_vbase_offset_funcs.end()) - func = s_vbase_offset_funcs - .emplace(key, make_base_offset_func(derived, base)) - .first; - long long dynamic_offset = 0; - if (func->second && - func->second(address.data, direction > 0, &dynamic_offset)) - offset = (intptr_t)dynamic_offset; - } - return (ptrdiff_t)(direction < 0 ? -offset : offset); } diff --git a/test/test_advancedcpp.py b/test/test_advancedcpp.py index e64d7ef5..6bd7aa7b 100644 --- a/test/test_advancedcpp.py +++ b/test/test_advancedcpp.py @@ -1040,9 +1040,10 @@ def test30_virtual_base_of_hidden_type(self): namespace vbase_hidden { struct IB { virtual ~IB() {} virtual long id() const { return 42; } }; struct ID1 : virtual IB {}; - struct ID2 : virtual IB {}; + struct Lead { virtual ~Lead() {} long padding[2] = {}; }; + struct ID2 : Lead, virtual IB {}; struct IC { virtual ~IC() {} long z = 7; }; - struct M : IC {}; + struct M : Lead, IC {}; struct ID3 : virtual M {}; long call(IB* p) { return p->id(); } long call_c(IC* p) { return p->z; } @@ -1066,3 +1067,34 @@ def test30_virtual_base_of_hidden_type(self): d3 = cppjit.bind_object(ns.make3(), ns.ID3) assert ns.call_c(d3) == 7 assert d3.z == 7 + + def test31_virtual_base_downcast(self): + """GetActualClass supplies the complete type for virtual-base downcasts""" + + import cppjit + + cppjit.cppdef("""\ + namespace vbase_downcast { + struct IB { virtual ~IB() {} virtual long id() const { return 42; } }; + struct ID1 : virtual IB {}; + struct ID2 : virtual IB {}; + struct IC { virtual ~IC() {} long z = 7; }; + struct Pad { virtual ~Pad() {} long padding[2] = {}; }; + struct M : Pad, IC {}; + struct ID3 : virtual M {}; + struct Visible : ID1, ID2 { long value = 19; }; + struct Visible3 : ID1, ID3 { long value = 23; }; + IB* make() { return new Visible; } + IC* make3() { return new Visible3; } + }""") + + ns = cppjit.gbl.vbase_downcast + obj = ns.make() + assert isinstance(obj, ns.Visible) + assert obj.value == 19 + assert obj.id() == 42 + + obj3 = ns.make3() + assert isinstance(obj3, ns.Visible3) + assert obj3.value == 23 + assert obj3.z == 7