Commit 48efe388 authored by Michael Hablich's avatar Michael Hablich Committed by Commit Bot

Revert "[map] Support in-place field representation changes."

This reverts commit 1416d5a5.

Reason for revert: blocks roll https://chromium-review.googlesource.com/c/chromium/src/+/1564550

Original change's description:
> [map] Support in-place field representation changes.
> 
> This adds a new flag --modify-field-representation-inplace (enabled by
> default), which lets the runtime perform field representation changes
> for Smi to Tagged or for HeapObject to Tagged in-place instead of
> creating new maps and marking the previous map tree as deprecated.
> 
> That means we create (a lot) fewer Maps and DescriptorArrays in the
> beginning and also need to self-heal fewer objects later (migrating
> off the deprecated maps). In TurboFan we just take the "field owner
> dependency" whenever we use the field representation, which is very
> similar to what we already do for the field types. That means if we
> change the representation of a field that we used in optimized code,
> we will simply deoptimize that code and have TurboFan potentially
> later optimize it again with the new field representation.
> 
> On the Speedometer2/ElmJS-TodoMVC test, this reduces the total execution
> time from around 415ms to around 352ms, which corresponds to a **15%**
> improvement. The overall Speedometer2 score improves from around 74.1
> to around 78.3 (on local runs with content_shell), corresponding to a
> **5.6%** improvement here. 🎉
> 
> On the CNN desktop browsing story, it seems that we reduce map space
> utilization/fragmentation by about 4-5%. But since we allocate a lot
> less (fewer Maps and DescriptorArrays) we also significantly change
> the GC timing, which heavily influences the results here. So take this
> with a grain of salt. 🤷‍♂️
> 
> Note: For Double fields, this doesn't change anything, meaning they
> still create new maps and deprecate the previous map trees.
> 
> Bug: v8:8749, v8:8865, v8:9114
> Change-Id: I694a53f87ae5caeb868fd98a21809b66d4297d35
> Cq-Include-Trybots: luci.chromium.try:linux-blink-rel
> Doc: http://bit.ly/v8-in-place-field-representation-changes
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1561132
> Commit-Queue: Benedikt Meurer <bmeurer@chromium.org>
> Reviewed-by: Igor Sheludko <ishell@chromium.org>
> Reviewed-by: Jaroslav Sevcik <jarin@chromium.org>
> Reviewed-by: Georg Neis <neis@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#60764}

TBR=jarin@chromium.org,neis@chromium.org,ishell@chromium.org,bmeurer@chromium.org,verwaest@chromium.org

# Not skipping CQ checks because original CL landed > 1 day ago.

