https://bugs.webkit.org/show_bug.cgi?id=158358
Reviewed by Geoffrey Garen.
This patch adds a new argument count bytecode. Normally, we would
just make sure that the argument.length bytecode was fast enough
that we shouldn't need such an bytecode. However, for the case of
Array.prototype.concat the overhead of the arguments object
allocation in the LLInt was too high and caused regressions.
* bytecode/BytecodeIntrinsicRegistry.h:
* bytecode/BytecodeList.json:
* bytecode/BytecodeUseDef.h:
(JSC::computeUsesForBytecodeOffset):
(JSC::computeDefsForBytecodeOffset):
* bytecode/CodeBlock.cpp:
(JSC::CodeBlock::dumpBytecode):
* bytecompiler/NodesCodegen.cpp:
(JSC::BytecodeIntrinsicNode::emit_intrinsic_argumentCount):
* dfg/DFGByteCodeParser.cpp:
(JSC::DFG::ByteCodeParser::getArgumentCount):
(JSC::DFG::ByteCodeParser::parseBlock):
* dfg/DFGCapabilities.cpp:
(JSC::DFG::capabilityLevel):
* jit/JIT.cpp:
(JSC::JIT::privateCompileMainPass):
* jit/JIT.h:
* jit/JITOpcodes.cpp:
(JSC::JIT::emit_op_argument_count):
* llint/LowLevelInterpreter32_64.asm:
* llint/LowLevelInterpreter64.asm:
* tests/stress/argument-count-bytecode.js: Added.
(inlineCount):
(inlineCount1):
(inlineCount2):
(inlineCountVarArgs):
(assert):
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@201668
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2016-06-03 Keith Miller <keith_miller@apple.com>
+
+ Add argument_count bytecode for concat
+ https://bugs.webkit.org/show_bug.cgi?id=158358
+
+ Reviewed by Geoffrey Garen.
+
+ This patch adds a new argument count bytecode. Normally, we would
+ just make sure that the argument.length bytecode was fast enough
+ that we shouldn't need such an bytecode. However, for the case of
+ Array.prototype.concat the overhead of the arguments object
+ allocation in the LLInt was too high and caused regressions.
+
+ * bytecode/BytecodeIntrinsicRegistry.h:
+ * bytecode/BytecodeList.json:
+ * bytecode/BytecodeUseDef.h:
+ (JSC::computeUsesForBytecodeOffset):
+ (JSC::computeDefsForBytecodeOffset):
+ * bytecode/CodeBlock.cpp:
+ (JSC::CodeBlock::dumpBytecode):
+ * bytecompiler/NodesCodegen.cpp:
+ (JSC::BytecodeIntrinsicNode::emit_intrinsic_argumentCount):
+ * dfg/DFGByteCodeParser.cpp:
+ (JSC::DFG::ByteCodeParser::getArgumentCount):
+ (JSC::DFG::ByteCodeParser::parseBlock):
+ * dfg/DFGCapabilities.cpp:
+ (JSC::DFG::capabilityLevel):
+ * jit/JIT.cpp:
+ (JSC::JIT::privateCompileMainPass):
+ * jit/JIT.h:
+ * jit/JITOpcodes.cpp:
+ (JSC::JIT::emit_op_argument_count):
+ * llint/LowLevelInterpreter32_64.asm:
+ * llint/LowLevelInterpreter64.asm:
+ * tests/stress/argument-count-bytecode.js: Added.
+ (inlineCount):
+ (inlineCount1):
+ (inlineCount2):
+ (inlineCountVarArgs):
+ (assert):
+
2016-06-03 Geoffrey Garen <ggaren@apple.com>
Clients of PolymorphicAccess::addCases shouldn't have to malloc
class Identifier;
#define JSC_COMMON_BYTECODE_INTRINSIC_FUNCTIONS_EACH_NAME(macro) \
+ macro(argumentCount) \
macro(assert) \
macro(isObject) \
macro(tryGetById) \
{ "name" : "op_create_scoped_arguments", "length" : 3 },
{ "name" : "op_create_cloned_arguments", "length" : 2 },
{ "name" : "op_create_this", "length" : 5 },
+ { "name" : "op_argument_count", "length" : 2 },
{ "name" : "op_to_this", "length" : 4 },
{ "name" : "op_check_tdz", "length" : 2 },
{ "name" : "op_new_object", "length" : 4 },
case op_jmp:
case op_new_object:
case op_enter:
+ case op_argument_count:
case op_catch:
case op_profile_control_flow:
case op_create_direct_arguments:
#undef LLINT_HELPER_OPCODES
return;
// These all have a single destination for the first argument.
+ case op_argument_count:
case op_to_index_string:
case op_get_enumerable_length:
case op_has_indexed_property:
out.printf("%s", registerName(r0).data());
break;
}
+ case op_argument_count: {
+ int r0 = (++it)->u.operand;
+ printLocationOpAndRegisterOperand(out, exec, location, it, "argument_count", r0);
+ break;
+ }
case op_copy_rest: {
int r0 = (++it)->u.operand;
int r1 = (++it)->u.operand;
return (this->*m_emitter)(generator, dst);
}
+RegisterID* BytecodeIntrinsicNode::emit_intrinsic_argumentCount(BytecodeGenerator& generator, RegisterID* dst)
+{
+ ASSERT(!m_args->m_listNode);
+
+ return generator.emitUnaryNoDstOp(op_argument_count, generator.finalDestination(dst));
+}
+
RegisterID* BytecodeIntrinsicNode::emit_intrinsic_assert(BytecodeGenerator& generator, RegisterID* dst)
{
#ifndef NDEBUG
forNode(node).setType(m_graph, SpecFunction);
break;
- case GetArgumentCount:
+ case GetArgumentCountIncludingThis:
forNode(node).setType(SpecInt32Only);
break;
Node* argumentCount;
if (!inlineCallFrame)
- argumentCount = insertionSet.insertNode(nodeIndex, SpecInt32Only, GetArgumentCount, origin);
+ argumentCount = insertionSet.insertNode(nodeIndex, SpecInt32Only, GetArgumentCountIncludingThis, origin);
else {
VirtualRegister argumentCountRegister(inlineCallFrame->stackOffset + JSStack::ArgumentCount);
Terminality handleVarargsCall(Instruction* pc, NodeType op, CallMode);
void emitFunctionChecks(CallVariant, Node* callTarget, VirtualRegister thisArgumnt);
void emitArgumentPhantoms(int registerOffset, int argumentCountIncludingThis);
+ Node* getArgumentCount();
unsigned inliningCost(CallVariant, int argumentCountIncludingThis, CallMode); // Return UINT_MAX if it's not an inlining candidate. By convention, intrinsics have a cost of 1.
// Handle inlining. Return true if it succeeded, false if we need to plant a call.
bool handleInlining(Node* callTargetNode, int resultOperand, const CallLinkStatus&, int registerOffset, VirtualRegister thisArgument, VirtualRegister argumentsArgument, unsigned argumentsOffset, int argumentCountIncludingThis, unsigned nextOffset, NodeType callOp, InlineCallFrame::Kind, SpeculatedType prediction);
addToGraph(CheckCell, OpInfo(m_graph.freeze(calleeCell)), callTargetForCheck, thisArgument);
}
+Node* ByteCodeParser::getArgumentCount()
+{
+ Node* argumentCount;
+ if (m_inlineStackTop->m_inlineCallFrame) {
+ if (m_inlineStackTop->m_inlineCallFrame->isVarargs())
+ argumentCount = get(VirtualRegister(JSStack::ArgumentCount));
+ else
+ argumentCount = jsConstant(m_graph.freeze(jsNumber(m_inlineStackTop->m_inlineCallFrame->arguments.size()))->value());
+ } else
+ argumentCount = addToGraph(GetArgumentCountIncludingThis, OpInfo(0), OpInfo(SpecInt32Only));
+ return argumentCount;
+}
+
void ByteCodeParser::emitArgumentPhantoms(int registerOffset, int argumentCountIncludingThis)
{
for (int i = 0; i < argumentCountIncludingThis; ++i)
set(VirtualRegister(currentInstruction[1].u.operand), result);
NEXT_OPCODE(op_get_scope);
}
-
+
+ case op_argument_count: {
+ Node* sub = addToGraph(ArithSub, OpInfo(Arith::Unchecked), OpInfo(SpecInt32Only), getArgumentCount(), addToGraph(JSConstant, OpInfo(m_constantOne)));
+
+ set(VirtualRegister(currentInstruction[1].u.operand), sub);
+ NEXT_OPCODE(op_argument_count);
+ }
+
case op_create_direct_arguments: {
noticeArgumentsUse();
Node* createArguments = addToGraph(CreateDirectArguments);
switch (opcodeID) {
case op_enter:
case op_to_this:
+ case op_argument_count:
case op_check_tdz:
case op_create_this:
case op_bitand:
def(HeapLocation(StackLoc, AbstractHeap(Stack, JSStack::Callee)), LazyNode(node));
return;
- case GetArgumentCount:
+ case GetArgumentCountIncludingThis:
read(AbstractHeap(Stack, JSStack::ArgumentCount));
def(HeapLocation(StackPayloadLoc, AbstractHeap(Stack, JSStack::ArgumentCount)), LazyNode(node));
return;
case LazyJSConstant:
case Identity:
case GetCallee:
- case GetArgumentCount:
+ case GetArgumentCountIncludingThis:
case GetRestLength:
case GetLocal:
case SetLocal:
case DoubleConstant:
case GetLocal:
case GetCallee:
- case GetArgumentCount:
+ case GetArgumentCountIncludingThis:
case GetRestLength:
case Flush:
case PhantomLocal:
case KillStack:
case GetStack:
case GetCallee:
- case GetArgumentCount:
+ case GetArgumentCountIncludingThis:
case GetRestLength:
case GetScope:
case PhantomLocal:
macro(ToThis, NodeResultJS) \
macro(CreateThis, NodeResultJS) /* Note this is not MustGenerate since we're returning it anyway. */ \
macro(GetCallee, NodeResultJS) \
- macro(GetArgumentCount, NodeResultInt32) \
+ macro(GetArgumentCountIncludingThis, NodeResultInt32) \
\
/* Nodes for local variable access. These nodes are linked together using Phi nodes. */\
/* Any two nodes that are part of the same Phi graph will share the same */\
break;
}
- case GetArgumentCount: {
+ case GetArgumentCountIncludingThis: {
setPrediction(SpecInt32Only);
break;
}
case ToThis:
case CreateThis:
case GetCallee:
- case GetArgumentCount:
+ case GetArgumentCountIncludingThis:
case GetRestLength:
case GetLocal:
case SetLocal:
break;
}
- case GetArgumentCount: {
+ case GetArgumentCountIncludingThis: {
GPRTemporary result(this);
m_jit.load32(JITCompiler::payloadFor(JSStack::ArgumentCount), result.gpr());
int32Result(result.gpr(), node);
break;
}
- case GetArgumentCount: {
+ case GetArgumentCountIncludingThis: {
GPRTemporary result(this);
m_jit.load32(JITCompiler::payloadFor(JSStack::ArgumentCount), result.gpr());
int32Result(result.gpr(), node);
case GetExecutable:
case GetScope:
case GetCallee:
- case GetArgumentCount:
+ case GetArgumentCountIncludingThis:
case ToString:
case CallStringConstructor:
case MakeRope:
case GetCallee:
compileGetCallee();
break;
- case GetArgumentCount:
- compileGetArgumentCount();
+ case GetArgumentCountIncludingThis:
+ compileGetArgumentCountIncludingThis();
break;
case GetScope:
compileGetScope();
setJSValue(m_out.loadPtr(addressFor(JSStack::Callee)));
}
- void compileGetArgumentCount()
+ void compileGetArgumentCountIncludingThis()
{
setInt32(m_out.load32(payloadFor(JSStack::ArgumentCount)));
}
DEFINE_OP(op_create_direct_arguments)
DEFINE_OP(op_create_scoped_arguments)
DEFINE_OP(op_create_cloned_arguments)
+ DEFINE_OP(op_argument_count)
DEFINE_OP(op_copy_rest)
DEFINE_OP(op_get_rest_length)
DEFINE_OP(op_check_tdz)
void emit_op_create_direct_arguments(Instruction*);
void emit_op_create_scoped_arguments(Instruction*);
void emit_op_create_cloned_arguments(Instruction*);
+ void emit_op_argument_count(Instruction*);
void emit_op_copy_rest(Instruction*);
void emit_op_get_rest_length(Instruction*);
void emit_op_check_tdz(Instruction*);
slowPathCall.call();
}
+void JIT::emit_op_argument_count(Instruction* currentInstruction)
+{
+ int dst = currentInstruction[1].u.operand;
+ load32(payloadFor(JSStack::ArgumentCount), regT0);
+ sub32(TrustedImm32(1), regT0);
+ JSValueRegs result = JSValueRegs::withTwoAvailableRegs(regT0, regT1);
+ boxInt32(regT0, result);
+ emitPutVirtualRegister(dst, result);
+}
+
void JIT::emit_op_copy_rest(Instruction* currentInstruction)
{
JITSlowPathCall slowPathCall(this, currentInstruction, slow_path_copy_rest);
dispatch(1)
+_llint_op_argument_count:
+ traceExecution()
+ loadisFromInstruction(1, t2)
+ loadi PayloadOffset + ArgumentCount[cfr], t0
+ subi 1, t0
+ move Int32Tag, t1
+ storei t1, TagOffset[cfr, t2, 8]
+ storei t0, PayloadOffset[cfr, t2, 8]
+ dispatch(2)
+
+
_llint_op_get_scope:
traceExecution()
loadi Callee + PayloadOffset[cfr], t0
dispatch(1)
+_llint_op_argument_count:
+ traceExecution()
+ loadisFromInstruction(1, t1)
+ loadi PayloadOffset + ArgumentCount[cfr], t0
+ subi 1, t0
+ orq TagTypeNumber, t0
+ storeq t0, [cfr, t1, 8]
+ dispatch(2)
+
+
_llint_op_get_scope:
traceExecution()
loadp Callee[cfr], t0
--- /dev/null
+count = createBuiltin("(function () { return @argumentCount(); })");
+countNoInline = createBuiltin("(function () { return @argumentCount(); })");
+noInline(countNoInline);
+
+
+function inlineCount() { return count(); }
+noInline(inlineCount);
+
+function inlineCount1() { return count(1); }
+noInline(inlineCount1);
+
+function inlineCount2() { return count(1,2); }
+noInline(inlineCount2);
+
+function inlineCountVarArgs(list) { return count(...list); }
+noInline(inlineCountVarArgs);
+
+function assert(condition, message) {
+ if (!condition)
+ throw new Error(message);
+}
+
+for (i = 0; i < 1000000; i++) {
+ assert(count(1,1,2) === 3, i);
+ assert(count() === 0, i);
+ assert(count(1) === 1, i);
+ assert(count(...[1,2,3,4,5]) === 5, i);
+ assert(count(...[]) === 0, i);
+ assert(inlineCount() === 0, i);
+ assert(inlineCount1() === 1, i);
+ assert(inlineCount2() === 2, i);
+ assert(inlineCountVarArgs([1,2,3,4]) === 4, i);
+ assert(inlineCountVarArgs([]) === 0, i);
+ // Insert extra junk so that inlineCountVarArgs.arguments.length !== count.arguments.length
+ assert(inlineCountVarArgs([1], 2, 4) === 1, i);
+ assert(countNoInline(4) === 1, i)
+ assert(countNoInline() === 0, i);
+}