Bug 1730843 - Part 8 - Add support for spread in records r=jandem

This is done introducing a new opcode, `AddRecordSpread`, that
takes a value and defines its enumerable properties on the record
which is currently being initialized.

Differential Revision: https://phabricator.services.mozilla.com/D125652
This commit is contained in:
Nicolò Ribaudo 2021-12-20 15:17:20 +00:00
Родитель 6d81435bfb
Коммит db4024ebf5
9 изменённых файлов: 119 добавлений и 14 удалений

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

@ -272,6 +272,17 @@ inline JSObject* ToObject(JSContext* cx, HandleValue v) {
return js::ToObjectSlow(cx, v, false);
}
#ifdef ENABLE_RECORD_TUPLE
inline JSObject* ToObjectOrGetObjectPayload(JSContext* cx, HandleValue v) {
detail::AssertArgumentsAreSane(cx, v);
if (v.hasObjectPayload()) {
return &v.getObjectPayload();
}
return js::ToObjectSlow(cx, v, false);
}
#endif
/**
* Convert a double value to UnsignedInteger (an unsigned integral type) using
* ECMAScript-style semantics (that is, in like manner to how ECMAScript's

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

@ -1598,6 +1598,7 @@ static bool BytecodeIsEffectful(JSOp op) {
#ifdef ENABLE_RECORD_TUPLE
case JSOp::InitRecord:
case JSOp::AddRecordProperty:
case JSOp::AddRecordSpread:
case JSOp::FinishRecord:
case JSOp::InitTuple:
case JSOp::AddTupleElement:

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

@ -10493,8 +10493,14 @@ bool BytecodeEmitter::emitRecordLiteral(ListNode* record) {
for (ParseNode* propdef : record->contents()) {
if (propdef->isKind(ParseNodeKind::Spread)) {
MOZ_CRASH(
"Bytecode emitter for record spread hasn't been implemented yet");
if (!emitTree(propdef->as<UnaryNode>().kid())) {
// [stack] RECORD SPREADEE
return false;
}
if (!emit1(JSOp::AddRecordSpread)) {
// [stack] RECORD
return false;
}
} else {
BinaryNode* prop = &propdef->as<BinaryNode>();

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

@ -2598,6 +2598,7 @@ bool BaselineCodeGen<Handler>::emit_RegExp() {
UNSUPPORTED_OPCODE(InitRecord)
UNSUPPORTED_OPCODE(AddRecordProperty)
UNSUPPORTED_OPCODE(AddRecordSpread)
UNSUPPORTED_OPCODE(FinishRecord)
UNSUPPORTED_OPCODE(InitTuple)
UNSUPPORTED_OPCODE(AddTupleElement)

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

@ -58,6 +58,7 @@ namespace jit {
/* Records and Tuples */ \
IF_RECORD_TUPLE(_(InitRecord)) \
IF_RECORD_TUPLE(_(AddRecordProperty)) \
IF_RECORD_TUPLE(_(AddRecordSpread)) \
IF_RECORD_TUPLE(_(FinishRecord)) \
IF_RECORD_TUPLE(_(InitTuple)) \
IF_RECORD_TUPLE(_(AddTupleElement)) \

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

@ -1,4 +1,4 @@
// |reftest| skip-if(!this.hasOwnProperty("Tuple"))
// |reftest| skip-if(!this.hasOwnProperty("Record"))
let rec = #{ x: 1, "y": 2, 0: 3, 1n: 4, [`key${4}`]: 5 };
@ -8,9 +8,8 @@ assertEq(rec[0], 3);
assertEq(rec[1], 4);
assertEq(rec.key4, 5);
// TODO: Handle duplicated keys
// let dup = #{ x: 1, x: 2 };
// assertEq(dup.x, 2);
let dup = #{ x: 1, x: 2 };
assertEq(dup.x, 2);
assertThrowsInstanceOf(
() => #{ [Symbol()]: 1 },
@ -18,4 +17,25 @@ assertThrowsInstanceOf(
"Symbols cannot be used as record keys"
);
let rec2 = #{ x: 1, ...{ a: 2, z: 3 }, b: 4, ...{ d: 5 } };
assertEq(rec2.x, 1);
assertEq(rec2.a, 2);
assertEq(rec2.z, 3);
assertEq(rec2.b, 4);
assertEq(rec2.d, 5);
assertThrowsInstanceOf(
() => #{ ...{ [Symbol()]: 1 } },
TypeError,
"Symbols cannot be used as record keys"
);
let rec3 = #{
...Object.defineProperty({}, "x", { value: 1 }),
...Object.defineProperty({}, Symbol(), { value: 2 }),
};
assertEq(rec3.x, undefined);
let rec4 = #{ ...{}, ...{}, ...{} };
if (typeof reportCompare === "function") reportCompare(0, 0);

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

@ -284,6 +284,39 @@ static bool MaybeCreateThisForConstructor(JSContext* cx, const CallArgs& args) {
return JSFunction::getOrCreateScript(cx, callee);
}
#ifdef ENABLE_RECORD_TUPLE
static bool AddRecordSpreadOperation(JSContext* cx, HandleValue recHandle,
HandleValue spreadeeHandle) {
MOZ_ASSERT(recHandle.toExtendedPrimitive().is<RecordType>());
RecordType* rec = &recHandle.toExtendedPrimitive().as<RecordType>();
RootedObject obj(cx, ToObjectOrGetObjectPayload(cx, spreadeeHandle));
RootedIdVector keys(cx);
if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &keys)) {
return false;
}
size_t len = keys.length();
RootedId propKey(cx);
RootedValue propValue(cx);
for (size_t i = 0; i < len; i++) {
propKey.set(keys[i]);
// Step 4.c.ii.1.
if (MOZ_UNLIKELY(!GetProperty(cx, obj, obj, propKey, &propValue))) {
return false;
}
if (MOZ_UNLIKELY(!rec->initializeNextProperty(cx, propKey, propValue))) {
return false;
}
}
return true;
}
#endif
static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx,
RunState& state);
@ -4018,6 +4051,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_CALLER bool Interpret(JSContext* cx,
}
END_CASE(AddRecordProperty)
CASE(AddRecordSpread) {
MOZ_ASSERT(REGS.stackDepth() >= 2);
if (!AddRecordSpreadOperation(cx, REGS.stackHandleAt(-2),
REGS.stackHandleAt(-1))) {
goto error;
}
REGS.sp--;
}
END_CASE(AddRecordSpread)
CASE(FinishRecord) {
MOZ_ASSERT(REGS.stackDepth() >= 1);
RecordType* rec = &REGS.sp[-1].toExtendedPrimitive().as<RecordType>();

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

@ -1558,6 +1558,20 @@
* Stack: record, key, value => record
*/ \
IF_RECORD_TUPLE(MACRO(AddRecordProperty, add_record_property, NULL, 1, 3, 1, JOF_BYTE)) \
/*
* Add the last element in the stack to the preceding tuple.
*
* Implements: [RecordPropertyDefinitionEvaluation][1] for
* RecordPropertyDefinition : ... AssignmentExpression
*
* [1]: https://tc39.es/proposal-record-tuple/#sec-addpropertyintorecordentrieslist
*
* Category: Compound primitives
* Type: Record literals
* Operands:
* Stack: record, value => record
*/ \
IF_RECORD_TUPLE(MACRO(AddRecordSpread, add_record_spread, NULL, 1, 2, 1, JOF_BYTE)) \
/*
* Mark a record as "initialized", going from "write-only" mode to
* "read-only" mode.
@ -3594,7 +3608,7 @@
IF_RECORD_TUPLE(/* empty */, MACRO(231)) \
IF_RECORD_TUPLE(/* empty */, MACRO(232)) \
IF_RECORD_TUPLE(/* empty */, MACRO(233)) \
MACRO(234) \
IF_RECORD_TUPLE(/* empty */, MACRO(234)) \
MACRO(235) \
MACRO(236) \
MACRO(237) \

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

@ -69,8 +69,8 @@ RecordType* RecordType::createUninitialized(JSContext* cx,
rec->initFixedSlots(SLOT_COUNT);
RootedArrayObject sortedKeys(cx,
NewDensePartlyAllocatedArray(cx, initialLength));
if (!sortedKeys->ensureElements(cx, initialLength)) {
NewDenseFullyAllocatedArray(cx, initialLength));
if (!sortedKeys) {
return nullptr;
}
@ -125,7 +125,7 @@ bool RecordType::initializeNextProperty(JSContext* cx, HandleId key,
getFixedSlot(INITIALIZED_LENGTH_SLOT).toPrivateUint32();
ArrayObject* sortedKeys =
&getFixedSlot(SORTED_KEYS_SLOT).toObject().as<ArrayObject>();
MOZ_ASSERT(initializedLength < sortedKeys->length());
MOZ_ASSERT(initializedLength == sortedKeys->getDenseInitializedLength());
if (!sortedKeys->ensureElements(cx, initializedLength + 1)) {
return false;
@ -147,18 +147,17 @@ bool RecordType::finishInitialization(JSContext* cx) {
if (!ObjectElements::FreezeOrSeal(cx, obj, IntegrityLevel::Frozen)) {
return false;
}
if (getFixedSlot(INITIALIZED_LENGTH_SLOT).toPrivateUint32() == 0) {
return true;
}
uint32_t length = getFixedSlot(INITIALIZED_LENGTH_SLOT).toPrivateUint32();
ArrayObject& sortedKeys =
getFixedSlot(SORTED_KEYS_SLOT).toObject().as<ArrayObject>();
MOZ_ASSERT(length == sortedKeys.getDenseInitializedLength());
Rooted<JSLinearString*> tmpKey(cx);
// Sort the keys. This is insertion sort - O(n^2) - but it's ok for now
// becase records are probably not too big anyway.
for (uint32_t i = 1, j; i < sortedKeys.length(); i++) {
for (uint32_t i = 1, j; i < length; i++) {
#define KEY(index) sortedKeys.getDenseElement(index)
#define KEY_S(index) &KEY(index).toString()->asLinear()
@ -177,6 +176,14 @@ bool RecordType::finishInitialization(JSContext* cx) {
#undef KEY_S
}
// We preallocate 1 element for each object spread. If spreads end up
// introducing zero elements, we can then shrink the sordedKeys array.
sortedKeys.setDenseInitializedLength(length);
sortedKeys.setLength(length);
sortedKeys.setNonWritableLength(cx);
MOZ_ASSERT(sortedKeys.length() == length);
return true;
}