Bug: v8:8749, v8:8865, v8:9114
Change-Id: I666975d08d51bbe7ab4faec9428b9a1f88e9b322
Cq-Include-Trybots: luci.chromium.try:linux-blink-rel
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1564208Reviewed-by: 's avatarMichael Hablich <hablich@chromium.org>
Commit-Queue: Michael Hablich <hablich@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60807}
parent e4b5fceb
......@@ -319,12 +319,9 @@ bool AccessInfoFactory::ComputeDataFieldAccessInfo(
Type field_type = Type::NonInternal();
MachineRepresentation field_representation = MachineRepresentation::kTagged;
MaybeHandle<Map> field_map;
MapRef map_ref(broker(), map);
if (details_representation.IsSmi()) {
field_type = Type::SignedSmall();
field_representation = MachineRepresentation::kTaggedSigned;
map_ref.SerializeOwnDescriptors(); // TODO(neis): Remove later.
dependencies()->DependOnFieldRepresentation(map_ref, number);
} else if (details_representation.IsDouble()) {
field_type = type_cache_->kFloat64;
field_representation = MachineRepresentation::kFloat64;
......@@ -340,10 +337,9 @@ bool AccessInfoFactory::ComputeDataFieldAccessInfo(
// The field type was cleared by the GC, so we don't know anything
// about the contents now.
}
map_ref.SerializeOwnDescriptors(); // TODO(neis): Remove later.
dependencies()->DependOnFieldRepresentation(map_ref, number);
if (descriptors_field_type->IsClass()) {
} else if (descriptors_field_type->IsClass()) {
MapRef map_ref(broker(), map);
map_ref.SerializeOwnDescriptors(); // TODO(neis): Remove later.
dependencies()->DependOnFieldType(map_ref, number);
// Remember the field map, and try to infer a useful type.
Handle<Map> map(descriptors_field_type->AsClass(), isolate());
......@@ -703,12 +699,9 @@ bool AccessInfoFactory::LookupTransition(
Type field_type = Type::NonInternal();
MaybeHandle<Map> field_map;
MachineRepresentation field_representation = MachineRepresentation::kTagged;
MapRef transition_map_ref(broker(), transition_map);
if (details_representation.IsSmi()) {
field_type = Type::SignedSmall();
field_representation = MachineRepresentation::kTaggedSigned;
transition_map_ref.SerializeOwnDescriptors(); // TODO(neis): Remove later.
dependencies()->DependOnFieldRepresentation(transition_map_ref, number);
} else if (details_representation.IsDouble()) {
field_type = type_cache_->kFloat64;
field_representation = MachineRepresentation::kFloat64;
......@@ -722,10 +715,10 @@ bool AccessInfoFactory::LookupTransition(
if (descriptors_field_type->IsNone()) {
// Store is not safe if the field type was cleared.
return false;
}
transition_map_ref.SerializeOwnDescriptors(); // TODO(neis): Remove later.
dependencies()->DependOnFieldRepresentation(transition_map_ref, number);
if (descriptors_field_type->IsClass()) {
} else if (descriptors_field_type->IsClass()) {
MapRef transition_map_ref(broker(), transition_map);
transition_map_ref
.SerializeOwnDescriptors(); // TODO(neis): Remove later.
dependencies()->DependOnFieldType(transition_map_ref, number);
// Remember the field map, and try to infer a useful type.
Handle<Map> map(descriptors_field_type->AsClass(), isolate());
......
......@@ -163,41 +163,6 @@ class PretenureModeDependency final
AllocationType allocation_;
};
class FieldRepresentationDependency final
: public CompilationDependencies::Dependency {
public:
// TODO(neis): Once the concurrent compiler frontend is always-on, we no
// longer need to explicitly store the representation.
FieldRepresentationDependency(const MapRef& owner, int descriptor,
Representation representation)
: owner_(owner),
descriptor_(descriptor),
representation_(representation) {
DCHECK(owner_.equals(owner_.FindFieldOwner(descriptor_)));
DCHECK(representation_.Equals(
owner_.GetPropertyDetails(descriptor_).representation()));
}
bool IsValid() const override {
DisallowHeapAllocation no_heap_allocation;
Handle<Map> owner = owner_.object();
return representation_.Equals(owner->instance_descriptors()
->GetDetails(descriptor_)
.representation());
}
void Install(const MaybeObjectHandle& code) override {
SLOW_DCHECK(IsValid());
DependentCode::InstallDependency(owner_.isolate(), code, owner_.object(),
DependentCode::kFieldOwnerGroup);
}
private:
MapRef owner_;
int descriptor_;
Representation representation_;
};
class FieldTypeDependency final : public CompilationDependencies::Dependency {
public:
// TODO(neis): Once the concurrent compiler frontend is always-on, we no
......@@ -445,16 +410,6 @@ PropertyConstness CompilationDependencies::DependOnFieldConstness(
return PropertyConstness::kConst;
}
void CompilationDependencies::DependOnFieldRepresentation(const MapRef& map,
int descriptor) {
MapRef owner = map.FindFieldOwner(descriptor);
PropertyDetails details = owner.GetPropertyDetails(descriptor);
DCHECK(details.representation().Equals(
map.GetPropertyDetails(descriptor).representation()));
dependencies_.push_front(new (zone_) FieldRepresentationDependency(
owner, descriptor, details.representation()));
}
void CompilationDependencies::DependOnFieldType(const MapRef& map,
int descriptor) {
MapRef owner = map.FindFieldOwner(descriptor);
......
......@@ -51,10 +51,6 @@ class V8_EXPORT_PRIVATE CompilationDependencies : public ZoneObject {
// not change.
AllocationType DependOnPretenureMode(const AllocationSiteRef& site);
// Record the assumption that the field representation of a field does not
// change. The field is identified by the arguments.
void DependOnFieldRepresentation(const MapRef& map, int descriptor);
// Record the assumption that the field type of a field does not change. The
// field is identified by the arguments.
void DependOnFieldType(const MapRef& map, int descriptor);
......
......@@ -1040,8 +1040,6 @@ DEFINE_BOOL_READONLY(track_constant_fields, true,
"enable constant field tracking")
DEFINE_BOOL_READONLY(fast_map_update, false,
"enable fast map update by caching the migration target")
DEFINE_BOOL(modify_field_representation_inplace, true,
"enable in-place field representation updates")
DEFINE_INT(max_polymorphic_map_count, 4,
"maximum number of maps to track in POLYMORPHIC state")
......
......@@ -211,9 +211,6 @@ MapUpdater::State MapUpdater::CopyGeneralizeAllFields(const char* reason) {
}
MapUpdater::State MapUpdater::TryReconfigureToDataFieldInplace() {
// Updating deprecated maps in-place doesn't make sense.
if (old_map_->is_deprecated()) return state_;
// If it's just a representation generalization case (i.e. property kind and
// attributes stays unchanged) it's fine to transition from None to anything
// but double without any modification to the object, because the default
......@@ -226,7 +223,7 @@ MapUpdater::State MapUpdater::TryReconfigureToDataFieldInplace() {
PropertyDetails old_details =
old_descriptors_->GetDetails(modified_descriptor_);
Representation old_representation = old_details.representation();
if (!old_representation.CanBeInPlaceChangedTo(new_representation_)) {
if (!old_representation.IsNone()) {
return state_; // Not done yet.
}
......
......@@ -748,14 +748,12 @@ void Map::UpdateFieldType(Isolate* isolate, int descriptor, Handle<Name> name,
DescriptorArray descriptors = current->instance_descriptors();
PropertyDetails details = descriptors->GetDetails(descriptor);
// It is allowed to change representation here only from None
// to something or from Smi or HeapObject to Tagged.
// It is allowed to change representation here only from None to something.
DCHECK(details.representation().Equals(new_representation) ||
details.representation().CanBeInPlaceChangedTo(new_representation));
details.representation().IsNone());
// Skip if already updated the shared descriptor.
if (new_constness != details.constness() ||
!new_representation.Equals(details.representation()) ||
descriptors->GetFieldType(descriptor) != *new_wrapped_type.object()) {
DCHECK_IMPLIES(!FLAG_track_constant_fields,
new_constness == PropertyConstness::kMutable);
......
......@@ -116,12 +116,6 @@ class Representation {
return Equals(other);
}
bool CanBeInPlaceChangedTo(const Representation& other) const {
if (IsNone()) return true;
if (!FLAG_modify_field_representation_inplace) return false;
return (IsSmi() || IsHeapObject()) && other.IsTagged();
}
bool is_more_general_than(const Representation& other) const {
if (IsHeapObject()) return other.IsNone();
return kind_ > other.kind_;
......
......@@ -646,7 +646,7 @@ void TestGeneralizeField(int detach_property_at_index, int property_index,
from.representation, from.type);
} else {
map = expectations.AddDataField(map, NONE, kDefaultFieldConstness,
Representation::Double(), any_type);
Representation::Smi(), any_type);
if (i == detach_property_at_index) {
detach_point_map = map;
}
......@@ -801,9 +801,7 @@ TEST(GeneralizeSmiFieldToTagged) {
TestGeneralizeField(
{PropertyConstness::kMutable, Representation::Smi(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type},
!FLAG_modify_field_representation_inplace,
FLAG_modify_field_representation_inplace);
{PropertyConstness::kMutable, Representation::Tagged(), any_type});
}
TEST(GeneralizeDoubleFieldToTagged) {
......@@ -833,9 +831,7 @@ TEST(GeneralizeHeapObjectFieldToTagged) {
TestGeneralizeField(
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Smi(), any_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type},
!FLAG_modify_field_representation_inplace,
FLAG_modify_field_representation_inplace);
{PropertyConstness::kMutable, Representation::Tagged(), any_type});
}
TEST(GeneralizeHeapObjectFieldToHeapObject) {
......@@ -2256,9 +2252,10 @@ TEST(ReconfigurePropertySplitMapTransitionsOverflow) {
// IS_PROTO_TRANS_ISSUE_FIXED and IS_NON_EQUIVALENT_TRANSITION_SUPPORTED are
// fixed.
template <typename TestConfig>
static void TestGeneralizeFieldWithSpecialTransition(
TestConfig& config, const CRFTData& from, const CRFTData& to,
const CRFTData& expected, bool expected_deprecation) {
static void TestGeneralizeFieldWithSpecialTransition(TestConfig& config,
const CRFTData& from,
const CRFTData& to,
const CRFTData& expected) {
Isolate* isolate = CcTest::i_isolate();
Expectations expectations(isolate);
......@@ -2304,46 +2301,39 @@ static void TestGeneralizeFieldWithSpecialTransition(
expectations.SetDataField(i, expected.constness, expected.representation,
expected.type);
if (expected_deprecation) {
CHECK(map->is_deprecated());
CHECK_NE(*map, *new_map);
CHECK(i == 0 || maps[i - 1]->is_deprecated());
CHECK(expectations.Check(*new_map));
Handle<Map> new_map2 = Map::Update(isolate, map2);
CHECK(!new_map2->is_deprecated());
CHECK(!new_map2->is_dictionary_map());
Handle<Map> tmp_map;
if (Map::TryUpdate(isolate, map2).ToHandle(&tmp_map)) {
// If Map::TryUpdate() manages to succeed the result must match the
// result of Map::Update().
CHECK_EQ(*new_map2, *tmp_map);
} else {
// Equivalent transitions should always find the updated map.
CHECK(config.is_non_equivalent_transition());
}
CHECK(map->is_deprecated());
CHECK_NE(*map, *new_map);
CHECK(i == 0 || maps[i - 1]->is_deprecated());
CHECK(expectations.Check(*new_map));
if (config.is_non_equivalent_transition()) {
// In case of non-equivalent transition currently we generalize all
// representations.
for (int i = 0; i < kPropCount; i++) {
expectations2.GeneralizeField(i);
}
CHECK(new_map2->GetBackPointer()->IsUndefined(isolate));
CHECK(expectations2.Check(*new_map2));
} else {
expectations2.SetDataField(i, expected.constness,
expected.representation, expected.type);
Handle<Map> new_map2 = Map::Update(isolate, map2);
CHECK(!new_map2->is_deprecated());
CHECK(!new_map2->is_dictionary_map());
Handle<Map> tmp_map;
if (Map::TryUpdate(isolate, map2).ToHandle(&tmp_map)) {
// If Map::TryUpdate() manages to succeed the result must match the result
// of Map::Update().
CHECK_EQ(*new_map2, *tmp_map);
} else {
// Equivalent transitions should always find the updated map.
CHECK(config.is_non_equivalent_transition());
}
CHECK(!new_map2->GetBackPointer()->IsUndefined(isolate));
CHECK(expectations2.Check(*new_map2));
if (config.is_non_equivalent_transition()) {
// In case of non-equivalent transition currently we generalize all
// representations.
for (int i = 0; i < kPropCount; i++) {
expectations2.GeneralizeField(i);
}
CHECK(new_map2->GetBackPointer()->IsUndefined(isolate));
CHECK(expectations2.Check(*new_map2));
} else {
CHECK(!map->is_deprecated());
// TODO(ishell): Update test expectations properly.
// CHECK_EQ(*map2, *new_map);
// CHECK(expectations2.Check(*new_map));
expectations2.SetDataField(i, expected.constness, expected.representation,
expected.type);
CHECK(!new_map2->GetBackPointer()->IsUndefined(isolate));
CHECK(expectations2.Check(*new_map2));
}
}
......@@ -2361,6 +2351,7 @@ static void TestGeneralizeFieldWithSpecialTransition(
}
}
TEST(ElementsKindTransitionFromMapOwningDescriptor) {
CcTest::InitializeVM();
v8::HandleScope scope(CcTest::isolate());
......@@ -2396,14 +2387,7 @@ TEST(ElementsKindTransitionFromMapOwningDescriptor) {
configs[i],
{PropertyConstness::kMutable, Representation::Smi(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type},
!FLAG_modify_field_representation_inplace);
TestGeneralizeFieldWithSpecialTransition(
configs[i],
{PropertyConstness::kMutable, Representation::Double(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type},
true);
{PropertyConstness::kMutable, Representation::Tagged(), any_type});
}
}
......@@ -2455,14 +2439,7 @@ TEST(ElementsKindTransitionFromMapNotOwningDescriptor) {
configs[i],
{PropertyConstness::kMutable, Representation::Smi(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type},
!FLAG_modify_field_representation_inplace);
TestGeneralizeFieldWithSpecialTransition(
configs[i],
{PropertyConstness::kMutable, Representation::Double(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type},
true);
{PropertyConstness::kMutable, Representation::Tagged(), any_type});
}
}
......@@ -2498,12 +2475,7 @@ TEST(PrototypeTransitionFromMapOwningDescriptor) {
TestGeneralizeFieldWithSpecialTransition(
config, {PropertyConstness::kMutable, Representation::Smi(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type},
!FLAG_modify_field_representation_inplace);
TestGeneralizeFieldWithSpecialTransition(
config, {PropertyConstness::kMutable, Representation::Double(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type}, true);
{PropertyConstness::kMutable, Representation::Tagged(), any_type});
}
......@@ -2550,12 +2522,7 @@ TEST(PrototypeTransitionFromMapNotOwningDescriptor) {
TestGeneralizeFieldWithSpecialTransition(
config, {PropertyConstness::kMutable, Representation::Smi(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type},
!FLAG_modify_field_representation_inplace);
TestGeneralizeFieldWithSpecialTransition(
config, {PropertyConstness::kMutable, Representation::Double(), any_type},
{PropertyConstness::kMutable, Representation::HeapObject(), value_type},
{PropertyConstness::kMutable, Representation::Tagged(), any_type}, true);
{PropertyConstness::kMutable, Representation::Tagged(), any_type});
}
......@@ -2844,17 +2811,12 @@ TEST(TransitionDataConstantToDataField) {
Handle<Object> value2 = isolate->factory()->NewHeapNumber(0);
TransitionToDataFieldOperator transition_op2(
PropertyConstness::kMutable, Representation::Tagged(), any_type, value2);
PropertyConstness::kMutable, Representation::Double(), any_type, value2);
if (FLAG_track_constant_fields && FLAG_modify_field_representation_inplace) {
SameMapChecker checker;
TestTransitionTo(transition_op1, transition_op2, checker);
} else {
FieldGeneralizationChecker checker(kPropCount - 1,
PropertyConstness::kMutable,
Representation::Tagged(), any_type);
TestTransitionTo(transition_op1, transition_op2, checker);
}
FieldGeneralizationChecker checker(kPropCount - 1,
PropertyConstness::kMutable,
Representation::Tagged(), any_type);
TestTransitionTo(transition_op1, transition_op2, checker);
}
......
......@@ -2948,8 +2948,7 @@ TEST(WeakContainers) {
CHECK_NE(0, count);
for (int i = 0; i < count; ++i) {
const v8::HeapGraphEdge* prop = dependent_code->GetChild(i);
CHECK(prop->GetType() == v8::HeapGraphEdge::kInternal ||
prop->GetType() == v8::HeapGraphEdge::kWeak);
CHECK_EQ(v8::HeapGraphEdge::kInternal, prop->GetType());
}
}
......
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --modify-field-representation-inplace
// Flags: --no-always-opt --opt
// Test that code embedding accesses to a Smi field gets properly
// deoptimized if s->t field representation changes are done in-place.
(function() {
function O(x) { this.x = x; }
function foo(o) { return o.x; }
%PrepareFunctionForOptimization(foo);
foo(new O(1));
foo(new O(2));
%OptimizeFunctionOnNextCall(foo);
foo(new O(3));
assertOptimized(foo);
new O(null);
assertUnoptimized(foo);
})();
// Test that code embedding assignments to a Smi field gets properly
// deoptimized if s->t field representation changes are done in-place.
(function() {
function O(x) { this.x = x; }
function foo(o) { o.x = 0; }
%PrepareFunctionForOptimization(foo);
foo(new O(1));
foo(new O(2));
%OptimizeFunctionOnNextCall(foo);
foo(new O(3));
assertOptimized(foo);
new O(null);
assertUnoptimized(foo);
})();
// Test that code embedding accesses to a HeapObject field gets properly
// deoptimized if h->t field representation changes are done in-place.
(function() {
function O(x) { this.x = x; }
function foo(o) { return o.x; }
%PrepareFunctionForOptimization(foo);
foo(new O(null));
foo(new O("Hello"));
%OptimizeFunctionOnNextCall(foo);
foo(new O({}));
assertOptimized(foo);
new O(1);
assertUnoptimized(foo);
})();
// Test that code embedding assignments to a Smi field gets properly
// deoptimized if s->t field representation changes are done in-place.
(function() {
function O(x) { this.x = x; }
function foo(o) { o.x = true; }
%PrepareFunctionForOptimization(foo);
foo(new O(null));
foo(new O("Hello"));
%OptimizeFunctionOnNextCall(foo);
foo(new O({}));
assertOptimized(foo);
new O(1);
assertUnoptimized(foo);
})();
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax --modify-field-representation-inplace
// Test that s->t field representation changes are done in-place.
(function() {
function O(x) { this.x = x; }
const a = new O(42);
const b = new O(-8);
assertTrue(%HaveSameMap(a, b));
a.x = null;
assertTrue(%HaveSameMap(a, b));
b.x = null;
assertTrue(%HaveSameMap(a, b));
})();
// Test that h->t field representation changes are done in-place.
(function() {
function O(x) { this.x = x; }
const a = new O(null);
const b = new O("Hello");
assertTrue(%HaveSameMap(a, b));
a.x = 1;
assertTrue(%HaveSameMap(a, b));
b.x = 2;
assertTrue(%HaveSameMap(a, b));
})();
......@@ -148,11 +148,11 @@
var f2 = new Narf(2);
var f3 = new Narf(3);
function baz(f, y) { f.y = y; }
baz(f1, {b: 9});
baz(f2, {b: 9});
baz(f2, {b: 9});
baz(f1, {y: 9});
baz(f2, {y: 9});
baz(f2, {y: 9});
%OptimizeFunctionOnNextCall(baz);
baz(f2, {b: 9});
baz(f2, {y: 9});
baz(f3, {a: -1});
assertUnoptimized(baz);
})();
......
......@@ -26,7 +26,6 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --track-fields --track-double-fields --allow-natives-syntax
// Flags: --modify-field-representation-inplace
// Test transitions caused by changes to field representations.
......@@ -96,7 +95,7 @@ o6.b = 1.5;
assertFalse(%HaveSameMap(o6, o7));
// Smi, double, object.
o7.c = {};
assertTrue(%HaveSameMap(o6, o7));
assertFalse(%HaveSameMap(o6, o7));
// Smi, double, object.
o6.c = {};
assertTrue(%HaveSameMap(o6, o7));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment