/*
* 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_arguments(codeBlock->m_numParameters)
, m_variables(codeBlock->m_numVars)
, m_temporaries(codeBlock->m_numCalleeRegisters - codeBlock->m_numVars)
{
for (unsigned i = 0; i < m_temporaries.size(); ++i)
m_temporaries[i] = NoNode;
}
// Parse a full CodeBlock of bytecode.
bool parse();
private:
// Parse a single basic block of bytecode instructions.
bool parseBlock(unsigned limit);
// 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 (operand < 0)
return getArgument(operand);
// Is this a variable?
unsigned numVariables = m_variables.size();
if ((unsigned)operand < numVariables)
return getVariable((unsigned)operand);
// Must be a temporary.
unsigned temporary = (unsigned)operand - numVariables;
ASSERT(temporary < m_temporaries.size());
return getTemporary(temporary);
}
void set(int operand, NodeIndex value)
{
// Is this an argument?
if (operand < 0) {
setArgument(operand, value);
return;
}
// Is this a variable?
unsigned numVariables = m_variables.size();
if ((unsigned)operand < numVariables) {
setVariable((unsigned)operand, value);
return;
}
// Must be a temporary.
unsigned temporary = (unsigned)operand - numVariables;
ASSERT(temporary < m_temporaries.size());
setTemporary(temporary, value);
}
// Used in implementing get/set, above, where the operand is a local variable.
NodeIndex getVariable(unsigned operand)
{
NodeIndex setNode = m_variables[operand].set;
if (setNode != NoNode)
return m_graph[setNode].child1;
NodeIndex getNode = m_variables[operand].get;
if (getNode != NoNode)
return getNode;
getNode = addToGraph(GetLocal, OpInfo(operand));
m_variables[operand].get = getNode;
return getNode;
}
void setVariable(unsigned operand, NodeIndex value)
{
NodeIndex priorSet = m_variables[operand].set;
m_variables[operand].set = addToGraph(SetLocal, OpInfo(operand), value);
if (priorSet != NoNode)
m_graph.deref(priorSet);
}
// Used in implementing get/set, above, where the operand is a temporary.
NodeIndex getTemporary(unsigned operand)
{
NodeIndex index = m_temporaries[operand];
if (index != NoNode)
return index;
// Detect a read of an temporary that is not a yet defined within this block (e.g. use of ?:).
m_parseFailed = true;
return constantUndefined();
}
void setTemporary(unsigned operand, NodeIndex value)
{
m_temporaries[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_arguments.size());
NodeIndex setNode = m_arguments[argument].set;
if (setNode != NoNode)
return m_graph[setNode].child1;
NodeIndex getNode = m_arguments[argument].get;
if (getNode != NoNode)
return getNode;
getNode = addToGraph(GetLocal, OpInfo(operand));
m_arguments[argument].get = getNode;
return getNode;
}
void setArgument(int operand, NodeIndex value)
{
unsigned argument = operand + m_codeBlock->m_numParameters + RegisterFile::CallFrameHeaderSize;
ASSERT(argument < m_arguments.size());
NodeIndex priorSet = m_arguments[argument].set;
m_arguments[argument].set = addToGraph(SetLocal, OpInfo(operand), value);
if (priorSet != NoNode)
m_graph.deref(priorSet);
}
// 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<UnaryOpMap::iterator, bool> 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<UnaryOpMap::iterator, bool> 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;
}
JSGlobalData* m_globalData;
CodeBlock* m_codeBlock;
Graph& m_graph;
// 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;
};
// For every local variable we track any existing get or set of the value.
// We track the get so that these may be shared, and we track the set to
// retrieve the current value, and to reference the final definition.
struct VariableRecord {
VariableRecord()
: get(NoNode)
, set(NoNode)
{
}
NodeIndex get;
NodeIndex set;
};
// Track the index of the node whose result is the current value for every
// register value in the bytecode - argument, local, and temporary.
Vector <ConstantRecord, 32> m_constants;
Vector <VariableRecord, 32> m_arguments;
Vector <VariableRecord, 32> m_variables;
Vector <NodeIndex, 32> m_temporaries;
// These maps are used to unique ToNumber and ToInt32 operations.
typedef HashMap<NodeIndex, NodeIndex> 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();
for (unsigned i = 0; i < m_variables.size(); ++i)
m_variables[i] = VariableRecord();
for (unsigned i = 0; i < m_arguments.size(); ++i)
m_arguments[i] = VariableRecord();
for (unsigned i = 0; i < m_temporaries.size(); ++i)
m_temporaries[i] = NoNode;
}
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);
set(currentInstruction[1].u.operand, addToGraph(BitAnd, op1, op2));
NEXT_OPCODE(op_bitand);
}
case op_bitor: {
NodeIndex op1 = getToInt32(currentInstruction[2].u.operand);
NodeIndex op2 = getToInt32(currentInstruction[3].u.operand);
set(currentInstruction[1].u.operand, addToGraph(BitOr, op1, op2));
NEXT_OPCODE(op_bitor);
}
case op_bitxor: {
NodeIndex op1 = getToInt32(currentInstruction[2].u.operand);
NodeIndex op2 = getToInt32(currentInstruction[3].u.operand);
set(currentInstruction[1].u.operand, addToGraph(BitXor, op1, op2));
NEXT_OPCODE(op_bitxor);
}
case op_rshift: {
NodeIndex op1 = getToInt32(currentInstruction[2].u.operand);
NodeIndex op2 = getToInt32(currentInstruction[3].u.operand);
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);
NEXT_OPCODE(op_rshift);
}
case op_lshift: {
NodeIndex op1 = getToInt32(currentInstruction[2].u.operand);
NodeIndex op2 = getToInt32(currentInstruction[3].u.operand);
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);
NEXT_OPCODE(op_lshift);
}
case op_urshift: {
NodeIndex op1 = getToInt32(currentInstruction[2].u.operand);
NodeIndex op2 = getToInt32(currentInstruction[3].u.operand);
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);
NEXT_OPCODE(op_urshift);
}
// === Increment/Decrement opcodes ===
case op_pre_inc: {
unsigned srcDst = currentInstruction[1].u.operand;
NodeIndex op = getToNumber(srcDst);
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);
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);
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);
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);
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);
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));
// FIXME: throw away terminal definitions of variables;
// should not be necessary once we have proper DCE!
for (unsigned i = 0; i < m_variables.size(); ++i) {
NodeIndex priorSet = m_variables[i].set;
if (priorSet != NoNode)
m_graph.deref(priorSet);
}
LAST_OPCODE(op_ret);
}
default:
// Parse failed!
return false;
}
}
}
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 {
unsigned bytecodeBegin = m_currentIndex;
NodeIndex begin = m_graph.size();
if (!parseBlock(limit))
return false;
// We should not have gone beyond the limit.
ASSERT(m_currentIndex <= limit);
NodeIndex end = m_graph.size();
m_graph.m_blocks.append(BasicBlock(bytecodeBegin, begin, end));
} while (m_currentIndex < limit);
}
// Should have reached the end of the instructions.
ASSERT(m_currentIndex == m_codeBlock->instructions().size());
// Assign VirtualRegisters.
ScoreBoard scoreBoard(m_graph, m_variables.size());
Node* nodes = m_graph.begin();
size_t size = m_graph.size();
for (size_t i = 0; i < size; ++i) {
Node& node = nodes[i];
if (node.refCount) {
// 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);
node.virtualRegister = 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_variables.size();
if ((unsigned)m_codeBlock->m_numCalleeRegisters < calleeRegisters)
m_codeBlock->m_numCalleeRegisters = calleeRegisters;
#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