Commit 4fa7ae1c authored by adamk's avatar adamk Committed by Commit bot

Optimize Object.seal and Object.preventExtensions

They both now run fast (due to utilizing transitions instead of always
creating new maps) and sealed or non-extensible objects can stay in
fast mode after transitioning.

This almost entirely reuses the code for transitioning objects
frozen by Object.freeze(), with the added benefit of freeing
up a bit on the map (we no longer keep track of frozen-ness,
as that bit wasn't used for anything interesting).

BUG=v8:3662,chromium:115960
LOG=y

Review URL: https://codereview.chromium.org/776143005

Cr-Commit-Position: refs/heads/master@{#25759}
parent 8877f156
...@@ -344,6 +344,7 @@ class Smi; ...@@ -344,6 +344,7 @@ class Smi;
template <typename Config, class Allocator = FreeStoreAllocationPolicy> template <typename Config, class Allocator = FreeStoreAllocationPolicy>
class SplayTree; class SplayTree;
class String; class String;
class Symbol;
class Name; class Name;
class Struct; class Struct;
class Symbol; class Symbol;
......
...@@ -279,6 +279,8 @@ namespace internal { ...@@ -279,6 +279,8 @@ namespace internal {
V(RegExp_string, "RegExp") V(RegExp_string, "RegExp")
#define PRIVATE_SYMBOL_LIST(V) \ #define PRIVATE_SYMBOL_LIST(V) \
V(nonextensible_symbol) \
V(sealed_symbol) \
V(frozen_symbol) \ V(frozen_symbol) \
V(nonexistent_symbol) \ V(nonexistent_symbol) \
V(elements_transition_symbol) \ V(elements_transition_symbol) \
......
...@@ -4637,16 +4637,6 @@ void Map::set_counter(int value) { ...@@ -4637,16 +4637,6 @@ void Map::set_counter(int value) {
int Map::counter() { return Counter::decode(bit_field3()); } int Map::counter() { return Counter::decode(bit_field3()); }
void Map::freeze() {
set_bit_field3(IsFrozen::update(bit_field3(), true));
}
bool Map::is_frozen() {
return IsFrozen::decode(bit_field3());
}
void Map::mark_unstable() { void Map::mark_unstable() {
set_bit_field3(IsUnstable::update(bit_field3(), true)); set_bit_field3(IsUnstable::update(bit_field3(), true));
} }
......
...@@ -416,11 +416,7 @@ void Map::MapPrint(std::ostream& os) { // NOLINT ...@@ -416,11 +416,7 @@ void Map::MapPrint(std::ostream& os) { // NOLINT
if (is_undetectable()) os << " - undetectable\n"; if (is_undetectable()) os << " - undetectable\n";
if (has_instance_call_handler()) os << " - instance_call_handler\n"; if (has_instance_call_handler()) os << " - instance_call_handler\n";
if (is_access_check_needed()) os << " - access_check_needed\n"; if (is_access_check_needed()) os << " - access_check_needed\n";
if (is_frozen()) { if (!is_extensible()) os << " - non-extensible\n";
os << " - frozen\n";
} else if (!is_extensible()) {
os << " - sealed\n";
}
os << " - back pointer: " << Brief(GetBackPointer()); os << " - back pointer: " << Brief(GetBackPointer());
os << "\n - instance descriptors " << (owns_descriptors() ? "(own) " : "") os << "\n - instance descriptors " << (owns_descriptors() ? "(own) " : "")
<< "#" << NumberOfOwnDescriptors() << ": " << "#" << NumberOfOwnDescriptors() << ": "
...@@ -1179,7 +1175,11 @@ void TransitionArray::PrintTransitions(std::ostream& os, ...@@ -1179,7 +1175,11 @@ void TransitionArray::PrintTransitions(std::ostream& os,
key->ShortPrint(os); key->ShortPrint(os);
#endif #endif
os << ": "; os << ": ";
if (key == GetHeap()->frozen_symbol()) { if (key == GetHeap()->nonextensible_symbol()) {
os << " (transition to non-extensible)";
} else if (key == GetHeap()->sealed_symbol()) {
os << " (transition to sealed)";
} else if (key == GetHeap()->frozen_symbol()) {
os << " (transition to frozen)"; os << " (transition to frozen)";
} else if (key == GetHeap()->elements_transition_symbol()) { } else if (key == GetHeap()->elements_transition_symbol()) {
os << " (transition to " << ElementsKindToString(target->elements_kind()) os << " (transition to " << ElementsKindToString(target->elements_kind())
......
This diff is collapsed.
...@@ -2115,6 +2115,9 @@ class JSObject: public JSReceiver { ...@@ -2115,6 +2115,9 @@ class JSObject: public JSReceiver {
MUST_USE_RESULT static MaybeHandle<Object> PreventExtensions( MUST_USE_RESULT static MaybeHandle<Object> PreventExtensions(
Handle<JSObject> object); Handle<JSObject> object);
// ES5 Object.seal
MUST_USE_RESULT static MaybeHandle<Object> Seal(Handle<JSObject> object);
// ES5 Object.freeze // ES5 Object.freeze
MUST_USE_RESULT static MaybeHandle<Object> Freeze(Handle<JSObject> object); MUST_USE_RESULT static MaybeHandle<Object> Freeze(Handle<JSObject> object);
...@@ -2404,6 +2407,15 @@ class JSObject: public JSReceiver { ...@@ -2404,6 +2407,15 @@ class JSObject: public JSReceiver {
static Handle<Smi> GetOrCreateIdentityHash(Handle<JSObject> object); static Handle<Smi> GetOrCreateIdentityHash(Handle<JSObject> object);
static Handle<SeededNumberDictionary> GetNormalizedElementDictionary(
Handle<JSObject> object);
// Helper for fast versions of preventExtensions, seal, and freeze.
// attrs is one of NONE, SEALED, or FROZEN (depending on the operation).
template <PropertyAttributes attrs>
MUST_USE_RESULT static MaybeHandle<Object> PreventExtensionsWithTransition(
Handle<JSObject> object);
DISALLOW_IMPLICIT_CONSTRUCTORS(JSObject); DISALLOW_IMPLICIT_CONSTRUCTORS(JSObject);
}; };
...@@ -5678,10 +5690,9 @@ class Map: public HeapObject { ...@@ -5678,10 +5690,9 @@ class Map: public HeapObject {
class OwnsDescriptors : public BitField<bool, 21, 1> {}; class OwnsDescriptors : public BitField<bool, 21, 1> {};
class HasInstanceCallHandler : public BitField<bool, 22, 1> {}; class HasInstanceCallHandler : public BitField<bool, 22, 1> {};
class Deprecated : public BitField<bool, 23, 1> {}; class Deprecated : public BitField<bool, 23, 1> {};
class IsFrozen : public BitField<bool, 24, 1> {}; class IsUnstable : public BitField<bool, 24, 1> {};
class IsUnstable : public BitField<bool, 25, 1> {}; class IsMigrationTarget : public BitField<bool, 25, 1> {};
class IsMigrationTarget : public BitField<bool, 26, 1> {}; // Bits 26 and 27 are free.
// Bit 27 is free.
// Keep this bit field at the very end for better code in // Keep this bit field at the very end for better code in
// Builtins::kJSConstructStubGeneric stub. // Builtins::kJSConstructStubGeneric stub.
...@@ -6031,8 +6042,6 @@ class Map: public HeapObject { ...@@ -6031,8 +6042,6 @@ class Map: public HeapObject {
inline void set_owns_descriptors(bool owns_descriptors); inline void set_owns_descriptors(bool owns_descriptors);
inline bool has_instance_call_handler(); inline bool has_instance_call_handler();
inline void set_has_instance_call_handler(); inline void set_has_instance_call_handler();
inline void freeze();
inline bool is_frozen();
inline void mark_unstable(); inline void mark_unstable();
inline bool is_stable(); inline bool is_stable();
inline void set_migration_target(bool value); inline void set_migration_target(bool value);
...@@ -6090,7 +6099,10 @@ class Map: public HeapObject { ...@@ -6090,7 +6099,10 @@ class Map: public HeapObject {
static Handle<Map> CopyForObserved(Handle<Map> map); static Handle<Map> CopyForObserved(Handle<Map> map);
static Handle<Map> CopyForFreeze(Handle<Map> map); static Handle<Map> CopyForPreventExtensions(Handle<Map> map,
PropertyAttributes attrs_to_add,
Handle<Symbol> transition_marker,
const char* reason);
// Maximal number of fast properties. Used to restrict the number of map // Maximal number of fast properties. Used to restrict the number of map
// transitions to avoid an explosion in the number of maps for objects used as // transitions to avoid an explosion in the number of maps for objects used as
// dictionaries. // dictionaries.
......
...@@ -530,6 +530,21 @@ RUNTIME_FUNCTION(Runtime_ObjectFreeze) { ...@@ -530,6 +530,21 @@ RUNTIME_FUNCTION(Runtime_ObjectFreeze) {
} }
RUNTIME_FUNCTION(Runtime_ObjectSeal) {
HandleScope scope(isolate);
DCHECK(args.length() == 1);
CONVERT_ARG_HANDLE_CHECKED(JSObject, object, 0);
// %ObjectSeal is a fast path and these cases are handled elsewhere.
RUNTIME_ASSERT(!object->HasSloppyArgumentsElements() &&
!object->map()->is_observed() && !object->IsJSProxy());
Handle<Object> result;
ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, result, JSObject::Seal(object));
return *result;
}
RUNTIME_FUNCTION(Runtime_GetProperty) { RUNTIME_FUNCTION(Runtime_GetProperty) {
HandleScope scope(isolate); HandleScope scope(isolate);
DCHECK(args.length() == 2); DCHECK(args.length() == 2);
......
...@@ -275,6 +275,7 @@ namespace internal { ...@@ -275,6 +275,7 @@ namespace internal {
F(LookupAccessor, 3, 1) \ F(LookupAccessor, 3, 1) \
\ \
/* ES5 */ \ /* ES5 */ \
F(ObjectSeal, 1, 1) \
F(ObjectFreeze, 1, 1) \ F(ObjectFreeze, 1, 1) \
\ \
/* Harmony modules */ \ /* Harmony modules */ \
......
...@@ -157,7 +157,8 @@ int TransitionArray::SearchName(Name* name, int* out_insertion_index) { ...@@ -157,7 +157,8 @@ int TransitionArray::SearchName(Name* name, int* out_insertion_index) {
bool TransitionArray::IsSpecialTransition(Name* name) { bool TransitionArray::IsSpecialTransition(Name* name) {
if (!name->IsSymbol()) return false; if (!name->IsSymbol()) return false;
Heap* heap = name->GetHeap(); Heap* heap = name->GetHeap();
return name == heap->frozen_symbol() || return name == heap->nonextensible_symbol() ||
name == heap->sealed_symbol() || name == heap->frozen_symbol() ||
name == heap->elements_transition_symbol() || name == heap->elements_transition_symbol() ||
name == heap->observed_symbol(); name == heap->observed_symbol();
} }
......
...@@ -1232,23 +1232,30 @@ function ProxyFix(obj) { ...@@ -1232,23 +1232,30 @@ function ProxyFix(obj) {
// ES5 section 15.2.3.8. // ES5 section 15.2.3.8.
function ObjectSeal(obj) { function ObjectSealJS(obj) {
if (!IS_SPEC_OBJECT(obj)) { if (!IS_SPEC_OBJECT(obj)) {
throw MakeTypeError("called_on_non_object", ["Object.seal"]); throw MakeTypeError("called_on_non_object", ["Object.seal"]);
} }
if (%_IsJSProxy(obj)) { var isProxy = %_IsJSProxy(obj);
ProxyFix(obj); if (isProxy || %HasSloppyArgumentsElements(obj) || %IsObserved(obj)) {
} if (isProxy) {
var names = ObjectGetOwnPropertyNames(obj); ProxyFix(obj);
for (var i = 0; i < names.length; i++) {
var name = names[i];
var desc = GetOwnPropertyJS(obj, name);
if (desc.isConfigurable()) {
desc.setConfigurable(false);
DefineOwnProperty(obj, name, desc, true);
} }
var names = ObjectGetOwnPropertyNames(obj);
for (var i = 0; i < names.length; i++) {
var name = names[i];
var desc = GetOwnPropertyJS(obj, name);
if (desc.isConfigurable()) {
desc.setConfigurable(false);
DefineOwnProperty(obj, name, desc, true);
}
}
%PreventExtensions(obj);
} else {
// TODO(adamk): Is it worth going to this fast path if the
// object's properties are already in dictionary mode?
%ObjectSeal(obj);
} }
%PreventExtensions(obj);
return obj; return obj;
} }
...@@ -1430,7 +1437,7 @@ function SetUpObject() { ...@@ -1430,7 +1437,7 @@ function SetUpObject() {
"isFrozen", ObjectIsFrozen, "isFrozen", ObjectIsFrozen,
"isSealed", ObjectIsSealed, "isSealed", ObjectIsSealed,
"preventExtensions", ObjectPreventExtension, "preventExtensions", ObjectPreventExtension,
"seal", ObjectSeal "seal", ObjectSealJS
// deliverChangeRecords, getNotifier, observe and unobserve are added // deliverChangeRecords, getNotifier, observe and unobserve are added
// in object-observe.js. // in object-observe.js.
)); ));
......
// Copyright 2014 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.
Object.freeze(this);
assertTrue(Object.isFrozen(this));
...@@ -339,3 +339,12 @@ obj.__proto__[1] = 1; ...@@ -339,3 +339,12 @@ obj.__proto__[1] = 1;
assertEquals(1, obj[1]); assertEquals(1, obj[1]);
Object.freeze(obj); Object.freeze(obj);
assertThrows(function() { obj.unshift(); }, TypeError); assertThrows(function() { obj.unshift(); }, TypeError);
// Sealing and then Freezing should do the right thing.
var obj = { foo: 'bar', 0: 'element' };
Object.seal(obj);
assertTrue(Object.isSealed(obj));
assertFalse(Object.isFrozen(obj));
Object.freeze(obj);
assertTrue(Object.isSealed(obj));
assertTrue(Object.isFrozen(obj));
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
// Tests the Object.preventExtensions method - ES 15.2.3.10 // Tests the Object.preventExtensions method - ES 15.2.3.10
// Flags: --allow-natives-syntax
var obj1 = {}; var obj1 = {};
// Extensible defaults to true. // Extensible defaults to true.
...@@ -126,3 +128,35 @@ assertEquals(50, v); ...@@ -126,3 +128,35 @@ assertEquals(50, v);
var n = o[0] = 100; var n = o[0] = 100;
assertEquals(undefined, o[0]); assertEquals(undefined, o[0]);
assertEquals(100, n); assertEquals(100, n);
// Fast properties should remain fast
obj = { x: 42, y: 'foo' };
assertTrue(%HasFastProperties(obj));
Object.preventExtensions(obj);
assertFalse(Object.isExtensible(obj));
assertFalse(Object.isSealed(obj));
assertTrue(%HasFastProperties(obj));
// Non-extensible objects should share maps where possible
obj = { prop1: 1, prop2: 2 };
obj2 = { prop1: 3, prop2: 4 };
assertTrue(%HaveSameMap(obj, obj2));
Object.preventExtensions(obj);
Object.preventExtensions(obj2);
assertFalse(Object.isExtensible(obj));
assertFalse(Object.isExtensible(obj2));
assertFalse(Object.isSealed(obj));
assertFalse(Object.isSealed(obj2));
assertTrue(%HaveSameMap(obj, obj2));
// Non-extensible objects should share maps even when they have elements
obj = { prop1: 1, prop2: 2, 75: 'foo' };
obj2 = { prop1: 3, prop2: 4, 150: 'bar' };
assertTrue(%HaveSameMap(obj, obj2));
Object.preventExtensions(obj);
Object.preventExtensions(obj2);
assertFalse(Object.isExtensible(obj));
assertFalse(Object.isExtensible(obj2));
assertFalse(Object.isSealed(obj));
assertFalse(Object.isSealed(obj2));
assertTrue(%HaveSameMap(obj, obj2));
// Copyright 2014 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.
Object.seal(this);
assertTrue(Object.isSealed(this));
assertFalse(Object.isFrozen(this));
...@@ -267,3 +267,132 @@ assertDoesNotThrow(function() { obj.splice(1,2,1,2); }); ...@@ -267,3 +267,132 @@ assertDoesNotThrow(function() { obj.splice(1,2,1,2); });
assertDoesNotThrow(function() { obj.splice(1,2000,1,2); }); assertDoesNotThrow(function() { obj.splice(1,2000,1,2); });
assertThrows(function() { obj.splice(0,0,1); }, TypeError); assertThrows(function() { obj.splice(0,0,1); }, TypeError);
assertThrows(function() { obj.splice(1,2000,1,2,3); }, TypeError); assertThrows(function() { obj.splice(1,2000,1,2,3); }, TypeError);
// Test that the enumerable attribute is unperturbed by sealing.
obj = { x: 42, y: 'foo' };
Object.defineProperty(obj, 'y', {enumerable: false});
Object.seal(obj);
assertTrue(Object.isSealed(obj));
assertFalse(Object.isFrozen(obj));
desc = Object.getOwnPropertyDescriptor(obj, 'x');
assertTrue(desc.enumerable);
desc = Object.getOwnPropertyDescriptor(obj, 'y');
assertFalse(desc.enumerable);
// Fast properties should remain fast
obj = { x: 42, y: 'foo' };
assertTrue(%HasFastProperties(obj));
Object.seal(obj);
assertTrue(Object.isSealed(obj));
assertFalse(Object.isFrozen(obj));
assertTrue(%HasFastProperties(obj));
// Sealed objects should share maps where possible
obj = { prop1: 1, prop2: 2 };
obj2 = { prop1: 3, prop2: 4 };
assertTrue(%HaveSameMap(obj, obj2));
Object.seal(obj);
Object.seal(obj2);
assertTrue(Object.isSealed(obj));
assertTrue(Object.isSealed(obj2));
assertFalse(Object.isFrozen(obj));
assertFalse(Object.isFrozen(obj2));
assertTrue(%HaveSameMap(obj, obj2));
// Sealed objects should share maps even when they have elements
obj = { prop1: 1, prop2: 2, 75: 'foo' };
obj2 = { prop1: 3, prop2: 4, 150: 'bar' };
assertTrue(%HaveSameMap(obj, obj2));
Object.seal(obj);
Object.seal(obj2);
assertTrue(Object.isSealed(obj));
assertTrue(Object.isSealed(obj2));
assertFalse(Object.isFrozen(obj));
assertFalse(Object.isFrozen(obj));
assertTrue(%HaveSameMap(obj, obj2));
// Setting elements after sealing should not be allowed
obj = { prop: 'thing' };
Object.seal(obj);
assertTrue(Object.isSealed(obj));
assertFalse(Object.isFrozen(obj));
obj[0] = 'hello';
assertFalse(obj.hasOwnProperty(0));
// Sealing an object in dictionary mode should work
// Also testing that getter/setter properties work after sealing
obj = { };
for (var i = 0; i < 100; ++i) {
obj['x' + i] = i;
}
var accessorDidRun = false;
Object.defineProperty(obj, 'accessor', {
get: function() { return 42 },
set: function() { accessorDidRun = true },
configurable: true,
enumerable: true
});
assertFalse(%HasFastProperties(obj));
Object.seal(obj);
assertFalse(%HasFastProperties(obj));
assertTrue(Object.isSealed(obj));
assertFalse(Object.isFrozen(obj));
assertFalse(Object.isExtensible(obj));
for (var i = 0; i < 100; ++i) {
desc = Object.getOwnPropertyDescriptor(obj, 'x' + i);
assertFalse(desc.configurable);
}
assertEquals(42, obj.accessor);
assertFalse(accessorDidRun);
obj.accessor = 'ignored value';
assertTrue(accessorDidRun);
// Sealing arguments should work
var func = function(arg) {
Object.seal(arguments);
assertTrue(Object.isSealed(arguments));
};
func('hello', 'world');
func('goodbye', 'world');
// Sealing sparse arrays
var sparseArr = [0, 1];
sparseArr[10000] = 10000;
Object.seal(sparseArr);
assertTrue(Object.isSealed(sparseArr));
// Accessors on fast object should behavior properly after sealing
obj = {};
Object.defineProperty(obj, 'accessor', {
get: function() { return 42 },
set: function() { accessorDidRun = true },
configurable: true,
enumerable: true
});
assertTrue(%HasFastProperties(obj));
Object.seal(obj);
assertTrue(Object.isSealed(obj));
assertTrue(%HasFastProperties(obj));
assertEquals(42, obj.accessor);
accessorDidRun = false;
obj.accessor = 'ignored value';
assertTrue(accessorDidRun);
// Test for regression in mixed accessor/data property objects.
// The strict function is one such object.
assertTrue(Object.isSealed(Object.seal(function(){"use strict";})));
// Also test a simpler case
obj = {};
Object.defineProperty(obj, 'accessor2', {
get: function() { return 42 },
set: function() { accessorDidRun = true },
configurable: true,
enumerable: true
});
obj.data = 'foo';
assertTrue(%HasFastProperties(obj));
Object.seal(obj);
assertTrue(%HasFastProperties(obj));
assertTrue(Object.isSealed(obj));
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