Commit 65e286e6 authored by oliver@apple.com's avatar oliver@apple.com

Improve function.apply performance

Reviewed by Geoff Garen.

Jump through a few hoops to improve performance of function.apply in the general case.

In the case of zero or one arguments, or if there are only two arguments and the
second is an array literal we treat function.apply as function.call.

Otherwise we use the new opcodes op_load_varargs and op_call_varargs to do the .apply call
without re-entering the virtual machine.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@42337 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent 3d454b76
2009-04-07 Oliver Hunt <oliver@apple.com>
Reviewed by Geoff Garen.
Improve function.apply performance
Jump through a few hoops to improve performance of function.apply in the general case.
In the case of zero or one arguments, or if there are only two arguments and the
second is an array literal we treat function.apply as function.call.
Otherwise we use the new opcodes op_load_varargs and op_call_varargs to do the .apply call
without re-entering the virtual machine.
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::dump):
* bytecode/Opcode.h:
* bytecompiler/BytecodeGenerator.cpp:
(JSC::BytecodeGenerator::emitJumpIfNotFunctionApply):
(JSC::BytecodeGenerator::emitLoadVarargs):
(JSC::BytecodeGenerator::emitCallVarargs):
* bytecompiler/BytecodeGenerator.h:
* interpreter/Interpreter.cpp:
(JSC::Interpreter::privateExecute):
* jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
(JSC::JIT::privateCompileSlowCases):
* jit/JIT.h:
* jit/JITCall.cpp:
(JSC::JIT::compileOpCallSetupArgs):
(JSC::JIT::compileOpCallVarargsSetupArgs):
(JSC::JIT::compileOpCallVarargs):
(JSC::JIT::compileOpCallVarargsSlowCase):
* jit/JITStubs.cpp:
(JSC::JITStubs::cti_op_load_varargs):
* jit/JITStubs.h:
* parser/Grammar.y:
* parser/Nodes.cpp:
(JSC::ArrayNode::isSimpleArray):
(JSC::ArrayNode::toArgumentList):
(JSC::CallFunctionCallDotNode::emitBytecode):
(JSC::ApplyFunctionCallDotNode::emitBytecode):
* parser/Nodes.h:
(JSC::ExpressionNode::):
(JSC::ApplyFunctionCallDotNode::):
* runtime/Arguments.cpp:
(JSC::Arguments::copyToRegisters):
(JSC::Arguments::fillArgList):
* runtime/Arguments.h:
(JSC::Arguments::numProvidedArguments):
* runtime/FunctionPrototype.cpp:
(JSC::FunctionPrototype::addFunctionProperties):
* runtime/FunctionPrototype.h:
* runtime/JSArray.cpp:
(JSC::JSArray::copyToRegisters):
* runtime/JSArray.h:
* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::reset):
(JSC::JSGlobalObject::mark):
* runtime/JSGlobalObject.h:
2009-04-08 Alexey Proskuryakov <ap@webkit.org>
Reviewed by Darin Adler.
......@@ -965,6 +965,18 @@ void CodeBlock::dump(ExecState* exec, const Vector<Instruction>::const_iterator&
printf("[%4d] call_eval\t %s, %s, %d, %d\n", location, registerName(dst).c_str(), registerName(func).c_str(), argCount, registerOffset);
break;
}
case op_call_varargs: {
int dst = (++it)->u.operand;
int func = (++it)->u.operand;
int argCount = (++it)->u.operand;
int registerOffset = (++it)->u.operand;
printf("[%4d] call_varargs\t %s, %s, %s, %d\n", location, registerName(dst).c_str(), registerName(func).c_str(), registerName(argCount).c_str(), registerOffset);
break;
}
case op_load_varargs: {
printUnaryOp(location, it, "load_varargs");
break;
}
case op_tear_off_activation: {
int r0 = (++it)->u.operand;
printf("[%4d] tear_off_activation\t %s\n", location, registerName(r0).c_str());
......
......@@ -140,6 +140,8 @@ namespace JSC {
macro(op_new_func_exp, 3) \
macro(op_call, 5) \
macro(op_call_eval, 5) \
macro(op_call_varargs, 5) \
macro(op_load_varargs, 3) \
macro(op_tear_off_activation, 2) \
macro(op_tear_off_arguments, 1) \
macro(op_ret, 2) \
......
......@@ -705,6 +705,15 @@ PassRefPtr<Label> BytecodeGenerator::emitJumpIfNotFunctionCall(RegisterID* cond,
return target;
}
PassRefPtr<Label> BytecodeGenerator::emitJumpIfNotFunctionApply(RegisterID* cond, Label* target)
{
emitOpcode(op_jneq_ptr);
instructions().append(cond->index());
instructions().append(m_scopeChain->globalObject()->d()->applyFunction);
instructions().append(target->offsetFrom(instructions().size()));
return target;
}
unsigned BytecodeGenerator::addConstant(FuncDeclNode* n)
{
// No need to explicitly unique function body nodes -- they're unique already.
......@@ -1337,6 +1346,44 @@ RegisterID* BytecodeGenerator::emitCall(OpcodeID opcodeID, RegisterID* dst, Regi
return dst;
}
RegisterID* BytecodeGenerator::emitLoadVarargs(RegisterID* argCountDst, RegisterID* arguments)
{
ASSERT(argCountDst->index() < arguments->index());
emitOpcode(op_load_varargs);
instructions().append(argCountDst->index());
instructions().append(arguments->index());
return argCountDst;
}
RegisterID* BytecodeGenerator::emitCallVarargs(RegisterID* dst, RegisterID* func, RegisterID* thisRegister, RegisterID* argCountRegister, unsigned divot, unsigned startOffset, unsigned endOffset)
{
ASSERT(func->refCount());
ASSERT(thisRegister->refCount());
ASSERT(dst != func);
if (m_shouldEmitProfileHooks) {
emitOpcode(op_profile_will_call);
instructions().append(func->index());
#if ENABLE(JIT)
m_codeBlock->addFunctionRegisterInfo(instructions().size(), func->index());
#endif
}
emitExpressionInfo(divot, startOffset, endOffset);
// Emit call.
emitOpcode(op_call_varargs);
instructions().append(dst->index()); // dst
instructions().append(func->index()); // func
instructions().append(argCountRegister->index()); // arg count
instructions().append(thisRegister->index() + RegisterFile::CallFrameHeaderSize); // initial registerOffset
if (m_shouldEmitProfileHooks) {
emitOpcode(op_profile_did_call);
instructions().append(func->index());
}
return dst;
}
RegisterID* BytecodeGenerator::emitReturn(RegisterID* src)
{
if (m_codeBlock->needsFullScopeChain()) {
......
......@@ -288,6 +288,8 @@ namespace JSC {
RegisterID* emitCall(RegisterID* dst, RegisterID* func, RegisterID* thisRegister, ArgumentsNode*, unsigned divot, unsigned startOffset, unsigned endOffset);
RegisterID* emitCallEval(RegisterID* dst, RegisterID* func, RegisterID* thisRegister, ArgumentsNode*, unsigned divot, unsigned startOffset, unsigned endOffset);
RegisterID* emitCallVarargs(RegisterID* dst, RegisterID* func, RegisterID* thisRegister, RegisterID* argCount, unsigned divot, unsigned startOffset, unsigned endOffset);
RegisterID* emitLoadVarargs(RegisterID* argCountDst, RegisterID* args);
RegisterID* emitReturn(RegisterID* src);
RegisterID* emitEnd(RegisterID* src) { return emitUnaryNoDstOp(op_end, src); }
......@@ -299,6 +301,7 @@ namespace JSC {
PassRefPtr<Label> emitJumpIfTrue(RegisterID* cond, Label* target);
PassRefPtr<Label> emitJumpIfFalse(RegisterID* cond, Label* target);
PassRefPtr<Label> emitJumpIfNotFunctionCall(RegisterID* cond, Label* target);
PassRefPtr<Label> emitJumpIfNotFunctionApply(RegisterID* cond, Label* target);
PassRefPtr<Label> emitJumpScopes(Label* target, int targetScopeDepth);
PassRefPtr<Label> emitJumpSubroutine(RegisterID* retAddrDst, Label*);
......@@ -365,7 +368,7 @@ namespace JSC {
typedef HashMap<RefPtr<UString::Rep>, int, IdentifierRepHash, HashTraits<RefPtr<UString::Rep> >, IdentifierMapIndexHashTraits> IdentifierMap;
typedef HashMap<double, JSValuePtr> NumberMap;
typedef HashMap<UString::Rep*, JSString*, IdentifierRepHash> IdentifierStringMap;
RegisterID* emitCall(OpcodeID, RegisterID* dst, RegisterID* func, RegisterID* thisRegister, ArgumentsNode*, unsigned divot, unsigned startOffset, unsigned endOffset);
RegisterID* newRegister();
......
......@@ -2972,6 +2972,141 @@ JSValuePtr Interpreter::privateExecute(ExecutionFlag flag, RegisterFile* registe
exceptionValue = createNotAFunctionError(callFrame, v, vPC - callFrame->codeBlock()->instructions().begin(), callFrame->codeBlock());
goto vm_throw;
}
DEFINE_OPCODE(op_load_varargs) {
int argCountDst = (++vPC)->u.operand;
int argsOffset = (++vPC)->u.operand;
JSValuePtr arguments = callFrame[argsOffset].jsValue(callFrame);
uint32_t argCount = 0;
if (!arguments.isUndefinedOrNull()) {
if (!arguments.isObject()) {
exceptionValue = createInvalidParamError(callFrame, "Function.prototype.apply", arguments, vPC - callFrame->codeBlock()->instructions().begin(), callFrame->codeBlock());
goto vm_throw;
}
if (asObject(arguments)->classInfo() == &Arguments::info) {
Arguments* args = asArguments(arguments);
argCount = args->numProvidedArguments(callFrame);
int32_t sizeDelta = argsOffset + argCount + RegisterFile::CallFrameHeaderSize;
Register* newEnd = callFrame->registers() + sizeDelta;
if (!registerFile->grow(newEnd) || ((newEnd - callFrame->registers()) != sizeDelta)) {
exceptionValue = createStackOverflowError(callFrame);
goto vm_throw;
}
args->copyToRegisters(callFrame, callFrame->registers() + argsOffset, argCount);
} else if (isJSArray(&callFrame->globalData(), arguments)) {
JSArray* array = asArray(arguments);
argCount = array->length();
int32_t sizeDelta = argsOffset + argCount + RegisterFile::CallFrameHeaderSize;
Register* newEnd = callFrame->registers() + sizeDelta;
if (!registerFile->grow(newEnd) || ((newEnd - callFrame->registers()) != sizeDelta)) {
exceptionValue = createStackOverflowError(callFrame);
goto vm_throw;
}
array->copyToRegisters(callFrame, callFrame->registers() + argsOffset, argCount);
} else if (asObject(arguments)->inherits(&JSArray::info)) {
JSObject* argObject = asObject(arguments);
argCount = argObject->get(callFrame, callFrame->propertyNames().length).toUInt32(callFrame);
int32_t sizeDelta = argsOffset + argCount + RegisterFile::CallFrameHeaderSize;
Register* newEnd = callFrame->registers() + sizeDelta;
if (!registerFile->grow(newEnd) || ((newEnd - callFrame->registers()) != sizeDelta)) {
exceptionValue = createStackOverflowError(callFrame);
goto vm_throw;
}
Register* argsBuffer = callFrame->registers() + argsOffset;
for (unsigned i = 0; i < argCount; ++i) {
argsBuffer[i] = asObject(arguments)->get(callFrame, i);
CHECK_FOR_EXCEPTION();
}
} else {
if (!arguments.isObject()) {
exceptionValue = createInvalidParamError(callFrame, "Function.prototype.apply", arguments, vPC - callFrame->codeBlock()->instructions().begin(), callFrame->codeBlock());
goto vm_throw;
}
}
}
CHECK_FOR_EXCEPTION();
callFrame[argCountDst] = argCount + 1;
++vPC;
NEXT_INSTRUCTION();
}
DEFINE_OPCODE(op_call_varargs) {
/* call_varargs dst(r) func(r) argCountReg(r) baseRegisterOffset(n)
Perform a function call with a dynamic set of arguments.
registerOffset is the distance the callFrame pointer should move
before the VM initializes the new call frame's header, excluding
space for arguments.
dst is where op_ret should store its result.
*/
int dst = vPC[1].u.operand;
int func = vPC[2].u.operand;
int argCountReg = vPC[3].u.operand;
int registerOffset = vPC[4].u.operand;
JSValuePtr v = callFrame[func].jsValue(callFrame);
int argCount = callFrame[argCountReg].i();
registerOffset += argCount;
CallData callData;
CallType callType = v.getCallData(callData);
if (callType == CallTypeJS) {
ScopeChainNode* callDataScopeChain = callData.js.scopeChain;
FunctionBodyNode* functionBodyNode = callData.js.functionBody;
CodeBlock* newCodeBlock = &functionBodyNode->bytecode(callDataScopeChain);
CallFrame* previousCallFrame = callFrame;
callFrame = slideRegisterWindowForCall(newCodeBlock, registerFile, callFrame, registerOffset, argCount);
if (UNLIKELY(!callFrame)) {
callFrame = previousCallFrame;
exceptionValue = createStackOverflowError(callFrame);
goto vm_throw;
}
callFrame->init(newCodeBlock, vPC + 5, callDataScopeChain, previousCallFrame, dst, argCount, asFunction(v));
vPC = newCodeBlock->instructions().begin();
#if ENABLE(OPCODE_STATS)
OpcodeStats::resetLastInstruction();
#endif
NEXT_INSTRUCTION();
}
if (callType == CallTypeHost) {
ScopeChainNode* scopeChain = callFrame->scopeChain();
CallFrame* newCallFrame = CallFrame::create(callFrame->registers() + registerOffset);
newCallFrame->init(0, vPC + 5, scopeChain, callFrame, dst, argCount, 0);
Register* thisRegister = newCallFrame->registers() - RegisterFile::CallFrameHeaderSize - argCount;
ArgList args(thisRegister + 1, argCount - 1);
// FIXME: All host methods should be calling toThisObject, but this is not presently the case.
JSValuePtr thisValue = thisRegister->jsValue(callFrame);
if (thisValue == jsNull())
thisValue = callFrame->globalThisValue();
JSValuePtr returnValue;
{
SamplingTool::HostCallRecord callRecord(m_sampler);
returnValue = callData.native.function(newCallFrame, asObject(v), thisValue, args);
}
CHECK_FOR_EXCEPTION();
callFrame[dst] = JSValuePtr(returnValue);
vPC += 5;
NEXT_INSTRUCTION();
}
ASSERT(callType == CallTypeNone);
exceptionValue = createNotAFunctionError(callFrame, v, vPC - callFrame->codeBlock()->instructions().begin(), callFrame->codeBlock());
goto vm_throw;
}
DEFINE_OPCODE(op_tear_off_activation) {
/* tear_off_activation activation(r)
......
......@@ -496,6 +496,16 @@ void JIT::privateCompileMainPass()
compileOpCall(opcodeID, currentInstruction, callLinkInfoIndex++);
NEXT_OPCODE(op_call_eval);
}
case op_load_varargs: {
emitPutJITStubArgConstant(currentInstruction[2].u.operand, 1);
emitCTICall(JITStubs::cti_op_load_varargs);
emitPutVirtualRegister(currentInstruction[1].u.operand);
NEXT_OPCODE(op_load_varargs);
}
case op_call_varargs: {
compileOpCallVarargs(currentInstruction);
NEXT_OPCODE(op_call_varargs);
}
case op_construct: {
compileOpCall(opcodeID, currentInstruction, callLinkInfoIndex++);
NEXT_OPCODE(op_construct);
......@@ -1596,6 +1606,10 @@ void JIT::privateCompileSlowCases()
compileOpCallSlowCase(currentInstruction, iter, callLinkInfoIndex++, opcodeID);
NEXT_OPCODE(op_call_eval);
}
case op_call_varargs: {
compileOpCallVarargsSlowCase(currentInstruction, iter);
NEXT_OPCODE(op_call_varargs);
}
case op_construct: {
compileOpCallSlowCase(currentInstruction, iter, callLinkInfoIndex++, opcodeID);
NEXT_OPCODE(op_construct);
......
......@@ -412,10 +412,13 @@ namespace JSC {
void compilePutByIdHotPath(int baseVReg, Identifier* ident, int valueVReg, unsigned propertyAccessInstructionIndex);
void compilePutByIdSlowCase(int baseVReg, Identifier* ident, int valueVReg, Vector<SlowCaseEntry>::iterator& iter, unsigned propertyAccessInstructionIndex);
void compileOpCall(OpcodeID, Instruction* instruction, unsigned callLinkInfoIndex);
void compileOpCallVarargs(Instruction* instruction);
void compileOpCallInitializeCallFrame();
void compileOpCallSetupArgs(Instruction*);
void compileOpCallVarargsSetupArgs(Instruction*);
void compileOpCallEvalSetupArgs(Instruction*);
void compileOpCallSlowCase(Instruction* instruction, Vector<SlowCaseEntry>::iterator& iter, unsigned callLinkInfoIndex, OpcodeID opcodeID);
void compileOpCallVarargsSlowCase(Instruction* instruction, Vector<SlowCaseEntry>::iterator& iter);
void compileOpConstructSetupArgs(Instruction*);
enum CompileOpStrictEqType { OpStrictEq, OpNStrictEq };
void compileOpStrictEq(Instruction* instruction, CompileOpStrictEqType type);
......
......@@ -87,8 +87,19 @@ void JIT::compileOpCallSetupArgs(Instruction* instruction)
// ecx holds func
emitPutJITStubArg(regT2, 1);
emitPutJITStubArgConstant(registerOffset, 2);
emitPutJITStubArgConstant(argCount, 3);
emitPutJITStubArgConstant(registerOffset, 2);
}
void JIT::compileOpCallVarargsSetupArgs(Instruction* instruction)
{
int registerOffset = instruction[4].u.operand;
// ecx holds func
emitPutJITStubArg(regT2, 1);
emitPutJITStubArg(regT1, 3);
addPtr(Imm32(registerOffset), regT1, regT0);
emitPutJITStubArg(regT0, 2);
}
void JIT::compileOpCallEvalSetupArgs(Instruction* instruction)
......@@ -334,6 +345,50 @@ void JIT::compileOpCallSlowCase(Instruction* instruction, Vector<SlowCaseEntry>:
#endif
void JIT::compileOpCallVarargs(Instruction* instruction)
{
int dst = instruction[1].u.operand;
int callee = instruction[2].u.operand;
int argCountRegister = instruction[3].u.operand;
emitGetVirtualRegister(argCountRegister, regT1);
emitGetVirtualRegister(callee, regT2);
compileOpCallVarargsSetupArgs(instruction);
// Check for JSFunctions.
emitJumpSlowCaseIfNotJSCell(regT2);
addSlowCase(branchPtr(NotEqual, Address(regT2), ImmPtr(m_globalData->jsFunctionVPtr)));
// Speculatively roll the callframe, assuming argCount will match the arity.
mul32(Imm32(sizeof(Register)), regT0, regT0);
intptr_t offset = (intptr_t)sizeof(Register) * (intptr_t)RegisterFile::CallerFrame;
addPtr(Imm32((int32_t)offset), regT0, regT3);
addPtr(callFrameRegister, regT3);
storePtr(callFrameRegister, regT3);
addPtr(regT0, callFrameRegister);
emitNakedCall(m_globalData->jitStubs.ctiVirtualCall());
// Put the return value in dst. In the interpreter, op_ret does this.
emitPutVirtualRegister(dst);
sampleCodeBlock(m_codeBlock);
}
void JIT::compileOpCallVarargsSlowCase(Instruction* instruction, Vector<SlowCaseEntry>::iterator& iter)
{
int dst = instruction[1].u.operand;
linkSlowCase(iter);
linkSlowCase(iter);
// This handles host functions
emitCTICall(JITStubs::cti_op_call_NotJSFunction);
// Put the return value in dst. In the interpreter, op_ret does this.
emitPutVirtualRegister(dst);
sampleCodeBlock(m_codeBlock);
}
} // namespace JSC
#endif // ENABLE(JIT)
......@@ -1374,6 +1374,66 @@ int JITStubs::cti_op_loop_if_true(STUB_ARGS)
CHECK_FOR_EXCEPTION_AT_END();
return result;
}
int JITStubs::cti_op_load_varargs(STUB_ARGS)
{
BEGIN_STUB_FUNCTION();
CallFrame* callFrame = ARG_callFrame;
RegisterFile* registerFile = ARG_registerFile;
int argsOffset = ARG_int1;
JSValuePtr arguments = callFrame[argsOffset].jsValue(callFrame);
uint32_t argCount = 0;
if (!arguments.isUndefinedOrNull()) {
if (!arguments.isObject()) {
CodeBlock* codeBlock = callFrame->codeBlock();
unsigned vPCIndex = codeBlock->getBytecodeIndex(callFrame, STUB_RETURN_ADDRESS);
ARG_globalData->exception = createInvalidParamError(callFrame, "Function.prototype.apply", arguments, vPCIndex, codeBlock);
VM_THROW_EXCEPTION();
}
if (asObject(arguments)->classInfo() == &Arguments::info) {
Arguments* argsObject = asArguments(arguments);
argCount = argsObject->numProvidedArguments(callFrame);
int32_t sizeDelta = argsOffset + argCount + RegisterFile::CallFrameHeaderSize;
Register* newEnd = callFrame->registers() + sizeDelta;
if (!registerFile->grow(newEnd) || ((newEnd - callFrame->registers()) != sizeDelta)) {
ARG_globalData->exception = createStackOverflowError(callFrame);
VM_THROW_EXCEPTION();
}
argsObject->copyToRegisters(callFrame, callFrame->registers() + argsOffset, argCount);
} else if (isJSArray(&callFrame->globalData(), arguments)) {
JSArray* array = asArray(arguments);
argCount = array->length();
int32_t sizeDelta = argsOffset + argCount + RegisterFile::CallFrameHeaderSize;
Register* newEnd = callFrame->registers() + sizeDelta;
if (!registerFile->grow(newEnd) || ((newEnd - callFrame->registers()) != sizeDelta)) {
ARG_globalData->exception = createStackOverflowError(callFrame);
VM_THROW_EXCEPTION();
}
array->copyToRegisters(callFrame, callFrame->registers() + argsOffset, argCount);
} else if (asObject(arguments)->inherits(&JSArray::info)) {
JSObject* argObject = asObject(arguments);
argCount = argObject->get(callFrame, callFrame->propertyNames().length).toUInt32(callFrame);
int32_t sizeDelta = argsOffset + argCount + RegisterFile::CallFrameHeaderSize;
Register* newEnd = callFrame->registers() + sizeDelta;
if (!registerFile->grow(newEnd) || ((newEnd - callFrame->registers()) != sizeDelta)) {
ARG_globalData->exception = createStackOverflowError(callFrame);
VM_THROW_EXCEPTION();
}
Register* argsBuffer = callFrame->registers() + argsOffset;
for (unsigned i = 0; i < argCount; ++i) {
argsBuffer[i] = asObject(arguments)->get(callFrame, i);
CHECK_FOR_EXCEPTION();
}
} else {
CodeBlock* codeBlock = callFrame->codeBlock();
unsigned vPCIndex = codeBlock->getBytecodeIndex(callFrame, STUB_RETURN_ADDRESS);
ARG_globalData->exception = createInvalidParamError(callFrame, "Function.prototype.apply", arguments, vPCIndex, codeBlock);
VM_THROW_EXCEPTION();
}
}
CHECK_FOR_EXCEPTION_AT_END();
return argCount + 1;
}
JSValueEncodedAsPointer* JITStubs::cti_op_negate(STUB_ARGS)
{
......
......@@ -170,6 +170,7 @@ namespace JSC {
static int JIT_STUB cti_op_loop_if_less(STUB_ARGS);
static int JIT_STUB cti_op_loop_if_lesseq(STUB_ARGS);
static int JIT_STUB cti_op_loop_if_true(STUB_ARGS);
static int JIT_STUB cti_op_load_varargs(STUB_ARGS);
static int JIT_STUB cti_timeout_check(STUB_ARGS);
static void JIT_STUB cti_op_create_arguments(STUB_ARGS);
static void JIT_STUB cti_op_create_arguments_no_params(STUB_ARGS);
......
......@@ -1928,6 +1928,8 @@ static ExpressionNodeInfo makeFunctionCallNode(void* globalPtr, ExpressionNodeIn
FunctionCallDotNode* node;
if (dot->identifier() == GLOBAL_DATA->propertyNames->call)
node = new CallFunctionCallDotNode(GLOBAL_DATA, dot->base(), dot->identifier(), args.m_node, divot, divot - start, end - divot);
else if (dot->identifier() == GLOBAL_DATA->propertyNames->apply)
node = new ApplyFunctionCallDotNode(GLOBAL_DATA, dot->base(), dot->identifier(), args.m_node, divot, divot - start, end - divot);
else
node = new FunctionCallDotNode(GLOBAL_DATA, dot->base(), dot->identifier(), args.m_node, divot, divot - start, end - divot);
node->setSubexpressionInfo(dot->divot(), dot->endOffset());
......
......@@ -404,6 +404,34 @@ RegisterID* ArrayNode::emitBytecode(BytecodeGenerator& generator, RegisterID* ds
return generator.moveToDestinationIfNeeded(dst, array.get());
}
bool ArrayNode::isSimpleArray() const
{
if (m_elision || m_optional)
return false;
for (ElementNode* ptr = m_element.get(); ptr; ptr = ptr->next()) {
if (ptr->elision())
return false;
}
return true;
}
PassRefPtr<ArgumentListNode> ArrayNode::toArgumentList(JSGlobalData* globalData) const
{
ASSERT(!m_elision && !m_optional);
RefPtr<ArgumentListNode> head;
ElementNode* ptr = m_element.get();
if (!ptr)
return head;
head = new ArgumentListNode(globalData, ptr->value());
ArgumentListNode* tail = head.get();
ptr = ptr->next();
for (; ptr; ptr = ptr->next()) {
ASSERT(!ptr->elision());
tail = new ArgumentListNode(globalData, tail, ptr->value());
}
return head.release();
}
// ------------------------------ PropertyNode ----------------------------
PropertyNode::~PropertyNode()
......@@ -710,9 +738,9 @@ RegisterID* CallFunctionCallDotNode::emitBytecode(BytecodeGenerator& generator,
if (m_args->m_listNode && m_args->m_listNode->m_expr) {
generator.emitNode(thisRegister.get(), m_args->m_listNode->m_expr.get());
m_args->m_listNode = m_args->m_listNode->m_next;
} else {
} else
generator.emitLoad(thisRegister.get(), jsNull());
}
generator.emitCall(finalDestination.get(), realFunction.get(), thisRegister.get(), m_args.get(), divot(), startOffset(), endOffset());
generator.emitJump(end.get());
m_args->m_listNode = oldList;
......@@ -725,6 +753,69 @@ RegisterID* CallFunctionCallDotNode::emitBytecode(BytecodeGenerator& generator,
generator.emitLabel(end.get());
return finalDestination.get();
}
static bool areTrivialApplyArguments(ArgumentsNode* args)
{
return !args->m_listNode || !args->m_listNode->m_expr || !args->m_listNode->m_next
|| (!args->m_listNode->m_next->m_next && args->m_listNode->m_next->m_expr->isSimpleArray());
}
RegisterID* ApplyFunctionCallDotNode::emitBytecode(BytecodeGenerator& generator, RegisterID* dst)
{
// A few simple cases can be trivially handled as ordinary functions calls
// function.apply(), function.apply(arg) -> identical to function.call
// function.apply(thisArg, [arg0, arg1, ...]) -> can be trivially coerced into function.call(thisArg, arg0, arg1, ...) and saves object allocation
bool mayBeCall = areTrivialApplyArguments(m_args.get());
RefPtr<Label> realCall = generator.newLabel();
RefPtr<Label> end = generator.newLabel();
RefPtr<RegisterID> base = generator.emitNode(m_base.get());
generator.emitExpressionInfo(divot() - m_subexpressionDivotOffset, startOffset() - m_subexpressionDivotOffset, m_subexpressionEndOffset);
RefPtr<RegisterID> function = generator.emitGetById(generator.tempDestination(dst), base.get(), m_ident);
RefPtr<RegisterID> finalDestination = generator.finalDestination(dst, function.get());
generator.emitJumpIfNotFunctionApply(function.get(), realCall.get());
{
if (mayBeCall) {
RefPtr<RegisterID> realFunction = generator.emitMove(generator.tempDestination(dst), base.get());
RefPtr<RegisterID> thisRegister = generator.newTemporary();
RefPtr<ArgumentListNode> oldList = m_args->m_listNode;
if (m_args->m_listNode && m_args->m_listNode->m_expr) {
generator.emitNode(thisRegister.get(), m_args->m_listNode->m_expr.get());
m_args->m_listNode = m_args->m_listNode->m_next;