Arquivos
hhvm/hphp/compiler/statement/switch_statement.cpp
T
Jordan Delong 363d1bb20f Code move src/ -> hphp/
This change is mostly for FB internal organizational reasons.
Building is not effected beyond the fact that the target now
lands in hphp/hhvm/hhvm rather than src/hhvm/hhvm.
2013-02-11 02:10:41 -08:00

744 linhas
24 KiB
C++

/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#include <compiler/statement/switch_statement.h>
#include <compiler/analysis/analysis_result.h>
#include <compiler/statement/statement_list.h>
#include <compiler/statement/case_statement.h>
#include <compiler/option.h>
#include <compiler/analysis/code_error.h>
#include <compiler/analysis/variable_table.h>
#include <compiler/expression/simple_variable.h>
#include <compiler/expression/scalar_expression.h>
#include <runtime/base/comparisons.h>
using namespace HPHP;
using std::string;
using std::set;
///////////////////////////////////////////////////////////////////////////////
// constructors/destructors
SwitchStatement::SwitchStatement
(STATEMENT_CONSTRUCTOR_PARAMETERS, ExpressionPtr exp, StatementListPtr cases)
: Statement(STATEMENT_CONSTRUCTOR_PARAMETER_VALUES(SwitchStatement)),
m_exp(exp), m_cases(cases) {
if (m_cases && m_exp->is(Expression::KindOfSimpleVariable)) {
for (int i = m_cases->getCount(); i--; ) {
CaseStatementPtr c(dynamic_pointer_cast<CaseStatement>((*m_cases)[i]));
if (c->getCondition() && c->getCondition()->hasEffect()) {
m_exp->setContext(Expression::LValue);
m_exp->setContext(Expression::NoLValueWrapper);
break;
}
}
}
}
StatementPtr SwitchStatement::clone() {
SwitchStatementPtr stmt(new SwitchStatement(*this));
stmt->m_cases = Clone(m_cases);
stmt->m_exp = Clone(m_exp);
return stmt;
}
int SwitchStatement::getRecursiveCount() const {
return 1 + (m_cases ? m_cases->getRecursiveCount() : 0);
}
///////////////////////////////////////////////////////////////////////////////
// parser functions
///////////////////////////////////////////////////////////////////////////////
// static analysis functions
void SwitchStatement::analyzeProgram(AnalysisResultPtr ar) {
m_exp->analyzeProgram(ar);
if (m_cases) m_cases->analyzeProgram(ar);
if (ar->getPhase() == AnalysisResult::AnalyzeAll &&
m_exp->is(Expression::KindOfSimpleVariable)) {
SimpleVariablePtr exp = dynamic_pointer_cast<SimpleVariable>(m_exp);
if (exp && exp->getSymbol() && exp->getSymbol()->isClassName()) {
// Mark some classes as volitle since the name is used in switch
for (int i = 0; i < m_cases->getCount(); i++) {
CaseStatementPtr stmt =
dynamic_pointer_cast<CaseStatement>((*m_cases)[i]);
assert(stmt);
ExpressionPtr caseCond = stmt->getCondition();
if (caseCond && caseCond->isScalar()) {
ScalarExpressionPtr name =
dynamic_pointer_cast<ScalarExpression>(caseCond);
if (name && name->isLiteralString()) {
string className = name->getLiteralString();
ClassScopePtr cls = ar->findClass(Util::toLower(className));
if (cls && cls->isUserClass()) {
cls->setVolatile();
}
}
}
}
// Also note this down as code error
ConstructPtr self = shared_from_this();
Compiler::Error(Compiler::ConditionalClassLoading, self);
}
}
}
bool SwitchStatement::hasDecl() const {
return m_cases && m_cases->hasDecl();
}
bool SwitchStatement::hasRetExp() const {
return m_cases && m_cases->hasRetExp();
}
ConstructPtr SwitchStatement::getNthKid(int n) const {
switch (n) {
case 0:
return m_exp;
case 1:
return m_cases;
default:
assert(false);
break;
}
return ConstructPtr();
}
int SwitchStatement::getKidCount() const {
return 2;
}
void SwitchStatement::setNthKid(int n, ConstructPtr cp) {
switch (n) {
case 0:
m_exp = boost::dynamic_pointer_cast<Expression>(cp);
break;
case 1:
m_cases = boost::dynamic_pointer_cast<StatementList>(cp);
break;
default:
assert(false);
break;
}
}
void SwitchStatement::inferTypes(AnalysisResultPtr ar) {
// we optimize the most two common cases of switch statements
bool allInteger = true;
bool allString = true;
if (m_cases && m_cases->getCount()) {
for (int i = 0; i < m_cases->getCount(); i++) {
CaseStatementPtr stmt =
dynamic_pointer_cast<CaseStatement>((*m_cases)[i]);
if (!stmt->getCondition()) {
if (m_cases->getCount() == 1) allInteger = allString = false;
} else {
if (!stmt->isLiteralInteger()) allInteger = false;
if (!stmt->isLiteralString()) allString = false;
}
}
}
if (allInteger && allString) {
allInteger = allString = false;
}
TypePtr ret = m_exp->inferAndCheck(ar, Type::Some, false);
// these are the cases where toInt64(x) is OK for the switch statement
if (allInteger && (ret->is(Type::KindOfInt32) || ret->is(Type::KindOfInt64))) {
m_exp->setExpectedType(Type::Int64);
}
if (ret->is(Type::KindOfObject) && ret->isSpecificObject()) {
m_exp->setExpectedType(Type::Object);
}
ConstructPtr self = shared_from_this();
if (m_cases && m_cases->getCount()) {
int defaultCount = 0;
for (int i = 0; i < m_cases->getCount(); i++) {
CaseStatementPtr stmt =
dynamic_pointer_cast<CaseStatement>((*m_cases)[i]);
stmt->inferAndCheck(ar, Type::Some, false);
ExpressionPtr cond = stmt->getCondition();
if (!cond) {
defaultCount++;
}
}
// TODO: this really belongs in analyzeProgram()
if (defaultCount > 1 && getScope()->isFirstPass()) {
Compiler::Error(Compiler::MoreThanOneDefault, m_cases);
}
}
}
///////////////////////////////////////////////////////////////////////////////
// code generation functions
void SwitchStatement::outputPHP(CodeGenerator &cg, AnalysisResultPtr ar) {
cg_printf("switch (");
m_exp->outputPHP(cg, ar);
cg_printf(") {\n");
if (m_cases) m_cases->outputPHP(cg, ar);
cg_printf("}\n");
}
void SwitchStatement::outputCPPImpl(CodeGenerator &cg, AnalysisResultPtr ar) {
// disable CSE for switch statements, for now.
// this is to simplify the implementation (no real reason we
// can't do it)
// TODO(stephentu): fix this up later
m_exp->disableCSE();
if (m_cases) {
for (int i = 0; i < m_cases->getCount(); i++) {
CaseStatementPtr stmt =
static_pointer_cast<CaseStatement>((*m_cases)[i]);
if (stmt->getCondition()) {
stmt->getCondition()->disableCSE();
}
}
}
int labelId = cg.createNewLocalId(shared_from_this());
// if isStaticInt, then we can avoid calling hashForIntSwitch() in static case
bool isStaticInt = m_exp->getType()->isInteger();
bool hasSentinel = true;
uint64 sentinel = 0;
int64 firstNonZero = 0;
bool staticIntCases = true;
bool staticStringCases = true;
uint64 numDistinctLabels = 0;
uint64 numNonDefaultLabels = 0;
uint64 tableSize = 0;
if (m_cases) {
bool seenDefault = false;
set<int64> seenNums;
set<string> seenStrs;
for (int i = 0;
i < m_cases->getCount() && (staticIntCases || staticStringCases);
i++) {
CaseStatementPtr stmt =
static_pointer_cast<CaseStatement>((*m_cases)[i]);
if (stmt->getCondition()) {
numNonDefaultLabels++;
Variant v;
bool hasValue = stmt->getScalarConditionValue(v);
if (hasValue && v.isInteger()) {
staticStringCases = false;
numDistinctLabels++;
int64 num = v.toInt64();
if (num != 0 && firstNonZero == 0) {
firstNonZero = num;
}
// detecting duplicate case value
if (seenNums.find(num) != seenNums.end()) {
staticIntCases = false;
break;
}
seenNums.insert(num);
} else if (hasValue && v.isString()) {
staticIntCases = false;
string str(v.toString());
// static string optimization can handle duplicate
// cases, since they will just hash to the same bucket
// anyways
if (seenStrs.find(str) == seenStrs.end()) {
numDistinctLabels++;
seenStrs.insert(str);
}
} else {
staticIntCases = staticStringCases = false;
break;
}
} else {
if (seenDefault) {
// don't want to optimize > 1 default cases for static int cases
staticIntCases = false;
break;
}
seenDefault = true;
}
}
// now scan for an available sentinel value - we only have to do this
// if we care (which is we will do static cases and our operand isn't
// known to be an int)
if (staticIntCases && !isStaticInt) {
while (sentinel < ULLONG_MAX) {
if (seenNums.find((int64)sentinel) == seenNums.end()) {
break;
}
sentinel++;
}
if (sentinel == ULLONG_MAX &&
seenNums.find((int64)sentinel) != seenNums.end()) {
hasSentinel = false;
}
}
}
// if theres no non-zero match, then we use the sentinel to create a no-match
if (firstNonZero == 0) {
assert(!staticIntCases || isStaticInt || hasSentinel);
firstNonZero = sentinel;
}
// if we somehow managed to enumerate all 2^64 int literals +
// we don't have statically known int switch operand, then sorry
// we are out of luck
if (staticIntCases && !isStaticInt && !hasSentinel) {
staticIntCases = false;
}
// in the degenerate case of no labels, then treat it as
// a static int case, or if there is only 1 (non-default) case, then also
// no point to do static string case optimization
if ((staticIntCases && staticStringCases) ||
(staticStringCases && numNonDefaultLabels <= 1)) {
staticStringCases = false;
}
if (staticStringCases) {
assert(numDistinctLabels > 0);
tableSize = Util::roundUpToPowerOfTwo(numDistinctLabels * 2);
if (tableSize == ULLONG_MAX) {
// we cannot guarantee a no match value in this case
staticStringCases = false;
}
}
labelId |= CodeGenerator::InsideSwitch;
if (staticIntCases) labelId |= CodeGenerator::StaticCases;
cg.pushBreakScope(labelId, false);
labelId &= ~CodeGenerator::BreakScopeBitMask;
string var;
int varId = -1;
bool closeBrace = false;
bool needsPreOutput = m_exp->preOutputCPP(cg, ar, 0);
TypePtr staticIntSwitchOpnd = m_exp->getType();
if (staticStringCases || needsPreOutput) {
varId = cg.createNewLocalId(shared_from_this());
if (!needsPreOutput &&
m_exp->hasContext(Expression::LValue) &&
m_exp->is(Expression::KindOfSimpleVariable) &&
static_pointer_cast<SimpleVariable>(m_exp)
->hasAssignableCPPVariable()) {
// use existing variable
var = static_pointer_cast<SimpleVariable>(m_exp)
->getAssignableCPPVariable(ar);
assert(!var.empty());
} else {
var = string(Option::SwitchPrefix) + lexical_cast<string>(varId);
string var0; // holds the variable name to call outputCPP on
bool needsDecl = true;
if (staticIntCases) {
assert(hasSentinel);
switch (m_exp->getType()->getKindOf()) {
case Type::KindOfInt64:
case Type::KindOfBoolean:
case Type::KindOfDouble:
m_exp->getType()->outputCPPDecl(cg, ar, getScope());
cg_printf(" %s;\n", var.c_str());
needsDecl = false;
var0 = var;
break;
default:
cg_printf("int64 %s;\n", var.c_str());
// we must do extra work here
var0 = string(Option::SwitchPrefix) +
lexical_cast<string>(cg.createNewLocalId(shared_from_this()));
staticIntSwitchOpnd = Type::Int64;
break;
}
} else var0 = var;
closeBrace = true;
cg_indentBegin("{\n");
if (needsDecl) {
m_exp->getType()->outputCPPDecl(cg, ar, getScope());
cg_printf(" %s;\n", var0.c_str());
}
m_exp->outputCPPBegin(cg, ar);
cg_printf("%s = (", var0.c_str());
m_exp->outputCPP(cg, ar);
cg_printf(");\n");
m_exp->outputCPPEnd(cg, ar);
if (staticIntCases) {
if (needsDecl) {
// copy var0 over to var
cg_printf("%s = %s.hashForIntSwitch(%lldLL, %lldLL);\n",
var.c_str(),
var0.c_str(),
firstNonZero,
(int64) sentinel);
}
cg_indentEnd("}\n");
closeBrace = false;
}
}
}
if (staticIntCases) {
assert(!closeBrace);
assert(hasSentinel);
assert(!needsPreOutput || !var.empty());
assert(staticIntSwitchOpnd);
cg_printf("switch (");
if (staticIntSwitchOpnd->is(Type::KindOfDouble)) {
// double we must special case
cg_printf("Variant::DoubleHashForIntSwitch(");
}
if (!var.empty()) {
cg_printf("%s", var.c_str());
} else {
cg_printf("(");
m_exp->outputCPP(cg, ar);
cg_printf(")");
}
if (staticIntSwitchOpnd->is(Type::KindOfBoolean)) {
// boolean we must special case
cg_printf(" ? %lldLL : 0LL", firstNonZero);
} else if (staticIntSwitchOpnd->is(Type::KindOfDouble)) {
cg_printf(", %lldLL)", (int64) sentinel);
} else if (!staticIntSwitchOpnd->is(Type::KindOfInt64)) {
// at this point we must be dealing with a variable
// which implements hashForIntSwitch()
cg_printf(".hashForIntSwitch(%lldLL, %lldLL)",
firstNonZero,
(int64) sentinel);
}
cg_printf(") {\n");
if (m_cases) m_cases->outputCPP(cg, ar);
cg_printf("}\n");
} else if (staticStringCases) {
assert(!var.empty());
assert(tableSize > 0);
// create cases
MapIntToStatementPtrWithPosVec caseMap;
int64
firstTrueCaseHash = 0,
firstNullCaseHash = 0,
firstFalseCaseHash = 0,
firstZeroCaseHash = 0,
firstHash = 0,
noMatchHash = 0;
bool
hasFirstTrue = false,
hasFirstNull = false,
hasFirstFalse = false,
hasFirstZero = false;
CaseStatementPtr defaultCase;
int defaultCaseNum = -1;
int maxHashCase = -1;
set<int64> defaultCases;
if (m_cases) {
for (int i = 0; i < m_cases->getCount(); i++) {
CaseStatementPtr stmt =
static_pointer_cast<CaseStatement>((*m_cases)[i]);
assert(stmt);
if (stmt->getCondition()) {
Variant v;
int64 condHash;
bool hasValue ATTRIBUTE_UNUSED = stmt->getScalarConditionValue(v);
assert(hasValue && !v.isNull());
int64 lval; double dval;
// allow errors, since we want "1abc" to match 1
DataType type = v.getStringData()->isNumericWithVal(lval, dval, 1);
switch (type) {
case KindOfInt64:
condHash = lval;
break;
case KindOfDouble:
condHash = (int64) dval;
break;
case KindOfNull:
{
string l(v.toString());
condHash = hash_string(l.c_str());
}
break;
default:
condHash = 0; // make the compiler happy
assert(false);
break;
}
if (!hasFirstTrue && equal(v, true)) {
hasFirstTrue = true;
firstTrueCaseHash = condHash;
}
if (!hasFirstNull && equal(v, null)) {
hasFirstNull = true;
firstNullCaseHash = condHash;
}
if (!hasFirstFalse && equal(v, false)) {
hasFirstFalse = true;
firstFalseCaseHash = condHash;
}
if (!hasFirstZero && equal(v, 0)) {
hasFirstZero = true;
firstZeroCaseHash = condHash;
}
if (i == 0) firstHash = condHash;
uint64 bucket = ((uint64)condHash) % tableSize;
MapIntToStatementPtrWithPosVec::iterator it(caseMap.find(bucket));
if (it == caseMap.end()) {
boost::shared_ptr<StatementPtrWithPosVec>
list(new StatementPtrWithPosVec());
list->push_back(StatementPtrWithPos(i, stmt));
caseMap[bucket] = list;
} else {
it->second->push_back(StatementPtrWithPos(i, stmt));
}
maxHashCase = std::max(maxHashCase, i);
} else {
// we only care about the *last* default case, so we let it be
// overriden each time
defaultCase = stmt;
defaultCaseNum = i;
defaultCases.insert(i);
}
}
// compute the no match hash
noMatchHash = tableSize;
}
cg_printf("bool needsOrder;\n");
cg_printf("int64 hash;\n");
switch (m_exp->getType()->getKindOf()) {
case Type::KindOfBoolean:
cg_printf("needsOrder = false;\n");
cg_printf("hash = %s ? %lldLL : %lldLL;\n",
var.c_str(),
firstTrueCaseHash,
firstFalseCaseHash);
break;
case Type::KindOfInt64:
cg_printf("needsOrder = false;\n");
cg_printf("hash = %s == 0 ? %lldLL : %s;\n",
var.c_str(),
firstZeroCaseHash,
var.c_str());
break;
case Type::KindOfDouble:
cg_printf("needsOrder = false;\n");
cg_printf("hash = %s == 0 ? %lldLL : ((int64)%s);\n",
var.c_str(),
firstZeroCaseHash,
var.c_str());
break;
default:
cg_printf("hash = %s.hashForStringSwitch("
"%lldLL, %lldLL, %lldLL, %lldLL, %lldLL, %lldLL, needsOrder);\n",
var.c_str(),
firstTrueCaseHash,
firstNullCaseHash,
firstFalseCaseHash,
firstZeroCaseHash,
firstHash,
noMatchHash);
break;
}
cg_printf("switch (((uint64) hash) & %lluUL) {\n", tableSize - 1);
// need to precompute which hash labels will be jumped to,
// thanks to no __attribute__((unused)) decls allowed after labels:
// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=11613
size_t bucketIdx = 0;
MapIntToStatementPtrWithPosVec::iterator it(caseMap.begin());
set<int> willJumpTo;
for (; it != caseMap.end(); ++it, ++bucketIdx) {
StatementPtrWithPosVecPtr cases = it->second;
StatementPtrWithPosVec::iterator caseIt(cases->begin());
for (; caseIt != cases->end(); ++caseIt) {
StatementPtrWithPos p = *caseIt;
if (p.first < maxHashCase) {
int c = p.first + 1;
while (defaultCases.find(c) != defaultCases.end()) c++;
assert(c <= maxHashCase);
if (caseIt + 1 == cases->end() ||
(*(caseIt + 1)).first != c) {
willJumpTo.insert(c);
}
}
}
}
bucketIdx = 0;
it = caseMap.begin();
for (; it != caseMap.end(); ++it, ++bucketIdx) {
uint64 bucket = it->first;
StatementPtrWithPosVecPtr cases = it->second;
size_t caseNum = 0;
StatementPtrWithPosVec::iterator caseIt(cases->begin());
for (; caseIt != cases->end(); ++caseIt, ++caseNum) {
StatementPtrWithPos p = *caseIt;
// emit case_h_s{i} label if necessary
if (willJumpTo.find(p.first) != willJumpTo.end()) {
cg_printf("case_%d_h_s%d:\n", varId, p.first);
}
if (caseNum == 0) {
// emit bucket case label
cg_printf("case %lluUL:\n", bucket);
}
assert(p.second->getCondition());
// emit equality check
cg.indentBegin();
p.second->getCondition()->outputCPPBegin(cg, ar);
cg_printf("if (equal(%s, (", var.c_str());
p.second->getCondition()->outputCPP(cg, ar);
cg_printf("))) goto case_%d_%d;\n", varId, p.first);
p.second->getCondition()->outputCPPEnd(cg, ar);
// emit jump check if necessary
if (p.first < maxHashCase) {
int c = p.first + 1;
while (defaultCases.find(c) != defaultCases.end()) c++;
assert(c <= maxHashCase);
// see if the next hash case to jump to
// is the next in the bucket chain, if so, no need to jump
if (caseIt + 1 == cases->end() ||
(*(caseIt + 1)).first != c) {
assert(willJumpTo.find(c) != willJumpTo.end());
cg_printf("if (UNLIKELY(needsOrder)) goto case_%d_h_s%d;\n",
varId, c);
}
}
cg.indentEnd();
}
// emit jump to default:
cg.indentBegin();
if (defaultCaseNum >= 0) {
cg_printf("goto case_%d_%d;\n", varId, defaultCaseNum);
} else {
cg_printf("goto break%d;\n", labelId);
}
cg.indentEnd();
}
if (defaultCaseNum >= 0) {
cg_printf("default: goto case_%d_%d;\n", varId, defaultCaseNum);
} else {
cg_printf("default: goto break%d;\n", labelId);
cg.addLabelId("break", labelId);
}
cg_printf("}\n");
if (closeBrace) cg_indentEnd("}\n");
// now emit the cases
if (m_cases) {
for (int i = 0; i < m_cases->getCount(); i++) {
CaseStatementPtr stmt =
static_pointer_cast<CaseStatement>((*m_cases)[i]);
stmt->outputCPPByNumber(cg, ar, varId,
!stmt->getCondition() && defaultCaseNum != i ?
-1 : i);
}
}
} else {
if (var.empty()) {
varId = cg.createNewLocalId(shared_from_this());
if (m_exp->hasContext(Expression::LValue) &&
m_exp->is(Expression::KindOfSimpleVariable) &&
static_pointer_cast<SimpleVariable>(m_exp)
->hasAssignableCPPVariable()) {
// use existing variable
var = static_pointer_cast<SimpleVariable>(m_exp)
->getAssignableCPPVariable(ar);
assert(!var.empty());
} else {
assert(!closeBrace);
cg_indentBegin("{\n");
closeBrace = true;
var = string(Option::SwitchPrefix) + lexical_cast<string>(varId);
m_exp->getType()->outputCPPDecl(cg, ar, getScope());
cg_printf(" %s = (", var.c_str());
m_exp->outputCPP(cg, ar);
cg_printf(");\n");
}
}
if (m_cases && m_cases->getCount()) {
CaseStatementPtr defaultCase;
int defaultCaseNum = -1;
for (int i = 0; i < m_cases->getCount(); i++) {
CaseStatementPtr stmt =
dynamic_pointer_cast<CaseStatement>((*m_cases)[i]);
if (stmt->getCondition()) {
stmt->outputCPPAsIf(cg, ar, varId, var.c_str(), i);
} else {
defaultCase = stmt;
defaultCaseNum = i;
}
}
if (defaultCaseNum != -1) {
defaultCase->outputCPPAsIf(cg, ar, varId, var.c_str(), defaultCaseNum);
} else {
cg_printf("goto break%d;\n", labelId);
cg.addLabelId("break", labelId);
}
if (closeBrace) cg_indentEnd("}\n");
cg_printf("\n");
for (int i = 0; i < m_cases->getCount(); i++) {
CaseStatementPtr stmt =
dynamic_pointer_cast<CaseStatement>((*m_cases)[i]);
stmt->outputCPPByNumber(cg, ar, varId,
!stmt->getCondition() && defaultCaseNum != i ?
-1 : i);
}
}
}
// Even though switch's break/continue will never goto these labels, we need
// them for "break/continue n" inside switches.
if (cg.findLabelId("continue", labelId)) {
cg_printf("continue%d:;\n", labelId);
}
if (cg.findLabelId("break", labelId)) {
cg_printf("break%d:;\n", labelId);
}
cg.popBreakScope();
}