Bug 1808572 - Part 1: Replace use of array object with a native vector for export names r=arai

Rather than create an array object we can use a vector for these names.

One wrinkle is that since we never mutate the name list after it's attached to
the namespace object we don't need barriers on it. This is more convenient
because it allows us to use the same vector type on the stack in a rooted and
in the object itself. I've added comments that hopefully explain things.

Differential Revision: https://phabricator.services.mozilla.com/D165985
This commit is contained in:
Jon Coppeard 2023-01-05 10:24:51 +00:00
Родитель 0196053126
Коммит 35948d26f6
6 изменённых файлов: 101 добавлений и 68 удалений

Просмотреть файл

@ -2099,10 +2099,6 @@ bool js::intrinsic_ArrayNativeSort(JSContext* cx, unsigned argc, Value* vp) {
return true;
}
bool js::ArrayNativeSort(JSContext* cx, Handle<JSObject*> obj) {
return ArrayNativeSortImpl(cx, obj, UndefinedHandleValue, Match_None);
}
static bool ArrayNativeSortImpl(JSContext* cx, Handle<JSObject*> obj,
Handle<Value> fval,
ComparatorMatchResult comp) {

Просмотреть файл

@ -104,8 +104,6 @@ extern bool GetElements(JSContext* cx, HandleObject aobj, uint32_t length,
extern bool intrinsic_ArrayNativeSort(JSContext* cx, unsigned argc,
js::Value* vp);
extern bool ArrayNativeSort(JSContext* cx, Handle<JSObject*> obj);
extern bool array_includes(JSContext* cx, unsigned argc, js::Value* vp);
extern bool array_indexOf(JSContext* cx, unsigned argc, js::Value* vp);
extern bool array_lastIndexOf(JSContext* cx, unsigned argc, js::Value* vp);

Просмотреть файл

@ -289,21 +289,25 @@ bool ModuleNamespaceObject::isInstance(HandleValue value) {
/* static */
ModuleNamespaceObject* ModuleNamespaceObject::create(
JSContext* cx, Handle<ModuleObject*> module, Handle<ArrayObject*> exports,
UniquePtr<IndirectBindingMap> bindings) {
JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<UniquePtr<ExportNameVector>> exports,
MutableHandle<UniquePtr<IndirectBindingMap>> bindings) {
RootedValue priv(cx, ObjectValue(*module));
ProxyOptions options;
options.setLazyProto(true);
Rooted<UniquePtr<IndirectBindingMap>> rootedBindings(cx, std::move(bindings));
RootedObject object(
cx, NewProxyObject(cx, &proxyHandler, priv, nullptr, options));
if (!object) {
return nullptr;
}
SetProxyReservedSlot(object, ExportsSlot, ObjectValue(*exports));
SetProxyReservedSlot(object, ExportsSlot,
PrivateValue(exports.get().release()));
AddCellMemory(object, sizeof(ExportNameVector), MemoryUse::ModuleExports);
SetProxyReservedSlot(object, BindingsSlot,
PrivateValue(rootedBindings.release()));
PrivateValue(bindings.get().release()));
AddCellMemory(object, sizeof(IndirectBindingMap),
MemoryUse::ModuleBindingMap);
@ -314,8 +318,17 @@ ModuleObject& ModuleNamespaceObject::module() {
return GetProxyPrivate(this).toObject().as<ModuleObject>();
}
ArrayObject& ModuleNamespaceObject::exports() {
return GetProxyReservedSlot(this, ExportsSlot).toObject().as<ArrayObject>();
const ExportNameVector& ModuleNamespaceObject::exports() const {
Value value = GetProxyReservedSlot(this, ExportsSlot);
auto* exports = static_cast<ExportNameVector*>(value.toPrivate());
MOZ_ASSERT(exports);
return *exports;
}
ExportNameVector& ModuleNamespaceObject::mutableExports() {
// Get a non-const reference for tracing/destruction. Do not actually mutate
// this vector! This would be incorrect without adding barriers.
return const_cast<ExportNameVector&>(exports());
}
IndirectBindingMap& ModuleNamespaceObject::bindings() {
@ -325,6 +338,11 @@ IndirectBindingMap& ModuleNamespaceObject::bindings() {
return *bindings;
}
bool ModuleNamespaceObject::hasExports() const {
// Exports may not be present if we hit OOM in initialization.
return !GetProxyReservedSlot(this, ExportsSlot).isUndefined();
}
bool ModuleNamespaceObject::hasBindings() const {
// Import bindings may not be present if we hit OOM in initialization.
return !GetProxyReservedSlot(this, BindingsSlot).isUndefined();
@ -556,21 +574,14 @@ bool ModuleNamespaceObject::ProxyHandler::delete_(
bool ModuleNamespaceObject::ProxyHandler::ownPropertyKeys(
JSContext* cx, HandleObject proxy, MutableHandleIdVector props) const {
Rooted<ModuleNamespaceObject*> ns(cx, &proxy->as<ModuleNamespaceObject>());
Rooted<ArrayObject*> exports(cx, &ns->exports());
uint32_t count = exports->length();
uint32_t count = ns->exports().length();
if (!props.reserve(props.length() + count + 1)) {
return false;
}
Rooted<ValueVector> names(cx, ValueVector(cx));
if (!names.resize(count) || !GetElements(cx, exports, count, names.begin())) {
return false;
for (JSAtom* atom : ns->exports()) {
props.infallibleAppend(AtomToId(atom));
}
for (uint32_t i = 0; i < count; i++) {
props.infallibleAppend(AtomToId(&names[i].toString()->asAtom()));
}
props.infallibleAppend(
PropertyKey::Symbol(cx->wellKnownSymbols().toStringTag));
@ -581,6 +592,13 @@ void ModuleNamespaceObject::ProxyHandler::trace(JSTracer* trc,
JSObject* proxy) const {
auto& self = proxy->as<ModuleNamespaceObject>();
if (self.hasExports()) {
for (JSAtom*& name : self.mutableExports()) {
TraceManuallyBarrieredEdge(trc, &name,
"ModuleNamespaceObject export name");
}
}
if (self.hasBindings()) {
self.bindings().trace(trc);
}
@ -590,6 +608,10 @@ void ModuleNamespaceObject::ProxyHandler::finalize(JS::GCContext* gcx,
JSObject* proxy) const {
auto& self = proxy->as<ModuleNamespaceObject>();
if (self.hasExports()) {
gcx->delete_(proxy, &self.mutableExports(), MemoryUse::ModuleExports);
}
if (self.hasBindings()) {
gcx->delete_(proxy, &self.bindings(), MemoryUse::ModuleBindingMap);
}
@ -1239,19 +1261,18 @@ void ModuleObject::onTopLevelEvaluationFinished(ModuleObject* module) {
}
/* static */
ModuleNamespaceObject* ModuleObject::createNamespace(JSContext* cx,
Handle<ModuleObject*> self,
HandleObject exports) {
ModuleNamespaceObject* ModuleObject::createNamespace(
JSContext* cx, Handle<ModuleObject*> self,
MutableHandle<UniquePtr<ExportNameVector>> exports) {
MOZ_ASSERT(!self->namespace_());
MOZ_ASSERT(exports->is<ArrayObject>());
auto bindings = cx->make_unique<IndirectBindingMap>();
Rooted<UniquePtr<IndirectBindingMap>> bindings(cx);
bindings = cx->make_unique<IndirectBindingMap>();
if (!bindings) {
return nullptr;
}
auto* ns = ModuleNamespaceObject::create(cx, self, exports.as<ArrayObject>(),
std::move(bindings));
auto* ns = ModuleNamespaceObject::create(cx, self, exports, &bindings);
if (!ns) {
return nullptr;
}

Просмотреть файл

@ -185,18 +185,25 @@ class IndirectBindingMap {
mozilla::Maybe<Map> map_;
};
// Vector of atoms representing the names exported from a module namespace.
//
// Barriers are not required because this is either used as a root, or a
// non-mutable field of ModuleNamespaceObject. Don't change this without adding
// barriers.
using ExportNameVector = GCVector<JSAtom*, 0, SystemAllocPolicy>;
class ModuleNamespaceObject : public ProxyObject {
public:
enum ModuleNamespaceSlot { ExportsSlot = 0, BindingsSlot };
static bool isInstance(HandleValue value);
static ModuleNamespaceObject* create(JSContext* cx,
Handle<ModuleObject*> module,
Handle<ArrayObject*> exports,
UniquePtr<IndirectBindingMap> bindings);
static ModuleNamespaceObject* create(
JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<UniquePtr<ExportNameVector>> exports,
MutableHandle<UniquePtr<IndirectBindingMap>> bindings);
ModuleObject& module();
ArrayObject& exports();
const ExportNameVector& exports() const;
IndirectBindingMap& bindings();
bool addBinding(JSContext* cx, Handle<JSAtom*> exportedName,
@ -245,6 +252,9 @@ class ModuleNamespaceObject : public ProxyObject {
};
bool hasBindings() const;
bool hasExports() const;
ExportNameVector& mutableExports();
public:
static const ProxyHandler proxyHandler;
@ -387,9 +397,9 @@ class ModuleObject : public NativeObject {
static bool execute(JSContext* cx, Handle<ModuleObject*> self);
static ModuleNamespaceObject* createNamespace(JSContext* cx,
Handle<ModuleObject*> self,
HandleObject exports);
static ModuleNamespaceObject* createNamespace(
JSContext* cx, Handle<ModuleObject*> self,
MutableHandle<UniquePtr<ExportNameVector>> exports);
static bool createEnvironment(JSContext* cx, Handle<ModuleObject*> self);

Просмотреть файл

@ -104,6 +104,7 @@ enum class GCAbortReason {
_(PropMapTable) \
_(ModuleBindingMap) \
_(ModuleCyclicFields) \
_(ModuleExports) \
_(BaselineScript) \
_(IonScript) \
_(ArgumentsData) \

Просмотреть файл

@ -38,8 +38,6 @@ using mozilla::Utf8Unit;
////////////////////////////////////////////////////////////////////////////////
// Public API
using NameList = GCVector<JSAtom*, 0, SystemAllocPolicy>;
JS_PUBLIC_API void JS::SetSupportedImportAssertions(
JSRuntime* rt, const ImportAssertionVector& assertions) {
AssertHeapIsIdle();
@ -326,7 +324,8 @@ static bool ModuleResolveExport(JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<ResolveSet> resolveSet,
MutableHandle<Value> result);
static ModuleNamespaceObject* ModuleNamespaceCreate(
JSContext* cx, Handle<ModuleObject*> module, Handle<ArrayObject*> exports);
JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<UniquePtr<ExportNameVector>> exports);
static bool InnerModuleLinking(JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<ModuleVector> stack, size_t index,
size_t* indexOut);
@ -357,13 +356,7 @@ static const char* ModuleStatusName(ModuleStatus status) {
}
}
static ArrayObject* NewList(JSContext* cx) {
// Note that this creates an ArrayObject, not a ListObject (see vm/List.h).
// Self hosted code currently depends on the length property being present.
return NewArrayWithNullProto(cx);
}
static bool ContainsElement(Handle<NameList> list, JSAtom* atom) {
static bool ContainsElement(Handle<ExportNameVector> list, JSAtom* atom) {
for (JSAtom* a : list) {
if (a == atom) {
return true;
@ -398,9 +391,10 @@ static size_t CountElements(Handle<ModuleVector> stack, ModuleObject* module) {
// https://tc39.es/ecma262/#sec-getexportednames
// ES2023 16.2.1.6.2 GetExportedNames
static bool ModuleGetExportedNames(JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<ModuleSet> exportStarSet,
MutableHandle<NameList> exportedNames) {
static bool ModuleGetExportedNames(
JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<ModuleSet> exportStarSet,
MutableHandle<ExportNameVector> exportedNames) {
// Step 4. Let exportedNames be a new empty List.
MOZ_ASSERT(exportedNames.empty());
@ -441,7 +435,6 @@ static bool ModuleGetExportedNames(JSContext* cx, Handle<ModuleObject*> module,
// Step 7. For each ExportEntry Record e of module.[[StarExportEntries]], do:
Rooted<ModuleRequestObject*> moduleRequest(cx);
Rooted<ModuleObject*> requestedModule(cx);
Rooted<ArrayObject*> starNames(cx);
Rooted<JSAtom*> name(cx);
for (const ExportEntry& e : module->starExportEntries()) {
// Step 7.a. Let requestedModule be ? HostResolveImportedModule(module,
@ -455,7 +448,7 @@ static bool ModuleGetExportedNames(JSContext* cx, Handle<ModuleObject*> module,
// Step 7.b. Let starNames be ?
// requestedModule.GetExportedNames(exportStarSet).
Rooted<NameList> starNames(cx);
Rooted<ExportNameVector> starNames(cx);
if (!ModuleGetExportedNames(cx, requestedModule, exportStarSet,
&starNames)) {
return false;
@ -716,13 +709,14 @@ ModuleNamespaceObject* js::GetOrCreateModuleNamespace(
if (!ns) {
// Step 3.a. Let exportedNames be ? module.GetExportedNames().
Rooted<ModuleSet> exportStarSet(cx);
Rooted<NameList> exportedNames(cx);
Rooted<ExportNameVector> exportedNames(cx);
if (!ModuleGetExportedNames(cx, module, &exportStarSet, &exportedNames)) {
return nullptr;
}
// Step 3.b. Let unambiguousNames be a new empty List.
Rooted<ArrayObject*> unambiguousNames(cx, NewList(cx));
Rooted<UniquePtr<ExportNameVector>> unambiguousNames(
cx, cx->make_unique<ExportNameVector>());
if (!unambiguousNames) {
return nullptr;
}
@ -740,15 +734,15 @@ ModuleNamespaceObject* js::GetOrCreateModuleNamespace(
// Step 3.c.ii. If resolution is a ResolvedBinding Record, append name to
// unambiguousNames.
if (resolution.isObject() &&
!NewbornArrayPush(cx, unambiguousNames, StringValue(name))) {
if (resolution.isObject() && !unambiguousNames->append(name)) {
ReportOutOfMemory(cx);
return nullptr;
}
}
// Step 3.d. Set namespace to ModuleNamespaceCreate(module,
// unambiguousNames).
ns = ModuleNamespaceCreate(cx, module, unambiguousNames);
ns = ModuleNamespaceCreate(cx, module, &unambiguousNames);
}
// Step 4. Return namespace.
@ -773,13 +767,33 @@ static void InitNamespaceBinding(JSContext* cx,
env->setSlot(prop->slot(), ObjectValue(*ns));
}
struct AtomComparator {
bool operator()(JSAtom* a, JSAtom* b, bool* lessOrEqualp) {
int32_t result = CompareStrings(a, b);
*lessOrEqualp = (result <= 0);
return true;
}
};
// https://tc39.es/ecma262/#sec-modulenamespacecreate
// ES2023 10.4.6.12 ModuleNamespaceCreate
static ModuleNamespaceObject* ModuleNamespaceCreate(
JSContext* cx, Handle<ModuleObject*> module, Handle<ArrayObject*> exports) {
JSContext* cx, Handle<ModuleObject*> module,
MutableHandle<UniquePtr<ExportNameVector>> exports) {
// Step 1. Assert: module.[[Namespace]] is empty.
MOZ_ASSERT(!module->namespace_());
// Step 6. Let sortedExports be a List whose elements are the elements of
// exports ordered as if an Array of the same values had been sorted
// using %Array.prototype.sort% using undefined as comparefn.
ExportNameVector scratch;
if (!scratch.resize(exports->length())) {
ReportOutOfMemory(cx);
return nullptr;
}
MOZ_ALWAYS_TRUE(MergeSort(exports->begin(), exports->length(),
scratch.begin(), AtomComparator()));
// Steps 2 - 5.
Rooted<ModuleNamespaceObject*> ns(
cx, ModuleObject::createNamespace(cx, module, exports));
@ -787,13 +801,6 @@ static ModuleNamespaceObject* ModuleNamespaceCreate(
return nullptr;
}
// Step 6. Let sortedExports be a List whose elements are the elements of
// exports ordered as if an Array of the same values had been sorted
// using %Array.prototype.sort% using undefined as comparefn.
if (!ArrayNativeSort(cx, exports)) {
return nullptr;
}
// Pre-compute all binding mappings now instead of on each access.
// See:
// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver
@ -804,8 +811,8 @@ static ModuleNamespaceObject* ModuleNamespaceCreate(
Rooted<ModuleObject*> importedModule(cx);
Rooted<ModuleNamespaceObject*> importedNamespace(cx);
Rooted<JSAtom*> bindingName(cx);
for (uint32_t i = 0; i != exports->length(); i++) {
name = &exports->getDenseElement(i).toString()->asAtom();
for (JSAtom* atom : ns->exports()) {
name = atom;
if (!ModuleResolveExport(cx, module, name, &resolution)) {
return nullptr;