Bug 1614041 - Defer ModuleBuilder GC allocations r=arai

Introduce StencilModuleEntry type to replace {Import,Export}EntryObject
during parsing. Also introduce StencilModuleMetadata to hold resulting
import/export tables.

Differential Revision: https://phabricator.services.mozilla.com/D83206
This commit is contained in:
Ted Campbell 2020-07-14 12:06:21 +00:00
Родитель 58ab7096a2
Коммит 5211daac48
7 изменённых файлов: 294 добавлений и 123 удалений

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

@ -29,6 +29,7 @@
#include "vm/JSObject-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/NativeObject-inl.h"
using namespace js;
@ -1217,45 +1218,52 @@ ModuleBuilder::ModuleBuilder(JSContext* cx,
requestedModules_(cx, RequestedModuleVector(cx)),
importEntries_(cx, ImportEntryMap(cx)),
exportEntries_(cx, ExportEntryVector(cx)),
exportNames_(cx, AtomSet(cx)),
localExportEntries_(cx, ExportEntryVector(cx)),
indirectExportEntries_(cx, ExportEntryVector(cx)),
starExportEntries_(cx, ExportEntryVector(cx)) {}
exportNames_(cx, AtomSet(cx)) {}
bool ModuleBuilder::buildTables() {
for (const auto& e : exportEntries_) {
RootedExportEntryObject exp(cx_, e);
if (!exp->moduleRequest()) {
RootedImportEntryObject importEntry(cx_,
importEntryFor(exp->localName()));
bool ModuleBuilder::buildTables(frontend::StencilModuleMetadata& metadata) {
// https://tc39.es/ecma262/#sec-parsemodule
// 15.2.1.17.1 ParseModule, Steps 4-11.
// Step 4.
metadata.requestedModules = std::move(requestedModules_.get());
// Step 5.
if (!metadata.importEntries.reserve(importEntries_.count())) {
return false;
}
for (auto r = importEntries_.all(); !r.empty(); r.popFront()) {
frontend::StencilModuleEntry& entry = r.front().value();
metadata.importEntries.infallibleAppend(entry);
}
// Steps 6-11.
for (const frontend::StencilModuleEntry& exp : exportEntries_) {
if (!exp.specifier) {
frontend::StencilModuleEntry* importEntry = importEntryFor(exp.localName);
if (!importEntry) {
if (!localExportEntries_.append(exp)) {
if (!metadata.localExportEntries.append(exp)) {
return false;
}
} else {
if (importEntry->importName() == cx_->names().star) {
if (!localExportEntries_.append(exp)) {
if (importEntry->importName == cx_->names().star) {
if (!metadata.localExportEntries.append(exp)) {
return false;
}
} else {
RootedAtom exportName(cx_, exp->exportName());
RootedAtom moduleRequest(cx_, importEntry->moduleRequest());
RootedAtom importName(cx_, importEntry->importName());
RootedExportEntryObject exportEntry(cx_);
exportEntry = ExportEntryObject::create(
cx_, exportName, moduleRequest, importName, nullptr,
exp->lineNumber(), exp->columnNumber());
if (!exportEntry || !indirectExportEntries_.append(exportEntry)) {
auto entry = frontend::StencilModuleEntry::exportFromEntry(
importEntry->specifier, importEntry->importName, exp.exportName,
exp.lineno, exp.column);
if (!metadata.indirectExportEntries.append(entry)) {
return false;
}
}
}
} else if (exp->importName() == cx_->names().star) {
if (!starExportEntries_.append(exp)) {
} else if (exp.importName == cx_->names().star) {
if (!metadata.starExportEntries.append(exp)) {
return false;
}
} else {
if (!indirectExportEntries_.append(exp)) {
if (!metadata.indirectExportEntries.append(exp)) {
return false;
}
}
@ -1264,39 +1272,106 @@ bool ModuleBuilder::buildTables() {
return true;
}
bool ModuleBuilder::initModule(JS::Handle<ModuleObject*> module) {
RootedArrayObject requestedModules(cx_,
js::CreateArray(cx_, requestedModules_));
if (!requestedModules) {
enum class ModuleArrayType {
ImportEntryObject,
ExportEntryObject,
RequestedModuleObject,
};
static ArrayObject* ModuleBuilderInitArray(
JSContext* cx, ModuleArrayType arrayType,
const frontend::StencilModuleMetadata::EntryVector& vector) {
RootedArrayObject resultArray(
cx, NewDenseFullyAllocatedArray(cx, vector.length()));
if (!resultArray) {
return nullptr;
}
resultArray->ensureDenseInitializedLength(cx, 0, vector.length());
RootedAtom specifier(cx);
RootedAtom localName(cx);
RootedAtom importName(cx);
RootedAtom exportName(cx);
RootedObject req(cx);
for (uint32_t i = 0; i < vector.length(); ++i) {
const frontend::StencilModuleEntry& entry = vector[i];
specifier = entry.specifier;
localName = entry.localName;
importName = entry.importName;
exportName = entry.exportName;
switch (arrayType) {
case ModuleArrayType::ImportEntryObject:
MOZ_ASSERT(localName && importName);
req = ImportEntryObject::create(cx, specifier, importName, localName,
entry.lineno, entry.column);
break;
case ModuleArrayType::ExportEntryObject:
MOZ_ASSERT(localName || importName || exportName);
req = ExportEntryObject::create(cx, exportName, specifier, importName,
localName, entry.lineno, entry.column);
break;
case ModuleArrayType::RequestedModuleObject:
req = RequestedModuleObject::create(cx, specifier, entry.lineno,
entry.column);
// TODO: Make this consistent with other object types.
if (req && !FreezeObject(cx, req)) {
return nullptr;
}
break;
}
if (!req) {
return nullptr;
}
resultArray->initDenseElement(i, ObjectValue(*req));
}
return resultArray;
}
// Use StencilModuleMetadata data to fill in ModuleObject
bool frontend::StencilModuleMetadata::initModule(
JSContext* cx, JS::Handle<ModuleObject*> module) {
RootedArrayObject requestedModulesObject(
cx, ModuleBuilderInitArray(cx, ModuleArrayType::RequestedModuleObject,
requestedModules));
if (!requestedModulesObject) {
return false;
}
RootedArrayObject importEntries(cx_, createArrayFromHashMap(importEntries_));
if (!importEntries) {
RootedArrayObject importEntriesObject(
cx, ModuleBuilderInitArray(cx, ModuleArrayType::ImportEntryObject,
importEntries));
if (!importEntriesObject) {
return false;
}
RootedArrayObject localExportEntries(
cx_, js::CreateArray(cx_, localExportEntries_));
if (!localExportEntries) {
RootedArrayObject localExportEntriesObject(
cx, ModuleBuilderInitArray(cx, ModuleArrayType::ExportEntryObject,
localExportEntries));
if (!localExportEntriesObject) {
return false;
}
RootedArrayObject indirectExportEntries(
cx_, js::CreateArray(cx_, indirectExportEntries_));
if (!indirectExportEntries) {
RootedArrayObject indirectExportEntriesObject(
cx, ModuleBuilderInitArray(cx, ModuleArrayType::ExportEntryObject,
indirectExportEntries));
if (!indirectExportEntriesObject) {
return false;
}
RootedArrayObject starExportEntries(cx_,
js::CreateArray(cx_, starExportEntries_));
if (!starExportEntries) {
RootedArrayObject starExportEntriesObject(
cx, ModuleBuilderInitArray(cx, ModuleArrayType::ExportEntryObject,
starExportEntries));
if (!starExportEntriesObject) {
return false;
}
module->initImportExportData(requestedModules, importEntries,
localExportEntries, indirectExportEntries,
starExportEntries);
module->initImportExportData(
requestedModulesObject, importEntriesObject, localExportEntriesObject,
indirectExportEntriesObject, starExportEntriesObject);
return true;
}
@ -1335,10 +1410,9 @@ bool ModuleBuilder::processImport(frontend::BinaryNode* importNode) {
eitherParser_.computeLineAndColumn(importNameNode->pn_pos.begin, &line,
&column);
RootedImportEntryObject importEntry(cx_);
importEntry = ImportEntryObject::create(cx_, module, importName, localName,
line, column);
if (!importEntry || !appendImportEntryObject(importEntry)) {
auto entry = frontend::StencilModuleEntry::importEntry(
module, localName, importName, line, column);
if (!importEntries_.put(localName, entry)) {
return false;
}
}
@ -1346,12 +1420,6 @@ bool ModuleBuilder::processImport(frontend::BinaryNode* importNode) {
return true;
}
bool ModuleBuilder::appendImportEntryObject(
HandleImportEntryObject importEntry) {
MOZ_ASSERT(importEntry->localName());
return importEntries_.put(importEntry->localName(), importEntry);
}
bool ModuleBuilder::processExport(frontend::ParseNode* exportNode) {
using namespace js::frontend;
@ -1568,14 +1636,15 @@ bool ModuleBuilder::processExportFrom(frontend::BinaryNode* exportNode) {
return true;
}
ImportEntryObject* ModuleBuilder::importEntryFor(JSAtom* localName) const {
frontend::StencilModuleEntry* ModuleBuilder::importEntryFor(
JSAtom* localName) const {
MOZ_ASSERT(localName);
auto ptr = importEntries_.lookup(localName);
if (!ptr) {
return nullptr;
}
return ptr->value();
return &ptr->value();
}
bool ModuleBuilder::hasExportedName(JSAtom* name) const {
@ -1592,10 +1661,19 @@ bool ModuleBuilder::appendExportEntry(HandleAtom exportName,
eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
}
Rooted<ExportEntryObject*> exportEntry(cx_);
exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr,
localName, line, column);
return exportEntry && appendExportEntryObject(exportEntry);
auto entry = frontend::StencilModuleEntry::exportAsEntry(
localName, exportName, line, column);
if (!exportEntries_.append(entry)) {
return false;
}
if (exportName) {
if (!exportNames_.put(exportName)) {
return false;
}
}
return true;
}
bool ModuleBuilder::appendExportFromEntry(HandleAtom exportName,
@ -1606,19 +1684,12 @@ bool ModuleBuilder::appendExportFromEntry(HandleAtom exportName,
uint32_t column;
eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
Rooted<ExportEntryObject*> exportEntry(cx_);
exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest,
importName, nullptr, line, column);
return exportEntry && appendExportEntryObject(exportEntry);
}
bool ModuleBuilder::appendExportEntryObject(
HandleExportEntryObject exportEntry) {
if (!exportEntries_.append(exportEntry)) {
auto entry = frontend::StencilModuleEntry::exportFromEntry(
moduleRequest, importName, exportName, line, column);
if (!exportEntries_.append(entry)) {
return false;
}
JSAtom* exportName = exportEntry->exportName();
return !exportName || exportNames_.put(exportName);
}
@ -1632,15 +1703,13 @@ bool ModuleBuilder::maybeAppendRequestedModule(HandleAtom specifier,
uint32_t column;
eitherParser_.computeLineAndColumn(node->pn_pos.begin, &line, &column);
JSContext* cx = cx_;
RootedRequestedModuleObject req(
cx, RequestedModuleObject::create(cx, specifier, line, column));
if (!req) {
auto entry =
frontend::StencilModuleEntry::moduleRequest(specifier, line, column);
if (!requestedModules_.append(entry)) {
return false;
}
return FreezeObject(cx, req) && requestedModules_.append(req) &&
requestedModuleSpecifiers_.put(specifier);
return requestedModuleSpecifiers_.put(specifier);
}
template <typename T>
@ -1660,25 +1729,6 @@ ArrayObject* js::CreateArray(JSContext* cx,
return array;
}
template <typename K, typename V>
ArrayObject* ModuleBuilder::createArrayFromHashMap(
const JS::Rooted<GCHashMap<K, V>>& map) {
uint32_t length = map.count();
RootedArrayObject array(cx_, NewDenseFullyAllocatedArray(cx_, length));
if (!array) {
return nullptr;
}
array->setDenseInitializedLength(length);
uint32_t i = 0;
for (auto r = map.all(); !r.empty(); r.popFront()) {
array->initDenseElement(i++, ObjectValue(*r.front().value()));
}
return array;
}
JSObject* js::GetOrCreateModuleMetaObject(JSContext* cx,
HandleObject moduleArg) {
HandleModuleObject module = moduleArg.as<ModuleObject>();

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

@ -551,7 +551,8 @@ ModuleObject* frontend::ModuleCompiler<Unit>::compile(
MOZ_ASSERT(compilationInfo.script);
if (!builder.initModule(module)) {
StencilModuleMetadata& moduleMetadata = compilationInfo.moduleMetadata.get();
if (!moduleMetadata.initModule(cx, module)) {
return nullptr;
}

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

@ -199,6 +199,9 @@ struct MOZ_RAII CompilationInfo : public JS::CustomAutoRooter {
// an index (and CompilationInfo reference).
JS::RootedVector<ScopeCreationData> scopeCreationData;
// Module metadata if this is a module compile.
JS::Rooted<StencilModuleMetadata> moduleMetadata;
// AsmJS modules generated by parsing.
HashMap<FunctionIndex, RefPtr<const JS::WasmModule>> asmJS;
@ -238,6 +241,7 @@ struct MOZ_RAII CompilationInfo : public JS::CustomAutoRooter {
enclosingScope(cx),
topLevel(cx),
scopeCreationData(cx),
moduleMetadata(cx),
asmJS(cx),
sourceObject(cx) {}

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

@ -1622,13 +1622,17 @@ ModuleNode* Parser<FullParseHandler, Unit>::moduleBody(
return null();
}
if (!modulesc->builder.buildTables()) {
// Generate the Import/Export tables and store in CompilationInfo.
if (!modulesc->builder.buildTables(
this->compilationInfo_.moduleMetadata.get())) {
return null();
}
// Check exported local bindings exist and mark them as closed over.
for (auto entry : modulesc->builder.localExportEntries()) {
JSAtom* name = entry->localName();
StencilModuleMetadata& moduleMetadata =
this->compilationInfo_.moduleMetadata.get();
for (auto entry : moduleMetadata.localExportEntries) {
JSAtom* name = entry.localName;
MOZ_ASSERT(name);
DeclaredNamePtr p = modulepc.varScope().lookupDeclaredName(name);

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

@ -229,6 +229,29 @@ uint32_t ScopeCreationData::nextFrameSlot() const {
MOZ_CRASH("Not an enclosing intra-frame scope");
}
void StencilModuleEntry::trace(JSTracer* trc) {
if (specifier) {
TraceManuallyBarrieredEdge(trc, &specifier, "module specifier");
}
if (localName) {
TraceManuallyBarrieredEdge(trc, &localName, "module local name");
}
if (importName) {
TraceManuallyBarrieredEdge(trc, &importName, "module import name");
}
if (exportName) {
TraceManuallyBarrieredEdge(trc, &exportName, "module export name");
}
}
void StencilModuleMetadata::trace(JSTracer* trc) {
requestedModules.trace(trc);
importEntries.trace(trc);
localExportEntries.trace(trc);
indirectExportEntries.trace(trc);
starExportEntries.trace(trc);
}
void ScriptStencil::trace(JSTracer* trc) {
for (ScriptThingVariant& thing : gcThings) {
if (thing.is<ScriptAtom>()) {

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

@ -330,6 +330,108 @@ class ScopeCreationData {
class EmptyGlobalScopeType {};
// Common type for ImportEntry / ExportEntry / ModuleRequest within frontend. We
// use a shared stencil class type to simplify serialization.
//
// https://tc39.es/ecma262/#importentry-record
// https://tc39.es/ecma262/#exportentry-record
//
// Note: We subdivide the spec's ExportEntry into ExportAs / ExportFrom forms
// for readability.
class StencilModuleEntry {
public:
// | ModuleRequest | ImportEntry | ExportAs | ExportFrom |
// |-----------------------------------------------------|
// specifier | required | required | nullptr | required |
// localName | null | required | required | nullptr |
// importName | null | required | nullptr | required |
// exportName | null | null | required | optional |
JSAtom* specifier = nullptr;
JSAtom* localName = nullptr;
JSAtom* importName = nullptr;
JSAtom* exportName = nullptr;
// Location used for error messages. If this is for a module request entry
// then it is the module specifier string, otherwise the import/export spec
// that failed. Exports may not fill these fields if an error cannot be
// generated such as `export let x;`.
uint32_t lineno = 0;
uint32_t column = 0;
private:
StencilModuleEntry(uint32_t lineno, uint32_t column)
: lineno(lineno), column(column) {}
public:
static StencilModuleEntry moduleRequest(JSAtom* specifier, uint32_t lineno,
uint32_t column) {
MOZ_ASSERT(specifier);
StencilModuleEntry entry(lineno, column);
entry.specifier = specifier;
return entry;
}
static StencilModuleEntry importEntry(JSAtom* specifier, JSAtom* localName,
JSAtom* importName, uint32_t lineno,
uint32_t column) {
MOZ_ASSERT(specifier && localName && importName);
StencilModuleEntry entry(lineno, column);
entry.specifier = specifier;
entry.localName = localName;
entry.importName = importName;
return entry;
}
static StencilModuleEntry exportAsEntry(JSAtom* localName, JSAtom* exportName,
uint32_t lineno, uint32_t column) {
MOZ_ASSERT(localName && exportName);
StencilModuleEntry entry(lineno, column);
entry.localName = localName;
entry.exportName = exportName;
return entry;
}
static StencilModuleEntry exportFromEntry(JSAtom* specifier,
JSAtom* importName,
JSAtom* exportName, uint32_t lineno,
uint32_t column) {
// NOTE: The `export * from "mod";` syntax generates nullptr exportName.
MOZ_ASSERT(specifier && importName);
StencilModuleEntry entry(lineno, column);
entry.specifier = specifier;
entry.importName = importName;
entry.exportName = exportName;
return entry;
}
// This traces the JSAtoms. This will be removed once atoms are deferred from
// parsing.
void trace(JSTracer* trc);
};
// Metadata generated by parsing module scripts, including import/export tables.
class StencilModuleMetadata {
public:
using EntryVector = JS::GCVector<StencilModuleEntry>;
EntryVector requestedModules;
EntryVector importEntries;
EntryVector localExportEntries;
EntryVector indirectExportEntries;
EntryVector starExportEntries;
explicit StencilModuleMetadata(JSContext* cx)
: requestedModules(cx),
importEntries(cx),
localExportEntries(cx),
indirectExportEntries(cx),
starExportEntries(cx) {}
bool initModule(JSContext* cx, JS::Handle<ModuleObject*> module);
void trace(JSTracer* trc);
};
// The lazy closed-over-binding info is represented by these types that will
// convert to a GCCellPtr(nullptr), GCCellPtr(JSAtom*).
class NullScriptThing {};

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

@ -11,11 +11,13 @@
#include "jstypes.h" // JS_PUBLIC_API
#include "builtin/ModuleObject.h" // js::{{Im,Ex}portEntry,Requested{Module,}}Object
#include "frontend/EitherParser.h" // js::frontend::EitherParser
#include "js/GCHashTable.h" // JS::GCHash{Map,Set}
#include "js/GCVector.h" // JS::GCVector
#include "js/RootingAPI.h" // JS::{Handle,Rooted}
#include "vm/AtomsTable.h" // js::AtomSet
#include "frontend/CompilationInfo.h" // js::frontend::CompilationInfo
#include "frontend/EitherParser.h" // js::frontend::EitherParser
#include "frontend/Stencil.h" // js::frontend::StencilModuleEntry
#include "js/GCHashTable.h" // JS::GCHash{Map,Set}
#include "js/GCVector.h" // JS::GCVector
#include "js/RootingAPI.h" // JS::{Handle,Rooted}
#include "vm/AtomsTable.h" // js::AtomSet
struct JS_PUBLIC_API JSContext;
class JS_PUBLIC_API JSAtom;
@ -47,18 +49,13 @@ class MOZ_STACK_CLASS ModuleBuilder {
bool hasExportedName(JSAtom* name) const;
using ExportEntryVector = GCVector<ExportEntryObject*>;
const ExportEntryVector& localExportEntries() const {
return localExportEntries_;
}
bool buildTables();
bool initModule(JS::Handle<ModuleObject*> module);
bool buildTables(frontend::StencilModuleMetadata& metadata);
private:
using RequestedModuleVector = JS::GCVector<RequestedModuleObject*>;
using RequestedModuleVector = JS::GCVector<frontend::StencilModuleEntry>;
using AtomSet = JS::GCHashSet<JSAtom*>;
using ImportEntryMap = JS::GCHashMap<JSAtom*, ImportEntryObject*>;
using ExportEntryVector = GCVector<frontend::StencilModuleEntry>;
using ImportEntryMap = JS::GCHashMap<JSAtom*, frontend::StencilModuleEntry>;
using RootedExportEntryVector = JS::Rooted<ExportEntryVector>;
using RootedRequestedModuleVector = JS::Rooted<RequestedModuleVector>;
using RootedAtomSet = JS::Rooted<AtomSet>;
@ -66,23 +63,19 @@ class MOZ_STACK_CLASS ModuleBuilder {
JSContext* cx_;
frontend::EitherParser eitherParser_;
RootedAtomSet requestedModuleSpecifiers_;
RootedRequestedModuleVector requestedModules_;
RootedImportEntryMap importEntries_;
RootedExportEntryVector exportEntries_;
RootedAtomSet exportNames_;
RootedExportEntryVector localExportEntries_;
RootedExportEntryVector indirectExportEntries_;
RootedExportEntryVector starExportEntries_;
ImportEntryObject* importEntryFor(JSAtom* localName) const;
frontend::StencilModuleEntry* importEntryFor(JSAtom* localName) const;
bool processExportBinding(frontend::ParseNode* pn);
bool processExportArrayBinding(frontend::ListNode* array);
bool processExportObjectBinding(frontend::ListNode* obj);
bool appendImportEntryObject(JS::Handle<ImportEntryObject*> importEntry);
bool appendExportEntry(JS::Handle<JSAtom*> exportName,
JS::Handle<JSAtom*> localName,
frontend::ParseNode* node = nullptr);
@ -92,14 +85,8 @@ class MOZ_STACK_CLASS ModuleBuilder {
JS::Handle<JSAtom*> importName,
frontend::ParseNode* node);
bool appendExportEntryObject(JS::Handle<ExportEntryObject*> exportEntry);
bool maybeAppendRequestedModule(JS::Handle<JSAtom*> specifier,
frontend::ParseNode* node);
template <typename K, typename V>
ArrayObject* createArrayFromHashMap(
const JS::Rooted<JS::GCHashMap<K, V>>& map);
};
template <typename T>