Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
danlapid committed Nov 30, 2024
1 parent 37e8d46 commit b9369dc
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 58 deletions.
8 changes: 8 additions & 0 deletions src/workerd/io/worker.c++
Original file line number Diff line number Diff line change
Expand Up @@ -1279,6 +1279,10 @@ Worker::Script::Script(kj::Own<const Isolate> isolateParam,
// (Undocumented, as usual.)
context =
v8::Context::New(lock.v8Isolate, nullptr, v8::ObjectTemplate::New(lock.v8Isolate));
// We need to set the highest used index in every context we create to be a nullptr
// This is because we might later on call GetAlignedPointerFromEmbedderData which fails with
// a fatal error if the array is smaller than the given index.
context->SetAlignedPointerInEmbedderData(3, nullptr);
}

JSG_WITHIN_CONTEXT_SCOPE(lock, context, [&](jsg::Lock& js) {
Expand Down Expand Up @@ -2594,6 +2598,10 @@ class Worker::Isolate::InspectorChannelImpl final: public v8_inspector::V8Inspec
// We don't know which contexts exist in this isolate, so I guess we have to
// create one. Ugh.
auto dummyContext = v8::Context::New(lock->v8Isolate);
// We need to set the highest used index in every context we create to be a nullptr
// This is because we might later on call GetAlignedPointerFromEmbedderData which fails with
// a fatal error if the array is smaller than the given index.
dummyContext->SetAlignedPointerInEmbedderData(3, nullptr);
auto& inspector = *KJ_ASSERT_NONNULL(isolate.impl->inspector);
inspector.contextCreated(v8_inspector::V8ContextInfo(dummyContext, 1,
v8_inspector::StringView(reinterpret_cast<const uint8_t*>("Worker"), 6)));
Expand Down
4 changes: 4 additions & 0 deletions src/workerd/jsg/resource.h
Original file line number Diff line number Diff line change
Expand Up @@ -1435,6 +1435,10 @@ class ResourceWrapper {

// Store a pointer to this object in slot 1, to be extracted in callbacks.
context->SetAlignedPointerInEmbedderData(1, ptr.get());
// We need to set the highest used index in every context we create to be a nullptr
// This is because we might later on call GetAlignedPointerFromEmbedderData which fails with
// a fatal error if the array is smaller than the given index.
context->SetAlignedPointerInEmbedderData(3, nullptr);

// (Note: V8 docs say: "Note that index 0 currently has a special meaning for Chrome's
// debugger." We aren't Chrome, but it does appear that some versions of V8 will mess with
Expand Down
27 changes: 0 additions & 27 deletions src/workerd/jsg/setup.c++
Original file line number Diff line number Diff line change
Expand Up @@ -404,33 +404,6 @@ v8::Local<v8::FunctionTemplate> IsolateBase::getOpaqueTemplate(v8::Isolate* isol
return static_cast<IsolateBase*>(isolate->GetData(0))->opaqueTemplate.Get(isolate);
}

void IsolateBase::dropWrappers(kj::Own<void> typeWrapperInstance) {
// Delete all wrappers.
jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {
v8::Locker lock(ptr);
v8::Isolate::Scope isolateScope(ptr);

// Make sure everything in the deferred destruction queue is dropped.
clearDestructionQueue();
clearPendingExternalMemoryDecrement();

// We MUST call heapTracer.destroy(), but we can't do it yet because destroying other handles
// may call into the heap tracer.
KJ_DEFER(heapTracer.destroy());

// Make sure v8::Globals are destroyed under lock (but not until later).
KJ_DEFER(symbolAsyncDispose.Reset());
KJ_DEFER(opaqueTemplate.Reset());

// Make sure the TypeWrapper is destroyed under lock by declaring a new copy of the variable
// that is destroyed before the lock is released.
kj::Own<void> typeWrapperInstanceInner = kj::mv(typeWrapperInstance);

// Destroy all wrappers.
heapTracer.clearWrappers();
});
}

void IsolateBase::fatalError(const char* location, const char* message) {
reportV8FatalError(location, message);
}
Expand Down
161 changes: 130 additions & 31 deletions src/workerd/jsg/setup.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <kj/map.h>
#include <kj/mutex.h>
#include <kj/vector.h>

namespace workerd::jsg {

Expand Down Expand Up @@ -295,7 +296,31 @@ class IsolateBase {
~IsolateBase() noexcept(false);
KJ_DISALLOW_COPY_AND_MOVE(IsolateBase);

void dropWrappers(kj::Own<void> typeWrapperInstance);
void dropWrappers(auto typeWrapperInstance) {
jsg::runInV8Stack([&](jsg::V8StackScope& stackScope) {
v8::Locker lock(ptr);
v8::Isolate::Scope isolateScope(ptr);

// Make sure everything in the deferred destruction queue is dropped.
clearDestructionQueue();
clearPendingExternalMemoryDecrement();

// We MUST call heapTracer.destroy(), but we can't do it yet because destroying other handles
// may call into the heap tracer.
KJ_DEFER(heapTracer.destroy());

// Make sure v8::Globals are destroyed under lock (but not until later).
KJ_DEFER(symbolAsyncDispose.Reset());
KJ_DEFER(opaqueTemplate.Reset());

// Make sure the TypeWrapper is destroyed under lock by declaring a new copy of the variable
// that is destroyed before the lock is released.
auto typeWrapperInstanceInner = kj::mv(typeWrapperInstance);

// Destroy all wrappers.
heapTracer.clearWrappers();
});
}

bool getCaptureThrowsAsRejections() const {
return captureThrowsAsRejections;
Expand Down Expand Up @@ -392,20 +417,28 @@ kj::Maybe<kj::StringPtr> getJsStackTrace(void* ucontext, kj::ArrayPtr<char> scra
template <typename TypeWrapper>
class Isolate: public IsolateBase {
public:
explicit Isolate(bool dud,
const V8System& system,
kj::Own<IsolateObserver> observer,
v8::Isolate::CreateParams createParams = {})
: IsolateBase(system, kj::mv(createParams), kj::mv(observer)) {
wrappers.resize(1);
}

// Construct an isolate that requires configuration. `configuration` is a value that all
// individual wrappers' configurations must be able to be constructed from. For example, if all
// wrappers use the same configuration type, then `MetaConfiguration` should just be that type.
// If different wrappers use different types, then `MetaConfiguration` should be some value that
// inherits or defines conversion operators to each required type -- or the individual
// configuration types must declare constructors from `MetaConfiguration`.
// TODO(now): update this comment
template <typename MetaConfiguration>
explicit Isolate(const V8System& system,
MetaConfiguration&& configuration,
kj::Own<IsolateObserver> observer,
v8::Isolate::CreateParams createParams = {})
: IsolateBase(system, kj::mv(createParams), kj::mv(observer)),
wrapper(wrapperSpace.construct(ptr, kj::fwd<MetaConfiguration>(configuration))) {
wrapper->initTypeWrapper();
: Isolate(false, system, kj::mv(observer), kj::mv(createParams)) {
instantiateDefaultWrapper(kj::fwd<MetaConfiguration>(configuration));
}

// Use this constructor when no wrappers have any required configuration.
Expand All @@ -414,23 +447,39 @@ class Isolate: public IsolateBase {
v8::Isolate::CreateParams createParams = {})
: Isolate(system, nullptr, kj::mv(observer), kj::mv(createParams)) {}

template <typename MetaConfiguration>
void instantiateDefaultWrapper(MetaConfiguration&& configuration) {
KJ_DASSERT(wrappers[0].get() == nullptr);
auto wrapper = wrapperSpace.construct(ptr, kj::fwd<MetaConfiguration>(configuration));
wrapper->initTypeWrapper();
wrappers[0] = kj::mv(wrapper);
}

template <typename MetaConfiguration>
kj::Own<TypeWrapper>& instantiateWrapper(MetaConfiguration&& configuration) {
hasExtraWrappers = true;
return wrappers.add(kj::heap<TypeWrapper>(ptr, kj::fwd<MetaConfiguration>(configuration)));
}

~Isolate() noexcept(false) {
dropWrappers(kj::mv(wrapper));
dropWrappers(wrappers.releaseAsArray());
}

public:
kj::Exception unwrapException(
v8::Local<v8::Context> context, v8::Local<v8::Value> exception) override {
return wrapper->template unwrap<kj::Exception>(
return getWrapperByContext(context)->template unwrap<kj::Exception>(
context, exception, jsg::TypeErrorContext::other());
}

v8::Local<v8::Value> wrapException(
v8::Local<v8::Context> context, kj::Exception&& exception) override {
return wrapper->wrap(context, kj::none, kj::fwd<kj::Exception>(exception));
return getWrapperByContext(context)->wrap(context, kj::none, kj::fwd<kj::Exception>(exception));
}

bool serialize(
Lock& js, std::type_index type, jsg::Object& instance, Serializer& serializer) override {
auto* wrapper = getWrapperByContext(js.v8Context());
KJ_IF_SOME(func, wrapper->serializerMap.find(type)) {
func(*wrapper, js, instance, serializer);
return true;
Expand All @@ -440,6 +489,7 @@ class Isolate: public IsolateBase {
}
kj::Maybe<v8::Local<v8::Object>> deserialize(
Lock& js, uint tag, Deserializer& deserializer) override {
auto* wrapper = getWrapperByContext(js.v8Context());
KJ_IF_SOME(func, wrapper->deserializerMap.find(tag)) {
return func(*wrapper, js, tag, deserializer);
} else {
Expand Down Expand Up @@ -475,29 +525,31 @@ class Isolate: public IsolateBase {
// Wrap a C++ value, returning a v8::Local (possibly of a specific type).
template <typename T>
auto wrap(v8::Local<v8::Context> context, T&& value) {
return jsgIsolate.wrapper->wrap(context, kj::none, kj::fwd<T>(value));
return jsgIsolate.getWrapperByContext(context)->wrap(context, kj::none, kj::fwd<T>(value));
}

// Wrap a context-independent value. Only a few built-in types, like numbers and strings,
// can be wrapped without a context.
template <typename T>
auto wrapNoContext(T&& value) {
return jsgIsolate.wrapper->wrap(v8Isolate, kj::none, kj::fwd<T>(value));
return jsgIsolate.getWrapperByContext(v8Context())
->wrap(v8Isolate, kj::none, kj::fwd<T>(value));
}

// Convert a JavaScript value to a C++ value, or throw a JS exception if the type doesn't
// match.
template <typename T>
auto unwrap(v8::Local<v8::Context> context, v8::Local<v8::Value> handle) {
return jsgIsolate.wrapper->template unwrap<T>(
return jsgIsolate.getWrapperByContext(context)->template unwrap<T>(
context, handle, jsg::TypeErrorContext::other());
}

Ref<DOMException> domException(
kj::String name, kj::String message, kj::Maybe<kj::String> maybeStack) override {
return withinHandleScope([&] {
v8::Local<v8::FunctionTemplate> tmpl =
jsgIsolate.wrapper->getTemplate(v8Isolate, static_cast<DOMException*>(nullptr));
jsgIsolate.getWrapperByContext(v8Context())
->getTemplate(v8Isolate, static_cast<DOMException*>(nullptr));
KJ_DASSERT(!tmpl.IsEmpty());
v8::Local<v8::Object> obj = check(tmpl->InstanceTemplate()->NewInstance(v8Context()));
v8::Local<v8::String> stackName = str("stack"_kjc);
Expand Down Expand Up @@ -533,47 +585,68 @@ class Isolate: public IsolateBase {
template <typename T>
jsg::JsObject getConstructor(v8::Local<v8::Context> context) {
v8::EscapableHandleScope scope(v8Isolate);
v8::Local<v8::FunctionTemplate> tpl = jsgIsolate.wrapper->getTemplate(v8Isolate, (T*)nullptr);
v8::Local<v8::FunctionTemplate> tpl =
jsgIsolate.getWrapperByContext(context)->getTemplate(v8Isolate, (T*)nullptr);
v8::Local<v8::Object> prototype = check(tpl->GetFunction(context));
return jsg::JsObject(scope.Escape(prototype));
}

v8::Local<v8::ArrayBuffer> wrapBytes(kj::Array<byte> data) override {
return jsgIsolate.wrapper->wrap(v8Isolate, kj::none, kj::mv(data));
return jsgIsolate.getWrapperByContext(v8Context())->wrap(v8Isolate, kj::none, kj::mv(data));
}
v8::Local<v8::Function> wrapSimpleFunction(v8::Local<v8::Context> context,
jsg::Function<void(const v8::FunctionCallbackInfo<v8::Value>& info)> simpleFunction)
override {
return jsgIsolate.wrapper->wrap(context, kj::none, kj::mv(simpleFunction));
return jsgIsolate.getWrapperByContext(context)->wrap(
context, kj::none, kj::mv(simpleFunction));
}
v8::Local<v8::Function> wrapReturningFunction(v8::Local<v8::Context> context,
jsg::Function<v8::Local<v8::Value>(const v8::FunctionCallbackInfo<v8::Value>& info)>
returningFunction) override {
return jsgIsolate.wrapper->wrap(context, kj::none, kj::mv(returningFunction));
return jsgIsolate.getWrapperByContext(context)->wrap(
context, kj::none, kj::mv(returningFunction));
}
v8::Local<v8::Function> wrapPromiseReturningFunction(v8::Local<v8::Context> context,
jsg::Function<jsg::Promise<jsg::Value>(const v8::FunctionCallbackInfo<v8::Value>& info)>
returningFunction) override {
return jsgIsolate.wrapper->wrap(context, kj::none, kj::mv(returningFunction));
return jsgIsolate.getWrapperByContext(context)->wrap(
context, kj::none, kj::mv(returningFunction));
}
kj::String toString(v8::Local<v8::Value> value) override {
return jsgIsolate.wrapper->template unwrap<kj::String>(
v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());
return jsgIsolate.getWrapperByContext(v8Context())
->template unwrap<kj::String>(
v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());
}
jsg::Dict<v8::Local<v8::Value>> toDict(v8::Local<v8::Value> value) override {
return jsgIsolate.wrapper->template unwrap<jsg::Dict<v8::Local<v8::Value>>>(
v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());
return jsgIsolate.getWrapperByContext(v8Context())
->template unwrap<jsg::Dict<v8::Local<v8::Value>>>(
v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());
}
jsg::Dict<jsg::JsValue> toDict(const jsg::JsValue& value) override {
return jsgIsolate.wrapper->template unwrap<jsg::Dict<jsg::JsValue>>(
v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());
return jsgIsolate.getWrapperByContext(v8Context())
->template unwrap<jsg::Dict<jsg::JsValue>>(
v8Isolate->GetCurrentContext(), value, jsg::TypeErrorContext::other());
}
v8::Local<v8::Promise> wrapSimplePromise(jsg::Promise<jsg::Value> promise) override {
return jsgIsolate.wrapper->wrap(v8Context(), kj::none, kj::mv(promise));
return jsgIsolate.getWrapperByContext(v8Context())
->wrap(v8Context(), kj::none, kj::mv(promise));
}
jsg::Promise<jsg::Value> toPromise(v8::Local<v8::Value> promise) override {
return jsgIsolate.wrapper->template unwrap<jsg::Promise<jsg::Value>>(
v8Isolate->GetCurrentContext(), promise, jsg::TypeErrorContext::other());
return jsgIsolate.getWrapperByContext(v8Context())
->template unwrap<jsg::Promise<jsg::Value>>(
v8Isolate->GetCurrentContext(), promise, jsg::TypeErrorContext::other());
}

template <typename T, typename... Args>
JsContext<T> newContextWithWrapperIndex(
kj::Own<TypeWrapper>& wrapper, NewContextOptions options, Args&&... args) {
// TODO(soon): Requiring move semantics for the global object is awkward. This should instead
// allocate the object (forwarding arguments to the constructor) and return something like
// a Ref.
auto context = wrapper->newContext(*this, options, jsgIsolate.getObserver(),
static_cast<T*>(nullptr), kj::fwd<Args>(args)...);
context.getHandle(v8Isolate)->SetAlignedPointerInEmbedderData(3, wrapper.get());
return context;
}

// Creates a new JavaScript "context", i.e. the global object. This is the first step to
Expand All @@ -584,8 +657,9 @@ class Isolate: public IsolateBase {
// TODO(soon): Requiring move semantics for the global object is awkward. This should instead
// allocate the object (forwarding arguments to the constructor) and return something like
// a Ref.
return jsgIsolate.wrapper->newContext(*this, options, jsgIsolate.getObserver(),
static_cast<T*>(nullptr), kj::fwd<Args>(args)...);
KJ_DASSERT(!jsgIsolate.wrappers.empty());
KJ_DASSERT(jsgIsolate.wrappers[0].get() != nullptr);
return newContextWithWrapperIndex<T>(jsgIsolate.wrappers[0], options, kj::fwd<Args>(args)...);
}

// Creates a new JavaScript "context", i.e. the global object. This is the first step to
Expand All @@ -596,10 +670,17 @@ class Isolate: public IsolateBase {
return newContext<T>(NewContextOptions{}, kj::fwd<Args>(args)...);
}

template <typename T, typename MetaConfiguration, typename... Args>
JsContext<T> newContextWithConfiguration(
MetaConfiguration&& configuration, NewContextOptions options, Args&&... args) {
auto& wrapper = jsgIsolate.instantiateWrapper(kj::fwd<MetaConfiguration>(configuration));
return newContextWithWrapperIndex<T>(wrapper, options, kj::fwd<Args>(args)...);
}

void reportError(const JsValue& value) override {
KJ_IF_SOME(domException,
jsgIsolate.wrapper->tryUnwrap(
v8Context(), value, static_cast<DOMException*>(nullptr), kj::none)) {
jsgIsolate.getWrapperByContext(v8Context())
->tryUnwrap(v8Context(), value, static_cast<DOMException*>(nullptr), kj::none)) {
auto desc =
kj::str("DOMException(", domException.getName(), "): ", domException.getMessage());
jsgIsolate.reportError(*this, kj::mv(desc), value, JsMessage::create(*this, value));
Expand All @@ -615,7 +696,7 @@ class Isolate: public IsolateBase {
virtual kj::Maybe<Object&> getInstance(
v8::Local<v8::Object> obj, const std::type_info& type) override {
auto instance = v8::Local<v8::Object>(obj)->FindInstanceInPrototypeChain(
jsgIsolate.wrapper->getDynamicTypeInfo(v8Isolate, type).tmpl);
jsgIsolate.getWrapperByContext(v8Context())->getDynamicTypeInfo(v8Isolate, type).tmpl);
if (instance.IsEmpty()) {
return kj::none;
} else {
Expand All @@ -634,9 +715,27 @@ class Isolate: public IsolateBase {
});
}

protected:
inline TypeWrapper* getWrapperByContext(auto context) {
if (KJ_LIKELY(!hasExtraWrappers)) {
return wrappers[0].get();
} else {
auto ptr = context->GetAlignedPointerFromEmbedderData(3);
if (KJ_LIKELY(ptr != nullptr)) {
return static_cast<TypeWrapper*>(ptr);
} else {
// This can happen when we create dummy contexts such as in worker.c++.
return wrappers[0].get();
}
}
}

private:
kj::SpaceFor<TypeWrapper> wrapperSpace;
kj::Own<TypeWrapper> wrapper; // Needs to be destroyed under lock...
kj::Vector<kj::Own<TypeWrapper>> wrappers; // Needs to be destroyed under lock...
// This is just an optimization boolean, when we only have one wrapper we can skip calling
// GetAlignedPointerFromEmbedderData and just return wrappers[0].
bool hasExtraWrappers = false;
};

// This macro helps cut down on template spam in error messages. Instead of instantiating Isolate
Expand Down

0 comments on commit b9369dc

Please sign in to comment.