hphp/runtime/vm/hhbc.cpp (1,006 lines of code) (raw):
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-present 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/hhbc.h"
#include <type_traits>
#include <sstream>
#include <cstring>
#include "hphp/runtime/base/array-iterator.h"
#include "hphp/runtime/base/repo-auth-type-array.h"
#include "hphp/runtime/base/repo-auth-type-codec.h"
#include "hphp/runtime/base/stats.h"
#include "hphp/runtime/base/zend-string.h"
#include "hphp/runtime/vm/class-meth-data-ref.h"
#include "hphp/runtime/vm/func-emitter.h"
#include "hphp/runtime/vm/hhbc-codec.h"
#include "hphp/runtime/vm/member-key.h"
#include "hphp/runtime/vm/repo-global-data.h"
#include "hphp/runtime/vm/unit.h"
#include "hphp/runtime/vm/unit-emitter.h"
#include "hphp/runtime/vm/unwind.h"
#include "hphp/util/text-util.h"
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
EXTERNALLY_VISIBLE
extern const int8_t numImmediatesTable[] = {
#define NA 0
#define ONE(...) 1
#define TWO(...) 2
#define THREE(...) 3
#define FOUR(...) 4
#define FIVE(...) 5
#define SIX(...) 6
#define O(name, imm, unusedPop, unusedPush, unusedFlags) imm,
OPCODES
#undef O
#undef NA
#undef ONE
#undef TWO
#undef THREE
#undef FOUR
#undef FIVE
#undef SIX
};
int numImmediates(Op opcode) {
assertx(isValidOpcode(opcode));
return numImmediatesTable[size_t(opcode)];
}
EXTERNALLY_VISIBLE
extern const int8_t immTypeTable[][kMaxHhbcImms] = {
#define NA {-1, -1, -1, -1, -1, -1},
#define ONE(a) { a, -1, -1, -1, -1, -1},
#define TWO(a, b) { a, b, -1, -1, -1, -1},
#define THREE(a, b, c) { a, b, c, -1, -1, -1},
#define FOUR(a, b, c, d) { a, b, c, d, -1, -1},
#define FIVE(a, b, c, d, e) { a, b, c, d, e, -1},
#define SIX(a, b, c, d, e, f) { a, b, c, d, e, f},
#define OA(x) OA
#define O(name, imm, unusedPop, unusedPush, unusedFlags) imm
OPCODES
#undef O
#undef OA
#undef NA
#undef ONE
#undef TWO
#undef THREE
#undef FOUR
#undef FIVE
#undef SIX
};
ArgType immType(const Op opcode, int idx) {
assertx(isValidOpcode(opcode));
assertx(idx >= 0 && idx < numImmediates(opcode));
always_assert(idx < kMaxHhbcImms); // No opcodes have more than 6 immediates
auto opInt = size_t(opcode);
return (ArgType)immTypeTable[opInt][idx];
}
static size_t encoded_iva_size(uint8_t lowByte) {
// High order bit set => 4-byte.
return int8_t(lowByte) >= 0 ? 1 : 4;
}
EXTERNALLY_VISIBLE
extern const int8_t immSizeTable[] = {
#define ARGTYPE(nm, type) sizeof(type),
#define ARGTYPEVEC(nm, type) 0,
ARGTYPES
#undef ARGTYPE
#undef ARGTYPEVEC
};
namespace {
bool argTypeIsVector(ArgType type) {
return
type == BLA || type == SLA || type == VSA;
}
int immSize(Op op, ArgType type, PC immPC) {
auto pc = immPC;
if (type == IVA || type == LA || type == ILA || type == IA) {
return encoded_iva_size(decode_raw<uint8_t>(pc));
}
if (type == NLA) {
auto nextPC = pc;
auto const nameSize = encoded_iva_size(decode_raw<uint8_t>(pc));
nextPC += nameSize;
auto const locSize = encoded_iva_size(decode_raw<uint8_t>(nextPC));
return nameSize + locSize;
}
if (type == KA) {
// 1 byte for member code, 1 byte for readonly op
switch (decode_raw<MemberCode>(pc)) {
case MW:
return 1;
case MEC: case MPC:
return 2 + encoded_iva_size(decode_raw<uint8_t>(pc));
case MEL: case MPL: {
auto nextPC = pc;
auto const nameSize = encoded_iva_size(decode_raw<uint8_t>(pc));
nextPC += nameSize;
auto const locSize = encoded_iva_size(decode_raw<uint8_t>(nextPC));
return 2 + nameSize + locSize;
}
case MEI:
return 2 + sizeof(int64_t);
case MET: case MPT: case MQT:
return 2 + sizeof(Id);
}
not_reached();
}
if (type == RATA) {
return encodedRATSize(pc);
}
if (type == LAR) {
decode_iva(pc); // first
decode_iva(pc); // restCount
return pc - immPC;
}
if (type == ITA) {
decodeIterArgs(pc);
return pc - immPC;
}
if (type == FCA) {
decodeFCallArgs(op, pc, nullptr /* StringDecoder */);
return pc - immPC;
}
if (argTypeIsVector(type)) {
auto size = decode_iva(pc);
int vecElemSz;
switch (type) {
case BLA: vecElemSz = sizeof(Offset); break;
case SLA: vecElemSz = sizeof(StrVecItem); break;
case VSA: vecElemSz = sizeof(Id); break;
default: not_reached();
}
return pc - immPC + vecElemSz * size;
}
return (type >= 0) ? immSizeTable[type] : 0;
}
}
bool hasImmVector(Op opcode) {
const int num = numImmediates(opcode);
for (int i = 0; i < num; ++i) {
if (argTypeIsVector(immType(opcode, i))) return true;
}
return false;
}
ArgUnion getImm(const PC origPC, int idx, const Unit* unit) {
auto pc = origPC;
auto const op = decode_op(pc);
assertx(idx >= 0 && idx < numImmediates(op));
ArgUnion retval;
retval.u_NA = 0;
int cursor = 0;
for (cursor = 0; cursor < idx; cursor++) {
// Advance over this immediate.
pc += immSize(op, immType(op, cursor), pc);
}
always_assert(cursor == idx);
auto const type = immType(op, idx);
if (type == IVA || type == LA || type == ILA || type == IA) {
retval.u_IVA = decode_iva(pc);
} else if (type == NLA) {
retval.u_NLA = decode_named_local(pc);
} else if (type == KA) {
assertx(unit != nullptr);
retval.u_KA = decode_member_key(pc, unit);
} else if (type == LAR) {
retval.u_LAR = decodeLocalRange(pc);
} else if (type == ITA) {
retval.u_ITA = decodeIterArgs(pc);
} else if (type == FCA) {
retval.u_FCA = decodeFCallArgs(op, pc, unit);
} else if (type == RATA) {
assertx(unit != nullptr);
retval.u_RATA = decodeRAT(unit, pc);
} else if (!argTypeIsVector(type)) {
memcpy(&retval.bytes, pc, immSize(op, type, pc));
}
always_assert(numImmediates(op) > idx);
return retval;
}
ArgUnion* getImmPtr(const PC origPC, int idx) {
auto pc = origPC;
auto const op = decode_op(pc);
assertx(immType(op, idx) != IVA);
assertx(immType(op, idx) != LA);
assertx(immType(op, idx) != NLA);
assertx(immType(op, idx) != ILA);
assertx(immType(op, idx) != IA);
assertx(immType(op, idx) != RATA);
for (int i = 0; i < idx; i++) {
pc += immSize(op, immType(op, i), pc);
}
return (ArgUnion*)pc;
}
template<typename T>
T decodeImm(const unsigned char** immPtr) {
T val = *(T*)*immPtr;
*immPtr += sizeof(T);
return val;
}
int instrLen(const PC origPC) {
auto pc = origPC;
auto op = decode_op(pc);
int nImm = numImmediates(op);
for (int i = 0; i < nImm; i++) {
pc += immSize(op, immType(op, i), pc);
}
return pc - origPC;
}
OffsetList instrJumpOffsets(const PC origPC) {
static const std::array<uint8_t, kMaxHhbcImms> argTypes[] = {
#define IMM_NA 0
#define IMM_IVA 0
#define IMM_I64A 0
#define IMM_DA 0
#define IMM_SA 0
#define IMM_AA 0
#define IMM_RATA 0
#define IMM_BA 1
#define IMM_BLA 2
#define IMM_SLA 3
#define IMM_LA 0
#define IMM_NLA 0
#define IMM_ILA 0
#define IMM_IA 0
#define IMM_OA(x) 0
#define IMM_VSA 0
#define IMM_KA 0
#define IMM_LAR 0
#define IMM_ITA 0
#define IMM_FCA 0
#define NA { 0, 0, 0, 0, 0, 0 },
#define ONE(a) { IMM_##a, 0, 0, 0, 0, 0 },
#define TWO(a, b) { IMM_##a, IMM_##b, 0, 0, 0, 0 },
#define THREE(a, b, c) { IMM_##a, IMM_##b, IMM_##c, 0, 0, 0 },
#define FOUR(a, b, c, d) { IMM_##a, IMM_##b, IMM_##c, IMM_##d, 0, 0 },
#define FIVE(a, b, c, d, e) { IMM_##a, IMM_##b, IMM_##c, IMM_##d, IMM_##e, 0 },
#define SIX(a, b, c, d, e, f) { IMM_##a, IMM_##b, IMM_##c, IMM_##d, IMM_##e, IMM_##f},
#define OA(x) OA
#define O(name, imm, unusedPop, unusedPush, unusedFlags) imm
OPCODES
#undef IMM_NA
#undef IMM_IVA
#undef IMM_I64A
#undef IMM_DA
#undef IMM_SA
#undef IMM_AA
#undef IMM_RATA
#undef IMM_LA
#undef IMM_NLA
#undef IMM_ILA
#undef IMM_IA
#undef IMM_BA
#undef IMM_BLA
#undef IMM_SLA
#undef IMM_OA
#undef IMM_VSA
#undef IMM_KA
#undef IMM_LAR
#undef IMM_ITA
#undef IMM_FCA
#undef O
#undef OA
#undef NA
#undef ONE
#undef TWO
#undef THREE
#undef FOUR
#undef FIVE
#undef SIX
};
auto pc = origPC;
auto const op = decode_op(pc);
OffsetList targets;
if (isFCall(op)) {
auto const offset = decodeFCallArgs(op, pc, nullptr).asyncEagerOffset;
if (offset != kInvalidOffset) targets.emplace_back(offset);
return targets;
}
auto const& types = argTypes[size_t(op)];
for (size_t i = 0; i < types.size(); ++i) {
switch (types[i]) {
case 0:
break;
case 1:
pc = origPC;
targets.emplace_back(getImmPtr(pc, i)->u_BA);
break;
case 2: {
pc = origPC;
PC vp = getImmPtr(pc, i)->bytes;
auto const size = decode_iva(vp);
ImmVector iv(vp, size, 0);
targets.insert(targets.end(), iv.vec32(), iv.vec32() + iv.size());
break;
}
case 3: {
pc = origPC;
PC vp = getImmPtr(pc, i)->bytes;
auto const size = decode_iva(vp);
ImmVector iv(vp, size, 0);
for (size_t j = 0; j < iv.size(); ++j) {
targets.emplace_back(iv.strvec()[j].dest);
}
break;
}
default:
always_assert(false);
}
}
return targets;
}
OffsetList instrJumpTargets(PC instrs, Offset pos) {
auto offsets = instrJumpOffsets(instrs + pos);
for (auto& o : offsets) o += pos;
return offsets;
}
OffsetSet instrSuccOffsets(PC opc, const Func* func) {
auto const bcStart = func->entry();
auto const offsets = instrJumpTargets(bcStart, opc - bcStart);
OffsetSet offsetsSet{offsets.begin(), offsets.end()};
auto const op = peek_op(opc);
if (!instrIsControlFlow(op) || instrAllowsFallThru(op)) {
Offset succOff = opc + instrLen(opc) - bcStart;
offsetsSet.emplace(succOff);
}
if (op == Op::Await || op == Op::Throw) {
auto const target = findCatchHandler(func, opc - bcStart);
if (target != kInvalidOffset) offsetsSet.emplace(target);
}
return offsetsSet;
}
/**
* Return the number of successor-edges including fall-through paths but not
* implicit exception paths.
*/
int numSuccs(const PC origPC) {
auto pc = origPC;
auto numTargets = instrJumpOffsets(pc).size();
pc = origPC;
if ((instrFlags(decode_op(pc)) & TF) == 0) ++numTargets;
return numTargets;
}
/**
* instrNumPops() returns the number of values consumed from the stack
* for a given push/pop instruction. For peek/poke instructions, this
* function returns 0.
*/
int instrNumPops(PC pc) {
static const int32_t numberOfPops[] = {
#define NOV 0
#define ONE(...) 1
#define TWO(...) 2
#define THREE(...) 3
#define FOUR(...) 4
#define FIVE(...) 5
#define SIX(...) 6
#define MFINAL -3
#define C_MFINAL(n) -10 - (n)
#define CUMANY -3
#define FCALL(nin, nobj) -20 - (nin)
#define CMANY -3
#define SMANY -1
#define O(name, imm, pop, push, flags) pop,
OPCODES
#undef NOV
#undef ONE
#undef TWO
#undef THREE
#undef FOUR
#undef FIVE
#undef SIX
#undef MFINAL
#undef C_MFINAL
#undef CUMANY
#undef FCALL
#undef CMANY
#undef SMANY
#undef O
};
auto const op = peek_op(pc);
int n = numberOfPops[size_t(op)];
// For most instructions, we know how many values are popped based
// solely on the opcode
if (n >= 0) return n;
// Some final member operations specify how many values are popped in their
// first immediate.
if (n == -3) return getImm(pc, 0).u_IVA;
// FCall* opcodes pop number of opcode specific inputs, unpack, numArgs,
// 2 cells/uninits reserved for ActRec and (numRets - 1) uninit values.
if (n <= -20) {
auto const fca = getImm(pc, 0).u_FCA;
auto const nin = -n - 20;
return nin + fca.numInputs() + (kNumActRecCells - 1) + fca.numRets;
}
// Other final member operations pop their first immediate + n
if (n <= -10) return getImm(pc, 0).u_IVA - n - 10;
// For instructions with vector immediates, we have to scan the contents of
// the vector immediate to determine how many values are popped
assertx(n == -1);
ImmVector iv = getImmVector(pc);
int k = iv.numStackValues();
return k;
}
/**
* instrNumPushes() returns the number of values pushed onto the stack
* for a given push/pop instruction. For peek/poke instructions or
* InsertMid instructions, this function returns 0.
*/
int instrNumPushes(PC pc) {
static const int8_t numberOfPushes[] = {
#define NOV 0
#define ONE(...) 1
#define TWO(...) 2
#define THREE(...) 3
#define FOUR(...) 4
#define FIVE(...) 5
#define SIX(...) 6
#define FCALL -1
#define O(name, imm, pop, push, flags) push,
OPCODES
#undef NOV
#undef ONE
#undef TWO
#undef THREE
#undef FOUR
#undef FIVE
#undef SIX
#undef FCALL
#undef O
};
auto const op = peek_op(pc);
int n = numberOfPushes[size_t(op)];
// The FCall* opcodes pushes all return values onto the stack
if (n == -1) return getImm(pc, 0).u_FCA.numRets;
return n;
}
namespace {
FlavorDesc doFlavor(uint32_t /*i*/) {
always_assert(0 && "Invalid stack index");
}
template<typename... Args>
FlavorDesc doFlavor(uint32_t i, FlavorDesc f, Args&&... args) {
return i == 0 ? f : doFlavor(i - 1, std::forward<Args>(args)...);
}
FlavorDesc manyFlavor(PC op, uint32_t i, FlavorDesc flavor) {
always_assert(i < uint32_t(instrNumPops(op)));
return flavor;
}
template<int nin, int nobj>
FlavorDesc fcallFlavor(PC op, uint32_t i) {
always_assert(i < uint32_t(instrNumPops(op)));
auto const fca = getImm(op, 0).u_FCA;
if (i < nin) return CV;
i -= nin;
if (i == 0 && fca.hasGenerics()) return CV;
i -= fca.hasGenerics() ? 1 : 0;
if (i == 0 && fca.hasUnpack()) return CV;
i -= fca.hasUnpack() ? 1 : 0;
if (i < fca.numArgs) return CV;
i -= fca.numArgs;
if (i == 2 && nobj) return CV;
return UV;
}
}
/**
* Returns the expected input flavor of stack slot idx.
*/
FlavorDesc instrInputFlavor(PC op, uint32_t idx) {
#define NOV always_assert(0 && "Opcode has no stack inputs");
#define ONE(f1) return doFlavor(idx, f1);
#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 FIVE(f1, f2, f3, f4, f5) return doFlavor(idx, f1, f2, f3, f4, f5);
#define SIX(f1, f2, f3, f4, f5, f6) return doFlavor(idx, f1, f2, f3, f4, f5, f6);
#define MFINAL return manyFlavor(op, idx, CV);
#define C_MFINAL(n) return manyFlavor(op, idx, CV);
#define CUMANY return manyFlavor(op, idx, CUV);
#define FCALL(nin, nobj) return fcallFlavor<nin, nobj>(op, idx);
#define CMANY return manyFlavor(op, idx, CV);
#define SMANY return manyFlavor(op, idx, CV);
#define O(name, imm, pop, push, flags) case Op::name: pop
switch (peek_op(op)) {
OPCODES
}
not_reached();
#undef NOV
#undef ONE
#undef TWO
#undef THREE
#undef FOUR
#undef FIVE
#undef SIX
#undef MFINAL
#undef C_MFINAL
#undef CUMANY
#undef FCALL
#undef CMANY
#undef SMANY
#undef O
}
void staticArrayStreamer(const ArrayData* ad, std::string& out) {
if (ad->isLegacyArray()) out += "legacy_";
assertx(ad->isVecType() || ad->isDictType() || ad->isKeysetType());
if (ad->isVecType()) out += "vec(";
if (ad->isDictType()) out += "dict(";
if (ad->isKeysetType()) out += "keyset(";
if (!ad->empty()) {
bool comma = false;
for (ArrayIter it(ad); !it.end(); it.next()) {
if (comma) {
out += ",";
} else {
comma = true;
}
Variant key = it.first();
if (!ad->isVecType() && !ad->isKeysetType()) {
staticStreamer(key.asTypedValue(), out);
out += "=>";
}
Variant val = it.second();
staticStreamer(val.asTypedValue(), out);
}
}
out += ")";
}
void staticStreamer(const TypedValue* tv, std::string& out) {
switch (tv->m_type) {
case KindOfUninit:
case KindOfNull:
out += "null";
return;
case KindOfBoolean:
out += (tv->m_data.num ? "true" : "false");
return;
case KindOfInt64:
out += folly::to<std::string>(tv->m_data.num);
return;
case KindOfDouble:
out += folly::to<std::string>(tv->m_data.dbl);
return;
case KindOfPersistentString:
case KindOfString:
folly::format(&out, "\"{}\"",
escapeStringForCPP(tv->m_data.pstr->data(),
tv->m_data.pstr->size()));
return;
case KindOfLazyClass:
out += tv->m_data.plazyclass.name()->data();
return;
case KindOfPersistentVec:
case KindOfVec:
case KindOfPersistentDict:
case KindOfDict:
case KindOfPersistentKeyset:
case KindOfKeyset:
staticArrayStreamer(tv->m_data.parr, out);
return;
case KindOfClsMeth:
case KindOfRClsMeth:
case KindOfObject:
case KindOfResource:
case KindOfRFunc:
case KindOfFunc:
case KindOfClass:
break;
}
not_reached();
}
std::string instrToString(PC it, Either<const Func*, const FuncEmitter*> f) {
std::string out;
PC iStart = it;
Op op = decode_op(it);
auto u = f.match(
[](const Func* f) -> Either<const Unit*, const UnitEmitter*> { return f->unit(); },
[](const FuncEmitter* fe) -> Either<const Unit*, const UnitEmitter*> { return &fe->ue(); }
);
auto readRATA = [&] {
if (auto func = f.left()) {
auto const unit = func->unit();
auto const rat = decodeRAT(unit, it);
folly::format(&out, " {}", show(rat));
return;
}
auto const pc = it;
it += encodedRATSize(pc);
out += " <RepoAuthType>";
};
auto offsetOf = [f](PC pc) {
return f.match(
[pc](const Func* f) { return f->offsetOf(pc); },
[pc](const FuncEmitter* fe) { return fe->offsetOf(pc); }
);
};
auto lookupLitstrId = [u](Id id) {
return u.match(
[id](const Unit* u) { return u->lookupLitstrId(id); },
[id](const UnitEmitter* ue) { return ue->lookupLitstr(id); }
);
};
auto lookupArrayId = [u](Id id) {
return u.match(
[id](const Unit* u) { return u->lookupArrayId(id); },
[id](const UnitEmitter* ue) { return ue->lookupArray(id); }
);
};
auto printLocal = [](int32_t local) {
return folly::to<std::string>(local);
};
auto showOffset = [&](Offset offset) {
if (f == nullptr) return folly::sformat("{}", offset);
auto const unitOff = offsetOf(iStart + offset);
return folly::sformat("{} ({})", offset, unitOff);
};
switch (op) {
#define READ(t) folly::format(&out, " {}", *((t*)&*it)); it += sizeof(t)
#define READV() folly::format(&out, " {}", decode_iva(it));
#define READLA() folly::format(&out, " L:{}", decode_iva(it));
#define READIVA() do { \
auto imm = decode_iva(it); \
folly::format(&out, " {}", imm); \
immIdx++; \
} while (false)
#define READOA(type) do { \
auto const immVal = static_cast<type>( \
*reinterpret_cast<const uint8_t*>(it) \
); \
it += sizeof(unsigned char); \
folly::format(&out, " {}", subopToName(immVal)); \
} while (false)
#define READLITSTR(sep) do { \
Id id = decode_raw<Id>(it); \
if (id < 0) { \
assertx(op == OpSSwitch); \
folly::format(&out, "{}-", sep); \
} else { \
auto const sd = lookupLitstrId(id); \
folly::format(&out, "{}\"{}\"", sep, \
escapeStringForCPP(sd->data(), sd->size())); \
} \
} while (false)
#define READSVEC() do { \
int sz = decode_iva(it); \
out += " <"; \
const char* sep = ""; \
for (int i = 0; i < sz; ++i) { \
out += sep; \
if (op == OpSSwitch) { \
READLITSTR(""); \
out += ":"; \
} \
Offset o = decode_raw<Offset>(it); \
folly::format(&out, "{}", offsetOf(iStart + o)); \
sep = " "; \
} \
out += ">"; \
} while (false)
#define ONE(a) H_##a
#define TWO(a, b) H_##a; H_##b
#define THREE(a, b, c) H_##a; H_##b; H_##c;
#define FOUR(a, b, c, d) H_##a; H_##b; H_##c; H_##d;
#define FIVE(a, b, c, d, e) H_##a; H_##b; H_##c; H_##d; H_##e;
#define SIX(a, b, c, d, e, f) H_##a; H_##b; H_##c; H_##d; H_##e; H_##f;
#define NA
#define H_BLA READSVEC()
#define H_SLA READSVEC()
#define H_IVA READIVA()
#define H_I64A READ(int64_t)
#define H_LA READLA()
#define H_NLA do { \
auto loc = decode_named_local(it); \
folly::format(&out, " L:{}:{}", loc.name, loc.id); \
} while (false)
#define H_ILA READLA()
#define H_IA READV()
#define H_DA READ(double)
#define H_BA (out += ' ', out += showOffset(decode_ba(it)))
#define H_OA(type) READOA(type)
#define H_SA READLITSTR(" ")
#define H_RATA readRATA()
#define H_AA do { \
out += ' '; \
staticArrayStreamer(lookupArrayId(decode_raw<Id>(it)), out); \
} while (false)
#define H_VSA do { \
int sz = decode_iva(it); \
out += " <"; \
for (int i = 0; i < sz; ++i) { \
H_SA; \
} \
out += " >"; \
} while (false)
#define H_KA (out += ' ', out += show(decode_member_key(it, u)))
#define H_LAR (out += ' ', out += show(decodeLocalRange(it)))
#define H_ITA (out += ' ', out += show(decodeIterArgs(it), printLocal))
#define H_FCA do { \
auto const fca = decodeFCallArgs(thisOpcode, it, u); \
auto const aeOffset = fca.asyncEagerOffset != kInvalidOffset \
? showOffset(fca.asyncEagerOffset) \
: "-"; \
out += ' '; \
out += show(fca, fca.inoutArgs, fca.readonlyArgs, aeOffset, fca.context); \
} while (false)
#define O(name, imm, push, pop, flags) \
case Op##name: { \
out += #name; \
UNUSED auto const thisOpcode = Op##name; \
UNUSED unsigned immIdx = 0; \
imm; \
break; \
}
OPCODES
#undef O
#undef READ
#undef READV
#undef READLA
#undef ONE
#undef TWO
#undef THREE
#undef FOUR
#undef FIVE
#undef SIX
#undef NA
#undef H_BLA
#undef H_SLA
#undef H_IVA
#undef H_I64A
#undef H_LA
#undef H_NLA
#undef H_ILA
#undef H_IA
#undef H_DA
#undef H_BA
#undef H_OA
#undef H_SA
#undef H_AA
#undef H_VSA
#undef H_KA
#undef H_LAR
#undef H_ITA
#undef H_FCA
default: assertx(false);
};
return out;
}
EXTERNALLY_VISIBLE
extern const char* const opcodeToNameTable[] = {
#define O(name, imm, inputs, outputs, flags) \
#name ,
OPCODES
#undef O
};
const char* opcodeToName(Op op) {
if (size_t(op) < Op_count) {
return opcodeToNameTable[size_t(op)];
}
return "Invalid";
}
//////////////////////////////////////////////////////////////////////
static const char* IsTypeOp_names[] = {
#define ISTYPE_OP(x) #x,
ISTYPE_OPS
#undef ISTYPE_OP
};
static const char* InitPropOp_names[] = {
#define INITPROP_OP(x) #x,
INITPROP_OPS
#undef INITPROP_OP
};
static const char* FatalOp_names[] = {
#define FATAL_OP(op) #op,
FATAL_OPS
#undef FATAL_OP
};
static const char* SetOpOp_names[] = {
#define SETOP_OP(x, y) #x,
SETOP_OPS
#undef SETOP_OP
};
static const char* IncDecOp_names[] = {
#define INCDEC_OP(x) #x,
INCDEC_OPS
#undef INCDEC_OP
};
static const char* BareThisOp_names[] = {
#define BARETHIS_OP(x) #x,
BARETHIS_OPS
#undef BARETHIS_OP
};
static const char* CollectionType_names[] = {
#define COL(x) #x,
COLLECTION_TYPES
#undef COL
};
static const char* SilenceOp_names[] = {
#define SILENCE_OP(x) #x,
SILENCE_OPS
#undef SILENCE_OP
};
static const char* OODeclExistsOp_names[] = {
#define OO_DECL_EXISTS_OP(x) #x,
OO_DECL_EXISTS_OPS
#undef OO_DECL_EXISTS_OP
};
static const char* ObjMethodOp_names[] = {
#define OBJMETHOD_OP(x) #x,
OBJMETHOD_OPS
#undef OBJMETHOD_OP
};
static const char* SwitchKind_names[] = {
#define KIND(x) #x,
SWITCH_KINDS
#undef KIND
};
static const char* QueryMOp_names[] = {
#define OP(x) #x,
QUERY_M_OPS
#undef OP
};
static const char* SetRangeOp_names[] = {
#define OP(x) #x,
SET_RANGE_OPS
#undef OP
};
static const char* TypeStructResolveOp_names[] = {
#define OP(x) #x,
TYPE_STRUCT_RESOLVE_OPS
#undef OP
};
static const char* MOpMode_names[] = {
#define MODE(x) #x,
M_OP_MODES
#undef MODE
};
static const char* ContCheckOp_names[] = {
#define CONT_CHECK_OP(x) #x,
CONT_CHECK_OPS
#undef CONT_CHECK_OP
};
static const char* IsLogAsDynamicCallOp_names[] = {
#define IS_LOG_AS_DYNAMIC_CALL_OP(x) #x,
IS_LOG_AS_DYNAMIC_CALL_OPS
#undef IS_LOG_AS_DYNAMIC_CALL_OP
};
static const char* SpecialClsRef_names[] = {
#define REF(x) #x,
SPECIAL_CLS_REFS
#undef REF
};
static const char* ReadonlyOp_names[] = {
#define OP(x) #x,
READONLY_OPS
#undef OP
};
template<class T, size_t Sz>
const char* subopToNameImpl(const char* (&arr)[Sz], T opcode, int off) {
static_assert(
std::is_same<typename std::underlying_type<T>::type,uint8_t>::value,
"Subops are all expected to be single-bytes"
);
auto const idx = static_cast<uint8_t>(opcode) - off;
always_assert(idx < Sz);
return arr[idx];
}
template <class T, size_t Sz>
bool subopValidImpl(const char* (&/*arr*/)[Sz], T op, int off) {
auto raw = static_cast<typename std::underlying_type<T>::type>(op) - off;
return raw >= 0 && raw < Sz;
}
template<class T, size_t Sz>
Optional<T> nameToSubopImpl(const char* (&arr)[Sz],
const char* str, int off) {
for (auto i = size_t{0}; i < Sz; ++i) {
if (!strcmp(str, arr[i])) return static_cast<T>(i + off);
}
return std::nullopt;
}
namespace {
template<class T> struct NameToSubopHelper;
}
template<class T> Optional<T> nameToSubop(const char* str) {
return NameToSubopHelper<T>::conv(str);
}
#define X(subop, off) \
const char* subopToName(subop op) { \
return subopToNameImpl(subop##_names, op, off); \
} \
template<> bool subopValid(subop op) { \
return subopValidImpl(subop##_names, op, off); \
} \
namespace { \
template<> struct NameToSubopHelper<subop> { \
static Optional<subop> conv(const char* str) { \
return nameToSubopImpl<subop>(subop##_names, str, off); \
} \
}; \
} \
template Optional<subop> nameToSubop(const char*);
// Not all subops start indexing at 0
/*Subop Name Numerically first value */
X(InitPropOp, static_cast<int>(InitPropOp::Static))
X(IsTypeOp, static_cast<int>(IsTypeOp::Null))
X(FatalOp, static_cast<int>(FatalOp::Runtime))
X(SetOpOp, static_cast<int>(SetOpOp::PlusEqual))
X(IncDecOp, static_cast<int>(IncDecOp::PreInc))
X(BareThisOp, static_cast<int>(BareThisOp::Notice))
X(SilenceOp, static_cast<int>(SilenceOp::Start))
X(CollectionType, static_cast<int>(HeaderKind::Vector))
X(OODeclExistsOp, static_cast<int>(OODeclExistsOp::Class))
X(ObjMethodOp, static_cast<int>(ObjMethodOp::NullThrows))
X(SwitchKind, static_cast<int>(SwitchKind::Unbounded))
X(QueryMOp, static_cast<int>(QueryMOp::CGet))
X(SetRangeOp, static_cast<int>(SetRangeOp::Forward))
X(TypeStructResolveOp,
static_cast<int>(TypeStructResolveOp::Resolve))
X(MOpMode, static_cast<int>(MOpMode::None))
X(ContCheckOp, static_cast<int>(ContCheckOp::IgnoreStarted))
X(SpecialClsRef, static_cast<int>(SpecialClsRef::SelfCls))
X(IsLogAsDynamicCallOp,
static_cast<int>(IsLogAsDynamicCallOp::LogAsDynamicCall))
X(ReadonlyOp, static_cast<int>(ReadonlyOp::Any))
#undef X
//////////////////////////////////////////////////////////////////////
namespace {
bool instrIsVMCall(Op opcode) {
if (isFCall(opcode)) return true;
switch (opcode) {
case OpContEnter:
case OpContRaise:
case OpEval:
case OpIncl:
case OpInclOnce:
case OpReq:
case OpReqDoc:
case OpReqOnce:
return true;
default:
return false;
}
}
bool instrMayVMCall(Op opcode) {
return instrIsVMCall(opcode) || opcode == OpIdx;
}
}
bool instrIsNonCallControlFlow(Op opcode) {
if (!instrIsControlFlow(opcode) || instrIsVMCall(opcode)) return false;
switch (opcode) {
case OpAwait:
case OpAwaitAll:
case OpYield:
case OpYieldK:
return false;
default:
return true;
}
}
bool instrAllowsFallThru(Op opcode) {
InstrFlags opFlags = instrFlags(opcode);
return (opFlags & TF) == 0;
}
PC skipCall(PC callPC) {
assertx(instrMayVMCall(peek_op(callPC)));
return callPC + instrLen(callPC);
}
ImmVector getImmVector(PC opcode) {
auto const op = peek_op(opcode);
int numImm = numImmediates(op);
for (int k = 0; k < numImm; ++k) {
ArgType t = immType(op, k);
if (t == BLA || t == SLA || t == VSA) {
PC vp = getImmPtr(opcode, k)->bytes;
auto const size = decode_iva(vp);
return ImmVector(vp, size, t == VSA ? size : 0);
}
}
not_reached();
}
///////////////////////////////////////////////////////////////////////////////
std::string show(const IterArgs& ita, PrintLocal print_local) {
auto const flags = [&]{
auto parts = std::vector<std::string>{};
if (ita.flags & IterArgs::Flags::BaseConst) parts.push_back("BaseConst");
if (parts.empty()) return std::string{};
return folly::sformat("<{}> ", folly::join(' ', parts));
}();
auto const key = ita.hasKey()
? folly::to<std::string>("K:", print_local(ita.keyId))
: folly::to<std::string>("NK");
auto const val = "V:" + print_local(ita.valId);
return folly::sformat("{}{} {} {}", flags, ita.iterId, key, val);
}
std::string show(const LocalRange& range) {
return folly::sformat(
"L:{}+{}", range.first, range.count
);
}
std::string show(uint32_t numArgs, const uint8_t* boolVecArgs) {
if (!boolVecArgs) return "";
std::string out = "";
uint8_t tmp = 0;
for (auto i = 0; i < numArgs; ++i) {
if (i % 8 == 0) tmp = *(boolVecArgs++);
out += ((tmp >> (i % 8)) & 1) ? "1" : "0";
}
return out;
}
std::string show(const FCallArgsBase& fca, const uint8_t* inoutArgs,
const uint8_t* readonlyArgs,
std::string asyncEagerLabel, const StringData* ctx) {
std::vector<std::string> flags;
if (fca.hasUnpack()) flags.push_back("Unpack");
if (fca.hasGenerics()) flags.push_back("Generics");
if (fca.lockWhileUnwinding()) flags.push_back("LockWhileUnwinding");
if (fca.skipRepack()) flags.push_back("SkipRepack");
if (fca.skipCoeffectsCheck()) flags.push_back("SkipCoeffectsCheck");
if (fca.enforceMutableReturn()) flags.push_back("EnforceMutableReturn");
if (fca.enforceReadonlyThis()) flags.push_back("EnforceReadonlyThis");
return folly::sformat(
"<{}> {} {} \"{}\" \"{}\" {} \"{}\"",
folly::join(' ', flags), fca.numArgs, fca.numRets,
show(fca.numArgs, inoutArgs),
show(fca.numArgs, readonlyArgs),
asyncEagerLabel, ctx ? ctx->data() : ""
);
}
///////////////////////////////////////////////////////////////////////////////
}