Commit 808dc8cf authored by Camillo Bruni's avatar Camillo Bruni Committed by Commit Bot

Support fast-path Function.prototype.bind for bound function

This CL speeds up a common pattern found in the React framework:

function f(a, b, c) { ... };
let f_bound = f.bind(this, 1);
let f_bound2 = f_bound(this, 2);

This CL yields roughly a 15x improvement for rebinding a bound function.

Change-Id: I4d8580a5bce422af411148bc6b3e4eb287fac9ce
Reviewed-on: https://chromium-review.googlesource.com/695206
Commit-Queue: Camillo Bruni <cbruni@chromium.org>
Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#48283}
parent ec489f39
......@@ -727,12 +727,11 @@ void Accessors::FunctionLengthGetter(
HandleScope scope(isolate);
Handle<JSFunction> function =
Handle<JSFunction>::cast(Utils::OpenHandle(*info.Holder()));
Handle<Object> result;
if (!JSFunction::GetLength(isolate, function).ToHandle(&result)) {
result = handle(Smi::kZero, isolate);
int length = 0;
if (!JSFunction::GetLength(isolate, function).To(&length)) {
isolate->OptionalRescheduleException(false);
}
Handle<Object> result(Smi::FromInt(length), isolate);
info.GetReturnValue().Set(Utils::ToLocal(result));
}
......@@ -1110,18 +1109,11 @@ void Accessors::BoundFunctionLengthGetter(
Handle<JSBoundFunction> function =
Handle<JSBoundFunction>::cast(Utils::OpenHandle(*info.Holder()));
Handle<Smi> target_length;
Handle<JSFunction> target(JSFunction::cast(function->bound_target_function()),
isolate);
if (!JSFunction::GetLength(isolate, target).ToHandle(&target_length)) {
target_length = handle(Smi::kZero, isolate);
int length = 0;
if (!JSBoundFunction::GetLength(isolate, function).To(&length)) {
isolate->OptionalRescheduleException(false);
return;
}
int bound_length = function->bound_arguments()->length();
int length = Max(0, target_length->value() - bound_length);
Handle<Object> result(Smi::FromInt(length), isolate);
info.GetReturnValue().Set(Utils::ToLocal(result));
}
......
......@@ -27,8 +27,15 @@ TF_BUILTIN(FastFunctionPrototypeBind, CodeStubAssembler) {
GotoIf(TaggedIsSmi(receiver), &slow);
Node* receiver_map = LoadMap(receiver);
Node* instance_type = LoadMapInstanceType(receiver_map);
GotoIf(Word32NotEqual(instance_type, Int32Constant(JS_FUNCTION_TYPE)), &slow);
{
Label fast(this);
Node* instance_type = LoadMapInstanceType(receiver_map);
GotoIf(Word32Equal(instance_type, Int32Constant(JS_FUNCTION_TYPE)), &fast);
GotoIf(Word32Equal(instance_type, Int32Constant(JS_BOUND_FUNCTION_TYPE)),
&fast);
Goto(&slow);
BIND(&fast);
}
// Disallow binding of slow-mode functions. We need to figure out whether the
// length and name property are in the original state.
......@@ -47,51 +54,55 @@ TF_BUILTIN(FastFunctionPrototypeBind, CodeStubAssembler) {
// AccessorInfo objects. In that case, their value can be recomputed even if
// the actual value on the object changes.
Comment("Check name and length properties");
const int length_index = JSFunction::kLengthDescriptorIndex;
Node* maybe_length = LoadFixedArrayElement(
descriptors, DescriptorArray::ToKeyIndex(length_index));
GotoIf(WordNotEqual(maybe_length, LoadRoot(Heap::klength_stringRootIndex)),
&slow);
Node* maybe_length_accessor = LoadFixedArrayElement(
descriptors, DescriptorArray::ToValueIndex(length_index));
GotoIf(TaggedIsSmi(maybe_length_accessor), &slow);
Node* length_value_map = LoadMap(maybe_length_accessor);
GotoIfNot(IsAccessorInfoMap(length_value_map), &slow);
const int name_index = JSFunction::kNameDescriptorIndex;
Node* maybe_name = LoadFixedArrayElement(
descriptors, DescriptorArray::ToKeyIndex(name_index));
GotoIf(WordNotEqual(maybe_name, LoadRoot(Heap::kname_stringRootIndex)),
&slow);
Node* maybe_name_accessor = LoadFixedArrayElement(
descriptors, DescriptorArray::ToValueIndex(name_index));
GotoIf(TaggedIsSmi(maybe_name_accessor), &slow);
Node* name_value_map = LoadMap(maybe_name_accessor);
GotoIfNot(IsAccessorInfoMap(name_value_map), &slow);
{
const int length_index = JSFunction::kLengthDescriptorIndex;
Node* maybe_length = LoadFixedArrayElement(
descriptors, DescriptorArray::ToKeyIndex(length_index));
GotoIf(WordNotEqual(maybe_length, LoadRoot(Heap::klength_stringRootIndex)),
&slow);
Node* maybe_length_accessor = LoadFixedArrayElement(
descriptors, DescriptorArray::ToValueIndex(length_index));
GotoIf(TaggedIsSmi(maybe_length_accessor), &slow);
Node* length_value_map = LoadMap(maybe_length_accessor);
GotoIfNot(IsAccessorInfoMap(length_value_map), &slow);
const int name_index = JSFunction::kNameDescriptorIndex;
Node* maybe_name = LoadFixedArrayElement(
descriptors, DescriptorArray::ToKeyIndex(name_index));
GotoIf(WordNotEqual(maybe_name, LoadRoot(Heap::kname_stringRootIndex)),
&slow);
Node* maybe_name_accessor = LoadFixedArrayElement(
descriptors, DescriptorArray::ToValueIndex(name_index));
GotoIf(TaggedIsSmi(maybe_name_accessor), &slow);
Node* name_value_map = LoadMap(maybe_name_accessor);
GotoIfNot(IsAccessorInfoMap(name_value_map), &slow);
}
// Choose the right bound function map based on whether the target is
// constructable.
Comment("Choose the right bound function map");
VARIABLE(bound_function_map, MachineRepresentation::kTagged);
Label with_constructor(this);
VariableList vars({&bound_function_map}, zone());
Node* native_context = LoadNativeContext(context);
{
Label with_constructor(this);
VariableList vars({&bound_function_map}, zone());
Node* native_context = LoadNativeContext(context);
Label map_done(this, vars);
GotoIf(IsConstructorMap(receiver_map), &with_constructor);
Label map_done(this, vars);
GotoIf(IsConstructorMap(receiver_map), &with_constructor);
bound_function_map.Bind(LoadContextElement(
native_context, Context::BOUND_FUNCTION_WITHOUT_CONSTRUCTOR_MAP_INDEX));
Goto(&map_done);
bound_function_map.Bind(LoadContextElement(
native_context, Context::BOUND_FUNCTION_WITHOUT_CONSTRUCTOR_MAP_INDEX));
Goto(&map_done);
BIND(&with_constructor);
bound_function_map.Bind(LoadContextElement(
native_context, Context::BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX));
Goto(&map_done);
BIND(&with_constructor);
bound_function_map.Bind(LoadContextElement(
native_context, Context::BOUND_FUNCTION_WITH_CONSTRUCTOR_MAP_INDEX));
Goto(&map_done);
BIND(&map_done);
BIND(&map_done);
}
// Verify that __proto__ matches that of a the target bound function.
Comment("Verify that __proto__ matches target bound function");
......@@ -102,68 +113,74 @@ TF_BUILTIN(FastFunctionPrototypeBind, CodeStubAssembler) {
// Allocate the arguments array.
Comment("Allocate the arguments array");
VARIABLE(argument_array, MachineRepresentation::kTagged);
Label empty_arguments(this);
Label arguments_done(this, &argument_array);
GotoIf(Uint32LessThanOrEqual(argc, Int32Constant(1)), &empty_arguments);
Node* elements_length =
ChangeUint32ToWord(Unsigned(Int32Sub(argc, Int32Constant(1))));
Node* elements =
AllocateFixedArray(PACKED_ELEMENTS, elements_length, INTPTR_PARAMETERS,
kAllowLargeObjectAllocation);
VARIABLE(index, MachineType::PointerRepresentation());
index.Bind(IntPtrConstant(0));
VariableList foreach_vars({&index}, zone());
args.ForEach(foreach_vars,
[this, elements, &index](Node* arg) {
StoreFixedArrayElement(elements, index.value(), arg);
Increment(&index);
},
IntPtrConstant(1));
argument_array.Bind(elements);
Goto(&arguments_done);
BIND(&empty_arguments);
argument_array.Bind(EmptyFixedArrayConstant());
Goto(&arguments_done);
BIND(&arguments_done);
{
Label empty_arguments(this);
Label arguments_done(this, &argument_array);
GotoIf(Uint32LessThanOrEqual(argc, Int32Constant(1)), &empty_arguments);
Node* elements_length =
ChangeUint32ToWord(Unsigned(Int32Sub(argc, Int32Constant(1))));
Node* elements =
AllocateFixedArray(PACKED_ELEMENTS, elements_length, INTPTR_PARAMETERS,
kAllowLargeObjectAllocation);
VARIABLE(index, MachineType::PointerRepresentation());
index.Bind(IntPtrConstant(0));
VariableList foreach_vars({&index}, zone());
args.ForEach(foreach_vars,
[this, elements, &index](Node* arg) {
StoreFixedArrayElement(elements, index.value(), arg);
Increment(&index);
},
IntPtrConstant(1));
argument_array.Bind(elements);
Goto(&arguments_done);
BIND(&empty_arguments);
argument_array.Bind(EmptyFixedArrayConstant());
Goto(&arguments_done);
BIND(&arguments_done);
}
// Determine bound receiver.
Comment("Determine bound receiver");
VARIABLE(bound_receiver, MachineRepresentation::kTagged);
Label has_receiver(this);
Label receiver_done(this, &bound_receiver);
GotoIf(Word32NotEqual(argc, Int32Constant(0)), &has_receiver);
bound_receiver.Bind(UndefinedConstant());
Goto(&receiver_done);
{
Label has_receiver(this);
Label receiver_done(this, &bound_receiver);
GotoIf(Word32NotEqual(argc, Int32Constant(0)), &has_receiver);
bound_receiver.Bind(UndefinedConstant());
Goto(&receiver_done);
BIND(&has_receiver);
bound_receiver.Bind(args.AtIndex(0));
Goto(&receiver_done);
BIND(&has_receiver);
bound_receiver.Bind(args.AtIndex(0));
Goto(&receiver_done);
BIND(&receiver_done);
BIND(&receiver_done);
}
// Allocate the resulting bound function.
Comment("Allocate the resulting bound function");
Node* bound_function = Allocate(JSBoundFunction::kSize);
StoreMapNoWriteBarrier(bound_function, bound_function_map.value());
StoreObjectFieldNoWriteBarrier(
bound_function, JSBoundFunction::kBoundTargetFunctionOffset, receiver);
StoreObjectFieldNoWriteBarrier(bound_function,
JSBoundFunction::kBoundThisOffset,
bound_receiver.value());
StoreObjectFieldNoWriteBarrier(bound_function,
JSBoundFunction::kBoundArgumentsOffset,
argument_array.value());
Node* empty_fixed_array = EmptyFixedArrayConstant();
StoreObjectFieldNoWriteBarrier(
bound_function, JSObject::kPropertiesOrHashOffset, empty_fixed_array);
StoreObjectFieldNoWriteBarrier(bound_function, JSObject::kElementsOffset,
empty_fixed_array);
args.PopAndReturn(bound_function);
BIND(&slow);
{
Node* bound_function = Allocate(JSBoundFunction::kSize);
StoreMapNoWriteBarrier(bound_function, bound_function_map.value());
StoreObjectFieldNoWriteBarrier(
bound_function, JSBoundFunction::kBoundTargetFunctionOffset, receiver);
StoreObjectFieldNoWriteBarrier(bound_function,
JSBoundFunction::kBoundThisOffset,
bound_receiver.value());
StoreObjectFieldNoWriteBarrier(bound_function,
JSBoundFunction::kBoundArgumentsOffset,
argument_array.value());
Node* empty_fixed_array = EmptyFixedArrayConstant();
StoreObjectFieldNoWriteBarrier(
bound_function, JSObject::kPropertiesOrHashOffset, empty_fixed_array);
StoreObjectFieldNoWriteBarrier(bound_function, JSObject::kElementsOffset,
empty_fixed_array);
args.PopAndReturn(bound_function);
}
BIND(&slow);
Node* target = LoadFromFrame(StandardFrameConstants::kFunctionOffset,
MachineType::TaggedPointer());
TailCallStub(CodeFactory::FunctionPrototypeBind(isolate()), context, target,
......
......@@ -5689,13 +5689,53 @@ MaybeHandle<Context> JSBoundFunction::GetFunctionRealm(
MaybeHandle<String> JSBoundFunction::GetName(Isolate* isolate,
Handle<JSBoundFunction> function) {
Handle<String> prefix = isolate->factory()->bound__string();
if (!function->bound_target_function()->IsJSFunction()) return prefix;
Handle<String> target_name = prefix;
Factory* factory = isolate->factory();
// Concatenate the "bound " up to the last non-bound target.
while (function->bound_target_function()->IsJSBoundFunction()) {
ASSIGN_RETURN_ON_EXCEPTION(isolate, target_name,
factory->NewConsString(prefix, target_name),
String);
function = handle(JSBoundFunction::cast(function->bound_target_function()),
isolate);
}
if (function->bound_target_function()->IsJSFunction()) {
Handle<JSFunction> target(
JSFunction::cast(function->bound_target_function()), isolate);
Handle<Object> name = JSFunction::GetName(isolate, target);
if (!name->IsString()) return target_name;
return factory->NewConsString(target_name, Handle<String>::cast(name));
}
// This will omit the proper target name for bound JSProxies.
return target_name;
}
// static
Maybe<int> JSBoundFunction::GetLength(Isolate* isolate,
Handle<JSBoundFunction> function) {
int nof_bound_arguments = function->bound_arguments()->length();
while (function->bound_target_function()->IsJSBoundFunction()) {
function = handle(JSBoundFunction::cast(function->bound_target_function()),
isolate);
// Make sure we never overflow {nof_bound_arguments}, the number of
// arguments of a function is strictly limited by the max length of an
// JSAarray, Smi::kMaxValue is thus a reasonably good overestimate.
int length = function->bound_arguments()->length();
if (V8_LIKELY(Smi::kMaxValue - nof_bound_arguments > length)) {
nof_bound_arguments += length;
} else {
nof_bound_arguments = Smi::kMaxValue;
}
}
// All non JSFunction targets get a direct property and don't use this
// accessor.
Handle<JSFunction> target(JSFunction::cast(function->bound_target_function()),
isolate);
Handle<Object> target_name = JSFunction::GetName(isolate, target);
if (!target_name->IsString()) return prefix;
Factory* factory = isolate->factory();
return factory->NewConsString(prefix, Handle<String>::cast(target_name));
Maybe<int> target_length = JSFunction::GetLength(isolate, target);
if (target_length.IsNothing()) return target_length;
int length = Max(0, target_length.FromJust() - nof_bound_arguments);
return Just(length);
}
// static
......@@ -5708,8 +5748,8 @@ Handle<Object> JSFunction::GetName(Isolate* isolate,
}
// static
MaybeHandle<Smi> JSFunction::GetLength(Isolate* isolate,
Handle<JSFunction> function) {
Maybe<int> JSFunction::GetLength(Isolate* isolate,
Handle<JSFunction> function) {
int length = 0;
if (function->shared()->is_compiled()) {
length = function->shared()->GetLength();
......@@ -5719,10 +5759,10 @@ MaybeHandle<Smi> JSFunction::GetLength(Isolate* isolate,
if (Compiler::Compile(function, Compiler::KEEP_EXCEPTION)) {
length = function->shared()->GetLength();
}
if (isolate->has_pending_exception()) return MaybeHandle<Smi>();
if (isolate->has_pending_exception()) return Nothing<int>();
}
DCHECK_GE(length, 0);
return handle(Smi::FromInt(length), isolate);
return Just(length);
}
// static
......
......@@ -4779,6 +4779,8 @@ class JSBoundFunction : public JSObject {
static MaybeHandle<String> GetName(Isolate* isolate,
Handle<JSBoundFunction> function);
static Maybe<int> GetLength(Isolate* isolate,
Handle<JSBoundFunction> function);
static MaybeHandle<Context> GetFunctionRealm(
Handle<JSBoundFunction> function);
......@@ -4824,8 +4826,7 @@ class JSFunction: public JSObject {
inline Context* native_context();
static Handle<Object> GetName(Isolate* isolate, Handle<JSFunction> function);
static MaybeHandle<Smi> GetLength(Isolate* isolate,
Handle<JSFunction> function);
static Maybe<int> GetLength(Isolate* isolate, Handle<JSFunction> function);
static Handle<Context> GetFunctionRealm(Handle<JSFunction> function);
// [code]: The generated code object for this function. Executed
......
......@@ -41,21 +41,25 @@ var f = foo.bind(foo);
assertEquals([foo, 3, 1], f(1, 2, 3));
assertEquals(3, f.length);
assertEquals("function () { [native code] }", f.toString());
%HeapObjectVerify(f);
f = foo.bind(foo, 1);
assertEquals([foo, 3, 1], f(2, 3));
assertEquals(2, f.length);
assertEquals("function () { [native code] }", f.toString());
%HeapObjectVerify(f);
f = foo.bind(foo, 1, 2);
assertEquals([foo, 3, 1], f(3));
assertEquals(1, f.length);
assertEquals("function () { [native code] }", f.toString());
%HeapObjectVerify(f);
f = foo.bind(foo, 1, 2, 3);
assertEquals([foo, 3, 1], f());
assertEquals(0, f.length);
assertEquals("function () { [native code] }", f.toString());
%HeapObjectVerify(f);
// Test that length works correctly even if more than the actual number
// of arguments are given when binding.
......@@ -63,6 +67,7 @@ f = foo.bind(foo, 1, 2, 3, 4, 5, 6, 7, 8, 9);
assertEquals([foo, 9, 1], f());
assertEquals(0, f.length);
assertEquals("function () { [native code] }", f.toString());
%HeapObjectVerify(f);
// Use a different bound object.
var obj = {x: 42, y: 43};
......@@ -78,11 +83,13 @@ assertEquals(3, f_bound_this(1))
f = f_bound_this.bind(obj);
assertEquals(2, f(1));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = f_bound_this.bind(obj, 2);
assertEquals(3, f());
assertEquals(0, f.length);
assertEquals('[object Function]', Object.prototype.toString.call(f));
%HeapObjectVerify(f);
// Test chained binds.
......@@ -90,65 +97,80 @@ assertEquals('[object Function]', Object.prototype.toString.call(f));
// the same effect.
f = foo.bind(foo);
assertEquals([foo, 3, 1], f(1, 2, 3));
%HeapObjectVerify(f);
var not_foo = {};
f = foo.bind(foo).bind(not_foo).bind(not_foo).bind(not_foo);
assertEquals([foo, 3, 1], f(1, 2, 3));
assertEquals(3, f.length);
%HeapObjectVerify(f);
// Giving bound parameters should work at any place in the chain.
f = foo.bind(foo, 1).bind(not_foo).bind(not_foo).bind(not_foo);
assertEquals([foo, 3, 1], f(2, 3));
assertEquals(2, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo).bind(not_foo, 1).bind(not_foo).bind(not_foo);
assertEquals([foo, 3, 1], f(2, 3));
assertEquals(2, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo).bind(not_foo).bind(not_foo,1 ).bind(not_foo);
assertEquals([foo, 3, 1], f(2, 3));
assertEquals(2, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo).bind(not_foo).bind(not_foo).bind(not_foo, 1);
assertEquals([foo, 3, 1], f(2, 3));
assertEquals(2, f.length);
%HeapObjectVerify(f);
// Several parameters can be given, and given in different bind invocations.
f = foo.bind(foo, 1, 2).bind(not_foo).bind(not_foo).bind(not_foo);
assertEquals([foo, 3, 1], f(3));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo).bind(not_foo, 1, 2).bind(not_foo).bind(not_foo);
assertEquals([foo, 3, 1], f(1));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo).bind(not_foo, 1, 2).bind(not_foo).bind(not_foo);
assertEquals([foo, 3, 1], f(3));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo).bind(not_foo).bind(not_foo, 1, 2).bind(not_foo);
assertEquals([foo, 3, 1], f(1));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo).bind(not_foo).bind(not_foo).bind(not_foo, 1, 2);
assertEquals([foo, 3, 1], f(3));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo, 1).bind(not_foo, 2).bind(not_foo).bind(not_foo);
assertEquals([foo, 3, 1], f(3));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo, 1).bind(not_foo).bind(not_foo, 2).bind(not_foo);
assertEquals([foo, 3, 1], f(3));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo, 1).bind(not_foo).bind(not_foo).bind(not_foo, 2);
assertEquals([foo, 3, 1], f(3));
assertEquals(1, f.length);
%HeapObjectVerify(f);
f = foo.bind(foo).bind(not_foo, 1).bind(not_foo).bind(not_foo, 2);
assertEquals([foo, 3, 1], f(3));
assertEquals(1, f.length);
%HeapObjectVerify(f);
// The wrong number of arguments can be given to bound functions too.
f = foo.bind(foo);
......@@ -158,6 +180,7 @@ assertEquals([foo, 1, 1], f(1));
assertEquals([foo, 2, 1], f(1, 2));
assertEquals([foo, 3, 1], f(1, 2, 3));
assertEquals([foo, 4, 1], f(1, 2, 3, 4));
%HeapObjectVerify(f);
f = foo.bind(foo, 1);
assertEquals(2, f.length);
......@@ -165,21 +188,25 @@ assertEquals([foo, 1, 1], f());
assertEquals([foo, 2, 1], f(2));
assertEquals([foo, 3, 1], f(2, 3));
assertEquals([foo, 4, 1], f(2, 3, 4));
%HeapObjectVerify(f);
f = foo.bind(foo, 1, 2);
assertEquals(1, f.length);
assertEquals([foo, 2, 1], f());
assertEquals([foo, 3, 1], f(3));
assertEquals([foo, 4, 1], f(3, 4));
%HeapObjectVerify(f);
f = foo.bind(foo, 1, 2, 3);
assertEquals(0, f.length);
assertEquals([foo, 3, 1], f());
assertEquals([foo, 4, 1], f(4));
%HeapObjectVerify(f);
f = foo.bind(foo, 1, 2, 3, 4);
assertEquals(0, f.length);
assertEquals([foo, 4, 1], f());
%HeapObjectVerify(f);
// Test constructor calls.
......@@ -194,24 +221,32 @@ var obj2 = new f(1,2,3);
assertEquals(1, obj2.x);
assertEquals(2, obj2.y);
assertEquals(3, obj2.z);
%HeapObjectVerify(f);
%HeapObjectVerify(obj2);
f = bar.bind(bar, 1);
obj2 = new f(2,3);
assertEquals(1, obj2.x);
assertEquals(2, obj2.y);
assertEquals(3, obj2.z);
%HeapObjectVerify(f);
%HeapObjectVerify(obj2);
f = bar.bind(bar, 1, 2);
obj2 = new f(3);
assertEquals(1, obj2.x);
assertEquals(2, obj2.y);
assertEquals(3, obj2.z);
%HeapObjectVerify(f);
%HeapObjectVerify(obj2);
f = bar.bind(bar, 1, 2, 3);
obj2 = new f();
assertEquals(1, obj2.x);
assertEquals(2, obj2.y);
assertEquals(3, obj2.z);
%HeapObjectVerify(f);
%HeapObjectVerify(obj2);
// Test bind chains when used as a constructor.
......@@ -220,6 +255,8 @@ obj2 = new f();
assertEquals(1, obj2.x);
assertEquals(2, obj2.y);
assertEquals(3, obj2.z);
%HeapObjectVerify(f);
%HeapObjectVerify(obj2);
// Test obj2 is instanceof both bar and f.
assertTrue(obj2 instanceof bar);
......@@ -235,22 +272,29 @@ assertTrue(obj3 instanceof f);
assertFalse(obj3 instanceof foo);
assertFalse(obj3 instanceof Function);
assertFalse(obj3 instanceof String);
%HeapObjectVerify(f);
%HeapObjectVerify(obj3);
// thisArg is converted to object.
f = foo.bind(undefined);
assertEquals([this, 0, undefined], f());
%HeapObjectVerify(f);
f = foo.bind(null);
assertEquals([this, 0, undefined], f());
%HeapObjectVerify(f);
f = foo.bind(42);
assertEquals([Object(42), 0, undefined], f());
%HeapObjectVerify(f);
f = foo.bind("foo");
assertEquals([Object("foo"), 0, undefined], f());
%HeapObjectVerify(f);
f = foo.bind(true);
assertEquals([Object(true), 0, undefined], f());
%HeapObjectVerify(f);
// Strict functions don't convert thisArg.
function soo(x, y, z) {
......@@ -260,18 +304,23 @@ function soo(x, y, z) {
var s = soo.bind(undefined);
assertEquals([undefined, 0, undefined], s());
%HeapObjectVerify(s);
s = soo.bind(null);
assertEquals([null, 0, undefined], s());
%HeapObjectVerify(s);
s = soo.bind(42);
assertEquals([42, 0, undefined], s());
%HeapObjectVerify(s);
s = soo.bind("foo");
assertEquals(["foo", 0, undefined], s());
%HeapObjectVerify(s);
s = soo.bind(true);
assertEquals([true, 0, undefined], s());
%HeapObjectVerify(s);
// Test that .arguments and .caller are poisoned according to the ES5 spec.
......@@ -316,11 +365,14 @@ assertThrows(function() { f.arguments = 42; }, TypeError);
Object.setPrototypeOf(fun, proto);
var bound = fun.bind({});
assertEquals(proto, Object.getPrototypeOf(bound));
%HeapObjectVerify(bound);
var bound2 = fun.bind({});
assertTrue(%HaveSameMap(new bound, new bound2));
%HeapObjectVerify(bound2);
Object.setPrototypeOf(fun, null);
bound = Function.prototype.bind.call(fun, {});
assertEquals(null, Object.getPrototypeOf(bound));
%HeapObjectVerify(bound);
})();
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