X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/b80e619319b1def83d1e8b4f84042b661be1be7f..14957cd040308e3eeec43d26bae5d76da13fcd85:/dfg/DFGByteCodeParser.cpp diff --git a/dfg/DFGByteCodeParser.cpp b/dfg/DFGByteCodeParser.cpp new file mode 100644 index 0000000..7c333de --- /dev/null +++ b/dfg/DFGByteCodeParser.cpp @@ -0,0 +1,1198 @@ +/* + * Copyright (C) 2011 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DFGByteCodeParser.h" + +#if ENABLE(DFG_JIT) + +#include "DFGAliasTracker.h" +#include "DFGScoreBoard.h" +#include "CodeBlock.h" + +namespace JSC { namespace DFG { + +#if ENABLE(DFG_JIT_RESTRICTIONS) +// FIXME: Temporarily disable arithmetic, until we fix associated performance regressions. +#define ARITHMETIC_OP() m_parseFailed = true +#else +#define ARITHMETIC_OP() ((void)0) +#endif + +// === ByteCodeParser === +// +// This class is used to compile the dataflow graph from a CodeBlock. +class ByteCodeParser { +public: + ByteCodeParser(JSGlobalData* globalData, CodeBlock* codeBlock, Graph& graph) + : m_globalData(globalData) + , m_codeBlock(codeBlock) + , m_graph(graph) + , m_currentIndex(0) + , m_parseFailed(false) + , m_constantUndefined(UINT_MAX) + , m_constantNull(UINT_MAX) + , m_constant1(UINT_MAX) + , m_constants(codeBlock->numberOfConstantRegisters()) + , m_numArguments(codeBlock->m_numParameters) + , m_numLocals(codeBlock->m_numCalleeRegisters) + , m_preservedVars(codeBlock->m_numVars) + { + } + + // Parse a full CodeBlock of bytecode. + bool parse(); + +private: + // Parse a single basic block of bytecode instructions. + bool parseBlock(unsigned limit); + // Setup predecessor links in the graph's BasicBlocks. + void setupPredecessors(); + // Link GetLocal & SetLocal nodes, to ensure live values are generated. + enum PhiStackType { + LocalPhiStack, + ArgumentPhiStack + }; + template + void processPhiStack(); + // Add spill locations to nodes. + void allocateVirtualRegisters(); + + // Get/Set the operands/result of a bytecode instruction. + NodeIndex get(int operand) + { + // Is this a constant? + if (operand >= FirstConstantRegisterIndex) { + unsigned constant = operand - FirstConstantRegisterIndex; + ASSERT(constant < m_constants.size()); + return getJSConstant(constant); + } + + // Is this an argument? + if (operandIsArgument(operand)) + return getArgument(operand); + + // Must be a local. + return getLocal((unsigned)operand); + } + void set(int operand, NodeIndex value, PredictedType prediction = PredictNone) + { + m_graph.predict(operand, prediction); + + // Is this an argument? + if (operandIsArgument(operand)) { + setArgument(operand, value); + return; + } + + // Must be a local. + setLocal((unsigned)operand, value); + } + + // Used in implementing get/set, above, where the operand is a local variable. + NodeIndex getLocal(unsigned operand) + { + NodeIndex nodeIndex = m_currentBlock->m_locals[operand].value; + + if (nodeIndex != NoNode) { + Node& node = m_graph[nodeIndex]; + if (node.op == GetLocal) + return nodeIndex; + ASSERT(node.op == SetLocal); + return node.child1; + } + + // Check for reads of temporaries from prior blocks, + // expand m_preservedVars to cover these. + m_preservedVars = std::max(m_preservedVars, operand + 1); + + NodeIndex phi = addToGraph(Phi); + m_localPhiStack.append(PhiStackEntry(m_currentBlock, phi, operand)); + nodeIndex = addToGraph(GetLocal, OpInfo(operand), phi); + m_currentBlock->m_locals[operand].value = nodeIndex; + return nodeIndex; + } + void setLocal(unsigned operand, NodeIndex value) + { + m_currentBlock->m_locals[operand].value = addToGraph(SetLocal, OpInfo(operand), value); + } + + // Used in implementing get/set, above, where the operand is an argument. + NodeIndex getArgument(unsigned operand) + { + unsigned argument = operand + m_codeBlock->m_numParameters + RegisterFile::CallFrameHeaderSize; + ASSERT(argument < m_numArguments); + + NodeIndex nodeIndex = m_currentBlock->m_arguments[argument].value; + + if (nodeIndex != NoNode) { + Node& node = m_graph[nodeIndex]; + if (node.op == GetLocal) + return nodeIndex; + ASSERT(node.op == SetLocal); + return node.child1; + } + + NodeIndex phi = addToGraph(Phi); + m_argumentPhiStack.append(PhiStackEntry(m_currentBlock, phi, argument)); + nodeIndex = addToGraph(GetLocal, OpInfo(operand), phi); + m_currentBlock->m_arguments[argument].value = nodeIndex; + return nodeIndex; + } + void setArgument(int operand, NodeIndex value) + { + unsigned argument = operand + m_codeBlock->m_numParameters + RegisterFile::CallFrameHeaderSize; + ASSERT(argument < m_numArguments); + + m_currentBlock->m_arguments[argument].value = addToGraph(SetLocal, OpInfo(operand), value); + } + + // Get an operand, and perform a ToInt32/ToNumber conversion on it. + NodeIndex getToInt32(int operand) + { + // Avoid wastefully adding a JSConstant node to the graph, only to + // replace it with a Int32Constant (which is what would happen if + // we called 'toInt32(get(operand))' in this case). + if (operand >= FirstConstantRegisterIndex) { + JSValue v = m_codeBlock->getConstant(operand); + if (v.isInt32()) + return getInt32Constant(v.asInt32(), operand - FirstConstantRegisterIndex); + } + return toInt32(get(operand)); + } + NodeIndex getToNumber(int operand) + { + // Avoid wastefully adding a JSConstant node to the graph, only to + // replace it with a DoubleConstant (which is what would happen if + // we called 'toNumber(get(operand))' in this case). + if (operand >= FirstConstantRegisterIndex) { + JSValue v = m_codeBlock->getConstant(operand); + if (v.isNumber()) + return getDoubleConstant(v.uncheckedGetNumber(), operand - FirstConstantRegisterIndex); + } + return toNumber(get(operand)); + } + + // Perform an ES5 ToInt32 operation - returns a node of type NodeResultInt32. + NodeIndex toInt32(NodeIndex index) + { + Node& node = m_graph[index]; + + if (node.hasInt32Result()) + return index; + + if (node.hasDoubleResult()) { + if (node.op == DoubleConstant) + return getInt32Constant(JSC::toInt32(valueOfDoubleConstant(index)), node.constantNumber()); + // 'NumberToInt32(Int32ToNumber(X))' == X, and 'NumberToInt32(UInt32ToNumber(X)) == X' + if (node.op == Int32ToNumber || node.op == UInt32ToNumber) + return node.child1; + + // We unique NumberToInt32 nodes in a map to prevent duplicate conversions. + pair result = m_numberToInt32Nodes.add(index, NoNode); + // Either we added a new value, or the existing value in the map is non-zero. + ASSERT(result.second == (result.first->second == NoNode)); + if (result.second) + result.first->second = addToGraph(NumberToInt32, index); + return result.first->second; + } + + // Check for numeric constants boxed as JSValues. + if (node.op == JSConstant) { + JSValue v = valueOfJSConstant(index); + if (v.isInt32()) + return getInt32Constant(v.asInt32(), node.constantNumber()); + if (v.isNumber()) + return getInt32Constant(JSC::toInt32(v.uncheckedGetNumber()), node.constantNumber()); + } + + return addToGraph(ValueToInt32, index); + } + + // Perform an ES5 ToNumber operation - returns a node of type NodeResultDouble. + NodeIndex toNumber(NodeIndex index) + { + Node& node = m_graph[index]; + + if (node.hasDoubleResult()) + return index; + + if (node.hasInt32Result()) { + if (node.op == Int32Constant) + return getDoubleConstant(valueOfInt32Constant(index), node.constantNumber()); + + // We unique Int32ToNumber nodes in a map to prevent duplicate conversions. + pair result = m_int32ToNumberNodes.add(index, NoNode); + // Either we added a new value, or the existing value in the map is non-zero. + ASSERT(result.second == (result.first->second == NoNode)); + if (result.second) + result.first->second = addToGraph(Int32ToNumber, index); + return result.first->second; + } + + if (node.op == JSConstant) { + JSValue v = valueOfJSConstant(index); + if (v.isNumber()) + return getDoubleConstant(v.uncheckedGetNumber(), node.constantNumber()); + } + + return addToGraph(ValueToNumber, index); + } + + + // Used in implementing get, above, where the operand is a constant. + NodeIndex getInt32Constant(int32_t value, unsigned constant) + { + NodeIndex index = m_constants[constant].asInt32; + if (index != NoNode) + return index; + NodeIndex resultIndex = addToGraph(Int32Constant, OpInfo(constant)); + m_graph[resultIndex].setInt32Constant(value); + m_constants[constant].asInt32 = resultIndex; + return resultIndex; + } + NodeIndex getDoubleConstant(double value, unsigned constant) + { + NodeIndex index = m_constants[constant].asNumeric; + if (index != NoNode) + return index; + NodeIndex resultIndex = addToGraph(DoubleConstant, OpInfo(constant)); + m_graph[resultIndex].setDoubleConstant(value); + m_constants[constant].asNumeric = resultIndex; + return resultIndex; + } + NodeIndex getJSConstant(unsigned constant) + { + NodeIndex index = m_constants[constant].asJSValue; + if (index != NoNode) + return index; + + NodeIndex resultIndex = addToGraph(JSConstant, OpInfo(constant)); + m_constants[constant].asJSValue = resultIndex; + return resultIndex; + } + + // Helper functions to get/set the this value. + NodeIndex getThis() + { + return getArgument(m_codeBlock->thisRegister()); + } + void setThis(NodeIndex value) + { + setArgument(m_codeBlock->thisRegister(), value); + } + + // Convenience methods for checking nodes for constants. + bool isInt32Constant(NodeIndex index) + { + return m_graph[index].op == Int32Constant; + } + bool isDoubleConstant(NodeIndex index) + { + return m_graph[index].op == DoubleConstant; + } + bool isJSConstant(NodeIndex index) + { + return m_graph[index].op == JSConstant; + } + + // Convenience methods for getting constant values. + int32_t valueOfInt32Constant(NodeIndex index) + { + ASSERT(isInt32Constant(index)); + return m_graph[index].int32Constant(); + } + double valueOfDoubleConstant(NodeIndex index) + { + ASSERT(isDoubleConstant(index)); + return m_graph[index].numericConstant(); + } + JSValue valueOfJSConstant(NodeIndex index) + { + ASSERT(isJSConstant(index)); + return m_codeBlock->getConstant(FirstConstantRegisterIndex + m_graph[index].constantNumber()); + } + + // This method returns a JSConstant with the value 'undefined'. + NodeIndex constantUndefined() + { + // Has m_constantUndefined been set up yet? + if (m_constantUndefined == UINT_MAX) { + // Search the constant pool for undefined, if we find it, we can just reuse this! + unsigned numberOfConstants = m_codeBlock->numberOfConstantRegisters(); + for (m_constantUndefined = 0; m_constantUndefined < numberOfConstants; ++m_constantUndefined) { + JSValue testMe = m_codeBlock->getConstant(FirstConstantRegisterIndex + m_constantUndefined); + if (testMe.isUndefined()) + return getJSConstant(m_constantUndefined); + } + + // Add undefined to the CodeBlock's constants, and add a corresponding slot in m_constants. + ASSERT(m_constants.size() == numberOfConstants); + m_codeBlock->addConstant(jsUndefined()); + m_constants.append(ConstantRecord()); + ASSERT(m_constants.size() == m_codeBlock->numberOfConstantRegisters()); + } + + // m_constantUndefined must refer to an entry in the CodeBlock's constant pool that has the value 'undefined'. + ASSERT(m_codeBlock->getConstant(FirstConstantRegisterIndex + m_constantUndefined).isUndefined()); + return getJSConstant(m_constantUndefined); + } + + // This method returns a JSConstant with the value 'null'. + NodeIndex constantNull() + { + // Has m_constantNull been set up yet? + if (m_constantNull == UINT_MAX) { + // Search the constant pool for null, if we find it, we can just reuse this! + unsigned numberOfConstants = m_codeBlock->numberOfConstantRegisters(); + for (m_constantNull = 0; m_constantNull < numberOfConstants; ++m_constantNull) { + JSValue testMe = m_codeBlock->getConstant(FirstConstantRegisterIndex + m_constantNull); + if (testMe.isNull()) + return getJSConstant(m_constantNull); + } + + // Add null to the CodeBlock's constants, and add a corresponding slot in m_constants. + ASSERT(m_constants.size() == numberOfConstants); + m_codeBlock->addConstant(jsNull()); + m_constants.append(ConstantRecord()); + ASSERT(m_constants.size() == m_codeBlock->numberOfConstantRegisters()); + } + + // m_constantNull must refer to an entry in the CodeBlock's constant pool that has the value 'null'. + ASSERT(m_codeBlock->getConstant(FirstConstantRegisterIndex + m_constantNull).isNull()); + return getJSConstant(m_constantNull); + } + + // This method returns a DoubleConstant with the value 1. + NodeIndex one() + { + // Has m_constant1 been set up yet? + if (m_constant1 == UINT_MAX) { + // Search the constant pool for the value 1, if we find it, we can just reuse this! + unsigned numberOfConstants = m_codeBlock->numberOfConstantRegisters(); + for (m_constant1 = 0; m_constant1 < numberOfConstants; ++m_constant1) { + JSValue testMe = m_codeBlock->getConstant(FirstConstantRegisterIndex + m_constant1); + if (testMe.isInt32() && testMe.asInt32() == 1) + return getDoubleConstant(1, m_constant1); + } + + // Add the value 1 to the CodeBlock's constants, and add a corresponding slot in m_constants. + ASSERT(m_constants.size() == numberOfConstants); + m_codeBlock->addConstant(jsNumber(1)); + m_constants.append(ConstantRecord()); + ASSERT(m_constants.size() == m_codeBlock->numberOfConstantRegisters()); + } + + // m_constant1 must refer to an entry in the CodeBlock's constant pool that has the integer value 1. + ASSERT(m_codeBlock->getConstant(FirstConstantRegisterIndex + m_constant1).isInt32()); + ASSERT(m_codeBlock->getConstant(FirstConstantRegisterIndex + m_constant1).asInt32() == 1); + return getDoubleConstant(1, m_constant1); + } + + + // These methods create a node and add it to the graph. If nodes of this type are + // 'mustGenerate' then the node will implicitly be ref'ed to ensure generation. + NodeIndex addToGraph(NodeType op, NodeIndex child1 = NoNode, NodeIndex child2 = NoNode, NodeIndex child3 = NoNode) + { + NodeIndex resultIndex = (NodeIndex)m_graph.size(); + m_graph.append(Node(op, m_currentIndex, child1, child2, child3)); + + if (op & NodeMustGenerate) + m_graph.ref(resultIndex); + return resultIndex; + } + NodeIndex addToGraph(NodeType op, OpInfo info, NodeIndex child1 = NoNode, NodeIndex child2 = NoNode, NodeIndex child3 = NoNode) + { + NodeIndex resultIndex = (NodeIndex)m_graph.size(); + m_graph.append(Node(op, m_currentIndex, info, child1, child2, child3)); + + if (op & NodeMustGenerate) + m_graph.ref(resultIndex); + return resultIndex; + } + NodeIndex addToGraph(NodeType op, OpInfo info1, OpInfo info2, NodeIndex child1 = NoNode, NodeIndex child2 = NoNode, NodeIndex child3 = NoNode) + { + NodeIndex resultIndex = (NodeIndex)m_graph.size(); + m_graph.append(Node(op, m_currentIndex, info1, info2, child1, child2, child3)); + + if (op & NodeMustGenerate) + m_graph.ref(resultIndex); + return resultIndex; + } + + void predictArray(NodeIndex nodeIndex) + { + Node* nodePtr = &m_graph[nodeIndex]; + + if (nodePtr->op == GetLocal) + m_graph.predict(nodePtr->local(), PredictArray); + } + + void predictInt32(NodeIndex nodeIndex) + { + Node* nodePtr = &m_graph[nodeIndex]; + + if (nodePtr->op == ValueToNumber) + nodePtr = &m_graph[nodePtr->child1]; + + if (nodePtr->op == ValueToInt32) + nodePtr = &m_graph[nodePtr->child1]; + + if (nodePtr->op == NumberToInt32) + nodePtr = &m_graph[nodePtr->child1]; + + if (nodePtr->op == GetLocal) + m_graph.predict(nodePtr->local(), PredictInt32); + } + + JSGlobalData* m_globalData; + CodeBlock* m_codeBlock; + Graph& m_graph; + + // The current block being generated. + BasicBlock* m_currentBlock; + // The bytecode index of the current instruction being generated. + unsigned m_currentIndex; + + // Record failures due to unimplemented functionality or regressions. + bool m_parseFailed; + + // We use these values during code generation, and to avoid the need for + // special handling we make sure they are available as constants in the + // CodeBlock's constant pool. These variables are initialized to + // UINT_MAX, and lazily updated to hold an index into the CodeBlock's + // constant pool, as necessary. + unsigned m_constantUndefined; + unsigned m_constantNull; + unsigned m_constant1; + + // A constant in the constant pool may be represented by more than one + // node in the graph, depending on the context in which it is being used. + struct ConstantRecord { + ConstantRecord() + : asInt32(NoNode) + , asNumeric(NoNode) + , asJSValue(NoNode) + { + } + + NodeIndex asInt32; + NodeIndex asNumeric; + NodeIndex asJSValue; + }; + + // Track the index of the node whose result is the current value for every + // register value in the bytecode - argument, local, and temporary. + Vector m_constants; + + // The number of arguments passed to the function. + unsigned m_numArguments; + // The number of locals (vars + temporaries) used in the function. + unsigned m_numLocals; + // The number of registers we need to preserve across BasicBlock boundaries; + // typically equal to the number vars, but we expand this to cover all + // temporaries that persist across blocks (dues to ?:, &&, ||, etc). + unsigned m_preservedVars; + + struct PhiStackEntry { + PhiStackEntry(BasicBlock* block, NodeIndex phi, unsigned varNo) + : m_block(block) + , m_phi(phi) + , m_varNo(varNo) + { + } + + BasicBlock* m_block; + NodeIndex m_phi; + unsigned m_varNo; + }; + Vector m_argumentPhiStack; + Vector m_localPhiStack; + + // These maps are used to unique ToNumber and ToInt32 operations. + typedef HashMap UnaryOpMap; + UnaryOpMap m_int32ToNumberNodes; + UnaryOpMap m_numberToInt32Nodes; +}; + +#define NEXT_OPCODE(name) \ + m_currentIndex += OPCODE_LENGTH(name); \ + continue + +#define LAST_OPCODE(name) \ + m_currentIndex += OPCODE_LENGTH(name); \ + return !m_parseFailed + +bool ByteCodeParser::parseBlock(unsigned limit) +{ + // No need to reset state initially, since it has been set by the constructor. + if (m_currentIndex) { + for (unsigned i = 0; i < m_constants.size(); ++i) + m_constants[i] = ConstantRecord(); + } + + AliasTracker aliases(m_graph); + + Interpreter* interpreter = m_globalData->interpreter; + Instruction* instructionsBegin = m_codeBlock->instructions().begin(); + while (true) { + // Don't extend over jump destinations. + if (m_currentIndex == limit) { + addToGraph(Jump, OpInfo(m_currentIndex)); + return !m_parseFailed; + } + + // Switch on the current bytecode opcode. + Instruction* currentInstruction = instructionsBegin + m_currentIndex; + switch (interpreter->getOpcodeID(currentInstruction->u.opcode)) { + + // === Function entry opcodes === + + case op_enter: + // Initialize all locals to undefined. + for (int i = 0; i < m_codeBlock->m_numVars; ++i) + set(i, constantUndefined()); + NEXT_OPCODE(op_enter); + + case op_convert_this: { + NodeIndex op1 = getThis(); + setThis(addToGraph(ConvertThis, op1)); + NEXT_OPCODE(op_convert_this); + } + + // === Bitwise operations === + + case op_bitand: { + NodeIndex op1 = getToInt32(currentInstruction[2].u.operand); + NodeIndex op2 = getToInt32(currentInstruction[3].u.operand); + predictInt32(op1); + predictInt32(op2); + set(currentInstruction[1].u.operand, addToGraph(BitAnd, op1, op2), PredictInt32); + NEXT_OPCODE(op_bitand); + } + + case op_bitor: { + NodeIndex op1 = getToInt32(currentInstruction[2].u.operand); + NodeIndex op2 = getToInt32(currentInstruction[3].u.operand); + predictInt32(op1); + predictInt32(op2); + set(currentInstruction[1].u.operand, addToGraph(BitOr, op1, op2), PredictInt32); + NEXT_OPCODE(op_bitor); + } + + case op_bitxor: { + NodeIndex op1 = getToInt32(currentInstruction[2].u.operand); + NodeIndex op2 = getToInt32(currentInstruction[3].u.operand); + predictInt32(op1); + predictInt32(op2); + set(currentInstruction[1].u.operand, addToGraph(BitXor, op1, op2), PredictInt32); + NEXT_OPCODE(op_bitxor); + } + + case op_rshift: { + NodeIndex op1 = getToInt32(currentInstruction[2].u.operand); + NodeIndex op2 = getToInt32(currentInstruction[3].u.operand); + predictInt32(op1); + predictInt32(op2); + NodeIndex result; + // Optimize out shifts by zero. + if (isInt32Constant(op2) && !(valueOfInt32Constant(op2) & 0x1f)) + result = op1; + else + result = addToGraph(BitRShift, op1, op2); + set(currentInstruction[1].u.operand, result, PredictInt32); + NEXT_OPCODE(op_rshift); + } + + case op_lshift: { + NodeIndex op1 = getToInt32(currentInstruction[2].u.operand); + NodeIndex op2 = getToInt32(currentInstruction[3].u.operand); + predictInt32(op1); + predictInt32(op2); + NodeIndex result; + // Optimize out shifts by zero. + if (isInt32Constant(op2) && !(valueOfInt32Constant(op2) & 0x1f)) + result = op1; + else + result = addToGraph(BitLShift, op1, op2); + set(currentInstruction[1].u.operand, result, PredictInt32); + NEXT_OPCODE(op_lshift); + } + + case op_urshift: { + NodeIndex op1 = getToInt32(currentInstruction[2].u.operand); + NodeIndex op2 = getToInt32(currentInstruction[3].u.operand); + predictInt32(op1); + predictInt32(op2); + NodeIndex result; + // The result of a zero-extending right shift is treated as an unsigned value. + // This means that if the top bit is set, the result is not in the int32 range, + // and as such must be stored as a double. If the shift amount is a constant, + // we may be able to optimize. + if (isInt32Constant(op2)) { + // If we know we are shifting by a non-zero amount, then since the operation + // zero fills we know the top bit of the result must be zero, and as such the + // result must be within the int32 range. Conversely, if this is a shift by + // zero, then the result may be changed by the conversion to unsigned, but it + // is not necessary to perform the shift! + if (valueOfInt32Constant(op2) & 0x1f) + result = addToGraph(BitURShift, op1, op2); + else + result = addToGraph(UInt32ToNumber, op1); + } else { + // Cannot optimize at this stage; shift & potentially rebox as a double. + result = addToGraph(BitURShift, op1, op2); + result = addToGraph(UInt32ToNumber, result); + } + set(currentInstruction[1].u.operand, result, PredictInt32); + NEXT_OPCODE(op_urshift); + } + + // === Increment/Decrement opcodes === + + case op_pre_inc: { + unsigned srcDst = currentInstruction[1].u.operand; + NodeIndex op = getToNumber(srcDst); + predictInt32(op); + set(srcDst, addToGraph(ArithAdd, op, one())); + NEXT_OPCODE(op_pre_inc); + } + + case op_post_inc: { + unsigned result = currentInstruction[1].u.operand; + unsigned srcDst = currentInstruction[2].u.operand; + NodeIndex op = getToNumber(srcDst); + predictInt32(op); + set(result, op); + set(srcDst, addToGraph(ArithAdd, op, one())); + NEXT_OPCODE(op_post_inc); + } + + case op_pre_dec: { + unsigned srcDst = currentInstruction[1].u.operand; + NodeIndex op = getToNumber(srcDst); + predictInt32(op); + set(srcDst, addToGraph(ArithSub, op, one())); + NEXT_OPCODE(op_pre_dec); + } + + case op_post_dec: { + unsigned result = currentInstruction[1].u.operand; + unsigned srcDst = currentInstruction[2].u.operand; + NodeIndex op = getToNumber(srcDst); + predictInt32(op); + set(result, op); + set(srcDst, addToGraph(ArithSub, op, one())); + NEXT_OPCODE(op_post_dec); + } + + // === Arithmetic operations === + + case op_add: { + ARITHMETIC_OP(); + NodeIndex op1 = get(currentInstruction[2].u.operand); + NodeIndex op2 = get(currentInstruction[3].u.operand); + // If both operands can statically be determined to the numbers, then this is an arithmetic add. + // Otherwise, we must assume this may be performing a concatenation to a string. + if (m_graph[op1].hasNumericResult() && m_graph[op2].hasNumericResult()) + set(currentInstruction[1].u.operand, addToGraph(ArithAdd, toNumber(op1), toNumber(op2))); + else + set(currentInstruction[1].u.operand, addToGraph(ValueAdd, op1, op2)); + NEXT_OPCODE(op_add); + } + + case op_sub: { + ARITHMETIC_OP(); + NodeIndex op1 = getToNumber(currentInstruction[2].u.operand); + NodeIndex op2 = getToNumber(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(ArithSub, op1, op2)); + NEXT_OPCODE(op_sub); + } + + case op_mul: { + ARITHMETIC_OP(); + NodeIndex op1 = getToNumber(currentInstruction[2].u.operand); + NodeIndex op2 = getToNumber(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(ArithMul, op1, op2)); + NEXT_OPCODE(op_mul); + } + + case op_mod: { + ARITHMETIC_OP(); + NodeIndex op1 = getToNumber(currentInstruction[2].u.operand); + NodeIndex op2 = getToNumber(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(ArithMod, op1, op2)); + NEXT_OPCODE(op_mod); + } + + case op_div: { + ARITHMETIC_OP(); + NodeIndex op1 = getToNumber(currentInstruction[2].u.operand); + NodeIndex op2 = getToNumber(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(ArithDiv, op1, op2)); + NEXT_OPCODE(op_div); + } + + // === Misc operations === + + case op_mov: { + NodeIndex op = get(currentInstruction[2].u.operand); + set(currentInstruction[1].u.operand, op); + NEXT_OPCODE(op_mov); + } + + case op_not: { + ARITHMETIC_OP(); + NodeIndex value = get(currentInstruction[2].u.operand); + set(currentInstruction[1].u.operand, addToGraph(LogicalNot, value)); + NEXT_OPCODE(op_not); + } + + case op_less: { + ARITHMETIC_OP(); + NodeIndex op1 = get(currentInstruction[2].u.operand); + NodeIndex op2 = get(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(CompareLess, op1, op2)); + NEXT_OPCODE(op_less); + } + + case op_lesseq: { + ARITHMETIC_OP(); + NodeIndex op1 = get(currentInstruction[2].u.operand); + NodeIndex op2 = get(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(CompareLessEq, op1, op2)); + NEXT_OPCODE(op_lesseq); + } + + case op_eq: { + ARITHMETIC_OP(); + NodeIndex op1 = get(currentInstruction[2].u.operand); + NodeIndex op2 = get(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(CompareEq, op1, op2)); + NEXT_OPCODE(op_eq); + } + + case op_eq_null: { + ARITHMETIC_OP(); + NodeIndex value = get(currentInstruction[2].u.operand); + set(currentInstruction[1].u.operand, addToGraph(CompareEq, value, constantNull())); + NEXT_OPCODE(op_eq_null); + } + + case op_stricteq: { + ARITHMETIC_OP(); + NodeIndex op1 = get(currentInstruction[2].u.operand); + NodeIndex op2 = get(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(CompareStrictEq, op1, op2)); + NEXT_OPCODE(op_stricteq); + } + + case op_neq: { + ARITHMETIC_OP(); + NodeIndex op1 = get(currentInstruction[2].u.operand); + NodeIndex op2 = get(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(LogicalNot, addToGraph(CompareEq, op1, op2))); + NEXT_OPCODE(op_neq); + } + + case op_neq_null: { + ARITHMETIC_OP(); + NodeIndex value = get(currentInstruction[2].u.operand); + set(currentInstruction[1].u.operand, addToGraph(LogicalNot, addToGraph(CompareEq, value, constantNull()))); + NEXT_OPCODE(op_neq_null); + } + + case op_nstricteq: { + ARITHMETIC_OP(); + NodeIndex op1 = get(currentInstruction[2].u.operand); + NodeIndex op2 = get(currentInstruction[3].u.operand); + set(currentInstruction[1].u.operand, addToGraph(LogicalNot, addToGraph(CompareStrictEq, op1, op2))); + NEXT_OPCODE(op_nstricteq); + } + + // === Property access operations === + + case op_get_by_val: { + NodeIndex base = get(currentInstruction[2].u.operand); + NodeIndex property = get(currentInstruction[3].u.operand); + predictArray(base); + predictInt32(property); + + NodeIndex getByVal = addToGraph(GetByVal, base, property, aliases.lookupGetByVal(base, property)); + set(currentInstruction[1].u.operand, getByVal); + aliases.recordGetByVal(getByVal); + + NEXT_OPCODE(op_get_by_val); + } + + case op_put_by_val: { + NodeIndex base = get(currentInstruction[1].u.operand); + NodeIndex property = get(currentInstruction[2].u.operand); + NodeIndex value = get(currentInstruction[3].u.operand); + predictArray(base); + predictInt32(property); + + NodeIndex aliasedGet = aliases.lookupGetByVal(base, property); + NodeIndex putByVal = addToGraph(aliasedGet != NoNode ? PutByValAlias : PutByVal, base, property, value); + aliases.recordPutByVal(putByVal); + + NEXT_OPCODE(op_put_by_val); + } + + case op_get_by_id: { + NodeIndex base = get(currentInstruction[2].u.operand); + unsigned identifier = currentInstruction[3].u.operand; + + NodeIndex getById = addToGraph(GetById, OpInfo(identifier), base); + set(currentInstruction[1].u.operand, getById); + aliases.recordGetById(getById); + + NEXT_OPCODE(op_get_by_id); + } + + case op_put_by_id: { + NodeIndex value = get(currentInstruction[3].u.operand); + NodeIndex base = get(currentInstruction[1].u.operand); + unsigned identifier = currentInstruction[2].u.operand; + bool direct = currentInstruction[8].u.operand; + + if (direct) { + NodeIndex putByIdDirect = addToGraph(PutByIdDirect, OpInfo(identifier), base, value); + aliases.recordPutByIdDirect(putByIdDirect); + } else { + NodeIndex putById = addToGraph(PutById, OpInfo(identifier), base, value); + aliases.recordPutById(putById); + } + + NEXT_OPCODE(op_put_by_id); + } + + case op_get_global_var: { + NodeIndex getGlobalVar = addToGraph(GetGlobalVar, OpInfo(currentInstruction[2].u.operand)); + set(currentInstruction[1].u.operand, getGlobalVar); + NEXT_OPCODE(op_get_global_var); + } + + case op_put_global_var: { + NodeIndex value = get(currentInstruction[2].u.operand); + addToGraph(PutGlobalVar, OpInfo(currentInstruction[1].u.operand), value); + NEXT_OPCODE(op_put_global_var); + } + + // === Block terminators. === + + case op_jmp: { + unsigned relativeOffset = currentInstruction[1].u.operand; + addToGraph(Jump, OpInfo(m_currentIndex + relativeOffset)); + LAST_OPCODE(op_jmp); + } + + case op_loop: { + unsigned relativeOffset = currentInstruction[1].u.operand; + addToGraph(Jump, OpInfo(m_currentIndex + relativeOffset)); + LAST_OPCODE(op_loop); + } + + case op_jtrue: { + unsigned relativeOffset = currentInstruction[2].u.operand; + NodeIndex condition = get(currentInstruction[1].u.operand); + addToGraph(Branch, OpInfo(m_currentIndex + relativeOffset), OpInfo(m_currentIndex + OPCODE_LENGTH(op_jtrue)), condition); + LAST_OPCODE(op_jtrue); + } + + case op_jfalse: { + unsigned relativeOffset = currentInstruction[2].u.operand; + NodeIndex condition = get(currentInstruction[1].u.operand); + addToGraph(Branch, OpInfo(m_currentIndex + OPCODE_LENGTH(op_jfalse)), OpInfo(m_currentIndex + relativeOffset), condition); + LAST_OPCODE(op_jfalse); + } + + case op_loop_if_true: { + unsigned relativeOffset = currentInstruction[2].u.operand; + NodeIndex condition = get(currentInstruction[1].u.operand); + addToGraph(Branch, OpInfo(m_currentIndex + relativeOffset), OpInfo(m_currentIndex + OPCODE_LENGTH(op_loop_if_true)), condition); + LAST_OPCODE(op_loop_if_true); + } + + case op_loop_if_false: { + unsigned relativeOffset = currentInstruction[2].u.operand; + NodeIndex condition = get(currentInstruction[1].u.operand); + addToGraph(Branch, OpInfo(m_currentIndex + OPCODE_LENGTH(op_loop_if_false)), OpInfo(m_currentIndex + relativeOffset), condition); + LAST_OPCODE(op_loop_if_false); + } + + case op_jeq_null: { + unsigned relativeOffset = currentInstruction[2].u.operand; + NodeIndex value = get(currentInstruction[1].u.operand); + NodeIndex condition = addToGraph(CompareEq, value, constantNull()); + addToGraph(Branch, OpInfo(m_currentIndex + relativeOffset), OpInfo(m_currentIndex + OPCODE_LENGTH(op_jeq_null)), condition); + LAST_OPCODE(op_jeq_null); + } + + case op_jneq_null: { + unsigned relativeOffset = currentInstruction[2].u.operand; + NodeIndex value = get(currentInstruction[1].u.operand); + NodeIndex condition = addToGraph(CompareEq, value, constantNull()); + addToGraph(Branch, OpInfo(m_currentIndex + OPCODE_LENGTH(op_jneq_null)), OpInfo(m_currentIndex + relativeOffset), condition); + LAST_OPCODE(op_jneq_null); + } + + case op_jnless: { + unsigned relativeOffset = currentInstruction[3].u.operand; + NodeIndex op1 = get(currentInstruction[1].u.operand); + NodeIndex op2 = get(currentInstruction[2].u.operand); + NodeIndex condition = addToGraph(CompareLess, op1, op2); + addToGraph(Branch, OpInfo(m_currentIndex + OPCODE_LENGTH(op_jnless)), OpInfo(m_currentIndex + relativeOffset), condition); + LAST_OPCODE(op_jnless); + } + + case op_jnlesseq: { + unsigned relativeOffset = currentInstruction[3].u.operand; + NodeIndex op1 = get(currentInstruction[1].u.operand); + NodeIndex op2 = get(currentInstruction[2].u.operand); + NodeIndex condition = addToGraph(CompareLessEq, op1, op2); + addToGraph(Branch, OpInfo(m_currentIndex + OPCODE_LENGTH(op_jnlesseq)), OpInfo(m_currentIndex + relativeOffset), condition); + LAST_OPCODE(op_jnlesseq); + } + + case op_jless: { + unsigned relativeOffset = currentInstruction[3].u.operand; + NodeIndex op1 = get(currentInstruction[1].u.operand); + NodeIndex op2 = get(currentInstruction[2].u.operand); + NodeIndex condition = addToGraph(CompareLess, op1, op2); + addToGraph(Branch, OpInfo(m_currentIndex + relativeOffset), OpInfo(m_currentIndex + OPCODE_LENGTH(op_jless)), condition); + LAST_OPCODE(op_jless); + } + + case op_jlesseq: { + unsigned relativeOffset = currentInstruction[3].u.operand; + NodeIndex op1 = get(currentInstruction[1].u.operand); + NodeIndex op2 = get(currentInstruction[2].u.operand); + NodeIndex condition = addToGraph(CompareLessEq, op1, op2); + addToGraph(Branch, OpInfo(m_currentIndex + relativeOffset), OpInfo(m_currentIndex + OPCODE_LENGTH(op_jlesseq)), condition); + LAST_OPCODE(op_jlesseq); + } + + case op_loop_if_less: { + unsigned relativeOffset = currentInstruction[3].u.operand; + NodeIndex op1 = get(currentInstruction[1].u.operand); + NodeIndex op2 = get(currentInstruction[2].u.operand); + NodeIndex condition = addToGraph(CompareLess, op1, op2); + addToGraph(Branch, OpInfo(m_currentIndex + relativeOffset), OpInfo(m_currentIndex + OPCODE_LENGTH(op_loop_if_less)), condition); + LAST_OPCODE(op_loop_if_less); + } + + case op_loop_if_lesseq: { + unsigned relativeOffset = currentInstruction[3].u.operand; + NodeIndex op1 = get(currentInstruction[1].u.operand); + NodeIndex op2 = get(currentInstruction[2].u.operand); + NodeIndex condition = addToGraph(CompareLessEq, op1, op2); + addToGraph(Branch, OpInfo(m_currentIndex + relativeOffset), OpInfo(m_currentIndex + OPCODE_LENGTH(op_loop_if_lesseq)), condition); + LAST_OPCODE(op_loop_if_lesseq); + } + + case op_ret: { + addToGraph(Return, get(currentInstruction[1].u.operand)); + LAST_OPCODE(op_ret); + } + + default: + // Parse failed! + return false; + } + } +} + +template +void ByteCodeParser::processPhiStack() +{ + Vector& phiStack = (stackType == ArgumentPhiStack) ? m_argumentPhiStack : m_localPhiStack; + + while (!phiStack.isEmpty()) { + PhiStackEntry entry = phiStack.last(); + phiStack.removeLast(); + + Node& phiNode = m_graph[entry.m_phi]; + PredecessorList& predecessors = entry.m_block->m_predecessors; + unsigned varNo = entry.m_varNo; + + for (size_t i = 0; i < predecessors.size(); ++i) { + BasicBlock* predecessorBlock = m_graph.m_blocks[predecessors[i]].get(); + + VariableRecord& var = (stackType == ArgumentPhiStack) ? predecessorBlock->m_arguments[varNo] : predecessorBlock->m_locals[varNo]; + + NodeIndex valueInPredecessor = var.value; + if (valueInPredecessor == NoNode) { + valueInPredecessor = addToGraph(Phi); + var.value = valueInPredecessor; + phiStack.append(PhiStackEntry(predecessorBlock, valueInPredecessor, varNo)); + } else if (m_graph[valueInPredecessor].op == GetLocal) + valueInPredecessor = m_graph[valueInPredecessor].child1; + ASSERT(m_graph[valueInPredecessor].op == SetLocal || m_graph[valueInPredecessor].op == Phi); + + if (phiNode.refCount()) + m_graph.ref(valueInPredecessor); + + if (phiNode.child1 == NoNode) { + phiNode.child1 = valueInPredecessor; + continue; + } + if (phiNode.child2 == NoNode) { + phiNode.child2 = valueInPredecessor; + continue; + } + if (phiNode.child3 == NoNode) { + phiNode.child3 = valueInPredecessor; + continue; + } + + NodeIndex newPhi = addToGraph(Phi); + Node& newPhiNode = m_graph[newPhi]; + if (phiNode.refCount()) + m_graph.ref(newPhi); + + newPhiNode.child1 = phiNode.child1; + newPhiNode.child2 = phiNode.child2; + newPhiNode.child3 = phiNode.child3; + + phiNode.child1 = newPhi; + phiNode.child1 = valueInPredecessor; + phiNode.child3 = NoNode; + } + } +} + +void ByteCodeParser::setupPredecessors() +{ + for (BlockIndex index = 0; index < m_graph.m_blocks.size(); ++index) { + BasicBlock* block = m_graph.m_blocks[index].get(); + ASSERT(block->end != NoNode); + Node& node = m_graph[block->end - 1]; + ASSERT(node.isTerminal()); + + if (node.isJump()) + m_graph.blockForBytecodeOffset(node.takenBytecodeOffset()).m_predecessors.append(index); + else if (node.isBranch()) { + m_graph.blockForBytecodeOffset(node.takenBytecodeOffset()).m_predecessors.append(index); + m_graph.blockForBytecodeOffset(node.notTakenBytecodeOffset()).m_predecessors.append(index); + } + } +} + +void ByteCodeParser::allocateVirtualRegisters() +{ + ScoreBoard scoreBoard(m_graph, m_preservedVars); + unsigned sizeExcludingPhiNodes = m_graph.m_blocks.last()->end; + for (size_t i = 0; i < sizeExcludingPhiNodes; ++i) { + Node& node = m_graph[i]; + if (!node.shouldGenerate()) + continue; + + // GetLocal nodes are effectively phi nodes in the graph, referencing + // results from prior blocks. + if (node.op != GetLocal) { + // First, call use on all of the current node's children, then + // allocate a VirtualRegister for this node. We do so in this + // order so that if a child is on its last use, and a + // VirtualRegister is freed, then it may be reused for node. + scoreBoard.use(node.child1); + scoreBoard.use(node.child2); + scoreBoard.use(node.child3); + } + + if (!node.hasResult()) + continue; + + node.setVirtualRegister(scoreBoard.allocate()); + // 'mustGenerate' nodes have their useCount artificially elevated, + // call use now to account for this. + if (node.mustGenerate()) + scoreBoard.use(i); + } + + // 'm_numCalleeRegisters' is the number of locals and temporaries allocated + // for the function (and checked for on entry). Since we perform a new and + // different allocation of temporaries, more registers may now be required. + unsigned calleeRegisters = scoreBoard.allocatedCount() + m_preservedVars; + if ((unsigned)m_codeBlock->m_numCalleeRegisters < calleeRegisters) + m_codeBlock->m_numCalleeRegisters = calleeRegisters; +} + +bool ByteCodeParser::parse() +{ + // Set during construction. + ASSERT(!m_currentIndex); + + for (unsigned jumpTargetIndex = 0; jumpTargetIndex <= m_codeBlock->numberOfJumpTargets(); ++jumpTargetIndex) { + // The maximum bytecode offset to go into the current basicblock is either the next jump target, or the end of the instructions. + unsigned limit = jumpTargetIndex < m_codeBlock->numberOfJumpTargets() ? m_codeBlock->jumpTarget(jumpTargetIndex) : m_codeBlock->instructions().size(); + ASSERT(m_currentIndex < limit); + + // Loop until we reach the current limit (i.e. next jump target). + do { + OwnPtr block = adoptPtr(new BasicBlock(m_currentIndex, m_graph.size(), m_numArguments, m_numLocals)); + m_currentBlock = block.get(); + m_graph.m_blocks.append(block.release()); + + if (!parseBlock(limit)) + return false; + // We should not have gone beyond the limit. + ASSERT(m_currentIndex <= limit); + + m_currentBlock->end = m_graph.size(); + } while (m_currentIndex < limit); + } + + // Should have reached the end of the instructions. + ASSERT(m_currentIndex == m_codeBlock->instructions().size()); + + setupPredecessors(); + processPhiStack(); + processPhiStack(); + + allocateVirtualRegisters(); + +#if DFG_DEBUG_VERBOSE + m_graph.dump(m_codeBlock); +#endif + + return true; +} + +bool parse(Graph& graph, JSGlobalData* globalData, CodeBlock* codeBlock) +{ +#if DFG_DEBUG_LOCAL_DISBALE + UNUSED_PARAM(graph); + UNUSED_PARAM(globalData); + UNUSED_PARAM(codeBlock); + return false; +#else + return ByteCodeParser(globalData, codeBlock, graph).parse(); +#endif +} + +} } // namespace JSC::DFG + +#endif