From 175cb7f2fa742e6c7e9dab9293c6824c7d280242 Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Tue, 4 Jun 2024 18:29:21 +0000 Subject: [PATCH] Bug 1900276 - Implement bytecode to handle using syntax in module context. r=arai Differential Revision: https://phabricator.services.mozilla.com/D212400 --- js/src/frontend/BytecodeEmitter.cpp | 8 +++ js/src/frontend/Parser.cpp | 15 ++++- .../using-in-module-dispose-order.js | 24 ++++++++ .../using-in-module.js | 16 ++++++ js/src/vm/EnvironmentObject.cpp | 55 +++++++++++++++---- js/src/vm/EnvironmentObject.h | 21 +++++++ js/src/vm/Interpreter.cpp | 24 ++++++-- 7 files changed, 143 insertions(+), 20 deletions(-) create mode 100644 js/src/jit-test/tests/explicit-resource-management/using-in-module-dispose-order.js create mode 100644 js/src/jit-test/tests/explicit-resource-management/using-in-module.js diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 36312e962382..5674a0ada84e 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2467,6 +2467,14 @@ bool BytecodeEmitter::emitScript(ParseNode* body) { } } +#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT + if (emitterScope.hasDisposables()) { + if (!emit1(JSOp::DisposeDisposables)) { + return false; + } + } +#endif + if (!markSimpleBreakpoint()) { return false; } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 60f4aecc0935..50b588d4b7cf 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1216,8 +1216,12 @@ static Maybe NewModuleScopeData( } ModuleScope::ParserData* bindings = nullptr; - uint32_t numBindings = - imports.length() + vars.length() + lets.length() + consts.length(); + uint32_t numBindings = imports.length() + vars.length() + lets.length() + + consts.length() +#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT + + usings.length() +#endif + ; if (numBindings > 0) { bindings = NewEmptyBindingData(fc, alloc, numBindings); @@ -1229,7 +1233,12 @@ static Maybe NewModuleScopeData( InitializeBindingData(bindings, numBindings, imports, &ParserModuleScopeSlotInfo::varStart, vars, &ParserModuleScopeSlotInfo::letStart, lets, - &ParserModuleScopeSlotInfo::constStart, consts); + &ParserModuleScopeSlotInfo::constStart, consts +#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT + , + &ParserModuleScopeSlotInfo::usingStart, usings +#endif + ); } return Some(bindings); diff --git a/js/src/jit-test/tests/explicit-resource-management/using-in-module-dispose-order.js b/js/src/jit-test/tests/explicit-resource-management/using-in-module-dispose-order.js new file mode 100644 index 000000000000..4fa640b6949f --- /dev/null +++ b/js/src/jit-test/tests/explicit-resource-management/using-in-module-dispose-order.js @@ -0,0 +1,24 @@ +// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management") + +load(libdir + "asserts.js"); + +globalThis.callOrder = []; + +const m = parseModule(` +using x = { + [Symbol.dispose]() { + globalThis.callOrder.push("x"); + } +} + +using y = { + [Symbol.dispose]() { + globalThis.callOrder.push("y"); + } +} +`); + +moduleLink(m); +moduleEvaluate(m); + +assertArrayEq(globalThis.callOrder, ["y", "x"]); diff --git a/js/src/jit-test/tests/explicit-resource-management/using-in-module.js b/js/src/jit-test/tests/explicit-resource-management/using-in-module.js new file mode 100644 index 000000000000..af14cc375231 --- /dev/null +++ b/js/src/jit-test/tests/explicit-resource-management/using-in-module.js @@ -0,0 +1,16 @@ +// |jit-test| skip-if: !getBuildConfiguration("explicit-resource-management") + +globalThis.called = false; + +const m = parseModule(` +using x = { + [Symbol.dispose]() { + globalThis.called = true; + } +} +`); + +moduleLink(m); +moduleEvaluate(m); + +assertEq(globalThis.called, true); diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp index f7b4e286c6c6..889b3d4ffafd 100644 --- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -414,9 +414,50 @@ ModuleEnvironmentObject* ModuleEnvironmentObject::create( MOZ_ASSERT(!env->inDictionaryMode()); #endif +#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT + env->initSlot(ModuleEnvironmentObject::DISPOSABLE_OBJECTS_SLOT, + UndefinedValue()); +#endif + return env; } +#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT +// TODO: at the time of unflagging ENABLE_EXPLICIT_RESOURCE_MANAGEMENT +// consider having a common base class for LexicalEnvironmentObject and +// ModuleEnvironmentObject containing all the common code. +static bool addDisposableObjectHelper(JS::Handle env, + uint32_t slot, JSContext* cx, + JS::Handle val) { + Value slotData = env->getReservedSlot(slot); + ListObject* disposablesList = nullptr; + if (slotData.isUndefined()) { + disposablesList = ListObject::create(cx); + if (!disposablesList) { + return false; + } + env->setReservedSlot(slot, ObjectValue(*disposablesList)); + } else { + disposablesList = &slotData.toObject().as(); + } + return disposablesList->append(cx, val); +} + +bool ModuleEnvironmentObject::addDisposableObject(JSContext* cx, + JS::Handle val) { + Rooted env(cx, this); + return addDisposableObjectHelper(env, DISPOSABLE_OBJECTS_SLOT, cx, val); +} + +Value ModuleEnvironmentObject::getDisposables() { + return getReservedSlot(DISPOSABLE_OBJECTS_SLOT); +} + +void ModuleEnvironmentObject::clearDisposables() { + setReservedSlot(DISPOSABLE_OBJECTS_SLOT, UndefinedValue()); +} +#endif + /* static */ ModuleEnvironmentObject* ModuleEnvironmentObject::createSynthetic( JSContext* cx, Handle module) { @@ -957,18 +998,8 @@ bool LexicalEnvironmentObject::isExtensible() const { #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT bool LexicalEnvironmentObject::addDisposableObject(JSContext* cx, JS::Handle val) { - Value slotData = getReservedSlot(DISPOSABLE_OBJECTS_SLOT); - ListObject* disposablesList = nullptr; - if (slotData.isUndefined()) { - disposablesList = ListObject::create(cx); - if (!disposablesList) { - return false; - } - setReservedSlot(DISPOSABLE_OBJECTS_SLOT, ObjectValue(*disposablesList)); - } else { - disposablesList = &slotData.toObject().as(); - } - return disposablesList->append(cx, val); + Rooted env(cx, this); + return addDisposableObjectHelper(env, DISPOSABLE_OBJECTS_SLOT, cx, val); } Value LexicalEnvironmentObject::getDisposables() { diff --git a/js/src/vm/EnvironmentObject.h b/js/src/vm/EnvironmentObject.h index 929f34384d47..13a8ade0b0c2 100644 --- a/js/src/vm/EnvironmentObject.h +++ b/js/src/vm/EnvironmentObject.h @@ -618,6 +618,10 @@ class VarEnvironmentObject : public EnvironmentObject { class ModuleEnvironmentObject : public EnvironmentObject { static constexpr uint32_t MODULE_SLOT = 1; +#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT + static constexpr uint32_t DISPOSABLE_OBJECTS_SLOT = 2; +#endif + static const ObjectOps objectOps_; static const JSClassOps classOps_; @@ -626,7 +630,12 @@ class ModuleEnvironmentObject : public EnvironmentObject { static const JSClass class_; +#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT + static constexpr uint32_t RESERVED_SLOTS = 3; +#else static constexpr uint32_t RESERVED_SLOTS = 2; +#endif + static constexpr ObjectFlags OBJECT_FLAGS = {ObjectFlag::NotExtensible, ObjectFlag::QualifiedVarObj}; @@ -655,6 +664,18 @@ class ModuleEnvironmentObject : public EnvironmentObject { uint32_t firstSyntheticValueSlot() { return RESERVED_SLOTS; } +#ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT + bool addDisposableObject(JSContext* cx, JS::Handle val); + + // Used to get the Disposable objects within the + // lexical scope, it returns a ListObject* if there + // is a non empty list of Disposables, else + // UndefinedValue. + Value getDisposables(); + + void clearDisposables(); +#endif + private: static bool lookupProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleObject objp, PropertyResult* propp); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index ee58d3b4bdfd..778f922e4992 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1639,11 +1639,15 @@ void js::ReportInNotObjectError(JSContext* cx, HandleValue lref, #ifdef ENABLE_EXPLICIT_RESOURCE_MANAGEMENT bool js::DisposeDisposablesOnScopeLeave(JSContext* cx, JS::Handle env) { - if (!env->is()) { + if (!env->is() && + !env->is()) { return true; } - Value maybeDisposables = env->as().getDisposables(); + Value maybeDisposables = + env->is() + ? env->as().getDisposables() + : env->as().getDisposables(); MOZ_ASSERT(maybeDisposables.isObject() || maybeDisposables.isUndefined()); @@ -1698,7 +1702,11 @@ bool js::DisposeDisposablesOnScopeLeave(JSContext* cx, // Step 3. Set disposeCapability.[[DisposableResourceStack]] to // a new empty List. - env->as().clearDisposables(); + if (env->is()) { + env->as().clearDisposables(); + } else { + env->as().clearDisposables(); + } // 4. Return ? completion. if (hadError) { @@ -2069,8 +2077,14 @@ bool MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER js::Interpret(JSContext* cx, ? UndefinedValue() : REGS.sp[-1]); - if (!env->as().addDisposableObject(cx, val)) { - goto error; + if (env->is()) { + if (!env->as().addDisposableObject(cx, val)) { + goto error; + } + } else if (env->is()) { + if (!env->as().addDisposableObject(cx, val)) { + goto error; + } } } END_CASE(AddDisposable)