Compare commits

...

2 Commits

Author SHA1 Message Date
Jarred Sumner
9b506edd10 Merge branch 'main' into jarred/experiment-asyncpromise 2024-06-20 20:43:09 -07:00
Jarred Sumner
9aa5fe5244 Experiment: AsyncPromise, a Promise that captures 1 async stack frame 2024-06-18 04:28:43 -07:00
13 changed files with 369 additions and 24 deletions

View File

@@ -22,12 +22,14 @@
#include "JSDOMWrapper.h"
#include <JavaScriptCore/DeferredWorkTimer.h>
#include "JSAsyncPromise.h"
namespace WebCore {
using namespace JSC;
JSHeapData::JSHeapData(Heap& heap)
: m_heapCellTypeForJSWorkerGlobalScope(JSC::IsoHeapCellType::Args<Zig::GlobalObject>())
, m_heapCellTypeForJSAsyncPromise(JSC::IsoHeapCellType::Args<Bun::JSAsyncPromise>())
, m_domBuiltinConstructorSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMBuiltinConstructorBase)
, m_domConstructorSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMConstructorBase)
, m_domNamespaceObjectSpace ISO_SUBSPACE_INIT(heap, heap.cellHeapCellType, JSDOMObject)

View File

@@ -58,6 +58,7 @@ public:
}
JSC::IsoHeapCellType m_heapCellTypeForJSWorkerGlobalScope;
JSC::IsoHeapCellType m_heapCellTypeForJSAsyncPromise;
private:
Lock m_lock;

View File

