Add bytecodes to decode a function, and push an actrec for a decoded function
array_filter and friends only need to evaluate the callback once, so add bytecodes to support that. Depends on D817883
Esse commit está contido em:
@@ -650,13 +650,14 @@ protected by a fault funclet that unsets i by calling IterFree or MIterFree so
|
||||
that when the unwinder or dispatcher pops the current frame, each iterator
|
||||
variable is uninitialized.
|
||||
|
||||
13) The iterator variable referenced by IterInit* or MIterInit* must be in
|
||||
the uninitialized state when the instruction executes. An iterator variable
|
||||
referenced by IterNext* and IterFree must be in the "iter-initialized" state,
|
||||
and an iterator variable referenced by MIterNext* or MIterFree must be in the
|
||||
"miter-initialized" state. Note that IterInit* and MIterInit* conditionally
|
||||
initializes the iterator variable, and IterNext* and MIterNext* conditionally
|
||||
frees the iterator variable.
|
||||
13) The iterator variable referenced by IterInit* or MIterInit* or
|
||||
DecodeCufIter must be in the uninitialized state when the instruction
|
||||
executes. An iterator variable referenced by IterNext* and IterFree must be
|
||||
in the "iter-initialized" state, an iterator variable referenced by MIterNext*
|
||||
or MIterFree must be in the "miter-initialized" state, and an iterator variable
|
||||
referenced by FPushCufIter or CIterFree must be in the citer-initialized state.
|
||||
Note that IterInit* and MIterInit* conditionally initialize the iterator variable,
|
||||
and IterNext* and MIterNext* conditionally free the iterator variable.
|
||||
|
||||
|
||||
Instruction set
|
||||
@@ -1703,6 +1704,17 @@ FPushCtorD <num params> <litstr id> [] -> [C]
|
||||
passed (given by %1) and a reference to the FPI structure for the constructor
|
||||
for class x.
|
||||
|
||||
DecodeCufIter <iterator id> [C] -> [C]
|
||||
This instruction looks up $1 as a callable, and writes enough information
|
||||
to iterator %1 for FPushDecoded to be able to push an actrec, as if it had
|
||||
been given the callable.
|
||||
If the function is successfully decoded, pushes true, otherwise, pushes
|
||||
false, and sets up iter to call a function that does nothing, and returns
|
||||
Null. No warning is raised.
|
||||
|
||||
FPushCufIter <num params> <iterator id> [] -> []
|
||||
FPI push the result of a previous DecodeCufIter. No warning is raised.
|
||||
|
||||
FPushCuf <num params> [C] -> []
|
||||
FPushCufF <num params> [C] -> []
|
||||
|
||||
@@ -3463,6 +3475,11 @@ MIterFree <iterator id> [] -> []
|
||||
only needed for guarding against exceptions and implementing break and
|
||||
return control flow statements inside iterator loops.
|
||||
|
||||
CIterFree <iterator id> [] -> []
|
||||
|
||||
Cuf iterator free. This instruction frees the specified iterator
|
||||
variable.
|
||||
|
||||
|
||||
12. Include, eval, and define instructions
|
||||
------------------------------------------
|
||||
@@ -3782,8 +3799,8 @@ ensure that the iterator variable is freed when a foreach loop exits abnormally
|
||||
through an exception.
|
||||
|
||||
Simple break statements and continue statements are implemented using the Jmp*,
|
||||
IterFree, and MIterFree instructions. Dynamic break is implemented using an
|
||||
unnamed local (to store the 'break count') and a chain of basic blocks, where
|
||||
IterFree, MIterFree and CIterFree instructions. Dynamic break is implemented using
|
||||
an unnamed local (to store the 'break count') and a chain of basic blocks, where
|
||||
each block decrements the unnamed local variable and compares it with 0, and
|
||||
then decides where to jump next.
|
||||
|
||||
|
||||
@@ -918,6 +918,17 @@ D:StkPtr = SpillFrame<numArgs,invName> S0:StkPtr
|
||||
Defines the fields for an activation record and writes them to the
|
||||
stack pointed to by S1.
|
||||
|
||||
D:StkPtr = CufIterSpillFrame<numArgs,iterId> S0:StkPtr
|
||||
S1:FramePtr
|
||||
|
||||
Operands:
|
||||
|
||||
S0 - caller stack pointer
|
||||
S1 - caller frame pointer
|
||||
|
||||
Defines the fields for an activation record using data from
|
||||
the iterator iterId, and writes them to the stack pointed to by S1
|
||||
|
||||
D:FramePtr = FreeActRec S0:FramePtr
|
||||
|
||||
Load the saved frame pointer from the activation record pointed to
|
||||
@@ -1595,7 +1606,12 @@ D:Bool = WIterNextK S0:FramePtr S1:ConstInt S2:ConstInt S3:ConstInt
|
||||
The WIterInit and WIterInitK instructions copy referenced array
|
||||
elements by reference, and non-referenced array elements by value.
|
||||
|
||||
IterFree S0:FramePtr S1:ConstInt
|
||||
IterFree<IterId> S0:FramePtr
|
||||
|
||||
Free the iterator variable whose index is given by S1 in the stack
|
||||
frame pointed to by S0.
|
||||
|
||||
CIterFree<IterId> S0:FramePtr
|
||||
|
||||
Free the iterator variable whose index is given by S1 in the stack
|
||||
frame pointed to by S0.
|
||||
|
||||
@@ -567,6 +567,13 @@ MArrayIter::~MArrayIter() {
|
||||
}
|
||||
}
|
||||
|
||||
CufIter::~CufIter() {
|
||||
if (m_ctx && !(uintptr_t(m_ctx) & 1)) {
|
||||
decRefObj((ObjectData*)m_ctx);
|
||||
}
|
||||
if (m_name) decRefStr(m_name);
|
||||
}
|
||||
|
||||
bool Iter::init(TypedValue* c1) {
|
||||
assert(c1->m_type != KindOfRef);
|
||||
bool hasElems = true;
|
||||
@@ -701,6 +708,10 @@ void Iter::mfree() {
|
||||
marr().~MArrayIter();
|
||||
}
|
||||
|
||||
void Iter::cfree() {
|
||||
cuf().~CufIter();
|
||||
}
|
||||
|
||||
/*
|
||||
* iter_value_cell* will store a copy of the current value at the address
|
||||
* given by 'out'. iter_value_cell* will increment the refcount of the current
|
||||
|
||||
@@ -444,6 +444,30 @@ class MArrayIter : public FullPos {
|
||||
friend struct Iter;
|
||||
};
|
||||
|
||||
class CufIter {
|
||||
public:
|
||||
CufIter() {}
|
||||
~CufIter();
|
||||
const Func* func() const { return m_func; }
|
||||
void* ctx() const { return m_ctx; }
|
||||
StringData* name() const { return m_name; }
|
||||
|
||||
void setFunc(const Func* f) { m_func = f; }
|
||||
void setCtx(ObjectData* obj) { m_ctx = obj; }
|
||||
void setCtx(const Class* cls) {
|
||||
m_ctx = cls ? (void*)((char*)cls + 1) : nullptr;
|
||||
}
|
||||
void setName(StringData* name) { m_name = name; }
|
||||
|
||||
static uint32_t funcOff() { return offsetof(CufIter, m_func); }
|
||||
static uint32_t ctxOff() { return offsetof(CufIter, m_ctx); }
|
||||
static uint32_t nameOff() { return offsetof(CufIter, m_name); }
|
||||
private:
|
||||
const Func* m_func;
|
||||
void* m_ctx;
|
||||
StringData* m_name;
|
||||
};
|
||||
|
||||
struct Iter {
|
||||
ArrayIter& arr() {
|
||||
return *(ArrayIter*)m_u;
|
||||
@@ -451,16 +475,19 @@ struct Iter {
|
||||
MArrayIter& marr() {
|
||||
return *(MArrayIter*)m_u;
|
||||
}
|
||||
CufIter& cuf() { return *(CufIter*)m_u; }
|
||||
|
||||
bool init(TypedValue* c1);
|
||||
bool minit(TypedValue* v1);
|
||||
bool next();
|
||||
bool mnext();
|
||||
void free();
|
||||
void mfree();
|
||||
void cfree();
|
||||
private:
|
||||
// C++ won't let you have union members with constructors. So we get to
|
||||
// implement unions by hand.
|
||||
char m_u[MAX(sizeof(ArrayIter), sizeof(MArrayIter))];
|
||||
char m_u[MAX(MAX(sizeof(ArrayIter), sizeof(MArrayIter)), sizeof(CufIter))];
|
||||
} __attribute__ ((aligned(16)));
|
||||
|
||||
bool interp_init_iterator(Iter* it, TypedValue* c1);
|
||||
|
||||
@@ -5902,6 +5902,72 @@ inline void OPTBLD_INLINE VMExecutionContext::iopFPushCtorD(PC& pc) {
|
||||
ar->setVarEnv(nullptr);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopDecodeCufIter(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_IA(itId);
|
||||
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
CufIter &cit = it->cuf();
|
||||
|
||||
ObjectData* obj = nullptr;
|
||||
HPHP::Class* cls = nullptr;
|
||||
StringData* invName = nullptr;
|
||||
TypedValue *func = m_stack.topTV();
|
||||
|
||||
ActRec* ar = m_fp;
|
||||
if (m_fp->m_func->isBuiltin()) {
|
||||
ar = getOuterVMFrame(ar);
|
||||
}
|
||||
const Func* f = vm_decode_function(tvAsVariant(func),
|
||||
ar, false,
|
||||
obj, cls, invName,
|
||||
true);
|
||||
|
||||
bool ret = true;
|
||||
if (f == nullptr) {
|
||||
f = SystemLib::s_nullFunc;
|
||||
obj = nullptr;
|
||||
cls = nullptr;
|
||||
invName = nullptr;
|
||||
ret = false;
|
||||
}
|
||||
|
||||
cit.setFunc(f);
|
||||
if (obj) {
|
||||
cit.setCtx(obj);
|
||||
obj->incRefCount();
|
||||
} else {
|
||||
cit.setCtx(cls);
|
||||
}
|
||||
cit.setName(invName);
|
||||
tvAsVariant(m_stack.top()) = ret;
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopFPushCufIter(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_IVA(numArgs);
|
||||
DECODE_IA(itId);
|
||||
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
|
||||
auto f = it->cuf().func();
|
||||
auto o = it->cuf().ctx();
|
||||
auto n = it->cuf().name();
|
||||
|
||||
ActRec* ar = m_stack.allocA();
|
||||
arSetSfp(ar, m_fp);
|
||||
ar->m_func = f;
|
||||
ar->m_this = (ObjectData*)o;
|
||||
if (o && !(uintptr_t(o) & 1)) ar->m_this->incRefCount();
|
||||
if (n) {
|
||||
ar->setInvName(n);
|
||||
n->incRefCount();
|
||||
} else {
|
||||
ar->setVarEnv(nullptr);
|
||||
}
|
||||
ar->initNumArgs(numArgs, false /* isFPushCtor */);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::doFPushCuf(PC& pc,
|
||||
bool forward,
|
||||
bool safe) {
|
||||
@@ -6667,6 +6733,13 @@ inline void OPTBLD_INLINE VMExecutionContext::iopMIterFree(PC& pc) {
|
||||
it->mfree();
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopCIterFree(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_IA(itId);
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
it->cfree();
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE inclOp(VMExecutionContext *ec, PC &pc,
|
||||
InclOpFlags flags) {
|
||||
NEXT();
|
||||
|
||||
@@ -499,6 +499,7 @@ bool pushesActRec(Opcode opcode) {
|
||||
case OpFPushClsMethodD:
|
||||
case OpFPushCtor:
|
||||
case OpFPushCtorD:
|
||||
case OpFPushCufIter:
|
||||
case OpFPushCuf:
|
||||
case OpFPushCufF:
|
||||
case OpFPushCufSafe:
|
||||
|
||||
@@ -505,6 +505,7 @@ enum SetOpOp {
|
||||
O(FPushClsMethodD, THREE(IVA,SA,SA), NOV, NOV, NF) \
|
||||
O(FPushCtor, ONE(IVA), ONE(AV), ONE(CV), NF) \
|
||||
O(FPushCtorD, TWO(IVA,SA), NOV, ONE(CV), NF) \
|
||||
O(FPushCufIter, TWO(IVA,IA), NOV, NOV, NF) \
|
||||
O(FPushCuf, ONE(IVA), ONE(CV), NOV, NF) \
|
||||
O(FPushCufF, ONE(IVA), ONE(CV), NOV, NF) \
|
||||
O(FPushCufSafe, ONE(IVA), TWO(CV,CV), TWO(CV,CV), NF) \
|
||||
@@ -539,6 +540,8 @@ enum SetOpOp {
|
||||
O(WIterNextK, FOUR(IA,BA,HA,HA),NOV, NOV, CF) \
|
||||
O(IterFree, ONE(IA), NOV, NOV, NF) \
|
||||
O(MIterFree, ONE(IA), NOV, NOV, NF) \
|
||||
O(DecodeCufIter, ONE(IA), ONE(CV), ONE(CV), NF) \
|
||||
O(CIterFree, ONE(IA), NOV, NOV, NF) \
|
||||
O(Incl, NA, ONE(CV), ONE(CV), CF) \
|
||||
O(InclOnce, NA, ONE(CV), ONE(CV), CF) \
|
||||
O(Req, NA, ONE(CV), ONE(CV), CF) \
|
||||
|
||||
@@ -2566,6 +2566,11 @@ int CodeGenerator::getIterOffset(SSATmp* tmp) {
|
||||
return -cellsToBytes(((index + 1) * kNumIterCells + func->numLocals()));
|
||||
}
|
||||
|
||||
int CodeGenerator::getIterOffset(uint32_t id) {
|
||||
const Func* func = getCurFunc();
|
||||
return -cellsToBytes(((id + 1) * kNumIterCells + func->numLocals()));
|
||||
}
|
||||
|
||||
void CodeGenerator::cgStLoc(IRInstruction* inst) {
|
||||
cgStore(m_regs[inst->src(0)].getReg(),
|
||||
getLocalOffset(inst->extra<StLoc>()->locId),
|
||||
@@ -3142,6 +3147,44 @@ void CodeGenerator::cgDecRefNZOrBranch(IRInstruction* inst) {
|
||||
cgDecRefWork(inst, true);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgCufIterSpillFrame(IRInstruction* inst) {
|
||||
auto const sp = inst->src(0);
|
||||
auto const fp = inst->src(1);
|
||||
auto const nArgs = inst->extra<CufIterSpillFrame>()->args;
|
||||
auto const iterId = inst->extra<CufIterSpillFrame>()->iterId;
|
||||
auto const itOff = getIterOffset(iterId);
|
||||
|
||||
const int64_t spOffset = -kNumActRecCells * sizeof(Cell);
|
||||
auto spReg = m_regs[sp].getReg();
|
||||
auto fpReg = m_regs[fp].getReg();
|
||||
|
||||
m_as.loadq (fpReg[itOff + CufIter::funcOff()], m_rScratch);
|
||||
m_as.storeq (m_rScratch, spReg[spOffset + int(AROFF(m_func))]);
|
||||
|
||||
m_as.loadq (fpReg[itOff + CufIter::ctxOff()], m_rScratch);
|
||||
m_as.storeq (m_rScratch, spReg[spOffset + int(AROFF(m_this))]);
|
||||
|
||||
m_as.shrq (1, m_rScratch);
|
||||
ifThen(m_as, CC_NBE, [this] {
|
||||
m_as.shlq(1, m_rScratch);
|
||||
emitIncRef(m_as, m_rScratch);
|
||||
});
|
||||
m_as.loadq (fpReg[itOff + CufIter::nameOff()], m_rScratch);
|
||||
m_as.testq (m_rScratch, m_rScratch);
|
||||
ifThen(m_as, CC_NZ, [this] {
|
||||
m_as.cmpl(RefCountStaticValue, m_rScratch[FAST_REFCOUNT_OFFSET]);
|
||||
ifThen(m_as, CC_NE, [&] { emitIncRef(m_as, m_rScratch); });
|
||||
m_as.orq (ActRec::kInvNameBit, m_rScratch);
|
||||
});
|
||||
m_as.storeq (m_rScratch, spReg[spOffset + int(AROFF(m_invName))]);
|
||||
m_as.storeq (fpReg, spReg[spOffset + int(AROFF(m_savedRbp))]);
|
||||
m_as.storel (nArgs, spReg[spOffset + int(AROFF(m_numArgsAndCtorFlag))]);
|
||||
|
||||
emitAdjustSp(spReg,
|
||||
m_regs[inst->dst()].getReg(),
|
||||
spOffset);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgSpillFrame(IRInstruction* inst) {
|
||||
auto const sp = inst->src(0);
|
||||
auto const fp = inst->src(1);
|
||||
@@ -3196,9 +3239,9 @@ void CodeGenerator::cgSpillFrame(IRInstruction* inst) {
|
||||
uintptr_t invName = !magicName
|
||||
? 0
|
||||
: reinterpret_cast<uintptr_t>(magicName) | ActRec::kInvNameBit;
|
||||
m_as.store_imm64_disp_reg64(invName,
|
||||
spOffset + int(AROFF(m_invName)),
|
||||
spReg);
|
||||
m_as.store_imm64_disp_reg64(invName,
|
||||
spOffset + int(AROFF(m_invName)),
|
||||
spReg);
|
||||
// actRec->m_func and possibly actRec->m_cls
|
||||
// Note m_cls is unioned with m_this and may overwrite previous value
|
||||
if (func->type().isNull()) {
|
||||
@@ -5141,13 +5184,24 @@ void iterFreeHelper(Iter* iter) {
|
||||
iter->free();
|
||||
}
|
||||
|
||||
void citerFreeHelper(Iter* iter) {
|
||||
iter->cfree();
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterFree(IRInstruction* inst) {
|
||||
PhysReg fpReg = m_regs[inst->src(0)].getReg();
|
||||
int64_t offset = getIterOffset(inst->src(1));
|
||||
int64_t offset = getIterOffset(inst->extra<IterFree>()->iterId);
|
||||
cgCallHelper(m_as, (TCA)iterFreeHelper, InvalidReg, kSyncPoint,
|
||||
ArgGroup(m_regs).addr(fpReg, offset));
|
||||
}
|
||||
|
||||
void CodeGenerator::cgCIterFree(IRInstruction* inst) {
|
||||
PhysReg fpReg = m_regs[inst->src(0)].getReg();
|
||||
int64_t offset = getIterOffset(inst->extra<CIterFree>()->iterId);
|
||||
cgCallHelper(m_as, (TCA)citerFreeHelper, InvalidReg, kSyncPoint,
|
||||
ArgGroup(m_regs).addr(fpReg, offset));
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIncStat(IRInstruction *inst) {
|
||||
Stats::emitInc(m_as,
|
||||
Stats::StatCounter(inst->src(0)->getValInt()),
|
||||
|
||||
@@ -323,6 +323,7 @@ private:
|
||||
Address getDtorGeneric();
|
||||
Address getDtorTyped();
|
||||
int getIterOffset(SSATmp* tmp);
|
||||
int getIterOffset(uint32_t id);
|
||||
void emitReqBindAddr(const Func* func, TCA& dest, Offset offset);
|
||||
|
||||
void emitAdjustSp(PhysReg spReg, PhysReg dstReg, int64_t adjustment);
|
||||
|
||||
@@ -126,6 +126,37 @@ struct LocalId : IRExtraData {
|
||||
uint32_t locId;
|
||||
};
|
||||
|
||||
struct IterId : IRExtraData {
|
||||
explicit IterId(uint32_t id)
|
||||
: iterId(id)
|
||||
{}
|
||||
|
||||
bool cseEquals(IterId o) const { return iterId == o.iterId; }
|
||||
size_t cseHash() const { return std::hash<uint32_t>()(iterId); }
|
||||
std::string show() const { return folly::to<std::string>(iterId); }
|
||||
|
||||
uint32_t iterId;
|
||||
};
|
||||
|
||||
struct FPushCufData : IRExtraData {
|
||||
FPushCufData(uint32_t a, int32_t id)
|
||||
: args(a), iterId(id)
|
||||
{}
|
||||
|
||||
bool cseEquals(FPushCufData o) const {
|
||||
return iterId == o.iterId && args == o.args;
|
||||
}
|
||||
size_t cseHash() const {
|
||||
return std::hash<uint32_t>()(iterId) ^ std::hash<uint32_t>()(args);
|
||||
}
|
||||
std::string show() const {
|
||||
return folly::to<std::string>(iterId, ',', args);
|
||||
}
|
||||
|
||||
uint32_t args;
|
||||
uint32_t iterId;
|
||||
};
|
||||
|
||||
struct ConstData : IRExtraData {
|
||||
template<class T>
|
||||
explicit ConstData(T data)
|
||||
@@ -272,6 +303,9 @@ X(DecRefLoc, LocalId);
|
||||
X(LdLoc, LocalId);
|
||||
X(StLoc, LocalId);
|
||||
X(StLocNT, LocalId);
|
||||
X(IterFree, IterId);
|
||||
X(CIterFree, IterId);
|
||||
X(CufIterSpillFrame, FPushCufData);
|
||||
X(DefConst, ConstData);
|
||||
X(LdConst, ConstData);
|
||||
X(SpillFrame, ActRecInfo);
|
||||
|
||||
@@ -957,7 +957,11 @@ void HhbcTranslator::emitWIterNextK(uint32_t iterId,
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitIterFree(uint32_t iterId) {
|
||||
gen(IterFree, m_tb->getFp(), cns(iterId));
|
||||
gen(IterFree, IterId(iterId), m_tb->getFp());
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitCIterFree(uint32_t iterId) {
|
||||
gen(CIterFree, IterId(iterId), m_tb->getFp());
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitCreateCont(bool getArgs,
|
||||
@@ -1539,6 +1543,15 @@ void HhbcTranslator::emitFPassV() {
|
||||
gen(DecRef, tmp);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitFPushCufIter(int32_t numParams,
|
||||
int32_t itId) {
|
||||
auto sp = spillStack();
|
||||
m_fpiStack.emplace(sp, m_tb->getSpOffset());
|
||||
gen(CufIterSpillFrame,
|
||||
FPushCufData(numParams, itId),
|
||||
sp, m_tb->getFp());
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitFPushCufOp(Op op, Class* cls, StringData* invName,
|
||||
const Func* callee, int numArgs) {
|
||||
const Func* curFunc = getCurFunc();
|
||||
|
||||
@@ -235,10 +235,11 @@ struct HhbcTranslator {
|
||||
void emitFPassCOp();
|
||||
void emitFPassR();
|
||||
void emitFPassV();
|
||||
void emitFPushCufIter(int32_t numParams, int32_t itId);
|
||||
void emitFPushCufOp(Op op, Class* cls, StringData* invName,
|
||||
const Func* func, int numArgs);
|
||||
void emitFPushActRec(SSATmp* func, SSATmp* objOrClass, int32_t numArgs,
|
||||
const StringData* invName);
|
||||
const StringData* invName = nullptr);
|
||||
void emitFPushFuncD(int32_t numParams, int32_t funcId);
|
||||
void emitFPushFuncU(int32_t numParams,
|
||||
int32_t funcId,
|
||||
@@ -351,6 +352,7 @@ struct HhbcTranslator {
|
||||
uint32_t keyLocalId);
|
||||
|
||||
void emitIterFree(uint32_t iterId);
|
||||
void emitCIterFree(uint32_t iterId);
|
||||
void emitVerifyParamType(uint32_t paramId);
|
||||
|
||||
// continuations
|
||||
|
||||
@@ -368,6 +368,8 @@ O(SpillFrame, D(StkPtr), S(StkPtr) \
|
||||
S(FramePtr) \
|
||||
S(Func,FuncCls,FuncCtx,Null) \
|
||||
S(Ctx,Cls,InitNull), CRc) \
|
||||
O(CufIterSpillFrame, D(StkPtr), S(StkPtr) \
|
||||
S(FramePtr), NF) \
|
||||
O(ExceptionBarrier, D(StkPtr), S(StkPtr), E) \
|
||||
O(ReqBindJmp, ND, NA, T|E) \
|
||||
O(ReqBindJmpNoIR, ND, NA, T|E) \
|
||||
@@ -471,7 +473,8 @@ O(WIterNext, D(Bool), S(FramePtr) \
|
||||
C(Int) C(Int), E|N|Mem|Refs) \
|
||||
O(WIterNextK, D(Bool), S(FramePtr) \
|
||||
C(Int) C(Int) C(Int), E|N|Mem|Refs) \
|
||||
O(IterFree, ND, S(FramePtr) C(Int), E|N|Mem|Refs) \
|
||||
O(IterFree, ND, S(FramePtr), E|N|Mem|Refs) \
|
||||
O(CIterFree, ND, S(FramePtr), E|N|Mem|Refs) \
|
||||
O(DefMIStateBase, D(PtrToCell), NA, NF) \
|
||||
O(BaseG, D(PtrToGen), C(TCA) \
|
||||
S(Str) \
|
||||
|
||||
@@ -1059,6 +1059,12 @@ TranslatorX64::irTranslateFPassV(const Tracelet& t,
|
||||
HHIR_EMIT(FPassV);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateFPushCufIter(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(FPushCufIter, i.imm[0].u_IVA, i.imm[1].u_IA);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateFPushCufOp(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
@@ -1449,6 +1455,13 @@ TranslatorX64::irTranslateIterFree(const Tracelet& t,
|
||||
HHIR_EMIT(IterFree, i.imm[0].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateCIterFree(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
|
||||
HHIR_EMIT(CIterFree, i.imm[0].u_IVA);
|
||||
}
|
||||
|
||||
// PSEUDOINSTR_DISPATCH is a switch() fragment that routes opcodes to their
|
||||
// shared handlers, as per the PSEUDOINSTRS macro.
|
||||
#define PSEUDOINSTR_DISPATCH(func) \
|
||||
|
||||
@@ -457,6 +457,7 @@ void LinearScan::allocRegToInstruction(InstructionList::iterator it) {
|
||||
opc == CallArray ||
|
||||
opc == SpillStack ||
|
||||
opc == SpillFrame ||
|
||||
opc == CufIterSpillFrame ||
|
||||
opc == ExceptionBarrier ||
|
||||
opc == RetAdjustStack ||
|
||||
opc == InterpOne ||
|
||||
|
||||
@@ -129,6 +129,7 @@ StackValueInfo getStackValue(SSATmp* sp, uint32_t index) {
|
||||
}
|
||||
|
||||
case SpillFrame:
|
||||
case CufIterSpillFrame:
|
||||
return getStackValue(inst->src(0),
|
||||
// pushes an ActRec
|
||||
index - kNumActRecCells);
|
||||
|
||||
@@ -229,6 +229,7 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) {
|
||||
}
|
||||
|
||||
case SpillFrame:
|
||||
case CufIterSpillFrame:
|
||||
m_spValue = inst->dst();
|
||||
m_spOffset += kNumActRecCells;
|
||||
break;
|
||||
|
||||
@@ -11943,8 +11943,9 @@ bool TranslatorX64::dumpTCData() {
|
||||
INTERP_OP(SetOpM) \
|
||||
INTERP_OP(IncDecM) \
|
||||
INTERP_OP(BindM) \
|
||||
INTERP_OP(UnsetM)
|
||||
|
||||
INTERP_OP(UnsetM) \
|
||||
INTERP_OP(FPushCufIter) \
|
||||
INTERP_OP(CIterFree)
|
||||
|
||||
// Define the trivial analyze methods
|
||||
#define PLAN(Op, Spt) \
|
||||
|
||||
@@ -556,7 +556,8 @@ private:
|
||||
CASE(Strlen) \
|
||||
CASE(IncStat) \
|
||||
CASE(ArrayIdx) \
|
||||
|
||||
CASE(FPushCufIter) \
|
||||
CASE(CIterFree) \
|
||||
// These are instruction-like functions which cover more than one
|
||||
// opcode.
|
||||
#define PSEUDOINSTRS \
|
||||
|
||||
@@ -1292,6 +1292,8 @@ static const struct {
|
||||
kNumActRecCells }},
|
||||
{ OpFPushCtorD, {None, Stack1|FStack,OutObject,
|
||||
kNumActRecCells + 1 }},
|
||||
{ OpFPushCufIter,{None, FStack, OutFDesc,
|
||||
kNumActRecCells }},
|
||||
{ OpFPushCuf, {Stack1, FStack, OutFDesc,
|
||||
kNumActRecCells - 1 }},
|
||||
{ OpFPushCufF, {Stack1, FStack, OutFDesc,
|
||||
@@ -1325,6 +1327,7 @@ static const struct {
|
||||
Stack1, OutArray, -2 }},
|
||||
{ OpCufSafeReturn,{StackTop3|DontGuardAny,
|
||||
Stack1, OutUnknown, -2 }},
|
||||
{ OpDecodeCufIter,{Stack1, Stack1, OutBoolean, 0 }},
|
||||
|
||||
/*** 11. Iterator instructions ***/
|
||||
|
||||
@@ -1342,6 +1345,7 @@ static const struct {
|
||||
{ OpWIterNextK, {None, Local, OutUnknown, 0 }},
|
||||
{ OpIterFree, {None, None, OutNone, 0 }},
|
||||
{ OpMIterFree, {None, None, OutNone, 0 }},
|
||||
{ OpCIterFree, {None, None, OutNone, 0 }},
|
||||
|
||||
/*** 12. Include, eval, and define instructions ***/
|
||||
|
||||
@@ -3566,7 +3570,8 @@ std::unique_ptr<Tracelet> Translator::analyze(SrcKey sk,
|
||||
tas.varEnvTaint();
|
||||
}
|
||||
|
||||
DynLocation* outputs[] = { ni->outStack, ni->outLocal, ni->outLocal2,
|
||||
DynLocation* outputs[] = { ni->outStack,
|
||||
ni->outLocal, ni->outLocal2,
|
||||
ni->outStack2, ni->outStack3 };
|
||||
for (size_t i = 0; i < sizeof(outputs) / sizeof(*outputs); ++i) {
|
||||
if (outputs[i]) {
|
||||
|
||||
@@ -274,7 +274,7 @@ class NormalizedInstruction {
|
||||
vector<DynLocation*> inputs;
|
||||
DynLocation* outStack;
|
||||
DynLocation* outLocal;
|
||||
DynLocation* outLocal2; // Used for IterInitK, MIterInitK, IterNextK, and
|
||||
DynLocation* outLocal2; // Used for IterInitK, MIterInitK, IterNextK,
|
||||
// MIterNextK
|
||||
DynLocation* outStack2; // Used for CGetL2
|
||||
DynLocation* outStack3; // Used for CGetL3
|
||||
|
||||
@@ -102,7 +102,7 @@ inline bool isRet(PC pc) {
|
||||
}
|
||||
|
||||
inline bool isIter(PC pc) {
|
||||
return Op(*pc) >= OpIterInit && Op(*pc) <= OpMIterFree;
|
||||
return Op(*pc) >= OpIterInit && Op(*pc) <= OpCIterFree;
|
||||
}
|
||||
|
||||
inline int getImmIva(PC pc) {
|
||||
|
||||
@@ -707,7 +707,8 @@ bool FuncChecker::checkIter(State* cur, PC pc) {
|
||||
bool ok = true;
|
||||
if (Op(*pc) == OpIterInit || Op(*pc) == OpIterInitK ||
|
||||
Op(*pc) == OpWIterInit || Op(*pc) == OpWIterInitK ||
|
||||
Op(*pc) == OpMIterInit || Op(*pc) == OpMIterInitK) {
|
||||
Op(*pc) == OpMIterInit || Op(*pc) == OpMIterInitK ||
|
||||
Op(*pc) == OpDecodeCufIter) {
|
||||
if (cur->iters[id]) {
|
||||
verify_error(
|
||||
"IterInit* or MIterInit* <%d> trying to double-initialize\n", id);
|
||||
@@ -718,7 +719,9 @@ bool FuncChecker::checkIter(State* cur, PC pc) {
|
||||
verify_error("Cannot access un-initialized iter %d\n", id);
|
||||
ok = false;
|
||||
}
|
||||
if (Op(*pc) == OpIterFree || Op(*pc) == OpMIterFree) {
|
||||
if (Op(*pc) == OpIterFree ||
|
||||
Op(*pc) == OpMIterFree ||
|
||||
Op(*pc) == OpCIterFree) {
|
||||
cur->iters[id] = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ These are the allowed extensions:
|
||||
* .hphp_opts - Options passed to hphp when generating a bytecode repo.
|
||||
* .diff - The diff for .expect tests.
|
||||
* .hhas - HipHop Assembly.
|
||||
* .norepo - don't run the test in repo mode
|
||||
|
||||
You must have one `.php`; one and only one of `.expect`, `.expectf`, and
|
||||
`.expectregex`; and the rest are optional.
|
||||
|
||||
@@ -4,9 +4,13 @@ require_once "support.hhas";
|
||||
|
||||
class X {
|
||||
function __destruct() { var_dump(__METHOD__); }
|
||||
static function test($x) { return $x !== true; }
|
||||
static function s_test($x) { return $x !== true; }
|
||||
function o_test($x) { return $x === $this->p; }
|
||||
function __call($n, $a) { return $a[0] == $n; }
|
||||
}
|
||||
|
||||
function filt($x) { return $x === true; }
|
||||
|
||||
function test() {
|
||||
$a = array();
|
||||
var_dump(test2($a, "foo", new X));
|
||||
@@ -23,7 +27,10 @@ function test() {
|
||||
|
||||
var_dump(fast_array_filter($a, null));
|
||||
var_dump(fast_array_filter($a, function($a) { return !$a; }));
|
||||
var_dump(fast_array_filter($a, array('X', 'test')));
|
||||
var_dump(fast_array_filter($a, array('X', 's_test')));
|
||||
var_dump(fast_array_filter($a, array(new X, 'o_test')));
|
||||
var_dump(fast_array_filter($a, array(new X, 'bar')));
|
||||
var_dump(fast_array_filter($a, 'filt'));
|
||||
|
||||
var_dump(fast_array_map(function($a) { return $a.$a; }, $a));
|
||||
var_dump(fast_array_map(function&(&$a) { return $a; }, $a));
|
||||
|
||||
@@ -36,6 +36,22 @@ array(4) {
|
||||
[3]=>
|
||||
bool(false)
|
||||
}
|
||||
string(13) "X::__destruct"
|
||||
array(0) {
|
||||
}
|
||||
string(13) "X::__destruct"
|
||||
array(3) {
|
||||
["foo"]=>
|
||||
&string(3) "bar"
|
||||
[1]=>
|
||||
int(0)
|
||||
[2]=>
|
||||
bool(true)
|
||||
}
|
||||
array(1) {
|
||||
[2]=>
|
||||
bool(true)
|
||||
}
|
||||
array(5) {
|
||||
[0]=>
|
||||
string(2) "11"
|
||||
|
||||
@@ -24,70 +24,94 @@
|
||||
}
|
||||
|
||||
.function fast_array_filter($arr, $func) {
|
||||
.numiters 1;
|
||||
.numiters 2;
|
||||
|
||||
NewArray
|
||||
SetL $res
|
||||
PopC
|
||||
|
||||
CGetL $arr
|
||||
WIterInitK 0 endloop $v $k
|
||||
IssetL $func
|
||||
JmpZ loop_n
|
||||
IsArrayL $func
|
||||
JmpNZ loop_a
|
||||
loop_s: CGetL $func
|
||||
FPushFunc 1
|
||||
FPassL 0 $v
|
||||
FCall 1
|
||||
UnboxR
|
||||
JmpZ skip_s
|
||||
SetWithRefLM <L:$res EL:$k> $v
|
||||
skip_s: WIterNextK 0 loop_s $v $k
|
||||
Jmp endloop
|
||||
JmpZ null_func
|
||||
|
||||
loop_n: CGetL $v
|
||||
JmpZ skip_n
|
||||
SetWithRefLM <L:$res EL:$k> $v
|
||||
skip_n: WIterNextK 0 loop_n $v $k
|
||||
Jmp endloop
|
||||
|
||||
loop_a: CGetL $func
|
||||
FPushCuf 1
|
||||
CGetL $func
|
||||
DecodeCufIter 0
|
||||
JmpZ bad_func
|
||||
.try_fault kill_iter_0 {
|
||||
CGetL $arr
|
||||
WIterInitK 1 endloop_a $v $k
|
||||
.try_fault kill_iter_1 {
|
||||
loop_a: FPushCufIter 1 0
|
||||
FPassL 0 $v
|
||||
FCall 1
|
||||
UnboxR
|
||||
JmpZ skip_a
|
||||
SetWithRefLM <L:$res EL:$k> $v
|
||||
skip_a: WIterNextK 0 loop_a $v $k
|
||||
|
||||
endloop: CGetL $res
|
||||
skip_a: WIterNextK 1 loop_a $v $k
|
||||
}
|
||||
}
|
||||
endloop_a:CIterFree 0
|
||||
endloop_n:CGetL $res
|
||||
RetC
|
||||
|
||||
null_func:CGetL $arr
|
||||
WIterInitK 1 endloop_n $v $k
|
||||
.try_fault kill_iter_1 {
|
||||
loop_n: CGetL $v
|
||||
JmpZ skip_n
|
||||
SetWithRefLM <L:$res EL:$k> $v
|
||||
skip_n: WIterNextK 1 loop_n $v $k
|
||||
}
|
||||
Jmp endloop_n
|
||||
|
||||
bad_func: Null
|
||||
RetC
|
||||
|
||||
kill_iter_0:
|
||||
CIterFree 0
|
||||
Unwind
|
||||
kill_iter_1:
|
||||
IterFree 0
|
||||
Unwind
|
||||
}
|
||||
|
||||
.function fast_array_map($func, $arr) {
|
||||
.numiters 1;
|
||||
.numiters 2;
|
||||
|
||||
IssetL $func
|
||||
JmpZ ident
|
||||
CGetL $func
|
||||
DecodeCufIter 0
|
||||
JmpZ bad_func
|
||||
|
||||
.try_fault kill_iter_0 {
|
||||
NewArray
|
||||
SetL $res
|
||||
PopC
|
||||
|
||||
CGetL $arr
|
||||
WIterInitK 0 endloop $v $k
|
||||
WIterInitK 1 endloop $v $k
|
||||
|
||||
loop_x: CGetL $func
|
||||
FPushCuf 1
|
||||
.try_fault kill_iter_1 {
|
||||
loop_x: FPushCufIter 1 0
|
||||
FPassL 0 $v
|
||||
FCall 1
|
||||
SetWithRefRM <L:$res EL:$k>
|
||||
WIterNextK 0 loop_x $v $k
|
||||
|
||||
endloop: CGetL $res
|
||||
WIterNextK 1 loop_x $v $k
|
||||
}
|
||||
}
|
||||
endloop: CIterFree 0
|
||||
CGetL $res
|
||||
RetC
|
||||
|
||||
ident: CGetL $arr
|
||||
RetC
|
||||
|
||||
bad_func: Null
|
||||
RetC
|
||||
kill_iter_0:
|
||||
CIterFree 0
|
||||
Unwind
|
||||
kill_iter_1:
|
||||
IterFree 0
|
||||
Unwind
|
||||
}
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário