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