@@ -0,0 +1,205 @@
#include "root.h"
#include "helpers.h"
#include "JavaScriptCore/Exception.h"
#include "JavaScriptCore/Interpreter.h"
#include "JavaScriptCore/JSCJSValue.h"
#include "BunClientData.h"
#include "wtf/Assertions.h"
#include "ZigGlobalObject.h"
#include "BunClientData.h"
#include "JavaScriptCore/SlotVisitorMacros.h"
#include "JavaScriptCore/WriteBarrier.h"
#include "wtf/IsoMallocInlines.h"
#include <JavaScriptCore/JSCell.h>
#include <JavaScriptCore/JSPromise.h>
#include <JavaScriptCore/StackFrame.h>
#include "JSAsyncPromise.h"
namespace Bun {
using namespace JSC;
const ClassInfo JSAsyncPromise::s_info = { "AsyncPromise"_s, nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(JSAsyncPromise) };
JSAsyncPromise* JSAsyncPromise::create(JSC::VM& vm, Zig::GlobalObject* bunGlobalObject)
{
auto* structure = bunGlobalObject->m_JSAsyncPromiseStructure.get(bunGlobalObject);
if (UNLIKELY(!structure)) {
return nullptr;
}
auto promise = JSPromise::create(vm, bunGlobalObject->promiseStructure());
auto* thisObject = new (NotNull, JSC::allocateCell<JSAsyncPromise>(vm)) JSAsyncPromise(vm, structure, promise);
thisObject->finishCreation(vm);
if (UNLIKELY(!thisObject)) {
return nullptr;
}
Vector<StackFrame> stackFrames;
vm.interpreter.getStackTrace(thisObject, stackFrames, 0);
StackFrame* lastBuiltinFrame = nullptr;
bool didPickAGoodFrame = false;
for (auto& frame : stackFrames) {
if (frame.hasLineAndColumnInfo()) {
if (auto* callee = frame.codeBlock()) {
auto* unlinked = callee->unlinkedCodeBlock();
if (unlinked && unlinked->isBuiltinFunction()) {
lastBuiltinFrame = &frame;
continue;
}
}
didPickAGoodFrame = true;
thisObject->frame = frame;
break;
}
}
if (!didPickAGoodFrame && lastBuiltinFrame) {
thisObject->frame = *lastBuiltinFrame;
}
return thisObject;
}
void JSAsyncPromise::destroy(JSC::JSCell* cell)
{
static_cast<JSAsyncPromise*>(cell)->JSAsyncPromise::destroy(cell);
}
void JSAsyncPromise::finishCreation(VM& vm)
{
Base::finishCreation(vm);
}
template<typename Visitor>
void JSAsyncPromise::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
JSAsyncPromise* thisObject = jsCast<JSAsyncPromise*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->promise);
thisObject->frame.visitAggregate(visitor);
}
DEFINE_VISIT_CHILDREN(JSAsyncPromise);
void JSAsyncPromise::reject(VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue value)
{
auto promise = this->promise.get();
ASSERT(promise);
if (this->frame.hasLineAndColumnInfo()) {
if (auto* errorInstance = JSC::jsDynamicCast<JSC::ErrorInstance*>(value)) {
auto* existingStackTrace = errorInstance->stackTrace();
if (existingStackTrace != nullptr) {
existingStackTrace->append(this->frame);
} else {
ASSERT_NOT_IMPLEMENTED_YET();
}
}
}
promise->reject(globalObject, value);
}
void JSAsyncPromise::resolve(VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue value)
{
auto promise = this->promise.get();
ASSERT(promise);
promise->resolve(globalObject, value);
}
// We tried to pool these
// But it was very complicated
class AsyncPromise {
WTF_MAKE_ISO_ALLOCATED(AsyncPromise);
public:
AsyncPromise(JSC::VM& vm, JSAsyncPromise* value)
: m_cell(vm, value)
{
}
AsyncPromise()
: m_cell()
{
}
JSC::Strong<JSAsyncPromise> m_cell;
};
WTF_MAKE_ISO_ALLOCATED_IMPL(AsyncPromise);
extern "C" void Bun__AsyncPromise__delete(AsyncPromise* strongRef)
{
delete strongRef;
}
extern "C" AsyncPromise* Bun__AsyncPromise__create(Zig::GlobalObject* globalObject)
{
auto& vm = globalObject->vm();
JSAsyncPromise* asyncPromise = JSAsyncPromise::create(globalObject->vm(), globalObject);
return new AsyncPromise(vm, asyncPromise);
}
extern "C" JSC::EncodedJSValue Bun__AsyncPromise__get(AsyncPromise* strongRef)
{
return JSC::JSValue::encode(strongRef->m_cell.get());
}
extern "C" void Bun__AsyncPromise__set(AsyncPromise* strongRef, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value)
{
strongRef->m_cell.set(globalObject->vm(), jsCast<JSAsyncPromise*>(JSC::JSValue::decode(value)));
}
extern "C" void Bun__AsyncPromise__clear(AsyncPromise* strongRef)
{
strongRef->m_cell.clear();
}
extern "C" void Bun__AsyncPromise__resolve(AsyncPromise* strongRef, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value)
{
auto* asyncPromise = strongRef->m_cell.get();
asyncPromise->resolve(globalObject->vm(), globalObject, JSC::JSValue::decode(value));
strongRef->m_cell.clear();
}
extern "C" void Bun__AsyncPromise__reject(AsyncPromise* strongRef, JSC::JSGlobalObject* globalObject, JSC::EncodedJSValue value)
{
auto* asyncPromise = strongRef->m_cell.get();
asyncPromise->reject(globalObject->vm(), globalObject, JSC::JSValue::decode(value));
strongRef->m_cell.clear();
}
extern "C" JSC::EncodedJSValue Bun__AsyncPromise__value(AsyncPromise* strongRef)
{
if (!strongRef->m_cell) {
return {};
}
auto* asyncPromise = strongRef->m_cell.get();
return JSC::JSValue::encode(asyncPromise->promise.get());
}
extern "C" JSC::JSPromise* Bun__AsyncPromise__promise(AsyncPromise* strongRef)
{
if (!strongRef->m_cell) {
return nullptr;
}
auto* asyncPromise = strongRef->m_cell.get();
return asyncPromise->promise.get();
}
JSC::Structure* createJSAsyncPromiseStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
return JSAsyncPromise::createStructure(vm, globalObject);
}
}

View File

