@@ -25,7 +25,26 @@ namespace workerd::jsg {
2525template <typename T, bool = isGcVisitable<T>()>
2626struct OpaqueWrappable ;
2727
28+ // Type tag used to identify OpaqueWrappable<T> types without relying on RTTI/dynamic_cast.
29+ // We use a unique address per T as a type tag. Alternatively, in theory, we could use
30+ // an optimization of dynamic_cast where each of the specialized OpaqueWrappable<T> types
31+ // are marked final, which enables dynamic_cast to just compare vtable pointers. However,
32+ // there's a bug in LLVM (https://github.com/llvm/llvm-project/issues/71196) that causes
33+ // this optimization to fail when using thin-LTO, hidden visibility, and shared libraries
34+ // (which we use for some tests and benchmarks).
35+ template <typename T>
36+ struct OpaqueWrappableTypeTag final {
37+ inline static const char storage = 0 ;
38+ static const void * get () {
39+ return &storage;
40+ }
41+ };
42+
2843struct OpaqueWrappableBase : public Wrappable {
44+ const void * const typeTag;
45+
46+ explicit OpaqueWrappableBase (const void * tag): typeTag(tag) {}
47+
2948 kj::StringPtr jsgGetMemoryName () const override final {
3049 return " OpaqueWrappable" _kjc;
3150 }
@@ -35,10 +54,12 @@ struct OpaqueWrappableBase: public Wrappable {
3554};
3655
3756template <typename T>
38- struct OpaqueWrappable <T, false >: public OpaqueWrappableBase {
57+ struct OpaqueWrappable <T, false > final : public OpaqueWrappableBase {
3958 // Used to implement wrapOpaque().
4059
41- OpaqueWrappable (T&& value): value(kj::mv(value)) {}
60+ OpaqueWrappable (T&& value)
61+ : OpaqueWrappableBase(OpaqueWrappableTypeTag<T>::get()),
62+ value (kj::mv(value)) {}
4263
4364 T value;
4465 bool movedAway = false ;
@@ -49,10 +70,19 @@ struct OpaqueWrappable<T, false>: public OpaqueWrappableBase {
4970};
5071
5172template <typename T>
52- struct OpaqueWrappable <T, true >: public OpaqueWrappable<T, false > {
73+ struct OpaqueWrappable <T, true > final : public OpaqueWrappableBase {
5374 // When T is GC-visitable, make sure to implement visitation.
5475
55- using OpaqueWrappable<T, false >::OpaqueWrappable;
76+ OpaqueWrappable (T&& value)
77+ : OpaqueWrappableBase(OpaqueWrappableTypeTag<T>::get()),
78+ value (kj::mv(value)) {}
79+
80+ T value;
81+ bool movedAway = false ;
82+
83+ size_t jsgGetMemorySelfSize () const override final {
84+ return sizeof (OpaqueWrappable);
85+ }
5686
5787 void jsgVisitForGc (GcVisitor& visitor) override {
5888 if (!this ->movedAway ) {
@@ -95,8 +125,9 @@ T unwrapOpaque(v8::Isolate* isolate, v8::Local<v8::Value> handle) {
95125 static_assert (!isV8Local<T>(), " can't opaque-wrap non-persistent handles" );
96126
97127 Wrappable& wrappable = KJ_ASSERT_NONNULL (Wrappable::tryUnwrapOpaque (isolate, handle));
98- OpaqueWrappable<T>* holder = dynamic_cast <OpaqueWrappable<T>*>(&wrappable);
99- KJ_ASSERT (holder != nullptr );
128+ auto * base = static_cast <OpaqueWrappableBase*>(&wrappable);
129+ KJ_ASSERT (base->typeTag == OpaqueWrappableTypeTag<T>::get ());
130+ auto * holder = static_cast <OpaqueWrappable<T>*>(base);
100131 KJ_ASSERT (!holder->movedAway );
101132 holder->movedAway = true ;
102133 return kj::mv (holder->value );
@@ -111,8 +142,9 @@ T& unwrapOpaqueRef(v8::Isolate* isolate, v8::Local<v8::Value> handle) {
111142 static_assert (!isV8Local<T>(), " can't opaque-wrap non-persistent handles" );
112143
113144 Wrappable& wrappable = KJ_ASSERT_NONNULL (Wrappable::tryUnwrapOpaque (isolate, handle));
114- OpaqueWrappable<T>* holder = dynamic_cast <OpaqueWrappable<T>*>(&wrappable);
115- KJ_ASSERT (holder != nullptr );
145+ auto * base = static_cast <OpaqueWrappableBase*>(&wrappable);
146+ KJ_ASSERT (base->typeTag == OpaqueWrappableTypeTag<T>::get ());
147+ auto * holder = static_cast <OpaqueWrappable<T>*>(base);
116148 KJ_ASSERT (!holder->movedAway );
117149 return holder->value ;
118150}
@@ -126,8 +158,9 @@ void dropOpaque(v8::Isolate* isolate, v8::Local<v8::Value> handle) {
126158 static_assert (!isV8Ref<T>());
127159
128160 KJ_IF_SOME (wrappable, Wrappable::tryUnwrapOpaque (isolate, handle)) {
129- OpaqueWrappable<T>* holder = dynamic_cast <OpaqueWrappable<T>*>(&wrappable);
130- if (holder != nullptr ) {
161+ auto * base = static_cast <OpaqueWrappableBase*>(&wrappable);
162+ if (base->typeTag == OpaqueWrappableTypeTag<T>::get ()) {
163+ auto * holder = static_cast <OpaqueWrappable<T>*>(base);
131164 holder->movedAway = true ;
132165 auto drop KJ_UNUSED = kj::mv (holder->value );
133166 }
0 commit comments