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;
template <typename Config, class Allocator = FreeStoreAllocationPolicy>
class SplayTree;
class String;
class Symbol;
class Name;
class Struct;
class Symbol;
......
......@@ -279,6 +279,8 @@ namespace internal {
V(RegExp_string, "RegExp")
#define PRIVATE_SYMBOL_LIST(V) \
V(nonextensible_symbol) \
V(sealed_symbol) \
V(frozen_symbol) \
V(nonexistent_symbol) \
V(elements_transition_symbol) \
......
......@@ -4637,16 +4637,6 @@ void Map::set_counter(int value) {
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() {
set_bit_field3(IsUnstable::update(bit_field3(), true));
}
......
......@@ -416,11 +416,7 @@ void Map::MapPrint(std::ostream& os) { // NOLINT
if (is_undetectable()) os << " - undetectable\n";
if (has_instance_call_handler()) os << " - instance_call_handler\n";
if (is_access_check_needed()) os << " - access_check_needed\n";
if (is_frozen()) {
os << " - frozen\n";
} else if (!is_extensible()) {
os << " - sealed\n";
}
if (!is_extensible()) os << " - non-extensible\n";
os << " - back pointer: " << Brief(GetBackPointer());
os << "\n - instance descriptors " << (owns_descriptors() ? "(own) " : "")
<< "#" << NumberOfOwnDescriptors() << ": "
......@@ -1179,7 +1175,11 @@ void TransitionArray::PrintTransitions(std::ostream& os,
key->ShortPrint(os);
#endif
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)";
} else if (key == GetHeap()->elements_transition_symbol()) {
os << " (transition to " << ElementsKindToString(target->elements_kind())
......
This diff is collapsed.
......@@ -2115,6 +2115,9 @@ class JSObject: public JSReceiver {
MUST_USE_RESULT static MaybeHandle<Object> PreventExtensions(
Handle<JSObject> object);
// ES5 Object.seal
MUST_USE_RESULT static MaybeHandle<Object> Seal(Handle<JSObject> object);
// ES5 Object.freeze
MUST_USE_RESULT static MaybeHandle<Object> Freeze(Handle<JSObject> object);
......@@ -2404,6 +2407,15 @@ class JSObject: public JSReceiver {
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);
};
......@@ -5678,10 +5690,9 @@ class Map: public HeapObject {
class OwnsDescriptors : public BitField<bool, 21, 1> {};
class HasInstanceCallHandler : public BitField<bool, 22, 1> {};
class Deprecated : public BitField<bool, 23, 1> {};
class IsFrozen : public BitField<bool, 24, 1> {};
class IsUnstable : public BitField<bool, 25, 1> {};
class IsMigrationTarget : public BitField<bool, 26, 1> {};
// Bit 27 is free.
class IsUnstable : public BitField<bool, 24, 1> {};
class IsMigrationTarget : public BitField<bool, 25, 1> {};
// Bits 26 and 27 are free.
// Keep this bit field at the very end for better code in
// Builtins::kJSConstructStubGeneric stub.
......@@ -6031,8 +6042,6 @@ class Map: public HeapObject {
inline void set_owns_descriptors(bool owns_descriptors);
inline bool has_instance_call_handler();
inline void set_has_instance_call_handler();
inline void freeze();
inline bool is_frozen();
inline void mark_unstable();
inline bool is_stable();
inline void set_migration_target(bool value);
......@@ -6090,7 +6099,10 @@ class Map: public HeapObject {
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
// transitions to avoid an explosion in the number of maps for objects used as
// dictionaries.
......
......@@ -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) {
HandleScope scope(isolate);
DCHECK(args.length() == 2);
......
......@@ -275,6 +275,7 @@ namespace internal {
F(LookupAccessor, 3, 1) \
\
/* ES5 */ \
F(ObjectSeal, 1, 1) \
F(ObjectFreeze, 1, 1) \
\
/* Harmony modules */ \
......
......@@ -157,7 +157,8 @@ int TransitionArray::SearchName(Name* name, int* out_insertion_index) {
bool TransitionArray::IsSpecialTransition(Name* name) {
if (!name->IsSymbol()) return false;
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->observed_symbol();
}
......
......@@ -1232,23 +1232,30 @@ function ProxyFix(obj) {
// ES5 section 15.2.3.8.
function ObjectSeal(obj) {
function ObjectSealJS(obj) {
if (!IS_SPEC_OBJECT(obj)) {
throw MakeTypeError("called_on_non_object", ["Object.seal"]);
}
if (%_IsJSProxy(obj)) {
ProxyFix(obj);
}
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);
var isProxy = %_IsJSProxy(obj);
if (isProxy || %HasSloppyArgumentsElements(obj) || %IsObserved(obj)) {
if (isProxy) {
ProxyFix(obj);
}
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;
}
......@@ -1430,7 +1437,7 @@ function SetUpObject() {
"isFrozen", ObjectIsFrozen,
"isSealed", ObjectIsSealed,
"preventExtensions", ObjectPreventExtension,
"seal", ObjectSeal
"seal", ObjectSealJS
// deliverChangeRecords, getNotifier, observe and unobserve are added
// 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;
assertEquals(1, obj[1]);
Object.freeze(obj);
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 @@
// Tests the Object.preventExtensions method - ES 15.2.3.10
// Flags: --allow-natives-syntax
var obj1 = {};
// Extensible defaults to true.
......@@ -126,3 +128,35 @@ assertEquals(50, v);
var n = o[0] = 100;
assertEquals(undefined, o[0]);
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); });
assertDoesNotThrow(function() { obj.splice(1,2000,1,2); });
assertThrows(function() { obj.splice(0,0,1); }, 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