@@ -0,0 +1,56 @@
#pragma once
namespace Bun {
class JSAsyncPromise : public JSC::JSCell {
public:
using Base = JSC::JSCell;
static constexpr unsigned StructureFlags = Base::StructureFlags | StructureIsImmortal;
static constexpr bool needsDestruction = true;
static JSAsyncPromise* create(JSC::VM& vm, Zig::GlobalObject* globalObject);
static void destroy(JSC::JSCell*);
~JSAsyncPromise() = default;
void finishCreation(VM& vm);
void reject(VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue value);
void resolve(VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue value);
mutable JSC::WriteBarrier<JSC::JSPromise> promise;
StackFrame frame;
DECLARE_VISIT_CHILDREN;
DECLARE_INFO;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
return Structure::create(vm, globalObject, jsNull(), TypeInfo(CellType, StructureFlags), info());
}
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return WebCore::subspaceForImpl<JSAsyncPromise, UseCustomHeapCellType::Yes>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSAsyncPromise.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSAsyncPromise = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSAsyncPromise.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSAsyncPromise = std::forward<decltype(space)>(space); },
[](auto& server) -> JSC::HeapCellType& { return server.m_heapCellTypeForJSAsyncPromise; });
}
JSAsyncPromise(JSC::VM& vm, JSC::Structure* structure, JSC::JSPromise* promise)
: Base(vm, structure)
, promise(promise, JSC::WriteBarrierEarlyInit)
{
}
};
JSC::Structure* createJSAsyncPromiseStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
}

View File

@@ -56,7 +56,7 @@
#include "JavaScriptCore/StackFrame.h"
#include "JavaScriptCore/StackVisitor.h"
#include "JavaScriptCore/VM.h"
#include "JSAsyncPromise.h"
#include "AddEventListenerOptions.h"
#include "AsyncContextFrame.h"
#include "BunClientData.h"
@@ -2533,6 +2533,10 @@ void GlobalObject::finishCreation(VM& vm)
m_commonStrings.initialize();
m_JSAsyncPromiseStructure.initLater([](const Initializer<Structure>& init) {
init.set(Bun::createJSAsyncPromiseStructure(init.vm, init.owner));
});
m_JSDOMFileConstructor.initLater(
[](const Initializer<JSObject>& init) {
JSObject* fileConstructor = Bun::createJSDOMFileConstructor(init.vm, init.owner);
@@ -3463,6 +3467,7 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_JSBufferSubclassStructure.visit(visitor);
thisObject->m_JSCryptoKey.visit(visitor);
thisObject->m_JSDOMFileConstructor.visit(visitor);
thisObject->m_JSAsyncPromiseStructure.visit(visitor);
thisObject->m_JSFFIFunctionStructure.visit(visitor);
thisObject->m_JSFileSinkClassStructure.visit(visitor);
thisObject->m_JSFileSinkControllerPrototype.visit(visitor);

View File

@@ -555,6 +555,7 @@ public:
LazyProperty<JSGlobalObject, Structure> m_NapiPrototypeStructure;
LazyProperty<JSGlobalObject, Structure> m_NAPIFunctionStructure;
LazyProperty<JSGlobalObject, Structure> m_JSSQLStatementStructure;
LazyProperty<JSGlobalObject, Structure> m_JSAsyncPromiseStructure;
LazyProperty<JSGlobalObject, JSObject> m_bunObject;
LazyProperty<JSGlobalObject, JSObject> m_cryptoObject;

View File

@@ -2187,6 +2187,80 @@ pub const JSPromise = extern struct {
};
}
pub const Async = struct {
strong: ?*AsyncPromise = null,
const AsyncPromise = opaque {
extern fn Bun__AsyncPromise__delete(*AsyncPromise) void;
extern fn Bun__AsyncPromise__resolve(*AsyncPromise, *JSGlobalObject, JSValue) void;
extern fn Bun__AsyncPromise__reject(*AsyncPromise, *JSGlobalObject, JSValue) void;
extern fn Bun__AsyncPromise__get(*AsyncPromise) JSValue;
extern fn Bun__AsyncPromise__value(*AsyncPromise) JSValue;
extern fn Bun__AsyncPromise__promise(*AsyncPromise) *JSC.JSPromise;
extern fn Bun__AsyncPromise__set(*AsyncPromise, JSValue) void;
extern fn Bun__AsyncPromise__create(*JSGlobalObject) *AsyncPromise;
pub const deinit = Bun__AsyncPromise__delete;
pub const resolve = Bun__AsyncPromise__resolve;
pub const reject = Bun__AsyncPromise__reject;
pub const get = Bun__AsyncPromise__get;
pub const value = Bun__AsyncPromise__value;
pub const promise = Bun__AsyncPromise__promise;
pub const set = Bun__AsyncPromise__set;
pub const init = Bun__AsyncPromise__create;
};
pub fn reject(this: *Async, globalThis: *JSC.JSGlobalObject, val: JSC.JSValue) void {
var str = this.strong.?;
this.strong = null;
str.reject(globalThis, val);
str.deinit();
}
/// Like `reject`, except it drains microtasks at the end of the current event loop iteration.
pub fn rejectTask(this: *Async, globalThis: *JSC.JSGlobalObject, val: JSC.JSValue) void {
const loop = JSC.VirtualMachine.get().eventLoop();
loop.enter();
defer loop.exit();
this.reject(globalThis, val);
}
pub fn resolve(this: *Async, globalThis: *JSC.JSGlobalObject, val: JSC.JSValue) void {
var str = this.strong.?;
this.strong = null;
str.resolve(globalThis, val);
str.deinit();
}
/// Like `resolve`, except it drains microtasks at the end of the current event loop iteration.
pub fn resolveTask(this: *Async, globalThis: *JSC.JSGlobalObject, val: JSC.JSValue) void {
const loop = JSC.VirtualMachine.get().eventLoop();
loop.enter();
defer loop.exit();
this.resolve(globalThis, val);
}
pub fn init(globalThis: *JSC.JSGlobalObject) Async {
return .{ .strong = AsyncPromise.init(globalThis) };
}
pub fn get(this: *const Async) *JSC.JSPromise {
return this.strong.?.promise();
}
pub fn value(this: *const Async) JSValue {
return this.strong.?.value();
}
pub fn deinit(this: *Async) void {
if (this.strong) |strong| {
this.strong = null;
strong.deinit();
}
}
};
pub const Strong = struct {
strong: JSC.Strong = .{},

View File

@@ -905,5 +905,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForEventListener;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForEventTarget;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForEventEmitter;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSAsyncPromise;
};
} // namespace WebCore

