Comparar commits
60 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 5848896a92 | |||
| 2f4aa50bc5 | |||
| e23001357e | |||
| 179439a9e4 | |||
| 6d7e68a5cc | |||
| 88a2da1408 | |||
| 7ae34b0022 | |||
| 4915bdf7fd | |||
| 9e1f0cd094 | |||
| 79db99c827 | |||
| 952163b9ae | |||
| 8e87aaa617 | |||
| 7f6e7e9cbe | |||
| 6a41c10cdf | |||
| df4a7e74da | |||
| 12a756085f | |||
| 4b33353789 | |||
| df1ca4c26c | |||
| 0a6adf3bf4 | |||
| b5972e789b | |||
| 37ba715ec4 | |||
| 22bc89ab93 | |||
| 094ba8ed5b | |||
| be934dc214 | |||
| 250a811e2b | |||
| 94a523a232 | |||
| 1db1c1c0b9 | |||
| e7ab1d6c48 | |||
| 72a86a583a | |||
| bbc51c4a89 | |||
| 7a6a9aa1eb | |||
| 345aad19ea | |||
| dbd5b4d531 | |||
| 25c9bf57ef | |||
| ba6bc49e63 | |||
| 1649218428 | |||
| ccb3ac68b0 | |||
| 73dfe7a892 | |||
| 2a5577435f | |||
| d2762c0a48 | |||
| 1d445d8ed7 | |||
| c4d323fac8 | |||
| 60d0e7dad9 | |||
| f68fad38d2 | |||
| ebe1575c98 | |||
| d27ddec787 | |||
| 8ea8e65c21 | |||
| 964a37d43f | |||
| 3680a85130 | |||
| 905f7e5386 | |||
| e3c32c5f8e | |||
| 283579ef6a | |||
| 2533a2193a | |||
| 45fa5837fc | |||
| 467c1a8024 | |||
| b04bae25f3 | |||
| 8f574018aa | |||
| cae20c2e28 | |||
| 4f3652034d | |||
| 75a3806bae |
@@ -21,7 +21,7 @@ CHECK_C_SOURCE_RUNS("#include <dlfcn.h>
|
||||
void testfunc() {}
|
||||
int main() {
|
||||
testfunc();
|
||||
return dyslm(0, "_testfunc") != (void*)0;
|
||||
return dyslm(0, \"_testfunc\") != (void*)0;
|
||||
}" LIBDL_NEEDS_UNDERSCORE)
|
||||
|
||||
mark_as_advanced(LIBDL_INCLUDE_DIRS LIBDL_LIBRARIES LIBDL_NEEDS_UNDERSCORE)
|
||||
|
||||
@@ -77,8 +77,29 @@ function(embed_systemlib TARGET DEST SOURCE)
|
||||
ARGS "--add-section" "systemlib=${SOURCE}" ${DEST}
|
||||
COMMENT "Embedding systemlib.php in ${TARGET}")
|
||||
endif()
|
||||
# Add the systemlib file to the "LINK_DEPENDS" for the systemlib, this will cause it
|
||||
# to be relinked and the systemlib re-embedded
|
||||
set_property(TARGET ${TARGET} APPEND PROPERTY LINK_DEPENDS ${SOURCE})
|
||||
endfunction(embed_systemlib)
|
||||
|
||||
# Custom install function that doesn't relink, instead it uses chrpath to change it, if
|
||||
# it's available, otherwise, it leaves the chrpath alone
|
||||
function(HHVM_INSTALL TARGET DEST)
|
||||
get_target_property(LOC ${TARGET} LOCATION)
|
||||
get_target_property(TY ${TARGET} TYPE)
|
||||
if (FOUND_CHRPATH)
|
||||
get_target_property(RPATH ${TARGET} INSTALL_RPATH)
|
||||
if (NOT RPATH STREQUAL "RPATH-NOTFOUND")
|
||||
if (RPATH STREQUAL "")
|
||||
install(CODE "execute_process(COMMAND \"${CHRPATH}\" \"-d\" \"${LOC}\" ERROR_QUIET)")
|
||||
else()
|
||||
install(CODE "execute_process(COMMAND \"${CHRPATH}\" \"-r\" \"${RPATH}\" \"${LOC}\" ERROR_QUIET)")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
install(CODE "FILE(INSTALL DESTINATION \"\${CMAKE_INSTALL_PREFIX}/${DEST}\" TYPE ${TY} FILES \"${LOC}\")")
|
||||
endfunction(HHVM_INSTALL)
|
||||
|
||||
function(HHVM_EXTENSION EXTNAME)
|
||||
list(REMOVE_AT ARGV 0)
|
||||
add_library(${EXTNAME} SHARED ${ARGV})
|
||||
|
||||
@@ -25,6 +25,15 @@ IF(NOT DEFINED CMAKE_PREFIX_PATH)
|
||||
message(STATUS "CMAKE_PREFIX_PATH was missing, proceeding anyway")
|
||||
endif()
|
||||
|
||||
# Look for the chrpath tool so we can warn if it's not there
|
||||
find_program(CHRPATH chrpath)
|
||||
IF (CHRPATH STREQUAL "CHRPATH-NOTFOUND")
|
||||
SET(FOUND_CHRPATH OFF)
|
||||
message(WARNING "chrpath not found, rpath will not be stripped from installed binaries")
|
||||
else()
|
||||
SET(FOUND_CHRPATH ON)
|
||||
endif()
|
||||
|
||||
LIST(APPEND CMAKE_PREFIX_PATH "$ENV{CMAKE_PREFIX_PATH}")
|
||||
|
||||
if(APPLE)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# HipHop VM for PHP [](https://travis-ci.org/facebook/hiphop-php)
|
||||
# HipHop VM for PHP [](https://travis-ci.org/facebook/hhvm)
|
||||
|
||||
HipHop VM (HHVM) is a new open-source virtual machine designed for executing programs written in PHP. HHVM uses a just-in-time compilation approach to achieve superior performance while maintaining the flexibility that PHP developers are accustomed to. HipHop VM (and before it HPHPc) has realized > 5x increase in throughput for Facebook compared with Zend PHP 5.2.
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# HHVM build results
|
||||
*.hhbc
|
||||
*.[oa]
|
||||
/hhvm/hhvm
|
||||
/hhvm/hphp
|
||||
|
||||
# vim swapfiles
|
||||
.*.swp
|
||||
.*.swo
|
||||
|
||||
# tags files
|
||||
*/tags
|
||||
*/TAGS
|
||||
|
||||
# logs
|
||||
*.log
|
||||
|
||||
# git patch files
|
||||
*.diff
|
||||
|
||||
# OS X
|
||||
.DS_Store
|
||||
._.DS_Store
|
||||
|
||||
# gdb
|
||||
.gdb_history
|
||||
@@ -118,6 +118,12 @@ namespace {
|
||||
namespace StackSym {
|
||||
static const char None = 0x00;
|
||||
|
||||
/*
|
||||
* We don't actually track the U flavor (we treat it as a C),
|
||||
* because there's nothing important to do with it for emission.
|
||||
* The verifier will check they are only created at the appropriate
|
||||
* times.
|
||||
*/
|
||||
static const char C = 0x01; // Cell symbolic flavor
|
||||
static const char V = 0x02; // Var symbolic flavor
|
||||
static const char A = 0x03; // Classref symbolic flavor
|
||||
@@ -287,12 +293,13 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#define COUNT_TWO(t1,t2) 2
|
||||
#define COUNT_THREE(t1,t2,t3) 3
|
||||
#define COUNT_FOUR(t1,t2,t3,t4) 4
|
||||
#define COUNT_LMANY 0
|
||||
#define COUNT_C_LMANY 0
|
||||
#define COUNT_R_LMANY 0
|
||||
#define COUNT_V_LMANY 0
|
||||
#define COUNT_MMANY 0
|
||||
#define COUNT_C_MMANY 0
|
||||
#define COUNT_R_MMANY 0
|
||||
#define COUNT_V_MMANY 0
|
||||
#define COUNT_FMANY 0
|
||||
#define COUNT_CVMANY 0
|
||||
#define COUNT_CVUMANY 0
|
||||
#define COUNT_CMANY 0
|
||||
|
||||
#define ONE(t) \
|
||||
@@ -333,21 +340,22 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
POP_##t2(1); \
|
||||
POP_##t3(2); \
|
||||
POP_##t4(3)
|
||||
#define POP_LMANY \
|
||||
getEmitterVisitor().popEvalStackLMany()
|
||||
#define POP_C_LMANY \
|
||||
#define POP_MMANY \
|
||||
getEmitterVisitor().popEvalStackMMany()
|
||||
#define POP_C_MMANY \
|
||||
getEmitterVisitor().popEvalStack(StackSym::C); \
|
||||
getEmitterVisitor().popEvalStackLMany()
|
||||
#define POP_V_LMANY \
|
||||
getEmitterVisitor().popEvalStackMMany()
|
||||
#define POP_V_MMANY \
|
||||
getEmitterVisitor().popEvalStack(StackSym::V); \
|
||||
getEmitterVisitor().popEvalStackLMany()
|
||||
#define POP_R_LMANY \
|
||||
getEmitterVisitor().popEvalStackMMany()
|
||||
#define POP_R_MMANY \
|
||||
getEmitterVisitor().popEvalStack(StackSym::R); \
|
||||
getEmitterVisitor().popEvalStackLMany()
|
||||
getEmitterVisitor().popEvalStackMMany()
|
||||
#define POP_FMANY \
|
||||
getEmitterVisitor().popEvalStackMany(a1, StackSym::F)
|
||||
#define POP_CVMANY \
|
||||
getEmitterVisitor().popEvalStackCVMany(a1)
|
||||
#define POP_CVUMANY POP_CVMANY
|
||||
#define POP_CMANY \
|
||||
getEmitterVisitor().popEvalStackMany(a1, StackSym::C)
|
||||
|
||||
@@ -409,6 +417,7 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#define PUSH_INS_2(t) PUSH_INS_2_##t
|
||||
|
||||
#define PUSH_CV getEmitterVisitor().pushEvalStack(StackSym::C)
|
||||
#define PUSH_UV PUSH_CV
|
||||
#define PUSH_VV getEmitterVisitor().pushEvalStack(StackSym::V)
|
||||
#define PUSH_AV getEmitterVisitor().pushEvalStack(StackSym::A)
|
||||
#define PUSH_RV getEmitterVisitor().pushEvalStack(StackSym::R)
|
||||
@@ -574,10 +583,10 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#undef POP_TWO
|
||||
#undef POP_THREE
|
||||
#undef POP_FOUR
|
||||
#undef POP_LMANY
|
||||
#undef POP_C_LMANY
|
||||
#undef POP_V_LMANY
|
||||
#undef POP_R_LMANY
|
||||
#undef POP_MMANY
|
||||
#undef POP_C_MMANY
|
||||
#undef POP_V_MMANY
|
||||
#undef POP_R_MMANY
|
||||
#undef POP_CV
|
||||
#undef POP_VV
|
||||
#undef POP_HV
|
||||
@@ -587,6 +596,7 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#undef POP_LREST
|
||||
#undef POP_FMANY
|
||||
#undef POP_CVMANY
|
||||
#undef POP_CVUMANY
|
||||
#undef POP_CMANY
|
||||
#undef POP_LA_ONE
|
||||
#undef POP_LA_TWO
|
||||
@@ -609,6 +619,7 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#undef PUSH_THREE
|
||||
#undef PUSH_FOUR
|
||||
#undef PUSH_CV
|
||||
#undef PUSH_UV
|
||||
#undef PUSH_VV
|
||||
#undef PUSH_HV
|
||||
#undef PUSH_AV
|
||||
@@ -1277,7 +1288,7 @@ void EmitterVisitor::popSymbolicLocal(Op op, int arg, int pos) {
|
||||
}
|
||||
}
|
||||
|
||||
void EmitterVisitor::popEvalStackLMany() {
|
||||
void EmitterVisitor::popEvalStackMMany() {
|
||||
while (!m_evalStack.empty()) {
|
||||
char sym = m_evalStack.top();
|
||||
char symFlavor = StackSym::GetSymFlavor(sym);
|
||||
@@ -1994,12 +2005,6 @@ bool EmitterVisitor::visit(ConstructPtr node) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EmitterVisitor::emitFatal(Emitter& e, const char* message) {
|
||||
const StringData* msg = makeStaticString(message);
|
||||
e.String(msg);
|
||||
e.Fatal(0);
|
||||
}
|
||||
|
||||
bool EmitterVisitor::visitImpl(ConstructPtr node) {
|
||||
if (!node) return false;
|
||||
|
||||
@@ -2013,8 +2018,8 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
|
||||
return false;
|
||||
|
||||
case Statement::KindOfTypedefStatement: {
|
||||
emitFatal(e, "Type statements are currently only allowed at "
|
||||
"the top-level");
|
||||
emitMakeUnitFatal(e, "Type statements are currently only allowed at "
|
||||
"the top-level");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2029,8 +2034,7 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
|
||||
if (bs->getDepth() > 1) {
|
||||
msg << "s";
|
||||
}
|
||||
e.String(makeStaticString(msg.str()));
|
||||
e.Fatal(0);
|
||||
emitMakeUnitFatal(e, msg.str().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3158,7 +3162,9 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
|
||||
if (p->getScalarValue(v)) {
|
||||
assert(v.isString());
|
||||
StringData* msg = makeStaticString(v.toString());
|
||||
throw IncludeTimeFatalException(call, "%s", msg->data());
|
||||
auto exn = IncludeTimeFatalException(call, "%s", msg->data());
|
||||
exn.setParseFatal(call->isParseFatalFunction());
|
||||
throw exn;
|
||||
}
|
||||
not_reached();
|
||||
}
|
||||
@@ -4750,15 +4756,14 @@ void EmitterVisitor::emitUnset(Emitter& e,
|
||||
case StackSym::LG: e.CGetL(m_evalStack.getLoc(i)); // fall through
|
||||
case StackSym::CG: e.UnsetG(); break;
|
||||
case StackSym::LS: // fall through
|
||||
case StackSym::CS:
|
||||
case StackSym::CS: {
|
||||
assert(exp);
|
||||
e.String(
|
||||
makeStaticString("Attempt to unset static property " +
|
||||
exp->getText())
|
||||
);
|
||||
e.Fatal(0);
|
||||
break;
|
||||
|
||||
std::ostringstream s;
|
||||
s << "Attempt to unset static property " << exp->getText();
|
||||
emitMakeUnitFatal(e, s.str().c_str());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
unexpectedStackSym(sym, "emitUnset");
|
||||
break;
|
||||
@@ -5879,10 +5884,10 @@ void EmitterVisitor::emitMethodPrologue(Emitter& e, MethodStatementPtr meth) {
|
||||
}
|
||||
|
||||
if (funcScope->isAbstract()) {
|
||||
StringData* msg = makeStaticString(
|
||||
"Cannot call abstract method " + meth->getOriginalFullName() + "()");
|
||||
e.String(msg);
|
||||
e.Fatal(1);
|
||||
std::ostringstream s;
|
||||
s << "Cannot call abstract method " << meth->getOriginalFullName() << "()";
|
||||
emitMakeUnitFatal(e, s.str().c_str(),
|
||||
FatalKind::Runtime, true /* skipFrame */);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7182,10 +7187,13 @@ void EmitterVisitor::emitRestoreErrorReporting(Emitter& e, Id oldLevelLoc) {
|
||||
dontRollback.set(e);
|
||||
}
|
||||
|
||||
void EmitterVisitor::emitMakeUnitFatal(Emitter& e, const std::string& msg) {
|
||||
StringData* sd = makeStaticString(msg);
|
||||
void EmitterVisitor::emitMakeUnitFatal(Emitter& e,
|
||||
const char* msg,
|
||||
FatalKind k /* = FatalKind::Runtime */,
|
||||
bool skipFrame /* = false */) {
|
||||
const StringData* sd = makeStaticString(msg);
|
||||
e.String(sd);
|
||||
e.Fatal(0);
|
||||
e.Fatal(uint8_t(k), uint8_t(skipFrame));
|
||||
}
|
||||
|
||||
void EmitterVisitor::addFunclet(Thunklet* body, Label* entry) {
|
||||
@@ -7453,7 +7461,8 @@ static UnitEmitter* emitHHBCUnitEmitter(AnalysisResultPtr ar, FileScopePtr fsp,
|
||||
EmitterVisitor fev(*ue);
|
||||
Emitter emitter(ex.m_node, *ue, fev);
|
||||
FuncFinisher ff(&fev, emitter, ue->getMain());
|
||||
fev.emitMakeUnitFatal(emitter, ex.getMessage());
|
||||
auto kind = ex.m_parseFatal ? FatalKind::Parse : FatalKind::Runtime;
|
||||
fev.emitMakeUnitFatal(emitter, ex.getMessage().c_str(), kind);
|
||||
}
|
||||
return ue;
|
||||
}
|
||||
@@ -7906,14 +7915,12 @@ static void batchCommit(std::vector<UnitEmitter*>& ues) {
|
||||
}
|
||||
|
||||
// Clean up.
|
||||
for (std::vector<UnitEmitter*>::const_iterator it = ues.begin();
|
||||
it != ues.end(); ++it) {
|
||||
UnitEmitter* ue = *it;
|
||||
// Commit units individually if an error occurred during batch commit.
|
||||
if (err) {
|
||||
repo.commitUnit(ue, UnitOrigin::File);
|
||||
}
|
||||
delete ue;
|
||||
for (auto ue : ues) {
|
||||
// Commit units individually if an error occurred during batch commit.
|
||||
if (err) {
|
||||
repo.commitUnit(ue, UnitOrigin::File);
|
||||
}
|
||||
delete ue;
|
||||
}
|
||||
ues.clear();
|
||||
}
|
||||
|
||||
@@ -349,7 +349,7 @@ public:
|
||||
bool evalStackIsUnknown() { return m_evalStackIsUnknown; }
|
||||
void popEvalStack(char symFlavor, int arg = -1, int pos = -1);
|
||||
void popSymbolicLocal(Op opcode, int arg = -1, int pos = -1);
|
||||
void popEvalStackLMany();
|
||||
void popEvalStackMMany();
|
||||
void popEvalStackMany(int len, char symFlavor);
|
||||
void popEvalStackCVMany(int len);
|
||||
void pushEvalStack(char symFlavor);
|
||||
@@ -374,12 +374,14 @@ public:
|
||||
class IncludeTimeFatalException : public Exception {
|
||||
public:
|
||||
ConstructPtr m_node;
|
||||
bool m_parseFatal;
|
||||
IncludeTimeFatalException(ConstructPtr node, const char* fmt, ...)
|
||||
: Exception(), m_node(node) {
|
||||
: Exception(), m_node(node), m_parseFatal(false) {
|
||||
va_list ap; va_start(ap, fmt); format(fmt, ap); va_end(ap);
|
||||
}
|
||||
virtual ~IncludeTimeFatalException() throw() {}
|
||||
EXCEPTION_COMMON_IMPL(IncludeTimeFatalException);
|
||||
void setParseFatal(bool b = true) { m_parseFatal = b; }
|
||||
};
|
||||
|
||||
void pushIterScope(Id id, IterKind kind) {
|
||||
@@ -513,9 +515,6 @@ private:
|
||||
int defI;
|
||||
};
|
||||
|
||||
private:
|
||||
void emitFatal(Emitter& e, const char* message);
|
||||
|
||||
private:
|
||||
static const size_t kMinStringSwitchCases = 8;
|
||||
UnitEmitter& m_ue;
|
||||
@@ -687,7 +686,10 @@ public:
|
||||
int vLocalId);
|
||||
void emitForeach(Emitter& e, ForEachStatementPtr fe);
|
||||
void emitRestoreErrorReporting(Emitter& e, Id oldLevelLoc);
|
||||
void emitMakeUnitFatal(Emitter& e, const std::string& message);
|
||||
void emitMakeUnitFatal(Emitter& e,
|
||||
const char* msg,
|
||||
FatalKind k = FatalKind::Runtime,
|
||||
bool skipFrame = false);
|
||||
|
||||
void addFunclet(Thunklet* body, Label* entry);
|
||||
void emitFunclets(Emitter& e);
|
||||
|
||||
@@ -73,8 +73,7 @@ FunctionScopePtr FileScope::setTree(AnalysisResultConstPtr ar,
|
||||
return createPseudoMain(ar);
|
||||
}
|
||||
|
||||
void FileScope::cleanupForError(AnalysisResultConstPtr ar,
|
||||
int line, const string &msg) {
|
||||
void FileScope::cleanupForError(AnalysisResultConstPtr ar) {
|
||||
for (StringToClassScopePtrVecMap::const_iterator iter = m_classes.begin();
|
||||
iter != m_classes.end(); ++iter) {
|
||||
BOOST_FOREACH(ClassScopePtr cls, iter->second) {
|
||||
@@ -90,8 +89,16 @@ void FileScope::cleanupForError(AnalysisResultConstPtr ar,
|
||||
StringToClassScopePtrVecMap().swap(m_classes);
|
||||
m_pseudoMain.reset();
|
||||
m_tree.reset();
|
||||
}
|
||||
|
||||
template <class Meth>
|
||||
void makeFatalMeth(FileScope& file,
|
||||
AnalysisResultConstPtr ar,
|
||||
const std::string& msg,
|
||||
int line,
|
||||
Meth meth) {
|
||||
LocationPtr loc(new Location());
|
||||
loc->file = m_fileName.c_str();
|
||||
loc->file = file.getName().c_str();
|
||||
loc->first(line, 0);
|
||||
loc->last(line, 0);
|
||||
BlockScopePtr scope;
|
||||
@@ -100,16 +107,30 @@ void FileScope::cleanupForError(AnalysisResultConstPtr ar,
|
||||
SimpleFunctionCallPtr e(
|
||||
new SimpleFunctionCall(scope, loc, "throw_fatal", false, args,
|
||||
ExpressionPtr()));
|
||||
e->setThrowFatal();
|
||||
meth(e);
|
||||
ExpStatementPtr exp(new ExpStatement(scope, loc, e));
|
||||
StatementListPtr stmts(new StatementList(scope, loc));
|
||||
stmts->addElement(exp);
|
||||
|
||||
FunctionScopePtr fs = setTree(ar, stmts);
|
||||
fs->setOuterScope(shared_from_this());
|
||||
FunctionScopePtr fs = file.setTree(ar, stmts);
|
||||
fs->setOuterScope(file.shared_from_this());
|
||||
fs->getStmt()->resetScope(fs);
|
||||
fs->getStmt()->setLocation(loc);
|
||||
setOuterScope(const_cast<AnalysisResult*>(ar.get())->shared_from_this());
|
||||
file.setOuterScope(const_cast<AnalysisResult*>(ar.get())->shared_from_this());
|
||||
}
|
||||
|
||||
void FileScope::makeFatal(AnalysisResultConstPtr ar,
|
||||
const std::string& msg,
|
||||
int line) {
|
||||
auto meth = [](SimpleFunctionCallPtr e) { e->setThrowFatal(); };
|
||||
makeFatalMeth(*this, ar, msg, line, meth);
|
||||
}
|
||||
|
||||
void FileScope::makeParseFatal(AnalysisResultConstPtr ar,
|
||||
const std::string& msg,
|
||||
int line) {
|
||||
auto meth = [](SimpleFunctionCallPtr e) { e->setThrowParseFatal(); };
|
||||
makeFatalMeth(*this, ar, msg, line, meth);
|
||||
}
|
||||
|
||||
bool FileScope::addFunction(AnalysisResultConstPtr ar,
|
||||
|
||||
@@ -107,8 +107,11 @@ public:
|
||||
* are the only functions a parser calls upon analysis results.
|
||||
*/
|
||||
FunctionScopePtr setTree(AnalysisResultConstPtr ar, StatementListPtr tree);
|
||||
void cleanupForError(AnalysisResultConstPtr ar,
|
||||
int line, const std::string &msg);
|
||||
void cleanupForError(AnalysisResultConstPtr ar);
|
||||
void makeFatal(AnalysisResultConstPtr ar,
|
||||
const std::string& msg, int line);
|
||||
void makeParseFatal(AnalysisResultConstPtr ar,
|
||||
const std::string& msg, int line);
|
||||
|
||||
bool addFunction(AnalysisResultConstPtr ar, FunctionScopePtr funcScope);
|
||||
bool addClass(AnalysisResultConstPtr ar, ClassScopePtr classScope);
|
||||
|
||||
@@ -42,7 +42,13 @@ public:
|
||||
void setValid() { m_valid = true; }
|
||||
void setFromCompiler() { m_fromCompiler = true; }
|
||||
void setThrowFatal() { m_type = FunType::ThrowFatal; }
|
||||
int isFatalFunction() const { return m_type == FunType::ThrowFatal; }
|
||||
void setThrowParseFatal() { m_type = FunType::ThrowParseFatal; }
|
||||
bool isParseFatalFunction() const {
|
||||
return m_type == FunType::ThrowParseFatal;
|
||||
}
|
||||
bool isFatalFunction() const {
|
||||
return isParseFatalFunction() || m_type == FunType::ThrowFatal;
|
||||
}
|
||||
int isStaticCompact() const { return m_type == FunType::StaticCompact; }
|
||||
|
||||
// define(<literal-string>, <scalar>);
|
||||
@@ -93,6 +99,7 @@ protected:
|
||||
GetDefinedVars,
|
||||
FBCallUserFuncSafe,
|
||||
ThrowFatal,
|
||||
ThrowParseFatal,
|
||||
ClassAlias,
|
||||
};
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
#ifdef yyerror
|
||||
#undef yyerror
|
||||
#endif
|
||||
#define yyerror(loc,p,msg) p->fatal(loc,msg)
|
||||
#define yyerror(loc,p,msg) p->parseFatal(loc,msg)
|
||||
|
||||
#ifdef YYLLOC_DEFAULT
|
||||
# undef YYLLOC_DEFAULT
|
||||
|
||||
@@ -174,10 +174,16 @@ bool Parser::parse() {
|
||||
"Parse error: %s",
|
||||
errString().c_str());
|
||||
}
|
||||
} catch (ParseTimeFatalException &e) {
|
||||
m_file->cleanupForError(m_ar, e.m_line, e.getMessage());
|
||||
return true;
|
||||
} catch (const ParseTimeFatalException& e) {
|
||||
m_file->cleanupForError(m_ar);
|
||||
if (e.m_parseFatal) {
|
||||
m_file->makeParseFatal(m_ar, e.getMessage(), e.m_line);
|
||||
} else {
|
||||
m_file->makeFatal(m_ar, e.getMessage(), e.m_line);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Parser::error(const char* fmt, ...) {
|
||||
@@ -190,9 +196,16 @@ void Parser::error(const char* fmt, ...) {
|
||||
fatal(&m_loc, msg.c_str());
|
||||
}
|
||||
|
||||
void Parser::fatal(Location *loc, const char *msg) {
|
||||
throw ParseTimeFatalException(loc->file, loc->line0,
|
||||
"%s", msg);
|
||||
void Parser::parseFatal(const Location* loc, const char* msg) {
|
||||
// we can't use loc->file, as the bison parser doesn't track that in YYLTYPE
|
||||
auto file = m_file->getName().c_str();
|
||||
auto exn = ParseTimeFatalException(file, loc->line0, "%s", msg);
|
||||
exn.setParseFatal();
|
||||
throw exn;
|
||||
}
|
||||
|
||||
void Parser::fatal(const Location* loc, const char* msg) {
|
||||
throw ParseTimeFatalException(loc->file, loc->line0, "%s", msg);
|
||||
}
|
||||
|
||||
string Parser::errString() {
|
||||
|
||||
@@ -26,24 +26,26 @@
|
||||
#include "hphp/compiler/expression/scalar_expression.h"
|
||||
#include "hphp/compiler/statement/statement.h"
|
||||
#include "hphp/compiler/statement/statement_list.h"
|
||||
#include "hphp/util/logger.h"
|
||||
|
||||
#ifdef HPHP_PARSER_NS
|
||||
#undef HPHP_PARSER_NS
|
||||
#endif
|
||||
#define HPHP_PARSER_NS Compiler
|
||||
|
||||
#define LOG_PARSE_ERROR(file, line, fmt, args...) \
|
||||
HPHP::Logger::Error( \
|
||||
"HipHop Fatal error: " fmt " in %s on line %d", \
|
||||
##args, \
|
||||
(file), \
|
||||
(line) \
|
||||
)
|
||||
|
||||
#ifdef HPHP_PARSER_ERROR
|
||||
#undef HPHP_PARSER_ERROR
|
||||
#endif
|
||||
#define HPHP_PARSER_ERROR(fmt, p, args...) \
|
||||
do { \
|
||||
if (HPHP::Option::WholeProgram) { \
|
||||
HPHP::Logger::Error(fmt " %s", ##args, (p)->getMessage(true).c_str()); \
|
||||
} \
|
||||
throw HPHP::ParseTimeFatalException((p)->file(), (p)->line1(), \
|
||||
fmt, ##args); \
|
||||
} while (0)
|
||||
|
||||
#define HPHP_PARSER_ERROR(fmt, p, args...) \
|
||||
throw HPHP::ParseTimeFatalException((p)->file(), (p)->line1(), fmt, ##args)
|
||||
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -125,7 +127,8 @@ public:
|
||||
virtual bool enableFinallyStatement();
|
||||
IMPLEMENT_XHP_ATTRIBUTES;
|
||||
|
||||
virtual void fatal(Location *loc, const char *msg);
|
||||
virtual void fatal(const Location* loc, const char* msg);
|
||||
virtual void parseFatal(const Location* loc, const char* msg);
|
||||
std::string errString();
|
||||
|
||||
// result
|
||||
|
||||
@@ -220,7 +220,8 @@ void MethodStatement::onParseRecur(AnalysisResultConstPtr ar,
|
||||
"Access type for interface method %s::%s() must be omitted",
|
||||
classScope->getOriginalName().c_str(), getOriginalName().c_str());
|
||||
}
|
||||
if (m_modifiers->isAsync()) {
|
||||
// FIXME: WholeProgram check is temporary (t3044335)
|
||||
if (!Option::WholeProgram && m_modifiers->isAsync()) {
|
||||
m_modifiers->parseTimeFatal(
|
||||
Compiler::InvalidAttribute,
|
||||
Strings::ASYNC_WITHOUT_BODY,
|
||||
@@ -253,7 +254,8 @@ void MethodStatement::onParseRecur(AnalysisResultConstPtr ar,
|
||||
classScope->getOriginalName().c_str(),
|
||||
getOriginalName().c_str());
|
||||
}
|
||||
if (m_modifiers->isAsync()) {
|
||||
// FIXME: WholeProgram check is temporary (t3044335)
|
||||
if (!Option::WholeProgram && m_modifiers->isAsync()) {
|
||||
m_modifiers->parseTimeFatal(
|
||||
Compiler::InvalidAttribute,
|
||||
Strings::ASYNC_WITHOUT_BODY,
|
||||
|
||||
@@ -9,7 +9,7 @@ Introduction
|
||||
HipHop bytecode (HHBC) v1 is intended to serve as the conceptual basis for
|
||||
encoding the semantic meaning of HipHop source code into a format that is
|
||||
appropriate for consumption by interpreters and just-in-time compilers. By
|
||||
using simpler constructs to encode more complex expressesion and statements,
|
||||
using simpler constructs to encode more complex expressions and statements,
|
||||
HHBC makes it straightforward for an interpreter or a compiler to determine
|
||||
the order of execution for a program.
|
||||
|
||||
@@ -24,7 +24,7 @@ code into HipHop bytecode in a way that preserves the semantic meaning of the
|
||||
source.
|
||||
|
||||
3) Simplicity. The design of HHBC should avoid features that could be removed
|
||||
or simplified without comprimising PHP 5.4 compatibility, run-time efficiency,
|
||||
or simplified without compromising PHP 5.4 compatibility, run-time efficiency,
|
||||
or design cleanliness.
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ logic.
|
||||
A "ref" is a structure that contains a pointer to a cell container. When a
|
||||
ref is duplicated, the new ref will point to the same container as the
|
||||
original ref. When a ref is duplicated or destroyed, the execution engine is
|
||||
responsible for honoring the containers's refcount logic. When the container
|
||||
responsible for honoring the container's refcount logic. When the container
|
||||
is destroyed, the cell it contains is also destroyed.
|
||||
|
||||
A "classref" is a structure that contains a reference to a class. When a
|
||||
@@ -126,7 +126,7 @@ Functions
|
||||
---------
|
||||
|
||||
A unit's bytecode is organized into functions. Each function has its own
|
||||
metadata that provdes essential information about the function, such as the
|
||||
metadata that provides essential information about the function, such as the
|
||||
name of the function, how many local variables it has, how many iterator
|
||||
variables it has, how many formal parameters it has, the names of the local
|
||||
variables, the names of the formal parameters, how each parameter should be
|
||||
@@ -153,7 +153,7 @@ in the current variable environment.
|
||||
Formally declared parameters are considered to be local variables. Given a
|
||||
function with n formally declared parameters, local ids 0 through n-1 will be
|
||||
used to reference the formally declared parameters. Formal parameters without
|
||||
default values are called "required parameters", while formal parmeters with
|
||||
default values are called "required parameters", while formal parameters with
|
||||
default values are called "optional parameters".
|
||||
|
||||
The bytecode for each function consists of the instructions of the primary
|
||||
@@ -163,7 +163,7 @@ the primary function body, along with information about each fault funclet.
|
||||
Entry points and fault funclets are discussed in more detail in the next
|
||||
section. The total size of the bytecode for the primary function body and all
|
||||
the fault funclets must not exceed 2^31 - 1 bytes. The primary function body
|
||||
and each fault funclet must be a continguous range of bytecode.
|
||||
and each fault funclet must be a contiguous range of bytecode.
|
||||
|
||||
Each function's metadata provides a "line number table" to allow mapping
|
||||
bytecode offsets back to source line numbers. Each row in the line number table
|
||||
@@ -221,7 +221,7 @@ Exceptions
|
||||
|
||||
The metadata for each function provides an "exception handler (EH) table".
|
||||
Each row in the EH table consists of a kind ("fault" or "catch"), a range
|
||||
of bytecode that constitues the protected region, and an offset of a fault
|
||||
of bytecode that constitutes the protected region, and an offset of a fault
|
||||
funclet or list of (classname, offset) pairs describing catch entry points.
|
||||
|
||||
Each range of bytecode is given by a starting offset and an ending
|
||||
@@ -270,8 +270,13 @@ Unit metadata
|
||||
Every compilation unit has a litstr table, a scalar array table, a function
|
||||
table, and a class table.
|
||||
|
||||
The litstr table maps litstr ids to literal strings. Litstr ids are signed
|
||||
32-bit integer values. Each litstr id must be between 0 and 2^31 - 2 inclusive.
|
||||
The litstr table maps litstr ids to literal strings. Bytecodes that refer to
|
||||
literal strings do so by litstr id. Litstr ids are signed 32-bit integer
|
||||
values, which must be between 0 and 2^31 - 2 inclusive. In addition to the
|
||||
per-unit litstr tables, a global table is built when generating an
|
||||
"authoritative" repo (one in which all the PHP code is known at bytecode
|
||||
generation time, and is guaranteed not to change). Global litstr ids can be
|
||||
used in any unit, and are encoded in the range [2^30..2^31-2].
|
||||
|
||||
The scalar array table maps scalar array ids to a description of the contents
|
||||
of a scalar array. An array is a scalar array if and only if each element of
|
||||
@@ -320,7 +325,7 @@ function (the callee), transfers the parameters from the evaluation stack to
|
||||
the callee, pops the current FPI off of the FPI stack, and then invokes the
|
||||
dispatcher to call the function.
|
||||
|
||||
Calls to builtin functions may be optimized to avoid pusing an entry on the FPI
|
||||
Calls to builtin functions may be optimized to avoid pushing an entry on the FPI
|
||||
stack if it is known that the builtin function does not need access to the call
|
||||
stack. In this case, the arguments to the builtin are pushed on stack as Cells
|
||||
and Vars, and the builtin can be invoked with the FCallBuiltin instruction.
|
||||
@@ -359,7 +364,7 @@ responsible for following certain rules to honor each property's accessibility
|
||||
and visibility.
|
||||
|
||||
The accessibility and visibility of a property in a given class is determined
|
||||
by that class's definition and the definitons of all of that class's ancestors.
|
||||
by that class's definition and the definitions of all of that class's ancestors.
|
||||
When a property is declared in a class definition (a "declared property") it
|
||||
may be specified as being "public", "protected", or "private". Accessibility
|
||||
and visibility are two related but distinct concepts. Depending on the current
|
||||
@@ -387,13 +392,13 @@ the ancestor class. Note that there may exist a class D that is a descendent of
|
||||
C and declares a property as "public" with the same name as P. In such cases
|
||||
the new "public" declaration in D is considered to refer to the same property
|
||||
as the original "protected" declaration in C, and the "protected" qualifier
|
||||
from the original declaration is effectively overriden by the "public"
|
||||
from the original declaration is effectively overridden by the "public"
|
||||
qualifier from the new declaration. Class D is said to "redeclare" property P
|
||||
with the "public" qualifier. Thus, for instances of class D and descendent
|
||||
classes of D, property P will be visible and accessible in all contexts.
|
||||
Finally, if a class E that is descendent of C does not redeclare P as public
|
||||
and does not have an ancestor class that redeclares P as public, for instances
|
||||
of class E the property P will be visibile in all contexts, but only accessible
|
||||
of class E the property P will be visible in all contexts, but only accessible
|
||||
in the context of class E, an ancestor class of E, or a descendent class of E.
|
||||
|
||||
If a property P is declared with the "private" qualifier in the definition of
|
||||
@@ -454,7 +459,7 @@ engine is responsible for following certain rules to honor each static
|
||||
property's accessibility and visibility.
|
||||
|
||||
The accessibility and visibility of a static property in a given class is
|
||||
determined by that class's definition and the definitons of all of that class's
|
||||
determined by that class's definition and the definitions of all of that class's
|
||||
ancestors. When a static property is declared in a class definition it may be
|
||||
specified as being "public", "protected", or "private". Depending on the
|
||||
current context, a static property may be visible and accessible, visible but
|
||||
@@ -513,7 +518,7 @@ not declared in the class definition.
|
||||
FPI regions
|
||||
-----------
|
||||
|
||||
An FPI region is a continguous range of bytecode that constitutes a call site.
|
||||
An FPI region is a contiguous range of bytecode that constitutes a call site.
|
||||
Each FPI region begins immediately after an FPush* instruction that pushes an
|
||||
FPI structure onto the FPI stack and must end with the corresponding FCall*
|
||||
instruction that pops that FPI structure off of the FPI stack. If two FPI
|
||||
@@ -552,6 +557,8 @@ Here is a description of each flavor descriptor:
|
||||
F - function argument; specifies that the value may be a cell or a ref at run
|
||||
time; this flavor descriptor is used for parameter values that are
|
||||
about to be passed into a function
|
||||
U - uninit; specifies that the value must be an uninitialized null at run
|
||||
time; this is only used for FCallBuiltin
|
||||
|
||||
|
||||
Verifiability
|
||||
@@ -564,7 +571,7 @@ inputs to each instruction is said to be "flavor-safe".
|
||||
|
||||
HHBC provides a set of verification rules that can be mechanically applied to
|
||||
verify that an HHBC program is flavor-safe. All valid HHBC programs must be
|
||||
verifiably flavor-safe, and the execution engine may refuse to execute HHBC
|
||||
verifiably flavor-safe, and the execution engine may refuse to execute HHBC
|
||||
programs that cannot be verified.
|
||||
|
||||
At bytecode generation time, what is known about the state of the evaluation
|
||||
@@ -760,7 +767,7 @@ number of cells from the stack. In most cases the immediate vector arguments
|
||||
are ordered such that the loc-desc comes first (deepest in the stack), with the
|
||||
last M-vector element last (shallowest in the stack). However, classrefs that
|
||||
are part of the BaseSC and BaseSL loc-desc inputs always come last (the cell
|
||||
input to BaseSC comes first though). Instuctions accepting an immediate vector
|
||||
input to BaseSC comes first though). Instructions accepting an immediate vector
|
||||
containing a list of iterators and iterator types use the notation "<iter-vec>".
|
||||
|
||||
In addition to describing each instruction, this instruction set documentation
|
||||
@@ -836,9 +843,9 @@ False [] -> [C:Bool]
|
||||
Push constant. Null pushes null onto the stack, True pushes true onto
|
||||
the stack, and False pushes false onto the stack.
|
||||
|
||||
NullUninit [] -> [C:Uninit]
|
||||
NullUninit [] -> [U]
|
||||
|
||||
Push Uninit Null Cell onto stack.
|
||||
Push an uninitialized null on the stack.
|
||||
|
||||
Int <signed 64-bit integer value> [] -> [C:Int]
|
||||
Double <double value> [] -> [C:Dbl]
|
||||
@@ -1087,7 +1094,7 @@ Shr [C C] -> [C:Int]
|
||||
|
||||
Floor [C] -> [C:Dbl]
|
||||
|
||||
Round $1 to nearest integer value not greater than $1. Converts $1 to
|
||||
Round $1 to nearest integer value not greater than $1. Converts $1 to
|
||||
numeric as appropriate and then takes floor of resulting numeric value.
|
||||
|
||||
Ceil [C] -> [C:Dbl]
|
||||
@@ -1158,13 +1165,15 @@ Exit [C] -> [C:Null]
|
||||
set the exit status to 0, push null onto the stack, and then it will
|
||||
terminate execution.
|
||||
|
||||
Fatal <skip frame> [C] -> []
|
||||
Fatal <error kind> <skip frame> [C] -> []
|
||||
|
||||
Fatal. This instruction throws a fatal error using $1 as the error message.
|
||||
If $1 is not a string, this instruction throws a fatal error with an error
|
||||
message that indicates that the error message was not a string.
|
||||
Setting %1 to 0 will include the full backtrace.
|
||||
Setting %1 to 1 will make the backtrace not include the topmost frame. This
|
||||
Setting %1 to 0 will throw a runtime fatal error.
|
||||
Setting %1 to 1 will throw a parse fatal error.
|
||||
Setting %2 to 0 will include the full backtrace.
|
||||
Setting %2 to 1 will make the backtrace not include the topmost frame. This
|
||||
is useful when fatalling from functions that shouldn't be seen from userland.
|
||||
|
||||
|
||||
@@ -1845,7 +1854,7 @@ FCallArray [F] -> [R]
|
||||
call the callee. When the callee returns, it will transfer the return value
|
||||
onto the caller's evaluation stack using the R flavor.
|
||||
|
||||
FCallBuiltin <total params> <passed params> <litstr id> [C|V..C|V] -> [R]
|
||||
FCallBuiltin <total params> <passed params> <litstr id> [C|V|U..C|V|U] -> [R]
|
||||
|
||||
Optimized builtin call without an ActRec. This instruction attempts to
|
||||
lookup a builtin function named %3. If no function named %3 is defined,
|
||||
@@ -3523,8 +3532,8 @@ CIterFree <iterator id> [] -> []
|
||||
|
||||
IterBreak <iter-vec> <rel offset> [] -> []
|
||||
|
||||
Iterator break. Frees vectors in %1 in left to right order then transfers
|
||||
control to the locaton specified by %2. Surprise checks are performed
|
||||
Iterator break. Frees vectors in %1 in left to right order then transfers
|
||||
control to the locaton specified by %2. Surprise checks are performed
|
||||
before iterators are freed so that in the event of an exception iterators
|
||||
are not double freed. Note that as with normal jumps surprise checks will
|
||||
only be performed if %2 is negative.
|
||||
@@ -3626,7 +3635,7 @@ This [] -> [C:Obj]
|
||||
instruction throws a fatal error. Next, this instruction pushes the current
|
||||
instance onto the stack.
|
||||
|
||||
BareThis <notice> [] -> [C:Obj]
|
||||
BareThis <notice> [] -> [C:Obj|Null]
|
||||
|
||||
This. This instruction pushes the current instance onto the stack. If %1 is
|
||||
not zero, and the current instance is null, emits a notice.
|
||||
@@ -3741,6 +3750,14 @@ ArrayIdx [C C C] -> [C]
|
||||
if found. Otherwise, $1 is pushed onto the stack. A fatal error will be
|
||||
thrown if $3 is not an array.
|
||||
|
||||
AssertTL <local id> <op>
|
||||
AssertTStk <stack offset> <op>
|
||||
|
||||
These opcodes are currently only supplied for debugging purposes. They
|
||||
instruct the runtime that the value in the specified local or stack offset
|
||||
must have a particular type. The type sub-opcodes are not currently
|
||||
specified, as they are particular to the implementation.
|
||||
|
||||
14. Continuation creation and execution
|
||||
---------------------------------------
|
||||
|
||||
@@ -3753,9 +3770,9 @@ CreateCont <function name> [] -> [C]
|
||||
|
||||
CreateAsync <function name> <label id> <number of iterators> [C] -> [C]
|
||||
|
||||
Analogy of CreateCont for async functions. Creates a Continuation object,
|
||||
updates its label with <label id> immediate and stores a value from the stack
|
||||
to its m_value field. Unlike CreateCont, it also copies the first
|
||||
Analogy of CreateCont for async functions. Creates a Continuation object,
|
||||
updates its label with <label id> immediate and stores a value from the stack
|
||||
to its m_value field. Unlike CreateCont, it also copies the first
|
||||
<number of iterators> iterators into the continuation frame.
|
||||
|
||||
ContEnter [C] -> []
|
||||
|
||||
+81
-90
@@ -65,14 +65,14 @@ but may not form loops.
|
||||
No Variables are defined on entry to the main Trace.
|
||||
|
||||
Blocks which are join points may start with a DefLabel with destination
|
||||
Variables. In that case, each predecessor must be a Jmp_ passing a matching
|
||||
number of source Variables. In this case the Jmp_ acts as a tail-call, passing
|
||||
Variables. In that case, each predecessor must be a Jmp passing a matching
|
||||
number of source Variables. In this case the Jmp acts as a tail-call, passing
|
||||
arguments the same way a plain call would.
|
||||
|
||||
Together, the sources of the Jmp_ instructions and the destinations of the
|
||||
Together, the sources of the Jmp instructions and the destinations of the
|
||||
DefLabel instructions act as traditional SSA Phi pseudo-functions; The type
|
||||
of the label's destination is the type-union of the corresponding sources.
|
||||
Because the Jmp_ sources are at the ends of blocks, they do not violate the
|
||||
Because the Jmp sources are at the ends of blocks, they do not violate the
|
||||
SSA dominator rule (rule 2, above).
|
||||
|
||||
Implementation note: The JIT's compilation unit ("Trace" in this spec) actually
|
||||
@@ -337,23 +337,23 @@ approach is that boxed values may alias. Therefore, in lack of memory
|
||||
alias analysis, the inner types generally need to be rechecked before
|
||||
each use.
|
||||
|
||||
D:T = CheckType<T> S0:Gen -> L
|
||||
D:T = CheckType<T> S0:Gen -> B
|
||||
|
||||
Check that the type of the src S0 is T, and if so copy it to D. If
|
||||
S0 is not type T, branch to the label L.
|
||||
S0 is not type T, branch to block B.
|
||||
|
||||
CheckNullptr S0:{CountedStr|NullPtr} -> L
|
||||
CheckNullptr S0:{CountedStr|NullPtr} -> B
|
||||
|
||||
If S0 is a null pointer, branch to L. This is used to check the return value
|
||||
of a native helper that returns a potentially null StringData*.
|
||||
If S0 is a null pointer, branch to block B. This is used to check the
|
||||
return value of a native helper that returns a potentially null StringData*.
|
||||
|
||||
D:T = AssertType<T> S0:{Gen|Nullptr}
|
||||
|
||||
Assert that the type of S0 is T, copying it to D.
|
||||
|
||||
CheckTypeMem<T> S0:PtrToGen -> L
|
||||
CheckTypeMem<T> S0:PtrToGen -> B
|
||||
|
||||
If the value pointed to by S0 is not type T, branch to the label L.
|
||||
If the value pointed to by S0 is not type T, branch to the block B.
|
||||
|
||||
D:FramePtr = GuardLoc<T,localId> S0:FramePtr
|
||||
|
||||
@@ -364,10 +364,10 @@ D:FramePtr = GuardLoc<T,localId> S0:FramePtr
|
||||
Returns a new frame pointer representing the same frame as S0 but with the
|
||||
knowledge that the guarded local has type T.
|
||||
|
||||
D:FramePtr = CheckLoc<T,localId> S0:FramePtr -> L
|
||||
D:FramePtr = CheckLoc<T,localId> S0:FramePtr -> B
|
||||
|
||||
Check that type of the given localId on the frame S0 is T; if not,
|
||||
branch to the label L.
|
||||
branch to block B.
|
||||
|
||||
Returns a new frame pointer representing the same frame as S0 but with the
|
||||
knowledge that the checked local has type T.
|
||||
@@ -407,10 +407,10 @@ D:StkPtr = GuardStk<T,offset> S0:StkPtr
|
||||
Returns a new StkPtr that represents the same stack but with the
|
||||
knowledge that the slot at the index S1 has type T.
|
||||
|
||||
D:StkPtr = CheckStk<T,offset> S0:StkPtr -> L
|
||||
D:StkPtr = CheckStk<T,offset> S0:StkPtr -> B
|
||||
|
||||
Check that the type of the cell on the stack pointed to by S0 at
|
||||
offset (in cells) is T; if not, branch to the label L.
|
||||
offset (in cells) is T; if not, branch to block B.
|
||||
|
||||
Returns a new StkPtr that represents the same stack but with the
|
||||
knowledge that the slot at the index S1 has type T.
|
||||
@@ -432,31 +432,31 @@ D:StkPtr = CastStk<T,offset> S0:StkPtr
|
||||
Returns a new StkPtr that represents the same stack as S0, but with
|
||||
the slot at offset (in cells) converted to type T.
|
||||
|
||||
D:StkPtr = CoerceStk<T,offset> S0:StkPtr -> L
|
||||
D:StkPtr = CoerceStk<T,offset> S0:StkPtr -> B
|
||||
|
||||
Returns a new StkPtr that represents the same stack as S0, but with
|
||||
the slot at offset (in cells) converted to type T. If the type conversion
|
||||
can't be done then branches to label L.
|
||||
can't be done then branches to block B.
|
||||
|
||||
CheckInit S0:Gen -> L
|
||||
CheckInit S0:Gen -> B
|
||||
|
||||
If S0's type is Uninit, branch to label L.
|
||||
If S0's type is Uninit, branch to block B.
|
||||
|
||||
CheckInitMem S0:PtrToGen S1:ConstInt -> L
|
||||
CheckInitMem S0:PtrToGen S1:ConstInt -> B
|
||||
|
||||
If the value at S0 + S1 (in bytes) has type Uninit, branch to L.
|
||||
If the value at S0 + S1 (in bytes) has type Uninit, branch to block B.
|
||||
|
||||
CheckDefinedClsEq<className,classPtr> -> L
|
||||
CheckDefinedClsEq<className,classPtr> -> B
|
||||
|
||||
Compare the currently defined class of name `className' with
|
||||
`classPtr'; if they aren't equal or if `className' is not defined,
|
||||
branch to L.
|
||||
branch to block B.
|
||||
|
||||
CheckCold<TransID> -> L
|
||||
CheckCold<TransID> -> B
|
||||
|
||||
Check if the counter associated with translation TransID is cold
|
||||
(i.e. within a fixed threshold). If it's not (i.e. such translation
|
||||
has reached the "hotness threshold"), then branch to label L.
|
||||
has reached the "hotness threshold"), then branch to block B.
|
||||
|
||||
GuardRefs S0:FuncPtr S1:Int S2:Int S3:Int S4:Int S5:Int
|
||||
|
||||
@@ -476,10 +476,10 @@ D:CountedStr = AssertNonNull S0:{Nullptr|CountedStr}
|
||||
Returns S0, with Nullptr removed from its type. This instruction currently
|
||||
supports a very limited range of types but can be expanded if needed.
|
||||
|
||||
CheckStaticLocInit S0:BoxedCell -> L
|
||||
CheckStaticLocInit S0:BoxedCell -> B
|
||||
|
||||
Check if the static local (RDS) RefData represented by S0 is
|
||||
initialized, and if not branch to L.
|
||||
initialized, and if not branch to block B.
|
||||
|
||||
|
||||
2. Arithmetic
|
||||
@@ -492,7 +492,7 @@ D:{Int|Dbl} = OpSub S0:{Int|Dbl} S1:{Int|Dbl}
|
||||
D:{Int|Dbl} = OpMul S0:{Int|Dbl} S1:{Int|Dbl}
|
||||
D:Int = OpMod S0:Int S1:Int
|
||||
D:Dbl = OpSqrt S0:Dbl
|
||||
D:Dbl = OpDivDbl S0:Dbl S1:Dbl -> L
|
||||
D:Dbl = OpDivDbl S0:Dbl S1:Dbl -> B
|
||||
D:Int = OpBitAnd S0:Int S1:Int
|
||||
D:Int = OpBitOr S0:Int S1:Int
|
||||
D:Int = OpBitXor S0:Int S1:Int
|
||||
@@ -510,10 +510,10 @@ D:Dbl = OpCeil S0:Dbl
|
||||
AbsInt and AbsDbl compute the absolute value of an integer or double
|
||||
respectively.
|
||||
|
||||
OpDivDbl Will branch to L when S1 is zero (signed or unsigned). When the
|
||||
result of the division is a real valued number OpDivDbl conforms to IEEE 754.
|
||||
In particular should the result of a division be zero the sign will follow
|
||||
normal sign rules for division.
|
||||
OpDivDbl Will branch to block B when S1 is zero (signed or unsigned). When
|
||||
the result of the division is a real valued number OpDivDbl conforms to IEEE
|
||||
754. In particular should the result of a division be zero the sign will
|
||||
follow normal sign rules for division.
|
||||
|
||||
Note that OpShr is an arithmetic right shift.
|
||||
|
||||
@@ -665,10 +665,10 @@ D:None JmpSwitchDest<JmpSwitchData> S0:Int
|
||||
Jump to the target of a switch stamement using table metadata
|
||||
<JmpSwitchData> and index S0.
|
||||
|
||||
CheckSurpriseFlags -> L
|
||||
CheckSurpriseFlags -> B
|
||||
|
||||
Tests the implementation-specific surprise flags. If they're true, branches
|
||||
to L.
|
||||
to block B.
|
||||
|
||||
SurpriseHook
|
||||
|
||||
@@ -680,33 +680,26 @@ FunctionExitSurpriseHook S0:FramePtr S1:StkPtr S2:Gen
|
||||
should be the current frame pointer, S1 should be the current stack pointer,
|
||||
and S2 should be the return value of the function.
|
||||
|
||||
ExitOnVarEnv S0:FramePtr -> L
|
||||
ExitOnVarEnv S0:FramePtr -> B
|
||||
|
||||
Loads the VarEnv slot off the ActRec pointed to by S0. If it is
|
||||
non-zero, jumps to the exit-trace label L.
|
||||
non-zero, jumps to the exit-trace block B.
|
||||
|
||||
Jmp_ [S:T ...] -> L
|
||||
Jmp -> B
|
||||
Jmp [S:T ...] -> B: DefLabel
|
||||
|
||||
Unconditional jump to L. L is the name of a DefLabel instruction.
|
||||
Each the sources are copied to the corresponding destinations of the
|
||||
target L. The number of destinations on L must match the number of
|
||||
sources of this Jmp_.
|
||||
Unconditional jump to block B. In the second form, the target block must
|
||||
start with a DefLabel with the same number of destinations as Jmp's number
|
||||
of sources. Jmp parallel-copies its sources to the DefLabel destinations.
|
||||
|
||||
DefLabel
|
||||
D:T ... = DefLabel
|
||||
|
||||
Denote the position of a jump target. Branch instructions, and
|
||||
instructions which implicitly guard, take a Label argument which refers
|
||||
to a specific DefLabel instruction. Additionally, every Trace must begin
|
||||
with a DefLabel instruction.
|
||||
|
||||
DefLabel can optionally define one or more destination variables. In that
|
||||
case, the values are passed to it by Jmp_ instruction(s) with the same
|
||||
number of sources. The type of each destination variable is the least common
|
||||
supertype of each of the corresponding Jmp_ sources.
|
||||
|
||||
Note that when a label defines destination variables, it is only reachable
|
||||
from the Jmp_ instruction. Other branch instructions cannot pass arguments.
|
||||
DefLabel defines variables received from a previous Jmp. A DefLabel with
|
||||
zero destinations is a no-op, and the predecessor blocks may not necessarily
|
||||
end in Jmp. A DefLabel with one or more destinations may only be reached
|
||||
by a Jmp instruction with the same number of sources. Ordinary branch
|
||||
instructions may not pass values to a DefLabel.
|
||||
|
||||
|
||||
6. Reference manipulation
|
||||
@@ -781,38 +774,37 @@ D:PtrToCell = LdPairBase S0:Obj<Pair>
|
||||
Loads the base pointer to an array of Cells from the given collection
|
||||
instance in S0.
|
||||
|
||||
D:T = LdMem<T> S0:PtrToGen S1:ConstInt [ -> L ]
|
||||
D:T = LdMem<T> S0:PtrToGen S1:ConstInt [ -> B ]
|
||||
|
||||
Loads from S0 + S1 (in bytes) and puts the value in D. If the
|
||||
optional label L is specified and the loaded value's type does not
|
||||
match T, this instruction does not load into D and transfers control
|
||||
to L.
|
||||
optional target block B is specified and the loaded value's type does not
|
||||
match T, this instruction does not load into D and branches to block B.
|
||||
|
||||
D:T = LdProp<T> S0:Obj S1:ConstInt [ -> L ]
|
||||
D:T = LdProp<T> S0:Obj S1:ConstInt [ -> B ]
|
||||
|
||||
Loads a property from the object referenced by S0 at the offset
|
||||
given by S1 and puts the value in D. If the optional label L is
|
||||
given by S1 and puts the value in D. If the optional target B is
|
||||
specified and the loaded value's type does not match T, this
|
||||
instruction does not load into D and transfers control to L.
|
||||
instruction does not load into D and branches to block B.
|
||||
|
||||
D:Cell = LdElem S0:PtrToCell S1:Int
|
||||
|
||||
Loads the element at index S1 from the base pointer in S0.
|
||||
The index in S1 is the number of bytes from the base in S0.
|
||||
|
||||
D:T = LdRef<T> S0:Cell& [ -> L ]
|
||||
D:T = LdRef<T> S0:Cell& [ -> B ]
|
||||
|
||||
Loads the value held in the box referenced by S0 and puts the value
|
||||
in D. If the optional label L is specified and the loaded value's
|
||||
in D. If the optional target B is specified and the loaded value's
|
||||
type does not match T, this instruction does not load into D and
|
||||
transfers control to L.
|
||||
branches to block B.
|
||||
|
||||
D:Obj = LdThis S0:FramePtr [ -> L ]
|
||||
D:Obj = LdThis S0:FramePtr [ -> B ]
|
||||
|
||||
Loads the this pointer out of the ActRec pointed to by S0, and puts
|
||||
it in D. If the optional label L is supplied, if the this pointer
|
||||
in S0 is not, this instruction does not load it into D and transfers
|
||||
control to the exit label L.
|
||||
it in D. If the optional block B is supplied, if the this pointer
|
||||
in S0 is null, this instruction does not load it into D and branches
|
||||
to block B.
|
||||
|
||||
D:Ctx = LdCtx<func> S0:FramePtr
|
||||
|
||||
@@ -873,20 +865,20 @@ D:Cls = LdClsCached S0:ConstStr
|
||||
Loads the class named S0 via the RDS. Invokes autoload and may raise
|
||||
an error if the class is not defined.
|
||||
|
||||
D:Cls = LdClsCachedSafe S0:ConstStr [ -> L ]
|
||||
D:Cls = LdClsCachedSafe S0:ConstStr [ -> B ]
|
||||
|
||||
Loads the class whose name is S0 out of the RDS. If the class is not
|
||||
defined, returns null and optionally branches to L.
|
||||
defined, returns null and optionally branches to block B.
|
||||
|
||||
D:T = LdClsCns<T,className,constName> [ -> L ]
|
||||
D:T = LdClsCns<T,className,constName> [ -> B ]
|
||||
|
||||
Loads the named class constant for a class via the RDS. This
|
||||
instruction should generally be followed by CheckInit, unless we
|
||||
know the class is already loaded.
|
||||
|
||||
If the optional label L is specified and the loaded value's type
|
||||
If the optional block B is specified and the loaded value's type
|
||||
does not match T, this instruction does not load into D and
|
||||
transfers control to L.
|
||||
branches to block B.
|
||||
|
||||
The result may be uninitialized if the class is not defined. Note
|
||||
that no decref of the result is necessary because class constants
|
||||
@@ -950,9 +942,9 @@ D:PtrToGen = LdPropAddr S0:Obj S1:ConstInt
|
||||
Load the address of the object property for S0 at offset S1 (in
|
||||
bytes) into D.
|
||||
|
||||
D:PtrToGen = LdGblAddr S0:Str -> L
|
||||
D:PtrToGen = LdGblAddr S0:Str -> B
|
||||
|
||||
Loads a pointer to a global. S0 is the global's name. Branches to L
|
||||
Loads a pointer to a global. S0 is the global's name. Branches to B
|
||||
if the global is not defined.
|
||||
|
||||
D:PtrToGen = LdGblAddrDef S0:Str
|
||||
@@ -961,24 +953,24 @@ D:PtrToGen = LdGblAddrDef S0:Str
|
||||
global if it is not already defined. Decrements the reference count
|
||||
of S0.
|
||||
|
||||
D:PtrToGen = LdClsPropAddr S0:Cls S1:Str S2:ConstCls [ -> L ]
|
||||
D:PtrToGen = LdClsPropAddr S0:Cls S1:Str S2:ConstCls [ -> B ]
|
||||
|
||||
Loads a pointer to a static class property. S0 points to the class, S1 is the
|
||||
property name, and S2 is the class representing the context of the code
|
||||
accessing the property. If class S0 does not have a visible and accessible
|
||||
static property named S1, then this instruction will either (1) jump to L if
|
||||
static property named S1, then this instruction will either (1) jump to B if
|
||||
it is present and not a catch trace, or (2) throw a fatal error.
|
||||
|
||||
D:PtrToGen = LdClsPropAddrCached S0:Cls S1:ConstStr S2:ConstStr
|
||||
S3:ConstCls [ -> L ]
|
||||
S3:ConstCls [ -> B ]
|
||||
|
||||
Loads a pointer to a static class property via the RDS. S0
|
||||
points to the class, S1 is the property name, S2 is the class name,
|
||||
and S3 is the class representing the context of the code accessing
|
||||
the property. If class S0 does not have a visible and accessible
|
||||
static property named S1, then this instruction will either (1)
|
||||
throw a fatal error if the optional label L is not present, or (2)
|
||||
jump to L if it is present.
|
||||
throw a fatal error if the optional block B is not given, or (2)
|
||||
jump to B if it is present.
|
||||
|
||||
LdObjMethod S0:Cls S1:ConstStr S2:StkPtr
|
||||
|
||||
@@ -988,10 +980,10 @@ LdObjMethod S0:Cls S1:ConstStr S2:StkPtr
|
||||
cache. Fatals if the class does not have an accessible method with
|
||||
the given name and does not have a __call method.
|
||||
|
||||
D:Func = LdObjInvoke S0:Cls -> L
|
||||
D:Func = LdObjInvoke S0:Cls -> B
|
||||
|
||||
Try to load a cached non-static __invoke Func from the Class in S0,
|
||||
or branch to L if it is not present.
|
||||
or branch to block B if it is not present.
|
||||
|
||||
D:Cls = LdObjClass S0:Obj
|
||||
|
||||
@@ -1008,10 +1000,10 @@ D:Func = LdFuncCached<funcName>
|
||||
autoload if it not defined yet. Fatal if function autoloader fails
|
||||
to define it.
|
||||
|
||||
D:Func = LdFuncCachedSafe<funcName> [ -> L ]
|
||||
D:Func = LdFuncCachedSafe<funcName> [ -> B ]
|
||||
|
||||
Try to load the Func named funcName from the RDS. If the function
|
||||
is not defined, returns null and optionally branches to L.
|
||||
is not defined, returns null and optionally branches to B.
|
||||
|
||||
D:Func = LdFuncCachedU<funcName,fallbackName>
|
||||
|
||||
@@ -1199,11 +1191,11 @@ D:StkPtr = RetAdjustStack S0:FramePtr
|
||||
Loads the new VM stack pointer into the destination. S0 is a
|
||||
pointer to the current activation record.
|
||||
|
||||
ReleaseVVOrExit S0:FramePtr -> L
|
||||
ReleaseVVOrExit S0:FramePtr -> B
|
||||
|
||||
Loads the VarEnv slot off the ActRec pointed to by S0. If it is
|
||||
null, does nothing. If it is an ExtraArgs, deallocates the
|
||||
ExtraArgs structure. Otherwise jumps to the exit-trace label L.
|
||||
ExtraArgs structure. Otherwise jumps to block B.
|
||||
|
||||
D:StkPtr = GenericRetDecRefs S0:FramePtr S1:ConstInt
|
||||
|
||||
@@ -1652,7 +1644,6 @@ D:T = Reload S0:T
|
||||
|
||||
Loads from a spilled temporary S0, and stores the result in D.
|
||||
|
||||
|
||||
16. Continuations & Closures
|
||||
|
||||
|
||||
@@ -1694,16 +1685,16 @@ ContEnter S0:FramePtr S1:TCA S2:ConstInt S3:FramePtr
|
||||
object. S1 is the address to jump to. S2 is the bytecode offset in the
|
||||
caller to return to when the generator body yields. S3 is the current frame.
|
||||
|
||||
ContPreNext S0:Obj -> L
|
||||
ContPreNext S0:Obj -> B
|
||||
|
||||
Performs operations needed for the Continuation::next() method. This
|
||||
includes checking m_done and m_running---if either is not the case
|
||||
branches to the label L.
|
||||
branches to block B.
|
||||
|
||||
ContStartedCheck S0:Obj -> L
|
||||
ContStartedCheck S0:Obj -> B
|
||||
|
||||
Checks if the continuation object S0 has started, and if not
|
||||
branches to the label L.
|
||||
branches to block B.
|
||||
|
||||
ContSetRunning S0:Obj S1:ConstBool
|
||||
|
||||
|
||||
@@ -7,4 +7,4 @@ target_link_libraries(hhvm ${HHVM_LINK_LIBRARIES})
|
||||
embed_systemlib(hhvm "${CMAKE_CURRENT_SOURCE_DIR}/hhvm" ${HPHP_HOME}/bin/systemlib.php)
|
||||
add_dependencies(hhvm systemlib)
|
||||
|
||||
install(TARGETS hhvm DESTINATION bin)
|
||||
HHVM_INSTALL(hhvm bin)
|
||||
|
||||
@@ -132,6 +132,17 @@ void ProcessInit() {
|
||||
|
||||
SystemLib::s_unit = compile_string(slib.c_str(), slib.size(),
|
||||
"systemlib.php");
|
||||
|
||||
const StringData* msg;
|
||||
int line;
|
||||
if (SystemLib::s_unit->compileTimeFatal(msg, line)) {
|
||||
Logger::Error("An error has been introduced into the systemlib, "
|
||||
"but we cannot give you a file and line number right now.");
|
||||
Logger::Error("Check all of your changes to hphp/system/php");
|
||||
Logger::Error("HipHop Parse Error: %s", msg->data());
|
||||
_exit(1);
|
||||
}
|
||||
|
||||
if (!hhas.empty()) {
|
||||
SystemLib::s_hhas_unit = compile_string(hhas.c_str(), hhas.size(),
|
||||
"systemlib.hhas");
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#ifdef yyerror
|
||||
#undef yyerror
|
||||
#endif
|
||||
#define yyerror(loc,p,msg) p->fatal(loc,msg)
|
||||
#define yyerror(loc,p,msg) p->parseFatal(loc,msg)
|
||||
|
||||
#ifdef YYLLOC_DEFAULT
|
||||
# undef YYLLOC_DEFAULT
|
||||
|
||||
@@ -100,7 +100,8 @@ public:
|
||||
void setRuleLocation(Location *loc) {
|
||||
m_loc = *loc;
|
||||
}
|
||||
virtual void fatal(Location *loc, const char *msg) {}
|
||||
virtual void fatal(const Location* loc, const char* msg) {}
|
||||
virtual void parseFatal(const Location* loc, const char* msg) {}
|
||||
|
||||
void pushFuncLocation();
|
||||
LocationPtr popFuncLocation();
|
||||
|
||||
@@ -389,7 +389,12 @@ struct Parser : ParserBase {
|
||||
#define X(...)
|
||||
//#define X(...) traceCb(__FUNCTION__,## __VA_ARGS__)
|
||||
|
||||
void fatal(Location* loc, const char* msg) {
|
||||
void fatal(const Location* loc, const char* msg) {
|
||||
throw std::runtime_error(folly::format(
|
||||
"{}:{}: {}", m_fileName, m_loc.line0, msg).str());
|
||||
}
|
||||
|
||||
void parseFatal(const Location* loc, const char* msg) {
|
||||
throw std::runtime_error(folly::format(
|
||||
"{}:{}: {}", m_fileName, m_loc.line0, msg).str());
|
||||
}
|
||||
|
||||
@@ -176,8 +176,11 @@ inline Variant throw_missing_class(const char *cls) {
|
||||
throw ClassNotFoundException((std::string("unknown class ") + cls).c_str());
|
||||
}
|
||||
|
||||
inline Variant throw_missing_file(const char *cls) {
|
||||
throw PhpFileDoesNotExistException(cls);
|
||||
inline Variant throw_missing_file(const char *file) {
|
||||
if (file[0] == '\0') {
|
||||
throw NoFileSpecifiedException();
|
||||
}
|
||||
throw PhpFileDoesNotExistException(file);
|
||||
}
|
||||
void throw_instance_method_fatal(const char *name);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// definitions
|
||||
|
||||
enum DataType: int8_t {
|
||||
enum DataType : int8_t {
|
||||
KindOfClass = -13,
|
||||
MinDataType = -13,
|
||||
|
||||
@@ -277,6 +277,21 @@ inline DataType typeInitNull(DataType t) {
|
||||
return t == KindOfUninit ? KindOfNull : t;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns whether two DataTypes for primitive types are "equivalent"
|
||||
* as far as user-visible php types are concerned. (I.e. ignoring
|
||||
* different types of strings or different types of nulls.)
|
||||
*
|
||||
* Pre: t1 and t2 must both be DataTypes that represent php-types.
|
||||
* (Non-internal KindOfs.)
|
||||
*/
|
||||
inline bool equivDataTypes(DataType t1, DataType t2) {
|
||||
return
|
||||
(t1 == t2) ||
|
||||
(IS_STRING_TYPE(t1) && IS_STRING_TYPE(t2)) ||
|
||||
(IS_NULL_TYPE(t1) && IS_NULL_TYPE(t2));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
} // namespace HPHP
|
||||
|
||||
|
||||
@@ -45,6 +45,10 @@ int emulate_zend(int argc, char** argv){
|
||||
vector<string> newargv;
|
||||
|
||||
newargv.push_back(argv[0]);
|
||||
#ifdef PHP_DEFAULT_HDF
|
||||
newargv.push_back("-c");
|
||||
newargv.push_back(PHP_DEFAULT_HDF);
|
||||
#endif
|
||||
|
||||
bool lint = false;
|
||||
bool show = false;
|
||||
|
||||
@@ -24,16 +24,19 @@ int ExitException::ExitCode = 0;
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
ExtendedException::ExtendedException() : Exception() {
|
||||
m_silent = false;
|
||||
computeBacktrace();
|
||||
}
|
||||
|
||||
ExtendedException::ExtendedException(const std::string &msg) {
|
||||
m_msg = msg;
|
||||
m_silent = false;
|
||||
computeBacktrace();
|
||||
}
|
||||
|
||||
ExtendedException::ExtendedException(SkipFrame, const std::string &msg) {
|
||||
m_msg = msg;
|
||||
m_silent = false;
|
||||
computeBacktrace(true);
|
||||
}
|
||||
|
||||
@@ -42,6 +45,7 @@ ExtendedException::ExtendedException(const char *fmt, ...) {
|
||||
va_start(ap, fmt);
|
||||
format(fmt, ap);
|
||||
va_end(ap);
|
||||
m_silent = false;
|
||||
computeBacktrace();
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,16 @@ public:
|
||||
ExtendedException(SkipFrame frame, const std::string &msg);
|
||||
ExtendedException(const char *fmt, ...) ATTRIBUTE_PRINTF(2,3);
|
||||
Array getBackTrace() const;
|
||||
// a silent exception does not have its exception message logged
|
||||
bool isSilent() const { return m_silent; }
|
||||
void setSilent(bool s = true) { m_silent = s; }
|
||||
virtual ~ExtendedException() throw() {}
|
||||
EXCEPTION_COMMON_IMPL(ExtendedException);
|
||||
protected:
|
||||
ArrayHolder m_btp;
|
||||
private:
|
||||
void computeBacktrace(bool skipFrame = false);
|
||||
bool m_silent;
|
||||
};
|
||||
|
||||
class Assertion : public ExtendedException {
|
||||
@@ -140,8 +144,11 @@ public:
|
||||
virtual ~ParseTimeFatalException() throw() {}
|
||||
EXCEPTION_COMMON_IMPL(ParseTimeFatalException);
|
||||
|
||||
void setParseFatal(bool b = true) { m_parseFatal = b; }
|
||||
|
||||
std::string m_file;
|
||||
int m_line;
|
||||
bool m_parseFatal;
|
||||
};
|
||||
|
||||
class FatalErrorException : public ExtendedException {
|
||||
@@ -293,10 +300,25 @@ class PhpFileDoesNotExistException : public ExtendedException {
|
||||
public:
|
||||
explicit PhpFileDoesNotExistException(const char *file)
|
||||
: ExtendedException("File could not be loaded: %s", file) {}
|
||||
explicit PhpFileDoesNotExistException(const char *msg, bool empty_file)
|
||||
: ExtendedException("%s", msg) {
|
||||
assert(empty_file);
|
||||
}
|
||||
virtual ~PhpFileDoesNotExistException() throw() {}
|
||||
EXCEPTION_COMMON_IMPL(PhpFileDoesNotExistException);
|
||||
};
|
||||
|
||||
class NoFileSpecifiedException : public PhpFileDoesNotExistException {
|
||||
public:
|
||||
explicit NoFileSpecifiedException()
|
||||
: PhpFileDoesNotExistException(
|
||||
"Nothing to do. Either pass a .php file to run, or use -m server",
|
||||
true
|
||||
) {}
|
||||
virtual ~NoFileSpecifiedException() throw() {}
|
||||
EXCEPTION_COMMON_IMPL(NoFileSpecifiedException);
|
||||
};
|
||||
|
||||
class RequestTimeoutException : public FatalErrorException {
|
||||
public:
|
||||
RequestTimeoutException(const std::string &msg, const Array& backtrace)
|
||||
|
||||
@@ -550,10 +550,10 @@ void BaseExecutionContext::onShutdownPostSend() {
|
||||
bool BaseExecutionContext::errorNeedsHandling(int errnum,
|
||||
bool callUserHandler,
|
||||
ErrorThrowMode mode) {
|
||||
if (m_throwAllErrors) throw errnum;
|
||||
if (mode != ErrorThrowMode::Never ||
|
||||
(getErrorReportingLevel() & errnum) != 0 ||
|
||||
RuntimeOption::NoSilencer) {
|
||||
if (m_throwAllErrors) {
|
||||
throw errnum;
|
||||
}
|
||||
if (mode != ErrorThrowMode::Never || errorNeedsLogging(errnum)) {
|
||||
return true;
|
||||
}
|
||||
if (callUserHandler) {
|
||||
@@ -565,6 +565,10 @@ bool BaseExecutionContext::errorNeedsHandling(int errnum,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BaseExecutionContext::errorNeedsLogging(int errnum) {
|
||||
return RuntimeOption::NoSilencer || (getErrorReportingLevel() & errnum) != 0;
|
||||
}
|
||||
|
||||
class ErrorStateHelper {
|
||||
public:
|
||||
ErrorStateHelper(BaseExecutionContext *context,
|
||||
@@ -585,15 +589,15 @@ const StaticString
|
||||
s_file("file"),
|
||||
s_line("line");
|
||||
|
||||
void BaseExecutionContext::handleError(const std::string &msg,
|
||||
void BaseExecutionContext::handleError(const std::string& msg,
|
||||
int errnum,
|
||||
bool callUserHandler,
|
||||
ErrorThrowMode mode,
|
||||
const std::string &prefix,
|
||||
const std::string& prefix,
|
||||
bool skipFrame /* = false */) {
|
||||
SYNC_VM_REGS_SCOPED();
|
||||
|
||||
ErrorState newErrorState = ErrorState::ErrorRaised;
|
||||
auto newErrorState = ErrorState::ErrorRaised;
|
||||
switch (getErrorState()) {
|
||||
case ErrorState::ErrorRaised:
|
||||
case ErrorState::ErrorRaisedByUserHandler:
|
||||
@@ -604,12 +608,11 @@ void BaseExecutionContext::handleError(const std::string &msg,
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ErrorStateHelper esh(this, newErrorState);
|
||||
ExtendedException ee = skipFrame ?
|
||||
auto const ee = skipFrame ?
|
||||
ExtendedException(ExtendedException::SkipFrame::skipFrame, msg) :
|
||||
ExtendedException(msg);
|
||||
Array bt = ee.getBackTrace();
|
||||
|
||||
recordLastError(ee, errnum);
|
||||
bool handled = false;
|
||||
if (callUserHandler) {
|
||||
@@ -618,22 +621,22 @@ void BaseExecutionContext::handleError(const std::string &msg,
|
||||
if (mode == ErrorThrowMode::Always ||
|
||||
(mode == ErrorThrowMode::IfUnhandled && !handled)) {
|
||||
DEBUGGER_ATTACHED_ONLY(phpDebuggerErrorHook(msg));
|
||||
throw FatalErrorException(msg, bt);
|
||||
auto exn = FatalErrorException(msg, ee.getBackTrace());
|
||||
exn.setSilent(!errorNeedsLogging(errnum));
|
||||
throw exn;
|
||||
}
|
||||
if (!handled &&
|
||||
(RuntimeOption::NoSilencer ||
|
||||
(getErrorReportingLevel() & errnum) != 0)) {
|
||||
DEBUGGER_ATTACHED_ONLY(phpDebuggerErrorHook(msg));
|
||||
if (!handled && errorNeedsLogging(errnum)) {
|
||||
DEBUGGER_ATTACHED_ONLY(phpDebuggerErrorHook(ee.getMessage()));
|
||||
String file = empty_string;
|
||||
int line = 0;
|
||||
if (RuntimeOption::InjectedStackTrace) {
|
||||
Array bt = ee.getBackTrace();
|
||||
if (!bt.empty()) {
|
||||
Array top = bt.rvalAt(0).toArray();
|
||||
if (top.exists(s_file)) file = top.rvalAt(s_file).toString();
|
||||
if (top.exists(s_line)) line = top.rvalAt(s_line).toInt64();
|
||||
}
|
||||
}
|
||||
|
||||
Logger::Log(Logger::LogError, prefix.c_str(), ee, file.c_str(), line);
|
||||
}
|
||||
}
|
||||
@@ -652,8 +655,7 @@ bool BaseExecutionContext::callUserErrorHandler(const Exception &e, int errnum,
|
||||
int errline = 0;
|
||||
String errfile;
|
||||
Array backtrace;
|
||||
const ExtendedException *ee = dynamic_cast<const ExtendedException*>(&e);
|
||||
if (ee) {
|
||||
if (auto const ee = dynamic_cast<const ExtendedException*>(&e)) {
|
||||
Array arr = ee->getBackTrace();
|
||||
if (!arr.isNull()) {
|
||||
backtrace = arr;
|
||||
@@ -690,9 +692,10 @@ bool BaseExecutionContext::onFatalError(const Exception &e) {
|
||||
recordLastError(e);
|
||||
String file = empty_string;
|
||||
int line = 0;
|
||||
bool silenced = false;
|
||||
if (RuntimeOption::InjectedStackTrace) {
|
||||
const ExtendedException *ee = dynamic_cast<const ExtendedException *>(&e);
|
||||
if (ee) {
|
||||
if (auto const ee = dynamic_cast<const ExtendedException *>(&e)) {
|
||||
silenced = ee->isSilent();
|
||||
Array bt = ee->getBackTrace();
|
||||
if (!bt.empty()) {
|
||||
Array top = bt.rvalAt(0).toArray();
|
||||
@@ -701,7 +704,8 @@ bool BaseExecutionContext::onFatalError(const Exception &e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (RuntimeOption::AlwaysLogUnhandledExceptions) {
|
||||
// need to silence even with the AlwaysLogUnhandledExceptions flag set
|
||||
if (!silenced && RuntimeOption::AlwaysLogUnhandledExceptions) {
|
||||
Logger::Log(Logger::LogError, "HipHop Fatal error: ", e,
|
||||
file.c_str(), line);
|
||||
}
|
||||
@@ -710,7 +714,7 @@ bool BaseExecutionContext::onFatalError(const Exception &e) {
|
||||
int errnum = static_cast<int>(ErrorConstants::ErrorModes::FATAL_ERROR);
|
||||
handled = callUserErrorHandler(e, errnum, true);
|
||||
}
|
||||
if (!handled && !RuntimeOption::AlwaysLogUnhandledExceptions) {
|
||||
if (!handled && !silenced && !RuntimeOption::AlwaysLogUnhandledExceptions) {
|
||||
Logger::Log(Logger::LogError, "HipHop Fatal error: ", e,
|
||||
file.c_str(), line);
|
||||
}
|
||||
|
||||
@@ -287,6 +287,7 @@ public:
|
||||
bool errorNeedsHandling(int errnum,
|
||||
bool callUserHandler,
|
||||
ErrorThrowMode mode);
|
||||
bool errorNeedsLogging(int errnum);
|
||||
void handleError(const std::string &msg,
|
||||
int errnum,
|
||||
bool callUserHandler,
|
||||
@@ -600,7 +601,8 @@ public:
|
||||
HPHP::Unit* unit = 0);
|
||||
bool evalUnit(HPHP::Unit* unit, PC& pc, int funcType);
|
||||
void invokeUnit(TypedValue* retval, HPHP::Unit* unit);
|
||||
HPHP::Unit* compileEvalString(StringData* code);
|
||||
HPHP::Unit* compileEvalString(StringData* code,
|
||||
const char* evalFilename = nullptr);
|
||||
const String& createFunction(const String& args, const String& code);
|
||||
bool evalPHPDebugger(TypedValue* retval, StringData *code, int frame);
|
||||
void enterDebuggerDummyEnv();
|
||||
|
||||
@@ -140,7 +140,7 @@ std::pair<uint32_t,uint32_t> computeCapAndMask(uint32_t minimumMaxElms) {
|
||||
}
|
||||
|
||||
ALWAYS_INLINE
|
||||
uint32_t computeAllocBytes(uint32_t cap, uint32_t mask) {
|
||||
size_t computeAllocBytes(uint32_t cap, uint32_t mask) {
|
||||
auto const tabSize = mask + 1;
|
||||
auto const tabBytes = tabSize * sizeof(int32_t);
|
||||
auto const dataBytes = cap * sizeof(HphpArray::Elm);
|
||||
|
||||
@@ -253,7 +253,7 @@ int RuntimeOption::ProxyPercentage = 0;
|
||||
std::set<std::string> RuntimeOption::ProxyURLs;
|
||||
std::vector<std::string> RuntimeOption::ProxyPatterns;
|
||||
bool RuntimeOption::AlwaysUseRelativePath = false;
|
||||
std::string RuntimeOption::IniFile;
|
||||
std::string RuntimeOption::IniFile = "/etc/hhvm/php.ini";
|
||||
|
||||
int RuntimeOption::HttpDefaultTimeout = 30;
|
||||
int RuntimeOption::HttpSlowQueryThreshold = 5000; // ms
|
||||
@@ -849,8 +849,11 @@ void RuntimeOption::Load(Hdf &config, StringVec *overwrites /* = NULL */,
|
||||
UseDirectCopy = server["UseDirectCopy"].getBool(false);
|
||||
AlwaysUseRelativePath = server["AlwaysUseRelativePath"].getBool(false);
|
||||
|
||||
IniFile = server["IniFile"].getString("/etc/hhvm/php.ini");
|
||||
IniFile = server["IniFile"].getString(IniFile);
|
||||
if (access(IniFile.c_str(), R_OK) == -1) {
|
||||
if (IniFile != "/etc/hhvm/php.ini") {
|
||||
Logger::Error("INI file doens't exist: %s", IniFile.c_str());
|
||||
}
|
||||
IniFile.clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "hphp/runtime/debugger/cmd/cmd_out.h"
|
||||
#include "hphp/runtime/vm/hhbc.h"
|
||||
|
||||
namespace HPHP { namespace Eval {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -48,6 +49,12 @@ void CmdOut::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) {
|
||||
assert(!m_complete); // Complete cmds should not be asked to do work.
|
||||
|
||||
m_needsVMInterrupt = false;
|
||||
|
||||
if (m_skippingOverPopR) {
|
||||
m_complete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
int currentVMDepth = g_vmContext->m_nesting;
|
||||
int currentStackDepth = proxy.getStackDepth();
|
||||
|
||||
@@ -70,8 +77,18 @@ void CmdOut::onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt) {
|
||||
|
||||
TRACE(2, "CmdOut: shallower stack depth, done.\n");
|
||||
cleanupStepOuts();
|
||||
m_complete = (decCount() == 0);
|
||||
if (!m_complete) {
|
||||
int depth = decCount();
|
||||
if (depth == 0) {
|
||||
PC pc = g_vmContext->getPC();
|
||||
// Step over PopR following a call
|
||||
if (toOp(*pc) == OpPopR) {
|
||||
m_skippingOverPopR = true;
|
||||
m_needsVMInterrupt = true;
|
||||
} else {
|
||||
m_complete = true;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
TRACE(2, "CmdOut: not complete, step out again.\n");
|
||||
onSetup(proxy, interrupt);
|
||||
}
|
||||
|
||||
@@ -25,11 +25,13 @@ namespace HPHP { namespace Eval {
|
||||
DECLARE_BOOST_TYPES(CmdOut);
|
||||
class CmdOut : public CmdFlowControl {
|
||||
public:
|
||||
CmdOut() : CmdFlowControl(KindOfOut) {}
|
||||
CmdOut() : CmdFlowControl(KindOfOut), m_skippingOverPopR(false) {}
|
||||
|
||||
virtual void help(DebuggerClient &client);
|
||||
virtual void onSetup(DebuggerProxy &proxy, CmdInterrupt &interrupt);
|
||||
virtual void onBeginInterrupt(DebuggerProxy &proxy, CmdInterrupt &interrupt);
|
||||
private:
|
||||
bool m_skippingOverPopR;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -189,7 +189,7 @@ const StaticString
|
||||
|
||||
// Add location information for the given continuation to the given frame.
|
||||
void addContinuationLocation(Array& frameData,
|
||||
c_ContinuationWaitHandle& contWh) {
|
||||
c_AsyncFunctionWaitHandle& contWh) {
|
||||
// A running continuation is active on the normal stack, and we
|
||||
// cannot compute the location just by inspecting the continuation
|
||||
// alone.
|
||||
@@ -226,7 +226,7 @@ Array createAsyncStacktrace() {
|
||||
frameData.set(s_id, wh->t_getid(), true);
|
||||
frameData.set(s_ancestors, ancestors, true);
|
||||
// Continuation wait handles may have a source location to add.
|
||||
auto contWh = dynamic_cast<c_ContinuationWaitHandle*>(wh);
|
||||
auto contWh = dynamic_cast<c_AsyncFunctionWaitHandle*>(wh);
|
||||
if (contWh != nullptr) addContinuationLocation(frameData, *contWh);
|
||||
trace.append(frameData);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
||||
| Copyright (c) 1997-2010 The PHP Group |
|
||||
+----------------------------------------------------------------------+
|
||||
| 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. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
#ifndef VARIANTCONTROLLER_H
|
||||
#define VARIANTCONTROLLER_H
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ void AsioContext::exit(context_idx_t ctx_idx) {
|
||||
}
|
||||
}
|
||||
|
||||
void AsioContext::schedule(c_ContinuationWaitHandle* wait_handle) {
|
||||
void AsioContext::schedule(c_AsyncFunctionWaitHandle* wait_handle) {
|
||||
m_runnableQueue.push(wait_handle);
|
||||
wait_handle->incRefCount();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FORWARD_DECLARE_CLASS(WaitableWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(ContinuationWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(AsyncFunctionWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(RescheduleWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(ExternalThreadEventWaitHandle);
|
||||
|
||||
@@ -42,9 +42,9 @@ class AsioContext {
|
||||
void exit(context_idx_t ctx_idx);
|
||||
|
||||
bool isRunning() { return m_current; }
|
||||
c_ContinuationWaitHandle* getCurrent() { return m_current; }
|
||||
c_AsyncFunctionWaitHandle* getCurrent() { return m_current; }
|
||||
|
||||
void schedule(c_ContinuationWaitHandle* wait_handle);
|
||||
void schedule(c_AsyncFunctionWaitHandle* wait_handle);
|
||||
void schedule(c_RescheduleWaitHandle* wait_handle, uint32_t queue, uint32_t priority);
|
||||
uint32_t registerExternalThreadEvent(c_ExternalThreadEventWaitHandle* wait_handle);
|
||||
void unregisterExternalThreadEvent(uint32_t ete_idx);
|
||||
@@ -59,10 +59,10 @@ class AsioContext {
|
||||
|
||||
bool runSingle(reschedule_priority_queue_t& queue);
|
||||
|
||||
c_ContinuationWaitHandle* m_current;
|
||||
c_AsyncFunctionWaitHandle* m_current;
|
||||
|
||||
// queue of ContinuationWaitHandles ready for immediate execution
|
||||
smart::queue<c_ContinuationWaitHandle*> m_runnableQueue;
|
||||
// queue of AsyncFunctionWaitHandles ready for immediate execution
|
||||
smart::queue<c_AsyncFunctionWaitHandle*> m_runnableQueue;
|
||||
|
||||
// queue of RescheduleWaitHandles scheduled in default mode
|
||||
reschedule_priority_queue_t m_priorityQueueDefault;
|
||||
|
||||
@@ -74,57 +74,47 @@ void AsioSession::initAbruptInterruptException() {
|
||||
"The request was abruptly interrupted.");
|
||||
}
|
||||
|
||||
void AsioSession::onFailed(CObjRef exception) {
|
||||
if (m_onFailedCallback.get()) {
|
||||
try {
|
||||
vm_call_user_func(m_onFailedCallback, Array::Create(exception));
|
||||
} catch (const Object& callback_exception) {
|
||||
raise_warning("[asio] Ignoring exception thrown by onFailed callback");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AsioSession::onContinuationCreate(c_ContinuationWaitHandle* cont) {
|
||||
assert(m_onContinuationCreateCallback.get());
|
||||
void AsioSession::onAsyncFunctionCreate(c_AsyncFunctionWaitHandle* cont) {
|
||||
assert(m_onAsyncFunctionCreateCallback.get());
|
||||
try {
|
||||
vm_call_user_func(
|
||||
m_onContinuationCreateCallback,
|
||||
m_onAsyncFunctionCreateCallback,
|
||||
Array::Create(cont));
|
||||
} catch (const Object& callback_exception) {
|
||||
raise_warning("[asio] Ignoring exception thrown by ContinuationWaitHandle::onCreate callback");
|
||||
raise_warning("[asio] Ignoring exception thrown by AsyncFunctionWaitHandle::onCreate callback");
|
||||
}
|
||||
}
|
||||
|
||||
void AsioSession::onContinuationYield(c_ContinuationWaitHandle* cont, c_WaitHandle* child) {
|
||||
assert(m_onContinuationYieldCallback.get());
|
||||
void AsioSession::onAsyncFunctionAwait(c_AsyncFunctionWaitHandle* cont, c_WaitHandle* child) {
|
||||
assert(m_onAsyncFunctionAwaitCallback.get());
|
||||
try {
|
||||
vm_call_user_func(
|
||||
m_onContinuationYieldCallback,
|
||||
m_onAsyncFunctionAwaitCallback,
|
||||
make_packed_array(cont, child));
|
||||
} catch (const Object& callback_exception) {
|
||||
raise_warning("[asio] Ignoring exception thrown by ContinuationWaitHandle::onYield callback");
|
||||
raise_warning("[asio] Ignoring exception thrown by AsyncFunctionWaitHandle::onAwait callback");
|
||||
}
|
||||
}
|
||||
|
||||
void AsioSession::onContinuationSuccess(c_ContinuationWaitHandle* cont, CVarRef result) {
|
||||
assert(m_onContinuationSuccessCallback.get());
|
||||
void AsioSession::onAsyncFunctionSuccess(c_AsyncFunctionWaitHandle* cont, CVarRef result) {
|
||||
assert(m_onAsyncFunctionSuccessCallback.get());
|
||||
try {
|
||||
vm_call_user_func(
|
||||
m_onContinuationSuccessCallback,
|
||||
m_onAsyncFunctionSuccessCallback,
|
||||
make_packed_array(cont, result));
|
||||
} catch (const Object& callback_exception) {
|
||||
raise_warning("[asio] Ignoring exception thrown by ContinuationWaitHandle::onSuccess callback");
|
||||
raise_warning("[asio] Ignoring exception thrown by AsyncFunctionWaitHandle::onSuccess callback");
|
||||
}
|
||||
}
|
||||
|
||||
void AsioSession::onContinuationFail(c_ContinuationWaitHandle* cont, CObjRef exception) {
|
||||
assert(m_onContinuationFailCallback.get());
|
||||
void AsioSession::onAsyncFunctionFail(c_AsyncFunctionWaitHandle* cont, CObjRef exception) {
|
||||
assert(m_onAsyncFunctionFailCallback.get());
|
||||
try {
|
||||
vm_call_user_func(
|
||||
m_onContinuationFailCallback,
|
||||
m_onAsyncFunctionFailCallback,
|
||||
make_packed_array(cont, exception));
|
||||
} catch (const Object& callback_exception) {
|
||||
raise_warning("[asio] Ignoring exception thrown by ContinuationWaitHandle::onFail callback");
|
||||
raise_warning("[asio] Ignoring exception thrown by AsyncFunctionWaitHandle::onFail callback");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ FORWARD_DECLARE_CLASS(GenArrayWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(GenMapWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(GenVectorWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(SetResultToRefWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(ContinuationWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(AsyncFunctionWaitHandle);
|
||||
|
||||
class AsioSession {
|
||||
public:
|
||||
@@ -64,7 +64,7 @@ class AsioSession {
|
||||
return static_cast<context_idx_t>(m_contexts.size());
|
||||
}
|
||||
|
||||
c_ContinuationWaitHandle* getCurrentWaitHandle() {
|
||||
c_AsyncFunctionWaitHandle* getCurrentWaitHandle() {
|
||||
assert(!isInContext() || getCurrentContext()->isRunning());
|
||||
return isInContext() ? getCurrentContext()->getCurrent() : nullptr;
|
||||
}
|
||||
@@ -87,38 +87,31 @@ class AsioSession {
|
||||
|
||||
void initAbruptInterruptException();
|
||||
|
||||
// callback: on failed
|
||||
void setOnFailedCallback(ObjectData* on_failed_callback) {
|
||||
assert(!on_failed_callback || on_failed_callback->instanceof(c_Closure::classof()));
|
||||
m_onFailedCallback = on_failed_callback;
|
||||
}
|
||||
void onFailed(CObjRef exception);
|
||||
|
||||
// ContinuationWaitHandle callbacks:
|
||||
void setOnContinuationCreateCallback(ObjectData* on_start) {
|
||||
// AsyncFunctionWaitHandle callbacks:
|
||||
void setOnAsyncFunctionCreateCallback(ObjectData* on_start) {
|
||||
assert(!on_start || on_start->instanceof(c_Closure::classof()));
|
||||
m_onContinuationCreateCallback = on_start;
|
||||
m_onAsyncFunctionCreateCallback = on_start;
|
||||
}
|
||||
void setOnContinuationYieldCallback(ObjectData* on_yield) {
|
||||
assert(!on_yield || on_yield->instanceof(c_Closure::classof()));
|
||||
m_onContinuationYieldCallback = on_yield;
|
||||
void setOnAsyncFunctionAwaitCallback(ObjectData* on_await) {
|
||||
assert(!on_await || on_await->instanceof(c_Closure::classof()));
|
||||
m_onAsyncFunctionAwaitCallback = on_await;
|
||||
}
|
||||
void setOnContinuationSuccessCallback(ObjectData* on_success) {
|
||||
void setOnAsyncFunctionSuccessCallback(ObjectData* on_success) {
|
||||
assert(!on_success || on_success->instanceof(c_Closure::classof()));
|
||||
m_onContinuationSuccessCallback = on_success;
|
||||
m_onAsyncFunctionSuccessCallback = on_success;
|
||||
}
|
||||
void setOnContinuationFailCallback(ObjectData* on_fail) {
|
||||
void setOnAsyncFunctionFailCallback(ObjectData* on_fail) {
|
||||
assert(!on_fail || on_fail->instanceof(c_Closure::classof()));
|
||||
m_onContinuationFailCallback = on_fail;
|
||||
m_onAsyncFunctionFailCallback = on_fail;
|
||||
}
|
||||
bool hasOnContinuationCreateCallback() { return m_onContinuationCreateCallback.get(); }
|
||||
bool hasOnContinuationYieldCallback() { return m_onContinuationYieldCallback.get(); }
|
||||
bool hasOnContinuationSuccessCallback() { return m_onContinuationSuccessCallback.get(); }
|
||||
bool hasOnContinuationFailCallback() { return m_onContinuationFailCallback.get(); }
|
||||
void onContinuationCreate(c_ContinuationWaitHandle* cont);
|
||||
void onContinuationYield(c_ContinuationWaitHandle* cont, c_WaitHandle* child);
|
||||
void onContinuationSuccess(c_ContinuationWaitHandle* cont, CVarRef result);
|
||||
void onContinuationFail(c_ContinuationWaitHandle* cont, CObjRef exception);
|
||||
bool hasOnAsyncFunctionCreateCallback() { return m_onAsyncFunctionCreateCallback.get(); }
|
||||
bool hasOnAsyncFunctionAwaitCallback() { return m_onAsyncFunctionAwaitCallback.get(); }
|
||||
bool hasOnAsyncFunctionSuccessCallback() { return m_onAsyncFunctionSuccessCallback.get(); }
|
||||
bool hasOnAsyncFunctionFailCallback() { return m_onAsyncFunctionFailCallback.get(); }
|
||||
void onAsyncFunctionCreate(c_AsyncFunctionWaitHandle* cont);
|
||||
void onAsyncFunctionAwait(c_AsyncFunctionWaitHandle* cont, c_WaitHandle* child);
|
||||
void onAsyncFunctionSuccess(c_AsyncFunctionWaitHandle* cont, CVarRef result);
|
||||
void onAsyncFunctionFail(c_AsyncFunctionWaitHandle* cont, CObjRef exception);
|
||||
|
||||
// WaitHandle callbacks:
|
||||
void setOnJoinCallback(ObjectData* on_join) {
|
||||
@@ -172,18 +165,15 @@ class AsioSession {
|
||||
|
||||
Object m_abruptInterruptException;
|
||||
|
||||
Object m_onContinuationCreateCallback;
|
||||
Object m_onContinuationYieldCallback;
|
||||
Object m_onContinuationSuccessCallback;
|
||||
Object m_onContinuationFailCallback;
|
||||
Object m_onAsyncFunctionCreateCallback;
|
||||
Object m_onAsyncFunctionAwaitCallback;
|
||||
Object m_onAsyncFunctionSuccessCallback;
|
||||
Object m_onAsyncFunctionFailCallback;
|
||||
Object m_onGenArrayCreateCallback;
|
||||
Object m_onGenMapCreateCallback;
|
||||
Object m_onGenVectorCreateCallback;
|
||||
Object m_onSetResultToRefCreateCallback;
|
||||
Object m_onJoinCallback;
|
||||
|
||||
// Legacy callback for backwards compatibility.
|
||||
Object m_onFailedCallback;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
+40
-40
@@ -34,57 +34,57 @@ namespace {
|
||||
StaticString s_continuation("Continuation");
|
||||
}
|
||||
|
||||
c_ContinuationWaitHandle::c_ContinuationWaitHandle(Class* cb)
|
||||
c_AsyncFunctionWaitHandle::c_AsyncFunctionWaitHandle(Class* cb)
|
||||
: c_BlockableWaitHandle(cb), m_continuation(), m_child(), m_privData(),
|
||||
m_depth(0) {
|
||||
}
|
||||
|
||||
c_ContinuationWaitHandle::~c_ContinuationWaitHandle() {
|
||||
c_AsyncFunctionWaitHandle::~c_AsyncFunctionWaitHandle() {
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::t___construct() {
|
||||
void c_AsyncFunctionWaitHandle::t___construct() {
|
||||
Object e(SystemLib::AllocInvalidOperationExceptionObject(
|
||||
"Use $continuation->getWaitHandle() instead of constructor"));
|
||||
throw e;
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::ti_setoncreatecallback(CVarRef callback) {
|
||||
void c_AsyncFunctionWaitHandle::ti_setoncreatecallback(CVarRef callback) {
|
||||
if (!callback.isNull() && !callback.instanceof(c_Closure::classof())) {
|
||||
Object e(SystemLib::AllocInvalidArgumentExceptionObject(
|
||||
"Unable to set ContinuationWaitHandle::onStart: on_start_cb not a closure"));
|
||||
"Unable to set AsyncFunctionWaitHandle::onStart: on_start_cb not a closure"));
|
||||
throw e;
|
||||
}
|
||||
AsioSession::Get()->setOnContinuationCreateCallback(callback.getObjectDataOrNull());
|
||||
AsioSession::Get()->setOnAsyncFunctionCreateCallback(callback.getObjectDataOrNull());
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::ti_setonyieldcallback(CVarRef callback) {
|
||||
void c_AsyncFunctionWaitHandle::ti_setonawaitcallback(CVarRef callback) {
|
||||
if (!callback.isNull() && !callback.instanceof(c_Closure::classof())) {
|
||||
Object e(SystemLib::AllocInvalidArgumentExceptionObject(
|
||||
"Unable to set ContinuationWaitHandle::onYield: on_yield_cb not a closure"));
|
||||
"Unable to set AsyncFunctionWaitHandle::onAwait: on_await_cb not a closure"));
|
||||
throw e;
|
||||
}
|
||||
AsioSession::Get()->setOnContinuationYieldCallback(callback.getObjectDataOrNull());
|
||||
AsioSession::Get()->setOnAsyncFunctionAwaitCallback(callback.getObjectDataOrNull());
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::ti_setonsuccesscallback(CVarRef callback) {
|
||||
void c_AsyncFunctionWaitHandle::ti_setonsuccesscallback(CVarRef callback) {
|
||||
if (!callback.isNull() && !callback.instanceof(c_Closure::classof())) {
|
||||
Object e(SystemLib::AllocInvalidArgumentExceptionObject(
|
||||
"Unable to set ContinuationWaitHandle::onSuccess: on_success_cb not a closure"));
|
||||
"Unable to set AsyncFunctionWaitHandle::onSuccess: on_success_cb not a closure"));
|
||||
throw e;
|
||||
}
|
||||
AsioSession::Get()->setOnContinuationSuccessCallback(callback.getObjectDataOrNull());
|
||||
AsioSession::Get()->setOnAsyncFunctionSuccessCallback(callback.getObjectDataOrNull());
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::ti_setonfailcallback(CVarRef callback) {
|
||||
void c_AsyncFunctionWaitHandle::ti_setonfailcallback(CVarRef callback) {
|
||||
if (!callback.isNull() && !callback.instanceof(c_Closure::classof())) {
|
||||
Object e(SystemLib::AllocInvalidArgumentExceptionObject(
|
||||
"Unable to set ContinuationWaitHandle::onFail: on_fail_cb not a closure"));
|
||||
"Unable to set AsyncFunctionWaitHandle::onFail: on_fail_cb not a closure"));
|
||||
throw e;
|
||||
}
|
||||
AsioSession::Get()->setOnContinuationFailCallback(callback.getObjectDataOrNull());
|
||||
AsioSession::Get()->setOnAsyncFunctionFailCallback(callback.getObjectDataOrNull());
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::Create(c_Continuation* continuation) {
|
||||
void c_AsyncFunctionWaitHandle::Create(c_Continuation* continuation) {
|
||||
assert(continuation);
|
||||
assert(continuation->m_waitHandle.isNull());
|
||||
|
||||
@@ -104,24 +104,24 @@ void c_ContinuationWaitHandle::Create(c_Continuation* continuation) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
continuation->m_waitHandle = NEWOBJ(c_ContinuationWaitHandle)();
|
||||
continuation->m_waitHandle = NEWOBJ(c_AsyncFunctionWaitHandle)();
|
||||
continuation->m_waitHandle->initialize(continuation, depth + 1);
|
||||
|
||||
// needs to be called after continuation->m_waitHandle is set
|
||||
if (UNLIKELY(session->hasOnContinuationCreateCallback())) {
|
||||
session->onContinuationCreate(continuation->m_waitHandle.get());
|
||||
if (UNLIKELY(session->hasOnAsyncFunctionCreateCallback())) {
|
||||
session->onAsyncFunctionCreate(continuation->m_waitHandle.get());
|
||||
}
|
||||
}
|
||||
|
||||
Object c_ContinuationWaitHandle::t_getprivdata() {
|
||||
Object c_AsyncFunctionWaitHandle::t_getprivdata() {
|
||||
return m_privData;
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::t_setprivdata(CObjRef data) {
|
||||
void c_AsyncFunctionWaitHandle::t_setprivdata(CObjRef data) {
|
||||
m_privData = data;
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::initialize(c_Continuation* continuation, uint16_t depth) {
|
||||
void c_AsyncFunctionWaitHandle::initialize(c_Continuation* continuation, uint16_t depth) {
|
||||
m_continuation = continuation;
|
||||
m_child = nullptr;
|
||||
m_privData = nullptr;
|
||||
@@ -146,7 +146,7 @@ void c_ContinuationWaitHandle::initialize(c_Continuation* continuation, uint16_t
|
||||
}
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::run() {
|
||||
void c_AsyncFunctionWaitHandle::run() {
|
||||
// may happen if scheduled in multiple contexts
|
||||
if (getState() != STATE_SCHEDULED) {
|
||||
return;
|
||||
@@ -186,13 +186,13 @@ void c_ContinuationWaitHandle::run() {
|
||||
c_WaitHandle* child = c_WaitHandle::fromCell(value);
|
||||
if (UNLIKELY(!child)) {
|
||||
Object e(SystemLib::AllocInvalidArgumentExceptionObject(
|
||||
"Expected yield argument to be an instance of WaitHandle"));
|
||||
"Expected await argument to be an instance of Awaitable"));
|
||||
throw e;
|
||||
}
|
||||
|
||||
AsioSession* session = AsioSession::Get();
|
||||
if (UNLIKELY(session->hasOnContinuationYieldCallback())) {
|
||||
session->onContinuationYield(this, child);
|
||||
if (UNLIKELY(session->hasOnAsyncFunctionAwaitCallback())) {
|
||||
session->onAsyncFunctionAwait(this, child);
|
||||
}
|
||||
|
||||
m_child = child;
|
||||
@@ -212,17 +212,17 @@ void c_ContinuationWaitHandle::run() {
|
||||
}
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::onUnblocked() {
|
||||
void c_AsyncFunctionWaitHandle::onUnblocked() {
|
||||
setState(STATE_SCHEDULED);
|
||||
if (isInContext()) {
|
||||
getContext()->schedule(this);
|
||||
}
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::markAsSucceeded(const Cell& result) {
|
||||
void c_AsyncFunctionWaitHandle::markAsSucceeded(const Cell& result) {
|
||||
AsioSession* session = AsioSession::Get();
|
||||
if (UNLIKELY(session->hasOnContinuationSuccessCallback())) {
|
||||
session->onContinuationSuccess(this, cellAsCVarRef(result));
|
||||
if (UNLIKELY(session->hasOnAsyncFunctionSuccessCallback())) {
|
||||
session->onAsyncFunctionSuccess(this, cellAsCVarRef(result));
|
||||
}
|
||||
|
||||
setResult(result);
|
||||
@@ -232,19 +232,19 @@ void c_ContinuationWaitHandle::markAsSucceeded(const Cell& result) {
|
||||
m_child = nullptr;
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::markAsFailed(CObjRef exception) {
|
||||
void c_AsyncFunctionWaitHandle::markAsFailed(CObjRef exception) {
|
||||
AsioSession* session = AsioSession::Get();
|
||||
session->onFailed(exception);
|
||||
if (UNLIKELY(session->hasOnContinuationFailCallback())) {
|
||||
session->onContinuationFail(this, exception);
|
||||
if (UNLIKELY(session->hasOnAsyncFunctionFailCallback())) {
|
||||
session->onAsyncFunctionFail(this, exception);
|
||||
}
|
||||
|
||||
setException(exception.get());
|
||||
|
||||
m_continuation = nullptr;
|
||||
m_child = nullptr;
|
||||
}
|
||||
|
||||
String c_ContinuationWaitHandle::getName() {
|
||||
String c_AsyncFunctionWaitHandle::getName() {
|
||||
switch (getState()) {
|
||||
case STATE_SUCCEEDED:
|
||||
return s_continuationResult;
|
||||
@@ -269,7 +269,7 @@ String c_ContinuationWaitHandle::getName() {
|
||||
}
|
||||
}
|
||||
|
||||
c_WaitableWaitHandle* c_ContinuationWaitHandle::getChild() {
|
||||
c_WaitableWaitHandle* c_AsyncFunctionWaitHandle::getChild() {
|
||||
if (getState() == STATE_BLOCKED) {
|
||||
assert(dynamic_cast<c_WaitableWaitHandle*>(m_child.get()));
|
||||
return static_cast<c_WaitableWaitHandle*>(m_child.get());
|
||||
@@ -279,7 +279,7 @@ c_WaitableWaitHandle* c_ContinuationWaitHandle::getChild() {
|
||||
}
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::enterContext(context_idx_t ctx_idx) {
|
||||
void c_AsyncFunctionWaitHandle::enterContext(context_idx_t ctx_idx) {
|
||||
assert(AsioSession::Get()->getContext(ctx_idx));
|
||||
|
||||
// stop before corrupting unioned data
|
||||
@@ -318,7 +318,7 @@ void c_ContinuationWaitHandle::enterContext(context_idx_t ctx_idx) {
|
||||
}
|
||||
}
|
||||
|
||||
void c_ContinuationWaitHandle::exitContext(context_idx_t ctx_idx) {
|
||||
void c_AsyncFunctionWaitHandle::exitContext(context_idx_t ctx_idx) {
|
||||
assert(AsioSession::Get()->getContext(ctx_idx));
|
||||
|
||||
// stop before corrupting unioned data
|
||||
@@ -361,7 +361,7 @@ void c_ContinuationWaitHandle::exitContext(context_idx_t ctx_idx) {
|
||||
}
|
||||
|
||||
// Get the filename in which execution will proceed when execution resumes.
|
||||
String c_ContinuationWaitHandle::getFileName() {
|
||||
String c_AsyncFunctionWaitHandle::getFileName() {
|
||||
if (m_continuation.isNull()) return empty_string;
|
||||
auto ar = m_continuation->actRec();
|
||||
auto file = ar->m_func->unit()->filepath()->data();
|
||||
@@ -372,7 +372,7 @@ String c_ContinuationWaitHandle::getFileName() {
|
||||
}
|
||||
|
||||
// Get the line number on which execution will proceed when execution resumes.
|
||||
int c_ContinuationWaitHandle::getLineNumber() {
|
||||
int c_AsyncFunctionWaitHandle::getLineNumber() {
|
||||
if (m_continuation.isNull()) return -1;
|
||||
auto const unit = m_continuation->actRec()->m_func->unit();
|
||||
return unit->getLineNumber(m_continuation->getNextExecutionOffset());
|
||||
@@ -85,7 +85,7 @@ Object c_GenArrayWaitHandle::ti_create(CArrRef dependencies) {
|
||||
|
||||
Cell* current = tvAssertCell(deps->nvGetValueRef(iter_pos));
|
||||
if (IS_NULL_TYPE(current->m_type)) {
|
||||
// {uninit,null} yields null
|
||||
// {uninit,null} gives null
|
||||
tvWriteNull(current);
|
||||
continue;
|
||||
}
|
||||
@@ -141,7 +141,7 @@ void c_GenArrayWaitHandle::onUnblocked() {
|
||||
|
||||
Cell* current = tvAssertCell(m_deps->nvGetValueRef(m_iterPos));
|
||||
if (IS_NULL_TYPE(current->m_type)) {
|
||||
// {uninit,null} yields null
|
||||
// {uninit,null} gives null
|
||||
tvWriteNull(current);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -51,18 +51,5 @@ Object f_asio_get_running() {
|
||||
return AsioSession::Get()->getCurrentWaitHandle();
|
||||
}
|
||||
|
||||
void f_asio_set_on_failed_callback(CVarRef on_failed_cb) {
|
||||
if (!on_failed_cb.isNull() && !on_failed_cb.instanceof(c_Closure::classof())) {
|
||||
Object e(SystemLib::AllocInvalidArgumentExceptionObject(
|
||||
"Unable to set asio on failed callback: on_failed_cb not a closure"));
|
||||
throw e;
|
||||
}
|
||||
AsioSession::Get()->setOnFailedCallback(on_failed_cb.getObjectDataOrNull());
|
||||
}
|
||||
|
||||
void f_asio_set_on_started_callback(CVarRef on_started_cb) {
|
||||
c_ContinuationWaitHandle::ti_setoncreatecallback(on_started_cb);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace HPHP {
|
||||
int f_asio_get_current_context_idx();
|
||||
Object f_asio_get_running_in_context(int ctx_idx);
|
||||
Object f_asio_get_running();
|
||||
void f_asio_set_on_failed_callback(CVarRef on_failed_cb);
|
||||
void f_asio_set_on_started_callback(CVarRef on_started_cb);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// class WaitHandle
|
||||
@@ -44,7 +42,7 @@ void f_asio_set_on_started_callback(CVarRef on_started_cb);
|
||||
* StaticExceptionWaitHandle - statically failed wait handle with exception
|
||||
* WaitableWaitHandle - wait handle that can be waited for
|
||||
* BlockableWaitHandle - wait handle that can be blocked by other WH
|
||||
* ContinuationWaitHandle - Continuation-powered asynchronous execution
|
||||
* AsyncFunctionWaitHandle - async function-based asynchronous execution
|
||||
* GenArrayWaitHandle - wait handle representing an array of WHs
|
||||
* GenMapWaitHandle - wait handle representing an Map of WHs
|
||||
* GenVectorWaitHandle - wait handle representing an Vector of WHs
|
||||
@@ -53,7 +51,7 @@ void f_asio_set_on_started_callback(CVarRef on_started_cb);
|
||||
*
|
||||
* A wait handle can be either synchronously joined (waited for the operation
|
||||
* to finish) or passed in various contexts as a dependency and waited for
|
||||
* asynchronously (such as using yield mechanism of ContinuationWaitHandle or
|
||||
* asynchronously (such as using await mechanism of async function or
|
||||
* passed as an array member of GenArrayWaitHandle).
|
||||
*/
|
||||
FORWARD_DECLARE_CLASS(WaitHandle);
|
||||
@@ -224,7 +222,7 @@ class c_WaitableWaitHandle : public c_WaitHandle {
|
||||
static const int8_t STATE_NEW = 2;
|
||||
|
||||
private:
|
||||
c_ContinuationWaitHandle* m_creator;
|
||||
c_AsyncFunctionWaitHandle* m_creator;
|
||||
c_BlockableWaitHandle* m_firstParent;
|
||||
};
|
||||
|
||||
@@ -267,26 +265,26 @@ class c_BlockableWaitHandle : public c_WaitableWaitHandle {
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// class ContinuationWaitHandle
|
||||
// class AsyncFunctionWaitHandle
|
||||
|
||||
/**
|
||||
* A continuation wait handle represents a basic unit of asynchronous execution
|
||||
* powered by continuation object. An asynchronous program can be written using
|
||||
* continuations; a dependency on another wait handle is set up by yielding such
|
||||
* continuations; a dependency on another wait handle is set up by awaiting such
|
||||
* wait handle, giving control of the execution back to the asio framework.
|
||||
*/
|
||||
FORWARD_DECLARE_CLASS(Continuation);
|
||||
FORWARD_DECLARE_CLASS(ContinuationWaitHandle);
|
||||
class c_ContinuationWaitHandle : public c_BlockableWaitHandle {
|
||||
FORWARD_DECLARE_CLASS(AsyncFunctionWaitHandle);
|
||||
class c_AsyncFunctionWaitHandle : public c_BlockableWaitHandle {
|
||||
public:
|
||||
DECLARE_CLASS_NO_SWEEP(ContinuationWaitHandle)
|
||||
DECLARE_CLASS_NO_SWEEP(AsyncFunctionWaitHandle)
|
||||
|
||||
// need to implement
|
||||
public: c_ContinuationWaitHandle(Class* cls = c_ContinuationWaitHandle::classof());
|
||||
public: ~c_ContinuationWaitHandle();
|
||||
public: c_AsyncFunctionWaitHandle(Class* cls = c_AsyncFunctionWaitHandle::classof());
|
||||
public: ~c_AsyncFunctionWaitHandle();
|
||||
public: void t___construct();
|
||||
public: static void ti_setoncreatecallback(CVarRef callback);
|
||||
public: static void ti_setonyieldcallback(CVarRef callback);
|
||||
public: static void ti_setonawaitcallback(CVarRef callback);
|
||||
public: static void ti_setonsuccesscallback(CVarRef callback);
|
||||
public: static void ti_setonfailcallback(CVarRef callback);
|
||||
public: Object t_getprivdata();
|
||||
|
||||
@@ -87,7 +87,7 @@ void c_Continuation::t_update_key(int64_t label, CVarRef key, CVarRef value) {
|
||||
|
||||
Object c_Continuation::t_getwaithandle() {
|
||||
if (m_waitHandle.isNull()) {
|
||||
c_ContinuationWaitHandle::Create(this);
|
||||
c_AsyncFunctionWaitHandle::Create(this);
|
||||
assert(!m_waitHandle.isNull());
|
||||
}
|
||||
return m_waitHandle;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
FORWARD_DECLARE_CLASS(Continuation);
|
||||
FORWARD_DECLARE_CLASS(ContinuationWaitHandle);
|
||||
FORWARD_DECLARE_CLASS(AsyncFunctionWaitHandle);
|
||||
Object f_hphp_create_continuation(const String& clsname, const String& funcname,
|
||||
const String& origFuncName,
|
||||
CArrRef args = null_array);
|
||||
@@ -159,7 +159,7 @@ public:
|
||||
|
||||
/* ActRec for continuation (does not live on stack) */
|
||||
ActRec* m_arPtr;
|
||||
p_ContinuationWaitHandle m_waitHandle;
|
||||
p_AsyncFunctionWaitHandle m_waitHandle;
|
||||
|
||||
/* temporary storage used to save the SP when inlining into a continuation */
|
||||
void* m_stashedSP;
|
||||
|
||||
@@ -932,14 +932,21 @@ void f_header(const String& str, bool replace /* = true */,
|
||||
// handle single line of status code
|
||||
if (header->size() >= 5 && strncasecmp(header_line, "HTTP/", 5) == 0) {
|
||||
int code = 200;
|
||||
const char *reason = nullptr;
|
||||
for (const char *ptr = header_line; *ptr; ptr++) {
|
||||
if (*ptr == ' ' && *(ptr + 1) != ' ') {
|
||||
code = atoi(ptr + 1);
|
||||
for (ptr++; *ptr; ptr++) {
|
||||
if (*ptr == ' ' && *(ptr + 1) != ' ') {
|
||||
reason = ptr + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (code) {
|
||||
transport->setResponse(code, "explicit_header");
|
||||
transport->setResponse(code, reason);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1482,6 +1482,20 @@ Variant c_PDO::t_quote(const String& str, int64_t paramtype /* = q_PDO$$PARAM_ST
|
||||
return false;
|
||||
}
|
||||
|
||||
bool c_PDO::t_sqlitecreatefunction(const String& name,
|
||||
CVarRef callback,
|
||||
int64_t argcount /* = -1 */) {
|
||||
raise_recoverable_error("PDO::sqliteCreateFunction not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool c_PDO::t_sqlitecreateaggregate(const String& name,
|
||||
CVarRef step, CVarRef final,
|
||||
int64_t argcount /* = -1 */) {
|
||||
raise_recoverable_error("PDO::sqliteCreateAggregate not implemented");
|
||||
return false;
|
||||
}
|
||||
|
||||
Variant c_PDO::t___wakeup() {
|
||||
throw_pdo_exception(uninit_null(), uninit_null(),
|
||||
"You cannot serialize or unserialize PDO instances");
|
||||
|
||||
@@ -135,7 +135,12 @@ class c_PDO : public ExtObjectData, public Sweepable {
|
||||
public: Variant t_errorcode();
|
||||
public: Array t_errorinfo();
|
||||
public: Variant t_query(const String& sql);
|
||||
public: Variant t_quote(const String& str, int64_t paramtype = q_PDO$$PARAM_STR);
|
||||
public: Variant t_quote(const String& str,
|
||||
int64_t paramtype = q_PDO$$PARAM_STR);
|
||||
public: bool t_sqlitecreatefunction(const String& name, CVarRef callback,
|
||||
int64_t argcount = -1);
|
||||
public: bool t_sqlitecreateaggregate(const String& name, CVarRef step,
|
||||
CVarRef final, int64_t argcount = -1);
|
||||
public: Variant t___wakeup();
|
||||
public: Variant t___sleep();
|
||||
public: static Array ti_getavailabledrivers();
|
||||
@@ -156,13 +161,25 @@ class c_PDOStatement : public ExtObjectData, public Sweepable {
|
||||
public: ~c_PDOStatement();
|
||||
public: void t___construct();
|
||||
public: Variant t_execute(CArrRef params = null_array);
|
||||
public: Variant t_fetch(int64_t how = 0, int64_t orientation = q_PDO$$FETCH_ORI_NEXT, int64_t offset = 0);
|
||||
public: Variant t_fetchobject(const String& class_name = null_string, CVarRef ctor_args = uninit_null());
|
||||
public: Variant t_fetch(int64_t how = 0,
|
||||
int64_t orientation = q_PDO$$FETCH_ORI_NEXT,
|
||||
int64_t offset = 0);
|
||||
public: Variant t_fetchobject(const String& class_name = null_string,
|
||||
CVarRef ctor_args = uninit_null());
|
||||
public: Variant t_fetchcolumn(int64_t column_numner = 0);
|
||||
public: Variant t_fetchall(int64_t how = 0, CVarRef class_name = uninit_null(), CVarRef ctor_args = uninit_null());
|
||||
public: bool t_bindvalue(CVarRef paramno, CVarRef param, int64_t type = q_PDO$$PARAM_STR);
|
||||
public: bool t_bindparam(CVarRef paramno, VRefParam param, int64_t type = q_PDO$$PARAM_STR, int64_t max_value_len = 0, CVarRef driver_params = uninit_null());
|
||||
public: bool t_bindcolumn(CVarRef paramno, VRefParam param, int64_t type = q_PDO$$PARAM_STR, int64_t max_value_len = 0, CVarRef driver_params = uninit_null());
|
||||
public: Variant t_fetchall(int64_t how = 0,
|
||||
CVarRef class_name = uninit_null(),
|
||||
CVarRef ctor_args = uninit_null());
|
||||
public: bool t_bindvalue(CVarRef paramno, CVarRef param,
|
||||
int64_t type = q_PDO$$PARAM_STR);
|
||||
public: bool t_bindparam(CVarRef paramno, VRefParam param,
|
||||
int64_t type = q_PDO$$PARAM_STR,
|
||||
int64_t max_value_len = 0,
|
||||
CVarRef driver_params = uninit_null());
|
||||
public: bool t_bindcolumn(CVarRef paramno, VRefParam param,
|
||||
int64_t type = q_PDO$$PARAM_STR,
|
||||
int64_t max_value_len = 0,
|
||||
CVarRef driver_params = uninit_null());
|
||||
public: int64_t t_rowcount();
|
||||
public: Variant t_errorcode();
|
||||
public: Array t_errorinfo();
|
||||
@@ -170,7 +187,8 @@ class c_PDOStatement : public ExtObjectData, public Sweepable {
|
||||
public: Variant t_getattribute(int64_t attribute);
|
||||
public: int64_t t_columncount();
|
||||
public: Variant t_getcolumnmeta(int64_t column);
|
||||
public: bool t_setfetchmode(int _argc, int64_t mode, CArrRef _argv = null_array);
|
||||
public: bool t_setfetchmode(int _argc, int64_t mode,
|
||||
CArrRef _argv = null_array);
|
||||
public: bool t_nextrowset();
|
||||
public: bool t_closecursor();
|
||||
public: Variant t_debugdumpparams();
|
||||
|
||||
@@ -445,7 +445,7 @@ static void set_function_info(Array &ret, const Func* func) {
|
||||
param.set(s_class, VarNR(func->cls() ? func->cls()->name() :
|
||||
func->preClass()->name()));
|
||||
}
|
||||
if (!nonExtendedConstraint || fpi.typeConstraint().nullable()) {
|
||||
if (!nonExtendedConstraint || fpi.typeConstraint().isNullable()) {
|
||||
param.set(s_nullable, true_varNR);
|
||||
}
|
||||
|
||||
|
||||
@@ -253,38 +253,53 @@ void HttpProtocol::PrepareSystemVariables(Transport *transport,
|
||||
HeaderMap headers;
|
||||
transport->getHeaders(headers);
|
||||
|
||||
static int bad_request_count = -1;
|
||||
for (HeaderMap::const_iterator iter = headers.begin();
|
||||
iter != headers.end(); ++iter) {
|
||||
const vector<string> &values = iter->second;
|
||||
static std::atomic<int> badRequests(-1);
|
||||
|
||||
// Detect suspicious headers. We are about to modify header names
|
||||
// for the SERVER variable. This means that it is possible to
|
||||
// deliberately cause a header collision, which an attacker could
|
||||
// use to sneak a header past a proxy that would either overwrite
|
||||
// or filter it otherwise. Client code should use
|
||||
// apache_request_headers() to retrieve the original headers if
|
||||
// they are security-critical.
|
||||
if (RuntimeOption::LogHeaderMangle != 0) {
|
||||
String key = "HTTP_";
|
||||
key += f_strtoupper(iter->first).replace("-","_");
|
||||
if (server.asArrRef().exists(key)) {
|
||||
if (!(++bad_request_count % RuntimeOption::LogHeaderMangle)) {
|
||||
Logger::Warning(
|
||||
"HeaderMangle warning: "
|
||||
"The header %s overwrote another header which mapped to the same "
|
||||
"key. This happens because PHP normalises - to _, ie AN_EXAMPLE "
|
||||
"and AN-EXAMPLE are equivalent. You should treat this as "
|
||||
"malicious.",
|
||||
iter->first.c_str());
|
||||
}
|
||||
}
|
||||
std::vector<std::string> badHeaders;
|
||||
for (auto const& header : headers) {
|
||||
auto const& key = header.first;
|
||||
auto const& values = header.second;
|
||||
auto normalizedKey = String("HTTP_") + f_strtoupper(key).replace("-", "_");
|
||||
|
||||
// Detect suspicious headers. We are about to modify header names for
|
||||
// the SERVER variable. This means that it is possible to deliberately
|
||||
// cause a header collision, which an attacker could use to sneak a
|
||||
// header past a proxy that would either overwrite or filter it
|
||||
// otherwise. Client code should use apache_request_headers() to
|
||||
// retrieve the original headers if they are security-critical.
|
||||
if (RuntimeOption::LogHeaderMangle != 0 &&
|
||||
server.asArrRef().exists(normalizedKey)) {
|
||||
badHeaders.push_back(key);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < values.size(); i++) {
|
||||
String key = "HTTP_";
|
||||
key += f_strtoupper(iter->first).replace("-", "_");
|
||||
server.set(key, String(values[i]));
|
||||
if (!values.empty()) {
|
||||
// When a header has multiple values, we always take the last one.
|
||||
server.set(normalizedKey, String(values.back()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!badHeaders.empty()) {
|
||||
auto reqId = badRequests.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
if (!(reqId % RuntimeOption::LogHeaderMangle)) {
|
||||
std::string badNames = folly::join(", ", badHeaders);
|
||||
std::string allHeaders;
|
||||
|
||||
const char* separator = "";
|
||||
for (auto const& header : headers) {
|
||||
for (auto const& value : header.second) {
|
||||
folly::toAppend(separator, header.first, ": ", value,
|
||||
&allHeaders);
|
||||
separator = "\n";
|
||||
}
|
||||
}
|
||||
|
||||
Logger::Warning(
|
||||
"HeaderMangle warning: "
|
||||
"The header(s) [%s] overwrote other headers which mapped to the same "
|
||||
"key. This happens because PHP normalises - to _, ie AN_EXAMPLE "
|
||||
"and AN-EXAMPLE are equivalent. You should treat this as "
|
||||
"malicious. All headers from this request:\n%s",
|
||||
badNames.c_str(), allHeaders.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -208,8 +208,7 @@ void HttpServer::onServerShutdown() {
|
||||
}
|
||||
}
|
||||
|
||||
void HttpServer::takeoverShutdown(HPHP::Server* server) {
|
||||
assert(server == m_pageServer.get());
|
||||
void HttpServer::takeoverShutdown() {
|
||||
// We want to synchronously shut down our satellite servers to free up ports,
|
||||
// then asynchronously shut down everything else.
|
||||
onServerShutdown();
|
||||
|
||||
@@ -46,7 +46,7 @@ public:
|
||||
void flushLog();
|
||||
void watchDog();
|
||||
|
||||
void takeoverShutdown(HPHP::Server* server);
|
||||
void takeoverShutdown();
|
||||
|
||||
ServerPtr getPageServer() { return m_pageServer;}
|
||||
void getSatelliteStats(vector<std::pair<std::string, int>> *stats);
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
|
||||
#include "hphp/runtime/server/libevent-server.h"
|
||||
#include "hphp/runtime/server/libevent-server-with-fd.h"
|
||||
#include "hphp/runtime/server/libevent-server-with-takeover.h"
|
||||
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@@ -29,23 +27,7 @@ public:
|
||||
};
|
||||
|
||||
ServerPtr LibEventServerFactory::createServer(const ServerOptions& options) {
|
||||
if (options.m_serverFD != -1 || options.m_sslFD != -1) {
|
||||
auto const server = std::make_shared<LibEventServerWithFd>
|
||||
(options.m_address, options.m_port, options.m_numThreads);
|
||||
server->setServerSocketFd(options.m_serverFD);
|
||||
server->setSSLSocketFd(options.m_sslFD);
|
||||
return server;
|
||||
}
|
||||
|
||||
if (!options.m_takeoverFilename.empty()) {
|
||||
auto const server = std::make_shared<LibEventServerWithTakeover>
|
||||
(options.m_address, options.m_port, options.m_numThreads);
|
||||
server->setTransferFilename(options.m_takeoverFilename);
|
||||
return server;
|
||||
}
|
||||
|
||||
return std::make_shared<LibEventServer>(options.m_address, options.m_port,
|
||||
options.m_numThreads);
|
||||
return std::make_shared<LibEventServer>(options);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace HPHP {
|
||||
|
||||
LibEventServerWithFd::LibEventServerWithFd
|
||||
(const std::string &address, int port, int thread)
|
||||
: LibEventServer(address, port, thread)
|
||||
: LibEventServer(ServerOptions(address, port, thread))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -78,12 +78,11 @@ LibEventTransportTraits::LibEventTransportTraits(LibEventJobPtr job,
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// constructor and destructor
|
||||
|
||||
LibEventServer::LibEventServer(const std::string &address, int port,
|
||||
int thread)
|
||||
: Server(address, port, thread),
|
||||
m_accept_sock(-1),
|
||||
m_accept_sock_ssl(-1),
|
||||
m_dispatcher(thread, RuntimeOption::ServerThreadRoundRobin,
|
||||
LibEventServer::LibEventServer(const ServerOptions &options)
|
||||
: Server(options.m_address, options.m_port, options.m_numThreads),
|
||||
m_accept_sock(options.m_serverFD),
|
||||
m_accept_sock_ssl(options.m_sslFD),
|
||||
m_dispatcher(options.m_numThreads, RuntimeOption::ServerThreadRoundRobin,
|
||||
RuntimeOption::ServerThreadDropCacheTimeoutSeconds,
|
||||
RuntimeOption::ServerThreadDropStack,
|
||||
this, RuntimeOption::ServerThreadJobLIFOSwitchThreshold,
|
||||
@@ -99,6 +98,10 @@ LibEventServer::LibEventServer(const std::string &address, int port,
|
||||
evhttp_set_read_limit(m_server, RuntimeOption::RequestBodyReadLimit);
|
||||
#endif
|
||||
m_responseQueue.create(m_eventBase);
|
||||
|
||||
if (!options.m_takeoverFilename.empty()) {
|
||||
m_takeover_agent.reset(new TakeoverAgent(options.m_takeoverFilename));
|
||||
}
|
||||
}
|
||||
|
||||
LibEventServer::~LibEventServer() {
|
||||
@@ -116,19 +119,129 @@ LibEventServer::~LibEventServer() {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// implementing HttpServer
|
||||
|
||||
int LibEventServer::getAcceptSocket() {
|
||||
int ret;
|
||||
const char *address = m_address.empty() ? nullptr : m_address.c_str();
|
||||
ret = evhttp_bind_socket_backlog_fd(m_server, address,
|
||||
m_port, RuntimeOption::ServerBacklog);
|
||||
void LibEventServer::addTakeoverListener(TakeoverListener* listener) {
|
||||
if (m_takeover_agent) {
|
||||
m_takeover_agent->addTakeoverListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
void LibEventServer::removeTakeoverListener(TakeoverListener* listener) {
|
||||
if (m_takeover_agent) {
|
||||
m_takeover_agent->removeTakeoverListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
int LibEventServer::useExistingFd(evhttp *server, int fd, bool needListen) {
|
||||
Logger::Info("inheritfd: using inherited fd %d for server", fd);
|
||||
|
||||
int ret = -1;
|
||||
if (needListen) {
|
||||
ret = listen(fd, RuntimeOption::ServerBacklog);
|
||||
if (ret != 0) {
|
||||
Logger::Error("inheritfd: listen() failed: %s",
|
||||
folly::errnoStr(errno).c_str());
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ret = evhttp_accept_socket(server, fd);
|
||||
if (ret < 0) {
|
||||
Logger::Error("Fail to bind port %d", m_port);
|
||||
Logger::Error("evhttp_accept_socket: %s",
|
||||
folly::errnoStr(errno).c_str());
|
||||
int errno_save = errno;
|
||||
close(fd);
|
||||
errno = errno_save;
|
||||
return -1;
|
||||
}
|
||||
m_accept_sock = ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LibEventServer::getAcceptSocket() {
|
||||
if (m_accept_sock >= 0) {
|
||||
if (useExistingFd(m_server, m_accept_sock, true /* listen */) != 0) {
|
||||
m_accept_sock = -1;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *address = m_address.empty() ? nullptr : m_address.c_str();
|
||||
int ret = evhttp_bind_socket_backlog_fd(m_server, address,
|
||||
m_port, RuntimeOption::ServerBacklog);
|
||||
if (ret < 0) {
|
||||
if (errno == EADDRINUSE && m_takeover_agent) {
|
||||
m_accept_sock = m_takeover_agent->takeover();
|
||||
if (m_accept_sock < 0) {
|
||||
return -1;
|
||||
}
|
||||
if (useExistingFd(m_server, m_accept_sock, false /* no listen */) != 0) {
|
||||
m_accept_sock = -1;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
Logger::Error("Fail to bind port %d", m_port);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
m_accept_sock = ret;
|
||||
if (m_takeover_agent) {
|
||||
m_takeover_agent->requestShutdown();
|
||||
m_takeover_agent->setupFdServer(m_eventBase, m_accept_sock, this);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LibEventServer::onTakeoverRequest(TakeoverAgent::RequestType type) {
|
||||
int ret = -1;
|
||||
if (type == TakeoverAgent::RequestType::LISTEN_SOCKET) {
|
||||
// TODO: This is broken-sauce. We should continue serving
|
||||
// requests from this process until shutdown.
|
||||
|
||||
// Make evhttp forget our copy of the accept socket so we don't accept any
|
||||
// more connections and drop them. Keep the socket open until we get the
|
||||
// shutdown request so that we can still serve AFDT requests (if the new
|
||||
// server crashes or something). The downside is that it will take the LB
|
||||
// longer to figure out that we are broken.
|
||||
ret = evhttp_del_accept_socket(m_server, m_accept_sock);
|
||||
if (ret < 0) {
|
||||
// This will fail if we get a second AFDT request, but the spurious
|
||||
// log message is not too harmful.
|
||||
Logger::Error("Unable to delete accept socket");
|
||||
}
|
||||
} else if (type == TakeoverAgent::RequestType::TERMINATE) {
|
||||
ret = close(m_accept_sock);
|
||||
if (ret < 0) {
|
||||
Logger::Error("Unable to close accept socket");
|
||||
return -1;
|
||||
}
|
||||
m_accept_sock = -1;
|
||||
|
||||
// Close SSL server
|
||||
if (m_server_ssl) {
|
||||
assert(m_accept_sock_ssl > 0);
|
||||
ret = evhttp_del_accept_socket(m_server_ssl, m_accept_sock_ssl);
|
||||
if (ret < 0) {
|
||||
Logger::Error("Unable to delete accept socket for SSL in evhttp");
|
||||
return -1;
|
||||
}
|
||||
ret = close(m_accept_sock_ssl);
|
||||
if (ret < 0) {
|
||||
Logger::Error("Unable to close accept socket for SSL");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void LibEventServer::takeoverAborted() {
|
||||
if (m_accept_sock >= 0) {
|
||||
close(m_accept_sock);
|
||||
m_accept_sock = -1;
|
||||
}
|
||||
}
|
||||
|
||||
int LibEventServer::getLibEventConnectionCount() {
|
||||
return evhttp_get_connection_count(m_server);
|
||||
}
|
||||
@@ -140,10 +253,7 @@ void LibEventServer::start() {
|
||||
throw FailedToListenException(m_address, m_port);
|
||||
}
|
||||
|
||||
if (m_server_ssl != nullptr && m_accept_sock_ssl != -2) {
|
||||
// m_accept_sock_ssl here serves as a flag to indicate whether it is
|
||||
// called from subclass (LibEventServerWithTakeover). If it is (==-2)
|
||||
// we delay the getAcceptSocketSSL();
|
||||
if (m_server_ssl != nullptr) {
|
||||
if (getAcceptSocketSSL() != 0) {
|
||||
Logger::Error("Fail to listen on ssl port %d", m_port_ssl);
|
||||
throw FailedToListenException(m_address, m_port_ssl);
|
||||
@@ -194,6 +304,10 @@ void LibEventServer::dispatch() {
|
||||
}
|
||||
m_responseQueue.close();
|
||||
|
||||
if (m_takeover_agent) {
|
||||
m_takeover_agent->stop();
|
||||
}
|
||||
|
||||
// flushing all remaining events
|
||||
if (RuntimeOption::ServerGracefulShutdownWait) {
|
||||
dispatchWithTimeout(RuntimeOption::ServerGracefulShutdownWait);
|
||||
@@ -315,6 +429,14 @@ bool LibEventServer::enableSSL(int port) {
|
||||
}
|
||||
|
||||
int LibEventServer::getAcceptSocketSSL() {
|
||||
if (m_accept_sock_ssl >= 0) {
|
||||
if (useExistingFd(m_server_ssl, m_accept_sock_ssl, true /*listen*/) != 0) {
|
||||
m_accept_sock_ssl = -1;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *address = m_address.empty() ? nullptr : m_address.c_str();
|
||||
int ret = evhttp_bind_socket_backlog_fd(m_server_ssl, address,
|
||||
m_port_ssl, RuntimeOption::ServerBacklog);
|
||||
@@ -382,7 +504,9 @@ void LibEventServer::onResponse(int worker, evhttp_request *request,
|
||||
int totalSize = 0;
|
||||
|
||||
if (RuntimeOption::LibEventSyncSend && !skip_sync) {
|
||||
const char *reason = HttpProtocol::GetReasonString(code);
|
||||
auto const& reasonStr = transport->getResponseInfo();
|
||||
const char* reason = reasonStr.empty() ? HttpProtocol::GetReasonString(code)
|
||||
: reasonStr.c_str();
|
||||
timespec begin, end;
|
||||
Timer::GetMonotonicTime(begin);
|
||||
#ifdef EVHTTP_SYNC_SEND_REPORT_TOTAL_LEN
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "hphp/runtime/server/job-queue-vm-stack.h"
|
||||
//#include "hphp/util/job-queue.h"
|
||||
//#include "hphp/util/service-data.h"
|
||||
#include "hphp/runtime/server/takeover-agent.h"
|
||||
#include "hphp/runtime/server/server-worker.h"
|
||||
#include "hphp/util/process.h"
|
||||
|
||||
@@ -96,12 +97,12 @@ private:
|
||||
* Implementing an evhttp based HTTP server with JobQueueDispatcher. This
|
||||
* server will have one dispather thread and multiple worker threads.
|
||||
*/
|
||||
class LibEventServer : public Server {
|
||||
class LibEventServer : public Server, public TakeoverAgent::Callback {
|
||||
public:
|
||||
/**
|
||||
* Constructor and destructor.
|
||||
*/
|
||||
LibEventServer(const std::string &address, int port, int thread);
|
||||
explicit LibEventServer(const ServerOptions &options);
|
||||
~LibEventServer();
|
||||
|
||||
// implementing Server
|
||||
@@ -119,6 +120,9 @@ public:
|
||||
}
|
||||
int getLibEventConnectionCount();
|
||||
|
||||
void addTakeoverListener(TakeoverListener* listener);
|
||||
void removeTakeoverListener(TakeoverListener* listener);
|
||||
|
||||
/**
|
||||
* Request handler called by evhttp library.
|
||||
*/
|
||||
@@ -141,6 +145,13 @@ public:
|
||||
*/
|
||||
virtual bool enableSSL(int port);
|
||||
|
||||
/**
|
||||
* TakeoverAgent::Callback
|
||||
*/
|
||||
int onTakeoverRequest(TakeoverAgent::RequestType type);
|
||||
|
||||
void takeoverAborted();
|
||||
|
||||
protected:
|
||||
virtual int getAcceptSocket();
|
||||
virtual int getAcceptSocketSSL();
|
||||
@@ -154,6 +165,8 @@ protected:
|
||||
evhttp *m_server_ssl;
|
||||
int m_port_ssl;
|
||||
|
||||
std::unique_ptr<TakeoverAgent> m_takeover_agent;
|
||||
|
||||
// signal to stop the thread
|
||||
event m_eventStop;
|
||||
CPipe m_pipeStop;
|
||||
@@ -166,6 +179,8 @@ private:
|
||||
};
|
||||
RequestPriority getRequestPriority(struct evhttp_request* request);
|
||||
|
||||
int useExistingFd(evhttp *server, int fd, bool listen);
|
||||
|
||||
static bool certHandler(const std::string &server_name,
|
||||
const std::string& key_file,
|
||||
const std::string& cert_file);
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef incl_HPHP_HTTP_SERVER_SERVER_H_
|
||||
#define incl_HPHP_HTTP_SERVER_SERVER_H_
|
||||
|
||||
#include "hphp/runtime/server/takeover-agent.h"
|
||||
#include "hphp/runtime/server/transport.h"
|
||||
#include "hphp/util/exception.h"
|
||||
#include "hphp/util/lock.h"
|
||||
@@ -91,16 +92,6 @@ private:
|
||||
int m_timeout;
|
||||
};
|
||||
|
||||
/**
|
||||
* A callback to be informed when a server is shutting down because its socket
|
||||
* has been taken over by a new process.
|
||||
*/
|
||||
class TakeoverListener {
|
||||
public:
|
||||
virtual ~TakeoverListener();
|
||||
virtual void takeoverShutdown(Server* server) = 0;
|
||||
};
|
||||
|
||||
typedef std::function<std::unique_ptr<RequestHandler>()> RequestHandlerFactory;
|
||||
typedef std::function<bool(const std::string&)> URLChecker;
|
||||
|
||||
@@ -167,8 +158,8 @@ public:
|
||||
*
|
||||
* This is a no-op for servers that do not support socket takeover.
|
||||
*/
|
||||
virtual void addTakeoverListener(TakeoverListener* lisener) {}
|
||||
virtual void removeTakeoverListener(TakeoverListener* lisener) {}
|
||||
virtual void addTakeoverListener(TakeoverListener* listener) {}
|
||||
virtual void removeTakeoverListener(TakeoverListener* listener) {}
|
||||
|
||||
/**
|
||||
* Add additional worker threads
|
||||
|
||||
+32
-112
@@ -14,7 +14,7 @@
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "hphp/runtime/server/libevent-server-with-takeover.h"
|
||||
#include "hphp/runtime/server/takeover-agent.h"
|
||||
#include "hphp/util/logger.h"
|
||||
#include "hphp/runtime/base/string-util.h"
|
||||
#include "hphp/runtime/ext/ext_string.h"
|
||||
@@ -22,12 +22,12 @@
|
||||
#include <afdt.h>
|
||||
|
||||
/*
|
||||
LibEventServerWithTakeover extends LibEventServer with the ability
|
||||
TakeoverAgent provides the ability
|
||||
to transfer the accept socket (the file descriptor used to accept
|
||||
new connections) from an older instance of the server to a new one
|
||||
that has just been brought up.
|
||||
|
||||
In getAcceptSocket, if binding fails, the server will attempt to
|
||||
In takeover, the agent will attempt to
|
||||
use libafdt to transfer a file descriptor from an existing process
|
||||
(which should exist, since we couldn't bind to the socket).
|
||||
The transfer is performed in a separate event loop that we wait on,
|
||||
@@ -74,22 +74,23 @@ static int fd_transfer_request_handler(
|
||||
uint8_t* response,
|
||||
uint32_t* response_length,
|
||||
void* userdata) {
|
||||
LibEventServerWithTakeover* server = (LibEventServerWithTakeover*)userdata;
|
||||
TakeoverAgent* agent = (TakeoverAgent*)userdata;
|
||||
String req((const char*)request, request_length, CopyString);
|
||||
String resp;
|
||||
int fd = server->afdtRequest(req, &resp);
|
||||
int fd = agent->afdtRequest(req, &resp);
|
||||
assert(resp.size() <= (int)*response_length);
|
||||
memcpy(response, resp.data(), resp.size());
|
||||
*response_length = resp.size();
|
||||
return fd;
|
||||
}
|
||||
|
||||
LibEventServerWithTakeover::LibEventServerWithTakeover
|
||||
(const std::string &address, int port, int thread)
|
||||
: LibEventServer(address, port, thread),
|
||||
m_delete_handle(nullptr),
|
||||
TakeoverAgent::TakeoverAgent(const std::string &fname)
|
||||
: m_delete_handle(nullptr),
|
||||
m_transfer_fname(fname),
|
||||
m_took_over(false),
|
||||
m_takeover_state(TakeoverState::NotStarted)
|
||||
m_takeover_state(TakeoverState::NotStarted),
|
||||
m_sock(-1),
|
||||
m_callback(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -98,25 +99,14 @@ const StaticString
|
||||
s_ver_C_TERM_REQ(P_VERSION C_TERM_REQ),
|
||||
s_ver_C_TERM_OK(P_VERSION C_TERM_OK);
|
||||
|
||||
int LibEventServerWithTakeover::afdtRequest(String request, String* response) {
|
||||
int TakeoverAgent::afdtRequest(String request, String* response) {
|
||||
Logger::Info("takeover: received request");
|
||||
if (request == s_ver_C_FD_REQ) {
|
||||
Logger::Info("takeover: request is a listen socket request");
|
||||
int ret;
|
||||
*response = P_VERSION C_FD_RESP;
|
||||
// Make evhttp forget our copy of the accept socket so we don't accept any
|
||||
// more connections and drop them. Keep the socket open until we get the
|
||||
// shutdown request so that we can still serve AFDT requests (if the new
|
||||
// server crashes or something). The downside is that it will take the LB
|
||||
// longer to figure out that we are broken.
|
||||
ret = evhttp_del_accept_socket(m_server, m_accept_sock);
|
||||
if (ret < 0) {
|
||||
// This will fail if we get a second AFDT request, but the spurious
|
||||
// log message is not too harmful.
|
||||
Logger::Error("Unable to delete accept socket");
|
||||
}
|
||||
m_takeover_state = TakeoverState::Started;
|
||||
return m_accept_sock;
|
||||
(void)m_callback->onTakeoverRequest(RequestType::LISTEN_SOCKET);
|
||||
return m_sock;
|
||||
} else if (request == s_ver_C_TERM_REQ) {
|
||||
Logger::Info("takeover: request is a terminate request");
|
||||
// It is a little bit of a hack to use an AFDT request/response
|
||||
@@ -124,28 +114,9 @@ int LibEventServerWithTakeover::afdtRequest(String request, String* response) {
|
||||
// within the main libevent thread.
|
||||
int ret;
|
||||
*response = P_VERSION C_TERM_BAD;
|
||||
ret = close(m_accept_sock);
|
||||
if (ret < 0) {
|
||||
Logger::Error("Unable to close accept socket");
|
||||
if (m_callback->onTakeoverRequest(RequestType::TERMINATE) != 0) {
|
||||
return -1;
|
||||
}
|
||||
m_accept_sock = -1;
|
||||
|
||||
// Close SSL server
|
||||
if (m_server_ssl) {
|
||||
assert(m_accept_sock_ssl > 0);
|
||||
ret = evhttp_del_accept_socket(m_server_ssl, m_accept_sock_ssl);
|
||||
if (ret < 0) {
|
||||
Logger::Error("Unable to delete accept socket for SSL in evhttp");
|
||||
return -1;
|
||||
}
|
||||
ret = close(m_accept_sock_ssl);
|
||||
if (ret < 0) {
|
||||
Logger::Error("Unable to close accept socket for SSL");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
ret = afdt_close_server(m_delete_handle);
|
||||
if (ret < 0) {
|
||||
Logger::Error("Unable to close afdt server");
|
||||
@@ -159,7 +130,7 @@ int LibEventServerWithTakeover::afdtRequest(String request, String* response) {
|
||||
for (std::set<TakeoverListener*>::iterator it =
|
||||
m_takeover_listeners.begin();
|
||||
it != m_takeover_listeners.end(); ++it) {
|
||||
(*it)->takeoverShutdown(this);
|
||||
(*it)->takeoverShutdown();
|
||||
}
|
||||
Logger::Info("takeover: notification complete");
|
||||
return -1;
|
||||
@@ -170,18 +141,21 @@ int LibEventServerWithTakeover::afdtRequest(String request, String* response) {
|
||||
}
|
||||
}
|
||||
|
||||
void LibEventServerWithTakeover::setupFdServer() {
|
||||
int TakeoverAgent::setupFdServer(event_base *eventBase, int sock,
|
||||
Callback *callback) {
|
||||
int ret;
|
||||
m_sock = sock;
|
||||
m_callback = callback;
|
||||
ret = unlink(m_transfer_fname.c_str());
|
||||
if (ret < 0 && errno != ENOENT) {
|
||||
Logger::Error("Unalbe to unlink '%s': %s",
|
||||
m_transfer_fname.c_str(), folly::errnoStr(errno).c_str());
|
||||
return;
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = afdt_create_server(
|
||||
m_transfer_fname.c_str(),
|
||||
m_eventBase,
|
||||
eventBase,
|
||||
fd_transfer_request_handler,
|
||||
afdt_no_post,
|
||||
fd_transfer_error_hander,
|
||||
@@ -193,31 +167,11 @@ void LibEventServerWithTakeover::setupFdServer() {
|
||||
if (ret >= 0) {
|
||||
Logger::Info("takeover: fd server set up successfully");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int LibEventServerWithTakeover::getAcceptSocket() {
|
||||
int TakeoverAgent::takeover(std::chrono::seconds timeoutSec) {
|
||||
int ret;
|
||||
const char *address = m_address.empty() ? nullptr : m_address.c_str();
|
||||
|
||||
if (m_accept_sock != -1) {
|
||||
Logger::Warning("LibEventServerWithTakeover trying to get a socket, "
|
||||
"but m_accept_sock is not -1. Possibly leaking file descriptors.");
|
||||
m_accept_sock = -1;
|
||||
}
|
||||
|
||||
ret = evhttp_bind_socket_backlog_fd(m_server, address,
|
||||
m_port, RuntimeOption::ServerBacklog);
|
||||
if (ret >= 0) {
|
||||
Logger::Info("takeover: bound directly to port %d", m_port);
|
||||
m_accept_sock = ret;
|
||||
return 0;
|
||||
} else if (errno != EADDRINUSE) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (m_transfer_fname.empty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
Logger::Info("takeover: beginning listen socket acquisition");
|
||||
uint8_t fd_request[3] = P_VERSION C_FD_REQ;
|
||||
@@ -225,21 +179,21 @@ int LibEventServerWithTakeover::getAcceptSocket() {
|
||||
uint32_t response_len = sizeof(fd_response);
|
||||
afdt_error_t err = AFDT_ERROR_T_INIT;
|
||||
// TODO(dreiss): Make this timeout configurable.
|
||||
struct timeval timeout = { 2 , 0 };
|
||||
struct timeval timeout = { timeoutSec.count() , 0 };
|
||||
ret = afdt_sync_client(
|
||||
m_transfer_fname.c_str(),
|
||||
fd_request,
|
||||
sizeof(fd_request) - 1,
|
||||
fd_response,
|
||||
&response_len,
|
||||
&m_accept_sock,
|
||||
&m_sock,
|
||||
&timeout,
|
||||
&err);
|
||||
if (ret < 0) {
|
||||
fd_transfer_error_hander(&err, nullptr);
|
||||
errno = EADDRINUSE;
|
||||
return -1;
|
||||
} else if (m_accept_sock < 0) {
|
||||
} else if (m_sock < 0) {
|
||||
String resp((const char*)fd_response, response_len, CopyString);
|
||||
Logger::Error(
|
||||
"AFDT did not receive a file descriptor: "
|
||||
@@ -252,30 +206,10 @@ int LibEventServerWithTakeover::getAcceptSocket() {
|
||||
Logger::Info("takeover: acquired listen socket");
|
||||
m_took_over = true;
|
||||
|
||||
ret = evhttp_accept_socket(m_server, m_accept_sock);
|
||||
if (ret < 0) {
|
||||
Logger::Error("evhttp_accept_socket: %s",
|
||||
folly::errnoStr(errno).c_str());
|
||||
int errno_save = errno;
|
||||
close(m_accept_sock);
|
||||
m_accept_sock = -1;
|
||||
errno = errno_save;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return m_sock;
|
||||
}
|
||||
|
||||
void LibEventServerWithTakeover::start() {
|
||||
|
||||
if (m_server_ssl) {
|
||||
// Set a flag to prevent parent class from trying to listen to ssl
|
||||
// before the old server releases the port
|
||||
m_accept_sock_ssl = -2;
|
||||
}
|
||||
|
||||
LibEventServer::start();
|
||||
|
||||
void TakeoverAgent::requestShutdown() {
|
||||
if (m_took_over) {
|
||||
Logger::Info("takeover: requesting shutdown of satellites");
|
||||
// Use AFDT to synchronously shut down the old server's satellites
|
||||
@@ -315,19 +249,9 @@ void LibEventServerWithTakeover::start() {
|
||||
Logger::Info("takeover: old satellites have shut down");
|
||||
}
|
||||
}
|
||||
|
||||
if (m_server_ssl) {
|
||||
if (getAcceptSocketSSL() != 0) {
|
||||
Logger::Error("Fail to listen on ssl port %d", m_port_ssl);
|
||||
throw FailedToListenException(m_address, m_port_ssl);
|
||||
}
|
||||
Logger::Info("Listen on ssl port %d",m_port_ssl);
|
||||
}
|
||||
|
||||
setupFdServer();
|
||||
}
|
||||
|
||||
void LibEventServerWithTakeover::stop() {
|
||||
void TakeoverAgent::stop() {
|
||||
if (m_delete_handle != nullptr) {
|
||||
afdt_close_server(m_delete_handle);
|
||||
}
|
||||
@@ -340,13 +264,9 @@ void LibEventServerWithTakeover::stop() {
|
||||
// be safe we close the socket so that if nobody else is listening
|
||||
// the OS starts rejecting requests but if somebody is listening we
|
||||
// let them receive the requests.
|
||||
if (m_takeover_state != TakeoverState::NotStarted &&
|
||||
m_accept_sock != -1) {
|
||||
close(m_accept_sock);
|
||||
m_accept_sock = -1;
|
||||
if (m_takeover_state != TakeoverState::NotStarted) {
|
||||
m_callback->takeoverAborted();
|
||||
}
|
||||
|
||||
LibEventServer::stop();
|
||||
}
|
||||
|
||||
TakeoverListener::~TakeoverListener() {
|
||||
+62
-23
@@ -14,36 +14,73 @@
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef incl_HPHP_HTTP_SERVER_LIB_EVENT_SERVER_WITH_TAKEOVER_H_
|
||||
#define incl_HPHP_HTTP_SERVER_LIB_EVENT_SERVER_WITH_TAKEOVER_H_
|
||||
#ifndef incl_HPHP_HTTP_SERVER_TAKEOVER_AGENT_H_
|
||||
#define incl_HPHP_HTTP_SERVER_TAKEOVER_AGENT_H_
|
||||
|
||||
#include "hphp/runtime/server/libevent-server.h"
|
||||
#include "hphp/runtime/base/complex-types.h"
|
||||
|
||||
#include <event.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* LibEventServer that adds the ability to take over an accept socket
|
||||
* A callback to be informed when a server is shutting down because its socket
|
||||
* has been taken over by a new process.
|
||||
*/
|
||||
class TakeoverListener {
|
||||
public:
|
||||
virtual ~TakeoverListener();
|
||||
virtual void takeoverShutdown() = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Agent with the ability to take over an accept socket
|
||||
* from another process, and give its accept socket up.
|
||||
*/
|
||||
class LibEventServerWithTakeover : public LibEventServer {
|
||||
class TakeoverAgent {
|
||||
public:
|
||||
LibEventServerWithTakeover(const std::string &address, int port, int thread);
|
||||
enum class RequestType {
|
||||
LISTEN_SOCKET,
|
||||
TERMINATE,
|
||||
};
|
||||
|
||||
virtual void stop();
|
||||
class Callback {
|
||||
public:
|
||||
virtual ~Callback() {}
|
||||
// Called by the TakeoverAgent when it receives a request for takeover
|
||||
// Returns non zero on error, which terminates the takeover action
|
||||
virtual int onTakeoverRequest(RequestType type) = 0;
|
||||
|
||||
// Set the name of the file to be used for a Unix domain socket
|
||||
// over which to transfer the accept socket.
|
||||
void setTransferFilename(const std::string &fname) {
|
||||
assert(m_transfer_fname.empty());
|
||||
m_transfer_fname = fname;
|
||||
// Called by the TakeoverAgent when it is shutdown mid-way through a
|
||||
// takeover.
|
||||
virtual void takeoverAborted() = 0;
|
||||
};
|
||||
|
||||
explicit TakeoverAgent(const std::string &fname);
|
||||
|
||||
// execute a takeover and return the fd. -1 if an fd could not be acquired
|
||||
int takeover(std::chrono::seconds timeout = std::chrono::seconds(2));
|
||||
|
||||
// instruct the old server to shutdown
|
||||
void requestShutdown();
|
||||
|
||||
// setup a server to listen for takeover requests
|
||||
int setupFdServer(event_base *eventBase, int sock, Callback *callback);
|
||||
|
||||
// stop the takeover agent, including in-progress takeovers
|
||||
void stop();
|
||||
|
||||
void addTakeoverListener(TakeoverListener* listener) {
|
||||
m_takeover_listeners.insert(listener);
|
||||
}
|
||||
|
||||
virtual void addTakeoverListener(TakeoverListener* lisener) {
|
||||
m_takeover_listeners.insert(lisener);
|
||||
}
|
||||
virtual void removeTakeoverListener(TakeoverListener* lisener) {
|
||||
m_takeover_listeners.erase(lisener);
|
||||
void removeTakeoverListener(TakeoverListener* listener) {
|
||||
m_takeover_listeners.erase(listener);
|
||||
}
|
||||
|
||||
// These are public so they can be called from a C-style callback.
|
||||
@@ -59,10 +96,6 @@ protected:
|
||||
Complete,
|
||||
};
|
||||
|
||||
virtual void start();
|
||||
virtual int getAcceptSocket();
|
||||
|
||||
void setupFdServer();
|
||||
void notifyTakeoverComplete();
|
||||
|
||||
void* m_delete_handle;
|
||||
@@ -74,9 +107,15 @@ protected:
|
||||
|
||||
// The state of taking over this server's socket
|
||||
TakeoverState m_takeover_state;
|
||||
|
||||
// Target socket
|
||||
int m_sock;
|
||||
|
||||
// User callback for events
|
||||
Callback *m_callback;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
#endif // incl_HPHP_HTTP_SERVER_LIB_EVENT_SERVER_H_
|
||||
#endif // incl_HPHP_HTTP_SERVER_TAKEOVER_AGENT_H_
|
||||
@@ -797,7 +797,7 @@ void Transport::onSendEnd() {
|
||||
}
|
||||
auto httpResponseStats = ServiceData::createTimeseries(
|
||||
folly::to<string>(HTTP_RESPONSE_STATS_PREFIX, getResponseCode()),
|
||||
{ServiceData::StatsType::RATE});
|
||||
{ServiceData::StatsType::SUM});
|
||||
httpResponseStats->addValue(1);
|
||||
onSendEndImpl();
|
||||
}
|
||||
|
||||
+26
-10
@@ -782,6 +782,15 @@ template<class Target> Target read_opcode_arg(AsmState& as) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t read_AssertT_arg(AsmState& as) {
|
||||
auto const str = read_opcode_arg<std::string>(as);
|
||||
#define ASSERTT_OP(x) if (str == #x) return static_cast<uint8_t>(AssertTOp::x);
|
||||
ASSERTT_OPS
|
||||
#undef ASSERTT_OP
|
||||
as.error("unknown AssertT operand");
|
||||
NOT_REACHED();
|
||||
}
|
||||
|
||||
const StringData* read_litstr(AsmState& as) {
|
||||
as.in.skipSpaceTab();
|
||||
std::string strVal;
|
||||
@@ -1002,8 +1011,11 @@ OpcodeParserMap opcode_parsers;
|
||||
read_opcode_arg<std::string>(as)))
|
||||
#define IMM_IA as.ue->emitIVA(as.getIterId( \
|
||||
read_opcode_arg<int32_t>(as)))
|
||||
#define IMM_OA as.ue->emitByte( \
|
||||
uint8_t(read_opcode_arg<int32_t>(as))) // TODO op names
|
||||
#define IMM_OA as.ue->emitByte( \
|
||||
(thisOpcode == Op::AssertTL || \
|
||||
thisOpcode == Op::AssertTStk) ? read_AssertT_arg(as) \
|
||||
: uint8_t(read_opcode_arg<int32_t>(as)))
|
||||
// TODO more subop names
|
||||
#define IMM_AA as.ue->emitInt32(as.ue->mergeArray(read_litarray(as)))
|
||||
|
||||
/*
|
||||
@@ -1068,12 +1080,13 @@ OpcodeParserMap opcode_parsers;
|
||||
#define NUM_POP_ONE(a) 1
|
||||
#define NUM_POP_TWO(a,b) 2
|
||||
#define NUM_POP_THREE(a,b,c) 3
|
||||
#define NUM_POP_LMANY vecImmStackValues
|
||||
#define NUM_POP_V_LMANY (1 + vecImmStackValues)
|
||||
#define NUM_POP_R_LMANY (1 + vecImmStackValues)
|
||||
#define NUM_POP_C_LMANY (1 + vecImmStackValues)
|
||||
#define NUM_POP_MMANY vecImmStackValues
|
||||
#define NUM_POP_V_MMANY (1 + vecImmStackValues)
|
||||
#define NUM_POP_R_MMANY (1 + vecImmStackValues)
|
||||
#define NUM_POP_C_MMANY (1 + vecImmStackValues)
|
||||
#define NUM_POP_FMANY immIVA /* number of arguments */
|
||||
#define NUM_POP_CVMANY immIVA /* number of arguments */
|
||||
#define NUM_POP_CVUMANY immIVA /* number of arguments */
|
||||
#define NUM_POP_CMANY immIVA /* number of arguments */
|
||||
|
||||
#define O(name, imm, pop, push, flags) \
|
||||
@@ -1147,12 +1160,13 @@ OPCODES
|
||||
#undef NUM_POP_TWO
|
||||
#undef NUM_POP_THREE
|
||||
#undef NUM_POP_POS_N
|
||||
#undef NUM_POP_LMANY
|
||||
#undef NUM_POP_V_LMANY
|
||||
#undef NUM_POP_R_LMANY
|
||||
#undef NUM_POP_C_LMANY
|
||||
#undef NUM_POP_MMANY
|
||||
#undef NUM_POP_V_MMANY
|
||||
#undef NUM_POP_R_MMANY
|
||||
#undef NUM_POP_C_MMANY
|
||||
#undef NUM_POP_FMANY
|
||||
#undef NUM_POP_CVMANY
|
||||
#undef NUM_POP_CVUMANY
|
||||
#undef NUM_POP_CMANY
|
||||
|
||||
void initialize_opcode_map() {
|
||||
@@ -1971,6 +1985,8 @@ UnitEmitter* assemble_string(const char*code, int codeLen,
|
||||
ue->emitOp(OpString);
|
||||
ue->emitInt32(ue->mergeLitstr(makeStaticString(e.what())));
|
||||
ue->emitOp(OpFatal);
|
||||
ue->emitByte(uint8_t(FatalKind::Runtime));
|
||||
ue->emitByte(false /* skipFrame */);
|
||||
FuncEmitter* fe = ue->getMain();
|
||||
fe->setMaxStackCells(kNumActRecCells + 1);
|
||||
// XXX line numbers are bogus
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
#include "hphp/runtime/base/extended-logger.h"
|
||||
#include "hphp/runtime/base/tracer.h"
|
||||
#include "hphp/runtime/base/memory-profile.h"
|
||||
#include "hphp/runtime/base/runtime-error.h"
|
||||
|
||||
#include "hphp/system/systemlib.h"
|
||||
#include "hphp/runtime/ext/ext_collections.h"
|
||||
@@ -203,6 +204,7 @@ Transl::Translator* tx() {
|
||||
|
||||
#define DECODE_LA(var) DECODE_IVA(var)
|
||||
#define DECODE_IA(var) DECODE_IVA(var)
|
||||
#define DECODE_OA(var) DECODE(unsigned char, var)
|
||||
|
||||
#define DECODE_ITER_LIST(typeList, idList, vecLen) \
|
||||
DECODE(int32_t, vecLen); \
|
||||
@@ -2513,13 +2515,19 @@ typedef RankedCHM<StringData*, HPHP::Unit*,
|
||||
StringDataHashCompare,
|
||||
RankEvaledUnits> EvaledUnitsMap;
|
||||
static EvaledUnitsMap s_evaledUnits;
|
||||
Unit* VMExecutionContext::compileEvalString(StringData* code) {
|
||||
Unit* VMExecutionContext::compileEvalString(
|
||||
StringData* code,
|
||||
const char* evalFilename /* = nullptr */) {
|
||||
EvaledUnitsMap::accessor acc;
|
||||
// Promote this to a static string; otherwise it may get swept
|
||||
// across requests.
|
||||
code = makeStaticString(code);
|
||||
if (s_evaledUnits.insert(acc, code)) {
|
||||
acc->second = compile_string(code->data(), code->size());
|
||||
acc->second = compile_string(
|
||||
code->data(),
|
||||
code->size(),
|
||||
evalFilename
|
||||
);
|
||||
}
|
||||
return acc->second;
|
||||
}
|
||||
@@ -3951,7 +3959,8 @@ OPTBLD_INLINE void VMExecutionContext::iopFatal(PC& pc) {
|
||||
NEXT();
|
||||
TypedValue* top = m_stack.topTV();
|
||||
std::string msg;
|
||||
DECODE_IVA(skipFrame);
|
||||
DECODE_OA(kind_char);
|
||||
DECODE_OA(skipFrame);
|
||||
if (IS_STRING_TYPE(top->m_type)) {
|
||||
msg = top->m_data.pstr->data();
|
||||
} else {
|
||||
@@ -4615,6 +4624,44 @@ IOP_TYPE_CHECK_INSTR(true, Double, is_double)
|
||||
IOP_TYPE_CHECK_INSTR(true, Bool, is_bool)
|
||||
#undef IOP_TYPE_CHECK_INSTR
|
||||
|
||||
OPTBLD_INLINE static void implAssertT(TypedValue* tv, AssertTOp op) {
|
||||
switch (op) {
|
||||
case AssertTOp::Uninit: assert(tv->m_type == KindOfUninit); break;
|
||||
case AssertTOp::InitNull: assert(tv->m_type == KindOfNull); break;
|
||||
case AssertTOp::Int: assert(tv->m_type == KindOfInt64); break;
|
||||
case AssertTOp::Dbl: assert(tv->m_type == KindOfDouble); break;
|
||||
case AssertTOp::Res: assert(tv->m_type == KindOfResource); break;
|
||||
case AssertTOp::Null: assert(IS_NULL_TYPE(tv->m_type)); break;
|
||||
case AssertTOp::Bool: assert(tv->m_type == KindOfBoolean); break;
|
||||
case AssertTOp::Str: assert(IS_STRING_TYPE(tv->m_type)); break;
|
||||
case AssertTOp::Arr: assert(tv->m_type == KindOfArray); break;
|
||||
case AssertTOp::Obj: assert(tv->m_type == KindOfObject); break;
|
||||
|
||||
case AssertTOp::InitUnc: assert(tv->m_type != KindOfUninit);
|
||||
/* fallthrough */
|
||||
case AssertTOp::Unc: assert(!IS_REFCOUNTED_TYPE(tv->m_type) ||
|
||||
(tv->m_type == KindOfString && tv->m_data.pstr->isStatic()) ||
|
||||
(tv->m_type == KindOfArray && tv->m_data.parr->isStatic())); break;
|
||||
|
||||
case AssertTOp::InitCell: assert(tv->m_type != KindOfUninit &&
|
||||
tv->m_type != KindOfRef); break;
|
||||
}
|
||||
}
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopAssertTL(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_LA(localId);
|
||||
DECODE_OA(op);
|
||||
implAssertT(frame_local(m_fp, localId), static_cast<AssertTOp>(op));
|
||||
}
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopAssertTStk(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_IVA(stkSlot);
|
||||
DECODE_OA(op);
|
||||
implAssertT(m_stack.indTV(stkSlot), static_cast<AssertTOp>(op));
|
||||
}
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopEmptyL(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_LA(local);
|
||||
@@ -4830,7 +4877,7 @@ OPTBLD_INLINE void VMExecutionContext::iopSetWithRefRM(PC& pc) {
|
||||
OPTBLD_INLINE void VMExecutionContext::iopSetOpL(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_LA(local);
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
Cell* fr = m_stack.topC();
|
||||
Cell* to = tvToCell(frame_local(m_fp, local));
|
||||
SETOP_BODY_CELL(to, op, fr);
|
||||
@@ -4840,7 +4887,7 @@ OPTBLD_INLINE void VMExecutionContext::iopSetOpL(PC& pc) {
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopSetOpN(PC& pc) {
|
||||
NEXT();
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
StringData* name;
|
||||
Cell* fr = m_stack.topC();
|
||||
TypedValue* tv2 = m_stack.indTV(1);
|
||||
@@ -4858,7 +4905,7 @@ OPTBLD_INLINE void VMExecutionContext::iopSetOpN(PC& pc) {
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopSetOpG(PC& pc) {
|
||||
NEXT();
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
StringData* name;
|
||||
Cell* fr = m_stack.topC();
|
||||
TypedValue* tv2 = m_stack.indTV(1);
|
||||
@@ -4876,7 +4923,7 @@ OPTBLD_INLINE void VMExecutionContext::iopSetOpG(PC& pc) {
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopSetOpS(PC& pc) {
|
||||
NEXT();
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
Cell* fr = m_stack.topC();
|
||||
TypedValue* classref = m_stack.indTV(1);
|
||||
TypedValue* propn = m_stack.indTV(2);
|
||||
@@ -4900,7 +4947,7 @@ OPTBLD_INLINE void VMExecutionContext::iopSetOpS(PC& pc) {
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopSetOpM(PC& pc) {
|
||||
NEXT();
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
DECLARE_SETHELPER_ARGS
|
||||
if (!setHelperPre<MoreWarnings, true, false, false, 1,
|
||||
VectorLeaveCode::LeaveLast>(MEMBERHELPERPRE_ARGS)) {
|
||||
@@ -4941,7 +4988,7 @@ OPTBLD_INLINE void VMExecutionContext::iopSetOpM(PC& pc) {
|
||||
OPTBLD_INLINE void VMExecutionContext::iopIncDecL(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_LA(local);
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
TypedValue* to = m_stack.allocTV();
|
||||
tvWriteUninit(to);
|
||||
TypedValue* fr = frame_local(m_fp, local);
|
||||
@@ -4950,7 +4997,7 @@ OPTBLD_INLINE void VMExecutionContext::iopIncDecL(PC& pc) {
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopIncDecN(PC& pc) {
|
||||
NEXT();
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
StringData* name;
|
||||
TypedValue* nameCell = m_stack.topTV();
|
||||
TypedValue* local = nullptr;
|
||||
@@ -4963,7 +5010,7 @@ OPTBLD_INLINE void VMExecutionContext::iopIncDecN(PC& pc) {
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopIncDecG(PC& pc) {
|
||||
NEXT();
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
StringData* name;
|
||||
TypedValue* nameCell = m_stack.topTV();
|
||||
TypedValue* gbl = nullptr;
|
||||
@@ -4977,7 +5024,7 @@ OPTBLD_INLINE void VMExecutionContext::iopIncDecG(PC& pc) {
|
||||
OPTBLD_INLINE void VMExecutionContext::iopIncDecS(PC& pc) {
|
||||
StringData* name;
|
||||
SPROP_OP_PRELUDE
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
if (!(visible && accessible)) {
|
||||
raise_error("Invalid static property access: %s::%s",
|
||||
clsref->m_data.pcls->name()->data(),
|
||||
@@ -4991,7 +5038,7 @@ OPTBLD_INLINE void VMExecutionContext::iopIncDecS(PC& pc) {
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopIncDecM(PC& pc) {
|
||||
NEXT();
|
||||
DECODE(unsigned char, op);
|
||||
DECODE_OA(op);
|
||||
DECLARE_SETHELPER_ARGS
|
||||
TypedValue to;
|
||||
tvWriteUninit(&to);
|
||||
@@ -6301,9 +6348,32 @@ OPTBLD_INLINE void VMExecutionContext::iopEval(PC& pc) {
|
||||
Cell* c1 = m_stack.topC();
|
||||
String code(prepareKey(c1));
|
||||
String prefixedCode = concat("<?php ", code);
|
||||
Unit* unit = compileEvalString(prefixedCode.get());
|
||||
if (unit == nullptr) {
|
||||
raise_error("Syntax error in eval()");
|
||||
|
||||
auto evalFilename = std::string();
|
||||
Util::string_printf(
|
||||
evalFilename,
|
||||
"%s(%d) : eval()'d code",
|
||||
getContainingFileName().data(),
|
||||
getLine()
|
||||
);
|
||||
Unit* unit = compileEvalString(prefixedCode.get(), evalFilename.c_str());
|
||||
|
||||
const StringData* msg;
|
||||
int line = 0;
|
||||
|
||||
if (unit->parseFatal(msg, line)) {
|
||||
int errnum = static_cast<int>(ErrorConstants::ErrorModes::WARNING);
|
||||
if (errorNeedsLogging(errnum)) {
|
||||
// manual call to Logger instead of logError as we need to use
|
||||
// evalFileName and line as the exception doesn't track the eval()
|
||||
Logger::Error(
|
||||
"HipHop Fatal error: %s in %s on line %d",
|
||||
msg->data(),
|
||||
evalFilename.c_str(),
|
||||
line
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_stack.popC();
|
||||
evalUnit(unit, pc, EventHook::Eval);
|
||||
@@ -6344,7 +6414,7 @@ OPTBLD_INLINE void VMExecutionContext::iopThis(PC& pc) {
|
||||
|
||||
OPTBLD_INLINE void VMExecutionContext::iopBareThis(PC& pc) {
|
||||
NEXT();
|
||||
DECODE(unsigned char, notice);
|
||||
DECODE_OA(notice);
|
||||
if (m_fp->hasThis()) {
|
||||
ObjectData* this_ = m_fp->getThis();
|
||||
m_stack.pushObject(this_);
|
||||
|
||||
@@ -751,6 +751,20 @@ int Func::getDVEntryNumParams(Offset offset) const {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool Func::shouldPGO() const {
|
||||
if (!RuntimeOption::EvalJitPGO) return false;
|
||||
|
||||
// Cloned closures use the func prologue tables to hold the
|
||||
// addresses of the DV funclets, and not real prologues. The
|
||||
// mechanism to retranslate prologues currently assumes that the
|
||||
// prologue tables contain real prologues, so it doesn't properly
|
||||
// handle cloned closures for now. So don't profile & retranslate
|
||||
// them for now.
|
||||
if (isClonedClosure()) return false;
|
||||
|
||||
if (!RuntimeOption::EvalJitPGOHotOnly) return true;
|
||||
return attrs() & AttrHot;
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// FuncEmitter.
|
||||
|
||||
@@ -369,6 +369,8 @@ struct Func {
|
||||
return shared()->m_userAttributes;
|
||||
}
|
||||
|
||||
bool shouldPGO() const;
|
||||
|
||||
/**
|
||||
* Closure's __invoke()s have an extra pointer used to keep cloned versions
|
||||
* of themselves with different contexts.
|
||||
|
||||
+33
-16
@@ -362,12 +362,13 @@ int instrNumPops(const Op* opcode) {
|
||||
#define TWO(...) 2
|
||||
#define THREE(...) 3
|
||||
#define FOUR(...) 4
|
||||
#define LMANY -1
|
||||
#define C_LMANY -2
|
||||
#define V_LMANY -2
|
||||
#define R_LMANY -2
|
||||
#define MMANY -1
|
||||
#define C_MMANY -2
|
||||
#define V_MMANY -2
|
||||
#define R_MMANY -2
|
||||
#define FMANY -3
|
||||
#define CVMANY -3
|
||||
#define CVUMANY -3
|
||||
#define CMANY -3
|
||||
#define O(name, imm, pop, push, flags) pop,
|
||||
OPCODES
|
||||
@@ -376,12 +377,13 @@ int instrNumPops(const Op* opcode) {
|
||||
#undef TWO
|
||||
#undef THREE
|
||||
#undef FOUR
|
||||
#undef LMANY
|
||||
#undef C_LMANY
|
||||
#undef V_LMANY
|
||||
#undef R_LMANY
|
||||
#undef MMANY
|
||||
#undef C_MMANY
|
||||
#undef V_MMANY
|
||||
#undef R_MMANY
|
||||
#undef FMANY
|
||||
#undef CVMANY
|
||||
#undef CVUMANY
|
||||
#undef CMANY
|
||||
#undef O
|
||||
};
|
||||
@@ -500,12 +502,13 @@ FlavorDesc instrInputFlavor(const Op* op, uint32_t idx) {
|
||||
#define TWO(f1, f2) return doFlavor(idx, f1, f2);
|
||||
#define THREE(f1, f2, f3) return doFlavor(idx, f1, f2, f3);
|
||||
#define FOUR(f1, f2, f3, f4) return doFlavor(idx, f1, f2, f3, f4);
|
||||
#define LMANY return minstrFlavor(op, idx, nov);
|
||||
#define C_LMANY return minstrFlavor(op, idx, CV);
|
||||
#define V_LMANY return minstrFlavor(op, idx, VV);
|
||||
#define R_LMANY return minstrFlavor(op, idx, RV);
|
||||
#define MMANY return minstrFlavor(op, idx, nov);
|
||||
#define C_MMANY return minstrFlavor(op, idx, CV);
|
||||
#define V_MMANY return minstrFlavor(op, idx, VV);
|
||||
#define R_MMANY return minstrFlavor(op, idx, RV);
|
||||
#define FMANY return manyFlavor(op, idx, FV);
|
||||
#define CVMANY return manyFlavor(op, idx, CVV);
|
||||
#define CVUMANY return manyFlavor(op, idx, CVUV);
|
||||
#define CMANY return manyFlavor(op, idx, CV);
|
||||
#define O(name, imm, pop, push, flags) case Op::name: pop
|
||||
switch (*op) {
|
||||
@@ -517,12 +520,13 @@ FlavorDesc instrInputFlavor(const Op* op, uint32_t idx) {
|
||||
#undef TWO
|
||||
#undef THREE
|
||||
#undef FOUR
|
||||
#undef LMANY
|
||||
#undef C_LMANY
|
||||
#undef V_LMANY
|
||||
#undef R_LMANY
|
||||
#undef MMANY
|
||||
#undef C_MMANY
|
||||
#undef V_MMANY
|
||||
#undef R_MMANY
|
||||
#undef FMANY
|
||||
#undef CVMANY
|
||||
#undef CVUMANY
|
||||
#undef CMANY
|
||||
#undef O
|
||||
}
|
||||
@@ -785,6 +789,16 @@ std::string instrToString(const Op* it, const Unit* u /* = NULL */) {
|
||||
static const int setopNamesCount =
|
||||
(int)(sizeof(setopNames)/sizeof(const char*));
|
||||
|
||||
auto assertTName = [&] (int immVal) {
|
||||
# define ASSERTT_OP(x) case AssertTOp::x: return #x;
|
||||
switch (static_cast<AssertTOp>(immVal)) {
|
||||
ASSERTT_OPS
|
||||
}
|
||||
# undef ASSERTT_OP
|
||||
assert(false);
|
||||
return "<" "?" ">";
|
||||
};
|
||||
|
||||
std::stringstream out;
|
||||
const Op* iStart = it;
|
||||
Op op = *it;
|
||||
@@ -830,6 +844,9 @@ std::string instrToString(const Op* it, const Unit* u /* = NULL */) {
|
||||
out << ((immVal >=0 && immVal < setopNamesCount) ? \
|
||||
setopNames[immVal] : "?"); \
|
||||
break; \
|
||||
case Op::AssertTL: case Op::AssertTStk: \
|
||||
out << assertTName(immVal); \
|
||||
break; \
|
||||
default: \
|
||||
out << immVal; \
|
||||
break; \
|
||||
|
||||
+49
-22
@@ -68,13 +68,15 @@ union ArgUnion {
|
||||
const Offset InvalidAbsoluteOffset = -1;
|
||||
|
||||
enum FlavorDesc {
|
||||
NOV, // None
|
||||
CV, // Cell
|
||||
VV, // Var
|
||||
AV, // Classref
|
||||
RV, // Return value (cell or var)
|
||||
FV, // Function parameter (cell or var)
|
||||
CVV, // Cell or Var argument
|
||||
NOV, // None
|
||||
CV, // Cell
|
||||
VV, // Var
|
||||
AV, // Classref
|
||||
RV, // Return value (cell or var)
|
||||
FV, // Function parameter (cell or var)
|
||||
UV, // Uninit
|
||||
CVV, // Cell or Var argument
|
||||
CVUV, // Cell, Var, or Uninit argument
|
||||
};
|
||||
|
||||
enum InstrFlags {
|
||||
@@ -305,12 +307,35 @@ enum IncDecOp {
|
||||
IncDec_invalid
|
||||
};
|
||||
|
||||
#define ASSERTT_OPS \
|
||||
ASSERTT_OP(Uninit) \
|
||||
ASSERTT_OP(InitNull) \
|
||||
ASSERTT_OP(Int) \
|
||||
ASSERTT_OP(Dbl) \
|
||||
ASSERTT_OP(Res) \
|
||||
ASSERTT_OP(Null) \
|
||||
ASSERTT_OP(Bool) \
|
||||
ASSERTT_OP(Str) \
|
||||
ASSERTT_OP(Arr) \
|
||||
ASSERTT_OP(Obj) \
|
||||
ASSERTT_OP(InitUnc) \
|
||||
ASSERTT_OP(Unc) \
|
||||
ASSERTT_OP(InitCell)
|
||||
|
||||
enum class AssertTOp : uint8_t {
|
||||
#define ASSERTT_OP(op) op,
|
||||
ASSERTT_OPS
|
||||
#undef ASSERTT_OP
|
||||
};
|
||||
|
||||
enum IterKind {
|
||||
KindOfIter = 0,
|
||||
KindOfMIter = 1,
|
||||
KindOfCIter = 2,
|
||||
};
|
||||
|
||||
enum class FatalKind : uint8_t { Parse = 0, Runtime };
|
||||
|
||||
// Each of the setop ops maps to a binary bytecode op. We have reasons
|
||||
// for using distinct bitwise representations, though. This macro records
|
||||
// their correspondence for mapping either direction.
|
||||
@@ -347,7 +372,7 @@ enum SetOpOp {
|
||||
O(BoxR, NA, ONE(RV), ONE(VV), NF) \
|
||||
O(UnboxR, NA, ONE(RV), ONE(CV), NF) \
|
||||
O(Null, NA, NOV, ONE(CV), NF) \
|
||||
O(NullUninit, NA, NOV, ONE(CV), NF) \
|
||||
O(NullUninit, NA, NOV, ONE(UV), NF) \
|
||||
O(True, NA, NOV, ONE(CV), NF) \
|
||||
O(False, NA, NOV, ONE(CV), NF) \
|
||||
O(Int, ONE(I64A), NOV, ONE(CV), NF) \
|
||||
@@ -404,7 +429,7 @@ enum SetOpOp {
|
||||
O(Print, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(Clone, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(Exit, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(Fatal, ONE(IVA), ONE(CV), NOV, CF_TF) \
|
||||
O(Fatal, TWO(OA,OA), ONE(CV), NOV, CF_TF) \
|
||||
O(Jmp, ONE(BA), NOV, NOV, CF_TF) \
|
||||
O(JmpZ, ONE(BA), ONE(CV), NOV, CF) \
|
||||
O(JmpNZ, ONE(BA), ONE(CV), NOV, CF) \
|
||||
@@ -421,12 +446,12 @@ enum SetOpOp {
|
||||
O(CGetN, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(CGetG, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(CGetS, NA, TWO(AV,CV), ONE(CV), NF) \
|
||||
O(CGetM, ONE(MA), LMANY, ONE(CV), NF) \
|
||||
O(CGetM, ONE(MA), MMANY, ONE(CV), NF) \
|
||||
O(VGetL, ONE(LA), NOV, ONE(VV), NF) \
|
||||
O(VGetN, NA, ONE(CV), ONE(VV), NF) \
|
||||
O(VGetG, NA, ONE(CV), ONE(VV), NF) \
|
||||
O(VGetS, NA, TWO(AV,CV), ONE(VV), NF) \
|
||||
O(VGetM, ONE(MA), LMANY, ONE(VV), NF) \
|
||||
O(VGetM, ONE(MA), MMANY, ONE(VV), NF) \
|
||||
O(AGetC, NA, ONE(CV), ONE(AV), NF) \
|
||||
O(AGetL, ONE(LA), NOV, ONE(AV), NF) \
|
||||
O(AKExists, NA, TWO(CV,CV), ONE(CV), NF) \
|
||||
@@ -434,12 +459,12 @@ enum SetOpOp {
|
||||
O(IssetN, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(IssetG, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(IssetS, NA, TWO(AV,CV), ONE(CV), NF) \
|
||||
O(IssetM, ONE(MA), LMANY, ONE(CV), NF) \
|
||||
O(IssetM, ONE(MA), MMANY, ONE(CV), NF) \
|
||||
O(EmptyL, ONE(LA), NOV, ONE(CV), NF) \
|
||||
O(EmptyN, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(EmptyG, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(EmptyS, NA, TWO(AV,CV), ONE(CV), NF) \
|
||||
O(EmptyM, ONE(MA), LMANY, ONE(CV), NF) \
|
||||
O(EmptyM, ONE(MA), MMANY, ONE(CV), NF) \
|
||||
/* NB: isTypePred depends on this ordering. */ \
|
||||
O(IsNullC, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(IsBoolC, NA, ONE(CV), ONE(CV), NF) \
|
||||
@@ -455,32 +480,34 @@ enum SetOpOp {
|
||||
O(IsStringL, ONE(LA), NOV, ONE(CV), NF) \
|
||||
O(IsArrayL, ONE(LA), NOV, ONE(CV), NF) \
|
||||
O(IsObjectL, ONE(LA), NOV, ONE(CV), NF) \
|
||||
O(AssertTL, TWO(LA,OA), NOV, NOV, NF) \
|
||||
O(AssertTStk, TWO(IVA,OA), NOV, NOV, NF) \
|
||||
O(SetL, ONE(LA), ONE(CV), ONE(CV), NF) \
|
||||
O(SetN, NA, TWO(CV,CV), ONE(CV), NF) \
|
||||
O(SetG, NA, TWO(CV,CV), ONE(CV), NF) \
|
||||
O(SetS, NA, THREE(CV,AV,CV), ONE(CV), NF) \
|
||||
O(SetM, ONE(MA), C_LMANY, ONE(CV), NF) \
|
||||
O(SetWithRefLM, TWO(MA,LA), LMANY, NOV, NF) \
|
||||
O(SetWithRefRM, ONE(MA), R_LMANY, NOV, NF) \
|
||||
O(SetM, ONE(MA), C_MMANY, ONE(CV), NF) \
|
||||
O(SetWithRefLM, TWO(MA,LA), MMANY, NOV, NF) \
|
||||
O(SetWithRefRM, ONE(MA), R_MMANY, NOV, NF) \
|
||||
O(SetOpL, TWO(LA,OA), ONE(CV), ONE(CV), NF) \
|
||||
O(SetOpN, ONE(OA), TWO(CV,CV), ONE(CV), NF) \
|
||||
O(SetOpG, ONE(OA), TWO(CV,CV), ONE(CV), NF) \
|
||||
O(SetOpS, ONE(OA), THREE(CV,AV,CV), ONE(CV), NF) \
|
||||
O(SetOpM, TWO(OA,MA), C_LMANY, ONE(CV), NF) \
|
||||
O(SetOpM, TWO(OA,MA), C_MMANY, ONE(CV), NF) \
|
||||
O(IncDecL, TWO(LA,OA), NOV, ONE(CV), NF) \
|
||||
O(IncDecN, ONE(OA), ONE(CV), ONE(CV), NF) \
|
||||
O(IncDecG, ONE(OA), ONE(CV), ONE(CV), NF) \
|
||||
O(IncDecS, ONE(OA), TWO(AV,CV), ONE(CV), NF) \
|
||||
O(IncDecM, TWO(OA,MA), LMANY, ONE(CV), NF) \
|
||||
O(IncDecM, TWO(OA,MA), MMANY, ONE(CV), NF) \
|
||||
O(BindL, ONE(LA), ONE(VV), ONE(VV), NF) \
|
||||
O(BindN, NA, TWO(VV,CV), ONE(VV), NF) \
|
||||
O(BindG, NA, TWO(VV,CV), ONE(VV), NF) \
|
||||
O(BindS, NA, THREE(VV,AV,CV), ONE(VV), NF) \
|
||||
O(BindM, ONE(MA), V_LMANY, ONE(VV), NF) \
|
||||
O(BindM, ONE(MA), V_MMANY, ONE(VV), NF) \
|
||||
O(UnsetL, ONE(LA), NOV, NOV, NF) \
|
||||
O(UnsetN, NA, ONE(CV), NOV, NF) \
|
||||
O(UnsetG, NA, ONE(CV), NOV, NF) \
|
||||
O(UnsetM, ONE(MA), LMANY, NOV, NF) \
|
||||
O(UnsetM, ONE(MA), MMANY, NOV, NF) \
|
||||
/* NOTE: isFPush below relies on the grouping of FPush* here */ \
|
||||
O(FPushFunc, ONE(IVA), ONE(CV), NOV, NF) \
|
||||
O(FPushFuncD, TWO(IVA,SA), NOV, NOV, NF) \
|
||||
@@ -505,10 +532,10 @@ enum SetOpOp {
|
||||
O(FPassN, ONE(IVA), ONE(CV), ONE(FV), FF) \
|
||||
O(FPassG, ONE(IVA), ONE(CV), ONE(FV), FF) \
|
||||
O(FPassS, ONE(IVA), TWO(AV,CV), ONE(FV), FF) \
|
||||
O(FPassM, TWO(IVA,MA), LMANY, ONE(FV), FF) \
|
||||
O(FPassM, TWO(IVA,MA), MMANY, ONE(FV), FF) \
|
||||
O(FCall, ONE(IVA), FMANY, ONE(RV), CF_FF) \
|
||||
O(FCallArray, NA, ONE(FV), ONE(RV), CF_FF) \
|
||||
O(FCallBuiltin, THREE(IVA,IVA,SA),CVMANY, ONE(RV), CF) \
|
||||
O(FCallBuiltin, THREE(IVA,IVA,SA),CVUMANY, ONE(RV), CF) \
|
||||
O(CufSafeArray, NA, THREE(RV,CV,CV), ONE(CV), NF) \
|
||||
O(CufSafeReturn, NA, THREE(RV,CV,CV), ONE(RV), NF) \
|
||||
O(IterInit, THREE(IA,BA,LA), ONE(CV), NOV, CF) \
|
||||
|
||||
@@ -219,7 +219,7 @@ template<typename L> inline
|
||||
void Block::forEachSrc(unsigned i, L body) {
|
||||
for (Edge& e : m_preds) {
|
||||
IRInstruction* jmp = e.from()->back();
|
||||
assert(jmp->op() == Jmp_ && jmp->taken() == this);
|
||||
assert(jmp->op() == Jmp && jmp->taken() == this);
|
||||
body(jmp, jmp->src(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ DEBUG_ONLY static int numBlockParams(Block* b) {
|
||||
* 6. If the DefLabel produces a value, all of its incoming edges must be from
|
||||
* blocks listed in the block list for this block's Trace.
|
||||
* 7. Any path from this block to a Block that expects values must be
|
||||
* from a Jmp_ instruciton.
|
||||
* from a Jmp instruciton.
|
||||
* 8. Every instruction's BCMarker must point to a valid bytecode instruction.
|
||||
* 9. If this block is a catch block, it must have at most one predecessor
|
||||
* and the trace containing it must contain exactly this block.
|
||||
@@ -129,9 +129,9 @@ bool checkBlock(Block* b) {
|
||||
|
||||
// Invariant #7
|
||||
if (b->taken()) {
|
||||
// only Jmp_ can branch to a join block expecting values.
|
||||
// only Jmp can branch to a join block expecting values.
|
||||
DEBUG_ONLY IRInstruction* branch = b->back();
|
||||
DEBUG_ONLY auto numArgs = branch->op() == Jmp_ ? branch->numSrcs() : 0;
|
||||
DEBUG_ONLY auto numArgs = branch->op() == Jmp ? branch->numSrcs() : 0;
|
||||
assert(numBlockParams(b->taken()) == numArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ PUNT_OPCODE(JmpIsType)
|
||||
PUNT_OPCODE(JmpIsNType)
|
||||
PUNT_OPCODE(JmpZero)
|
||||
PUNT_OPCODE(JmpNZero)
|
||||
PUNT_OPCODE(Jmp_)
|
||||
PUNT_OPCODE(Jmp)
|
||||
PUNT_OPCODE(ReqBindJmpGt)
|
||||
PUNT_OPCODE(ReqBindJmpGte)
|
||||
PUNT_OPCODE(ReqBindJmpLt)
|
||||
@@ -458,10 +458,10 @@ void CodeGenerator::emitTypeTest(Type type, Loc typeSrc, Loc dataSrc,
|
||||
// Note: ARM can actually do better here; it has a fused test-and-branch
|
||||
// instruction. The way this code is factored makes it difficult to use,
|
||||
// though; the jump instruction will be written by some other code.
|
||||
m_as. Cmp (rAsm.W(), KindOfStringBit);
|
||||
m_as. Tst (rAsm.W(), KindOfStringBit);
|
||||
cc = CC_NE;
|
||||
} else if (type.equals(Type::UncountedInit)) {
|
||||
m_as. Cmp (rAsm.W(), KindOfUncountedInitBit);
|
||||
m_as. Tst (rAsm.W(), KindOfUncountedInitBit);
|
||||
cc = CC_NE;
|
||||
} else if (type.equals(Type::Uncounted)) {
|
||||
m_as. Cmp (rAsm.W(), KindOfRefCountThreshold);
|
||||
@@ -486,8 +486,8 @@ void CodeGenerator::emitTypeTest(Type type, Loc typeSrc, Loc dataSrc,
|
||||
doJcc(CC_E);
|
||||
} else if (type.subtypeOf(Type::Arr) && type.hasArrayKind()) {
|
||||
m_as. Ldr (rAsm, dataSrc);
|
||||
m_as. Ldr (rAsm, rAsm[ArrayData::offsetofKind()]);
|
||||
m_as. Cmp (rAsm, type.getArrayKind());
|
||||
m_as. Ldrb (rAsm.W(), rAsm[ArrayData::offsetofKind()]);
|
||||
m_as. Cmp (rAsm.W(), type.getArrayKind());
|
||||
doJcc(CC_E);
|
||||
}
|
||||
}
|
||||
@@ -840,7 +840,8 @@ void CodeGenerator::cgBlock(Block* block, vector<TransBCMapping>* bcMap) {
|
||||
// marker since the last instruction, update the bc mapping.
|
||||
if ((!prevMarker.valid() || inst->marker() != prevMarker) &&
|
||||
m_tx64->isTransDBEnabled() && bcMap) {
|
||||
bcMap->push_back(TransBCMapping{inst->marker().bcOff,
|
||||
bcMap->push_back(TransBCMapping{inst->marker().func->unit()->md5(),
|
||||
inst->marker().bcOff,
|
||||
m_as.frontier(),
|
||||
m_astubs.frontier()});
|
||||
prevMarker = inst->marker();
|
||||
|
||||
@@ -170,6 +170,15 @@ struct IfCountNotStatic {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void emitTransCounterInc(Asm& a) {
|
||||
if (!tx64->isTransDBEnabled()) return;
|
||||
|
||||
a. movq (tx64->getTransCounterAddr(), rAsm);
|
||||
a. lock ();
|
||||
a. incq (*rAsm);
|
||||
}
|
||||
|
||||
void emitIncRef(Asm& as, PhysReg base) {
|
||||
if (RuntimeOption::EvalHHIRGenerateAsserts) {
|
||||
emitAssertRefCount(as, base);
|
||||
|
||||
@@ -49,6 +49,8 @@ void emitEagerSyncPoint(Asm& as, const HPHP::Opcode* pc,
|
||||
void emitEagerVMRegSave(Asm& as, RegSaveFlags flags);
|
||||
void emitGetGContext(Asm& as, PhysReg dest);
|
||||
|
||||
void emitTransCounterInc(Asm& a);
|
||||
|
||||
void emitIncRef(Asm& as, PhysReg base);
|
||||
void emitIncRefCheckNonStatic(Asm& as, PhysReg base, DataType dtype);
|
||||
void emitIncRefGenericRegSafe(Asm& as, PhysReg base, int disp, PhysReg tmpReg);
|
||||
|
||||
@@ -709,42 +709,41 @@ static int64_t shuffleArgs(Asm& a, ArgGroup& args) {
|
||||
argDescs[int(dstReg)] = &args[i];
|
||||
}
|
||||
}
|
||||
std::vector<MoveInfo> howTo;
|
||||
doRegMoves(moves, int(rCgGP), howTo);
|
||||
auto const howTo = doRegMoves(moves, int(rCgGP));
|
||||
|
||||
// Execute the plan
|
||||
for (size_t i = 0; i < howTo.size(); ++i) {
|
||||
if (howTo[i].m_kind == MoveInfo::Kind::Move) {
|
||||
if (howTo[i].m_reg2 == rCgGP) {
|
||||
emitMovRegReg(a, howTo[i].m_reg1, howTo[i].m_reg2);
|
||||
for (auto& how : howTo) {
|
||||
if (how.m_kind == MoveInfo::Kind::Move) {
|
||||
if (how.m_reg2 == rCgGP) {
|
||||
emitMovRegReg(a, how.m_reg1, how.m_reg2);
|
||||
} else {
|
||||
ArgDesc* argDesc = argDescs[int(howTo[i].m_reg2)];
|
||||
ArgDesc* argDesc = argDescs[int(how.m_reg2)];
|
||||
ArgDesc::Kind kind = argDesc->kind();
|
||||
if (kind == ArgDesc::Kind::Reg || kind == ArgDesc::Kind::TypeReg) {
|
||||
if (argDesc->isZeroExtend()) {
|
||||
assert(howTo[i].m_reg1.isGP());
|
||||
assert(howTo[i].m_reg2.isGP());
|
||||
a. movzbl (rbyte(howTo[i].m_reg1), r32(howTo[i].m_reg2));
|
||||
assert(how.m_reg1.isGP());
|
||||
assert(how.m_reg2.isGP());
|
||||
a. movzbl (rbyte(how.m_reg1), r32(how.m_reg2));
|
||||
} else {
|
||||
emitMovRegReg(a, howTo[i].m_reg1, howTo[i].m_reg2);
|
||||
emitMovRegReg(a, how.m_reg1, how.m_reg2);
|
||||
}
|
||||
} else {
|
||||
assert(kind == ArgDesc::Kind::Addr);
|
||||
assert(howTo[i].m_reg1.isGP());
|
||||
assert(howTo[i].m_reg2.isGP());
|
||||
a. lea (howTo[i].m_reg1[argDesc->imm().q()],
|
||||
howTo[i].m_reg2);
|
||||
assert(how.m_reg1.isGP());
|
||||
assert(how.m_reg2.isGP());
|
||||
a. lea (how.m_reg1[argDesc->imm().q()], how.m_reg2);
|
||||
}
|
||||
if (kind != ArgDesc::Kind::TypeReg) {
|
||||
argDesc->markDone();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(howTo[i].m_reg1.isGP());
|
||||
assert(howTo[i].m_reg2.isGP());
|
||||
a. xchgq (howTo[i].m_reg1, howTo[i].m_reg2);
|
||||
assert(how.m_reg1.isGP());
|
||||
assert(how.m_reg2.isGP());
|
||||
a. xchgq (how.m_reg1, how.m_reg2);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle const-to-register moves, type shifting,
|
||||
// load-effective address and zero extending for bools.
|
||||
// Ignore args that have been handled by the
|
||||
@@ -4378,6 +4377,7 @@ void CodeGenerator::cgCheckBounds(IRInstruction* inst) {
|
||||
kVoidDest, SyncOptions::kSyncPoint, args);
|
||||
|
||||
};
|
||||
|
||||
if (idx->isConst()) {
|
||||
int64_t idxVal = idx->getValInt();
|
||||
assert(idxVal >= 0); // we would have punted otherwise
|
||||
@@ -4385,18 +4385,17 @@ void CodeGenerator::cgCheckBounds(IRInstruction* inst) {
|
||||
unlikelyIfBlock(CC_LE, throwHelper);
|
||||
return;
|
||||
}
|
||||
|
||||
auto idxReg = m_regs[idx].reg();
|
||||
m_as.cmpq(0, idxReg);
|
||||
unlikelyIfBlock(CC_L, throwHelper);
|
||||
if (size->isConst()) {
|
||||
int64_t sizeVal = size->getValInt();
|
||||
assert(sizeVal >= 0);
|
||||
m_as.cmpq(sizeVal, m_regs[idx].reg());
|
||||
m_as.cmpq(sizeVal, idxReg);
|
||||
} else {
|
||||
auto sizeReg = m_regs[size].reg();
|
||||
m_as.cmpq(sizeReg, idxReg);
|
||||
}
|
||||
unlikelyIfBlock(CC_GE, throwHelper);
|
||||
unlikelyIfBlock(CC_AE, throwHelper);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgLdVectorSize(IRInstruction* inst) {
|
||||
@@ -5089,7 +5088,8 @@ void CodeGenerator::cgLdClsCached(IRInstruction* inst) {
|
||||
CppCall(func),
|
||||
callDest(inst->dst()),
|
||||
SyncOptions::kSyncPoint,
|
||||
ArgGroup(m_regs).addr(rVmTl, intptr_t(ch)).ssas(inst, 0));
|
||||
ArgGroup(m_regs).addr(rVmTl, intptr_t(ch))
|
||||
.ssa(inst->src(0)));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -5281,7 +5281,7 @@ void CodeGenerator::cgReqBindJmpNZero(IRInstruction* inst) {
|
||||
emitReqBindJcc(CC_NZ, inst->extra<ReqBindJmpNZero>());
|
||||
}
|
||||
|
||||
void CodeGenerator::cgJmp_(IRInstruction* inst) {
|
||||
void CodeGenerator::cgJmp(IRInstruction* inst) {
|
||||
Block* target = inst->taken();
|
||||
if (unsigned n = inst->numSrcs()) {
|
||||
// Parallel-copy sources to the label's destination registers.
|
||||
@@ -5294,7 +5294,7 @@ void CodeGenerator::cgJmp_(IRInstruction* inst) {
|
||||
auto dst = &dsts[i];
|
||||
auto src = srcs[i];
|
||||
// Currently, full XMM registers cannot be assigned to SSATmps
|
||||
// passed from to Jmp_ to DefLabel. If this changes, it'll require
|
||||
// passed from to Jmp to DefLabel. If this changes, it'll require
|
||||
// teaching shuffleArgs() how to handle full XMM values.
|
||||
assert(!m_regs[src].isFullXMM() && !m_regs[dst].isFullXMM());
|
||||
if (m_regs[dst].reg(0) == InvalidReg) continue; // dst is unused.
|
||||
@@ -5316,10 +5316,10 @@ void CodeGenerator::cgJmp_(IRInstruction* inst) {
|
||||
}
|
||||
}
|
||||
assert(args.numStackArgs() == 0 &&
|
||||
"Jmp_ doesn't support passing arguments on the stack yet.");
|
||||
"Jmp doesn't support passing arguments on the stack yet.");
|
||||
shuffleArgs(m_as, args);
|
||||
}
|
||||
if (!m_state.noTerminalJmp_) {
|
||||
if (!m_state.noTerminalJmp) {
|
||||
emitFwdJmp(m_as, target, m_state);
|
||||
}
|
||||
}
|
||||
@@ -5874,7 +5874,7 @@ void CodeGenerator::cgIncStat(IRInstruction *inst) {
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIncTransCounter(IRInstruction* inst) {
|
||||
m_tx64->emitTransCounterInc(m_as);
|
||||
emitTransCounterInc(m_as);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgDbgAssertRefCount(IRInstruction* inst) {
|
||||
@@ -5933,8 +5933,7 @@ void CodeGenerator::cgRBTrace(IRInstruction* inst) {
|
||||
}
|
||||
|
||||
void CodeGenerator::print() const {
|
||||
JIT::print(std::cout, m_unit,
|
||||
&m_state.regs, m_state.lifetime, m_state.asmInfo);
|
||||
JIT::print(std::cout, m_unit, &m_state.regs, nullptr, m_state.asmInfo);
|
||||
}
|
||||
|
||||
static void patchJumps(CodeBlock& cb, CodegenState& state, Block* block) {
|
||||
@@ -5961,7 +5960,8 @@ void CodeGenerator::cgBlock(Block* block, vector<TransBCMapping>* bcMap) {
|
||||
// marker since the last instruction, update the bc mapping.
|
||||
if ((!prevMarker.valid() || inst->marker() != prevMarker) &&
|
||||
m_tx64->isTransDBEnabled() && bcMap) {
|
||||
bcMap->push_back(TransBCMapping{inst->marker().bcOff,
|
||||
bcMap->push_back(TransBCMapping{inst->marker().func->unit()->md5(),
|
||||
inst->marker().bcOff,
|
||||
m_as.frontier(),
|
||||
m_astubs.frontier()});
|
||||
prevMarker = inst->marker();
|
||||
@@ -6004,16 +6004,15 @@ LiveRegs computeLiveRegs(const IRUnit& unit, const RegAllocInfo& regs,
|
||||
return live_regs;
|
||||
}
|
||||
|
||||
void genCode(CodeBlock& mainCode,
|
||||
CodeBlock& stubsCode,
|
||||
IRUnit& unit,
|
||||
vector<TransBCMapping>* bcMap,
|
||||
Transl::TranslatorX64* tx64,
|
||||
const RegAllocInfo& regs,
|
||||
const LifetimeInfo* lifetime,
|
||||
AsmInfo* asmInfo) {
|
||||
void genCodeImpl(CodeBlock& mainCode,
|
||||
CodeBlock& stubsCode,
|
||||
IRUnit& unit,
|
||||
vector<TransBCMapping>* bcMap,
|
||||
Transl::TranslatorX64* tx64,
|
||||
const RegAllocInfo& regs,
|
||||
AsmInfo* asmInfo) {
|
||||
LiveRegs live_regs = computeLiveRegs(unit, regs, unit.entry());
|
||||
CodegenState state(unit, regs, live_regs, lifetime, asmInfo);
|
||||
CodegenState state(unit, regs, live_regs, asmInfo);
|
||||
|
||||
// Returns: whether a block has already been emitted.
|
||||
DEBUG_ONLY auto isEmitted = [&](Block* block) {
|
||||
@@ -6037,10 +6036,10 @@ void genCode(CodeBlock& mainCode,
|
||||
patchJumps(cb, state, block);
|
||||
state.addresses[block] = aStart;
|
||||
|
||||
// If the block ends with a Jmp_ and the next block is going to be
|
||||
// If the block ends with a Jmp and the next block is going to be
|
||||
// its target, we don't need to actually emit it.
|
||||
IRInstruction* last = block->back();
|
||||
state.noTerminalJmp_ = last->op() == Jmp_ && nextBlock == last->taken();
|
||||
state.noTerminalJmp = last->op() == Jmp && nextBlock == last->taken();
|
||||
|
||||
if (state.asmInfo) {
|
||||
state.asmInfo->asmRanges[block] = TcaRange(aStart, cb.frontier());
|
||||
@@ -6096,4 +6095,17 @@ void genCode(CodeBlock& mainCode,
|
||||
}
|
||||
}
|
||||
|
||||
void genCode(CodeBlock& main, CodeBlock& stubs, IRUnit& unit,
|
||||
vector<TransBCMapping>* bcMap,
|
||||
Transl::TranslatorX64* tx64,
|
||||
const RegAllocInfo& regs) {
|
||||
if (dumpIREnabled()) {
|
||||
AsmInfo ai(unit);
|
||||
genCodeImpl(main, stubs, unit, bcMap, tx64, regs, &ai);
|
||||
dumpTrace(kCodeGenLevel, unit, " after code gen ", ®s, nullptr, &ai);
|
||||
} else {
|
||||
genCodeImpl(main, stubs, unit, bcMap, tx64, regs, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -82,13 +82,11 @@ typedef StateVector<IRInstruction, RegSet> LiveRegs;
|
||||
// and address information produced during codegen.
|
||||
struct CodegenState {
|
||||
CodegenState(const IRUnit& unit, const RegAllocInfo& regs,
|
||||
const LiveRegs& liveRegs, const LifetimeInfo* lifetime,
|
||||
AsmInfo* asmInfo)
|
||||
const LiveRegs& liveRegs, AsmInfo* asmInfo)
|
||||
: patches(unit, nullptr)
|
||||
, addresses(unit, nullptr)
|
||||
, regs(regs)
|
||||
, liveRegs(liveRegs)
|
||||
, lifetime(lifetime)
|
||||
, asmInfo(asmInfo)
|
||||
, catches(unit, CatchInfo())
|
||||
{}
|
||||
@@ -98,9 +96,9 @@ struct CodegenState {
|
||||
StateVector<Block,void*> patches;
|
||||
StateVector<Block,TCA> addresses;
|
||||
|
||||
// True if this block's terminal Jmp_ has a desination equal to the
|
||||
// True if this block's terminal Jmp has a desination equal to the
|
||||
// next block in the same assmbler.
|
||||
bool noTerminalJmp_;
|
||||
bool noTerminalJmp;
|
||||
|
||||
// output from register allocator
|
||||
const RegAllocInfo& regs;
|
||||
@@ -110,10 +108,6 @@ struct CodegenState {
|
||||
// registers.
|
||||
const LiveRegs& liveRegs;
|
||||
|
||||
// Optional information used when pretty-printing code after codegen.
|
||||
// when not available, these are nullptrs.
|
||||
const LifetimeInfo* lifetime;
|
||||
|
||||
// Output: start/end ranges of machine code addresses of each instruction.
|
||||
AsmInfo* asmInfo;
|
||||
|
||||
@@ -478,7 +472,7 @@ private:
|
||||
* assert(args.size() == 3);
|
||||
*/
|
||||
struct ArgGroup {
|
||||
typedef std::vector<ArgDesc> ArgVec;
|
||||
typedef smart::vector<ArgDesc> ArgVec;
|
||||
|
||||
explicit ArgGroup(const RegAllocInfo& regs)
|
||||
: m_regs(regs), m_override(nullptr)
|
||||
@@ -525,13 +519,6 @@ struct ArgGroup {
|
||||
return *this;
|
||||
}
|
||||
|
||||
ArgGroup& ssas(IRInstruction* inst, unsigned begin, unsigned count = 1) {
|
||||
for (SSATmp* s : inst->srcs().subpiece(begin, count)) {
|
||||
push_arg(ArgDesc(s, m_regs[s]));
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Pass tmp as a TypedValue passed by value.
|
||||
*/
|
||||
@@ -601,9 +588,7 @@ void genCode(CodeBlock& mainCode,
|
||||
IRUnit& unit,
|
||||
vector<TransBCMapping>* bcMap,
|
||||
TranslatorX64* tx64,
|
||||
const RegAllocInfo& regs,
|
||||
const LifetimeInfo* lifetime = nullptr,
|
||||
AsmInfo* asmInfo = nullptr);
|
||||
const RegAllocInfo& regs);
|
||||
|
||||
}}
|
||||
|
||||
|
||||
@@ -189,14 +189,14 @@ BlockList removeUnreachable(IRTrace* trace, IRUnit& unit) {
|
||||
continue;
|
||||
}
|
||||
FTRACE(5, "erasing block {}\n", (*bit)->id());
|
||||
if ((*bit)->taken() && (*bit)->back()->op() == Jmp_) {
|
||||
if ((*bit)->taken() && (*bit)->back()->op() == Jmp) {
|
||||
needsReflow = true;
|
||||
}
|
||||
bit = t->erase(bit);
|
||||
}
|
||||
});
|
||||
|
||||
// 3. if we removed any whole blocks that ended in Jmp_
|
||||
// 3. if we removed any whole blocks that ended in Jmp
|
||||
// instructions, reflow all types in case they change the
|
||||
// incoming types of DefLabel instructions.
|
||||
if (needsReflow) reflowTypes(blocks.front(), blocks);
|
||||
@@ -215,7 +215,7 @@ initInstructions(const BlockList& blocks, DceState& state) {
|
||||
state[inst].setLive();
|
||||
wl.push_back(&inst);
|
||||
}
|
||||
if (inst.op() == DecRefNZ) {
|
||||
if (RuntimeOption::EvalHHIREnableRefCountOpt && inst.op() == DecRefNZ) {
|
||||
auto* srcInst = inst.src(0)->inst();
|
||||
Opcode srcOpc = srcInst->op();
|
||||
if (srcOpc != DefConst) {
|
||||
@@ -314,7 +314,7 @@ void optimizeRefCount(IRTrace* trace, IRTrace* main, DceState& state,
|
||||
void sinkIncRefs(IRTrace* trace, IRUnit& unit, DceState& state) {
|
||||
assert(trace->isMain());
|
||||
|
||||
assert(trace->back()->back()->op() != Jmp_);
|
||||
assert(trace->back()->back()->op() != Jmp);
|
||||
|
||||
auto copyPropTrace = [] (IRTrace* trace) {
|
||||
forEachInst(trace->blocks(), copyProp);
|
||||
@@ -681,20 +681,22 @@ void eliminateDeadCode(IRUnit& unit) {
|
||||
|
||||
// If inst consumes this source, find the true source instruction and
|
||||
// mark it as consumed if it's an IncRef.
|
||||
if (inst->consumesReference(i)) {
|
||||
if (RuntimeOption::EvalHHIREnableRefCountOpt && inst->consumesReference(i)) {
|
||||
consumeIncRef(inst, src, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize IncRefs and DecRefs.
|
||||
forEachTrace(unit, [&](IRTrace* t) {
|
||||
optimizeRefCount(t, unit.main(), state, uses);
|
||||
});
|
||||
if (RuntimeOption::EvalHHIREnableRefCountOpt) {
|
||||
forEachTrace(unit, [&](IRTrace* t) {
|
||||
optimizeRefCount(t, unit.main(), state, uses);
|
||||
});
|
||||
|
||||
if (RuntimeOption::EvalHHIREnableSinking) {
|
||||
// Sink IncRefs consumed off trace.
|
||||
sinkIncRefs(trace, unit, state);
|
||||
if (RuntimeOption::EvalHHIREnableSinking) {
|
||||
// Sink IncRefs consumed off trace.
|
||||
sinkIncRefs(trace, unit, state);
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize unused inlined activation records. It's not necessary
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010-2013 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 "hphp/runtime/vm/jit/func-prologues-x64.h"
|
||||
|
||||
#include "hphp/util/asm-x64.h"
|
||||
|
||||
#include "hphp/runtime/ext/ext_closure.h"
|
||||
#include "hphp/runtime/vm/func.h"
|
||||
#include "hphp/runtime/vm/srckey.h"
|
||||
#include "hphp/runtime/vm/jit/code-gen-helpers-x64.h"
|
||||
#include "hphp/runtime/vm/jit/translator-x64.h"
|
||||
#include "hphp/runtime/vm/jit/translator-x64-internal.h"
|
||||
#include "hphp/runtime/vm/jit/write-lease.h"
|
||||
|
||||
namespace HPHP { namespace JIT { namespace X64 {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
using Transl::TCA;
|
||||
|
||||
TRACE_SET_MOD(tx64);
|
||||
|
||||
constexpr auto kJcc8Len = 3;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace {
|
||||
|
||||
void emitStackCheck(int funcDepth, Offset pc) {
|
||||
using namespace reg;
|
||||
Asm a { tx64->mainCode };
|
||||
funcDepth += kStackCheckPadding * sizeof(Cell);
|
||||
|
||||
uint64_t stackMask = cellsToBytes(RuntimeOption::EvalVMStackElms) - 1;
|
||||
a. mov_reg64_reg64(rVmSp, rAsm); // copy to destroy
|
||||
a. and_imm64_reg64(stackMask, rAsm);
|
||||
a. sub_imm64_reg64(funcDepth + Stack::sSurprisePageSize, rAsm);
|
||||
// Unlikely branch to failure.
|
||||
a. jl(tx64->uniqueStubs.stackOverflowHelper);
|
||||
// Success.
|
||||
}
|
||||
|
||||
TCA emitFuncGuard(X64Assembler& a, const Func* func) {
|
||||
using namespace reg;
|
||||
assert(kScratchCrossTraceRegs.contains(rax));
|
||||
assert(kScratchCrossTraceRegs.contains(rdx));
|
||||
|
||||
const int kAlign = kX64CacheLineSize;
|
||||
const int kAlignMask = kAlign - 1;
|
||||
int loBits = uintptr_t(a.frontier()) & kAlignMask;
|
||||
int delta, size;
|
||||
|
||||
// Ensure the immediate is safely smashable
|
||||
// the immediate must not cross a qword boundary,
|
||||
if (!deltaFits((intptr_t)func, sz::dword)) {
|
||||
size = 8;
|
||||
delta = loBits + kFuncMovImm;
|
||||
} else {
|
||||
size = 4;
|
||||
delta = loBits + kFuncCmpImm;
|
||||
}
|
||||
|
||||
delta = (delta + size - 1) & kAlignMask;
|
||||
if (delta < size - 1) {
|
||||
a.emitNop(size - 1 - delta);
|
||||
}
|
||||
|
||||
TCA aStart DEBUG_ONLY = a.frontier();
|
||||
if (!deltaFits((intptr_t)func, sz::dword)) {
|
||||
a. load_reg64_disp_reg64(rStashedAR, AROFF(m_func), rax);
|
||||
/*
|
||||
Although func doesnt fit in a signed 32-bit immediate, it may still
|
||||
fit in an unsigned one. Rather than deal with yet another case
|
||||
(which only happens when we disable jemalloc) just force it to
|
||||
be an 8-byte immediate, and patch it up afterwards.
|
||||
*/
|
||||
a. mov_imm64_reg(0xdeadbeeffeedface, rdx);
|
||||
assert(((uint64_t*)a.frontier())[-1] == 0xdeadbeeffeedface);
|
||||
((uint64_t*)a.frontier())[-1] = uintptr_t(func);
|
||||
a. cmp_reg64_reg64(rax, rdx);
|
||||
} else {
|
||||
a. cmp_imm32_disp_reg32(uint64_t(func), AROFF(m_func), rStashedAR);
|
||||
}
|
||||
|
||||
assert(tx64->uniqueStubs.funcPrologueRedispatch);
|
||||
|
||||
a. jnz(tx64->uniqueStubs.funcPrologueRedispatch);
|
||||
|
||||
assert(funcPrologueToGuard(a.frontier(), func) == aStart);
|
||||
assert(funcPrologueHasGuard(a.frontier(), func));
|
||||
return a.frontier();
|
||||
}
|
||||
|
||||
// Initialize at most this many locals inline in function body prologue; more
|
||||
// than this, and emitting a loop is more compact. To be precise, the actual
|
||||
// crossover point in terms of code size is 6; 9 was determined by experiment to
|
||||
// be the optimal point in certain benchmarks. #microoptimization
|
||||
constexpr auto kLocalsToInitializeInline = 9;
|
||||
|
||||
SrcKey emitPrologueWork(Func* func, int nPassed) {
|
||||
using Transl::Location;
|
||||
using namespace reg;
|
||||
|
||||
int numParams = func->numParams();
|
||||
const Func::ParamInfoVec& paramInfo = func->params();
|
||||
|
||||
Offset dvInitializer = InvalidAbsoluteOffset;
|
||||
|
||||
assert(IMPLIES(func->isGenerator(), nPassed == numParams));
|
||||
|
||||
Asm a { tx64->mainCode };
|
||||
|
||||
if (tx64->mode() == TransProflogue) {
|
||||
assert(func->shouldPGO());
|
||||
TransID transId = tx64->profData()->curTransID();
|
||||
auto counterAddr = tx64->profData()->transCounterAddr(transId);
|
||||
a.movq(counterAddr, rAsm);
|
||||
a.decq(rAsm[0]);
|
||||
}
|
||||
|
||||
if (nPassed > numParams) {
|
||||
// Too many args; a weird case, so just callout. Stash ar
|
||||
// somewhere callee-saved.
|
||||
if (false) { // typecheck
|
||||
Transl::trimExtraArgs((ActRec*)nullptr);
|
||||
}
|
||||
a. mov_reg64_reg64(rStashedAR, argNumToRegName[0]);
|
||||
emitCall(a, TCA(Transl::trimExtraArgs));
|
||||
// We'll fix rVmSp below.
|
||||
} else if (nPassed < numParams) {
|
||||
// Figure out which, if any, default value initializer to go to
|
||||
for (int i = nPassed; i < numParams; ++i) {
|
||||
const Func::ParamInfo& pi = paramInfo[i];
|
||||
if (pi.hasDefaultValue()) {
|
||||
dvInitializer = pi.funcletOff();
|
||||
break;
|
||||
}
|
||||
}
|
||||
TRACE(1, "Only have %d of %d args; getting dvFunclet\n",
|
||||
nPassed, numParams);
|
||||
a. emitImmReg(nPassed, rax);
|
||||
// do { *(--rVmSp) = NULL; nPassed++; } while (nPassed < numParams);
|
||||
// This should be an unusual case, so optimize for code density
|
||||
// rather than execution speed; i.e., don't unroll the loop.
|
||||
TCA loopTop = a.frontier();
|
||||
a. sub_imm32_reg64(sizeof(Cell), rVmSp);
|
||||
a. incl(eax);
|
||||
emitStoreUninitNull(a, 0, rVmSp);
|
||||
a. cmp_imm32_reg32(numParams, rax);
|
||||
a. jcc8(CC_L, loopTop);
|
||||
}
|
||||
|
||||
// Entry point for numParams == nPassed is here.
|
||||
// Args are kosher. Frame linkage: set fp = ar.
|
||||
a. mov_reg64_reg64(rStashedAR, rVmFp);
|
||||
|
||||
int numLocals = numParams;
|
||||
if (func->isClosureBody()) {
|
||||
// Closure object properties are the use vars followed by the
|
||||
// static locals (which are per-instance).
|
||||
int numUseVars = func->cls()->numDeclProperties() -
|
||||
func->numStaticLocals();
|
||||
|
||||
emitLea(a, rVmFp[-cellsToBytes(numParams)], rVmSp);
|
||||
|
||||
PhysReg rClosure = rcx;
|
||||
a. loadq(rVmFp[AROFF(m_this)], rClosure);
|
||||
|
||||
// Swap in the $this or late bound class
|
||||
a. loadq(rClosure[c_Closure::ctxOffset()], rAsm);
|
||||
a. storeq(rAsm, rVmFp[AROFF(m_this)]);
|
||||
|
||||
if (!(func->attrs() & AttrStatic)) {
|
||||
a.shrq(1, rAsm);
|
||||
JccBlock<CC_BE> ifRealThis(a);
|
||||
a.shlq(1, rAsm);
|
||||
// XXX can objects be static?
|
||||
emitIncRefCheckNonStatic(a, rAsm, KindOfObject);
|
||||
}
|
||||
|
||||
// Put in the correct context
|
||||
a. loadq(rClosure[c_Closure::funcOffset()], rAsm);
|
||||
a. storeq(rAsm, rVmFp[AROFF(m_func)]);
|
||||
|
||||
// Copy in all the use vars
|
||||
int baseUVOffset = sizeof(ObjectData) + func->cls()->builtinPropSize();
|
||||
for (int i = 0; i < numUseVars + 1; i++) {
|
||||
int spOffset = -cellsToBytes(i+1);
|
||||
|
||||
if (i == 0) {
|
||||
// The closure is the first local.
|
||||
// We don't incref because it used to be $this
|
||||
// and now it is a local, so they cancel out
|
||||
emitStoreTypedValue(a, KindOfObject, rClosure, spOffset, rVmSp);
|
||||
continue;
|
||||
}
|
||||
|
||||
int uvOffset = baseUVOffset + cellsToBytes(i-1);
|
||||
|
||||
emitCopyTo(a, rClosure, uvOffset, rVmSp, spOffset, rAsm);
|
||||
emitIncRefGenericRegSafe(a, rVmSp, spOffset, rAsm);
|
||||
}
|
||||
|
||||
numLocals += numUseVars + 1;
|
||||
}
|
||||
|
||||
// We're in the callee frame; initialize locals. Unroll the loop all
|
||||
// the way if there are a modest number of locals to update;
|
||||
// otherwise, do it in a compact loop. If we're in a generator body,
|
||||
// named locals will be initialized by UnpackCont so we can leave
|
||||
// them alone here.
|
||||
int numUninitLocals = func->numLocals() - numLocals;
|
||||
assert(numUninitLocals >= 0);
|
||||
if (numUninitLocals > 0 && !func->isGenerator()) {
|
||||
|
||||
// If there are too many locals, then emitting a loop to initialize locals
|
||||
// is more compact, rather than emitting a slew of movs inline.
|
||||
if (numUninitLocals > kLocalsToInitializeInline) {
|
||||
PhysReg loopReg = rcx;
|
||||
|
||||
// rVmFp + rcx points to the count/type fields of the TypedValue we're
|
||||
// about to write to.
|
||||
int loopStart = -func->numLocals() * sizeof(TypedValue) + TVOFF(m_type);
|
||||
int loopEnd = -numLocals * sizeof(TypedValue) + TVOFF(m_type);
|
||||
|
||||
a. emitImmReg(loopStart, loopReg);
|
||||
a. emitImmReg(KindOfUninit, rdx);
|
||||
|
||||
TCA topOfLoop = a.frontier();
|
||||
// do {
|
||||
// rVmFp[loopReg].m_type = KindOfUninit;
|
||||
// } while(++loopReg != loopEnd);
|
||||
|
||||
emitStoreTVType(a, edx, rVmFp[loopReg]);
|
||||
a. addq (sizeof(Cell), loopReg);
|
||||
a. cmpq (loopEnd, loopReg);
|
||||
a. jcc8 (CC_NE, topOfLoop);
|
||||
} else {
|
||||
PhysReg base;
|
||||
int disp, k;
|
||||
static_assert(KindOfUninit == 0, "");
|
||||
if (numParams < func->numLocals()) {
|
||||
a.xorl (eax, eax);
|
||||
}
|
||||
for (k = numLocals; k < func->numLocals(); ++k) {
|
||||
locToRegDisp(Location(Location::Local, k), &base, &disp, func);
|
||||
emitStoreTVType(a, eax, base[disp + TVOFF(m_type)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HPHP::Opcode* destPC = func->unit()->entry() + func->base();
|
||||
if (dvInitializer != InvalidAbsoluteOffset) {
|
||||
// dispatch to funclet.
|
||||
destPC = func->unit()->entry() + dvInitializer;
|
||||
}
|
||||
SrcKey funcBody(func, destPC);
|
||||
|
||||
// Move rVmSp to the right place: just past all locals
|
||||
int frameCells = func->numSlotsInFrame();
|
||||
if (func->isGenerator()) {
|
||||
frameCells = 1;
|
||||
} else {
|
||||
emitLea(a, rVmFp[-cellsToBytes(frameCells)], rVmSp);
|
||||
}
|
||||
|
||||
Fixup fixup(funcBody.offset() - func->base(), frameCells);
|
||||
|
||||
// Emit warnings for any missing arguments
|
||||
if (!func->info() && !(func->attrs() & AttrNative)) {
|
||||
for (int i = nPassed; i < numParams; ++i) {
|
||||
if (paramInfo[i].funcletOff() == InvalidAbsoluteOffset) {
|
||||
a. emitImmReg((intptr_t)func->name()->data(), argNumToRegName[0]);
|
||||
a. emitImmReg(numParams, argNumToRegName[1]);
|
||||
a. emitImmReg(i, argNumToRegName[2]);
|
||||
emitCall(a, (TCA)raiseMissingArgument);
|
||||
tx64->fixupMap().recordFixup(a.frontier(), fixup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check surprise flags in the same place as the interpreter: after
|
||||
// setting up the callee's frame but before executing any of its
|
||||
// code
|
||||
emitCheckSurpriseFlagsEnter(tx64->mainCode, tx64->stubsCode, false,
|
||||
tx64->fixupMap(), fixup);
|
||||
|
||||
if (func->isClosureBody() && func->cls()) {
|
||||
int entry = nPassed <= numParams ? nPassed : numParams + 1;
|
||||
// Relying on rStashedAR == rVmFp here
|
||||
a. loadq (rStashedAR[AROFF(m_func)], rax);
|
||||
a. loadq (rax[Func::prologueTableOff() + sizeof(TCA)*entry],
|
||||
rax);
|
||||
a. jmp (rax);
|
||||
} else {
|
||||
emitBindJmp(tx64->mainCode, tx64->stubsCode, funcBody);
|
||||
}
|
||||
return funcBody;
|
||||
}
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
TCA emitCallArrayPrologue(Func* func, DVFuncletsVec& dvs) {
|
||||
auto& mainCode = tx64->mainCode;
|
||||
auto& stubsCode = tx64->stubsCode;
|
||||
Asm a { mainCode };
|
||||
Asm astubs { stubsCode };
|
||||
TCA start = mainCode.frontier();
|
||||
if (dvs.size() == 1) {
|
||||
a. cmp_imm32_disp_reg32(dvs[0].first,
|
||||
AROFF(m_numArgsAndCtorFlag), rVmFp);
|
||||
emitBindJcc(mainCode, stubsCode, CC_LE, SrcKey(func, dvs[0].second));
|
||||
emitBindJmp(mainCode, stubsCode, SrcKey(func, func->base()));
|
||||
} else {
|
||||
a. load_reg64_disp_reg32(rVmFp, AROFF(m_numArgsAndCtorFlag), reg::rax);
|
||||
for (unsigned i = 0; i < dvs.size(); i++) {
|
||||
a. cmp_imm32_reg32(dvs[i].first, reg::rax);
|
||||
emitBindJcc(mainCode, stubsCode, CC_LE, SrcKey(func, dvs[i].second));
|
||||
}
|
||||
emitBindJmp(mainCode, stubsCode, SrcKey(func, func->base()));
|
||||
}
|
||||
return start;
|
||||
}
|
||||
|
||||
SrcKey emitFuncPrologue(CodeBlock& mainCode, CodeBlock& stubsCode,
|
||||
Func* func, bool funcIsMagic, int nPassed,
|
||||
TCA& start, TCA& aStart) {
|
||||
Asm a { mainCode };
|
||||
|
||||
// Guard: we're in the right callee. This happens in magicStart for
|
||||
// magic callees.
|
||||
if (!funcIsMagic) {
|
||||
start = aStart = emitFuncGuard(a, func);
|
||||
}
|
||||
|
||||
emitRB(a, Trace::RBTypeFuncPrologueTry, func->fullName()->data());
|
||||
|
||||
// NB: We have most of the register file to play with, since we know
|
||||
// we're between BB's. So, we hardcode some registers here rather
|
||||
// than using the scratch allocator.
|
||||
TRACE(2, "funcPrologue: user function: %s\n", func->name()->data());
|
||||
|
||||
// Add a counter for the translation if requested
|
||||
if (RuntimeOption::EvalJitTransCounters) {
|
||||
emitTransCounterInc(a);
|
||||
}
|
||||
|
||||
if (!funcIsMagic) {
|
||||
emitPopRetIntoActRec(a);
|
||||
// entry point for magic methods comes later
|
||||
emitRB(a, Trace::RBTypeFuncEntry, func->fullName()->data());
|
||||
|
||||
/*
|
||||
* Guard: we have stack enough stack space to complete this
|
||||
* function. We omit overflow checks if it is a leaf function
|
||||
* that can't use more than kStackCheckLeafPadding cells.
|
||||
*/
|
||||
auto const needStackCheck =
|
||||
!(func->attrs() & AttrPhpLeafFn) ||
|
||||
func->maxStackCells() >= kStackCheckLeafPadding;
|
||||
if (needStackCheck) {
|
||||
emitStackCheck(cellsToBytes(func->maxStackCells()), func->base());
|
||||
}
|
||||
}
|
||||
|
||||
SrcKey skFuncBody = emitPrologueWork(func, nPassed);
|
||||
|
||||
if (funcIsMagic) {
|
||||
// entry points for magic methods is here
|
||||
TCA magicStart = emitFuncGuard(a, func);
|
||||
emitPopRetIntoActRec(a);
|
||||
emitRB(a, Trace::RBTypeFuncEntry, func->fullName()->data());
|
||||
// Guard: we have stack enough stack space to complete this function.
|
||||
emitStackCheck(cellsToBytes(func->maxStackCells()), func->base());
|
||||
assert(func->numParams() == 2);
|
||||
// Special __call prologue
|
||||
a. mov_reg64_reg64(rStashedAR, argNumToRegName[0]);
|
||||
emitCall(a, TCA(Transl::shuffleArgsForMagicCall));
|
||||
if (memory_profiling) {
|
||||
tx64->fixupMap().recordFixup(
|
||||
a.frontier(),
|
||||
Fixup(skFuncBody.offset() - func->base(), func->numSlotsInFrame())
|
||||
);
|
||||
}
|
||||
// if shuffleArgs returns 0, that means this was not a magic call
|
||||
// and we should proceed to a prologue specialized for nPassed;
|
||||
// otherwise, proceed to a prologue specialized for nPassed==numParams (2).
|
||||
if (nPassed == 2) {
|
||||
a.jmp(start);
|
||||
} else {
|
||||
a.test_reg64_reg64(reg::rax, reg::rax);
|
||||
// z ==> not a magic call, go to prologue for nPassed
|
||||
if (deltaFits(start - (a.frontier() + kJcc8Len), sz::byte)) {
|
||||
a.jcc8(CC_Z, start);
|
||||
} else {
|
||||
a.jcc(CC_Z, start);
|
||||
}
|
||||
// this was a magic call
|
||||
// nPassed == 2
|
||||
// Fix up hardware stack pointer
|
||||
nPassed = 2;
|
||||
emitLea(a, rStashedAR[-cellsToBytes(nPassed)], rVmSp);
|
||||
// Optimization TODO: Reuse the prologue for args == 2
|
||||
emitPrologueWork(func, nPassed);
|
||||
}
|
||||
start = magicStart;
|
||||
}
|
||||
|
||||
return skFuncBody;
|
||||
}
|
||||
|
||||
}}}
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010-2013 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. |
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
#ifndef incl_HPHP_JIT_FUNC_PROLOGUES_X64_H
|
||||
#define incl_HPHP_JIT_FUNC_PROLOGUES_X64_H
|
||||
|
||||
#include "hphp/util/asm-x64.h"
|
||||
#include "hphp/runtime/vm/jit/translator-x64.h"
|
||||
#include "hphp/runtime/vm/jit/types.h"
|
||||
|
||||
namespace HPHP {
|
||||
|
||||
struct ActRec;
|
||||
class Func;
|
||||
|
||||
namespace JIT { namespace X64 {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The funcGuard gets skipped and patched by other code, so we have some
|
||||
// magic offsets.
|
||||
constexpr auto kFuncMovImm = 6; // Offset to the immediate for 8 byte Func*
|
||||
constexpr auto kFuncCmpImm = 4; // Offset to the immediate for 4 byte Func*
|
||||
constexpr auto kFuncGuardLen = 23;
|
||||
constexpr auto kFuncGuardShortLen = 14;
|
||||
|
||||
template<typename T>
|
||||
T* funcPrologueToGuardImm(Transl::TCA prologue) {
|
||||
assert(arch() == Arch::X64);
|
||||
assert(sizeof(T) == 4 || sizeof(T) == 8);
|
||||
T* retval = (T*)(prologue - (sizeof(T) == 8 ?
|
||||
kFuncGuardLen - kFuncMovImm :
|
||||
kFuncGuardShortLen - kFuncCmpImm));
|
||||
// We padded these so the immediate would fit inside a cache line
|
||||
assert(((uintptr_t(retval) ^ (uintptr_t(retval + 1) - 1)) &
|
||||
~(kX64CacheLineSize - 1)) == 0);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
inline bool funcPrologueHasGuard(Transl::TCA prologue, const Func* func) {
|
||||
assert(arch() == Arch::X64);
|
||||
intptr_t iptr = uintptr_t(func);
|
||||
if (deltaFits(iptr, sz::dword)) {
|
||||
return *funcPrologueToGuardImm<int32_t>(prologue) == iptr;
|
||||
}
|
||||
return *funcPrologueToGuardImm<int64_t>(prologue) == iptr;
|
||||
}
|
||||
|
||||
inline TCA funcPrologueToGuard(TCA prologue, const Func* func) {
|
||||
assert(arch() == Arch::X64);
|
||||
if (!prologue || prologue == tx64->uniqueStubs.fcallHelperThunk) {
|
||||
return prologue;
|
||||
}
|
||||
return prologue -
|
||||
(deltaFits(uintptr_t(func), sz::dword) ?
|
||||
kFuncGuardShortLen :
|
||||
kFuncGuardLen);
|
||||
}
|
||||
|
||||
inline void funcPrologueSmashGuard(Transl::TCA prologue, const Func* func) {
|
||||
intptr_t iptr = uintptr_t(func);
|
||||
if (deltaFits(iptr, sz::dword)) {
|
||||
*funcPrologueToGuardImm<int32_t>(prologue) = 0;
|
||||
return;
|
||||
}
|
||||
*funcPrologueToGuardImm<int64_t>(prologue) = 0;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
Transl::TCA emitCallArrayPrologue(Func* func, DVFuncletsVec& dvs);
|
||||
|
||||
SrcKey emitFuncPrologue(CodeBlock& mainCode, CodeBlock& stubsCode,
|
||||
Func* func, bool funcIsMagic, int nPassed,
|
||||
TCA& start, TCA& aStart);
|
||||
|
||||
}}}
|
||||
|
||||
#endif
|
||||
@@ -87,7 +87,7 @@ static void setupAfterPrologue(ActRec* fp) {
|
||||
TCA fcallHelper(ActRec* ar) {
|
||||
try {
|
||||
TCA tca =
|
||||
Translator::Get()->funcPrologue((Func*)ar->m_func, ar->numArgs(), ar);
|
||||
tx64->getFuncPrologue((Func*)ar->m_func, ar->numArgs(), ar);
|
||||
if (tca) {
|
||||
return tca;
|
||||
}
|
||||
|
||||
@@ -1327,7 +1327,7 @@ void HhbcTranslator::emitIterBreak(const ImmVector& iv,
|
||||
}
|
||||
|
||||
if (!breakTracelet) return;
|
||||
gen(Jmp_, makeExit(offset));
|
||||
gen(Jmp, makeExit(offset));
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitCreateCont(Id funNameStrId) {
|
||||
@@ -1885,7 +1885,7 @@ void HhbcTranslator::emitJmp(int32_t offset,
|
||||
emitJmpSurpriseCheck();
|
||||
}
|
||||
if (!breakTracelet) return;
|
||||
gen(Jmp_, makeExit(offset));
|
||||
gen(Jmp, makeExit(offset));
|
||||
}
|
||||
|
||||
SSATmp* HhbcTranslator::emitJmpCondHelper(int32_t offset,
|
||||
@@ -2852,13 +2852,13 @@ void HhbcTranslator::emitSwitch(const ImmVector& iv,
|
||||
}
|
||||
|
||||
if (type.subtypeOf(Type::Null)) {
|
||||
gen(Jmp_, makeExit(zeroOff));
|
||||
gen(Jmp, makeExit(zeroOff));
|
||||
return;
|
||||
}
|
||||
if (type.subtypeOf(Type::Bool)) {
|
||||
Offset nonZeroOff = bcOff() + iv.vec32()[iv.size() - 2];
|
||||
gen(JmpNZero, makeExit(nonZeroOff), switchVal);
|
||||
gen(Jmp_, makeExit(zeroOff));
|
||||
gen(Jmp, makeExit(zeroOff));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2884,7 +2884,7 @@ void HhbcTranslator::emitSwitch(const ImmVector& iv,
|
||||
index = gen(LdSwitchObjIndex, catchBlock, switchVal, ssabase, ssatargets);
|
||||
} else if (type.subtypeOf(Type::Arr)) {
|
||||
gen(DecRef, switchVal);
|
||||
gen(Jmp_, makeExit(defaultOff));
|
||||
gen(Jmp, makeExit(defaultOff));
|
||||
return;
|
||||
} else {
|
||||
PUNT(Switch-UnknownType);
|
||||
@@ -3215,7 +3215,7 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
|
||||
if (tc.isTypeVar()) {
|
||||
return;
|
||||
}
|
||||
if (tc.nullable() && locType.isNull()) {
|
||||
if (tc.isNullable() && locType.isNull()) {
|
||||
return;
|
||||
}
|
||||
if (tc.isCallable()) {
|
||||
@@ -3455,12 +3455,14 @@ bool isSupportedAGet(SSATmp* classSrc, const StringData* clsName) {
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitAGet(SSATmp* classSrc, const StringData* clsName) {
|
||||
auto const catchBlock = makeCatch();
|
||||
|
||||
if (classSrc->isA(Type::Str)) {
|
||||
push(gen(LdCls, classSrc, cns(curClass())));
|
||||
push(gen(LdCls, catchBlock, classSrc, cns(curClass())));
|
||||
} else if (classSrc->isA(Type::Obj)) {
|
||||
push(gen(LdObjClass, classSrc));
|
||||
} else if (clsName) {
|
||||
push(gen(LdCls, cns(clsName), cns(curClass())));
|
||||
push(gen(LdCls, catchBlock, cns(clsName), cns(curClass())));
|
||||
} else {
|
||||
not_reached();
|
||||
}
|
||||
@@ -3732,6 +3734,33 @@ void HhbcTranslator::emitCeil() {
|
||||
push(gen(Ceil, dblVal));
|
||||
}
|
||||
|
||||
static Type assertOpToType(AssertTOp op) {
|
||||
switch (op) {
|
||||
case AssertTOp::Uninit: return Type::Uninit;
|
||||
case AssertTOp::InitNull: return Type::InitNull;
|
||||
case AssertTOp::Int: return Type::Int;
|
||||
case AssertTOp::Dbl: return Type::Dbl;
|
||||
case AssertTOp::Res: return Type::Res;
|
||||
case AssertTOp::Null: return Type::Null;
|
||||
case AssertTOp::Bool: return Type::Bool;
|
||||
case AssertTOp::Str: return Type::Str;
|
||||
case AssertTOp::Arr: return Type::Arr;
|
||||
case AssertTOp::Obj: return Type::Obj;
|
||||
case AssertTOp::InitUnc: return Type::UncountedInit;
|
||||
case AssertTOp::Unc: return Type::Uncounted;
|
||||
case AssertTOp::InitCell: return Type::Cell - Type::Uninit;
|
||||
}
|
||||
not_reached();
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitAssertTL(int32_t id, AssertTOp op) {
|
||||
assertTypeLocal(id, assertOpToType(op));
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitAssertTStk(int32_t offset, AssertTOp op) {
|
||||
assertTypeStack(offset, assertOpToType(op));
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitAbs() {
|
||||
auto value = popC();
|
||||
|
||||
|
||||
@@ -343,8 +343,11 @@ struct HhbcTranslator {
|
||||
void emitRetC(bool freeInline);
|
||||
void emitRetV(bool freeInline);
|
||||
|
||||
// miscelaneous ops
|
||||
void emitFloor();
|
||||
void emitCeil();
|
||||
void emitAssertTL(int32_t id, AssertTOp op);
|
||||
void emitAssertTStk(int32_t offset, AssertTOp op);
|
||||
|
||||
// binary arithmetic ops
|
||||
void emitAdd();
|
||||
|
||||
@@ -141,15 +141,14 @@ bool IRInstruction::mayRaiseError() const {
|
||||
|
||||
bool IRInstruction::isEssential() const {
|
||||
Opcode opc = op();
|
||||
if (opc == DecRefNZ) {
|
||||
if (is(IncRef, IncRefCtx, DecRefNZ, DecRefNZOrBranch)) {
|
||||
// If the ref count optimization is turned off, mark all refcounting
|
||||
// operations as essential.
|
||||
if (!RuntimeOption::EvalHHIREnableRefCountOpt) return true;
|
||||
|
||||
// If the source of a DecRefNZ is not an IncRef, mark it as essential
|
||||
// because we won't remove its source as well as itself.
|
||||
// If the ref count optimization is turned off, mark all DecRefNZ as
|
||||
// essential.
|
||||
if (!RuntimeOption::EvalHHIREnableRefCountOpt ||
|
||||
src(0)->inst()->op() != IncRef) {
|
||||
return true;
|
||||
}
|
||||
if (is(DecRefNZ) && !src(0)->inst()->is(IncRef)) return true;
|
||||
}
|
||||
return isControlFlow() ||
|
||||
opcodeHasFlags(opc, Essential) ||
|
||||
@@ -335,7 +334,7 @@ void IRInstruction::convertToNop() {
|
||||
void IRInstruction::convertToJmp() {
|
||||
assert(isControlFlow());
|
||||
assert(IMPLIES(block(), block()->back() == this));
|
||||
m_op = Jmp_;
|
||||
m_op = Jmp;
|
||||
m_typeParam = Type::None;
|
||||
m_numSrcs = 0;
|
||||
m_numDsts = 0;
|
||||
|
||||
@@ -57,6 +57,11 @@ struct BCMarker {
|
||||
|
||||
std::string show() const;
|
||||
bool valid() const;
|
||||
|
||||
SrcKey sk() const {
|
||||
assert(valid());
|
||||
return SrcKey { func, bcOff };
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -217,10 +222,6 @@ struct IRInstruction {
|
||||
Type typeParam() const { return m_typeParam; }
|
||||
void setTypeParam(Type t) { m_typeParam = t; }
|
||||
uint32_t numSrcs() const { return m_numSrcs; }
|
||||
void setNumSrcs(uint32_t i) {
|
||||
assert(i <= m_numSrcs);
|
||||
m_numSrcs = i;
|
||||
}
|
||||
SSATmp* src(uint32_t i) const;
|
||||
void setSrc(uint32_t i, SSATmp* newSrc);
|
||||
SrcRange srcs() const {
|
||||
|
||||
@@ -244,7 +244,7 @@ IRTranslator::translateLtGtOp(const NormalizedInstruction& i) {
|
||||
|
||||
DataType leftType = i.inputs[0]->outerType();
|
||||
DataType rightType = i.inputs[1]->outerType();
|
||||
bool ok = HPHP::TypeConstraint::equivDataTypes(leftType, rightType) &&
|
||||
bool ok = equivDataTypes(leftType, rightType) &&
|
||||
(i.inputs[0]->isNull() ||
|
||||
leftType == KindOfBoolean ||
|
||||
i.inputs[0]->isInt());
|
||||
@@ -463,6 +463,14 @@ IRTranslator::translateCeil(const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(Ceil);
|
||||
}
|
||||
|
||||
void IRTranslator::translateAssertTL(const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(AssertTL, i.imm[0].u_LA, static_cast<AssertTOp>(i.imm[1].u_OA));
|
||||
}
|
||||
|
||||
void IRTranslator::translateAssertTStk(const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(AssertTStk, i.imm[0].u_IVA, static_cast<AssertTOp>(i.imm[1].u_OA));
|
||||
}
|
||||
|
||||
void
|
||||
IRTranslator::translateAddNewElemC(const NormalizedInstruction& i) {
|
||||
assert(i.inputs.size() == 2);
|
||||
@@ -1652,10 +1660,11 @@ static Type flavorToType(FlavorDesc f) {
|
||||
switch (f) {
|
||||
case NOV: not_reached();
|
||||
|
||||
case CV: return Type::Cell;
|
||||
case CV: return Type::Cell; // TODO(#3029148) this could be Cell - Uninit
|
||||
case UV: return Type::Uninit;
|
||||
case VV: return Type::BoxedCell;
|
||||
case AV: return Type::Cls;
|
||||
case RV: case FV: case CVV: return Type::Gen;
|
||||
case RV: case FV: case CVV: case CVUV: return Type::Gen;
|
||||
}
|
||||
not_reached();
|
||||
}
|
||||
|
||||
@@ -300,7 +300,7 @@ O(JmpIsNType, D(None), SUnk, E) \
|
||||
/* name dstinfo srcinfo flags */ \
|
||||
O(JmpZero, D(None), SNum, E) \
|
||||
O(JmpNZero, D(None), SNum, E) \
|
||||
O(Jmp_, D(None), SUnk, T|E) \
|
||||
O(Jmp, D(None), SUnk, T|E) \
|
||||
O(ReqBindJmpGt, ND, S(Gen) S(Gen), T|E) \
|
||||
O(ReqBindJmpGte, ND, S(Gen) S(Gen), T|E) \
|
||||
O(ReqBindJmpLt, ND, S(Gen) S(Gen), T|E) \
|
||||
@@ -889,7 +889,6 @@ typename std::enable_if<
|
||||
return std::is_same<T,bool>::value ? Type::Bool : Type::Int;
|
||||
}
|
||||
|
||||
inline Type typeForConst(const StringData*) { return Type::StaticStr; }
|
||||
inline Type typeForConst(const NamedEntity*) { return Type::NamedEntity; }
|
||||
inline Type typeForConst(const Func*) { return Type::Func; }
|
||||
inline Type typeForConst(const Class*) { return Type::Cls; }
|
||||
@@ -899,10 +898,14 @@ inline Type typeForConst(double) { return Type::Dbl; }
|
||||
inline Type typeForConst(SetOpOp) { return Type::Int; }
|
||||
inline Type typeForConst(IncDecOp) { return Type::Int; }
|
||||
inline Type typeForConst(std::nullptr_t) { return Type::Nullptr; }
|
||||
|
||||
inline Type typeForConst(const StringData* sd) {
|
||||
assert(sd->isStatic());
|
||||
return Type::StaticStr;
|
||||
}
|
||||
inline Type typeForConst(const ArrayData* ad) {
|
||||
assert(ad->isStatic());
|
||||
// TODO: Task #2124292, Reintroduce StaticArr
|
||||
return Type::Arr;
|
||||
return Type::StaticArr;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -208,7 +208,7 @@ void eliminateUnconditionalJump(IRUnit& unit) {
|
||||
Block* lastBlock = trace->back();
|
||||
auto lastInst = lastBlock->backIter(); // iterator to last instruction
|
||||
IRInstruction& jmp = *lastInst;
|
||||
if (jmp.op() == Jmp_ && jmp.taken()->numPreds() == 1) {
|
||||
if (jmp.op() == Jmp && jmp.taken()->numPreds() == 1) {
|
||||
Block* target = jmp.taken();
|
||||
lastBlock->splice(lastInst, target, target->skipHeader(), target->end(),
|
||||
lastInst->marker());
|
||||
|
||||
@@ -153,9 +153,10 @@ void smashJcc(TCA jccAddr, TCA newDest) {
|
||||
- X64::kJmpImmBytes);
|
||||
*deltaAddr = newDelta;
|
||||
} else {
|
||||
// This offset is asserted in emitSmashableJump. We wrote four instructions.
|
||||
// Then the jump destination was written at the next 8-byte boundary.
|
||||
auto dataPtr = jccAddr + 16;
|
||||
// This offset is asserted in emitSmashableJump. We wrote three
|
||||
// instructions. Then the jump destination was written at the next 8-byte
|
||||
// boundary.
|
||||
auto dataPtr = jccAddr + 12;
|
||||
if ((uintptr_t(dataPtr) & 7) != 0) {
|
||||
dataPtr += 4;
|
||||
assert((uintptr_t(dataPtr) & 7) == 0);
|
||||
@@ -189,8 +190,7 @@ void emitSmashableJump(CodeBlock& cb, Transl::TCA dest,
|
||||
// 26-bit jump offsets (not big enough). It does, however, entail an
|
||||
// indirect jump.
|
||||
if (cc == CC_None) {
|
||||
a. Adr (ARM::rAsm, &targetData);
|
||||
a. Ldr (ARM::rAsm, ARM::rAsm[0]);
|
||||
a. Ldr (ARM::rAsm, &targetData);
|
||||
a. Br (ARM::rAsm);
|
||||
if (!cb.isFrontierAligned(8)) {
|
||||
a. Nop ();
|
||||
@@ -200,12 +200,11 @@ void emitSmashableJump(CodeBlock& cb, Transl::TCA dest,
|
||||
a. dc64 (reinterpret_cast<int64_t>(dest));
|
||||
|
||||
// If this assert breaks, you need to change smashJmp
|
||||
assert(targetData.target() == start + 12 ||
|
||||
targetData.target() == start + 16);
|
||||
assert(targetData.target() == start + 8 ||
|
||||
targetData.target() == start + 12);
|
||||
} else {
|
||||
a. B (&afterData, InvertCondition(ARM::convertCC(cc)));
|
||||
a. Adr (ARM::rAsm, &targetData);
|
||||
a. Ldr (ARM::rAsm, ARM::rAsm[0]);
|
||||
a. Ldr (ARM::rAsm, &targetData);
|
||||
a. Br (ARM::rAsm);
|
||||
if (!cb.isFrontierAligned(8)) {
|
||||
a. Nop ();
|
||||
@@ -216,8 +215,8 @@ void emitSmashableJump(CodeBlock& cb, Transl::TCA dest,
|
||||
a. bind (&afterData);
|
||||
|
||||
// If this assert breaks, you need to change smashJcc
|
||||
assert(targetData.target() == start + 16 ||
|
||||
targetData.target() == start + 20);
|
||||
assert(targetData.target() == start + 12 ||
|
||||
targetData.target() == start + 16);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,13 +228,13 @@ Transl::TCA jmpTarget(Transl::TCA jmp) {
|
||||
if (jmp[0] != 0xe9) return nullptr;
|
||||
return jmp + 5 + ((int32_t*)(jmp + 5))[-1];
|
||||
} else if (arch() == Arch::ARM) {
|
||||
// This doesn't verify that each of the three or four instructions that make
|
||||
// This doesn't verify that each of the two or three instructions that make
|
||||
// up this sequence matches; just the first one and the indirect jump.
|
||||
using namespace vixl;
|
||||
Instruction* adr = Instruction::Cast(jmp);
|
||||
if (adr->Bit(31) != 0 || adr->Bits(28, 24) != 0x10) return nullptr;
|
||||
Instruction* ldr = Instruction::Cast(jmp);
|
||||
if (ldr->Bits(31, 24) != 0x58) return nullptr;
|
||||
|
||||
Instruction* br = Instruction::Cast(jmp + 8);
|
||||
Instruction* br = Instruction::Cast(jmp + 4);
|
||||
if (br->Bits(31, 10) != 0x3587C0 || br->Bits(5, 0) != 0) return nullptr;
|
||||
|
||||
uintptr_t dest = reinterpret_cast<uintptr_t>(jmp + 8);
|
||||
@@ -258,10 +257,10 @@ Transl::TCA jccTarget(Transl::TCA jmp) {
|
||||
Instruction* b = Instruction::Cast(jmp);
|
||||
if (b->Bits(31, 24) != 0x54 || b->Bit(4) != 0) return nullptr;
|
||||
|
||||
Instruction* br = Instruction::Cast(jmp + 12);
|
||||
Instruction* br = Instruction::Cast(jmp + 8);
|
||||
if (br->Bits(31, 10) != 0x3587C0 || br->Bits(5, 0) != 0) return nullptr;
|
||||
|
||||
uintptr_t dest = reinterpret_cast<uintptr_t>(jmp + 12);
|
||||
uintptr_t dest = reinterpret_cast<uintptr_t>(jmp + 8);
|
||||
if ((dest & 7) != 0) {
|
||||
dest += 4;
|
||||
assert((dest & 7) == 0);
|
||||
|
||||
@@ -27,7 +27,6 @@ constexpr int kJmpLen = 5;
|
||||
constexpr int kCallLen = 5;
|
||||
constexpr int kJmpccLen = 6;
|
||||
constexpr int kJmpImmBytes = 4;
|
||||
constexpr int kJcc8Len = 3;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -60,7 +60,7 @@ struct LinearScan : private boost::noncopyable {
|
||||
static const int NumRegs = kNumRegs;
|
||||
|
||||
explicit LinearScan(IRUnit&);
|
||||
RegAllocInfo allocRegs(LifetimeInfo*);
|
||||
RegAllocInfo allocRegs();
|
||||
|
||||
private:
|
||||
class RegState {
|
||||
@@ -204,7 +204,7 @@ private:
|
||||
// stores pre-coloring hints
|
||||
PreColoringHint m_preColoringHint;
|
||||
|
||||
// a map from SSATmp* to a list of Jmp_ instructions that have it as
|
||||
// a map from SSATmp* to a list of Jmp instructions that have it as
|
||||
// a source.
|
||||
typedef smart::vector<IRInstruction*> JmpList;
|
||||
StateVector<SSATmp, JmpList> m_jmps;
|
||||
@@ -757,7 +757,7 @@ void LinearScan::collectInfo(BlockList::iterator it, IRTrace* trace) {
|
||||
}
|
||||
|
||||
IRInstruction* jmp = block->back();
|
||||
if (jmp->op() == Jmp_ && jmp->numSrcs() != 0) {
|
||||
if (jmp->op() == Jmp && jmp->numSrcs() != 0) {
|
||||
for (SSATmp* src : jmp->srcs()) {
|
||||
m_jmps[src].push_back(jmp);
|
||||
}
|
||||
@@ -872,7 +872,7 @@ void LinearScan::computePreColoringHint() {
|
||||
}
|
||||
|
||||
// Given a label, dest index for that label, and register index, scan
|
||||
// the sources of all incoming Jmp_s to see if any have a register
|
||||
// the sources of all incoming Jmps to see if any have a register
|
||||
// allocated at the specified index.
|
||||
static RegNumber findLabelSrcReg(const RegAllocInfo& regs, IRInstruction* label,
|
||||
unsigned dstIdx, uint32_t regIndex) {
|
||||
@@ -886,8 +886,8 @@ static RegNumber findLabelSrcReg(const RegAllocInfo& regs, IRInstruction* label,
|
||||
|
||||
// This function attempts to find a pre-coloring hint from two
|
||||
// different sources: If tmp comes from a DefLabel, it will scan up to
|
||||
// the SSATmps providing values to incoming Jmp_s to look for a
|
||||
// hint. If tmp is consumed by a Jmp_, look for other incoming Jmp_s
|
||||
// the SSATmps providing values to incoming Jmps to look for a
|
||||
// hint. If tmp is consumed by a Jmp, look for other incoming Jmps
|
||||
// to its destination and see if any of them have already been given a
|
||||
// register. If all of these fail, let normal register allocation
|
||||
// proceed unhinted.
|
||||
@@ -911,7 +911,7 @@ RegNumber LinearScan::getJmpPreColor(SSATmp* tmp, uint32_t regIndex,
|
||||
auto reg = findLabelSrcReg(m_allocInfo, srcInst, i, regIndex);
|
||||
// Until we handle loops, it's a bug to try and allocate a
|
||||
// register to a DefLabel's dest before all of its incoming
|
||||
// Jmp_s have had their srcs allocated, unless the incoming
|
||||
// Jmps have had their srcs allocated, unless the incoming
|
||||
// block is unreachable.
|
||||
const DEBUG_ONLY bool unreachable =
|
||||
std::find(m_blocks.begin(), m_blocks.end(),
|
||||
@@ -923,19 +923,19 @@ RegNumber LinearScan::getJmpPreColor(SSATmp* tmp, uint32_t regIndex,
|
||||
not_reached();
|
||||
}
|
||||
|
||||
// If srcInst wasn't a label, check if tmp is used by any Jmp_
|
||||
// instructions. If it is, trace to the Jmp_'s label and use the
|
||||
// If srcInst wasn't a label, check if tmp is used by any Jmp
|
||||
// instructions. If it is, trace to the Jmp's label and use the
|
||||
// same procedure as above.
|
||||
for (unsigned ji = 0, jn = jmps.size(); ji < jn; ++ji) {
|
||||
IRInstruction* jmp = jmps[ji];
|
||||
IRInstruction* label = jmp->taken()->front();
|
||||
|
||||
// Figure out which src of the Jmp_ is tmp
|
||||
// Figure out which src of the Jmp is tmp
|
||||
for (unsigned si = 0, sn = jmp->numSrcs(); si < sn; ++si) {
|
||||
SSATmp* src = jmp->src(si);
|
||||
if (tmp == src) {
|
||||
// For now, a DefLabel should never have a register assigned
|
||||
// to it before any of its incoming Jmp_ instructions.
|
||||
// to it before any of its incoming Jmp instructions.
|
||||
always_assert(m_allocInfo[label->dst(si)].reg(regIndex) ==
|
||||
reg::noreg);
|
||||
auto reg = findLabelSrcReg(m_allocInfo, label, si, regIndex);
|
||||
@@ -1075,7 +1075,7 @@ void LinearScan::findFullXMMCandidates() {
|
||||
m_fullXMMCandidates -= notCandidates;
|
||||
}
|
||||
|
||||
RegAllocInfo LinearScan::allocRegs(LifetimeInfo* lifetime) {
|
||||
RegAllocInfo LinearScan::allocRegs() {
|
||||
if (RuntimeOption::EvalHHIREnableCoalescing) {
|
||||
// <coalesce> doesn't need instruction numbering.
|
||||
coalesce();
|
||||
@@ -1104,9 +1104,9 @@ RegAllocInfo LinearScan::allocRegs(LifetimeInfo* lifetime) {
|
||||
|
||||
if (m_slots.size()) genSpillStats(numSpillLocs);
|
||||
|
||||
if (lifetime) {
|
||||
lifetime->linear = std::move(m_linear);
|
||||
lifetime->uses = std::move(m_uses);
|
||||
if (dumpIREnabled()) {
|
||||
dumpTrace(kRegAllocLevel, m_unit, " after reg alloc ", &m_allocInfo,
|
||||
&m_lifetime, nullptr, nullptr);
|
||||
}
|
||||
return m_allocInfo;
|
||||
}
|
||||
@@ -1451,8 +1451,8 @@ void LinearScan::PreColoringHint::add(SSATmp* tmp, uint32_t index, int argNum) {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
RegAllocInfo allocRegsForUnit(IRUnit& unit, LifetimeInfo* lifetime) {
|
||||
return LinearScan(unit).allocRegs(lifetime);
|
||||
RegAllocInfo allocRegsForUnit(IRUnit& unit) {
|
||||
return LinearScan(unit).allocRegs();
|
||||
}
|
||||
|
||||
}} // HPHP::JIT
|
||||
|
||||
@@ -206,7 +206,7 @@ inline std::ostream& operator<<(std::ostream& os, SpillInfo si) {
|
||||
* The main entry point for register allocation. Called prior to code
|
||||
* generation.
|
||||
*/
|
||||
RegAllocInfo allocRegsForUnit(IRUnit&, LifetimeInfo* = nullptr);
|
||||
RegAllocInfo allocRegsForUnit(IRUnit&);
|
||||
|
||||
// Native stack layout:
|
||||
// | |
|
||||
|
||||
@@ -683,27 +683,32 @@ HhbcTranslator::MInstrTranslator::simpleCollectionOp() {
|
||||
}
|
||||
} else if (baseType.strictSubtypeOf(Type::Obj)) {
|
||||
const Class* klass = baseType.getClass();
|
||||
if (klass == c_Vector::classof() ||
|
||||
klass == c_Pair::classof()) {
|
||||
auto const isVector = klass == c_Vector::classof();
|
||||
auto const isPair = klass == c_Pair::classof();
|
||||
auto const isMap = klass == c_Map::classof();
|
||||
auto const isStableMap = klass == c_StableMap::classof();
|
||||
|
||||
if (isVector || isPair) {
|
||||
if (mcodeMaybeVectorKey(m_ni.immVecM[0])) {
|
||||
SSATmp* key = getInput(m_mii.valCount() + 1, DataTypeGeneric);
|
||||
if (key->isA(Type::Int)) {
|
||||
return (klass == c_Vector::classof()) ?
|
||||
SimpleOp::Vector : SimpleOp::Pair;
|
||||
// We don't specialize setting pair elements.
|
||||
if (isPair && op == OpSetM) return SimpleOp::None;
|
||||
|
||||
return isVector ? SimpleOp::Vector : SimpleOp::Pair;
|
||||
}
|
||||
}
|
||||
} else if (klass == c_Map::classof() ||
|
||||
klass == c_StableMap::classof()) {
|
||||
} else if (isMap || isStableMap) {
|
||||
if (mcodeMaybeArrayOrMapKey(m_ni.immVecM[0])) {
|
||||
SSATmp* key = getInput(m_mii.valCount() + 1, DataTypeGeneric);
|
||||
if (key->isA(Type::Int) || key->isA(Type::Str)) {
|
||||
return (klass == c_Map::classof()) ?
|
||||
SimpleOp::Map : SimpleOp::StableMap;
|
||||
return isMap ? SimpleOp::Map : SimpleOp::StableMap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SimpleOp::None;
|
||||
}
|
||||
|
||||
@@ -1721,7 +1726,7 @@ void HhbcTranslator::MInstrTranslator::emitVectorGet(SSATmp* key) {
|
||||
PUNT(emitVectorGet);
|
||||
}
|
||||
SSATmp* size = gen(LdVectorSize, m_base);
|
||||
gen(CheckBounds, key, size);
|
||||
gen(CheckBounds, makeCatch(), key, size);
|
||||
SSATmp* base = gen(LdVectorBase, m_base);
|
||||
static_assert(sizeof(TypedValue) == 16,
|
||||
"TypedValue size expected to be 16 bytes");
|
||||
@@ -1745,7 +1750,7 @@ void HhbcTranslator::MInstrTranslator::emitPairGet(SSATmp* key) {
|
||||
auto index = cns(key->getValInt() << 4);
|
||||
value = gen(LdElem, base, index);
|
||||
} else {
|
||||
gen(CheckBounds, key, cns(1));
|
||||
gen(CheckBounds, makeCatch(), key, cns(1));
|
||||
SSATmp* base = gen(LdPairBase, m_base);
|
||||
auto idx = gen(Shl, key, cns(4));
|
||||
value = gen(LdElem, base, idx);
|
||||
@@ -2310,7 +2315,7 @@ void HhbcTranslator::MInstrTranslator::emitVectorSet(
|
||||
PUNT(emitVectorSet); // will throw
|
||||
}
|
||||
SSATmp* size = gen(LdVectorSize, m_base);
|
||||
gen(CheckBounds, key, size);
|
||||
gen(CheckBounds, makeCatch(), key, size);
|
||||
|
||||
SSATmp* increffed = gen(IncRef, value);
|
||||
SSATmp* vecBase = gen(LdVectorBase, m_base);
|
||||
|
||||
@@ -50,8 +50,8 @@ bool cycleHasXMMReg(const CycleInfo& cycle, const int (&moves)[N]) {
|
||||
}
|
||||
|
||||
template <int N>
|
||||
void doRegMoves(int (&moves)[N], int rTmp, std::vector<MoveInfo>& howTo) {
|
||||
assert(howTo.empty());
|
||||
smart::vector<MoveInfo> doRegMoves(int (&moves)[N], int rTmp) {
|
||||
smart::vector<MoveInfo> howTo;
|
||||
int outDegree[N];
|
||||
CycleInfo cycles[N];
|
||||
int numCycles = 0;
|
||||
@@ -149,6 +149,7 @@ pathloop:
|
||||
howTo.push_back(MoveInfo(MoveInfo::Kind::Move, rTmp, w));
|
||||
}
|
||||
}
|
||||
return howTo;
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -313,6 +313,7 @@ RegionDescPtr selectTraceletLegacy(const RegionContext& rCtx,
|
||||
for (auto cni = callee.m_instrStream.first; cni; cni = cni->next) {
|
||||
assert(cSk == cni->source);
|
||||
assert(cni->op() == OpRetC ||
|
||||
cni->op() == OpRetV ||
|
||||
cni->op() == OpContRetC ||
|
||||
cni->op() == OpNativeImpl ||
|
||||
!instrIsNonCallControlFlow(cni->op()));
|
||||
@@ -461,7 +462,7 @@ RegionDescPtr selectHotRegion(TransID transId,
|
||||
std::string dotFileName = string("/tmp/trans-cfg-") +
|
||||
lexical_cast<std::string>(transId) + ".dot";
|
||||
|
||||
cfg.print(dotFileName, profData, &selectedTIDs);
|
||||
cfg.print(dotFileName, funcId, profData, &selectedTIDs);
|
||||
FTRACE(5, "selectHotRegion: New Translation {} (file: {}) {}\n",
|
||||
tx64->profData()->curTransID(), dotFileName,
|
||||
region ? show(*region) : std::string("empty region"));
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
/*
|
||||
+----------------------------------------------------------------------+
|
||||
| HipHop for PHP |
|
||||
+----------------------------------------------------------------------+
|
||||
| Copyright (c) 2010-2013 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 "hphp/vixl/a64/macro-assembler-a64.h"
|
||||
|
||||
|
||||
@@ -170,6 +170,11 @@ StackValueInfo getStackValue(SSATmp* sp, uint32_t index) {
|
||||
if (index == kNumActRecCells) return StackValueInfo { inst, Type::Bool };
|
||||
if (index == kNumActRecCells + 1) return getStackValue(prevSp, 0);
|
||||
break;
|
||||
case Op::FPushCtor:
|
||||
case Op::FPushCtorD:
|
||||
if (index == kNumActRecCells) return StackValueInfo { inst, Type::Obj };
|
||||
if (index == kNumActRecCells + 1) return getStackValue(prevSp, 0);
|
||||
break;
|
||||
|
||||
default:
|
||||
if (index == 0 && !resultType.equals(Type::None)) {
|
||||
@@ -365,8 +370,7 @@ SSATmp* Simplifier::simplify(IRInstruction* inst) {
|
||||
case UnboxPtr: return simplifyUnboxPtr(inst);
|
||||
case IsType:
|
||||
case IsNType: return simplifyIsType(inst);
|
||||
case CheckInit:
|
||||
case CheckInitMem: return simplifyCheckInit(inst);
|
||||
case CheckInit: return simplifyCheckInit(inst);
|
||||
|
||||
case JmpZero:
|
||||
case JmpNZero:
|
||||
@@ -538,7 +542,7 @@ SSATmp* Simplifier::simplifyLdCls(IRInstruction* inst) {
|
||||
return cns(cls);
|
||||
}
|
||||
}
|
||||
return gen(LdClsCached, clsName);
|
||||
return gen(LdClsCached, inst->taken(), clsName);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
@@ -1391,7 +1395,7 @@ SSATmp* Simplifier::simplifyJmpIsType(IRInstruction* inst) {
|
||||
assert(res->isConst());
|
||||
if (res->getValBool()) {
|
||||
// Taken jump
|
||||
return gen(Jmp_, inst->taken());
|
||||
return gen(Jmp, inst->taken());
|
||||
} else {
|
||||
// Not taken jump; turn jump into a nop
|
||||
inst->convertToNop();
|
||||
@@ -1783,13 +1787,10 @@ SSATmp* Simplifier::simplifyUnboxPtr(IRInstruction* inst) {
|
||||
}
|
||||
|
||||
SSATmp* Simplifier::simplifyCheckInit(IRInstruction* inst) {
|
||||
Type srcType = inst->src(0)->type();
|
||||
srcType = inst->op() == CheckInitMem ? srcType.deref() : srcType;
|
||||
auto const srcType = inst->src(0)->type();
|
||||
assert(srcType.notPtr());
|
||||
assert(inst->taken());
|
||||
if (srcType.isInit()) {
|
||||
inst->convertToNop();
|
||||
}
|
||||
if (srcType.isInit()) inst->convertToNop();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -1841,7 +1842,7 @@ SSATmp* Simplifier::simplifyCondJmp(IRInstruction* inst) {
|
||||
val = !val;
|
||||
}
|
||||
if (val) {
|
||||
return gen(Jmp_, inst->taken());
|
||||
return gen(Jmp, inst->taken());
|
||||
}
|
||||
inst->convertToNop();
|
||||
return nullptr;
|
||||
|
||||
@@ -26,10 +26,12 @@
|
||||
#include "hphp/runtime/base/execution-context.h"
|
||||
#include "hphp/runtime/base/runtime-error.h"
|
||||
#include "hphp/runtime/vm/jit/translator-inline.h"
|
||||
#include "hphp/runtime/vm/jit/translator-runtime.h"
|
||||
|
||||
namespace HPHP { namespace JIT {
|
||||
|
||||
using namespace HPHP::MethodLookup;
|
||||
using namespace HPHP::Transl;
|
||||
|
||||
TRACE_SET_MOD(targetcache);
|
||||
|
||||
@@ -252,30 +254,14 @@ void methodCacheSlowPath(MethodCache* mce,
|
||||
assert(name->isStatic()); // No incRef needed.
|
||||
}
|
||||
} catch (...) {
|
||||
/*
|
||||
* Barf.
|
||||
*
|
||||
* If the slow lookup fails, we're going to rewind to the state
|
||||
* before the FPushObjMethodD that dumped us here. In this state,
|
||||
* the object is still on the stack, but for efficiency reasons,
|
||||
* we've smashed this TypedValue* with the ActRec we were trying
|
||||
* to push.
|
||||
*
|
||||
* Reconstitute the virtual object before rethrowing.
|
||||
*/
|
||||
TypedValue* shouldBeObj = reinterpret_cast<TypedValue*>(ar) +
|
||||
kNumActRecCells - 1;
|
||||
// This is extreme shadiness. See the comments of
|
||||
// arPreliveOverwriteCells() for more info on how this code gets the
|
||||
// unwinder to restore the pre-FPushObjMethodD state, including decref
|
||||
// of the ar->getThis() object.
|
||||
ObjectData* arThis = ar->getThis();
|
||||
shouldBeObj->m_type = KindOfObject;
|
||||
shouldBeObj->m_data.pobj = arThis;
|
||||
|
||||
// There used to be a half-built ActRec on the stack that we need the
|
||||
// unwinder to ignore. We overwrote 1/3 of it with the code above, but
|
||||
// because of the emitMarker() in LdObjMethod we need the other two slots
|
||||
// to not have any TypedValues.
|
||||
tvWriteNull(shouldBeObj - 1);
|
||||
tvWriteNull(shouldBeObj - 2);
|
||||
|
||||
auto firstActRecCell = arPreliveOverwriteCells(ar);
|
||||
firstActRecCell->m_type = KindOfObject;
|
||||
firstActRecCell->m_data.pobj = arThis;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -527,4 +513,3 @@ StaticMethodFCache::lookupIR(RDS::Handle handle, const Class* cls,
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}}
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ SSATmp* TraceBuilder::preOptimizeCheckLoc(IRInstruction* inst) {
|
||||
if (!typeParam.isBoxed() || !prevType.isBoxed()) {
|
||||
if ((typeParam & prevType) == Type::Bottom) {
|
||||
assert(RuntimeOption::EvalJitPGO);
|
||||
return gen(Jmp_, inst->taken());
|
||||
return gen(Jmp, inst->taken());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,10 +218,10 @@ struct TraceBuilder {
|
||||
DisableCseGuard guard(*this);
|
||||
branch(taken_block);
|
||||
SSATmp* v1 = next();
|
||||
gen(Jmp_, done_block, v1);
|
||||
gen(Jmp, done_block, v1);
|
||||
appendBlock(taken_block);
|
||||
SSATmp* v2 = taken();
|
||||
gen(Jmp_, done_block, v2);
|
||||
gen(Jmp, done_block, v2);
|
||||
appendBlock(done_block);
|
||||
SSATmp* result = label->dst(0);
|
||||
result->setType(Type::unionOf(v1->type(), v2->type()));
|
||||
|
||||
@@ -168,13 +168,17 @@ void TransCFG::addArc(TransID srcId, TransID dstId, int64_t weight) {
|
||||
m_nodeInfo[dstIdx].addInArc(arc);
|
||||
}
|
||||
|
||||
void TransCFG::print(std::string fileName, const ProfData* profData,
|
||||
void TransCFG::print(std::string fileName, FuncId funcId,
|
||||
const ProfData* profData,
|
||||
const TransIDSet* selected) const {
|
||||
FILE* file = fopen(fileName.c_str(), "wt");
|
||||
if (!file) return;
|
||||
|
||||
fprintf(file, "digraph CFG {\n");
|
||||
|
||||
fprintf(file, "# function: %s\n",
|
||||
Func::fromFuncId(funcId)->fullName()->data());
|
||||
|
||||
// find max node weight
|
||||
int64_t maxWeight = 1; // 1 to avoid div by 0
|
||||
for (auto tid : nodes()) {
|
||||
|
||||
@@ -94,6 +94,7 @@ class TransCFG {
|
||||
bool hasNode(TransID id) const;
|
||||
void addArc(TransID srcId, TransID dstId, int64_t weight=0);
|
||||
void print(std::string fileName,
|
||||
FuncId funcId,
|
||||
const ProfData* profData,
|
||||
const TransIDSet* selected = nullptr) const;
|
||||
|
||||
|
||||
@@ -176,6 +176,9 @@
|
||||
CASE(Div) \
|
||||
CASE(Floor) \
|
||||
CASE(Ceil) \
|
||||
CASE(AssertTL) \
|
||||
CASE(AssertTStk) \
|
||||
/* */
|
||||
|
||||
// These are instruction-like functions which cover more than one
|
||||
// opcode.
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "hphp/runtime/vm/type-constraint.h"
|
||||
#include "hphp/runtime/vm/jit/translator-inline.h"
|
||||
#include "hphp/runtime/vm/jit/translator-x64.h"
|
||||
#include "hphp/runtime/vm/jit/translator-x64-internal.h"
|
||||
#include "hphp/runtime/base/stats.h"
|
||||
|
||||
namespace HPHP {
|
||||
@@ -32,6 +33,8 @@ TRACE_SET_MOD(runtime);
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
const StaticString s_staticPrefix("86static_");
|
||||
const StaticString s___call("__call");
|
||||
const StaticString s___callStatic("__callStatic");
|
||||
|
||||
// Defined here so it can be inlined below.
|
||||
RefData* lookupStaticFromClosure(ObjectData* closure,
|
||||
@@ -649,32 +652,12 @@ void loadArrayFunctionContext(ArrayData* arr, ActRec* preLiveAR, ActRec* fp) {
|
||||
preLiveAR->setInvName(invName);
|
||||
}
|
||||
} catch (...) {
|
||||
/*
|
||||
* This is some shady shit, similar to methodCacheSlowPath.
|
||||
*
|
||||
* Because we encountered an exception in the middle of doing work
|
||||
* (filling up a pre-live ActRec) and already called emitMarker in
|
||||
* emitFPushFuncArr, the bytecode offset looks like we've not made it
|
||||
* through the instruction (correct!), but the stack pointer has moved
|
||||
* as if we have allocated kNumActRecCells eval cells onto the
|
||||
* stack. The unwinder will thus believe that it has to decref those
|
||||
* kNumActRecCells.
|
||||
*
|
||||
* We need the unwinder to ignore the half-built ActRec on the stack
|
||||
* when building the back-trace and certainly to avoid attempting to
|
||||
* decref its contents. We also need to make sure that arr gets
|
||||
* decref'd. We achieve both of these aims by overwriting one cell of
|
||||
* the ActRec with the pointer to arr and the remainder with null
|
||||
* TypeValues and thus ensure that the unwinder leaves state the same
|
||||
* as it was before the call into the FPushFunc that started this whole
|
||||
* stack trace.
|
||||
*/
|
||||
auto firstActRecCell = reinterpret_cast<TypedValue*>(preLiveAR);
|
||||
// This is extreme shadiness. See the comments of
|
||||
// arPreliveOverwriteCells() for more info on how this code gets the
|
||||
// unwinder to restore the pre-FPush state.
|
||||
auto firstActRecCell = arPreliveOverwriteCells(preLiveAR);
|
||||
firstActRecCell->m_type = KindOfArray;
|
||||
firstActRecCell->m_data.parr = arr;
|
||||
for (size_t ar_cell = 1; ar_cell < kNumActRecCells; ++ar_cell) {
|
||||
tvWriteNull(firstActRecCell + ar_cell);
|
||||
}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -744,4 +727,121 @@ ObjectData* colAddElemCHelper(ObjectData* coll, TypedValue key,
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
void setArgInActRec(ActRec* ar, int argNum, uint64_t datum, DataType t) {
|
||||
TypedValue* tv =
|
||||
(TypedValue*)(uintptr_t(ar) - (argNum+1) * sizeof(TypedValue));
|
||||
tv->m_data.num = datum;
|
||||
tv->m_type = t;
|
||||
}
|
||||
|
||||
int shuffleArgsForMagicCall(ActRec* ar) {
|
||||
if (!ar->hasInvName()) {
|
||||
return 0;
|
||||
}
|
||||
const Func* f UNUSED = ar->m_func;
|
||||
f->validate();
|
||||
assert(f->name()->isame(s___call.get())
|
||||
|| f->name()->isame(s___callStatic.get()));
|
||||
assert(f->numParams() == 2);
|
||||
TRACE(1, "shuffleArgsForMagicCall: ar %p\n", ar);
|
||||
assert(ar->hasInvName());
|
||||
StringData* invName = ar->getInvName();
|
||||
assert(invName);
|
||||
ar->setVarEnv(nullptr);
|
||||
int nargs = ar->numArgs();
|
||||
|
||||
// We need to make an array containing all the arguments passed by the
|
||||
// caller and put it where the second argument is
|
||||
PackedArrayInit aInit(nargs);
|
||||
for (int i = 0; i < nargs; ++i) {
|
||||
auto const tv = reinterpret_cast<TypedValue*>(
|
||||
uintptr_t(ar) - (i+1) * sizeof(TypedValue)
|
||||
);
|
||||
aInit.append(tvAsCVarRef(tv));
|
||||
tvRefcountedDecRef(tv);
|
||||
}
|
||||
|
||||
// Put invName in the slot for first argument
|
||||
setArgInActRec(ar, 0, uint64_t(invName), BitwiseKindOfString);
|
||||
// Put argArray in the slot for second argument
|
||||
auto const argArray = aInit.toArray().detach();
|
||||
setArgInActRec(ar, 1, uint64_t(argArray), KindOfArray);
|
||||
// Fix up ActRec's numArgs
|
||||
ar->initNumArgs(2);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* The standard VMRegAnchor treatment won't work for some cases called
|
||||
* during function preludes.
|
||||
*
|
||||
* The fp sync machinery is fundamentally based on the notion that
|
||||
* instruction pointers in the TC are uniquely associated with source
|
||||
* HHBC instructions, and that source HHBC instructions are in turn
|
||||
* uniquely associated with SP->FP deltas.
|
||||
*
|
||||
* trimExtraArgs is called from the prologue of the callee.
|
||||
* The prologue is 1) still in the caller frame for now,
|
||||
* and 2) shared across multiple call sites. 1 means that we have the
|
||||
* fp from the caller's frame, and 2 means that this fp is not enough
|
||||
* to figure out sp.
|
||||
*
|
||||
* However, the prologue passes us the callee actRec, whose predecessor
|
||||
* has to be the caller. So we can sync sp and fp by ourselves here.
|
||||
* Geronimo!
|
||||
*/
|
||||
static void sync_regstate_to_caller(ActRec* preLive) {
|
||||
assert(tl_regState == VMRegState::DIRTY);
|
||||
VMExecutionContext* ec = g_vmContext;
|
||||
ec->m_stack.top() = (TypedValue*)preLive - preLive->numArgs();
|
||||
ActRec* fp = preLive == ec->m_firstAR ?
|
||||
ec->m_nestedVMs.back().m_savedState.fp : (ActRec*)preLive->m_savedRbp;
|
||||
ec->m_fp = fp;
|
||||
ec->m_pc = fp->m_func->unit()->at(fp->m_func->base() + preLive->m_soff);
|
||||
tl_regState = VMRegState::CLEAN;
|
||||
}
|
||||
|
||||
void trimExtraArgs(ActRec* ar) {
|
||||
assert(!ar->hasInvName());
|
||||
|
||||
sync_regstate_to_caller(ar);
|
||||
const Func* f = ar->m_func;
|
||||
int numParams = f->numParams();
|
||||
int numArgs = ar->numArgs();
|
||||
assert(numArgs > numParams);
|
||||
int numExtra = numArgs - numParams;
|
||||
|
||||
TRACE(1, "trimExtraArgs: %d args, function %s takes only %d, ar %p\n",
|
||||
numArgs, f->name()->data(), numParams, ar);
|
||||
|
||||
if (f->attrs() & AttrMayUseVV) {
|
||||
assert(!ar->hasExtraArgs());
|
||||
ar->setExtraArgs(ExtraArgs::allocateCopy(
|
||||
(TypedValue*)(uintptr_t(ar) - numArgs * sizeof(TypedValue)),
|
||||
numArgs - numParams));
|
||||
} else {
|
||||
// Function is not marked as "MayUseVV", so discard the extra arguments
|
||||
TypedValue* tv = (TypedValue*)(uintptr_t(ar) - numArgs*sizeof(TypedValue));
|
||||
for (int i = 0; i < numExtra; ++i) {
|
||||
tvRefcountedDecRef(tv);
|
||||
++tv;
|
||||
}
|
||||
ar->setNumArgs(numParams);
|
||||
}
|
||||
|
||||
// Only go back to dirty in a non-exception case. (Same reason as
|
||||
// above.)
|
||||
tl_regState = VMRegState::DIRTY;
|
||||
}
|
||||
|
||||
void raiseMissingArgument(const char* name, int expected, int got) {
|
||||
if (expected == 1) {
|
||||
raise_warning(Strings::MISSING_ARGUMENT, name, got);
|
||||
} else {
|
||||
raise_warning(Strings::MISSING_ARGUMENTS, name, expected, got);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include "hphp/runtime/vm/jit/abi-x64.h"
|
||||
#include "hphp/runtime/base/rds.h"
|
||||
#include "hphp/runtime/vm/type-constraint.h"
|
||||
#include "hphp/runtime/vm/bytecode.h"
|
||||
|
||||
namespace HPHP { namespace Transl {
|
||||
|
||||
@@ -53,6 +54,33 @@ static_assert(sizeof(MInstrState) - sizeof(uintptr_t) // return address
|
||||
|
||||
/* Helper functions for translated code */
|
||||
|
||||
/**
|
||||
* Only use in case of extreme shadiness.
|
||||
*
|
||||
* There are a few cases in the JIT where we allocate a pre-live ActRec on
|
||||
* the stack, call updateMarker() and then a CPP helper
|
||||
* (methodCacheSlowPath, loadArrayFunctionContext) to fill the ActRec with
|
||||
* te function that it needs to call. In the case that the helper throws an
|
||||
* exception, the unwinder sees a strange stack marker that has moved while
|
||||
* the bytecode offset remains unchanged and believes it has decref work to
|
||||
* do; we need the unwinder to ignore the half-built ActRec allocated on
|
||||
* the stack when building its back-trace and certainly to avoid attempting
|
||||
* to decref its contents. We achieve this by overwriting the ActRec cells
|
||||
* with null TypeValues.
|
||||
*
|
||||
* A TypedValue* is returned here to allow the CPP helper to write whatever
|
||||
* it needs to be decref'd into those eval cells, to ensure that the
|
||||
* unwinder leaves state the same as it was before the call into FPush
|
||||
* bytecode that started this whole stack trace.
|
||||
*/
|
||||
inline TypedValue* arPreliveOverwriteCells(ActRec *preLiveAR) {
|
||||
auto actRecCell = reinterpret_cast<TypedValue*>(preLiveAR);
|
||||
for (size_t ar_cell = 0; ar_cell < HPHP::kNumActRecCells; ++ar_cell) {
|
||||
tvWriteNull(actRecCell + ar_cell);
|
||||
}
|
||||
return actRecCell;
|
||||
}
|
||||
|
||||
ArrayData* addElemIntKeyHelper(ArrayData* ad, int64_t key, TypedValue val);
|
||||
ArrayData* addElemStringKeyHelper(ArrayData* ad, StringData* key,
|
||||
TypedValue val);
|
||||
@@ -141,6 +169,12 @@ ObjectData* colAddNewElemCHelper(ObjectData* coll, TypedValue value);
|
||||
ObjectData* colAddElemCHelper(ObjectData* coll, TypedValue key,
|
||||
TypedValue value);
|
||||
|
||||
void trimExtraArgs(ActRec* ar);
|
||||
void setArgInActRec(ActRec* ar, int argNum, uint64_t datum, DataType t);
|
||||
int shuffleArgsForMagicCall(ActRec* ar);
|
||||
|
||||
void raiseMissingArgument(const char* name, int expected, int got);
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário