Bug 1580246: Remove object-literal singleton objects allocated at parse. r=djvj,mgaudet

Instead, this patch introduces a new `ObjLiteral` mini-bytecode format
that is used to carry object-literal information from parse time to a
later time at which GC objects are safe to allocate. The mini-bytecode simply
specifies a list of fields and constant field values.

The original intent of this patch (realized in previous versions of it)
was to make this an opcode, and completely replace object creation
sequences (NEWINIT, INITPROP, INITPROP, ...) with one OBJLITERAL opcode.
However, there are quite a few performance regressions that occur when
replacing the finely-tuned set of optimizations around this with a new
mechanism.

As a result, this patch only defers allocation of the objects until the
very end of parse. Each object literal adds an ObjLiteralCreationData
instance to the GC-things list, and when the GC-things list is processed
to perform deferred allocations, the described objects will be created.

Differential Revision: https://phabricator.services.mozilla.com/D47985

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Chris Fallin 2019-11-04 18:31:18 +00:00
parent eb530ab0f2
commit dd56740967
11 changed files with 1052 additions and 135 deletions

View File

@ -7780,6 +7780,91 @@ bool BytecodeEmitter::emitConditionalExpression(
return true;
}
// Check for an object-literal property list that can be handled by the
// ObjLiteral writer. We immediately rule out class bodies. Then, we ensure
// that for each `prop: value` pair, the key is a constant name (and not a
// numeric index), there is no accessor specified, and the value can be encoded
// by an ObjLiteral instruction (constant number, string, boolean,
// null/undefined).
void BytecodeEmitter::isPropertyListObjLiteralCompatible(ListNode* obj,
PropListType type,
bool* withValues,
bool* withoutValues) {
if (type == ClassBody) {
*withValues = false;
*withoutValues = false;
return;
}
bool keysOK = true;
bool valuesOK = true;
int propCount = 0;
for (ParseNode* propdef : obj->contents()) {
if (!propdef->is<BinaryNode>()) {
keysOK = false;
break;
}
propCount++;
BinaryNode* prop = &propdef->as<BinaryNode>();
ParseNode* key = prop->left();
ParseNode* value = prop->right();
// Computed keys not OK (ObjLiteral data stores constant keys).
if (key->isKind(ParseNodeKind::ComputedName)) {
keysOK = false;
break;
}
// Numeric keys OK as long as they are integers and in range.
if (key->isKind(ParseNodeKind::NumberExpr)) {
double numValue = key->as<NumericLiteral>().value();
int32_t i = 0;
if (!NumberIsInt32(numValue, &i)) {
keysOK = false;
break;
}
if (!ObjLiteralWriter::arrayIndexInRange(i)) {
keysOK = false;
break;
}
}
AccessorType accessorType =
prop->is<PropertyDefinition>()
? prop->as<PropertyDefinition>().accessorType()
: AccessorType::None;
if (accessorType != AccessorType::None) {
keysOK = false;
break;
}
if (!isRHSObjLiteralCompatible(value)) {
valuesOK = false;
}
}
if (propCount >= PropertyTree::MAX_HEIGHT) {
// JSOP_NEWOBJECT cannot accept dictionary-mode objects.
keysOK = false;
}
*withValues = keysOK && valuesOK;
*withoutValues = keysOK;
}
bool BytecodeEmitter::isArrayObjLiteralCompatible(ParseNode* arrayHead) {
for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next) {
if (elem->isKind(ParseNodeKind::Spread)) {
return false;
}
if (!isRHSObjLiteralCompatible(elem)) {
return false;
}
}
return true;
}
bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe,
PropListType type) {
// [stack] CTOR? OBJ
@ -7825,8 +7910,8 @@ bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe,
}
if (propdef->is<LexicalScopeNode>()) {
// Constructors are sometimes wrapped in LexicalScopeNodes. As we already
// handled emitting the constructor, skip it.
// Constructors are sometimes wrapped in LexicalScopeNodes. As we
// already handled emitting the constructor, skip it.
MOZ_ASSERT(propdef->as<LexicalScopeNode>().scopeBody()->isKind(
ParseNodeKind::ClassMethod));
continue;
@ -8088,6 +8173,159 @@ bool BytecodeEmitter::emitPropertyList(ListNode* obj, PropertyEmitter& pe,
return true;
}
bool BytecodeEmitter::emitPropertyListObjLiteral(ListNode* obj,
PropListType type,
bool isSingletonContext,
bool noValuesMode) {
#ifdef DEBUG
bool withValues = false, withoutValues = false;
isPropertyListObjLiteralCompatible(obj, type, &withValues, &withoutValues);
MOZ_ASSERT_IF(noValuesMode, withoutValues);
MOZ_ASSERT_IF(!noValuesMode, withValues);
#endif
int32_t stackDepth = bytecodeSection().stackDepth();
ObjLiteralCreationData data(cx);
ObjLiteralFlags flags = (isSingletonContext ? OBJ_LITERAL_SINGLETON : 0) |
(noValuesMode ? OBJ_LITERAL_TEMPLATE : 0);
data.writer().beginObject(flags);
for (ParseNode* propdef : obj->contents()) {
MOZ_ASSERT(propdef->is<BinaryNode>());
BinaryNode* prop = &propdef->as<BinaryNode>();
ParseNode* key = prop->left();
MOZ_ASSERT(key->is<NameNode>() || key->is<NumericLiteral>());
ParseNode* value = noValuesMode ? nullptr : prop->right();
if (key->is<NameNode>()) {
uint32_t propNameIndex = 0;
if (!data.addAtom(key->as<NameNode>().atom(), &propNameIndex)) {
return false;
}
data.writer().setPropName(propNameIndex);
} else {
MOZ_ASSERT(key->is<NumericLiteral>());
double numValue = key->as<NumericLiteral>().value();
int32_t i = 0;
DebugOnly<bool> numIsInt =
NumberIsInt32(numValue, &i); // checked previously.
MOZ_ASSERT(numIsInt);
MOZ_ASSERT(
ObjLiteralWriter::arrayIndexInRange(i)); // checked previously.
data.writer().setPropIndex(i);
}
if (!emitObjLiteralValue(&data, value)) {
return false;
}
}
uint32_t gcThingIndex = 0;
if (!perScriptData().gcThingList().append(std::move(data), &gcThingIndex)) {
return false;
}
bool success = noValuesMode ? emitIndex32(JSOP_NEWOBJECT, gcThingIndex)
: emitIndex32(JSOP_OBJECT, gcThingIndex);
if (!success) {
return false;
}
bytecodeSection().setStackDepth(stackDepth + 1);
return true;
}
bool BytecodeEmitter::emitObjLiteralArray(ParseNode* arrayHead,
bool isSingleton) {
int32_t stackDepth = bytecodeSection().stackDepth();
ObjLiteralCreationData data(cx);
data.writer().beginObject(OBJ_LITERAL_ARRAY |
(isSingleton ? OBJ_LITERAL_SINGLETON : 0));
uint32_t index = 0;
for (ParseNode* elem = arrayHead; elem; elem = elem->pn_next, index++) {
data.writer().setPropIndex(index);
if (!emitObjLiteralValue(&data, elem)) {
return false;
}
}
uint32_t gcThingIndex = 0;
if (!perScriptData().gcThingList().append(std::move(data), &gcThingIndex)) {
return false;
}
JSOp op = isSingleton ? JSOP_OBJECT : JSOP_NEWARRAY_COPYONWRITE;
if (!emitIndex32(op, gcThingIndex)) {
return false;
}
bytecodeSection().setStackDepth(stackDepth + 1);
return true;
}
bool BytecodeEmitter::isRHSObjLiteralCompatible(ParseNode* value) {
return value->isKind(ParseNodeKind::NumberExpr) ||
value->isKind(ParseNodeKind::TrueExpr) ||
value->isKind(ParseNodeKind::FalseExpr) ||
value->isKind(ParseNodeKind::NullExpr) ||
value->isKind(ParseNodeKind::RawUndefinedExpr) ||
value->isKind(ParseNodeKind::StringExpr) ||
value->isKind(ParseNodeKind::TemplateStringExpr);
}
bool BytecodeEmitter::emitObjLiteralValue(ObjLiteralCreationData* data,
ParseNode* value) {
if (!value) {
// Template-only / no-values mode.
if (!data->writer().propWithUndefinedValue()) {
return false;
}
} else if (value->isKind(ParseNodeKind::NumberExpr)) {
double numValue = value->as<NumericLiteral>().value();
int32_t i = 0;
js::Value v;
if (NumberIsInt32(numValue, &i)) {
v.setInt32(i);
} else {
v.setDouble(numValue);
}
if (!data->writer().propWithConstNumericValue(v)) {
return false;
}
} else if (value->isKind(ParseNodeKind::TrueExpr)) {
if (!data->writer().propWithTrueValue()) {
return false;
}
} else if (value->isKind(ParseNodeKind::FalseExpr)) {
if (!data->writer().propWithFalseValue()) {
return false;
}
} else if (value->isKind(ParseNodeKind::NullExpr)) {
if (!data->writer().propWithNullValue()) {
return false;
}
} else if (value->isKind(ParseNodeKind::RawUndefinedExpr)) {
if (!data->writer().propWithUndefinedValue()) {
return false;
}
} else if (value->isKind(ParseNodeKind::StringExpr) ||
value->isKind(ParseNodeKind::TemplateStringExpr)) {
uint32_t valueAtomIndex = 0;
if (!data->addAtom(value->as<NameNode>().atom(), &valueAtomIndex)) {
return false;
}
if (!data->writer().propWithAtomValue(valueAtomIndex)) {
return false;
}
} else {
return false;
}
return true;
}
FieldInitializers BytecodeEmitter::setupFieldInitializers(
ListNode* classMembers) {
size_t numFields = 0;
@ -8104,8 +8342,9 @@ FieldInitializers BytecodeEmitter::setupFieldInitializers(
}
// Purpose of .fieldKeys:
// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time, not
// object construction time. The transformation to do so is roughly as follows:
// Computed field names (`["x"] = 2;`) must be ran at class-evaluation time,
// not object construction time. The transformation to do so is roughly as
// follows:
//
// class C {
// [keyExpr] = valueExpr;
@ -8256,9 +8495,9 @@ bool BytecodeEmitter::emitInitializeInstanceFields() {
for (size_t fieldIndex = 0; fieldIndex < numFields; fieldIndex++) {
if (fieldIndex < numFields - 1) {
// We DUP to keep the array around (it is consumed in the bytecode below)
// for next iterations of this loop, except for the last iteration, which
// avoids an extra POP at the end of the loop.
// We DUP to keep the array around (it is consumed in the bytecode
// below) for next iterations of this loop, except for the last
// iteration, which avoids an extra POP at the end of the loop.
if (!emit1(JSOP_DUP)) {
// [stack] ARRAY ARRAY
return false;
@ -8270,9 +8509,9 @@ bool BytecodeEmitter::emitInitializeInstanceFields() {
return false;
}
// Don't use CALLELEM here, because the receiver of the call != the receiver
// of this getelem. (Specifically, the call receiver is `this`, and the
// receiver of this getelem is `.initializers`)
// Don't use CALLELEM here, because the receiver of the call != the
// receiver of this getelem. (Specifically, the call receiver is `this`,
// and the receiver of this getelem is `.initializers`)
if (!emit1(JSOP_GETELEM)) {
// [stack] ARRAY? FUNC
return false;
@ -8301,22 +8540,89 @@ bool BytecodeEmitter::emitInitializeInstanceFields() {
// Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
// the comment on emitSwitch.
MOZ_NEVER_INLINE bool BytecodeEmitter::emitObject(ListNode* objNode) {
if (!objNode->hasNonConstInitializer() && objNode->head() &&
checkSingletonContext()) {
return emitSingletonInitialiser(objNode);
}
bool isSingletonContext = !objNode->hasNonConstInitializer() &&
objNode->head() && checkSingletonContext();
// Note: this method uses the ObjLiteralWriter and emits
// ObjLiteralCreationData objects into the GC list will eventually evaluate
// these object-literal creation instructions. Eventually we want OBJLITERAL
// to be a real opcode, but for now, performance constraints limit us to
// evaluating object literals at the end of parse, when we're allowed to
// allocate GC things.
//
// There are three cases here, in descending order of preference:
//
// 1. The list of property names is "normal" and constant (no computed
// values, no integer indices), the values are all simple constants
// (numbers, booleans, strings), *and* this occurs in a run-once
// (singleton) context. In this case, we can add emit ObjLiteral
// instructions to build an object with values, and the object will be
// attached to a JSOP_OBJECT opcode, whose semantics are for the backend to
// simply steal the object from the script.
//
// 2. The list of property names is "normal" and constant as above, but some
// values are complex (computed expressions, sub-objects, functions,
// etc.), or else this occurs in a non-run-once (non-singleton) context.
// In this case, we can use the ObjLiteral functionality to describe an
// *empty* object (all values left undefined) with the right fields, which
// will become a JSOP_NEWOBJECT opcode using this template object to speed
// the creation of the object each time it executes (stealing its shape,
// etc.). The emitted bytecode still needs INITPROP ops to set the values
// in this case.
//
// 3. Any other case. As a fallback, we use NEWINIT to create a new, empty
// object (i.e., `{}`) and then emit bytecode to initialize its properties
// one-by-one.
bool okWithValues = false, okWithoutValues = false;
isPropertyListObjLiteralCompatible(objNode, ObjectLiteral, &okWithValues,
&okWithoutValues);
bool isObjLiteralWithValues = okWithValues && isSingletonContext;
bool isObjLiteralTemplate = !isObjLiteralWithValues && okWithoutValues;
// [stack]
//
ObjectEmitter oe(this);
if (!oe.emitObject(objNode->count())) {
// [stack] OBJ
return false;
}
if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
// [stack] OBJ
return false;
if (isObjLiteralWithValues || isObjLiteralTemplate) {
// Use an ObjLiteral op. This will record ObjLiteral insns in the
// objLiteralWriter's buffer and add a fixup to the list of ObjLiteral
// fixups so that at GC-publish time at the end of parse, the full (case 1)
// or template-without-values (case 2) object can be allocated and the
// bytecode can be patched to refer to it.
if (!emitPropertyListObjLiteral(
objNode, ObjectLiteral,
/* isSingletonContext = */ isObjLiteralWithValues,
/* noValuesMode = */ !isObjLiteralWithValues)) {
// [stack] OBJ
return false;
}
// Put the ObjectEmitter in the right state. This tells it that there will
// already be an object on the stack as a result of the (eventual)
// NEWOBJECT or OBJECT op, and prepares it to emit values if needed.
if (!oe.emitObjectWithTemplateOnStack()) {
// [stack] OBJ
return false;
}
if (isObjLiteralTemplate) {
// Case 2 above: the ObjLiteral only created a template object. We still
// need to emit bytecode to fill in its values.
if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
// [stack] OBJ
return false;
}
}
} else {
// No ObjLiteral use, just bytecode to build the object from scratch.
if (!oe.emitObject(objNode->count())) {
// [stack] OBJ
return false;
}
if (!emitPropertyList(objNode, oe, ObjectLiteral)) {
// [stack] OBJ
return false;
}
}
if (!oe.emitEnd()) {
// [stack] OBJ
return false;
@ -8352,42 +8658,16 @@ bool BytecodeEmitter::replaceNewInitWithNewObject(JSObject* obj,
bool BytecodeEmitter::emitArrayLiteral(ListNode* array) {
if (!array->hasNonConstInitializer() && array->head()) {
if (checkSingletonContext()) {
// Bake in the object entirely if it will only be created once.
return emitSingletonInitialiser(array);
}
// If the array consists entirely of primitive values, make a
// template object with copy on write elements that can be reused
// every time the initializer executes. Don't do this if the array is
// small: copying the elements lazily is not worth it in that case.
static const size_t MinElementsForCopyOnWrite = 5;
if (emitterMode != BytecodeEmitter::SelfHosting &&
array->count() >= MinElementsForCopyOnWrite) {
RootedValue value(cx);
if (!array->getConstantValue(cx, ParseNode::ForCopyOnWriteArray,
&value)) {
return false;
}
if (!value.isMagic(JS_GENERIC_MAGIC)) {
// Note: the group of the template object might not yet reflect
// that the object has copy on write elements. When the
// interpreter or JIT compiler fetches the template, it should
// use ObjectGroup::getOrFixupCopyOnWriteObject to make sure the
// group for the template is accurate. We don't do this here as we
// want to use ObjectGroup::allocationSiteGroup, which requires a
// finished script.
JSObject* obj = &value.toObject();
MOZ_ASSERT(obj->is<ArrayObject>() &&
obj->as<ArrayObject>().denseElementsAreCopyOnWrite());
ObjectBox* objbox = parser->newObjectBox(obj);
if (!objbox) {
return false;
}
return emitObjectOp(objbox, JSOP_NEWARRAY_COPYONWRITE);
}
array->count() >= MinElementsForCopyOnWrite &&
isArrayObjLiteralCompatible(array->head())) {
bool isSingleton = checkSingletonContext();
return emitObjLiteralArray(array->head(), isSingleton);
}
}
@ -8987,8 +9267,8 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationSlow(
// Instrumentation is emitted in the form of a call to the realm's
// instrumentation callback, guarded by a test of whether instrumentation is
// currently active in the realm. The callback is invoked with the kind of
// operation which is executing, the current script's instrumentation ID, and
// the offset of the bytecode location after the instrumentation. Some
// operation which is executing, the current script's instrumentation ID,
// and the offset of the bytecode location after the instrumentation. Some
// operation kinds have more arguments, which will be pushed by
// pushOperandsCallback.
@ -8998,24 +9278,24 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationSlow(
if (!emit1(JSOP_INSTRUMENTATION_ACTIVE)) {
return false;
}
// [stack] ACTIVE
// [stack] ACTIVE
if (!ifEmitter.emitThen()) {
return false;
}
// [stack]
// [stack]
// Push the instrumentation callback for the current realm as the callee.
if (!emit1(JSOP_INSTRUMENTATION_CALLBACK)) {
return false;
}
// [stack] CALLBACK
// [stack] CALLBACK
// Push undefined for the call's |this| value.
if (!emit1(JSOP_UNDEFINED)) {
return false;
}
// [stack] CALLBACK UNDEFINED
// [stack] CALLBACK UNDEFINED
JSAtom* atom = RealmInstrumentation::getInstrumentationKindName(cx, kind);
if (!atom) {
@ -9030,37 +9310,37 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::emitInstrumentationSlow(
if (!emitAtomOp(index, JSOP_STRING)) {
return false;
}
// [stack] CALLBACK UNDEFINED KIND
// [stack] CALLBACK UNDEFINED KIND
if (!emit1(JSOP_INSTRUMENTATION_SCRIPT_ID)) {
return false;
}
// [stack] CALLBACK UNDEFINED KIND SCRIPT
// [stack] CALLBACK UNDEFINED KIND SCRIPT
// Push the offset of the bytecode location following the instrumentation.
BytecodeOffset updateOffset;
if (!emitN(JSOP_INT32, 4, &updateOffset)) {
return false;
}
// [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET
// [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET
unsigned numPushed = bytecodeSection().stackDepth() - initialDepth;
if (pushOperandsCallback && !pushOperandsCallback(numPushed)) {
return false;
}
// [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET ...EXTRA_ARGS
// [stack] CALLBACK UNDEFINED KIND SCRIPT OFFSET ...EXTRA_ARGS
unsigned argc = bytecodeSection().stackDepth() - initialDepth - 2;
if (!emitCall(JSOP_CALL_IGNORES_RV, argc)) {
return false;
}
// [stack] RV
// [stack] RV
if (!emit1(JSOP_POP)) {
return false;
}
// [stack]
// [stack]
if (!ifEmitter.emitEnd()) {
return false;

View File

@ -11,8 +11,9 @@
#include "mozilla/Assertions.h" // MOZ_ASSERT
#include "mozilla/Attributes.h" // MOZ_STACK_CLASS, MOZ_MUST_USE, MOZ_ALWAYS_INLINE, MOZ_NEVER_INLINE, MOZ_RAII
#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some
#include "mozilla/Span.h" // mozilla::Span
#include "mozilla/Maybe.h" // mozilla::Maybe, mozilla::Some
#include "mozilla/Span.h" // mozilla::Span
#include "mozilla/Vector.h" // mozilla::Vector
#include <stddef.h> // ptrdiff_t
#include <stdint.h> // uint16_t, uint32_t
@ -482,9 +483,29 @@ struct MOZ_STACK_CLASS BytecodeEmitter {
MOZ_MUST_USE bool emitHoistedFunctionsInList(ListNode* stmtList);
// Can we use the object-literal writer either in singleton-object mode (with
// values) or in template mode (field names only, no values) for the property
// list?
void isPropertyListObjLiteralCompatible(ListNode* obj, PropListType type,
bool* withValues,
bool* withoutValues);
bool isArrayObjLiteralCompatible(ParseNode* arrayHead);
MOZ_MUST_USE bool emitPropertyList(ListNode* obj, PropertyEmitter& pe,
PropListType type);
MOZ_MUST_USE bool emitPropertyListObjLiteral(ListNode* obj, PropListType type,
bool isSingletonContext,
bool noValuesMode);
MOZ_MUST_USE bool emitObjLiteralArray(ParseNode* arrayHead, bool isSingleton);
// Is a field value OBJLITERAL-compatible?
MOZ_MUST_USE bool isRHSObjLiteralCompatible(ParseNode* value);
MOZ_MUST_USE bool emitObjLiteralValue(ObjLiteralCreationData* data,
ParseNode* value);
FieldInitializers setupFieldInitializers(ListNode* classMembers);
MOZ_MUST_USE bool emitCreateFieldKeys(ListNode* obj);
MOZ_MUST_USE bool emitCreateFieldInitializers(ClassEmitter& ce,

View File

@ -64,6 +64,14 @@ bool GCThingList::finish(JSContext* cx, mozilla::Span<JS::GCCellPtr> array) {
array[i] = JS::GCCellPtr(bi);
return true;
}
bool operator()(ObjLiteralCreationData& data) {
JSObject* obj = data.create(cx);
if (!obj) {
return false;
}
array[i] = JS::GCCellPtr(obj);
return true;
}
};
for (uint32_t i = 0; i < length(); i++) {
@ -146,6 +154,10 @@ void CGResumeOffsetList::finish(mozilla::Span<uint32_t> array) {
}
}
JSObject* ObjLiteralCreationData::create(JSContext* cx) {
return InterpretObjLiteral(cx, atoms_, writer_);
}
BytecodeSection::BytecodeSection(JSContext* cx, uint32_t lineNum)
: code_(cx),
notes_(cx),

View File

@ -19,6 +19,7 @@
#include "frontend/BytecodeOffset.h" // BytecodeOffset
#include "frontend/JumpList.h" // JumpTarget
#include "frontend/NameCollections.h" // AtomIndexMap, PooledMapPtr
#include "frontend/ObjLiteral.h" // ObjLiteralCreationData
#include "frontend/ParseNode.h" // BigIntLiteral
#include "frontend/SourceNotes.h" // jssrcnote
#include "gc/Barrier.h" // GCPtrObject, GCPtrScope, GCPtrValue
@ -43,7 +44,8 @@ class BigIntLiteral;
class ObjectBox;
struct MOZ_STACK_CLASS GCThingList {
using ListType = mozilla::Variant<StackGCCellPtr, BigIntCreationData>;
using ListType = mozilla::Variant<StackGCCellPtr, BigIntCreationData,
ObjLiteralCreationData>;
JS::RootedVector<ListType> vector;
// Last emitted object.
@ -73,6 +75,10 @@ struct MOZ_STACK_CLASS GCThingList {
return vector.append(
mozilla::AsVariant(StackGCCellPtr(JS::GCCellPtr(literal->value()))));
}
MOZ_MUST_USE bool append(ObjLiteralCreationData objlit, uint32_t* index) {
*index = vector.length();
return vector.append(mozilla::AsVariant(std::move(objlit)));
}
MOZ_MUST_USE bool append(ObjectBox* obj, uint32_t* index);
uint32_t length() const { return vector.length(); }

View File

@ -0,0 +1,161 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sw=2 et tw=0 ft=c:
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "frontend/ObjLiteral.h"
#include "mozilla/DebugOnly.h"
#include "js/RootingAPI.h"
#include "vm/JSAtom.h"
#include "vm/JSObject.h"
#include "vm/ObjectGroup.h"
#include "gc/ObjectKind-inl.h"
#include "vm/JSAtom-inl.h"
#include "vm/JSObject-inl.h"
namespace js {
static void InterpretObjLiteralValue(ObjLiteralAtomVector& atoms,
const ObjLiteralInsn& insn,
MutableHandleValue propVal) {
switch (insn.getOp()) {
case ObjLiteralOpcode::ConstValue:
propVal.set(insn.getConstValue());
break;
case ObjLiteralOpcode::ConstAtom: {
uint32_t index = insn.getAtomIndex();
propVal.setString(atoms[index]);
break;
}
case ObjLiteralOpcode::Null:
propVal.setNull();
break;
case ObjLiteralOpcode::Undefined:
propVal.setUndefined();
break;
case ObjLiteralOpcode::True:
propVal.setBoolean(true);
break;
case ObjLiteralOpcode::False:
propVal.setBoolean(false);
break;
default:
MOZ_CRASH("Unexpected object-literal instruction opcode");
}
}
static JSObject* InterpretObjLiteralObj(
JSContext* cx, ObjLiteralAtomVector& atoms,
mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) {
bool singleton = flags & OBJ_LITERAL_SINGLETON;
ObjLiteralReader reader(literalInsns);
ObjLiteralInsn insn;
jsid propId;
Rooted<Value> propVal(cx);
Rooted<IdValueVector> properties(cx, IdValueVector(cx));
// Compute property values and build the key/value-pair list.
while (true) {
if (!reader.readInsn(&insn)) {
break;
}
MOZ_ASSERT(insn.isValid());
if (insn.getKey().isArrayIndex()) {
propId = INT_TO_JSID(insn.getKey().getArrayIndex());
} else {
propId = AtomToId(atoms[insn.getKey().getAtomIndex()]);
}
propVal.setUndefined();
InterpretObjLiteralValue(atoms, insn, &propVal);
if (!properties.append(IdValuePair(propId, propVal))) {
return nullptr;
}
}
// Actually build the object:
// - In the singleton case, we want to collect all properties *first*, then
// call ObjectGroup::newPlainObject at the end to build a group specific to
// this singleton object.
// - In the non-singleton case (template case), we want to allocate an
// ordinary tenured object, *not* necessarily with its own group, to prevent
// memory regressions (too many groups compared to old behavior).
if (singleton) {
return ObjectGroup::newPlainObject(cx, properties.begin(),
properties.length(), SingletonObject);
}
gc::AllocKind allocKind = gc::GetGCObjectKind(properties.length());
RootedPlainObject result(
cx, NewBuiltinClassInstance<PlainObject>(cx, allocKind, TenuredObject));
if (!result) {
return nullptr;
}
Rooted<JS::PropertyKey> propKey(cx);
for (const auto& kvPair : properties) {
propKey.set(kvPair.id);
propVal.set(kvPair.value);
if (!NativeDefineDataProperty(cx, result, propKey, propVal,
JSPROP_ENUMERATE)) {
return nullptr;
}
}
return result;
}
static JSObject* InterpretObjLiteralArray(
JSContext* cx, ObjLiteralAtomVector& atoms,
mozilla::Span<const uint8_t> literalInsns, ObjLiteralFlags flags) {
ObjLiteralReader reader(literalInsns);
ObjLiteralInsn insn;
Rooted<ValueVector> elements(cx, ValueVector(cx));
Rooted<Value> propVal(cx);
mozilla::DebugOnly<uint32_t> index = 0;
while (true) {
if (!reader.readInsn(&insn)) {
break;
}
MOZ_ASSERT(insn.isValid());
MOZ_ASSERT(insn.getKey().isArrayIndex());
MOZ_ASSERT(insn.getKey().getArrayIndex() == index);
#ifdef DEBUG
index++;
#endif
propVal.setUndefined();
InterpretObjLiteralValue(atoms, insn, &propVal);
if (!elements.append(propVal)) {
return nullptr;
}
}
ObjectGroup::NewArrayKind arrayKind =
(flags & OBJ_LITERAL_SINGLETON) ? ObjectGroup::NewArrayKind::Normal
: ObjectGroup::NewArrayKind::CopyOnWrite;
RootedObject result(
cx, ObjectGroup::newArrayObject(cx, elements.begin(), elements.length(),
NewObjectKind::TenuredObject, arrayKind));
if (!result) {
return nullptr;
}
return result;
}
JSObject* InterpretObjLiteral(JSContext* cx, ObjLiteralAtomVector& atoms,
mozilla::Span<const uint8_t> literalInsns,
ObjLiteralFlags flags) {
return (flags & OBJ_LITERAL_ARRAY)
? InterpretObjLiteralArray(cx, atoms, literalInsns, flags)
: InterpretObjLiteralObj(cx, atoms, literalInsns, flags);
}
} // namespace js

View File

@ -0,0 +1,423 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sw=2 et tw=0 ft=c:
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef frontend_ObjLiteral_h
#define frontend_ObjLiteral_h
#include "mozilla/EndianUtils.h"
#include "mozilla/Span.h"
#include "js/Vector.h"
namespace js {
// Object-literal instruction opcodes. An object literal is constructed by a
// straight-line sequence of these ops, each adding one property to the
// object.
enum ObjLiteralOpcode : uint8_t {
INVALID = 0,
// --- not supported: we had this working in interpreter and baseline, but
// --- values from stack create nontrivial issues in Ion.
// Stack = 1,
// ---
ConstValue = 2, // numeric types only.
ConstAtom = 3,
Null = 4,
Undefined = 5,
True = 6,
False = 7,
MAX = False,
};
static_assert(ObjLiteralOpcode::MAX <= UINT8_MAX,
"ObjLiteralOpcode must be a single byte");
// Flags that are associated with a sequence of object-literal instructions.
enum : uint8_t {
// If set, the created object will be created with a singleton object group.
OBJ_LITERAL_SINGLETON = 0x01,
// If set, this object is used as a template, and has no values. Mutually
// exclusive with SINGLETON above.
OBJ_LITERAL_TEMPLATE = 0x02,
// If set, this object is an array.
OBJ_LITERAL_ARRAY = 0x04,
};
using ObjLiteralFlags = uint8_t;
inline bool ObjLiteralOpcodeHasValueArg(ObjLiteralOpcode op) {
return op == ObjLiteralOpcode::ConstValue;
}
inline bool ObjLiteralOpcodeHasAtomArg(ObjLiteralOpcode op) {
return op == ObjLiteralOpcode::ConstAtom;
}
struct ObjLiteralReaderBase;
// An unpacked representation of a property in the ObjLiteral instructions:
// either a property name (as an index into the ObjLiteralWriter's atom table)
// or an integer index.
struct ObjLiteralKey {
private:
uint32_t value_;
bool isArrayIndex_;
public:
ObjLiteralKey() : value_(0), isArrayIndex_(true) {}
ObjLiteralKey(uint32_t value, bool isArrayIndex)
: value_(value), isArrayIndex_(isArrayIndex) {}
ObjLiteralKey(const ObjLiteralKey& other) = default;
static ObjLiteralKey fromPropName(uint32_t atomIndex) {
return ObjLiteralKey(atomIndex, false);
}
static ObjLiteralKey fromArrayIndex(uint32_t index) {
return ObjLiteralKey(index, true);
}
bool isAtomIndex() const { return !isArrayIndex_; }
bool isArrayIndex() const { return isArrayIndex_; }
uint32_t getAtomIndex() const {
MOZ_ASSERT(isAtomIndex());
return value_;
}
uint32_t getArrayIndex() const {
MOZ_ASSERT(isArrayIndex());
return value_;
}
uint32_t rawIndex() const { return value_; }
};
struct ObjLiteralWriterBase {
protected:
friend struct ObjLiteralReaderBase; // for access to mask and shift.
static const uint32_t ATOM_INDEX_MASK = 0x007fffff;
// If set, the atom index field is an array index, not an atom index.
static const uint32_t INDEXED_PROP = 0x00800000;
static const int OP_SHIFT = 24;
protected:
Vector<uint8_t, 64> code_;
public:
explicit ObjLiteralWriterBase(JSContext* cx) : code_(cx) {}
uint32_t curOffset() const { return code_.length(); }
MOZ_MUST_USE bool prepareBytes(size_t len, uint8_t** p) {
size_t offset = code_.length();
if (!code_.growByUninitialized(len)) {
return false;
}
*p = &code_[offset];
return true;
}
template <typename T>
MOZ_MUST_USE bool pushRawData(T data) {
uint8_t* p = nullptr;
if (!prepareBytes(sizeof(T), &p)) {
return false;
}
mozilla::NativeEndian::copyAndSwapToLittleEndian(reinterpret_cast<void*>(p),
&data, 1);
return true;
}
MOZ_MUST_USE bool pushOpAndName(ObjLiteralOpcode op, ObjLiteralKey key) {
uint32_t data = (key.rawIndex() & ATOM_INDEX_MASK) |
(key.isArrayIndex() ? INDEXED_PROP : 0) |
(static_cast<uint8_t>(op) << OP_SHIFT);
return pushRawData(data);
}
MOZ_MUST_USE bool pushValueArg(const js::Value& value) {
MOZ_ASSERT(value.isNumber() || value.isNullOrUndefined() ||
value.isBoolean());
uint64_t data = value.asRawBits();
return pushRawData(data);
}
MOZ_MUST_USE bool pushAtomArg(uint32_t atomIndex) {
return pushRawData(atomIndex);
}
};
// An object-literal instruction writer. This class, held by the bytecode
// emitter, keeps a sequence of object-literal instructions emitted as object
// literal expressions are parsed. It allows the user to 'begin' and 'end'
// straight-line sequences, returning the offsets for this range of instructions
// within the writer.
struct ObjLiteralWriter : private ObjLiteralWriterBase {
public:
explicit ObjLiteralWriter(JSContext* cx)
: ObjLiteralWriterBase(cx), flags_(0) {}
void clear() { code_.clear(); }
mozilla::Span<const uint8_t> getCode() const { return code_; }
ObjLiteralFlags getFlags() const { return flags_; }
void beginObject(ObjLiteralFlags flags) { flags_ = flags; }
void setPropName(uint32_t propName) {
MOZ_ASSERT(propName <= ATOM_INDEX_MASK);
nextKey_ = ObjLiteralKey::fromPropName(propName);
}
void setPropIndex(uint32_t propIndex) {
MOZ_ASSERT(propIndex <= ATOM_INDEX_MASK);
nextKey_ = ObjLiteralKey::fromArrayIndex(propIndex);
}
MOZ_MUST_USE bool propWithConstNumericValue(const js::Value& value) {
MOZ_ASSERT(value.isNumber());
return pushOpAndName(ObjLiteralOpcode::ConstValue, nextKey_) &&
pushValueArg(value);
}
MOZ_MUST_USE bool propWithAtomValue(uint32_t value) {
return pushOpAndName(ObjLiteralOpcode::ConstAtom, nextKey_) &&
pushAtomArg(value);
}
MOZ_MUST_USE bool propWithNullValue() {
return pushOpAndName(ObjLiteralOpcode::Null, nextKey_);
}
MOZ_MUST_USE bool propWithUndefinedValue() {
return pushOpAndName(ObjLiteralOpcode::Undefined, nextKey_);
}
MOZ_MUST_USE bool propWithTrueValue() {
return pushOpAndName(ObjLiteralOpcode::True, nextKey_);
}
MOZ_MUST_USE bool propWithFalseValue() {
return pushOpAndName(ObjLiteralOpcode::False, nextKey_);
}
static bool arrayIndexInRange(int32_t i) {
return i >= 0 && static_cast<uint32_t>(i) <= ATOM_INDEX_MASK;
}
private:
ObjLiteralFlags flags_;
ObjLiteralKey nextKey_;
};
struct ObjLiteralReaderBase {
private:
mozilla::Span<const uint8_t> data_;
size_t cursor_;
MOZ_MUST_USE bool readBytes(size_t size, const uint8_t** p) {
if (cursor_ + size > data_.Length()) {
return false;
}
*p = data_.From(cursor_).data();
cursor_ += size;
return true;
}
template <typename T>
MOZ_MUST_USE bool readRawData(T* data) {
const uint8_t* p = nullptr;
if (!readBytes(sizeof(T), &p)) {
return false;
}
mozilla::NativeEndian::copyAndSwapFromLittleEndian(
data, reinterpret_cast<const void*>(p), 1);
return true;
}
public:
explicit ObjLiteralReaderBase(mozilla::Span<const uint8_t> data)
: data_(data), cursor_(0) {}
MOZ_MUST_USE bool readOpAndKey(ObjLiteralOpcode* op, ObjLiteralKey* key) {
uint32_t data;
if (!readRawData(&data)) {
return false;
}
uint8_t opbyte =
static_cast<uint8_t>(data >> ObjLiteralWriterBase::OP_SHIFT);
if (MOZ_UNLIKELY(opbyte > ObjLiteralOpcode::MAX)) {
return false;
}
*op = static_cast<ObjLiteralOpcode>(opbyte);
bool isArray = data & ObjLiteralWriterBase::INDEXED_PROP;
uint32_t rawIndex = data & ObjLiteralWriterBase::ATOM_INDEX_MASK;
*key = ObjLiteralKey(rawIndex, isArray);
return true;
}
MOZ_MUST_USE bool readValueArg(js::Value* value) {
uint64_t data;
if (!readRawData(&data)) {
return false;
}
*value = js::Value::fromRawBits(data);
return true;
}
MOZ_MUST_USE bool readAtomArg(uint32_t* atomIndex) {
return readRawData(atomIndex);
}
};
// A single object-literal instruction, creating one property on an object.
struct ObjLiteralInsn {
private:
ObjLiteralOpcode op_;
ObjLiteralKey key_;
union Arg {
explicit Arg(uint64_t raw_) : raw(raw_) {}
js::Value constValue;
uint32_t atomIndex;
uint64_t raw;
} arg_;
public:
ObjLiteralInsn() : op_(ObjLiteralOpcode::INVALID), arg_(0) {}
ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key)
: op_(op), key_(key), arg_(0) {
MOZ_ASSERT(!hasConstValue());
MOZ_ASSERT(!hasAtomIndex());
}
ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, const js::Value& value)
: op_(op), key_(key), arg_(0) {
MOZ_ASSERT(hasConstValue());
MOZ_ASSERT(!hasAtomIndex());
arg_.constValue = value;
}
ObjLiteralInsn(ObjLiteralOpcode op, ObjLiteralKey key, uint32_t atomIndex)
: op_(op), key_(key), arg_(0) {
MOZ_ASSERT(!hasConstValue());
MOZ_ASSERT(hasAtomIndex());
arg_.atomIndex = atomIndex;
}
ObjLiteralInsn(const ObjLiteralInsn& other) : ObjLiteralInsn() {
*this = other;
}
ObjLiteralInsn& operator=(const ObjLiteralInsn& other) {
op_ = other.op_;
key_ = other.key_;
arg_.raw = other.arg_.raw;
return *this;
}
bool isValid() const {
return op_ > ObjLiteralOpcode::INVALID && op_ <= ObjLiteralOpcode::MAX;
}
ObjLiteralOpcode getOp() const {
MOZ_ASSERT(isValid());
return op_;
}
const ObjLiteralKey& getKey() const {
MOZ_ASSERT(isValid());
return key_;
}
bool hasConstValue() const {
MOZ_ASSERT(isValid());
return ObjLiteralOpcodeHasValueArg(op_);
}
bool hasAtomIndex() const {
MOZ_ASSERT(isValid());
return ObjLiteralOpcodeHasAtomArg(op_);
}
js::Value getConstValue() const {
MOZ_ASSERT(isValid());
MOZ_ASSERT(hasConstValue());
return arg_.constValue;
}
uint32_t getAtomIndex() const {
MOZ_ASSERT(isValid());
MOZ_ASSERT(hasAtomIndex());
return arg_.atomIndex;
};
};
// A reader that parses a sequence of object-literal instructions out of the
// encoded form.
struct ObjLiteralReader : private ObjLiteralReaderBase {
public:
explicit ObjLiteralReader(mozilla::Span<const uint8_t> data)
: ObjLiteralReaderBase(data) {}
MOZ_MUST_USE bool readInsn(ObjLiteralInsn* insn) {
ObjLiteralOpcode op;
ObjLiteralKey key;
if (!readOpAndKey(&op, &key)) {
return false;
}
if (ObjLiteralOpcodeHasValueArg(op)) {
js::Value value;
if (!readValueArg(&value)) {
return false;
}
*insn = ObjLiteralInsn(op, key, value);
return true;
}
if (ObjLiteralOpcodeHasAtomArg(op)) {
uint32_t atomIndex;
if (!readAtomArg(&atomIndex)) {
return false;
}
*insn = ObjLiteralInsn(op, key, atomIndex);
return true;
}
*insn = ObjLiteralInsn(op, key);
return true;
}
};
typedef Vector<JSAtom*, 4> ObjLiteralAtomVector;
JSObject* InterpretObjLiteral(JSContext* cx, ObjLiteralAtomVector& atoms,
mozilla::Span<const uint8_t> insns,
ObjLiteralFlags flags);
inline JSObject* InterpretObjLiteral(JSContext* cx, ObjLiteralAtomVector& atoms,
const ObjLiteralWriter& writer) {
return InterpretObjLiteral(cx, atoms, writer.getCode(), writer.getFlags());
}
class ObjLiteralCreationData {
private:
ObjLiteralWriter writer_;
ObjLiteralAtomVector atoms_;
public:
explicit ObjLiteralCreationData(JSContext* cx) : writer_(cx), atoms_(cx) {}
ObjLiteralWriter& writer() { return writer_; }
bool addAtom(JSAtom* atom, uint32_t* index) {
*index = atoms_.length();
return atoms_.append(atom);
}
JSObject* create(JSContext* cx);
};
} // namespace js
namespace JS {
// Ignore GC tracing for the ObjLiteralCreationData. It contains JSAtom
// pointers, but these are already held and rooted by the parser. (We must
// specify GC policy for the creation data because it is placed in the
// GC-things vector.)
template <>
struct GCPolicy<js::ObjLiteralCreationData>
: JS::IgnoreGCPolicy<js::ObjLiteralCreationData> {};
} // namespace JS
#endif // frontend_ObjLiteral_h

View File

@ -31,8 +31,7 @@ using namespace js::frontend;
using mozilla::Maybe;
PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce)
: bce_(bce), obj_(bce->cx) {}
PropertyEmitter::PropertyEmitter(BytecodeEmitter* bce) : bce_(bce) {}
bool PropertyEmitter::prepareForProtoValue(const Maybe<uint32_t>& keyPos) {
MOZ_ASSERT(propertyState_ == PropertyState::Start ||
@ -62,7 +61,6 @@ bool PropertyEmitter::emitMutateProto() {
return false;
}
obj_ = nullptr;
#ifdef DEBUG
propertyState_ = PropertyState::Init;
#endif
@ -102,7 +100,6 @@ bool PropertyEmitter::emitSpread() {
return false;
}
obj_ = nullptr;
#ifdef DEBUG
propertyState_ = PropertyState::Init;
#endif
@ -163,8 +160,6 @@ bool PropertyEmitter::prepareForIndexPropKey(
// [stack] CTOR? OBJ
obj_ = nullptr;
if (!prepareForProp(keyPos,
/* isStatic_ = */ kind == Kind::Static,
/* isIndexOrComputed = */ true)) {
@ -196,8 +191,6 @@ bool PropertyEmitter::prepareForComputedPropKey(
// [stack] CTOR? OBJ
obj_ = nullptr;
if (!prepareForProp(keyPos,
/* isStatic_ = */ kind == Kind::Static,
/* isIndexOrComputed = */ true)) {
@ -273,13 +266,11 @@ bool PropertyEmitter::emitInitProp(JS::Handle<JSAtom*> key) {
}
bool PropertyEmitter::emitInitGetter(JS::Handle<JSAtom*> key) {
obj_ = nullptr;
return emitInit(isClass_ ? JSOP_INITHIDDENPROP_GETTER : JSOP_INITPROP_GETTER,
key);
}
bool PropertyEmitter::emitInitSetter(JS::Handle<JSAtom*> key) {
obj_ = nullptr;
return emitInit(isClass_ ? JSOP_INITHIDDENPROP_SETTER : JSOP_INITPROP_SETTER,
key);
}
@ -290,13 +281,11 @@ bool PropertyEmitter::emitInitIndexProp() {
}
bool PropertyEmitter::emitInitIndexGetter() {
obj_ = nullptr;
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER
: JSOP_INITELEM_GETTER);
}
bool PropertyEmitter::emitInitIndexSetter() {
obj_ = nullptr;
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER
: JSOP_INITELEM_SETTER);
}
@ -307,13 +296,11 @@ bool PropertyEmitter::emitInitComputedProp() {
}
bool PropertyEmitter::emitInitComputedGetter() {
obj_ = nullptr;
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_GETTER
: JSOP_INITELEM_GETTER);
}
bool PropertyEmitter::emitInitComputedSetter() {
obj_ = nullptr;
return emitInitIndexOrComputed(isClass_ ? JSOP_INITHIDDENELEM_SETTER
: JSOP_INITELEM_SETTER);
}
@ -333,19 +320,6 @@ bool PropertyEmitter::emitInit(JSOp op, JS::Handle<JSAtom*> key) {
return false;
}
if (obj_) {
MOZ_ASSERT(!IsHiddenInitOp(op));
MOZ_ASSERT(!obj_->inDictionaryMode());
JS::Rooted<JS::PropertyKey> propKey(bce_->cx, AtomToId(key));
if (!NativeDefineDataProperty(bce_->cx, obj_, propKey, UndefinedHandleValue,
JSPROP_ENUMERATE)) {
return false;
}
if (obj_->inDictionaryMode()) {
obj_ = nullptr;
}
}
if (!bce_->emitIndex32(op, index)) {
// [stack] CTOR? OBJ CTOR?
return false;
@ -412,25 +386,20 @@ bool ObjectEmitter::emitObject(size_t propertyCount) {
// Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing
// a new object and defining (in source order) each property on the object
// (or mutating the object's [[Prototype]], in the case of __proto__).
top_ = bce_->bytecodeSection().offset();
if (!bce_->emitNewInit()) {
// [stack] OBJ
return false;
}
// Try to construct the shape of the object as we go, so we can emit a
// JSOP_NEWOBJECT with the final shape instead.
// In the case of computed property names and indices, we cannot fix the
// shape at bytecode compile time. When the shape cannot be determined,
// |obj| is nulled out.
#ifdef DEBUG
objectState_ = ObjectState::Object;
#endif
return true;
}
// No need to do any guessing for the object kind, since we know the upper
// bound of how many properties we plan to have.
gc::AllocKind kind = gc::GetGCObjectKind(propertyCount);
obj_ = NewBuiltinClassInstance<PlainObject>(bce_->cx, kind, TenuredObject);
if (!obj_) {
return false;
}
bool ObjectEmitter::emitObjectWithTemplateOnStack() {
MOZ_ASSERT(propertyState_ == PropertyState::Start);
MOZ_ASSERT(objectState_ == ObjectState::Start);
#ifdef DEBUG
objectState_ = ObjectState::Object;
@ -445,15 +414,6 @@ bool ObjectEmitter::emitEnd() {
// [stack] OBJ
if (obj_) {
// The object survived and has a predictable shape: update the original
// bytecode.
if (!bce_->replaceNewInitWithNewObject(obj_, top_)) {
// [stack] OBJ
return false;
}
}
#ifdef DEBUG
objectState_ = ObjectState::End;
#endif

View File

@ -16,12 +16,13 @@
#include "frontend/BytecodeOffset.h" // BytecodeOffset
#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/BytecodeUtil.h" // JSOp
#include "vm/JSAtom.h" // JSAtom
#include "vm/NativeObject.h" // PlainObject
#include "vm/Scope.h" // LexicalScope
#include "frontend/ObjLiteral.h" // ObjLiteralWriter, ObjLiteralCreationData
#include "frontend/TDZCheckCache.h" // TDZCheckCache
#include "js/RootingAPI.h" // JS::Handle, JS::Rooted
#include "vm/BytecodeUtil.h" // JSOp
#include "vm/JSAtom.h" // JSAtom
#include "vm/NativeObject.h" // PlainObject
#include "vm/Scope.h" // LexicalScope
namespace js {
@ -55,13 +56,6 @@ class MOZ_STACK_CLASS PropertyEmitter {
// True if the property has computed or index key.
bool isIndexOrComputed_ = false;
// An object which keeps the shape of this object literal.
// This fields is reset to nullptr whenever the object literal turns out to
// have at least one numeric, computed, spread or __proto__ property, or
// the object becomes dictionary mode.
// This field is used only in ObjectEmitter.
JS::Rooted<PlainObject*> obj_;
#ifdef DEBUG
// The state of this emitter.
//
@ -383,10 +377,6 @@ class MOZ_STACK_CLASS PropertyEmitter {
//
class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter {
private:
// The offset of JSOP_NEWINIT, which is replced by JSOP_NEWOBJECT later
// when the object is known to have a fixed shape.
BytecodeOffset top_;
#ifdef DEBUG
// The state of this emitter.
//
@ -416,6 +406,9 @@ class MOZ_STACK_CLASS ObjectEmitter : public PropertyEmitter {
explicit ObjectEmitter(BytecodeEmitter* bce);
MOZ_MUST_USE bool emitObject(size_t propertyCount);
// Same as `emitObject()`, but start with an empty template object already on
// the stack.
MOZ_MUST_USE bool emitObjectWithTemplateOnStack();
MOZ_MUST_USE bool emitEnd();
};

View File

@ -45,6 +45,7 @@ UNIFIED_SOURCES += [
'NameFunctions.cpp',
'NameOpEmitter.cpp',
'ObjectEmitter.cpp',
'ObjLiteral.cpp',
'ParseContext.cpp',
'ParseNode.cpp',
'ParseNodeVerify.cpp',

View File

@ -0,0 +1,59 @@
load(libdir + "./asserts.js");
// Exercise object-literal creation. Many other tests also exercise details of
// objects created by object literals -- e.g., byteSize-of-objects.js. Here we
// simply want to hit the cases {run-once ctx, repeated ctx} x {constant,
// parameterized} x {small, large}.
function build_large_literal(var_name, num_keys, extra) {
let s = "var " + var_name + " = {";
for (let i = 0; i < num_keys; i++) {
s += "prop" + i + ": " + i + ",";
}
s += extra;
s += "};";
return s;
}
let small_singleton = {a: 1, b: 2, 0: "I'm an indexed property" };
// Large enough to force dictionary mode -- should inhibit objliteral use in
// frontend:
eval(build_large_literal("large_singleton", 513, ""));
let an_integer = 42;
let small_singleton_param = { a: 1, b: 2, c: an_integer };
eval(build_large_literal("large_singleton_param", 513, "prop_int: an_integer"));
function f(a_parameterized_integer) {
let small_templated = {a: 1, b: 2, 0: "I'm an indexed property" };
// Large enough to force dictionary mode -- should inhibit objliteral use in
// frontend:
eval(build_large_literal("large_templated", 513, ""));
let small_templated_param = { a: 1, b: 2, c: a_parameterized_integer };
eval(build_large_literal("large_templated_param", 513, "prop_int: a_parameterized_integer"));
return {small_templated, large_templated,
small_templated_param, large_templated_param};
}
for (let i = 0; i < 10; i++) {
let {small_templated, large_templated,
small_templated_param, large_templated_param} = f(42);
assertDeepEq(small_templated, small_singleton);
assertDeepEq(large_templated, large_singleton);
assertDeepEq(small_templated_param, small_singleton_param);
assertDeepEq(large_templated_param, large_singleton_param);
}
let small_lit_array = [0, 1, 2, 3];
let large_cow_lit_array = [0, 1, 2, 3, 4, 5, 6, 7];
assertEq(4, small_lit_array.length);
assertEq(8, large_cow_lit_array.length);
for (let i = 0; i < small_lit_array.length; i++) {
assertEq(i, small_lit_array[i]);
}
for (let i = 0; i < large_cow_lit_array.length; i++) {
assertEq(i, large_cow_lit_array[i]);
}

View File

@ -21,8 +21,9 @@ assertOffsetColumns(
// getColumnOffsets correctly places object properties.
assertOffsetColumns(
// Should hit each property in the object.
"function f(n){var o={a:1,b:2,c:3}}",
// Should hit each property in the object if OBJLITERAL optimization is not
// hit.
"function f(n){var o={a:1,b:2,c:n}}",
" ^^ ^ ^ ^"
);