diff --git a/src/code-stub-assembler.cc b/src/code-stub-assembler.cc index a49ae86e52..3bcd6f1c28 100644 --- a/src/code-stub-assembler.cc +++ b/src/code-stub-assembler.cc @@ -3085,6 +3085,24 @@ TNode CodeStubAssembler::AllocateMutableHeapNumber() { return UncheckedCast(result); } +TNode CodeStubAssembler::CloneIfMutablePrimitive(TNode object) { + TVARIABLE(Object, result, object); + Label done(this); + + GotoIf(TaggedIsSmi(object), &done); + GotoIfNot(IsMutableHeapNumber(UncheckedCast(object)), &done); + { + // Mutable heap number found --- allocate a clone. + TNode value = + LoadHeapNumberValue(UncheckedCast(object)); + result = AllocateMutableHeapNumberWithValue(value); + Goto(&done); + } + + BIND(&done); + return result.value(); +} + TNode CodeStubAssembler::AllocateMutableHeapNumberWithValue( SloppyTNode value) { TNode result = AllocateMutableHeapNumber(); @@ -5027,7 +5045,8 @@ void CodeStubAssembler::CopyPropertyArrayValues(Node* from_array, Node* to_array, Node* property_count, WriteBarrierMode barrier_mode, - ParameterMode mode) { + ParameterMode mode, + DestroySource destroy_source) { CSA_SLOW_ASSERT(this, MatchesParameterMode(property_count, mode)); CSA_SLOW_ASSERT(this, Word32Or(IsPropertyArray(from_array), IsEmptyFixedArray(from_array))); @@ -5039,9 +5058,14 @@ void CodeStubAssembler::CopyPropertyArrayValues(Node* from_array, ElementsKind kind = PACKED_ELEMENTS; BuildFastFixedArrayForEach( from_array, kind, start, property_count, - [this, to_array, needs_write_barrier](Node* array, Node* offset) { + [this, to_array, needs_write_barrier, destroy_source](Node* array, + Node* offset) { Node* value = Load(MachineType::AnyTagged(), array, offset); + if (destroy_source == DestroySource::kNo) { + value = CloneIfMutablePrimitive(CAST(value)); + } + if (needs_write_barrier) { Store(to_array, offset, value); } else { @@ -5050,6 +5074,18 @@ void CodeStubAssembler::CopyPropertyArrayValues(Node* from_array, } }, mode); + +#ifdef DEBUG + // Zap {from_array} if the copying above has made it invalid. + if (destroy_source == DestroySource::kYes) { + Label did_zap(this); + GotoIf(IsEmptyFixedArray(from_array), &did_zap); + FillPropertyArrayWithUndefined(from_array, start, property_count, mode); + + Goto(&did_zap); + BIND(&did_zap); + } +#endif Comment("] CopyPropertyArrayValues"); } diff --git a/src/code-stub-assembler.h b/src/code-stub-assembler.h index 767d3969fa..e6f64ff4fb 100644 --- a/src/code-stub-assembler.h +++ b/src/code-stub-assembler.h @@ -1589,10 +1589,19 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { Node* to_index, ParameterMode mode = INTPTR_PARAMETERS); - void CopyPropertyArrayValues( - Node* from_array, Node* to_array, Node* length, - WriteBarrierMode barrier_mode = UPDATE_WRITE_BARRIER, - ParameterMode mode = INTPTR_PARAMETERS); + enum class DestroySource { kNo, kYes }; + + // Specify DestroySource::kYes if {from_array} is being supplanted by + // {to_array}. This offers a slight performance benefit by simply copying the + // array word by word. The source may be destroyed at the end of this macro. + // + // Otherwise, specify DestroySource::kNo for operations where an Object is + // being cloned, to ensure that MutableHeapNumbers are unique between the + // source and cloned object. + void CopyPropertyArrayValues(Node* from_array, Node* to_array, Node* length, + WriteBarrierMode barrier_mode, + ParameterMode mode, + DestroySource destroy_source); // Copies all elements from |from_array| of |length| size to // |to_array| of the same size respecting the elements kind. @@ -3213,6 +3222,10 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler { TNode ArrayCreate(TNode context, TNode length); + // Allocate a clone of a mutable primitive, if {object} is a + // MutableHeapNumber. + TNode CloneIfMutablePrimitive(TNode object); + private: friend class CodeStubArguments; diff --git a/src/ic/accessor-assembler.cc b/src/ic/accessor-assembler.cc index 2c8684fe2c..b677ab31c5 100644 --- a/src/ic/accessor-assembler.cc +++ b/src/ic/accessor-assembler.cc @@ -1684,7 +1684,8 @@ Node* AccessorAssembler::ExtendPropertiesBackingStore(Node* object, // |new_properties| is guaranteed to be in new space, so we can skip // the write barrier. CopyPropertyArrayValues(var_properties.value(), new_properties, - var_length.value(), SKIP_WRITE_BARRIER, mode); + var_length.value(), SKIP_WRITE_BARRIER, mode, + DestroySource::kYes); // TODO(gsathya): Clean up the type conversions by creating smarter // helpers that do the correct op based on the mode. @@ -3620,7 +3621,7 @@ void AccessorAssembler::GenerateCloneObjectIC() { auto mode = INTPTR_PARAMETERS; var_properties = CAST(AllocatePropertyArray(length, mode)); CopyPropertyArrayValues(source_properties, var_properties.value(), length, - SKIP_WRITE_BARRIER, mode); + SKIP_WRITE_BARRIER, mode, DestroySource::kNo); } Goto(&allocate_object); @@ -3640,7 +3641,8 @@ void AccessorAssembler::GenerateCloneObjectIC() { BuildFastLoop(source_start, source_size, [=](Node* field_index) { Node* field_offset = TimesPointerSize(field_index); - Node* field = LoadObjectField(source, field_offset); + TNode field = LoadObjectField(source, field_offset); + field = CloneIfMutablePrimitive(field); Node* result_offset = IntPtrAdd(field_offset, field_offset_difference); StoreObjectFieldNoWriteBarrier(object, result_offset, diff --git a/test/mjsunit/es9/object-spread-ic.js b/test/mjsunit/es9/object-spread-ic.js index d76ffd4eb8..55d60f2cf8 100644 --- a/test/mjsunit/es9/object-spread-ic.js +++ b/test/mjsunit/es9/object-spread-ic.js @@ -99,3 +99,25 @@ // Megamorphic assertEquals({ boop: 1 }, f({ boop: 1 })); })(); + +// There are 2 paths in CloneObjectIC's handler which need to handle double +// fields specially --- in object properties, and copying the property array. +function testMutableInlineProperties() { + function inobject() { "use strict"; this.x = 1.1; } + const src = new inobject(); + const x0 = src.x; + const clone = { ...src, x: x0 + 1 }; + assertEquals(x0, src.x); + assertEquals({ x: 2.1 }, clone); +} +testMutableInlineProperties() + +function testMutableOutOfLineProperties() { + const src = { a: 1, b: 2, c: 3 }; + src.x = 2.3; + const x0 = src.x; + const clone = { ...src, x: x0 + 1 }; + assertEquals(x0, src.x); + assertEquals({ a: 1, b: 2, c: 3, x: 3.3 }, clone); +} +testMutableOutOfLineProperties();