/* +----------------------------------------------------------------------+ | 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/compiler/analysis/emitter.h" #include "hphp/compiler/builtin_symbols.h" #include "hphp/compiler/analysis/class_scope.h" #include "hphp/compiler/analysis/file_scope.h" #include "hphp/compiler/analysis/function_scope.h" #include "hphp/compiler/analysis/peephole.h" #include "hphp/compiler/expression/array_element_expression.h" #include "hphp/compiler/expression/array_pair_expression.h" #include "hphp/compiler/expression/assignment_expression.h" #include "hphp/compiler/expression/binary_op_expression.h" #include "hphp/compiler/expression/class_constant_expression.h" #include "hphp/compiler/expression/closure_expression.h" #include "hphp/compiler/expression/constant_expression.h" #include "hphp/compiler/expression/dynamic_variable.h" #include "hphp/compiler/expression/encaps_list_expression.h" #include "hphp/compiler/expression/expression_list.h" #include "hphp/compiler/expression/include_expression.h" #include "hphp/compiler/expression/list_assignment.h" #include "hphp/compiler/expression/modifier_expression.h" #include "hphp/compiler/expression/new_object_expression.h" #include "hphp/compiler/expression/object_method_expression.h" #include "hphp/compiler/expression/object_property_expression.h" #include "hphp/compiler/expression/parameter_expression.h" #include "hphp/compiler/expression/qop_expression.h" #include "hphp/compiler/expression/scalar_expression.h" #include "hphp/compiler/expression/simple_variable.h" #include "hphp/compiler/expression/simple_function_call.h" #include "hphp/compiler/expression/static_member_expression.h" #include "hphp/compiler/expression/unary_op_expression.h" #include "hphp/compiler/expression/yield_expression.h" #include "hphp/compiler/statement/break_statement.h" #include "hphp/compiler/statement/case_statement.h" #include "hphp/compiler/statement/catch_statement.h" #include "hphp/compiler/statement/class_constant.h" #include "hphp/compiler/statement/class_variable.h" #include "hphp/compiler/statement/do_statement.h" #include "hphp/compiler/statement/echo_statement.h" #include "hphp/compiler/statement/exp_statement.h" #include "hphp/compiler/statement/for_statement.h" #include "hphp/compiler/statement/foreach_statement.h" #include "hphp/compiler/statement/function_statement.h" #include "hphp/compiler/statement/global_statement.h" #include "hphp/compiler/statement/goto_statement.h" #include "hphp/compiler/statement/if_branch_statement.h" #include "hphp/compiler/statement/if_statement.h" #include "hphp/compiler/statement/label_statement.h" #include "hphp/compiler/statement/method_statement.h" #include "hphp/compiler/statement/return_statement.h" #include "hphp/compiler/statement/statement_list.h" #include "hphp/compiler/statement/static_statement.h" #include "hphp/compiler/statement/switch_statement.h" #include "hphp/compiler/statement/try_statement.h" #include "hphp/compiler/statement/unset_statement.h" #include "hphp/compiler/statement/while_statement.h" #include "hphp/compiler/statement/use_trait_statement.h" #include "hphp/compiler/statement/trait_prec_statement.h" #include "hphp/compiler/statement/trait_alias_statement.h" #include "hphp/compiler/statement/typedef_statement.h" #include "hphp/compiler/parser/parser.h" #include "hphp/util/logger.h" #include "hphp/util/util.h" #include "hphp/util/job_queue.h" #include "hphp/util/parser/hphp.tab.hpp" #include "hphp/runtime/vm/bytecode.h" #include "hphp/runtime/vm/repo.h" #include "hphp/runtime/vm/as.h" #include "hphp/runtime/base/stats.h" #include "hphp/runtime/base/runtime_option.h" #include "hphp/runtime/base/zend_string.h" #include "hphp/runtime/base/type_conversions.h" #include "hphp/runtime/base/builtin_functions.h" #include "hphp/runtime/base/variable_serializer.h" #include "hphp/runtime/base/program_functions.h" #include "hphp/runtime/base/file_repository.h" #include "hphp/runtime/ext_hhvm/ext_hhvm.h" #include "hphp/runtime/vm/preclass-emit.h" #include "hphp/system/systemlib.h" #include "folly/ScopeGuard.h" #include #include #include #include namespace HPHP { namespace Compiler { /////////////////////////////////////////////////////////////////////////////// TRACE_SET_MOD(emitter) using boost::dynamic_pointer_cast; using boost::static_pointer_cast; namespace { const StringData* s_continuationVarArgsLocal = StringData::GetStaticString("0ContinuationVarArgsLocal"); } namespace StackSym { static const char None = 0x00; static const char C = 0x01; // Cell symbolic flavor static const char V = 0x02; // Var symbolic flavor static const char A = 0x03; // Classref symbolic flavor static const char R = 0x04; // Return value symbolic flavor static const char F = 0x05; // Function argument symbolic flavor static const char L = 0x06; // Local symbolic flavor static const char T = 0x07; // String literal symbolic flavor static const char I = 0x08; // int literal symbolic flavor static const char H = 0x09; // $this symbolic flavor static const char N = 0x10; // Name marker static const char G = 0x20; // Global name marker static const char E = 0x30; // Element marker static const char W = 0x40; // New element marker static const char P = 0x50; // Property marker static const char S = 0x60; // Static property marker static const char M = 0x70; // Non elem/prop/W part of M-vector static const char K = 0x80; // Marker for information about a class base static const char CN = C | N; static const char CG = C | G; static const char CE = C | E; static const char CP = C | P; static const char CS = C | S; static const char LN = L | N; static const char LG = L | G; static const char LE = L | E; static const char LP = L | P; static const char LS = L | S; static const char AM = A | M; char GetSymFlavor(char sym) { return (sym & 0x0F); } char GetMarker(char sym) { return (sym & 0xF0); } std::string ToString(char sym) { char symFlavor = StackSym::GetSymFlavor(sym); std::string res; switch (symFlavor) { case StackSym::C: res = "C"; break; case StackSym::V: res = "V"; break; case StackSym::A: res = "A"; break; case StackSym::R: res = "R"; break; case StackSym::F: res = "F"; break; case StackSym::L: res = "L"; break; case StackSym::T: res = "T"; break; case StackSym::I: res = "I"; break; case StackSym::H: res = "H"; break; default: break; } char marker = StackSym::GetMarker(sym); switch (marker) { case StackSym::N: res += "N"; break; case StackSym::G: res += "G"; break; case StackSym::E: res += "E"; break; case StackSym::W: res += "W"; break; case StackSym::P: res += "P"; break; case StackSym::S: res += "S"; break; case StackSym::K: res += "K"; break; default: break; } if (res == "") { if (sym == StackSym::None) { res = "None"; } else { res = "?"; } } return res; } } //============================================================================= // Emitter. #define InvariantViolation(...) do { \ Logger::Warning(__VA_ARGS__); \ Logger::Warning("Eval stack at the time of error: %s", \ m_evalStack.pretty().c_str()); \ assert(false); \ } while (0) // RAII guard for function creation. class FuncFinisher { EmitterVisitor* m_ev; Emitter& m_e; FuncEmitter* m_fe; public: FuncFinisher(EmitterVisitor* ev, Emitter& e, FuncEmitter* fe) : m_ev(ev), m_e(e), m_fe(fe) { TRACE(2, "FuncFinisher constructed: %s\n", m_fe->name()->data()); } ~FuncFinisher() { TRACE(2, "Finishing func: %s\n", m_fe->name()->data()); m_ev->finishFunc(m_e, m_fe); } }; // RAII guard for temporarily overriding an Emitter's location class LocationGuard { Emitter& m_e; LocationPtr m_loc; public: LocationGuard(Emitter& e, LocationPtr newLoc) : m_e(e), m_loc(e.getTempLocation()) { if (newLoc) m_e.setTempLocation(newLoc); } ~LocationGuard() { m_e.setTempLocation(m_loc); } }; // Count the number of stack elements in an immediate vector. static int32_t countStackValues(const std::vector& immVec) { assert(!immVec.empty()); int count = 0; const uint8_t* vec = &immVec[0]; // Count the location; the LS location type accounts for up to two // values on the stack, all other location types account for at most // one value on the stack. Subtract the number that are actually // immediates. const LocationCode locCode = LocationCode(*vec++); count += numLocationCodeStackVals(locCode); const int numLocImms = numLocationCodeImms(locCode); for (int i = 0; i < numLocImms; ++i) { decodeVariableSizeImm(&vec); } // Count each of the members; MEC and MPC account for one value on // the stack, while MW/MEL/MPL/MET/MPT/MEI don't account for any // values on the stack. while (vec - &immVec[0] < int(immVec.size())) { MemberCode code = MemberCode(*vec++); if (memberCodeHasImm(code)) { decodeMemberCodeImm(&vec, code); } else if (code != MW) { ++count; } } assert(vec - &immVec[0] == int(immVec.size())); return count; } #define O(name, imm, pop, push, flags) \ void Emitter::name(imm) { \ auto const opcode = Op::name; \ Offset curPos UNUSED = getUnitEmitter().bcPos(); \ getEmitterVisitor().prepareEvalStack(); \ POP_##pop; \ const int nIn UNUSED = COUNT_##pop; \ POP_HA_##imm; \ PUSH_##push; \ getUnitEmitter().emitOp(Op##name); \ IMPL_##imm; \ getUnitEmitter().recordSourceLocation(m_tempLoc ? m_tempLoc.get() : \ m_node->getLocation().get(), curPos); \ if (flags & TF) getEmitterVisitor().restoreJumpTargetEvalStack(); \ if (isFCallStar(opcode)) getEmitterVisitor().recordCall(); \ getEmitterVisitor().setPrevOpcode(opcode); \ } #define COUNT_NOV 0 #define COUNT_ONE(t) 1 #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_FMANY 0 #define COUNT_CVMANY 0 #define COUNT_CMANY 0 #define ONE(t) \ DEC_##t a1 #define TWO(t1, t2) \ DEC_##t1 a1, DEC_##t2 a2 #define THREE(t1, t2, t3) \ DEC_##t1 a1, DEC_##t2 a2, DEC_##t3 a3 #define FOUR(t1, t2, t3, t4) \ DEC_##t1 a1, DEC_##t2 a2, DEC_##t3 a3, DEC_##t4 a4 #define NA #define DEC_MA std::vector #define DEC_BLA std::vector& #define DEC_SLA std::vector& #define DEC_ILA std::vector& #define DEC_IVA int32_t #define DEC_HA int32_t #define DEC_IA int32_t #define DEC_I64A int64_t #define DEC_DA double #define DEC_SA const StringData* #define DEC_AA ArrayData* #define DEC_BA Label& #define DEC_OA uchar #define POP_NOV #define POP_ONE(t) \ POP_##t(0) #define POP_TWO(t1, t2) \ POP_##t1(0); \ POP_##t2(1) #define POP_THREE(t1, t2, t3) \ POP_##t1(0); \ POP_##t2(1); \ POP_##t3(2) #define POP_FOUR(t1, t2, t3, t4) \ POP_##t1(0); \ POP_##t2(1); \ POP_##t3(2); \ POP_##t4(3) #define POP_LMANY() \ getEmitterVisitor().popEvalStackLMany() #define POP_C_LMANY() \ getEmitterVisitor().popEvalStack(StackSym::C); \ getEmitterVisitor().popEvalStackLMany() #define POP_V_LMANY() \ getEmitterVisitor().popEvalStack(StackSym::V); \ getEmitterVisitor().popEvalStackLMany() #define POP_R_LMANY() \ getEmitterVisitor().popEvalStack(StackSym::R); \ getEmitterVisitor().popEvalStackLMany() #define POP_FMANY \ getEmitterVisitor().popEvalStackMany(a1, StackSym::F) #define POP_CVMANY \ getEmitterVisitor().popEvalStackCVMany(a1) #define POP_CMANY \ getEmitterVisitor().popEvalStackMany(a1, StackSym::C) #define POP_CV(i) getEmitterVisitor().popEvalStack(StackSym::C, i, curPos) #define POP_VV(i) getEmitterVisitor().popEvalStack(StackSym::V, i, curPos) #define POP_AV(i) getEmitterVisitor().popEvalStack(StackSym::A, i, curPos) #define POP_RV(i) getEmitterVisitor().popEvalStack(StackSym::R, i, curPos) #define POP_FV(i) getEmitterVisitor().popEvalStack(StackSym::F, i, curPos) // Pop of virtual "locs" on the stack that turn into immediates. #define POP_HA_ONE(t) \ POP_HA_##t(nIn) #define POP_HA_TWO(t1, t2) \ POP_HA_##t1(nIn); \ POP_HA_##t2(nIn) #define POP_HA_THREE(t1, t2, t3) \ POP_HA_##t1(nIn); \ POP_HA_##t2(nIn); \ POP_HA_##t3(nIn) #define POP_HA_FOUR(t1, t2, t3, t4) \ POP_HA_##t1(nIn); \ POP_HA_##t2(nIn); \ POP_HA_##t3(nIn); \ POP_HA_##t4(nIn) #define POP_HA_NA #define POP_HA_MA(i) #define POP_HA_BLA(i) #define POP_HA_SLA(i) #define POP_HA_ILA(i) #define POP_HA_IVA(i) #define POP_HA_IA(i) #define POP_HA_I64A(i) #define POP_HA_DA(i) #define POP_HA_SA(i) #define POP_HA_AA(i) #define POP_HA_BA(i) #define POP_HA_OA(i) #define POP_HA_HA(i) \ getEmitterVisitor().popSymbolicLocal(opcode, i, curPos) #define PUSH_NOV #define PUSH_ONE(t) \ PUSH_##t #define PUSH_TWO(t1, t2) \ PUSH_##t2; \ PUSH_##t1 #define PUSH_THREE(t1, t2, t3) \ PUSH_##t3; \ PUSH_##t2; \ PUSH_##t1 #define PUSH_FOUR(t1, t2, t3, t4) \ PUSH_##t4; \ PUSH_##t3; \ PUSH_##t2; \ PUSH_##t1 #define PUSH_INS_1(t) PUSH_INS_1_##t #define PUSH_INS_2(t) PUSH_INS_2_##t #define PUSH_CV getEmitterVisitor().pushEvalStack(StackSym::C) #define PUSH_VV getEmitterVisitor().pushEvalStack(StackSym::V) #define PUSH_AV getEmitterVisitor().pushEvalStack(StackSym::A) #define PUSH_RV getEmitterVisitor().pushEvalStack(StackSym::R) #define PUSH_FV getEmitterVisitor().pushEvalStack(StackSym::F) #define PUSH_INS_1_CV \ getEmitterVisitor().getEvalStack().insertAt(1, StackSym::C); #define PUSH_INS_1_AV \ getEmitterVisitor().getEvalStack().insertAt(1, StackSym::A); #define PUSH_INS_2_CV \ getEmitterVisitor().getEvalStack().insertAt(2, StackSym::C); #define IMPL_NA #define IMPL_ONE(t) \ IMPL1_##t #define IMPL_TWO(t1, t2) \ IMPL1_##t1; \ IMPL2_##t2 #define IMPL_THREE(t1, t2, t3) \ IMPL1_##t1; \ IMPL2_##t2; \ IMPL3_##t3 #define IMPL_FOUR(t1, t2, t3, t4) \ IMPL1_##t1; \ IMPL2_##t2; \ IMPL3_##t3; \ IMPL4_##t4 #define IMPL_MA(var) do { \ getUnitEmitter().emitInt32(var.size()); \ getUnitEmitter().emitInt32(countStackValues(var)); \ for (unsigned int i = 0; i < var.size(); ++i) { \ getUnitEmitter().emitByte(var[i]); \ } \ } while (0) #define IMPL1_MA IMPL_MA(a1) #define IMPL2_MA IMPL_MA(a2) #define IMPL3_MA IMPL_MA(a3) #define IMPL4_MA IMPL_MA(a4) #define IMPL_BLA(var) do { \ getUnitEmitter().emitInt32(var.size()); \ for (unsigned int i = 0; i < var.size(); ++i) { \ IMPL_BA(*var[i]); \ } \ } while (0) #define IMPL1_BLA IMPL_BLA(a1) #define IMPL2_BLA IMPL_BLA(a2) #define IMPL3_BLA IMPL_BLA(a3) #define IMPL4_BLA IMPL_BLA(a4) #define IMPL_ILA(var) do { \ auto& ue = getUnitEmitter(); \ ue.emitInt32(var.size()); \ for (auto& i : var) { \ ue.emitInt32(i.kind); \ ue.emitInt32(i.id); \ } \ } while(0) #define IMPL1_ILA IMPL_ILA(a1) #define IMPL2_ILA IMPL_ILA(a2) #define IMPL3_ILA IMPL_ILA(a3) #define IMPL4_ILA IMPL_ILA(a4) #define IMPL_SLA(var) do { \ auto& ue = getUnitEmitter(); \ ue.emitInt32(var.size()); \ for (auto& i : var) { \ ue.emitInt32(i.str); \ IMPL_BA(*i.dest); \ } \ } while (0) #define IMPL1_SLA IMPL_SLA(a1) #define IMPL2_SLA IMPL_SLA(a2) #define IMPL3_SLA IMPL_SLA(a3) #define IMPL_IVA(var) do { \ getUnitEmitter().emitIVA(var); \ } while (0) #define IMPL1_IVA IMPL_IVA(a1) #define IMPL2_IVA IMPL_IVA(a2) #define IMPL3_IVA IMPL_IVA(a3) #define IMPL4_IVA IMPL_IVA(a4) #define IMPL1_HA IMPL_IVA(a1) #define IMPL2_HA IMPL_IVA(a2) #define IMPL3_HA IMPL_IVA(a3) #define IMPL4_HA IMPL_IVA(a4) #define IMPL1_IA IMPL_IVA(a1) #define IMPL2_IA IMPL_IVA(a2) #define IMPL3_IA IMPL_IVA(a3) #define IMPL4_IA IMPL_IVA(a4) #define IMPL_I64A(var) getUnitEmitter().emitInt64(var) #define IMPL1_I64A IMPL_I64A(a1) #define IMPL2_I64A IMPL_I64A(a2) #define IMPL3_I64A IMPL_I64A(a3) #define IMPL4_I64A IMPL_I64A(a4) #define IMPL_SA(var) \ getUnitEmitter().emitInt32(getUnitEmitter().mergeLitstr(var)) #define IMPL1_SA IMPL_SA(a1) #define IMPL2_SA IMPL_SA(a2) #define IMPL3_SA IMPL_SA(a3) #define IMPL4_SA IMPL_SA(a4) #define IMPL_AA(var) \ getUnitEmitter().emitInt32(getUnitEmitter().mergeArray(var)) #define IMPL1_AA IMPL_AA(a1) #define IMPL2_AA IMPL_AA(a2) #define IMPL3_AA IMPL_AA(a3) #define IMPL4_AA IMPL_AA(a4) #define IMPL_DA(var) getUnitEmitter().emitDouble(var) #define IMPL1_DA IMPL_DA(a1) #define IMPL2_DA IMPL_DA(a2) #define IMPL3_DA IMPL_DA(a3) #define IMPL4_DA IMPL_DA(a4) #define IMPL_BA(var) \ if ((var).getAbsoluteOffset() == InvalidAbsoluteOffset) { \ /* For forward jumps, we store information about the */ \ /* current instruction in the Label. When the Label is */ \ /* set, it will fix up any instructions that reference */ \ /* it, and then it will call recordJumpTarget */ \ (var).bind(getEmitterVisitor(), curPos, getUnitEmitter().bcPos()); \ } else { \ /* For backward jumps, we simply call recordJumpTarget */ \ getEmitterVisitor().recordJumpTarget((var).getAbsoluteOffset()); \ } \ getUnitEmitter().emitInt32((var).getAbsoluteOffset() - curPos); #define IMPL1_BA IMPL_BA(a1) #define IMPL2_BA IMPL_BA(a2) #define IMPL3_BA IMPL_BA(a3) #define IMPL4_BA IMPL_BA(a4) #define IMPL_OA(var) getUnitEmitter().emitByte(var) #define IMPL1_OA IMPL_OA(a1) #define IMPL2_OA IMPL_OA(a2) #define IMPL3_OA IMPL_OA(a3) #define IMPL4_OA IMPL_OA(a4) OPCODES #undef O #undef ONE #undef TWO #undef THREE #undef FOUR #undef NA #undef DEC_MA #undef DEC_IVA #undef DEC_HA #undef DEC_IA #undef DEC_I64A #undef DEC_DA #undef DEC_SA #undef DEC_AA #undef DEC_BA #undef DEC_OA #undef POP_NOV #undef POP_ONE #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_CV #undef POP_VV #undef POP_HV #undef POP_AV #undef POP_RV #undef POP_FV #undef POP_LREST #undef POP_FMANY #undef POP_CVMANY #undef POP_CMANY #undef POP_HA_ONE #undef POP_HA_TWO #undef POP_HA_THREE #undef POP_HA_FOUR #undef POP_HA_NA #undef POP_HA_MA #undef POP_HA_IVA #undef POP_HA_IA #undef POP_HA_I64A #undef POP_HA_DA #undef POP_HA_SA #undef POP_HA_AA #undef POP_HA_BA #undef POP_HA_OA #undef POP_HA_HA #undef PUSH_NOV #undef PUSH_ONE #undef PUSH_TWO #undef PUSH_THREE #undef PUSH_FOUR #undef PUSH_CV #undef PUSH_VV #undef PUSH_HV #undef PUSH_AV #undef PUSH_RV #undef PUSH_FV #undef IMPL_ONE #undef IMPL_TWO #undef IMPL_THREE #undef IMPL_FOUR #undef IMPL_NA #undef IMPL_MA #undef IMPL1_MA #undef IMPL2_MA #undef IMPL3_MA #undef IMPL4_MA #undef IMPL_BLA #undef IMPL1_BLA #undef IMPL2_BLA #undef IMPL3_BLA #undef IMPL4_BLA #undef IMPL_SLA #undef IMPL1_SLA #undef IMPL2_SLA #undef IMPL3_SLA #undef IMPL4_SLA #undef IMPL_ILA #undef IMPL1_ILA #undef IMPL2_ILA #undef IMPL3_ILA #undef IMPL4_ILA #undef IMPL_IVA #undef IMPL1_IVA #undef IMPL2_IVA #undef IMPL3_IVA #undef IMPL4_IVA #undef IMPL1_HA #undef IMPL2_HA #undef IMPL3_HA #undef IMPL4_HA #undef IMPL1_IA #undef IMPL2_IA #undef IMPL3_IA #undef IMPL4_IA #undef IMPL_I64A #undef IMPL1_I64A #undef IMPL2_I64A #undef IMPL3_I64A #undef IMPL4_I64A #undef IMPL_DA #undef IMPL1_DA #undef IMPL2_DA #undef IMPL3_DA #undef IMPL4_DA #undef IMPL_SA #undef IMPL1_SA #undef IMPL2_SA #undef IMPL3_SA #undef IMPL4_SA #undef IMPL_AA #undef IMPL1_AA #undef IMPL2_AA #undef IMPL3_AA #undef IMPL4_AA #undef IMPL_BA #undef IMPL1_BA #undef IMPL2_BA #undef IMPL3_BA #undef IMPL4_BA #undef IMPL_OA #undef IMPL1_OA #undef IMPL2_OA #undef IMPL3_OA #undef IMPL4_OA static void checkJmpTargetEvalStack(const SymbolicStack& source, const SymbolicStack& dest) { if (source.size() != dest.size()) { Logger::Warning("Emitter detected a point in the bytecode where the " "depth of the stack is not the same for all possible " "control flow paths. source size: %d. dest size: %d", source.size(), dest.size()); Logger::Warning("src stack : %s", source.pretty().c_str()); Logger::Warning("dest stack: %s", dest.pretty().c_str()); assert(false); return; } for (unsigned int i = 0; i < source.size(); ++i) { char flavor = StackSym::GetSymFlavor(source.get(i)); bool matches = source.get(i) == dest.get(i) && (flavor != StackSym::L || source.getLoc(i) == dest.getLoc(i)) && (flavor != StackSym::T || source.getName(i) == dest.getName(i)) && (flavor != StackSym::I || source.getInt(i) == dest.getInt(i)); if (!matches) { Logger::Warning("Emitter detected a point in the bytecode where the " "symbolic flavor of a slot on the stack is not the same " "for all possible control flow paths"); Logger::Warning("src stack : %s", source.pretty().c_str()); Logger::Warning("dest stack: %s", dest.pretty().c_str()); assert(false); return; } } } std::string SymbolicStack::pretty() const { std::ostringstream out; out << " [" << std::hex; size_t j = 0; for (size_t i = 0; i < m_symStack.size(); ++i) { while (j < m_actualStack.size() && m_actualStack[j] < int(i)) { ++j; } if (j < m_actualStack.size() && m_actualStack[j] == int(i)) { out << "*"; } out << StackSym::ToString(m_symStack[i].sym); char flavor = StackSym::GetSymFlavor(m_symStack[i].sym); if (flavor == StackSym::L || flavor == StackSym::I) { out << ":" << m_symStack[i].intval; } else if (flavor == StackSym::T) { out << ":" << m_symStack[i].metaData.name->data(); } out << ' '; } return out.str(); } void SymbolicStack::push(char sym) { if (sym != StackSym::W && sym != StackSym::K && sym != StackSym::L && sym != StackSym::T && sym != StackSym::I && sym != StackSym::H) { m_actualStack.push_back(m_symStack.size()); *m_actualStackHighWaterPtr = MAX(*m_actualStackHighWaterPtr, (int)m_actualStack.size()); } m_symStack.push_back(SymEntry(sym)); } void SymbolicStack::pop() { // TODO(drew): assert eval stack unknown is false? assert(!m_symStack.empty()); char sym = m_symStack.back().sym; char flavor = StackSym::GetSymFlavor(sym); if (StackSym::GetMarker(sym) != StackSym::W && flavor != StackSym::L && flavor != StackSym::T && flavor != StackSym::I && flavor != StackSym::H) { assert(!m_actualStack.empty()); m_actualStack.pop_back(); } m_symStack.pop_back(); } char SymbolicStack::top() const { assert(!m_symStack.empty()); return m_symStack.back().sym; } char SymbolicStack::get(int index) const { assert(index >= 0 && index < (int)m_symStack.size()); return m_symStack[index].sym; } const StringData* SymbolicStack::getName(int index) const { assert(index >= 0 && index < (int)m_symStack.size()); if (m_symStack[index].metaType == META_LITSTR) { return m_symStack[index].metaData.name; } return nullptr; } const StringData* SymbolicStack::getClsName(int index) const { assert(index >= 0 && index < (int)m_symStack.size()); return m_symStack[index].className; } bool SymbolicStack::isCls(int index) const { assert(index >= 0 && index < (int)m_symStack.size()); return m_symStack[index].className != nullptr; } void SymbolicStack::setString(const StringData* s) { assert(m_symStack.size()); SymEntry& se = m_symStack.back(); if (se.metaType == META_LITSTR) { assert(se.metaData.name == s); } else { assert(se.metaType == META_NONE); } se.metaData.name = s; se.metaType = META_LITSTR; } void SymbolicStack::setKnownCls(const StringData* s, bool nonNull) { assert(m_symStack.size()); SymEntry& se = m_symStack.back(); assert(!se.className || se.className == s); if (se.metaType == META_DATA_TYPE) { assert(se.metaData.dt == KindOfObject); nonNull = true; } se.className = s; se.notNull = se.notNull || nonNull; } void SymbolicStack::setNotRef() { assert(m_symStack.size()); SymEntry& se = m_symStack.back(); se.notRef = true; } bool SymbolicStack::getNotRef() const { assert(m_symStack.size()); const SymEntry& se = m_symStack.back(); return se.notRef; } void SymbolicStack::setInt(int64_t v) { assert(m_symStack.size()); m_symStack.back().intval = v; } void SymbolicStack::setKnownType(DataType dt, bool predicted /* = false */) { assert(m_symStack.size()); SymEntry& se = m_symStack.back(); if (se.className) { assert(dt == KindOfObject); se.notNull = true; } else { assert(se.metaType == META_NONE); se.metaType = META_DATA_TYPE; se.metaData.dt = dt; } se.dtPredicted = predicted; } DataType SymbolicStack::getKnownType(int index, bool noRef) const { if (index < 0) index += m_symStack.size(); assert((unsigned)index < m_symStack.size()); const SymEntry& se = m_symStack[index]; if (!noRef || se.notRef) { if (se.className && se.notNull) { return KindOfObject; } else if (se.metaType == META_DATA_TYPE) { return se.metaData.dt; } } return KindOfUnknown; } bool SymbolicStack::isTypePredicted(int index /* = -1, stack top */) const { if (index < 0) index += m_symStack.size(); assert((unsigned)index < m_symStack.size()); return m_symStack[index].dtPredicted; } void SymbolicStack::cleanTopMeta() { SymEntry& se = m_symStack.back(); se.clsBaseType = CLS_INVALID; se.metaType = META_NONE; se.notRef = false; } void SymbolicStack::setClsBaseType(ClassBaseType type) { assert(!m_symStack.empty()); m_symStack.back().clsBaseType = type; } void SymbolicStack::setUnnamedLocal(int index, int localId, Offset startOff) { assert(size_t(index) < m_symStack.size()); assert(m_symStack[index].sym == StackSym::K); assert(m_symStack[index].clsBaseType == CLS_UNNAMED_LOCAL); m_symStack[index].intval = localId; m_symStack[index].unnamedLocalStart = startOff; } void SymbolicStack::set(int index, char sym) { assert(index >= 0 && index < (int)m_symStack.size()); // XXX Add assert in debug build to make sure W is not getting // written or overwritten by something else m_symStack[index].sym = sym; } unsigned SymbolicStack::size() const { return m_symStack.size(); } bool SymbolicStack::empty() const { return m_symStack.empty(); } void SymbolicStack::clear() { m_symStack.clear(); m_actualStack.clear(); m_fdescCount = 0; } void SymbolicStack::consumeBelowTop(int depth) { if (int(m_symStack.size()) < depth + 1) { Logger::Warning( "Emitter tried to consumeBelowTop() when the symbolic " "stack did not have enough elements in it."); assert(false); return; } assert(int(m_symStack.size()) >= depth + 1); int index = m_symStack.size() - depth - 1; m_symStack.erase(m_symStack.begin() + index); /* * Update any indexes into the actual stack that pointed to or past * this element. * * (In practice they should all be past---we don't currently ever * remove below the top for actual stack elements.) */ for (size_t i = 0; i < m_actualStack.size(); ++i) { if (m_actualStack[i] >= index) { --m_actualStack[i]; } } } int SymbolicStack::getActualPos(int vpos) const { assert(vpos >= 0 && vpos < int(m_symStack.size())); assert(!m_actualStack.empty()); for (int j = int(m_actualStack.size()) - 1; j >= 0; --j) { if (m_actualStack[j] == vpos) { return j; } } NOT_REACHED(); } char SymbolicStack::getActual(int index) const { assert(index >= 0 && index < (int)m_actualStack.size()); return get(m_actualStack[index]); } void SymbolicStack::setActual(int index, char sym) { assert(index >= 0 && index < (int)m_actualStack.size()); set(m_actualStack[index], sym); } SymbolicStack::ClassBaseType SymbolicStack::getClsBaseType(int index) const { assert(m_symStack.size() > size_t(index)); assert(m_symStack[index].sym == StackSym::K); assert(m_symStack[index].clsBaseType != CLS_INVALID); return m_symStack[index].clsBaseType; } int SymbolicStack::getLoc(int index) const { assert(m_symStack.size() > size_t(index)); assert(StackSym::GetSymFlavor(m_symStack[index].sym) == StackSym::L || m_symStack[index].clsBaseType == CLS_NAMED_LOCAL || m_symStack[index].clsBaseType == CLS_UNNAMED_LOCAL); assert(m_symStack[index].intval != -1); return m_symStack[index].intval; } int64_t SymbolicStack::getInt(int index) const { assert(m_symStack.size() > size_t(index)); assert(StackSym::GetSymFlavor(m_symStack[index].sym) == StackSym::I); return m_symStack[index].intval; } Offset SymbolicStack::getUnnamedLocStart(int index) const { assert(m_symStack.size() > size_t(index)); assert(m_symStack[index].sym == StackSym::K); assert(m_symStack[index].clsBaseType == CLS_UNNAMED_LOCAL); return m_symStack[index].unnamedLocalStart; } // Insert an element in the actual stack at the specified depth of the // actual stack. void SymbolicStack::insertAt(int depth, char sym) { assert(depth <= sizeActual() && depth > 0); int virtIdx = m_actualStack[sizeActual() - depth]; m_symStack.insert(m_symStack.begin() + virtIdx, SymEntry(sym)); m_actualStack.insert(m_actualStack.end() - depth, virtIdx); for (size_t i = sizeActual() - depth + 1; i < m_actualStack.size(); ++i) { ++m_actualStack[i]; } } int SymbolicStack::sizeActual() const { return m_actualStack.size(); } void SymbolicStack::pushFDesc() { m_fdescCount += kNumActRecCells; *m_fdescHighWaterPtr = MAX(*m_fdescHighWaterPtr, m_fdescCount); } void SymbolicStack::popFDesc() { m_fdescCount -= kNumActRecCells; } void Label::set(Emitter& e) { if (isSet()) { InvariantViolation( "Label::set was called more than once on the same " "Label; originally set to %d; now %d", m_off, e.getUnitEmitter().bcPos()); return; } m_off = e.getUnitEmitter().bcPos(); // Fix up any forward jumps that reference to this Label for (std::vector >::const_iterator it = m_emittedOffs.begin(); it != m_emittedOffs.end(); ++it) { e.getUnitEmitter().emitInt32(m_off - it->first, it->second); } EmitterVisitor& ev = e.getEmitterVisitor(); if (!m_emittedOffs.empty()) { // If there were forward jumps that referenced this Label, // compare the the eval stack from the first foward jump we // saw with the current eval stack if (!ev.evalStackIsUnknown()) { checkJmpTargetEvalStack(m_evalStack, ev.getEvalStack()); } else { // Assume the current eval stack matches that of the forward branch ev.setEvalStack(m_evalStack); } // Fix up the EmitterVisitor's table of jump targets ev.recordJumpTarget(m_off, ev.getEvalStack()); } else { // There were no forward jumps that referenced this label ev.prepareEvalStack(); // Fix up the EmitterVisitor's table of jump targets ev.recordJumpTarget(m_off, ev.getEvalStack()); } } bool Label::isUsed() { return (m_off != InvalidAbsoluteOffset || !m_emittedOffs.empty()); } void Label::bind(EmitterVisitor& ev, Offset instrAddr, Offset offAddr) { if (m_off != InvalidAbsoluteOffset) { InvariantViolation("Label::bind was called on a Label that has already " "been set to %d", m_off); return; } bool labelHasEvalStack = !m_emittedOffs.empty(); m_emittedOffs.push_back(std::pair(instrAddr, offAddr)); if (labelHasEvalStack) { checkJmpTargetEvalStack(m_evalStack, ev.getEvalStack()); } else { m_evalStack = ev.getEvalStack(); } } struct FPIRegionRecorder { FPIRegionRecorder(EmitterVisitor* ev, UnitEmitter& ue, SymbolicStack& stack, Offset start) : m_ev(ev), m_ue(ue), m_stack(stack), m_fpOff(m_stack.sizeActual()), m_start(start) { m_stack.pushFDesc(); } ~FPIRegionRecorder() { m_stack.popFDesc(); m_ev->newFPIRegion(m_start, m_ue.bcPos(), m_fpOff); } private: EmitterVisitor* m_ev; UnitEmitter& m_ue; SymbolicStack& m_stack; int m_fpOff; Offset m_start; }; //============================================================================= // EmitterVisitor. void MetaInfoBuilder::add(int pos, Unit::MetaInfo::Kind kind, bool mVector, int arg, Id data) { assert(arg >= 0); if (arg > 127) return; if (mVector) arg |= Unit::MetaInfo::VectorArg; Vec& info = m_metaMap[pos]; int i = info.size(); if (kind == Unit::MetaInfo::Kind::NopOut) { info.clear(); } else if (i == 1 && info[0].m_kind == Unit::MetaInfo::Kind::NopOut) { return; } else if (kind == Unit::MetaInfo::Kind::DataTypeInferred || kind == Unit::MetaInfo::Kind::DataTypePredicted) { // Put DataType first, because if applyInputMetaData saw Class // first, it would call recordRead which mark the input as // needing a guard before we saw the DataType i = 0; } info.insert(info.begin() + i, Unit::MetaInfo(kind, arg, data)); } void MetaInfoBuilder::addKnownDataType(DataType dt, bool dtPredicted, int pos, bool mVector, int arg) { if (dt != KindOfUnknown) { Unit::MetaInfo::Kind dtKind = (dtPredicted ? Unit::MetaInfo::Kind::DataTypePredicted : Unit::MetaInfo::Kind::DataTypeInferred); add(pos, dtKind, mVector, arg, dt); } } void MetaInfoBuilder::deleteInfo(Offset bcOffset) { m_metaMap.erase(bcOffset); } void MetaInfoBuilder::setForUnit(UnitEmitter& target) const { int entries = m_metaMap.size(); if (!entries) return; vector index1; vector index2; vector data; index1.push_back(entries); size_t sz1 = (2 + entries) * sizeof(Offset); size_t sz2 = (1 + entries) * sizeof(Offset); for (Map::const_iterator it = m_metaMap.begin(), end = m_metaMap.end(); it != end; ++it) { index1.push_back(it->first); index2.push_back(sz1 + sz2 + data.size()); const Vec& v = it->second; assert(v.size()); for (unsigned i = 0; i < v.size(); i++) { const Unit::MetaInfo& mi = v[i]; data.push_back(static_cast(mi.m_kind)); data.push_back(mi.m_arg); if (mi.m_data < 0x80) { data.push_back(mi.m_data << 1); } else { union { uint32_t val; uint8_t bytes[4]; } u; u.val = (mi.m_data << 1) | 1; for (int j = 0; j < 4; j++) { data.push_back(u.bytes[j]); } } } } index1.push_back(INT_MAX); index2.push_back(sz1 + sz2 + data.size()); size_t size = sz1 + sz2 + data.size(); uint8_t* meta = (uint8_t*)malloc(size); memcpy(meta, &index1[0], sz1); memcpy(meta + sz1, &index2[0], sz2); memcpy(meta + sz1 + sz2, &data[0], data.size()); target.setBcMeta(meta, size); free(meta); } EmitterVisitor::EmittedClosures EmitterVisitor::s_emittedClosures; EmitterVisitor::EmitterVisitor(UnitEmitter& ue) : m_ue(ue), m_curFunc(ue.getMain()), m_evalStackIsUnknown(false), m_actualStackHighWater(0), m_fdescHighWater(0) { m_prevOpcode = OpLowInvalid; m_evalStack.m_actualStackHighWaterPtr = &m_actualStackHighWater; m_evalStack.m_fdescHighWaterPtr = &m_fdescHighWater; } EmitterVisitor::~EmitterVisitor() { // If a fatal occurs during emission, some extra cleanup is necessary. for (std::deque::const_iterator it = m_exnHandlers.begin(); it != m_exnHandlers.end(); ++it) { delete *it; } } bool EmitterVisitor::checkIfStackEmpty(const char* forInstruction) const { if (m_evalStack.empty()) { InvariantViolation("Emitter tried to emit a %s instruction when the " "evaluation stack is empty (at offset %d)", forInstruction, m_ue.bcPos()); return true; } return false; } void EmitterVisitor::unexpectedStackSym(char sym, const char* where) const { InvariantViolation("Emitter encountered an unexpected StackSym \"%s\"" " in %s() (at offset %d)", StackSym::ToString(sym).c_str(), where, m_ue.bcPos()); } void EmitterVisitor::popEvalStack(char expected, int arg, int pos) { // Pop a value off of the evaluation stack, and verify that it // matches the specified symbolic flavor if (m_evalStack.size() == 0) { InvariantViolation("Emitter emitted an instruction that tries to consume " "a value from the stack when the stack is empty " "(expected symbolic flavor \"%s\" at offset %d)", StackSym::ToString(expected).c_str(), m_ue.bcPos()); return; } if (arg >= 0 && pos >= 0 && (expected == StackSym::C || expected == StackSym::R)) { m_metaInfo.addKnownDataType(m_evalStack.getKnownType(), m_evalStack.isTypePredicted(), pos, false, arg); } char sym = m_evalStack.top(); char actual = StackSym::GetSymFlavor(sym); m_evalStack.pop(); if (actual != expected) { InvariantViolation( "Emitter emitted an instruction that tries to consume a " "value from the stack when the top of the stack does not " "match the symbolic flavor that the instruction expects " "(expected symbolic flavor \"%s\", actual symbolic flavor \"%s\" " "at offset %d)", StackSym::ToString(expected).c_str(), StackSym::ToString(actual).c_str(), m_ue.bcPos()); } } void EmitterVisitor::popSymbolicLocal(Op op, int arg, int pos) { // Special case for instructions that consume the loc below the // top. int belowTop = -1; if (op == OpCGetL3) { belowTop = 3; } else if (op == OpCGetL2) { belowTop = 2; } if (belowTop != -1) { char symFlavor = StackSym::GetSymFlavor( m_evalStack.get(m_evalStack.size() - belowTop)); if (symFlavor != StackSym::L) { InvariantViolation("Operation tried to remove a local below the top of" " the symbolic stack but instead found \"%s\"", StackSym::ToString(symFlavor).c_str()); } m_evalStack.consumeBelowTop(belowTop - 1); } else { if (arg >= 0 && pos >= 0) { m_metaInfo.addKnownDataType(m_evalStack.getKnownType(), m_evalStack.isTypePredicted(), pos, false, arg); } popEvalStack(StackSym::L); } } void EmitterVisitor::popEvalStackLMany() { while (!m_evalStack.empty()) { char sym = m_evalStack.top(); char symFlavor = StackSym::GetSymFlavor(sym); char marker = StackSym::GetMarker(sym); if (marker == StackSym::E || marker == StackSym::P) { if (symFlavor != StackSym::C && symFlavor != StackSym::L && symFlavor != StackSym::T && symFlavor != StackSym::I) { InvariantViolation( "Emitter emitted an instruction that tries to consume " "a value from the stack when the top of the stack " "does not match the symbolic flavor that the instruction " "expects (expected symbolic flavor \"C\", \"L\", \"T\", or \"I\", " "actual symbolic flavor \"%s\" at offset %d)", StackSym::ToString(symFlavor).c_str(), m_ue.bcPos()); } } else if (marker == StackSym::W) { if (symFlavor != StackSym::None) { InvariantViolation( "Emitter emitted an instruction that tries to consume " "a value from the stack when the top of the stack " "does not match the symbolic flavor that the instruction " "expects (expected symbolic flavor \"None\", actual " "symbolic flavor \"%s\" at offset %d)", StackSym::ToString(symFlavor).c_str(), m_ue.bcPos()); } } else if (marker == StackSym::M) { assert(symFlavor == StackSym::A); } else { break; } m_evalStack.pop(); } if (m_evalStack.empty()) { InvariantViolation("Emitter emitted an instruction that tries to consume " "a value from the stack when the stack is empty " "(at offset %d)", m_ue.bcPos()); return; } char sym = m_evalStack.top(); char symFlavor = StackSym::GetSymFlavor(sym); m_evalStack.pop(); if (symFlavor != StackSym::C && symFlavor != StackSym::L && symFlavor != StackSym::R && symFlavor != StackSym::H) { InvariantViolation( "Emitter emitted an instruction that tries to consume a " "value from the stack when the top of the stack does not " "match the symbolic flavor that the instruction expects " "(expected symbolic flavor \"C\", \"L\", \"R\", or \"H\", actual " "symbolic flavor \"%s\" at offset %d)", StackSym::ToString(symFlavor).c_str(), m_ue.bcPos()); } } void EmitterVisitor::popEvalStackMany(int len, char symFlavor) { for (int i = 0; i < len; ++i) { popEvalStack(symFlavor); } } void EmitterVisitor::popEvalStackCVMany(int len) { for (int i = 0; i < len; i++) { if (m_evalStack.size() == 0) { InvariantViolation("Emitter emitted an instruction that tries to consume " "a value from the stack when the stack is empty " "(expected symbolic flavor C or V at offset %d)", m_ue.bcPos()); return; } char sym = m_evalStack.top(); char actual = StackSym::GetSymFlavor(sym); m_evalStack.pop(); if (actual != StackSym::C && actual != StackSym::V) { InvariantViolation( "Emitter emitted an instruction that tries to consume a " "value from the stack when the top of the stack does not " "match the symbolic flavor that the instruction expects " "(expected symbolic flavor C or V, actual symbolic flavor \"%s\" " "at offset %d)", StackSym::ToString(actual).c_str(), m_ue.bcPos()); } } } void EmitterVisitor::pushEvalStack(char symFlavor) { // Push a value from the evaluation stack with the specified // symbolic flavor m_evalStack.push(symFlavor); } void EmitterVisitor::peekEvalStack(char expected, int depthActual) { int posActual = (m_evalStack.sizeActual() - depthActual - 1); if (posActual >= 0 && posActual < (int)m_evalStack.sizeActual()) { char sym = m_evalStack.getActual(posActual); char actual = StackSym::GetSymFlavor(sym); if (actual != expected) { InvariantViolation( "Emitter emitted an instruction that tries to consume a " "value from the stack whose symbolic flavor does not match " "the symbolic flavor that the instruction expects (expected " "symbolic flavor \"%s\", actual symbolic flavor \"%s\" at " "offset %d)", StackSym::ToString(expected).c_str(), StackSym::ToString(actual).c_str(), m_ue.bcPos()); } } } void EmitterVisitor::pokeEvalStack(char symFlavor, int depthActual) { int sizeActual = m_evalStack.sizeActual(); int posActual = sizeActual - depthActual - 1; if (posActual >= 0 && posActual < sizeActual) { m_evalStack.setActual(posActual, symFlavor); } } /* * Prior to making any changes to the evaluation stack in between * instructions, this function should be called. * * What this handles is recording the evaluation stack state at * instruction boundaries so that jumps know what their stack state * will be at the destination. * * When m_evalStackIsUnknown, it means we have hit a place in the * bytecode where the offset cannot be reached via fallthrough, but no * forward jumps targeted it either. In this case, the stack must be * empty, and we need to record that this is the case so that later * backward jumps can check this is the case at their jump site. */ void EmitterVisitor::prepareEvalStack() { if (m_evalStackIsUnknown) { if (!m_evalStack.empty()) { InvariantViolation("Emitter expected to have an empty evaluation " "stack because the eval stack was unknown, but " "it was non-empty."); return; } // Record that we are assuming that the eval stack is empty recordJumpTarget(m_ue.bcPos(), m_evalStack); m_evalStackIsUnknown = false; } } void EmitterVisitor::recordJumpTarget(Offset target, const SymbolicStack& evalStack) { if (target == InvalidAbsoluteOffset) { InvariantViolation( "Offset passed to EmitterVisitor::recordJumpTarget was invalid"); } hphp_hash_map::iterator it = m_jumpTargetEvalStacks.find(target); if (it == m_jumpTargetEvalStacks.end()) { m_jumpTargetEvalStacks[target] = evalStack; return; } checkJmpTargetEvalStack(evalStack, it->second); } void EmitterVisitor::restoreJumpTargetEvalStack() { m_evalStack.clear(); hphp_hash_map::iterator it = m_jumpTargetEvalStacks.find(m_ue.bcPos()); if (it == m_jumpTargetEvalStacks.end()) { m_evalStackIsUnknown = true; return; } m_evalStack = it->second; } void EmitterVisitor::recordCall() { m_curFunc->setContainsCalls(); } bool EmitterVisitor::isJumpTarget(Offset target) { // Returns true iff one of the following conditions is true: // 1) We have seen an instruction that jumps to the specified offset // 2) We know of a Label that has been set to the specified offset // 3) We have seen a try region that ends at the specified offset hphp_hash_map::iterator it = m_jumpTargetEvalStacks.find(target); return (it != m_jumpTargetEvalStacks.end()); } #define CONTROL_BODY(brk, cnt) \ ControlTargetPusher _cop(this, -1, false, brk, cnt) #define FOREACH_BODY(itId, itRef, brk, cnt) \ ControlTargetPusher _cop(this, itId, itRef, brk, cnt) class IterFreeThunklet : public Thunklet { public: IterFreeThunklet(Id iterId, bool itRef) : m_id(iterId), m_itRef(itRef) {} virtual void emit(Emitter& e) { if (m_itRef) { e.MIterFree(m_id); } else { e.IterFree(m_id); } e.Unwind(); } private: Id m_id; bool m_itRef; }; /** * A thunklet for the fault region protecting a silenced (@) expression. */ class RestoreErrorReportingThunklet : public Thunklet { public: explicit RestoreErrorReportingThunklet(Id loc) : m_oldLevelLoc(loc) {} virtual void emit(Emitter& e) { e.getEmitterVisitor().emitRestoreErrorReporting(e, m_oldLevelLoc); e.Unwind(); } private: Id m_oldLevelLoc; }; class UnsetUnnamedLocalThunklet : public Thunklet { public: explicit UnsetUnnamedLocalThunklet(Id loc) : m_loc(loc) {} virtual void emit(Emitter& e) { e.getEmitterVisitor().emitVirtualLocal(m_loc); e.getEmitterVisitor().emitUnset(e); e.Unwind(); } private: Id m_loc; }; /** * Helper to deal with emitting list assignment and keeping track of some * associated info. A list assignment can be thought of as a list of "index * chains"; that is, sequences of indices that should be accessed for each * bottom-level expression in the list assignment. We recursively walk down the * LHS, building up index chains and copying them into the top-level list as we * reach the leaves of the tree. */ void EmitterVisitor::visitListAssignmentLHS(Emitter& e, ExpressionPtr exp, IndexChain& indexChain, std::vector& all) { if (!exp) { // Empty slot return; } if (exp->is(Expression::KindOfListAssignment)) { // Nested assignment ListAssignmentPtr la = static_pointer_cast(exp); ExpressionListPtr lhs = la->getVariables(); int n = lhs->getCount(); for (int i = 0; i < n; ++i) { indexChain.push_back(i); visitListAssignmentLHS(e, (*lhs)[i], indexChain, all); indexChain.pop_back(); } } else { // Reached a "leaf". Lock in this index chain and deal with this exp. assert(!indexChain.empty()); all.push_back(new IndexChain(indexChain)); visit(exp); emitClsIfSPropBase(e); } } /** * visitIfCondition() serves as a helper method for visiting the condition * of an "if" statement. This function recursively visits each node in an * expression, keeping track of where to jump if an "and" or "or" expression * short circuits. If an expression other than "and", "or", or "not" is * encountered, this method calls EmitterVisitor::visit() to handle the * expression. */ void EmitterVisitor::visitIfCondition( ExpressionPtr cond, Emitter& e, Label& tru, Label& fals, bool truFallthrough) { BinaryOpExpressionPtr binOpNode( dynamic_pointer_cast(cond)); if (binOpNode) { int op = binOpNode->getOp(); // Short circuit && and || if (op == T_LOGICAL_OR || op == T_LOGICAL_AND || op == T_BOOLEAN_OR || op == T_BOOLEAN_AND) { bool isOr = (op == T_LOGICAL_OR || op == T_BOOLEAN_OR); Label localLabel; ExpressionPtr lhs = binOpNode->getExp1(); if (isOr) { Emitter lhsEmitter(lhs, m_ue, *this); visitIfCondition(lhs, lhsEmitter, tru, localLabel, false); // falls through if that condition was false } else { Emitter lhsEmitter(lhs, m_ue, *this); visitIfCondition(lhs, lhsEmitter, localLabel, fals, true); // falls through if that condition was true } if (localLabel.isUsed()) { localLabel.set(e); } if (currentPositionIsReachable()) { ExpressionPtr rhs = binOpNode->getExp2(); Emitter rhsEmitter(rhs, m_ue, *this); visitIfCondition(rhs, rhsEmitter, tru, fals, truFallthrough); } return; } } UnaryOpExpressionPtr unOpNode(dynamic_pointer_cast(cond)); if (unOpNode) { int op = unOpNode->getOp(); // Logical not if (op == '!') { ExpressionPtr val = unOpNode->getExpression(); Emitter valEmitter(val, m_ue, *this); visitIfCondition(val, valEmitter, fals, tru, !truFallthrough); return; } } Variant val; if (cond->getScalarValue(val)) { if (truFallthrough) { if (!val.toBoolean()) e.Jmp(fals); } else { if (val.toBoolean()) e.Jmp(tru); } return; } visit(cond); emitConvertToCell(e); if (truFallthrough) { e.JmpZ(fals); } else { e.JmpNZ(tru); } } void EmitterVisitor::assignLocalVariableIds(FunctionScopePtr fs) { VariableTablePtr variables = fs->getVariables(); std::vector localNames; variables->getLocalVariableNames(localNames); for (int i = 0; i < (int)localNames.size(); ++i) { StringData* nLiteral = StringData::GetStaticString(localNames[i].c_str()); m_curFunc->allocVarId(nLiteral); } } void EmitterVisitor::visit(FileScopePtr file) { const std::string& filename = file->getName(); m_ue.setFilepath(StringData::GetStaticString(filename)); FunctionScopePtr func(file->getPseudoMain()); if (!func) return; m_file = file; // Assign ids to all of the local variables eagerly. This gives us the // nice property that all named local variables will be assigned ids // 0 through k-1, while any unnamed local variable will have an id >= k. assignLocalVariableIds(func); AnalysisResultPtr ar(file->getContainingProgram()); assert(ar); MethodStatementPtr m(dynamic_pointer_cast(func->getStmt())); if (!m) return; StatementListPtr stmts(m->getStmts()); if (!stmts) return; Emitter e(m, m_ue, *this); int i, nk = stmts->getCount(); for (i = 0; i < nk; i++) { StatementPtr s = (*stmts)[i]; if (MethodStatementPtr meth = dynamic_pointer_cast(s)) { // Emit afterwards postponeMeth(meth, nullptr, true); } } { FunctionScopePtr fsp = m->getFunctionScope(); if (fsp->needsLocalThis()) { static const StringData* thisStr = StringData::GetStaticString("this"); Id thisId = m_curFunc->lookupVarId(thisStr); e.InitThisLoc(thisId); } FuncFinisher ff(this, e, m_curFunc); TypedValue mainReturn; mainReturn.m_type = KindOfInvalid; bool notMergeOnly = false; PreClass::Hoistable allHoistable = PreClass::AlwaysHoistable; for (i = 0; i < nk; i++) { StatementPtr s = (*stmts)[i]; switch (s->getKindOf()) { case Statement::KindOfMethodStatement: case Statement::KindOfFunctionStatement: break; case Statement::KindOfInterfaceStatement: case Statement::KindOfClassStatement: { // Handle classes directly here, since only top-level classes are // hoistable. ClassScopePtr cNode = s->getClassScope(); PreClass::Hoistable h = emitClass(e, cNode, true); if (h < allHoistable) allHoistable = h; break; } case Statement::KindOfTypedefStatement: emitTypedef(e, static_pointer_cast(s)); notMergeOnly = true; // TODO(#2103206): typedefs should be mergable break; case Statement::KindOfReturnStatement: if (mainReturn.m_type != KindOfInvalid) break; if (notMergeOnly) { tvWriteUninit(&mainReturn); m_ue.returnSeen(); goto fail; } else { ReturnStatementPtr r(static_pointer_cast(s)); Variant v((Variant::NullInit())); if (r->getRetExp() && !r->getRetExp()->getScalarValue(v)) { tvWriteUninit(&mainReturn); goto fail; } if (v.isString()) { v = String(StringData::GetStaticString(v.asCStrRef().get())); } else if (v.isArray()) { v = Array(ArrayData::GetScalarArray(v.asCArrRef().get())); } else { assert(!IS_REFCOUNTED_TYPE(v.getType())); } mainReturn = *v.asCell(); m_ue.returnSeen(); } break; case Statement::KindOfExpStatement: if (mainReturn.m_type == KindOfInvalid) { ExpressionPtr e = static_pointer_cast(s)->getExpression(); switch (e->getKindOf()) { case Expression::KindOfSimpleFunctionCall: { SimpleFunctionCallPtr func = static_pointer_cast(e); StringData *name; TypedValue tv; if (func->isSimpleDefine(&name, &tv)) { UnitMergeKind k = func->isDefineWithoutImpl(ar) ? UnitMergeKindPersistentDefine : UnitMergeKindDefine; if (tv.m_type == KindOfUninit) { tv.m_type = KindOfNull; } m_ue.pushMergeableDef(k, name, tv); visit(s); continue; } break; } case Expression::KindOfAssignmentExpression: { AssignmentExpressionPtr ae( static_pointer_cast(e)); StringData *name; TypedValue tv; if (ae->isSimpleGlobalAssign(&name, &tv)) { m_ue.pushMergeableDef(UnitMergeKindGlobal, name, tv); visit(s); continue; } break; } case Expression::KindOfIncludeExpression: { IncludeExpressionPtr inc = static_pointer_cast(e); if (inc->isReqLit()) { if (FileScopeRawPtr f = inc->getIncludedFile(ar)) { if (StatementListPtr sl = f->getStmt()) { FunctionScopeRawPtr ps DEBUG_ONLY = sl->getFunctionScope(); assert(ps && ps->inPseudoMain()); UnitMergeKind kind = UnitMergeKindReqDoc; m_ue.pushMergeableInclude( kind, StringData::GetStaticString(inc->includePath())); visit(s); continue; } } } break; } default: break; } } // fall through default: if (mainReturn.m_type != KindOfInvalid) break; // fall through fail: notMergeOnly = true; visit(s); } } if (mainReturn.m_type == KindOfInvalid) { tvWriteUninit(&mainReturn); tvAsVariant(&mainReturn) = 1; } m_ue.setMainReturn(&mainReturn); m_ue.setMergeOnly(!notMergeOnly); // Pseudo-main returns the integer value 1 by default. If the // current position in the bytecode is reachable, emit code to // return 1. if (currentPositionIsReachable()) { e.Int(1); e.RetC(); } } if (!m_evalStack.empty()) { InvariantViolation("Eval stack was not empty as expected before " "emitPostponed* phase"); } // Method bodies emitPostponedMeths(); emitPostponedCtors(); emitPostponedPinits(); emitPostponedSinits(); emitPostponedCinits(); Peephole peephole(m_ue, m_metaInfo); m_metaInfo.setForUnit(m_ue); } static StringData* getClassName(ExpressionPtr e) { ClassScopeRawPtr cls; if (e->isThis()) { cls = e->getOriginalClass(); if (TypePtr t = e->getAssertedType()) { if (t->isSpecificObject()) { AnalysisResultConstPtr ar = e->getScope()->getContainingProgram(); ClassScopeRawPtr c2 = t->getClass(ar, e->getScope()); if (c2 && c2->derivesFrom(ar, cls->getName(), true, false)) { cls = c2; } } } } else if (TypePtr t = e->getActualType()) { if (t->isSpecificObject()) { cls = t->getClass(e->getScope()->getContainingProgram(), e->getScope()); } } if (cls && !cls->isTrait()) { return StringData::GetStaticString(cls->getOriginalName()); } return nullptr; } static DataType getPredictedDataType(ExpressionPtr expr) { if (!expr->maybeInited()) { return KindOfUninit; } // Note that expr->isNonNull() may be false, // but that's ok since this is just a prediction. TypePtr act = expr->getActualType(); if (!act) { return KindOfUnknown; } return act->getDataType(); } void EmitterVisitor::fixReturnType(Emitter& e, FunctionCallPtr fn, Func* builtinFunc) { int ref = -1; if (fn->hasAnyContext(Expression::RefValue | Expression::DeepReference | Expression::LValue | Expression::OprLValue | Expression::UnsetContext)) { return; } bool voidReturn = false; if (builtinFunc) { ref = (builtinFunc->info()->attribute & ClassInfo::IsReference) != 0; voidReturn = builtinFunc->info()->returnType == KindOfNull; } else if (fn->isValid() && fn->getFuncScope()) { ref = fn->getFuncScope()->isRefReturn(); if (!(fn->getFuncScope()->getReturnType())) { voidReturn = true; } } else if (!fn->getName().empty()) { FunctionScope::FunctionInfoPtr fi = FunctionScope::GetFunctionInfo(fn->getName()); if (!fi || !fi->getMaybeRefReturn()) ref = false; } if (!fn->isUnused() && ref >= 0 && (!ref || !fn->hasAnyContext(Expression::AccessContext | Expression::ObjectContext))) { /* we dont support V in M-vectors, so leave it as an R in that case */ assert(m_evalStack.get(m_evalStack.size() - 1) == StackSym::R); m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::Kind::NopOut, false, 0, 0); if (ref) { e.BoxR(); } else { e.UnboxR(); } } if (voidReturn) { m_evalStack.setKnownType(KindOfNull, false /* inferred */); m_evalStack.setNotRef(); } else if (!ref) { DataType dt = builtinFunc ? builtinFunc->info()->returnType : getPredictedDataType(fn); if (dt != KindOfUnknown) { if (builtinFunc) { switch (dt) { case KindOfBoolean: case KindOfInt64: case KindOfDouble: /* inferred */ m_evalStack.setKnownType(dt, false); break; default: /* predicted */ m_evalStack.setKnownType(dt, true); break; } } else { m_evalStack.setKnownType(dt, true /* predicted */); } } m_evalStack.setNotRef(); } } void EmitterVisitor::visitKids(ConstructPtr c) { for (int i = 0, nk = c->getKidCount(); i < nk; i++) { ConstructPtr kid(c->getNthKid(i)); visit(kid); } } /* * isTupleInit() returns true if this expression list looks like a vanilla * tuple-shaped array with no keys, no ref values; e.g. array(x,y,z), * where we can NewTuple to create the array. In that case the elements are * pushed on the stack, so we arbitrarily limit this to a small multiple of * HphpArray::SmallSize (12). */ bool isTupleInit(ExpressionPtr init_expr, int* cap) { if (init_expr->getKindOf() != Expression::KindOfExpressionList) return false; ExpressionListPtr el = static_pointer_cast(init_expr); int n = el->getCount(); if (n < 1 || n > int(4 * HphpArray::SmallSize)) return false; for (int i = 0, n = el->getCount(); i < n; ++i) { ExpressionPtr ex = (*el)[i]; if (ex->getKindOf() != Expression::KindOfArrayPairExpression) return false; ArrayPairExpressionPtr ap = static_pointer_cast(ex); if (ap->getName() != nullptr || ap->isRef()) return false; } *cap = n; return true; } bool EmitterVisitor::visit(ConstructPtr node) { bool ret = visitImpl(node); if (!Option::WholeProgram || !ret) return ret; ExpressionPtr e = boost::dynamic_pointer_cast(node); if (!e || e->isScalar()) return ret; DataType dt = KindOfUnknown; if (!e->maybeInited()) { dt = KindOfUninit; } else if (node->isNonNull()) { TypePtr act = e->getActualType(); if (!act) return ret; dt = act->getDataType(); if (dt == KindOfUnknown) return ret; } else { return ret; } char sym = m_evalStack.top(); if (StackSym::GetMarker(sym)) return ret; switch (StackSym::GetSymFlavor(sym)) { case StackSym::C: m_evalStack.setNotRef(); m_evalStack.setKnownType(dt); break; case StackSym::L: if (dt == KindOfUninit || !e->maybeRefCounted() || (e->is(Expression::KindOfSimpleVariable) && !static_pointer_cast(e)->couldBeAliased())) { m_evalStack.setNotRef(); } m_evalStack.setKnownType(dt); break; } return ret; } void EmitterVisitor::emitFatal(Emitter& e, const char* message) { const StringData* msg = StringData::GetStaticString(message); e.String(msg); e.Fatal(0); } bool EmitterVisitor::visitImpl(ConstructPtr node) { if (!node) return false; Emitter e(node, m_ue, *this); if (StatementPtr s = dynamic_pointer_cast(node)) { switch (s->getKindOf()) { case Statement::KindOfBlockStatement: case Statement::KindOfStatementList: visitKids(node); return false; case Statement::KindOfTypedefStatement: { emitFatal(e, "Type statements are currently only allowed at " "the top-level"); return false; } case Statement::KindOfContinueStatement: case Statement::KindOfBreakStatement: { BreakStatementPtr bs(static_pointer_cast(s)); uint64_t destLevel = bs->getDepth() - 1; if (bs->getDepth() > m_controlTargets.size()) { std::ostringstream msg; msg << "Cannot break/continue " << bs->getDepth() << " level"; if (bs->getDepth() > 1) { msg << "s"; } e.String(StringData::GetStaticString(msg.str())); e.Fatal(0); return false; } if (bs->is(Statement::KindOfBreakStatement)) { // break N levels for a break emitIterBreak(e, destLevel+1, m_controlTargets[destLevel].m_brkTarg); } else { // break N-1 levels for a continue emitIterBreak(e, destLevel, m_controlTargets[destLevel].m_cntTarg); } return false; } case Statement::KindOfDoStatement: { DoStatementPtr ds(static_pointer_cast(s)); Label top(e); Label condition; Label exit; { CONTROL_BODY(exit, condition); visit(ds->getBody()); } condition.set(e); { ExpressionPtr c = ds->getCondExp(); Emitter condEmitter(c, m_ue, *this); visitIfCondition(c, condEmitter, top, exit, false); } if (exit.isUsed()) exit.set(e); return false; } case Statement::KindOfCaseStatement: { // Should never be called. Handled in visitSwitch. not_reached(); } case Statement::KindOfCatchStatement: { // Store the current exception object in the appropriate local variable CatchStatementPtr cs(static_pointer_cast(node)); StringData* vName = StringData::GetStaticString(cs->getVariable()->getName()); Id i = m_curFunc->lookupVarId(vName); emitVirtualLocal(i); e.Catch(); emitSet(e); emitPop(e); visit(cs->getStmt()); return false; } case Statement::KindOfEchoStatement: { EchoStatementPtr es(static_pointer_cast(node)); ExpressionListPtr exps = es->getExpressionList(); int count = exps->getCount(); for (int i = 0; i < count; i++) { visit((*exps)[i]); emitConvertToCell(e); e.Print(); e.PopC(); } return false; } case Statement::KindOfExpStatement: { ExpStatementPtr es(static_pointer_cast(s)); if (visit(es->getExpression())) { emitPop(e); } return false; } case Statement::KindOfForStatement: { ForStatementPtr fs(static_pointer_cast(s)); if (visit(fs->getInitExp())) { emitPop(e); } Label preCond(e); Label preInc; Label fail; if (ExpressionPtr condExp = fs->getCondExp()) { Label tru; Emitter condEmitter(condExp, m_ue, *this); visitIfCondition(condExp, condEmitter, tru, fail, true); if (tru.isUsed()) tru.set(e); } { CONTROL_BODY(fail, preInc); visit(fs->getBody()); } preInc.set(e); if (visit(fs->getIncExp())) { emitPop(e); } e.Jmp(preCond); if (fail.isUsed()) fail.set(e); return false; } case Statement::KindOfForEachStatement: { ForEachStatementPtr fe(static_pointer_cast(node)); emitForeach(e, fe); return false; } case Statement::KindOfGlobalStatement: { ExpressionListPtr vars( static_pointer_cast(node)->getVars()); for (int i = 0, n = vars->getCount(); i < n; i++) { ExpressionPtr var((*vars)[i]); if (var->is(Expression::KindOfSimpleVariable)) { SimpleVariablePtr sv(static_pointer_cast(var)); if (sv->isSuperGlobal()) { continue; } StringData* nLiteral = StringData::GetStaticString(sv->getName()); Id i = m_curFunc->lookupVarId(nLiteral); emitVirtualLocal(i); e.String(nLiteral); markGlobalName(e); e.VGetG(); emitBind(e); e.PopV(); } else if (var->is(Expression::KindOfDynamicVariable)) { // global $ =& $GLOBALS[] DynamicVariablePtr dv(static_pointer_cast(var)); // Get the variable name as a cell, for the LHS visit(dv->getSubExpression()); emitConvertToCell(e); // Copy the variable name, for indexing into $GLOBALS e.Dup(); markNameSecond(e); markGlobalName(e); e.VGetG(); e.BindN(); e.PopV(); } else { not_implemented(); } } return false; } case Statement::KindOfIfStatement: { IfStatementPtr ifp(static_pointer_cast(node)); StatementListPtr branches(ifp->getIfBranches()); int nb = branches->getCount(); Label done; for (int i = 0; i < nb; i++) { IfBranchStatementPtr branch( static_pointer_cast((*branches)[i])); Label fals; if (branch->getCondition()) { Label tru; Emitter condEmitter(branch->getCondition(), m_ue, *this); visitIfCondition(branch->getCondition(), condEmitter, tru, fals, true); if (tru.isUsed()) { tru.set(e); } } visit(branch->getStmt()); if (currentPositionIsReachable() && i + 1 < nb) { e.Jmp(done); } if (fals.isUsed()) { fals.set(e); } } if (done.isUsed()) { done.set(e); } return false; } case Statement::KindOfIfBranchStatement: not_reached(); // handled by KindOfIfStatement case Statement::KindOfReturnStatement: { ReturnStatementPtr r(static_pointer_cast(node)); bool retV = false; if (visit(r->getRetExp())) { if (r->getRetExp()->getContext() & Expression::RefValue) { emitConvertToVar(e); emitFreePendingIters(e); retV = true; } else { emitConvertToCell(e); emitFreePendingIters(e); } } else { emitFreePendingIters(e); e.Null(); } if (m_curFunc->isGenerator()) { assert(!retV); assert(m_evalStack.size() == 1); e.ContRetC(); return false; } if (r->isGuarded()) { m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::Kind::GuardedThis, false, 0, 0); } // XXX: disabled until static analysis is more reliable: t2225399 /*for (auto& l : r->nonRefcountedLocals()) { auto v = m_curFunc->lookupVarId(StringData::GetStaticString(l)); m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::Kind::NonRefCounted, false, 0, v); }*/ if (retV) { e.RetV(); } else { e.RetC(); } return false; } case Statement::KindOfStaticStatement: { ExpressionListPtr vars( static_pointer_cast(node)->getVars()); for (int i = 0, n = vars->getCount(); i < n; i++) { ExpressionPtr se((*vars)[i]); assert(se->is(Expression::KindOfAssignmentExpression)); AssignmentExpressionPtr ae( static_pointer_cast(se)); ExpressionPtr var(ae->getVariable()); ExpressionPtr value(ae->getValue()); assert(var->is(Expression::KindOfSimpleVariable)); SimpleVariablePtr sv(static_pointer_cast(var)); StringData* name = StringData::GetStaticString(sv->getName()); Id local = m_curFunc->lookupVarId(name); Func::SVInfo svInfo; svInfo.name = name; std::ostringstream os; CodeGenerator cg(&os, CodeGenerator::PickledPHP); AnalysisResultPtr ar(new AnalysisResult()); value->outputPHP(cg, ar); svInfo.phpCode = StringData::GetStaticString(os.str()); m_curFunc->addStaticVar(svInfo); if (value->isScalar()) { visit(value); emitConvertToCell(e); e.StaticLocInit(local, name); } else { Label done; e.StaticLoc(local, name); e.JmpNZ(done); emitVirtualLocal(local); visit(value); emitConvertToCell(e); emitSet(e); emitPop(e); done.set(e); } } return false; } case Statement::KindOfSwitchStatement: { SwitchStatementPtr sw(static_pointer_cast(node)); StatementListPtr cases(sw->getCases()); if (!cases) { visit(sw->getExp()); emitPop(e); return false; } uint ncase = cases->getCount(); std::vector