diff --git a/hphp/doc/bytecode.specification b/hphp/doc/bytecode.specification index dc34e0718..d14baec2a 100644 --- a/hphp/doc/bytecode.specification +++ b/hphp/doc/bytecode.specification @@ -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 [] -> [C] passed (given by %1) and a reference to the FPI structure for the constructor for class x. +DecodeCufIter [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 [] -> [] + FPI push the result of a previous DecodeCufIter. No warning is raised. + FPushCuf [C] -> [] FPushCufF [C] -> [] @@ -3463,6 +3475,11 @@ MIterFree [] -> [] only needed for guarding against exceptions and implementing break and return control flow statements inside iterator loops. +CIterFree [] -> [] + + 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. diff --git a/hphp/doc/ir.specification b/hphp/doc/ir.specification index 2f6990883..f69d2d380 100644 --- a/hphp/doc/ir.specification +++ b/hphp/doc/ir.specification @@ -918,6 +918,17 @@ D:StkPtr = SpillFrame S0:StkPtr Defines the fields for an activation record and writes them to the stack pointed to by S1. +D:StkPtr = CufIterSpillFrame 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 S0:FramePtr + + Free the iterator variable whose index is given by S1 in the stack + frame pointed to by S0. + +CIterFree S0:FramePtr Free the iterator variable whose index is given by S1 in the stack frame pointed to by S0. diff --git a/hphp/runtime/base/array/array_iterator.cpp b/hphp/runtime/base/array/array_iterator.cpp index 5bca51244..bf895fc5d 100644 --- a/hphp/runtime/base/array/array_iterator.cpp +++ b/hphp/runtime/base/array/array_iterator.cpp @@ -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 diff --git a/hphp/runtime/base/array/array_iterator.h b/hphp/runtime/base/array/array_iterator.h index eaf4464be..11d281380 100644 --- a/hphp/runtime/base/array/array_iterator.h +++ b/hphp/runtime/base/array/array_iterator.h @@ -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); diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index a74f90cf0..87b48df1f 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -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(); diff --git a/hphp/runtime/vm/hhbc.cpp b/hphp/runtime/vm/hhbc.cpp index cf2db1056..3e08b5de6 100644 --- a/hphp/runtime/vm/hhbc.cpp +++ b/hphp/runtime/vm/hhbc.cpp @@ -499,6 +499,7 @@ bool pushesActRec(Opcode opcode) { case OpFPushClsMethodD: case OpFPushCtor: case OpFPushCtorD: + case OpFPushCufIter: case OpFPushCuf: case OpFPushCufF: case OpFPushCufSafe: diff --git a/hphp/runtime/vm/hhbc.h b/hphp/runtime/vm/hhbc.h index f96247269..0e834aaba 100644 --- a/hphp/runtime/vm/hhbc.h +++ b/hphp/runtime/vm/hhbc.h @@ -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) \ diff --git a/hphp/runtime/vm/translator/hopt/codegen.cpp b/hphp/runtime/vm/translator/hopt/codegen.cpp index 8d653699f..c7141118b 100644 --- a/hphp/runtime/vm/translator/hopt/codegen.cpp +++ b/hphp/runtime/vm/translator/hopt/codegen.cpp @@ -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()->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()->args; + auto const iterId = inst->extra()->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(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()->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()->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()), diff --git a/hphp/runtime/vm/translator/hopt/codegen.h b/hphp/runtime/vm/translator/hopt/codegen.h index d0f0bafc6..f2ddc179c 100644 --- a/hphp/runtime/vm/translator/hopt/codegen.h +++ b/hphp/runtime/vm/translator/hopt/codegen.h @@ -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); diff --git a/hphp/runtime/vm/translator/hopt/extradata.h b/hphp/runtime/vm/translator/hopt/extradata.h index 57a08cb28..aea66a343 100644 --- a/hphp/runtime/vm/translator/hopt/extradata.h +++ b/hphp/runtime/vm/translator/hopt/extradata.h @@ -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()(iterId); } + std::string show() const { return folly::to(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()(iterId) ^ std::hash()(args); + } + std::string show() const { + return folly::to(iterId, ',', args); + } + + uint32_t args; + uint32_t iterId; +}; + struct ConstData : IRExtraData { template 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); diff --git a/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp b/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp index 06030ffa9..0a2ede348 100644 --- a/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp @@ -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(); diff --git a/hphp/runtime/vm/translator/hopt/hhbctranslator.h b/hphp/runtime/vm/translator/hopt/hhbctranslator.h index a9f9a8acd..1ff1472aa 100644 --- a/hphp/runtime/vm/translator/hopt/hhbctranslator.h +++ b/hphp/runtime/vm/translator/hopt/hhbctranslator.h @@ -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 diff --git a/hphp/runtime/vm/translator/hopt/ir.h b/hphp/runtime/vm/translator/hopt/ir.h index 23f9a8861..0bdf9f0ad 100644 --- a/hphp/runtime/vm/translator/hopt/ir.h +++ b/hphp/runtime/vm/translator/hopt/ir.h @@ -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) \ diff --git a/hphp/runtime/vm/translator/hopt/irtranslator.cpp b/hphp/runtime/vm/translator/hopt/irtranslator.cpp index d3b236364..36ed4d606 100644 --- a/hphp/runtime/vm/translator/hopt/irtranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/irtranslator.cpp @@ -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) \ diff --git a/hphp/runtime/vm/translator/hopt/linearscan.cpp b/hphp/runtime/vm/translator/hopt/linearscan.cpp index 6208998c4..b0f974085 100644 --- a/hphp/runtime/vm/translator/hopt/linearscan.cpp +++ b/hphp/runtime/vm/translator/hopt/linearscan.cpp @@ -457,6 +457,7 @@ void LinearScan::allocRegToInstruction(InstructionList::iterator it) { opc == CallArray || opc == SpillStack || opc == SpillFrame || + opc == CufIterSpillFrame || opc == ExceptionBarrier || opc == RetAdjustStack || opc == InterpOne || diff --git a/hphp/runtime/vm/translator/hopt/simplifier.cpp b/hphp/runtime/vm/translator/hopt/simplifier.cpp index 6ee2b204e..4314c7bfa 100644 --- a/hphp/runtime/vm/translator/hopt/simplifier.cpp +++ b/hphp/runtime/vm/translator/hopt/simplifier.cpp @@ -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); diff --git a/hphp/runtime/vm/translator/hopt/tracebuilder.cpp b/hphp/runtime/vm/translator/hopt/tracebuilder.cpp index 2e20ca409..7ff9f8445 100644 --- a/hphp/runtime/vm/translator/hopt/tracebuilder.cpp +++ b/hphp/runtime/vm/translator/hopt/tracebuilder.cpp @@ -229,6 +229,7 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) { } case SpillFrame: + case CufIterSpillFrame: m_spValue = inst->dst(); m_spOffset += kNumActRecCells; break; diff --git a/hphp/runtime/vm/translator/translator-x64.cpp b/hphp/runtime/vm/translator/translator-x64.cpp index 0aceba690..812e582c8 100644 --- a/hphp/runtime/vm/translator/translator-x64.cpp +++ b/hphp/runtime/vm/translator/translator-x64.cpp @@ -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) \ diff --git a/hphp/runtime/vm/translator/translator-x64.h b/hphp/runtime/vm/translator/translator-x64.h index 2df3520d6..e687d0253 100644 --- a/hphp/runtime/vm/translator/translator-x64.h +++ b/hphp/runtime/vm/translator/translator-x64.h @@ -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 \ diff --git a/hphp/runtime/vm/translator/translator.cpp b/hphp/runtime/vm/translator/translator.cpp index 3da884b5c..d780b4efa 100644 --- a/hphp/runtime/vm/translator/translator.cpp +++ b/hphp/runtime/vm/translator/translator.cpp @@ -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 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]) { diff --git a/hphp/runtime/vm/translator/translator.h b/hphp/runtime/vm/translator/translator.h index d98f57344..97e96957b 100644 --- a/hphp/runtime/vm/translator/translator.h +++ b/hphp/runtime/vm/translator/translator.h @@ -274,7 +274,7 @@ class NormalizedInstruction { vector 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 diff --git a/hphp/runtime/vm/verifier/cfg.h b/hphp/runtime/vm/verifier/cfg.h index 3499fd156..ce4bd30b9 100644 --- a/hphp/runtime/vm/verifier/cfg.h +++ b/hphp/runtime/vm/verifier/cfg.h @@ -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) { diff --git a/hphp/runtime/vm/verifier/check_func.cpp b/hphp/runtime/vm/verifier/check_func.cpp index 15e51c829..ae8f9dbe5 100644 --- a/hphp/runtime/vm/verifier/check_func.cpp +++ b/hphp/runtime/vm/verifier/check_func.cpp @@ -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; } } diff --git a/hphp/test/README.md b/hphp/test/README.md index cd3afc867..8c041be87 100644 --- a/hphp/test/README.md +++ b/hphp/test/README.md @@ -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. diff --git a/hphp/test/slow/builtin_support/setwithref.php b/hphp/test/slow/builtin_support/setwithref.php index 90e971907..3033b9447 100644 --- a/hphp/test/slow/builtin_support/setwithref.php +++ b/hphp/test/slow/builtin_support/setwithref.php @@ -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)); diff --git a/hphp/test/slow/builtin_support/setwithref.php.expect b/hphp/test/slow/builtin_support/setwithref.php.expect index a99e6e3f4..bcfcdbcb1 100644 --- a/hphp/test/slow/builtin_support/setwithref.php.expect +++ b/hphp/test/slow/builtin_support/setwithref.php.expect @@ -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" diff --git a/hphp/test/slow/builtin_support/support.hhas b/hphp/test/slow/builtin_support/support.hhas index f2eca79b5..7198e3615 100644 --- a/hphp/test/slow/builtin_support/support.hhas +++ b/hphp/test/slow/builtin_support/support.hhas @@ -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 $v -skip_s: WIterNextK 0 loop_s $v $k - Jmp endloop + JmpZ null_func -loop_n: CGetL $v - JmpZ skip_n - SetWithRefLM $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 $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 $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 - 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 }