Skip to content

Commit 1a2b9c2

Browse files
committed
Mark OpaqueWrappable specializations as final
For better perf optimization of dynamic_cast<T*>
1 parent 04fb23c commit 1a2b9c2

File tree

1 file changed

+43
-10
lines changed

1 file changed

+43
-10
lines changed

src/workerd/jsg/promise.h

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,26 @@ namespace workerd::jsg {
2525
template <typename T, bool = isGcVisitable<T>()>
2626
struct 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+
2843
struct 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

3756
template <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

5172
template <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

Comments
 (0)