Issue #2142 - Optimize .initializers scoping and emitter

* Refactor code for emitting the .initializers array into ClassEmitter
* Only emit .initializers scope when actually required
* Remove unfinished code to handle non-present class field initialisers
* Use predicate count_if and any_of of ListNode
* Remove unnecessary parameters for class field parsing

Based-on: m-c 1553744, 1555979, 1555037/1, 1535804/{1-5}
This commit is contained in:
Martok 2023-04-10 15:40:27 +02:00 • committed by roytam1
commit e0b5528c29
10 changed files with 215 additions and 117 deletions

View file

@ -2766,24 +2766,22 @@ ASTSerializer::classField(ClassField* classField, MutableHandleValue dst)
{
RootedValue key(cx), val(cx);
// Dig through the lambda and get to the actual expression
if (classField->initializer()) {
ParseNode* value = classField->initializer()
->body()
->head()->as<LexicalScopeNode>()
.scopeBody()->as<ListNode>()
.head()->as<UnaryNode>()
.kid()->as<BinaryNode>()
.right();
// RawUndefinedExpr is the node we use for "there is no initializer". If one
// writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr
// node, but rather a variable reference.
// Behavior for "there is no initializer" should be { ..., "init": null }
if (value->getKind() != PNK_RAW_UNDEFINED) {
if (!expression(value, &val))
return false;
} else {
val.setNull();
}
ParseNode* value = classField->initializer()
->body()
->head()->as<LexicalScopeNode>()
.scopeBody()->as<ListNode>()
.head()->as<UnaryNode>()
.kid()->as<BinaryNode>()
.right();
// RawUndefinedExpr is the node we use for "there is no initializer". If one
// writes, literally, `x = undefined;`, it will not be a RawUndefinedExpr
// node, but rather a variable reference.
// Behavior for "there is no initializer" should be { ..., "init": null }
if (value->getKind() != PNK_RAW_UNDEFINED) {
if (!expression(value, &val))
return false;
} else {
val.setNull();
}
return propertyName(&classField->name(), &key) &&
builder.classField(key, val, &classField->pn_pos, dst);

View file

@ -7942,18 +7942,7 @@ BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe, PropListTy
FieldInitializers
BytecodeEmitter::setupFieldInitializers(ListNode* classMembers)
{
size_t numFields = 0;
for (ParseNode* propdef : classMembers->contents()) {
if (propdef->is<ClassField>()) {
FunctionNode* initializer = propdef->as<ClassField>().initializer();
// Don't include fields without initializers.
if (initializer != nullptr) {
numFields++;
}
continue;
}
}
size_t numFields = classMembers->count_if([](ParseNode* propdef) { return propdef->is<ClassField>(); });
return FieldInitializers(numFields);
}
@ -7984,15 +7973,10 @@ BytecodeEmitter::setupFieldInitializers(ListNode* classMembers)
bool
BytecodeEmitter::emitCreateFieldKeys(ListNode* obj)
{
size_t numFieldKeys = 0;
for (ParseNode* propdef : obj->contents()) {
if (propdef->is<ClassField>()) {
ClassField* field = &propdef->as<ClassField>();
if (field->name().getKind() == PNK_COMPUTED_NAME) {
numFieldKeys++;
}
}
}
size_t numFieldKeys = obj->count_if([](ParseNode* propdef) {
return propdef->is<ClassField>() &&
propdef->as<ClassField>().name().getKind() == PNK_COMPUTED_NAME;
});
if (numFieldKeys == 0)
return true;
@ -8021,8 +8005,9 @@ BytecodeEmitter::emitCreateFieldKeys(ListNode* obj)
}
bool
BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj)
BytecodeEmitter::emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj)
{
// [stack] HOMEOBJ HERITAGE?
FieldInitializers fieldInitializers = setupFieldInitializers(obj);
MOZ_ASSERT(fieldInitializers.valid);
size_t numFields = fieldInitializers.numFieldInitializers;
@ -8030,50 +8015,28 @@ BytecodeEmitter::emitCreateFieldInitializers(ListNode* obj)
if (numFields == 0)
return true;
// .initializers is a variable that stores an array of lambdas containing
// code (the initializer) for each field. Upon an object's construction,
// these lambdas will be called, defining the values.
NameOpEmitter noe(this, cx->names().dotInitializers,
NameOpEmitter::Kind::Initialize);
if (!noe.prepareForRhs()) {
if (!ce.prepareForFieldInitializers(numFields)) {
// [stack] HOMEOBJ HERITAGE? ARRAY
return false;
}
if (!emitUint32Operand(JSOP_NEWARRAY, numFields)) {
// [stack] CTOR? OBJ ARRAY
return false;
}
size_t curFieldIndex = 0;
for (ParseNode* propdef : obj->contents()) {
if (propdef->is<ClassField>()) {
FunctionNode* initializer = propdef->as<ClassField>().initializer();
if (initializer == nullptr) {
continue;
}
if (!propdef->is<ClassField>())
continue;
if (!emitTree(initializer)) {
// [stack] CTOR? OBJ ARRAY LAMBDA
return false;
}
if (!emitUint32Operand(JSOP_INITELEM_ARRAY, curFieldIndex)) {
// [stack] CTOR? OBJ ARRAY
return false;
}
curFieldIndex++;
FunctionNode* initializer = propdef->as<ClassField>().initializer();
if (!emitTree(initializer)) {
// [stack] HOMEOBJ HERITAGE? ARRAY LAMBDA
return false;
}
if (!ce.emitStoreFieldInitializer()) {
// [stack] HOMEOBJ HERITAGE? ARRAY
return false;
}
}
if (!noe.emitAssignment()) {
// [stack] CTOR? OBJ ARRAY
return false;
}
if (!emit1(JSOP_POP)) {
// [stack] CTOR? OBJ
if (!ce.emitFieldInitializersEnd()) {
// [stack] HOMEOBJ HERITAGE?
return false;
}
@ -8698,20 +8661,37 @@ BytecodeEmitter::emitClass(ClassNode* classNode)
}
if (constructor) {
FunctionNode* ctor;
// .fieldKeys must be declared outside the scope .initializers is declared
// in, hence this extra scope.
// See |Parser::classMember(...)| for the reason why |.initializers| is
// created within its own scope.
Maybe<LexicalScopeEmitter> lse;
FunctionNode* ctor;
if (constructor->is<LexicalScopeNode>()) {
lse.emplace(this);
if (!lse->emitScope(ScopeKind::Lexical, constructor->as<LexicalScopeNode>().scopeBindings()))
return false;
LexicalScopeNode* constructorScope = &constructor->as<LexicalScopeNode>();
// Any class with field initializers will have a constructor
if (!emitCreateFieldInitializers(classMembers))
return false;
ctor = &constructor->as<LexicalScopeNode>().scopeBody()->as<ClassMethod>().method();
// The constructor scope should only contain the |.initializers| binding.
MOZ_ASSERT(!constructorScope->isEmptyScope());
MOZ_ASSERT(constructorScope->scopeBindings()->length == 1);
MOZ_ASSERT(constructorScope->scopeBindings()->trailingNames[0].name() ==
cx->names().dotInitializers);
// As an optimization omit the |.initializers| binding when no instance
// fields are present.
bool hasInstanceFields = classMembers->any_of([](ParseNode* propdef) {
return propdef->is<ClassField>();
});
if (hasInstanceFields) {
lse.emplace(this);
if (!lse->emitScope(ScopeKind::Lexical, constructorScope->scopeBindings()))
return false;
// Any class with field initializers will have a constructor
if (!emitCreateFieldInitializers(ce, classMembers))
return false;
}
ctor = &constructorScope->scopeBody()->as<ClassMethod>().method();
} else {
// The |.initializers| binding is never emitted when in self-hosting mode.
MOZ_ASSERT(emitterMode == BytecodeEmitter::SelfHosting);
ctor = &constructor->as<ClassMethod>().method();
}

View file

@ -122,6 +122,7 @@ typedef Vector<jsbytecode, 256> BytecodeVector;
typedef Vector<jssrcnote, 64> SrcNotesVector;
class CallOrNewEmitter;
class ClassEmitter;
class ElemOpEmitter;
class EmitterScope;
class NestableControl;
@ -531,7 +532,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter
FieldInitializers setupFieldInitializers(ListNode* classMembers);
MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj);
MOZ_MUST_USE bool emitCreateFieldInitializers(ListNode* obj);
MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce, ListNode* obj);
const FieldInitializers& findFieldInitializersForCall();
MOZ_MUST_USE bool emitInitializeInstanceFields();

View file

@ -487,7 +487,6 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS)
return true;
}
UnaryNodeType newInitialYieldExpression(uint32_t begin, Node gen) {
TokenPos pos(begin, begin + 1);
return new_<UnaryNode>(PNK_INITIALYIELD, JSOP_INITIALYIELD, pos, gen);

View file

@ -484,6 +484,71 @@ bool ObjectEmitter::emitObject(size_t propertyCount)
return true;
}
bool ClassEmitter::prepareForFieldInitializers(size_t numFields)
{
MOZ_ASSERT(classState_ == ClassState::Class);
// .initializers is a variable that stores an array of lambdas containing
// code (the initializer) for each field. Upon an object's construction,
// these lambdas will be called, defining the values.
initializersAssignment_.emplace(bce_, bce_->cx->names().dotInitializers,
NameOpEmitter::Kind::Initialize);
if (!initializersAssignment_->prepareForRhs()) {
return false;
}
if (!bce_->emitUint32Operand(JSOP_NEWARRAY, numFields)) {
// [stack] HOMEOBJ HERITAGE? ARRAY
return false;
}
MOZ_ASSERT(fieldIndex_ == 0);
#ifdef DEBUG
classState_ = ClassState::FieldInitializers;
numFields_ = numFields;
#endif
return true;
}
bool ClassEmitter::emitStoreFieldInitializer()
{
MOZ_ASSERT(classState_ == ClassState::FieldInitializers);
MOZ_ASSERT(fieldIndex_ < numFields_);
// [stack] HOMEOBJ HERITAGE? ARRAY METHOD
if (!bce_->emitUint32Operand(JSOP_INITELEM_ARRAY, fieldIndex_)) {
// [stack] HOMEOBJ HERITAGE? ARRAY
return false;
}
fieldIndex_++;
return true;
}
bool ClassEmitter::emitFieldInitializersEnd()
{
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
MOZ_ASSERT(classState_ == ClassState::FieldInitializers);
MOZ_ASSERT(fieldIndex_ == numFields_);
if (!initializersAssignment_->emitAssignment()) {
// [stack] HOMEOBJ HERITAGE? ARRAY
return false;
}
initializersAssignment_.reset();
if (!bce_->emit1(JSOP_POP)) {
// [stack] HOMEOBJ HERITAGE?
return false;
}
#ifdef DEBUG
classState_ = ClassState::FieldInitializersEnd;
#endif
return true;
}
bool ObjectEmitter::emitEnd()
{
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
@ -614,7 +679,8 @@ void ClassEmitter::setName(JS::Handle<JSAtom*> name)
bool ClassEmitter::emitInitConstructor(bool needsHomeObject)
{
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(classState_ == ClassState::Class);
MOZ_ASSERT(classState_ == ClassState::Class ||
classState_ == ClassState::FieldInitializersEnd);
// [stack] HOMEOBJ CTOR
@ -640,7 +706,8 @@ bool ClassEmitter::emitInitDefaultConstructor(const Maybe<uint32_t>& classStart,
const Maybe<uint32_t>& classEnd)
{
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(classState_ == ClassState::Class);
MOZ_ASSERT(classState_ == ClassState::Class ||
classState_ == ClassState::FieldInitializersEnd);
if (classStart && classEnd) {
// In the case of default class constructors, emit the start and end
@ -706,7 +773,8 @@ bool ClassEmitter::emitEnd(Kind kind)
{
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
propertyState_ == PropertyState::Init);
MOZ_ASSERT(classState_ == ClassState::InitConstructor);
MOZ_ASSERT(classState_ == ClassState::InitConstructor ||
classState_ == ClassState::FieldInitializersEnd);
// [stack] CTOR HOMEOBJ

View file

@ -18,6 +18,7 @@
#include "jsscript.h" // FunctionAsyncKind
#include "frontend/EmitterScope.h" // EmitterScope
#include "frontend/NameOpEmitter.h" // NameOpEmitter
#include "frontend/TDZCheckCache.h" // TDZCheckCache
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
#include "vm/String.h" // JSAtom
@ -527,6 +528,23 @@ class MOZ_RAII AutoSaveLocalStrictMode
//
// ce.emitEnd(ClassEmitter::Kind::Expression);
//
// `class X extends Y { field0 = expr0; ... }`
// ClassEmitter ce(this);
// ce.emitScope(scopeBindings);
// emit(Y);
// ce.emitDerivedClass(atom_of_X, nullptr, false);
//
// ce.prepareForFieldInitializers(fields.length());
// for (auto field : fields) {
// emit(field.expr_method());
// ce.emitStoreFieldInitializer();
// }
// ce.emitFieldInitializersEnd();
//
// emit(function_for_constructor);
// ce.emitInitConstructor(/* needsHomeObject = */ false);
// ce.emitEnd(ClassEmitter::Kind::Expression);
//
// `m() {}` in class
// // after emitInitConstructor/emitInitDefaultConstructor
// ce.prepareForPropValue(Some(offset_of_m));
@ -655,6 +673,21 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter
// |
// +-------------------------------+
// |
// | prepareForFieldInitializers +-------------------+
// +----------------------------->| FieldInitializers |-+
// | +-------------------+ |
// | |
// | +-------------------------------------------------+
// | |
// | | (expr emitStoreFieldInitializer)*
// | |
// | |
// | | emitFieldInitializersEnd +----------------------+
// | +-------------------------->| FieldInitializersEnd |-+
// | +----------------------+ |
// | |
// |<------------------------------------------------------+
// |
// |
// | emitInitConstructor +-----------------+
// +-+--------------------------->+->| InitConstructor |-+
@ -680,13 +713,23 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter
// After calling emitInitConstructor or emitInitDefaultConstructor.
InitConstructor,
// After calling prepareForFieldInitializers
// and 0 or more calls to emitFieldInitializersEnd.
FieldInitializers,
// After calling emitFieldInitializersEnd.
FieldInitializersEnd,
// After calling emitEnd.
End,
};
ClassState classState_ = ClassState::Start;
size_t numFields_ = 0;
#endif
JS::Rooted<JSAtom*> name_;
mozilla::Maybe<NameOpEmitter> initializersAssignment_;
size_t fieldIndex_ = 0;
public:
explicit ClassEmitter(BytecodeEmitter* bce);
@ -715,6 +758,10 @@ class MOZ_STACK_CLASS ClassEmitter : public PropertyEmitter
const mozilla::Maybe<uint32_t>& classStart,
const mozilla::Maybe<uint32_t>& classEnd);
MOZ_MUST_USE bool prepareForFieldInitializers(size_t numFields);
MOZ_MUST_USE bool emitStoreFieldInitializer();
MOZ_MUST_USE bool emitFieldInitializersEnd();
MOZ_MUST_USE bool emitEnd(Kind kind);
private:

View file

@ -2143,7 +2143,7 @@ class ClassField : public BinaryNode
public:
ClassField(ParseNode* name, ParseNode* initializer)
: BinaryNode(PNK_CLASSFIELD, JSOP_NOP,
initializer == nullptr ? name->pn_pos : TokenPos::box(name->pn_pos, initializer->pn_pos),
TokenPos::box(name->pn_pos, initializer->pn_pos),
name, initializer)
{
}
@ -2156,9 +2156,7 @@ class ClassField : public BinaryNode
ParseNode& name() const { return *left(); }
FunctionNode* initializer() const {
return right() ? &right()->as<FunctionNode>() : nullptr;
}
FunctionNode* initializer() const { return &right()->as<FunctionNode>(); }
};
class SwitchStatement : public BinaryNode

View file

@ -557,13 +557,10 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt
}
void
FunctionBox::initFieldInitializer(ParseContext* enclosing, bool hasHeritage)
FunctionBox::initFieldInitializer(ParseContext* enclosing)
{
this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Expression);
allowSuperProperty_ = false;
allowSuperCall_ = false;
this->initWithEnclosingParseContext(enclosing, FunctionSyntaxKind::Method);
allowArguments_ = false;
needsThisTDZChecks_ = hasHeritage;
}
void
@ -7419,7 +7416,7 @@ JSOpFromPropertyType(PropertyType propType)
template <typename ParseHandler>
bool
Parser<ParseHandler>::classMember(YieldHandling yieldHandling, DefaultHandling defaultHandling,
Parser<ParseHandler>::classMember(YieldHandling yieldHandling,
const ParseContext::ClassStatement& classStmt,
HandlePropertyName className,
uint32_t classStartOffset, bool hasHeritage,
@ -7492,8 +7489,7 @@ Parser<ParseHandler>::classMember(YieldHandling yieldHandling, DefaultHandling d
numFields++;
FunctionNodeType initializer = fieldInitializerOpt(yieldHandling, hasHeritage,
propAtom, numFieldKeys);
FunctionNodeType initializer = fieldInitializerOpt(propAtom, numFieldKeys);
if (!initializer)
return false;
@ -7558,8 +7554,25 @@ Parser<ParseHandler>::classMember(YieldHandling yieldHandling, DefaultHandling d
funName = propAtom;
}
// .fieldKeys must be declared outside the scope .initializers is declared in,
// hence this extra scope.
// When |super()| is invoked, we search for the nearest scope containing
// |.initializers| to initialize the class fields. This set-up precludes
// declaring |.initializers| in the class scope, because in some syntactic
// contexts |super()| can appear nested in a class, while actually belonging
// to an outer class definition.
//
// Example:
// class Outer extends Base {
// field = 1;
// constructor() {
// class Inner {
// field = 2;
//
// // The super() call in the computed property name mustn't access
// // Inner's |.initializers| array, but instead Outer's.
// [super()]() {}
// }
// }
// }
Maybe<ParseContext::Scope> dotInitializersScope;
if (isConstructor && !options().selfHostingMode) {
dotInitializersScope.emplace(this);
@ -7747,9 +7760,8 @@ Parser<ParseHandler>::classDefinition(YieldHandling yieldHandling,
size_t numFieldKeys = 0;
for (;;) {
bool done;
if (!classMember(yieldHandling, defaultHandling, classStmt, className,
classStartOffset, hasHeritage, numFields, numFieldKeys,
classMembers, &done))
if (!classMember(yieldHandling, classStmt, className, classStartOffset,
hasHeritage, numFields, numFieldKeys, classMembers, &done))
return null();
if (done)
break;
@ -7831,7 +7843,6 @@ Parser<ParseHandler>::synthesizeConstructor(HandleAtom className, uint32_t class
if (!funbox)
return null();
funbox->initWithEnclosingParseContext(pc, functionSyntaxKind);
handler.setFunctionBox(funNode, funbox);
funbox->setEnd(pos().end);
// Push a ParseContext on to the stack.
@ -7944,8 +7955,7 @@ Parser<ParseHandler>::synthesizeConstructor(HandleAtom className, uint32_t class
template <class ParseHandler>
typename ParseHandler::FunctionNodeType
Parser<ParseHandler>::fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage,
HandleAtom propAtom, size_t& numFieldKeys)
Parser<ParseHandler>::fieldInitializerOpt(HandleAtom propAtom, size_t& numFieldKeys)
{
bool hasInitializer = false;
if (!tokenStream.matchToken(&hasInitializer, TOK_ASSIGN))
@ -7983,8 +7993,7 @@ Parser<ParseHandler>::fieldInitializerOpt(YieldHandling yieldHandling, bool hasH
FunctionAsyncKind::SyncFunction, false);
if (!funbox)
return null();
funbox->initFieldInitializer(pc, hasHeritage);
handler.setFunctionBox(funNode, funbox);
funbox->initFieldInitializer(pc);
funbox->setStart(tokenStream, firstTokenPos);
// Push a SourceParseContext on to the stack.

View file

@ -1489,7 +1489,6 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
ClassNodeType classDefinition(YieldHandling yieldHandling, ClassContext classContext,
DefaultHandling defaultHandling);
MOZ_MUST_USE bool classMember(YieldHandling yieldHandling,
DefaultHandling defaultHandling,
const ParseContext::ClassStatement& classStmt,
HandlePropertyName className,
uint32_t classStartOffset, bool hasHeritage,
@ -1502,8 +1501,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE)
uint32_t classStartOffset, uint32_t classEndOffset,
size_t numFieldsWithInitializers, ListNodeType& classMembers);
FunctionNodeType fieldInitializerOpt(YieldHandling yieldHandling, bool hasHeritage,
HandleAtom atom, size_t& numFieldKeys);
FunctionNodeType fieldInitializerOpt(HandleAtom atom, size_t& numFieldKeys);
FunctionNodeType synthesizeConstructor(HandleAtom className,
uint32_t classNameOffset,
bool hasHeritage);

View file

@ -455,7 +455,7 @@ class FunctionBox : public ObjectBox, public SharedContext
void initFromLazyFunction();
void initStandaloneFunction(Scope* enclosingScope);
void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind);
void initFieldInitializer(ParseContext* enclosing, bool hasHeritage);
void initFieldInitializer(ParseContext* enclosing);
ObjectBox* toObjectBox() override { return this; }
JSFunction* function() const { return &object->as<JSFunction>(); }