View File

@@ -909,6 +909,7 @@ public:
// std::unique_ptr<IsoSubspace> m_subspaceForDOMFormData;
// std::unique_ptr<IsoSubspace> m_subspaceForDOMFormDataIterator;
std::unique_ptr<IsoSubspace> m_subspaceForDOMURL;
std::unique_ptr<IsoSubspace> m_subspaceForJSAsyncPromise;
};
} // namespace WebCore

View File

@@ -143,7 +143,7 @@ pub const Async = struct {
fn NewAsyncFSTask(comptime ReturnType: type, comptime ArgumentType: type, comptime Function: anytype) type {
return struct {
promise: JSC.JSPromise.Strong,
promise: JSC.JSPromise.Async,
args: ArgumentType,
globalObject: *JSC.JSGlobalObject,
task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback },
@@ -163,7 +163,7 @@ pub const Async = struct {
var task = bun.new(
Task,
Task{
.promise = JSC.JSPromise.Strong.init(globalObject),
.promise = JSC.JSPromise.Async.init(globalObject),
.args = args,
.result = undefined,
.globalObject = globalObject,
@@ -204,9 +204,8 @@ pub const Async = struct {
break :brk out;
},
};
var promise_value = this.promise.value();
var promise = this.promise.get();
promise_value.ensureStillAlive();
var promise = this.promise;
this.promise = .{};
const tracker = this.tracker;
tracker.willDispatch(globalObject);
@@ -234,7 +233,7 @@ pub const Async = struct {
} else {
this.args.deinit();
}
this.promise.strong.deinit();
this.promise.deinit();
bun.destroy(this);
}
};
@@ -248,7 +247,7 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
const ShellTask = bun.shell.Interpreter.Builtin.Cp.ShellCpTask;
const ShellTaskT = if (is_shell) *ShellTask else u0;
return struct {
promise: JSC.JSPromise.Strong = .{},
promise: JSC.JSPromise.Async = .{},
args: Arguments.Cp,
evtloop: JSC.EventLoopHandle,
task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback },
@@ -374,7 +373,7 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
var task = bun.new(
ThisAsyncCpTask,
ThisAsyncCpTask{
.promise = if (comptime enable_promise) JSC.JSPromise.Strong.init(globalObject) else .{},
.promise = if (comptime enable_promise) JSC.JSPromise.Async.init(globalObject) else .{},
.args = cp_args,
.has_result = .{ .raw = false },
.result = undefined,
@@ -472,14 +471,14 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
break :brk out;
},
};
var promise_value = this.promise.value();
var promise = this.promise.get();
promise_value.ensureStillAlive();
const tracker = this.tracker;
tracker.willDispatch(globalObject);
defer tracker.didDispatch(globalObject);
var promise = this.promise;
this.promise = .{};
this.deinit();
switch (success) {
false => {
@@ -496,7 +495,7 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
this.deinitialized = true;
if (comptime !is_shell) this.ref.unref(this.evtloop);
this.args.deinit();
this.promise.strong.deinit();
this.promise.deinit();
this.arena.deinit();
bun.destroy(this);
}
@@ -727,7 +726,7 @@ pub fn NewAsyncCpTask(comptime is_shell: bool) type {
}
pub const AsyncReaddirRecursiveTask = struct {
promise: JSC.JSPromise.Strong,
promise: JSC.JSPromise.Async,
args: Arguments.Readdir,
globalObject: *JSC.JSGlobalObject,
task: JSC.WorkPoolTask = .{ .callback = &workPoolCallback },
@@ -835,7 +834,7 @@ pub const AsyncReaddirRecursiveTask = struct {
vm: *JSC.VirtualMachine,
) JSC.JSValue {
var task = AsyncReaddirRecursiveTask.new(.{
.promise = JSC.JSPromise.Strong.init(globalObject),
.promise = JSC.JSPromise.Async.init(globalObject),
.args = args,
.has_result = .{ .raw = false },
.globalObject = globalObject,
@@ -1030,9 +1029,8 @@ pub const AsyncReaddirRecursiveTask = struct {
break :brk out;
};
var promise_value = this.promise.value();
var promise = this.promise.get();
promise_value.ensureStillAlive();
var promise = this.promise;
this.promise = .{};
const tracker = this.tracker;
tracker.willDispatch(globalObject);
@@ -1059,7 +1057,7 @@ pub const AsyncReaddirRecursiveTask = struct {
this.args.deinit();
bun.default_allocator.free(this.root_path.slice());
this.clearResultList();
this.promise.strong.deinit();
this.promise.deinit();
this.destroy();
}
};

View File

@@ -688,14 +688,14 @@ pub const WriteFileWaitFromLockedValueTask = struct {
.Error => |err| {
file_blob.detach();
_ = value.use();
this.promise.strong.deinit();
this.promise.deinit();
bun.destroy(this);
promise.reject(globalThis, err);
},
.Used => {
file_blob.detach();
_ = value.use();
this.promise.strong.deinit();
this.promise.deinit();
bun.destroy(this);
promise.reject(globalThis, ZigString.init("Body was used after it was consumed").toErrorInstance(globalThis));
},
@@ -727,7 +727,7 @@ pub const WriteFileWaitFromLockedValueTask = struct {
}
file_blob.detach();
this.promise.strong.deinit();
this.promise.deinit();
bun.destroy(this);
},
.Locked => {

View File

@@ -1195,7 +1195,7 @@ pub const Fetch = struct {
.globalObject = globalThis,
.task = undefined,
};
this.promise.strong = .{};
this.promise = .{};
holder.task = switch (success) {
true => JSC.AnyTask.New(Holder, Holder.resolve).init(holder),
false => JSC.AnyTask.New(Holder, Holder.reject).init(holder),

View File

@@ -739,7 +739,7 @@ pub const StreamResult = union(Tag) {
pub fn deinit(this: *@This()) void {
if (this.* == .promise) {
this.promise.strong.deinit();
this.promise.deinit();
this.* = .{ .none = {} };
}
}