зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
6d81435bfb
Коммит
db4024ebf5
|
@ -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 = ®S.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;
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче