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: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#48283}
This commit is contained in:
parent
ec489f39c1
commit
808dc8cff3
@ -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);
|
||||
{
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
{
|
||||
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(&empty_arguments);
|
||||
argument_array.Bind(EmptyFixedArrayConstant());
|
||||
Goto(&arguments_done);
|
||||
|
||||
BIND(&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);
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user