Commit 562f90d3 authored by sgjesse@chromium.org's avatar sgjesse@chromium.org

Ensure correct boxing of values when calling functions on them

When a function is called with a value type as the receiver this is now boxed as an object.

This is a low-impact solution where the receiver is only boxed when required. For IC calls to the V8 builtins values are not boxed and as most of the functions on String.prototype, Number.prototype and Boolean.prototype are sitting there most IC calls on values will not need any boxing of the receiver.

For calls which are not IC calls but calls through the CallFunctionStub a flag is used to determine whether the receiver might be a value and only when that is the case will the receiver be boxed.

No changtes to Function.call and Function.apply - they already boxed values. According to the ES5 spec the receiver should not be boxed for these functions, but current browsers have not adopted that change yet.

BUG=223
TEST=test/mjsunit/value-wrapper.js
TEST=test/mjsunit/regress/regress-crbug-3184.js
Review URL: http://codereview.chromium.org/542087

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@3617 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent e174a64a
......@@ -1091,7 +1091,8 @@ void CodeGenerator::Comparison(Condition cc,
// Call the function on the stack with the given arguments.
void CodeGenerator::CallWithArguments(ZoneList<Expression*>* args,
int position) {
CallFunctionFlags flags,
int position) {
VirtualFrame::SpilledScope spilled_scope;
// Push the arguments ("left-to-right") on the stack.
int arg_count = args->length();
......@@ -1104,7 +1105,7 @@ void CodeGenerator::CallWithArguments(ZoneList<Expression*>* args,
// Use the shared code stub to call the function.
InLoopFlag in_loop = loop_nesting() > 0 ? IN_LOOP : NOT_IN_LOOP;
CallFunctionStub call_function(arg_count, in_loop);
CallFunctionStub call_function(arg_count, in_loop, flags);
frame_->CallStub(&call_function, arg_count + 1);
// Restore context and pop function from the stack.
......@@ -2999,7 +3000,7 @@ void CodeGenerator::VisitCall(Call* node) {
CodeForSourcePosition(node->position());
InLoopFlag in_loop = loop_nesting() > 0 ? IN_LOOP : NOT_IN_LOOP;
CallFunctionStub call_function(arg_count, in_loop);
CallFunctionStub call_function(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE);
frame_->CallStub(&call_function, arg_count + 1);
__ ldr(cp, frame_->Context());
......@@ -3056,7 +3057,7 @@ void CodeGenerator::VisitCall(Call* node) {
frame_->EmitPush(r1); // receiver
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, NO_CALL_FUNCTION_FLAGS, node->position());
frame_->EmitPush(r0);
} else if (property != NULL) {
......@@ -3109,7 +3110,7 @@ void CodeGenerator::VisitCall(Call* node) {
}
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, RECEIVER_MIGHT_BE_VALUE, node->position());
frame_->EmitPush(r0);
}
......@@ -3125,7 +3126,7 @@ void CodeGenerator::VisitCall(Call* node) {
LoadGlobalReceiver(r0);
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, NO_CALL_FUNCTION_FLAGS, node->position());
frame_->EmitPush(r0);
}
ASSERT(frame_->height() == original_height + 1);
......@@ -6601,6 +6602,33 @@ void ArgumentsAccessStub::GenerateNewObject(MacroAssembler* masm) {
void CallFunctionStub::Generate(MacroAssembler* masm) {
Label slow;
// If the receiver might be a value (string, number or boolean) check for this
// and box it if it is.
if (ReceiverMightBeValue()) {
// Get the receiver from the stack.
// function, receiver [, arguments]
Label receiver_is_value, receiver_is_js_object;
__ ldr(r1, MemOperand(sp, argc_ * kPointerSize));
// Check if receiver is a smi (which is a number value).
__ BranchOnSmi(r1, &receiver_is_value);
// Check if the receiver is a valid JS object.
__ CompareObjectType(r1, r2, r2, FIRST_JS_OBJECT_TYPE);
__ b(ge, &receiver_is_js_object);
// Call the runtime to box the value.
__ bind(&receiver_is_value);
__ EnterInternalFrame();
__ push(r1);
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_JS);
__ LeaveInternalFrame();
__ str(r0, MemOperand(sp, argc_ * kPointerSize));
__ bind(&receiver_is_js_object);
}
// Get the function to call from the stack.
// function, receiver [, arguments]
__ ldr(r1, MemOperand(sp, (argc_ + 1) * kPointerSize));
......
......@@ -304,7 +304,9 @@ class CodeGenerator: public AstVisitor {
bool reversed,
OverwriteMode mode);
void CallWithArguments(ZoneList<Expression*>* arguments, int position);
void CallWithArguments(ZoneList<Expression*>* arguments,
CallFunctionFlags flags,
int position);
// Control flow
void Branch(bool if_true, JumpTarget* target);
......@@ -432,27 +434,6 @@ class CodeGenerator: public AstVisitor {
};
class CallFunctionStub: public CodeStub {
public:
CallFunctionStub(int argc, InLoopFlag in_loop)
: argc_(argc), in_loop_(in_loop) {}
void Generate(MacroAssembler* masm);
private:
int argc_;
InLoopFlag in_loop_;
#if defined(DEBUG)
void Print() { PrintF("CallFunctionStub (argc %d)\n", argc_); }
#endif // defined(DEBUG)
Major MajorKey() { return CallFunction; }
int MinorKey() { return argc_; }
InLoopFlag InLoop() { return in_loop_; }
};
class GenericBinaryOpStub : public CodeStub {
public:
GenericBinaryOpStub(Token::Value op,
......
......@@ -1073,7 +1073,7 @@ void FastCodeGenerator::EmitCallWithStub(Call* expr) {
}
// Record source position for debugger.
SetSourcePosition(expr->position());
CallFunctionStub stub(arg_count, NOT_IN_LOOP);
CallFunctionStub stub(arg_count, NOT_IN_LOOP, RECEIVER_MIGHT_BE_VALUE);
__ CallStub(&stub);
// Restore context register.
__ ldr(cp, MemOperand(fp, StandardFrameConstants::kContextOffset));
......
......@@ -637,50 +637,65 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
break;
case STRING_CHECK:
// Check that the object is a two-byte string or a symbol.
__ CompareObjectType(r1, r2, r2, FIRST_NONSTRING_TYPE);
__ b(hs, &miss);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::STRING_FUNCTION_INDEX,
r2);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r2, holder, r3,
r1, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
// Check that the object is a two-byte string or a symbol.
__ CompareObjectType(r1, r2, r2, FIRST_NONSTRING_TYPE);
__ b(hs, &miss);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::STRING_FUNCTION_INDEX,
r2);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r2, holder, r3,
r1, name, &miss);
}
break;
case NUMBER_CHECK: {
Label fast;
// Check that the object is a smi or a heap number.
__ tst(r1, Operand(kSmiTagMask));
__ b(eq, &fast);
__ CompareObjectType(r1, r2, r2, HEAP_NUMBER_TYPE);
__ b(ne, &miss);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::NUMBER_FUNCTION_INDEX,
r2);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r2, holder, r3,
r1, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
Label fast;
// Check that the object is a smi or a heap number.
__ tst(r1, Operand(kSmiTagMask));
__ b(eq, &fast);
__ CompareObjectType(r1, r2, r2, HEAP_NUMBER_TYPE);
__ b(ne, &miss);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::NUMBER_FUNCTION_INDEX,
r2);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r2, holder, r3,
r1, name, &miss);
}
break;
}
case BOOLEAN_CHECK: {
Label fast;
// Check that the object is a boolean.
__ LoadRoot(ip, Heap::kTrueValueRootIndex);
__ cmp(r1, ip);
__ b(eq, &fast);
__ LoadRoot(ip, Heap::kFalseValueRootIndex);
__ cmp(r1, ip);
__ b(ne, &miss);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::BOOLEAN_FUNCTION_INDEX,
r2);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r2, holder, r3,
r1, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
Label fast;
// Check that the object is a boolean.
__ LoadRoot(ip, Heap::kTrueValueRootIndex);
__ cmp(r1, ip);
__ b(eq, &fast);
__ LoadRoot(ip, Heap::kFalseValueRootIndex);
__ cmp(r1, ip);
__ b(ne, &miss);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::BOOLEAN_FUNCTION_INDEX,
r2);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r2, holder, r3,
r1, name, &miss);
}
break;
}
......
......@@ -516,6 +516,64 @@ class RegExpExecStub: public CodeStub {
};
class CallFunctionStub: public CodeStub {
public:
CallFunctionStub(int argc, InLoopFlag in_loop, CallFunctionFlags flags)
: argc_(argc), in_loop_(in_loop), flags_(flags) { }
void Generate(MacroAssembler* masm);
private:
int argc_;
InLoopFlag in_loop_;
CallFunctionFlags flags_;
#ifdef DEBUG
void Print() {
PrintF("CallFunctionStub (args %d, in_loop %d, flags %d)\n",
argc_,
static_cast<int>(in_loop_),
static_cast<int>(flags_));
}
#endif
// Minor key encoding in 31 bits AAAAAAAAAAAAAAAAAAAAAFI A(rgs)F(lag)I(nloop).
class InLoopBits: public BitField<InLoopFlag, 0, 1> {};
class FlagBits: public BitField<CallFunctionFlags, 1, 1> {};
class ArgcBits: public BitField<int, 2, 29> {};
Major MajorKey() { return CallFunction; }
int MinorKey() {
// Encode the parameters in a unique 31 bit value.
return InLoopBits::encode(in_loop_)
| FlagBits::encode(flags_)
| ArgcBits::encode(argc_);
}
InLoopFlag InLoop() { return in_loop_; }
bool ReceiverMightBeValue() {
return (flags_ & RECEIVER_MIGHT_BE_VALUE) != 0;
}
public:
static int ExtractArgcFromMinorKey(int minor_key) {
return ArgcBits::decode(minor_key);
}
};
class ToBooleanStub: public CodeStub {
public:
ToBooleanStub() { }
void Generate(MacroAssembler* masm);
private:
Major MajorKey() { return ToBoolean; }
int MinorKey() { return 0; }
};
} // namespace internal
} // namespace v8
......
......@@ -1241,12 +1241,14 @@ void Debug::PrepareStep(StepAction step_action, int step_count) {
uint32_t key = Smi::cast(*obj)->value();
// Argc in the stub is the number of arguments passed - not the
// expected arguments of the called function.
int call_function_arg_count = CodeStub::MinorKeyFromKey(key);
int call_function_arg_count =
CallFunctionStub::ExtractArgcFromMinorKey(
CodeStub::MinorKeyFromKey(key));
ASSERT(call_function_stub->major_key() ==
CodeStub::MajorKeyFromKey(key));
// Find target function on the expression stack.
// Expression stack lools like this (top to bottom):
// Expression stack looks like this (top to bottom):
// argN
// ...
// arg0
......
......@@ -379,6 +379,12 @@ enum InLoopFlag {
};
enum CallFunctionFlags {
NO_CALL_FUNCTION_FLAGS = 0,
RECEIVER_MIGHT_BE_VALUE = 1 << 0 // Receiver might not be a JSObject.
};
// Type of properties.
// Order of properties is significant.
// Must fit in the BitField PropertyDetails::TypeField.
......
......@@ -2289,6 +2289,7 @@ void CodeGenerator::Comparison(AstNode* node,
// Call the function just below TOS on the stack with the given
// arguments. The receiver is the TOS.
void CodeGenerator::CallWithArguments(ZoneList<Expression*>* args,
CallFunctionFlags flags,
int position) {
// Push the arguments ("left-to-right") on the stack.
int arg_count = args->length();
......@@ -2301,7 +2302,7 @@ void CodeGenerator::CallWithArguments(ZoneList<Expression*>* args,
// Use the shared code stub to call the function.
InLoopFlag in_loop = loop_nesting() > 0 ? IN_LOOP : NOT_IN_LOOP;
CallFunctionStub call_function(arg_count, in_loop);
CallFunctionStub call_function(arg_count, in_loop, flags);
Result answer = frame_->CallStub(&call_function, arg_count + 1);
// Restore context and replace function on the stack with the
// result of the stub invocation.
......@@ -2471,7 +2472,7 @@ void CodeGenerator::CallApplyLazy(Property* apply,
frame_->Push(&fn);
frame_->Push(&a1);
frame_->Push(&a2);
CallFunctionStub call_function(2, NOT_IN_LOOP);
CallFunctionStub call_function(2, NOT_IN_LOOP, NO_CALL_FUNCTION_FLAGS);
Result res = frame_->CallStub(&call_function, 3);
frame_->Push(&res);
......@@ -4746,7 +4747,7 @@ void CodeGenerator::VisitCall(Call* node) {
// Call the function.
CodeForSourcePosition(node->position());
InLoopFlag in_loop = loop_nesting() > 0 ? IN_LOOP : NOT_IN_LOOP;
CallFunctionStub call_function(arg_count, in_loop);
CallFunctionStub call_function(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE);
result = frame_->CallStub(&call_function, arg_count + 1);
// Restore the context and overwrite the function on the stack with
......@@ -4806,7 +4807,7 @@ void CodeGenerator::VisitCall(Call* node) {
frame_->EmitPush(edx);
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, NO_CALL_FUNCTION_FLAGS, node->position());
} else if (property != NULL) {
// Check if the key is a literal string.
......@@ -4872,7 +4873,7 @@ void CodeGenerator::VisitCall(Call* node) {
}
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, RECEIVER_MIGHT_BE_VALUE, node->position());
}
} else {
......@@ -4887,7 +4888,7 @@ void CodeGenerator::VisitCall(Call* node) {
LoadGlobalReceiver();
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, NO_CALL_FUNCTION_FLAGS, node->position());
}
}
......@@ -8681,6 +8682,33 @@ void StackCheckStub::Generate(MacroAssembler* masm) {
void CallFunctionStub::Generate(MacroAssembler* masm) {
Label slow;
// If the receiver might be a value (string, number or boolean) check for this
// and box it if it is.
if (ReceiverMightBeValue()) {
// Get the receiver from the stack.
// +1 ~ return address
Label receiver_is_value, receiver_is_js_object;
__ mov(eax, Operand(esp, (argc_ + 1) * kPointerSize));
// Check if receiver is a smi (which is a number value).
__ test(eax, Immediate(kSmiTagMask));
__ j(zero, &receiver_is_value, not_taken);
// Check if the receiver is a valid JS object.
__ CmpObjectType(eax, FIRST_JS_OBJECT_TYPE, edi);
__ j(above_equal, &receiver_is_js_object);
// Call the runtime to box the value.
__ bind(&receiver_is_value);
__ EnterInternalFrame();
__ push(eax);
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION);
__ LeaveInternalFrame();
__ mov(Operand(esp, (argc_ + 1) * kPointerSize), eax);
__ bind(&receiver_is_js_object);
}
// Get the function to call from the stack.
// +2 ~ receiver, return address
__ mov(edi, Operand(esp, (argc_ + 2) * kPointerSize));
......
......@@ -266,7 +266,7 @@ class CodeGenState BASE_EMBEDDED {
// -------------------------------------------------------------------------
// Arguments allocation mode
// Arguments allocation mode.
enum ArgumentsAllocationMode {
NO_ARGUMENTS_ALLOCATION,
......@@ -475,7 +475,9 @@ class CodeGenerator: public AstVisitor {
void StoreUnsafeSmiToLocal(int offset, Handle<Object> value);
void PushUnsafeSmi(Handle<Object> value);
void CallWithArguments(ZoneList<Expression*>* arguments, int position);
void CallWithArguments(ZoneList<Expression*>* arguments,
CallFunctionFlags flags,
int position);
// Use an optimized version of Function.prototype.apply that avoid
// allocating the arguments object and just copies the arguments
......@@ -620,39 +622,6 @@ class CodeGenerator: public AstVisitor {
};
class CallFunctionStub: public CodeStub {
public:
CallFunctionStub(int argc, InLoopFlag in_loop)
: argc_(argc), in_loop_(in_loop) { }
void Generate(MacroAssembler* masm);
private:
int argc_;
InLoopFlag in_loop_;
#ifdef DEBUG
void Print() { PrintF("CallFunctionStub (args %d)\n", argc_); }
#endif
Major MajorKey() { return CallFunction; }
int MinorKey() { return argc_; }
InLoopFlag InLoop() { return in_loop_; }
};
class ToBooleanStub: public CodeStub {
public:
ToBooleanStub() { }
void Generate(MacroAssembler* masm);
private:
Major MajorKey() { return ToBoolean; }
int MinorKey() { return 0; }
};
// Flag that indicates how to generate code for the stub GenericBinaryOpStub.
enum GenericBinaryFlags {
NO_GENERIC_BINARY_FLAGS = 0,
......
......@@ -1049,7 +1049,7 @@ void FastCodeGenerator::EmitCallWithStub(Call* expr) {
}
// Record source position for debugger.
SetSourcePosition(expr->position());
CallFunctionStub stub(arg_count, NOT_IN_LOOP);
CallFunctionStub stub(arg_count, NOT_IN_LOOP, RECEIVER_MIGHT_BE_VALUE);
__ CallStub(&stub);
// Restore context register.
__ mov(esi, Operand(ebp, StandardFrameConstants::kContextOffset));
......
......@@ -997,50 +997,65 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
break;
case STRING_CHECK:
// Check that the object is a two-byte string or a symbol.
__ mov(eax, FieldOperand(edx, HeapObject::kMapOffset));
__ movzx_b(eax, FieldOperand(eax, Map::kInstanceTypeOffset));
__ cmp(eax, FIRST_NONSTRING_TYPE);
__ j(above_equal, &miss, not_taken);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::STRING_FUNCTION_INDEX,
eax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), eax, holder,
ebx, edx, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
// Check that the object is a string or a symbol.
__ mov(eax, FieldOperand(edx, HeapObject::kMapOffset));
__ movzx_b(eax, FieldOperand(eax, Map::kInstanceTypeOffset));
__ cmp(eax, FIRST_NONSTRING_TYPE);
__ j(above_equal, &miss, not_taken);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::STRING_FUNCTION_INDEX,
eax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), eax, holder,
ebx, edx, name, &miss);
}
break;
case NUMBER_CHECK: {
Label fast;
// Check that the object is a smi or a heap number.
__ test(edx, Immediate(kSmiTagMask));
__ j(zero, &fast, taken);
__ CmpObjectType(edx, HEAP_NUMBER_TYPE, eax);
__ j(not_equal, &miss, not_taken);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::NUMBER_FUNCTION_INDEX,
eax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), eax, holder,
ebx, edx, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
Label fast;
// Check that the object is a smi or a heap number.
__ test(edx, Immediate(kSmiTagMask));
__ j(zero, &fast, taken);
__ CmpObjectType(edx, HEAP_NUMBER_TYPE, eax);
__ j(not_equal, &miss, not_taken);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::NUMBER_FUNCTION_INDEX,
eax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), eax, holder,
ebx, edx, name, &miss);
}
break;
}
case BOOLEAN_CHECK: {
Label fast;
// Check that the object is a boolean.
__ cmp(edx, Factory::true_value());
__ j(equal, &fast, taken);
__ cmp(edx, Factory::false_value());
__ j(not_equal, &miss, not_taken);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::BOOLEAN_FUNCTION_INDEX,
eax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), eax, holder,
ebx, edx, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
Label fast;
// Check that the object is a boolean.
__ cmp(edx, Factory::true_value());
__ j(equal, &fast, taken);
__ cmp(edx, Factory::false_value());
__ j(not_equal, &miss, not_taken);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::BOOLEAN_FUNCTION_INDEX,
eax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), eax, holder,
ebx, edx, name, &miss);
}
break;
}
......
......@@ -378,6 +378,18 @@ Object* CallIC::TryCallAsFunction(Object* object) {
return *delegate;
}
void CallIC::ReceiverToObject(Object* object) {
HandleScope scope;
Handle<Object> receiver(object);
// Change the receiver to the result of calling ToObject on it.
const int argc = this->target()->arguments_count();
StackFrameLocator locator;
JavaScriptFrame* frame = locator.FindJavaScriptFrame(0);
int index = frame->ComputeExpressionsCount() - (argc + 1);
frame->SetExpression(index, object->ToObject());
}
Object* CallIC::LoadFunction(State state,
Handle<Object> object,
......@@ -388,6 +400,10 @@ Object* CallIC::LoadFunction(State state,
return TypeError("non_object_property_call", object, name);
}
if (object->IsString() || object->IsNumber() || object->IsBoolean()) {
ReceiverToObject(*object);
}
// Check if the name is trivially convertible to an index and get
// the element if so.
uint32_t index;
......
......@@ -209,6 +209,8 @@ class CallIC: public IC {
// Otherwise, it returns the undefined value.
Object* TryCallAsFunction(Object* object);
void ReceiverToObject(Object* object);
static void Clear(Address address, Code* target);
friend class IC;
};
......
......@@ -815,7 +815,7 @@ void CodeGenerator::CallApplyLazy(Property* apply,
frame_->Push(&fn);
frame_->Push(&a1);
frame_->Push(&a2);
CallFunctionStub call_function(2, NOT_IN_LOOP);
CallFunctionStub call_function(2, NOT_IN_LOOP, NO_CALL_FUNCTION_FLAGS);
Result res = frame_->CallStub(&call_function, 3);
frame_->Push(&res);
......@@ -2706,7 +2706,7 @@ void CodeGenerator::VisitCall(Call* node) {
// Call the function.
CodeForSourcePosition(node->position());
InLoopFlag in_loop = loop_nesting() > 0 ? IN_LOOP : NOT_IN_LOOP;
CallFunctionStub call_function(arg_count, in_loop);
CallFunctionStub call_function(arg_count, in_loop, RECEIVER_MIGHT_BE_VALUE);
result = frame_->CallStub(&call_function, arg_count + 1);
// Restore the context and overwrite the function on the stack with
......@@ -2767,7 +2767,7 @@ void CodeGenerator::VisitCall(Call* node) {
frame_->EmitPush(rdx);
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, NO_CALL_FUNCTION_FLAGS, node->position());
} else if (property != NULL) {
// Check if the key is a literal string.
......@@ -2832,7 +2832,7 @@ void CodeGenerator::VisitCall(Call* node) {
}
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, RECEIVER_MIGHT_BE_VALUE, node->position());
}
} else {
......@@ -2847,7 +2847,7 @@ void CodeGenerator::VisitCall(Call* node) {
LoadGlobalReceiver();
// Call the function.
CallWithArguments(args, node->position());
CallWithArguments(args, NO_CALL_FUNCTION_FLAGS, node->position());
}
}
......@@ -6558,6 +6558,7 @@ void CompareStub::BranchIfNonSymbol(MacroAssembler* masm,
// Call the function just below TOS on the stack with the given
// arguments. The receiver is the TOS.
void CodeGenerator::CallWithArguments(ZoneList<Expression*>* args,
CallFunctionFlags flags,
int position) {
// Push the arguments ("left-to-right") on the stack.
int arg_count = args->length();
......@@ -6570,7 +6571,7 @@ void CodeGenerator::CallWithArguments(ZoneList<Expression*>* args,
// Use the shared code stub to call the function.
InLoopFlag in_loop = loop_nesting() > 0 ? IN_LOOP : NOT_IN_LOOP;
CallFunctionStub call_function(arg_count, in_loop);
CallFunctionStub call_function(arg_count, in_loop, flags);
Result answer = frame_->CallStub(&call_function, arg_count + 1);
// Restore context and replace function on the stack with the
// result of the stub invocation.
......@@ -6968,6 +6969,32 @@ void CEntryStub::GenerateThrowUncatchable(MacroAssembler* masm,
void CallFunctionStub::Generate(MacroAssembler* masm) {
Label slow;
// If the receiver might be a value (string, number or boolean) check for this
// and box it if it is.
if (ReceiverMightBeValue()) {
// Get the receiver from the stack.
// +1 ~ return address
Label receiver_is_value, receiver_is_js_object;
__ movq(rax, Operand(rsp, (argc_ + 1) * kPointerSize));
// Check if receiver is a smi (which is a number value).
__ JumpIfSmi(rax, &receiver_is_value);
// Check if the receiver is a valid JS object.
__ CmpObjectType(rax, FIRST_JS_OBJECT_TYPE, rdi);
__ j(above_equal, &receiver_is_js_object);
// Call the runtime to box the value.
__ bind(&receiver_is_value);
__ EnterInternalFrame();
__ push(rax);
__ InvokeBuiltin(Builtins::TO_OBJECT, CALL_FUNCTION);
__ LeaveInternalFrame();
__ movq(Operand(rsp, (argc_ + 1) * kPointerSize), rax);
__ bind(&receiver_is_js_object);
}
// Get the function to call from the stack.
// +2 ~ receiver, return address
__ movq(rdi, Operand(rsp, (argc_ + 2) * kPointerSize));
......
......@@ -474,7 +474,9 @@ class CodeGenerator: public AstVisitor {
// at most 16 bits of user-controlled data per assembly operation.
void LoadUnsafeSmi(Register target, Handle<Object> value);
void CallWithArguments(ZoneList<Expression*>* arguments, int position);
void CallWithArguments(ZoneList<Expression*>* arguments,
CallFunctionFlags flags,
int position);
// Use an optimized version of Function.prototype.apply that avoid
// allocating the arguments object and just copies the arguments
......@@ -617,46 +619,6 @@ class CodeGenerator: public AstVisitor {
};
// -------------------------------------------------------------------------
// Code stubs
//
// These independent code objects are created once, and used multiple
// times by generated code to perform common tasks, often the slow
// case of a JavaScript operation. They are all subclasses of CodeStub,
// which is declared in code-stubs.h.
class CallFunctionStub: public CodeStub {
public:
CallFunctionStub(int argc, InLoopFlag in_loop)
: argc_(argc), in_loop_(in_loop) { }
void Generate(MacroAssembler* masm);
private:
int argc_;
InLoopFlag in_loop_;
#ifdef DEBUG
void Print() { PrintF("CallFunctionStub (args %d)\n", argc_); }
#endif
Major MajorKey() { return CallFunction; }
int MinorKey() { return argc_; }
InLoopFlag InLoop() { return in_loop_; }
};
class ToBooleanStub: public CodeStub {
public:
ToBooleanStub() { }
void Generate(MacroAssembler* masm);
private:
Major MajorKey() { return ToBoolean; }
int MinorKey() { return 0; }
};
// Flag that indicates how to generate code for the stub GenericBinaryOpStub.
enum GenericBinaryFlags {
NO_GENERIC_BINARY_FLAGS = 0,
......
......@@ -1063,7 +1063,7 @@ void FastCodeGenerator::EmitCallWithStub(Call* expr) {
}
// Record source position for debugger.
SetSourcePosition(expr->position());
CallFunctionStub stub(arg_count, NOT_IN_LOOP);
CallFunctionStub stub(arg_count, NOT_IN_LOOP, RECEIVER_MIGHT_BE_VALUE);
__ CallStub(&stub);
// Restore context register.
__ movq(rsi, Operand(rbp, StandardFrameConstants::kContextOffset));
......
......@@ -721,47 +721,62 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
break;
case STRING_CHECK:
// Check that the object is a two-byte string or a symbol.
__ CmpObjectType(rdx, FIRST_NONSTRING_TYPE, rcx);
__ j(above_equal, &miss);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::STRING_FUNCTION_INDEX,
rcx);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rcx, holder,
rbx, rdx, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
// Check that the object is a two-byte string or a symbol.
__ CmpObjectType(rdx, FIRST_NONSTRING_TYPE, rcx);
__ j(above_equal, &miss);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::STRING_FUNCTION_INDEX,
rcx);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rcx, holder,
rbx, rdx, name, &miss);
}
break;
case NUMBER_CHECK: {
Label fast;
// Check that the object is a smi or a heap number.
__ JumpIfSmi(rdx, &fast);
__ CmpObjectType(rdx, HEAP_NUMBER_TYPE, rcx);
__ j(not_equal, &miss);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::NUMBER_FUNCTION_INDEX,
rcx);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rcx, holder,
rbx, rdx, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
Label fast;
// Check that the object is a smi or a heap number.
__ JumpIfSmi(rdx, &fast);
__ CmpObjectType(rdx, HEAP_NUMBER_TYPE, rcx);
__ j(not_equal, &miss);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::NUMBER_FUNCTION_INDEX,
rcx);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rcx, holder,
rbx, rdx, name, &miss);
}
break;
}
case BOOLEAN_CHECK: {
Label fast;
// Check that the object is a boolean.
__ CompareRoot(rdx, Heap::kTrueValueRootIndex);
__ j(equal, &fast);
__ CompareRoot(rdx, Heap::kFalseValueRootIndex);
__ j(not_equal, &miss);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::BOOLEAN_FUNCTION_INDEX,
rcx);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rcx, holder,
rbx, rdx, name, &miss);
if (!function->IsBuiltin()) {
// Calling non-builtins with a value as receiver requires boxing.
__ jmp(&miss);
} else {
Label fast;
// Check that the object is a boolean.
__ CompareRoot(rdx, Heap::kTrueValueRootIndex);
__ j(equal, &fast);
__ CompareRoot(rdx, Heap::kFalseValueRootIndex);
__ j(not_equal, &miss);
__ bind(&fast);
// Check that the maps starting from the prototype haven't changed.
GenerateLoadGlobalFunctionPrototype(masm(),
Context::BOOLEAN_FUNCTION_INDEX,
rcx);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rcx, holder,
rbx, rdx, name, &miss);
}
break;
}
......
Object.extend = function (dest, source) {
for (property in source) dest[property] = source[property];
return dest;
};
Object.extend ( Function.prototype,
{
wrap : function (wrapper) {
var method = this;
var bmethod = (function(_method) {
return function () {
this.$$$parentMethodStore$$$ = this.$proceed;
this.$proceed = function() { return _method.apply(this, arguments); };
};
})(method);
var amethod = function () {
this.$proceed = this.$$$parentMethodStore$$$;
if (this.$proceed == undefined) delete this.$proceed;
delete this.$$$parentMethodStore$$$;
};
var value = function() { bmethod.call(this); retval = wrapper.apply(this, arguments); amethod.call(this); return retval; };
return value;
}
});
String.prototype.cap = function() {
return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
};
String.prototype.cap = String.prototype.cap.wrap(
function(each) {
if (each && this.indexOf(" ") != -1) {
return this.split(" ").map(
function (value) {
return value.cap();
}
).join(" ");
} else {
return this.$proceed();
}
});
Object.extend( Array.prototype,
{
map : function(fun) {
if (typeof fun != "function") throw new TypeError();
var len = this.length;
var res = new Array(len);
var thisp = arguments[1];
for (var i = 0; i < len; i++) { if (i in this) res[i] = fun.call(thisp, this[i], i, this); }
return res;
}
});
assertEquals("Test1 test1", "test1 test1".cap());
assertEquals("Test2 Test2", "test2 test2".cap(true));
......@@ -28,12 +28,111 @@
// When calling user-defined functions on strings, booleans or
// numbers, we should create a wrapper object.
function RunTests() {
for (var i = 0; i < 10; i++) {
assertEquals('object', 'xxx'.TypeOfThis());
assertEquals('object', true.TypeOfThis(2,3));
assertEquals('object', false.TypeOfThis());
assertEquals('object', (42).TypeOfThis());
assertEquals('object', (3.14).TypeOfThis());
}
for (var i = 0; i < 10; i++) {
assertEquals('object', 'xxx'['TypeOfThis']());
assertEquals('object', true['TypeOfThis']());
assertEquals('object', false['TypeOfThis']());
assertEquals('object', (42)['TypeOfThis']());
assertEquals('object', (3.14)['TypeOfThis']());
}
function CallTypeOfThis(obj) {
assertEquals('object', obj.TypeOfThis());
}
for (var i = 0; i < 10; i++) {
CallTypeOfThis('xxx');
CallTypeOfThis(true);
CallTypeOfThis(false);
CallTypeOfThis(42);
CallTypeOfThis(3.14);
}
function TestWithWith(obj) {
with (obj) {
for (var i = 0; i < 10; i++) {
assertEquals('object', TypeOfThis());
}
}
}
TestWithWith('xxx');
TestWithWith(true);
TestWithWith(false);
TestWithWith(42);
TestWithWith(3.14);
for (var i = 0; i < 10; i++) {
assertEquals('object', true[7]());
assertEquals('object', false[7]());
assertEquals('object', (42)[7]());
assertEquals('object', (3.14)[7]());
}
}
function TypeOfThis() { return typeof this; }
// Test with normal setup of prototype.
String.prototype.TypeOfThis = TypeOfThis;
Boolean.prototype.TypeOfThis = TypeOfThis;
Number.prototype.TypeOfThis = TypeOfThis;
Boolean.prototype[7] = TypeOfThis;
Number.prototype[7] = TypeOfThis;
RunTests();
// Run test after properties have been set to a different value.
String.prototype.TypeOfThis = 'x';
Boolean.prototype.TypeOfThis = 'x';
Number.prototype.TypeOfThis = 'x';
Boolean.prototype[7] = 'x';
Number.prototype[7] = 'x';
String.prototype.TypeOfThis = TypeOfThis;
Boolean.prototype.TypeOfThis = TypeOfThis;
Number.prototype.TypeOfThis = TypeOfThis;
Boolean.prototype[7] = TypeOfThis;
Number.prototype[7] = TypeOfThis;
RunTests();
// Force the prototype into slow case and run the test again.
delete String.prototype.TypeOfThis;
delete Boolean.prototype.TypeOfThis;
delete Number.prototype.TypeOfThis;
Boolean.prototype[7];
Number.prototype[7];
String.prototype.TypeOfThis = TypeOfThis;
Boolean.prototype.TypeOfThis = TypeOfThis;
Number.prototype.TypeOfThis = TypeOfThis;
Boolean.prototype[7] = TypeOfThis;
Number.prototype[7] = TypeOfThis;
RunTests();
// According to ES3 15.3.4.3 the this value passed to Function.prototyle.apply
// should wrapped. According to ES5 it should not.
assertEquals('object', TypeOfThis.apply('xxx', []));
assertEquals('object', TypeOfThis.apply(true, []));
assertEquals('object', TypeOfThis.apply(false, []));
assertEquals('object', TypeOfThis.apply(42, []));
assertEquals('object', TypeOfThis.apply(3.14, []));
assertEquals('object', 'xxx'.TypeOfThis());
assertEquals('object', true.TypeOfThis());
assertEquals('object', (42).TypeOfThis());
// According to ES3 15.3.4.3 the this value passed to Function.prototyle.call
// should wrapped. According to ES5 it should not.
assertEquals('object', TypeOfThis.call('xxx'));
assertEquals('object', TypeOfThis.call(true));
assertEquals('object', TypeOfThis.call(false));
assertEquals('object', TypeOfThis.call(42));
assertEquals('object', TypeOfThis.call(3.14));
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