diff --git a/src/workerd/jsg/promise.h b/src/workerd/jsg/promise.h index e00b4c651f5..9c20ba83383 100644 --- a/src/workerd/jsg/promise.h +++ b/src/workerd/jsg/promise.h @@ -25,7 +25,26 @@ namespace workerd::jsg { template ()> struct OpaqueWrappable; +// Type tag used to identify OpaqueWrappable types without relying on RTTI/dynamic_cast. +// We use a unique address per T as a type tag. Alternatively, in theory, we could use +// an optimization of dynamic_cast where each of the specialized OpaqueWrappable types +// are marked final, which enables dynamic_cast to just compare vtable pointers. However, +// there's a bug in LLVM (https://github.com/llvm/llvm-project/issues/71196) that causes +// this optimization to fail when using thin-LTO, hidden visibility, and shared libraries +// (which we use for some tests and benchmarks). +template +struct OpaqueWrappableTypeTag final { + inline static const char storage = 0; + static const void* get() { + return &storage; + } +}; + struct OpaqueWrappableBase: public Wrappable { + const void* const typeTag; + + explicit OpaqueWrappableBase(const void* tag): typeTag(tag) {} + kj::StringPtr jsgGetMemoryName() const override final { return "OpaqueWrappable"_kjc; } @@ -35,10 +54,12 @@ struct OpaqueWrappableBase: public Wrappable { }; template -struct OpaqueWrappable: public OpaqueWrappableBase { +struct OpaqueWrappable final: public OpaqueWrappableBase { // Used to implement wrapOpaque(). - OpaqueWrappable(T&& value): value(kj::mv(value)) {} + OpaqueWrappable(T&& value) + : OpaqueWrappableBase(OpaqueWrappableTypeTag::get()), + value(kj::mv(value)) {} T value; bool movedAway = false; @@ -49,10 +70,19 @@ struct OpaqueWrappable: public OpaqueWrappableBase { }; template -struct OpaqueWrappable: public OpaqueWrappable { +struct OpaqueWrappable final: public OpaqueWrappableBase { // When T is GC-visitable, make sure to implement visitation. - using OpaqueWrappable::OpaqueWrappable; + OpaqueWrappable(T&& value) + : OpaqueWrappableBase(OpaqueWrappableTypeTag::get()), + value(kj::mv(value)) {} + + T value; + bool movedAway = false; + + size_t jsgGetMemorySelfSize() const override final { + return sizeof(OpaqueWrappable); + } void jsgVisitForGc(GcVisitor& visitor) override { if (!this->movedAway) { @@ -95,8 +125,9 @@ T unwrapOpaque(v8::Isolate* isolate, v8::Local handle) { static_assert(!isV8Local(), "can't opaque-wrap non-persistent handles"); Wrappable& wrappable = KJ_ASSERT_NONNULL(Wrappable::tryUnwrapOpaque(isolate, handle)); - OpaqueWrappable* holder = dynamic_cast*>(&wrappable); - KJ_ASSERT(holder != nullptr); + auto* base = static_cast(&wrappable); + KJ_ASSERT(base->typeTag == OpaqueWrappableTypeTag::get()); + auto* holder = static_cast*>(base); KJ_ASSERT(!holder->movedAway); holder->movedAway = true; return kj::mv(holder->value); @@ -111,8 +142,9 @@ T& unwrapOpaqueRef(v8::Isolate* isolate, v8::Local handle) { static_assert(!isV8Local(), "can't opaque-wrap non-persistent handles"); Wrappable& wrappable = KJ_ASSERT_NONNULL(Wrappable::tryUnwrapOpaque(isolate, handle)); - OpaqueWrappable* holder = dynamic_cast*>(&wrappable); - KJ_ASSERT(holder != nullptr); + auto* base = static_cast(&wrappable); + KJ_ASSERT(base->typeTag == OpaqueWrappableTypeTag::get()); + auto* holder = static_cast*>(base); KJ_ASSERT(!holder->movedAway); return holder->value; } @@ -126,8 +158,9 @@ void dropOpaque(v8::Isolate* isolate, v8::Local handle) { static_assert(!isV8Ref()); KJ_IF_SOME(wrappable, Wrappable::tryUnwrapOpaque(isolate, handle)) { - OpaqueWrappable* holder = dynamic_cast*>(&wrappable); - if (holder != nullptr) { + auto* base = static_cast(&wrappable); + if (base->typeTag == OpaqueWrappableTypeTag::get()) { + auto* holder = static_cast*>(base); holder->movedAway = true; auto drop KJ_UNUSED = kj::mv(holder->value); }