Added IterBreakV, MIter{Init,InitK,Next,NextK,Free} and fixed memory tracking bug.
Added IR opcodes to perform MIter* instructions in JIT. Added IterBreakV bytecode operation to break out of multiple loops containing iterators. Emitter and assembler were modified to support such use.
Esse commit está contido em:
@@ -306,6 +306,7 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#define DEC_MA std::vector<uchar>
|
||||
#define DEC_BLA std::vector<Label*>&
|
||||
#define DEC_SLA std::vector<StrOff>&
|
||||
#define DEC_ILA std::vector<IterPair>&
|
||||
#define DEC_IVA int32_t
|
||||
#define DEC_HA int32_t
|
||||
#define DEC_IA int32_t
|
||||
@@ -375,6 +376,7 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#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)
|
||||
@@ -458,6 +460,19 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#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()); \
|
||||
@@ -618,6 +633,11 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#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
|
||||
@@ -2024,31 +2044,16 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// "continue N" breaks out of N-1 loops and jumps to the top of the Nth.
|
||||
// So whether this is break or continue, free N-1 Iters.
|
||||
for (uint64_t i = 0; i < destLevel; ++i) {
|
||||
if (m_controlTargets[i].m_itId != -1) {
|
||||
if (m_controlTargets[i].m_itRef) {
|
||||
e.MIterFree(m_controlTargets[i].m_itId);
|
||||
} else {
|
||||
e.IterFree(m_controlTargets[i].m_itId);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// Only free the Nth-level Iter for break statements.
|
||||
if (bs->is(Statement::KindOfBreakStatement)) {
|
||||
if (m_controlTargets[destLevel].m_itId != -1) {
|
||||
if (m_controlTargets[destLevel].m_itRef) {
|
||||
e.MIterFree(m_controlTargets[destLevel].m_itId);
|
||||
} else {
|
||||
e.IterFree(m_controlTargets[destLevel].m_itId);
|
||||
}
|
||||
}
|
||||
e.Jmp(m_controlTargets[destLevel].m_brkTarg);
|
||||
} else {
|
||||
e.Jmp(m_controlTargets[destLevel].m_cntTarg);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4280,6 +4285,25 @@ void EmitterVisitor::emitCGet(Emitter& e) {
|
||||
}
|
||||
}
|
||||
|
||||
void EmitterVisitor::emitIterBreak(Emitter& e, uint64_t n, Label& targ) {
|
||||
std::vector<Emitter::IterPair> immItrList;
|
||||
|
||||
for (uint64_t level = 0; level < n; ++level) {
|
||||
if (m_controlTargets[level].m_itId != -1) {
|
||||
immItrList.push_back(Emitter::IterPair(m_controlTargets[level].m_itRef
|
||||
? KindOfMIter : KindOfIter,
|
||||
m_controlTargets[level]
|
||||
.m_itId));
|
||||
}
|
||||
}
|
||||
|
||||
if (immItrList.size()) {
|
||||
e.IterBreak(immItrList, targ);
|
||||
} else {
|
||||
e.Jmp(targ);
|
||||
}
|
||||
}
|
||||
|
||||
void EmitterVisitor::emitVGet(Emitter& e) {
|
||||
if (checkIfStackEmpty("VGet*")) return;
|
||||
LocationGuard loc(e, m_tempLoc);
|
||||
@@ -6460,7 +6484,7 @@ class ForeachIterGuard {
|
||||
public:
|
||||
ForeachIterGuard(EmitterVisitor& ev,
|
||||
Id iterId,
|
||||
EmitterVisitor::IterKind kind)
|
||||
IterKind kind)
|
||||
: m_ev(ev)
|
||||
{
|
||||
m_ev.pushIterScope(iterId, kind);
|
||||
|
||||
@@ -89,6 +89,12 @@ public:
|
||||
Id str;
|
||||
Label* dest;
|
||||
};
|
||||
|
||||
struct IterPair {
|
||||
IterPair(IterKind k, Id i) : kind(k), id(i) {}
|
||||
IterKind kind;
|
||||
Id id;
|
||||
};
|
||||
#define O(name, imm, pop, push, flags) \
|
||||
void name(imm);
|
||||
#define NA
|
||||
@@ -103,6 +109,7 @@ public:
|
||||
#define MA std::vector<uchar>
|
||||
#define BLA std::vector<Label*>&
|
||||
#define SLA std::vector<StrOff>&
|
||||
#define ILA std::vector<IterPair>&
|
||||
#define IVA int32_t
|
||||
#define HA int32_t
|
||||
#define IA int32_t
|
||||
@@ -122,6 +129,7 @@ public:
|
||||
#undef MA
|
||||
#undef BLA
|
||||
#undef SLA
|
||||
#undef ILA
|
||||
#undef IVA
|
||||
#undef HA
|
||||
#undef IA
|
||||
@@ -374,11 +382,6 @@ public:
|
||||
EXCEPTION_COMMON_IMPL(IncludeTimeFatalException);
|
||||
};
|
||||
|
||||
enum IterKind {
|
||||
KindOfIter = 0,
|
||||
KindOfMIter = 1
|
||||
};
|
||||
|
||||
void pushIterScope(Id id, IterKind kind) {
|
||||
m_pendingIters.emplace_back(id, kind);
|
||||
}
|
||||
@@ -605,6 +608,7 @@ public:
|
||||
void emitStringSwitch(Emitter& e, SwitchStatementPtr s,
|
||||
std::vector<Label>& caseLabels, Label& done,
|
||||
const SwitchState& state);
|
||||
void emitIterBreak(Emitter& e, uint64_t n, Label& targ);
|
||||
|
||||
void markElem(Emitter& e);
|
||||
void markNewElem(Emitter& e);
|
||||
|
||||
@@ -167,6 +167,11 @@ void Peephole::buildJumpTargets() {
|
||||
foreachSwitchTarget(instr, [&](Offset& o) {
|
||||
m_jumpTargets.insert(pos + o);
|
||||
});
|
||||
} else if (*instr == OpIterBreak) {
|
||||
uint32_t veclen = *(uint32_t *)(instr + 1);
|
||||
assert(veclen > 0);
|
||||
Offset target = *(Offset *)((uint32_t *)(instr + 1) + 2 * veclen + 1);
|
||||
m_jumpTargets.insert(pos + target);
|
||||
} else {
|
||||
Offset target = instrJumpTarget((Op*)m_ue.m_bc, pos);
|
||||
if (target != InvalidAbsoluteOffset) {
|
||||
|
||||
@@ -755,7 +755,8 @@ number of cells from the stack. In most cases the immediate vector arguments
|
||||
are ordered such that the loc-desc comes first (deepest in the stack), with the
|
||||
last M-vector element last (shallowest in the stack). However, classrefs that
|
||||
are part of the BaseSC and BaseSL loc-desc inputs always come last (the cell
|
||||
input to BaseSC comes first though).
|
||||
input to BaseSC comes first though). Instuctions accepting an immediate vector
|
||||
containing a list of iterators and iterator types use the notation "<iter-vec>".
|
||||
|
||||
In addition to describing each instruction, this instruction set documentation
|
||||
also describes several operations that encapsulate fundamental, but non-trivial
|
||||
@@ -3468,6 +3469,14 @@ CIterFree <iterator id> [] -> []
|
||||
Cuf iterator free. This instruction frees the specified iterator
|
||||
variable.
|
||||
|
||||
IterBreak <iter-vec> <rel offset> [] -> []
|
||||
|
||||
Iterator break. Frees vectors in %1 in left to right order then transfers
|
||||
control to the locaton specified by %2. Surprise checks are performed
|
||||
before iterators are freed so that in the event of an exception iterators
|
||||
are not double freed. Note that as with normal jumps surprise checks will
|
||||
only be performed if %2 is negative.
|
||||
|
||||
|
||||
12. Include, eval, and define instructions
|
||||
------------------------------------------
|
||||
@@ -4408,3 +4417,33 @@ class B extends A { public static function f() { return parent::x; } }
|
||||
ClsCnsD "x" "A"
|
||||
RetC
|
||||
|
||||
foreach ($arr1 as $v1) {
|
||||
foreach($arr2 as $v2) {
|
||||
if ($v1 == $v2) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CGetL $arr1
|
||||
IterInit 0 endFor1 $v1
|
||||
startFor1:
|
||||
|
||||
CGetL $arr2
|
||||
IterInit 1 endFor2 $v2
|
||||
startFor2:
|
||||
|
||||
CGetL $v1
|
||||
CGetL $v2
|
||||
Eq
|
||||
|
||||
JmpNZ notEqual
|
||||
IterBreak <(Iter) 1, (Iter) 2> endFor1
|
||||
notEqual:
|
||||
|
||||
IterNext 1 startFor2 $v2
|
||||
endFor2:
|
||||
|
||||
IterNext 1 startFor1 $v1
|
||||
endFor1:
|
||||
|
||||
|
||||
@@ -1590,6 +1590,8 @@ D:Bool = IterInit S0:{Arr|Obj} S1:FramePtr S2:ConstInt S3:ConstInt
|
||||
D:Bool = IterInitK S0:{Arr|Obj} S1:FramePtr S2:ConstInt S3:ConstInt S4:ConstInt
|
||||
D:Bool = WIterInit S0:{Arr|Obj} S1:FramePtr S2:ConstInt S3:ConstInt
|
||||
D:Bool = WIterInitK S0:{Arr|Obj} S1:FramePtr S2:ConstInt S3:ConstInt S4:ConstInt
|
||||
D:Bool = MIterInit S0:{Arr|Obj} S1:FramePtr S2:ConstInt S3:ConstInt
|
||||
D:Bool = MIterInitK S0:{Arr|Obj} S1:FramePtr S2:ConstInt S3:ConstInt S4:ConstInt
|
||||
|
||||
Initializes the iterator variable whose index is given by S2. This
|
||||
instruction creates the appropriate iterator for the array or object
|
||||
@@ -1610,6 +1612,9 @@ D:Bool = WIterInitK S0:{Arr|Obj} S1:FramePtr S2:ConstInt S3:ConstInt S4:ConstInt
|
||||
The WIterInit and WIterInitK instructions copy referenced array
|
||||
elements by reference, and non-referenced array elements by value.
|
||||
|
||||
The MIterInit and MIterInitK instructions always copy the array element
|
||||
by reference.
|
||||
|
||||
This instruction has the ConsumesRC property because it either
|
||||
decrements the reference count of s0 or stores a reference to S0
|
||||
into the new iterator.
|
||||
@@ -1618,6 +1623,8 @@ D:Bool = IterNext S0:FramePtr S1:ConstInt S2:ConstInt
|
||||
D:Bool = IterNextK S0:FramePtr S1:ConstInt S2:ConstInt S3:ConstInt
|
||||
D:Bool = WIterNext S0:FramePtr S1:ConstInt S2:ConstInt
|
||||
D:Bool = WIterNextK S0:FramePtr S1:ConstInt S2:ConstInt S3:ConstInt
|
||||
D:Bool = MIterNext S0:FramePtr S1:ConstInt S2:ConstInt
|
||||
D:Bool = MIterNextK S0:FramePtr S1:ConstInt S2:ConstInt S3:ConstInt
|
||||
|
||||
Advances the iterator variable whose index is given by S1. S2 and S3
|
||||
are local variable indices. S0 points to the stack frame containing
|
||||
@@ -1633,8 +1640,11 @@ D:Bool = WIterNextK S0:FramePtr S1:ConstInt S2:ConstInt S3:ConstInt
|
||||
element by value.
|
||||
The WIterInit and WIterInitK instructions copy referenced array
|
||||
elements by reference, and non-referenced array elements by value.
|
||||
The MIterNext and MIterNextK instructions always copy the array element
|
||||
by reference.
|
||||
|
||||
IterFree<IterId> S0:FramePtr
|
||||
MIterFree<IterId> S0:FramePtr
|
||||
|
||||
Free the iterator variable whose index is given by S1 in the stack
|
||||
frame pointed to by S0.
|
||||
|
||||
@@ -713,48 +713,6 @@ bool Iter::init(TypedValue* c1) {
|
||||
return hasElems;
|
||||
}
|
||||
|
||||
bool Iter::minit(TypedValue* v1) {
|
||||
assert(v1->m_type == KindOfRef);
|
||||
bool hasElems = true;
|
||||
TypedValue* rtv = v1->m_data.pref->tv();
|
||||
if (rtv->m_type == KindOfArray) {
|
||||
ArrayData* ad = rtv->m_data.parr;
|
||||
if (!ad->empty()) {
|
||||
MArrayIter& mi = marr();
|
||||
(void) new (&mi) MArrayIter(v1->m_data.pref);
|
||||
mi.advance();
|
||||
} else {
|
||||
hasElems = false;
|
||||
}
|
||||
} else if (rtv->m_type == KindOfObject) {
|
||||
if (rtv->m_data.pobj->isCollection()) {
|
||||
raise_error("Collection elements cannot be taken by reference");
|
||||
}
|
||||
bool isIterator;
|
||||
Object obj = rtv->m_data.pobj->iterableObject(isIterator);
|
||||
if (isIterator) {
|
||||
raise_error("An iterator cannot be used with foreach by reference");
|
||||
}
|
||||
Class* ctx = arGetContextClass(g_vmContext->getFP());
|
||||
CStrRef ctxStr = ctx ? ctx->nameRef() : null_string;
|
||||
Array iterArray = obj->o_toIterArray(ctxStr, true);
|
||||
if (iterArray->empty()) {
|
||||
hasElems = false;
|
||||
} else {
|
||||
ArrayData* ad = iterArray.detach();
|
||||
MArrayIter& mi = marr();
|
||||
(void) new (&mi) MArrayIter(ad);
|
||||
mi.advance();
|
||||
}
|
||||
} else {
|
||||
if (!hphpiCompat) {
|
||||
raise_warning("Invalid argument supplied for foreach()");
|
||||
}
|
||||
hasElems = false;
|
||||
}
|
||||
return hasElems;
|
||||
}
|
||||
|
||||
bool Iter::next() {
|
||||
assert(arr().getIterType() == ArrayIter::TypeArray ||
|
||||
arr().getIterType() == ArrayIter::TypeIterator);
|
||||
@@ -775,20 +733,6 @@ bool Iter::next() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Iter::mnext() {
|
||||
MArrayIter &mi = marr();
|
||||
if (!mi.advance()) {
|
||||
// If after advancing the iterator we have reached the end, free
|
||||
// the iterator and fall through to the next instruction.
|
||||
mi.~MArrayIter();
|
||||
return false;
|
||||
} else {
|
||||
// If after advancing the iterator we have not reached the end,
|
||||
// jump to the location specified by the second immediate argument.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void Iter::free() {
|
||||
assert(arr().getIterType() == ArrayIter::TypeArray ||
|
||||
arr().getIterType() == ArrayIter::TypeIterator);
|
||||
@@ -1378,5 +1322,91 @@ template int64_t iter_next_key<true>(Iter* dest,
|
||||
TypedValue* valOut,
|
||||
TypedValue* keyOut);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// MIter functions
|
||||
|
||||
HOT_FUNC
|
||||
int64_t new_miter_array_key(Iter* dest, RefData* v1,
|
||||
TypedValue* valOut, TypedValue* keyOut) {
|
||||
TRACE(2, "%s: I %p, ad %p\n", __func__, dest, v1);
|
||||
|
||||
TypedValue* rtv = v1->tv();
|
||||
ArrayData* ad = rtv->m_data.parr;
|
||||
|
||||
if (UNLIKELY(ad->empty())) {
|
||||
return 0LL;
|
||||
}
|
||||
|
||||
(void) new (&dest->marr()) MArrayIter(v1);
|
||||
dest->marr().advance();
|
||||
|
||||
tvAsVariant(valOut).assignRef(dest->marr().val());
|
||||
if (keyOut) {
|
||||
tvAsVariant(keyOut).assignVal(dest->marr().key());
|
||||
}
|
||||
|
||||
return 1LL;
|
||||
}
|
||||
|
||||
HOT_FUNC
|
||||
int64_t new_miter_object(Iter* dest, RefData* ref, Class* ctx,
|
||||
TypedValue* valOut, TypedValue* keyOut) {
|
||||
ObjectData *obj = ref->tv()->m_data.pobj;
|
||||
if (obj->isCollection()) {
|
||||
raise_error("Collection elements cannot be taken by reference");
|
||||
}
|
||||
|
||||
bool isIterator;
|
||||
Object itObj = obj->iterableObject(isIterator);
|
||||
if (isIterator) {
|
||||
raise_error("An iterator cannot be used with foreach by reference");
|
||||
}
|
||||
|
||||
TRACE(2, "%s: I %p, obj %p, ctx %p, iterate as array\n",
|
||||
__func__, dest, obj, ctx);
|
||||
CStrRef ctxStr = ctx ? ctx->nameRef() : null_string;
|
||||
Array iterArray(itObj->o_toIterArray(ctxStr, true));
|
||||
ArrayData* ad = iterArray.detach();
|
||||
(void) new (&dest->marr()) MArrayIter(ad);
|
||||
if (UNLIKELY(!dest->marr().advance())) {
|
||||
// Iterator was empty; call the destructor on the iterator we just
|
||||
// constructed.
|
||||
dest->marr().~MArrayIter();
|
||||
return 0LL;
|
||||
}
|
||||
|
||||
tvAsVariant(valOut).assignRef(dest->marr().val());
|
||||
if (keyOut) {
|
||||
tvAsVariant(keyOut).assignVal(dest->marr().key());
|
||||
}
|
||||
return 1LL;
|
||||
}
|
||||
|
||||
int64_t new_miter_other(Iter* dest, RefData* data) {
|
||||
TRACE(2, "%s: I %p, data %p, invalid type\n",
|
||||
__func__, dest, data);
|
||||
|
||||
// TODO(#2570852): we should really issue a warning here
|
||||
return 0LL;
|
||||
}
|
||||
|
||||
HOT_FUNC
|
||||
int64_t miter_next_key(Iter* iter, TypedValue* valOut, TypedValue* keyOut) {
|
||||
TRACE(2, "miter_next_key: I %p\n", iter);
|
||||
MArrayIter& marr = iter->marr();
|
||||
|
||||
if (UNLIKELY(!marr.advance())) {
|
||||
marr.~MArrayIter();
|
||||
return 0LL;
|
||||
}
|
||||
|
||||
tvAsVariant(valOut).assignRef(marr.val());
|
||||
if (keyOut) {
|
||||
tvAsVariant(keyOut).assignVal(marr.key());
|
||||
}
|
||||
|
||||
return 1LL;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
@@ -536,9 +536,7 @@ struct Iter {
|
||||
CufIter& cuf() { return m_u.cufiter; }
|
||||
|
||||
bool init(TypedValue* c1);
|
||||
bool minit(TypedValue* v1);
|
||||
bool next();
|
||||
bool mnext();
|
||||
void free();
|
||||
void mfree();
|
||||
void cfree();
|
||||
@@ -567,6 +565,14 @@ int64_t iter_next(Iter* dest, TypedValue* val);
|
||||
template <bool withRef>
|
||||
int64_t iter_next_key(Iter* dest, TypedValue* val, TypedValue* key);
|
||||
|
||||
|
||||
int64_t new_miter_array_key(Iter* dest, RefData* arr, TypedValue* val,
|
||||
TypedValue* key);
|
||||
int64_t new_miter_object(Iter* dest, RefData* obj, Class* ctx,
|
||||
TypedValue* val, TypedValue* key);
|
||||
int64_t new_miter_other(Iter* dest, RefData* data);
|
||||
int64_t miter_next_key(Iter* dest, TypedValue* val, TypedValue* key);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
|
||||
@@ -487,7 +487,7 @@ private:
|
||||
bool initIterator(PC& pc, PC& origPc, Iter* it,
|
||||
Offset offset, Cell* c1);
|
||||
bool initIteratorM(PC& pc, PC& origPc, Iter* it,
|
||||
Offset offset, Var* v1);
|
||||
Offset offset, Var* v1, TypedValue* val, TypedValue* key);
|
||||
void jmpSurpriseCheck(Offset o);
|
||||
template<Op op> void jmpOpImpl(PC& pc);
|
||||
#define O(name, imm, pusph, pop, flags) \
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#endif
|
||||
#define __STDC_LIMIT_MACROS
|
||||
#include <stdint.h>
|
||||
|
||||
#include "hphp/runtime/base/memory/memory_manager.h"
|
||||
#include "hphp/runtime/base/memory/smart_allocator.h"
|
||||
@@ -33,6 +32,8 @@
|
||||
#include "hphp/util/process.h"
|
||||
#include "hphp/util/trace.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -325,12 +326,12 @@ inline void* MemoryManager::smartRealloc(void* ptr, size_t nbytes) {
|
||||
SweepNode* next = n->next;
|
||||
SweepNode* prev = n->prev;
|
||||
SweepNode* n2 = (SweepNode*) realloc(n, nbytes + sizeof(SweepNode));
|
||||
|
||||
// ensure that we have not exceeded the per request memory limit (#2529805)
|
||||
refreshStatsHelper();
|
||||
if (n2 != n) {
|
||||
// block moved; must re-link to sweeplist
|
||||
next->prev = prev->next = n2;
|
||||
if (UNLIKELY(m_stats.usage > m_stats.maxBytes)) {
|
||||
refreshStatsHelper();
|
||||
}
|
||||
}
|
||||
return n2 + 1;
|
||||
}
|
||||
|
||||
@@ -879,6 +879,46 @@ std::vector<uchar> read_immvector(AsmState& as, int& stackCount) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Read in a vector of iterators the format for this vector is:
|
||||
// <(TYPE) ID, (TYPE) ID, ...>
|
||||
// Where TYPE := Iter | MIter | CIter
|
||||
// and ID := Integer
|
||||
std::vector<uint32_t> read_itervec(AsmState& as) {
|
||||
std::vector<uint32_t> ret;
|
||||
|
||||
as.in.skipSpaceTab();
|
||||
as.in.expect('<');
|
||||
|
||||
std::string word;
|
||||
char *end;
|
||||
|
||||
for (;;) {
|
||||
as.in.expectWs('(');
|
||||
if (!as.in.readword(word)) as.error("Was expecting Iterator type.");
|
||||
if (!word.compare("Iter")) ret.push_back(KindOfIter);
|
||||
else if (!word.compare("MIter")) ret.push_back(KindOfMIter);
|
||||
else if (!word.compare("CIter")) ret.push_back(KindOfCIter);
|
||||
else as.error("Unknown iterator type `" + word + "'");
|
||||
as.in.expectWs(')');
|
||||
|
||||
as.in.skipSpaceTab();
|
||||
|
||||
if (!as.in.readword(word)) as.error("Was expecting iterator id.");
|
||||
uint32_t iterId = folly::to<uint32_t>(word);
|
||||
|
||||
if (!isdigit(word.back())) {
|
||||
if (word.back() == '>') break;
|
||||
if (word.back() != ',') as.error("Was expecting `,'.");
|
||||
} else {
|
||||
as.in.skipSpaceTab();
|
||||
if (as.in.peek() == '>') { as.in.getc(); break; }
|
||||
as.in.expect(',');
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Jump tables are lists of labels.
|
||||
std::vector<std::string> read_jmpvector(AsmState& as) {
|
||||
std::vector<std::string> ret;
|
||||
@@ -978,6 +1018,14 @@ OpcodeParserMap opcode_parsers;
|
||||
as.ue->emitByte(vecImm[i]); \
|
||||
}
|
||||
|
||||
#define IMM_ILA do { \
|
||||
std::vector<uint32_t> vecImm = read_itervec(as); \
|
||||
as.ue->emitInt32(vecImm.size() / 2); \
|
||||
for (auto& i : vecImm) { \
|
||||
as.ue->emitInt32(i); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define IMM_BLA do { \
|
||||
std::vector<std::string> vecImm = read_jmpvector(as); \
|
||||
as.ue->emitInt32(vecImm.size()); \
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include "hphp/runtime/base/shared/shared_variant.h"
|
||||
#include "hphp/runtime/vm/debug/debug.h"
|
||||
|
||||
#include "hphp/runtime/vm/hhbc.h"
|
||||
#include "hphp/runtime/vm/treadmill.h"
|
||||
#include "hphp/runtime/vm/php_debug.h"
|
||||
#include "hphp/runtime/vm/debugger_hook.h"
|
||||
@@ -202,6 +203,13 @@ Transl::Translator* tx() {
|
||||
#define DECODE_HA(var) DECODE_IVA(var)
|
||||
#define DECODE_IA(var) DECODE_IVA(var)
|
||||
|
||||
#define DECODE_ITER_LIST(typeList, idList, vecLen) \
|
||||
DECODE(int32_t, vecLen); \
|
||||
assert(vecLen > 0); \
|
||||
Id* typeList = (Id*)pc; \
|
||||
Id* idList = (Id*)pc + 1; \
|
||||
pc += 2 * vecLen * sizeof(Id);
|
||||
|
||||
#define SYNC() m_pc = pc
|
||||
|
||||
//=============================================================================
|
||||
@@ -4036,6 +4044,37 @@ inline void OPTBLD_INLINE VMExecutionContext::iopJmpNZ(PC& pc) {
|
||||
jmpOpImpl<OpJmpNZ>(pc);
|
||||
}
|
||||
|
||||
#define FREE_ITER_LIST(typeList, idList, vecLen) do { \
|
||||
int iterIndex; \
|
||||
for (iterIndex = 0; iterIndex < 2 * veclen; iterIndex += 2) { \
|
||||
Id iterType = typeList[iterIndex]; \
|
||||
Id iterId = idList[iterIndex]; \
|
||||
\
|
||||
Iter *iter = frame_iter(m_fp, iterId); \
|
||||
\
|
||||
switch (iterType) { \
|
||||
case KindOfIter: iter->free(); break; \
|
||||
case KindOfMIter: iter->mfree(); break; \
|
||||
case KindOfCIter: iter->cfree(); break; \
|
||||
} \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopIterBreak(PC& pc) {
|
||||
PC savedPc = pc;
|
||||
NEXT();
|
||||
DECODE_ITER_LIST(iterTypeList, iterIdList, veclen);
|
||||
DECODE_JMP(Offset, offset);
|
||||
|
||||
jmpSurpriseCheck(offset); // we do this early so iterators are still dirty if
|
||||
// we have an exception
|
||||
|
||||
FREE_ITER_LIST(iterTypeList, iterIdList, veclen);
|
||||
pc = savedPc + offset;
|
||||
}
|
||||
|
||||
#undef FREE_ITER_LIST
|
||||
|
||||
enum class SwitchMatch {
|
||||
NORMAL, // value was converted to an int: match normally
|
||||
NONZERO, // can't be converted to an int: match first nonzero case
|
||||
@@ -6170,12 +6209,26 @@ inline void OPTBLD_INLINE VMExecutionContext::iopWIterInitK(PC& pc) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline bool VMExecutionContext::initIteratorM(PC& pc, PC& origPc, Iter* it,
|
||||
Offset offset, Var* v1) {
|
||||
bool hasElems = it->minit(v1);
|
||||
Offset offset, Var* v1,
|
||||
TypedValue *val,
|
||||
TypedValue *key) {
|
||||
bool hasElems = false;
|
||||
TypedValue* rtv = v1->m_data.pref->tv();
|
||||
if (rtv->m_type == KindOfArray) {
|
||||
hasElems = new_miter_array_key(it, v1->m_data.pref, val, key);
|
||||
} else if (rtv->m_type == KindOfObject) {
|
||||
Class* ctx = arGetContextClass(g_vmContext->getFP());
|
||||
hasElems = new_miter_object(it, v1->m_data.pref, ctx, val, key);
|
||||
} else {
|
||||
hasElems = new_miter_other(it, v1->m_data.pref);
|
||||
}
|
||||
|
||||
if (!hasElems) {
|
||||
ITER_SKIP(offset);
|
||||
}
|
||||
|
||||
m_stack.popV();
|
||||
return hasElems;
|
||||
}
|
||||
@@ -6190,9 +6243,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopMIterInit(PC& pc) {
|
||||
assert(v1->m_type == KindOfRef);
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
TypedValue* tv1 = frame_local(m_fp, val);
|
||||
if (initIteratorM(pc, origPc, it, offset, v1)) {
|
||||
tvAsVariant(tv1).assignRef(it->marr().val());
|
||||
}
|
||||
initIteratorM(pc, origPc, it, offset, v1, tv1, nullptr);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopMIterInitK(PC& pc) {
|
||||
@@ -6207,10 +6258,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopMIterInitK(PC& pc) {
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
TypedValue* tv1 = frame_local(m_fp, val);
|
||||
TypedValue* tv2 = frame_local(m_fp, key);
|
||||
if (initIteratorM(pc, origPc, it, offset, v1)) {
|
||||
tvAsVariant(tv1).assignRef(it->marr().val());
|
||||
tvAsVariant(tv2) = it->marr().key();
|
||||
}
|
||||
initIteratorM(pc, origPc, it, offset, v1, tv1, tv2);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopIterNext(PC& pc) {
|
||||
@@ -6283,9 +6331,8 @@ inline void OPTBLD_INLINE VMExecutionContext::iopMIterNext(PC& pc) {
|
||||
DECODE_HA(val);
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
TypedValue* tv1 = frame_local(m_fp, val);
|
||||
if (it->mnext()) {
|
||||
if (miter_next_key(it, tv1, nullptr)) {
|
||||
ITER_SKIP(offset);
|
||||
tvAsVariant(tv1).assignRef(it->marr().val());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6299,10 +6346,8 @@ inline void OPTBLD_INLINE VMExecutionContext::iopMIterNextK(PC& pc) {
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
TypedValue* tv1 = frame_local(m_fp, val);
|
||||
TypedValue* tv2 = frame_local(m_fp, key);
|
||||
if (it->mnext()) {
|
||||
if (miter_next_key(it, tv1, tv2)) {
|
||||
ITER_SKIP(offset);
|
||||
tvAsVariant(tv1).assignRef(it->marr().val());
|
||||
tvAsVariant(tv2) = it->marr().key();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -146,6 +146,9 @@ int immSize(const Op* opcode, int idx) {
|
||||
} else if (itype == BLA) {
|
||||
prefixes = 1;
|
||||
vecElemSz = sizeof(Offset);
|
||||
} else if (itype == ILA) {
|
||||
prefixes = 1;
|
||||
vecElemSz = 2 * sizeof(uint32_t);
|
||||
} else {
|
||||
assert(itype == SLA);
|
||||
prefixes = 1;
|
||||
@@ -161,7 +164,7 @@ int immSize(const Op* opcode, int idx) {
|
||||
|
||||
bool immIsVector(Op opcode, int idx) {
|
||||
ArgType type = immType(opcode, idx);
|
||||
return (type == MA || type == BLA || type == SLA);
|
||||
return (type == MA || type == BLA || type == SLA || type == ILA);
|
||||
}
|
||||
|
||||
bool hasImmVector(Op opcode) {
|
||||
@@ -303,6 +306,14 @@ Offset* instrJumpOffset(Op* instr) {
|
||||
};
|
||||
|
||||
assert(!isSwitch(*instr));
|
||||
|
||||
if (Op(*instr) == OpIterBreak) {
|
||||
uint32_t veclen = *(uint32_t *)(instr + 1);
|
||||
assert(veclen > 0);
|
||||
Offset* target = (Offset *)((uint32_t *)(instr + 1) + 2 * veclen + 1);
|
||||
return target;
|
||||
}
|
||||
|
||||
int mask = jumpMask[uint8_t(*instr)];
|
||||
if (mask == 0) {
|
||||
return nullptr;
|
||||
@@ -340,7 +351,7 @@ int numSuccs(const Op* instr) {
|
||||
if (isSwitch(*instr)) {
|
||||
return *(int*)(instr + 1);
|
||||
}
|
||||
if (*instr == OpJmp) return 1;
|
||||
if (*instr == OpJmp || *instr == OpIterBreak) return 1;
|
||||
return 0;
|
||||
}
|
||||
if (instrJumpOffset(const_cast<Op*>(instr))) return 2;
|
||||
@@ -817,6 +828,24 @@ std::string instrToString(const Op* it, const Unit* u /* = NULL */) {
|
||||
out << ">"; \
|
||||
} while (false)
|
||||
|
||||
#define READIVEC() do { \
|
||||
int sz = readData<int>(it); \
|
||||
out << " <"; \
|
||||
const char* sep = ""; \
|
||||
for (int i = 0; i < sz; ++i) { \
|
||||
out << sep; \
|
||||
IterKind k = (IterKind)readData<Id>(it); \
|
||||
switch(k) { \
|
||||
case KindOfIter: out << "(Iter) "; break; \
|
||||
case KindOfMIter: out << "(MIter) "; break; \
|
||||
case KindOfCIter: out << "(CIter) "; break; \
|
||||
} \
|
||||
out << readData<Id>(it); \
|
||||
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;
|
||||
@@ -825,6 +854,7 @@ std::string instrToString(const Op* it, const Unit* u /* = NULL */) {
|
||||
#define H_MA READVEC()
|
||||
#define H_BLA READSVEC()
|
||||
#define H_SLA READSVEC()
|
||||
#define H_ILA READIVEC()
|
||||
#define H_IVA READIVA()
|
||||
#define H_I64A READ(int64_t)
|
||||
#define H_HA READV()
|
||||
@@ -859,6 +889,7 @@ OPCODES
|
||||
#undef H_MA
|
||||
#undef H_BLA
|
||||
#undef H_SLA
|
||||
#undef H_ILA
|
||||
#undef H_IVA
|
||||
#undef H_I64A
|
||||
#undef H_HA
|
||||
@@ -926,7 +957,7 @@ ImmVector getImmVector(const Op* opcode) {
|
||||
void* vp = getImmPtr(opcode, k);
|
||||
return ImmVector::createFromStream(
|
||||
static_cast<const uint8_t*>(vp));
|
||||
} else if (t == BLA || t == SLA) {
|
||||
} else if (t == BLA || t == SLA || t == ILA) {
|
||||
void* vp = getImmPtr(opcode, k);
|
||||
return ImmVector::createFromStream(
|
||||
static_cast<const int32_t*>(vp));
|
||||
|
||||
+21
-13
@@ -32,20 +32,21 @@ struct Unit;
|
||||
// The types in this macro for MA, BLA, and SLA are meaningless since
|
||||
// they are never read out of ArgUnion (they use ImmVector and
|
||||
// ImmVectorO).
|
||||
#define ARGTYPES \
|
||||
ARGTYPE(NA, void*) /* unused */ \
|
||||
ARGTYPEVEC(MA, int32_t) /* Member vector immediate */ \
|
||||
ARGTYPEVEC(BLA,Offset) /* Bytecode address vector immediate */ \
|
||||
ARGTYPEVEC(SLA,Id) /* litstrid/offset pair vector */ \
|
||||
ARGTYPE(IVA, int32_t) /* variable size: 8 or 32-bit integer */ \
|
||||
ARGTYPE(I64A, int64_t) /* 64-bit Integer */ \
|
||||
ARGTYPE(HA, int32_t) /* Local variable ID: 8 or 32-bit int */ \
|
||||
/* TODO(jdelong): rename HA to LA */ \
|
||||
#define ARGTYPES \
|
||||
ARGTYPE(NA, void*) /* unused */ \
|
||||
ARGTYPEVEC(MA, int32_t) /* Member vector immediate */ \
|
||||
ARGTYPEVEC(BLA,Offset) /* Bytecode address vector immediate */ \
|
||||
ARGTYPEVEC(SLA,Id) /* litstrid/offset pair vector */ \
|
||||
ARGTYPEVEC(ILA,Id) /* IterKind/IterId pair vector */ \
|
||||
ARGTYPE(IVA, int32_t) /* variable size: 8 or 32-bit integer */ \
|
||||
ARGTYPE(I64A, int64_t) /* 64-bit Integer */ \
|
||||
ARGTYPE(HA, int32_t) /* Local variable ID: 8 or 32-bit int */ \
|
||||
/* TODO(jdelong): rename HA to LA */ \
|
||||
ARGTYPE(IA, int32_t) /* Iterator variable ID: 8 or 32-bit int */ \
|
||||
ARGTYPE(DA, double) /* Double */ \
|
||||
ARGTYPE(SA, Id) /* litStr ID */ \
|
||||
ARGTYPE(AA, Id) /* static array ID */ \
|
||||
ARGTYPE(BA, Offset) /* Bytecode address */ \
|
||||
ARGTYPE(DA, double) /* Double */ \
|
||||
ARGTYPE(SA, Id) /* litStr ID */ \
|
||||
ARGTYPE(AA, Id) /* static array ID */ \
|
||||
ARGTYPE(BA, Offset) /* Bytecode address */ \
|
||||
ARGTYPE(OA, unsigned char) /* Opcode */
|
||||
|
||||
enum ArgType {
|
||||
@@ -305,6 +306,12 @@ enum IncDecOp {
|
||||
IncDec_invalid
|
||||
};
|
||||
|
||||
enum IterKind {
|
||||
KindOfIter = 0,
|
||||
KindOfMIter = 1,
|
||||
KindOfCIter = 2,
|
||||
};
|
||||
|
||||
// Each of the setop ops maps to a binary bytecode op. We have reasons
|
||||
// for using distinct bitwise representations, though. This macro records
|
||||
// their correspondence for mapping either direction.
|
||||
@@ -520,6 +527,7 @@ enum SetOpOp {
|
||||
O(IterFree, ONE(IA), NOV, NOV, NF) \
|
||||
O(MIterFree, ONE(IA), NOV, NOV, NF) \
|
||||
O(CIterFree, ONE(IA), NOV, NOV, NF) \
|
||||
O(IterBreak, TWO(ILA,BA), NOV, NOV, CF_TF) \
|
||||
O(Incl, NA, ONE(CV), ONE(CV), CF) \
|
||||
O(InclOnce, NA, ONE(CV), ONE(CV), CF) \
|
||||
O(Req, NA, ONE(CV), ONE(CV), CF) \
|
||||
|
||||
@@ -5332,6 +5332,53 @@ void CodeGenerator::cgIterInitCommon(IRInstruction* inst) {
|
||||
}
|
||||
}
|
||||
|
||||
void CodeGenerator::cgMIterInit(IRInstruction* inst) {
|
||||
cgMIterInitCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgMIterInitK(IRInstruction* inst) {
|
||||
cgMIterInitCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgMIterInitCommon(IRInstruction* inst) {
|
||||
PhysReg fpReg = m_regs[inst->src(1)].reg();
|
||||
int64_t iterOffset = this->iterOffset(inst->src(2));
|
||||
int64_t valLocalOffset = localOffset(inst->src(3));
|
||||
SSATmp* src = inst->src(0);
|
||||
|
||||
ArgGroup args(m_regs);
|
||||
args.addr(fpReg, iterOffset).ssa(src);
|
||||
|
||||
assert(src->type().isBoxed());
|
||||
|
||||
if (src->type().innerType().isArray()) {
|
||||
args.addr(fpReg, valLocalOffset);
|
||||
if (inst->op() == MIterInitK) {
|
||||
args.addr(fpReg, localOffset(inst->src(4)));
|
||||
} else {
|
||||
args.imm(0);
|
||||
}
|
||||
cgCallHelper(m_as, (TCA)new_miter_array_key, inst->dst(),
|
||||
SyncOptions::kSyncPoint, args);
|
||||
} else if (src->type() == Type::BoxedObj) {
|
||||
args.immPtr(curClass()).addr(fpReg, valLocalOffset);
|
||||
if (inst->op() == MIterInitK) {
|
||||
args.addr(fpReg, localOffset(inst->src(4)));
|
||||
} else {
|
||||
args.imm(0);
|
||||
}
|
||||
// new_miter_object decrefs its src object if it propagates an
|
||||
// exception out, so we use kSyncPointAdjustOne, which adjusts the
|
||||
// stack pointer by 1 stack element on an unwind, skipping over
|
||||
// the src object.
|
||||
cgCallHelper(m_as, (TCA)new_miter_object, inst->dst(),
|
||||
SyncOptions::kSyncPointAdjustOne, args);
|
||||
} else {
|
||||
cgCallHelper(m_as, (TCA)new_miter_other, inst->dst(),
|
||||
SyncOptions::kSyncPoint, args);
|
||||
}
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterNext(IRInstruction* inst) {
|
||||
cgIterNextCommon(inst);
|
||||
}
|
||||
@@ -5365,10 +5412,36 @@ void CodeGenerator::cgIterNextCommon(IRInstruction* inst) {
|
||||
cgCallHelper(m_as, helperAddr, inst->dst(), SyncOptions::kSyncPoint, args);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgMIterNext(IRInstruction* inst) {
|
||||
cgMIterNextCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgMIterNextK(IRInstruction* inst) {
|
||||
cgMIterNextCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgMIterNextCommon(IRInstruction* inst) {
|
||||
PhysReg fpReg = m_regs[inst->src(0)].reg();
|
||||
ArgGroup args(m_regs);
|
||||
args.addr(fpReg, iterOffset(inst->src(1)))
|
||||
.addr(fpReg, localOffset(inst->src(2)));
|
||||
if (inst->op() == MIterNextK) {
|
||||
args.addr(fpReg, localOffset(inst->src(3)));
|
||||
} else {
|
||||
args.imm(0);
|
||||
}
|
||||
cgCallHelper(m_as, (TCA)miter_next_key, inst->dst(),
|
||||
SyncOptions::kSyncPoint, args);
|
||||
}
|
||||
|
||||
void iterFreeHelper(Iter* iter) {
|
||||
iter->free();
|
||||
}
|
||||
|
||||
void miterFreeHelper(Iter* iter) {
|
||||
iter->mfree();
|
||||
}
|
||||
|
||||
void citerFreeHelper(Iter* iter) {
|
||||
iter->cfree();
|
||||
}
|
||||
@@ -5380,6 +5453,13 @@ void CodeGenerator::cgIterFree(IRInstruction* inst) {
|
||||
ArgGroup(m_regs).addr(fpReg, offset));
|
||||
}
|
||||
|
||||
void CodeGenerator::cgMIterFree(IRInstruction* inst) {
|
||||
PhysReg fpReg = m_regs[inst->src(0)].reg();
|
||||
int64_t offset = iterOffset(inst->extra<MIterFree>()->iterId);
|
||||
cgCallHelper(m_as, (TCA)miterFreeHelper, InvalidReg, SyncOptions::kSyncPoint,
|
||||
ArgGroup(m_regs).addr(fpReg, offset));
|
||||
}
|
||||
|
||||
void CodeGenerator::cgDecodeCufIter(IRInstruction* inst) {
|
||||
PhysReg fpReg = m_regs[inst->src(1)].reg();
|
||||
int64_t offset = iterOffset(inst->extra<DecodeCufIter>()->iterId);
|
||||
|
||||
@@ -306,6 +306,8 @@ private:
|
||||
|
||||
void cgIterNextCommon(IRInstruction* inst);
|
||||
void cgIterInitCommon(IRInstruction* inst);
|
||||
void cgMIterNextCommon(IRInstruction* inst);
|
||||
void cgMIterInitCommon(IRInstruction* inst);
|
||||
void cgLdFuncCachedCommon(IRInstruction* inst);
|
||||
TargetCache::CacheHandle cgLdClsCachedCommon(IRInstruction* inst);
|
||||
void emitFwdJcc(ConditionCode cc, Block* target);
|
||||
|
||||
@@ -363,6 +363,7 @@ X(LdLoc, LocalId);
|
||||
X(StLoc, LocalId);
|
||||
X(StLocNT, LocalId);
|
||||
X(IterFree, IterId);
|
||||
X(MIterFree, IterId);
|
||||
X(CIterFree, IterId);
|
||||
X(DecodeCufIter, IterId);
|
||||
X(CufIterSpillFrame, FPushCufData);
|
||||
|
||||
@@ -856,10 +856,25 @@ SSATmp* HhbcTranslator::emitIterInitCommon(int offset, Lambda genFunc) {
|
||||
return emitJmpCondHelper(offset, true, res);
|
||||
}
|
||||
|
||||
template<class Lambda>
|
||||
SSATmp* HhbcTranslator::emitMIterInitCommon(int offset, Lambda genFunc) {
|
||||
auto trace = getExitTrace();
|
||||
|
||||
SSATmp* src = topV();
|
||||
Type type = src->type();
|
||||
|
||||
assert(type.isBoxed());
|
||||
gen(LdRef, type.innerType(), trace, src);
|
||||
SSATmp* res = genFunc(src);
|
||||
SSATmp* out = popV();
|
||||
gen(DecRef, out);
|
||||
return emitJmpCondHelper(offset, true, res);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitIterInit(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId) {
|
||||
emitIterInitCommon(offset, [=] (SSATmp* src) {
|
||||
emitIterInitCommon(offset, [&] (SSATmp* src) {
|
||||
return gen(
|
||||
IterInit,
|
||||
Type::Bool,
|
||||
@@ -875,7 +890,7 @@ void HhbcTranslator::emitIterInitK(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId) {
|
||||
emitIterInitCommon(offset, [=] (SSATmp* src) {
|
||||
emitIterInitCommon(offset, [&] (SSATmp* src) {
|
||||
return gen(
|
||||
IterInitK,
|
||||
Type::Bool,
|
||||
@@ -920,7 +935,7 @@ void HhbcTranslator::emitWIterInit(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId) {
|
||||
emitIterInitCommon(
|
||||
offset, [=] (SSATmp* src) {
|
||||
offset, [&] (SSATmp* src) {
|
||||
return gen(
|
||||
WIterInit,
|
||||
Type::Bool,
|
||||
@@ -938,7 +953,7 @@ void HhbcTranslator::emitWIterInitK(uint32_t iterId,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId) {
|
||||
emitIterInitCommon(
|
||||
offset, [=] (SSATmp* src) {
|
||||
offset, [&] (SSATmp* src) {
|
||||
return gen(
|
||||
WIterInitK,
|
||||
Type::Bool,
|
||||
@@ -980,10 +995,74 @@ void HhbcTranslator::emitWIterNextK(uint32_t iterId,
|
||||
emitJmpCondHelper(offset, false, res);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitMIterInit(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId) {
|
||||
emitMIterInitCommon(offset, [&] (SSATmp* src) {
|
||||
return gen(
|
||||
MIterInit,
|
||||
Type::Bool,
|
||||
src,
|
||||
m_tb->fp(),
|
||||
cns(iterId),
|
||||
cns(valLocalId)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitMIterInitK(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId) {
|
||||
emitMIterInitCommon(offset, [&] (SSATmp* src) {
|
||||
return gen(
|
||||
MIterInitK,
|
||||
Type::Bool,
|
||||
src,
|
||||
m_tb->fp(),
|
||||
cns(iterId),
|
||||
cns(valLocalId),
|
||||
cns(keyLocalId)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitMIterNext(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId) {
|
||||
SSATmp* res = gen(
|
||||
MIterNext,
|
||||
Type::Bool,
|
||||
m_tb->fp(),
|
||||
cns(iterId),
|
||||
cns(valLocalId)
|
||||
);
|
||||
emitJmpCondHelper(offset, false, res);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitMIterNextK(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId) {
|
||||
SSATmp* res = gen(
|
||||
MIterNextK,
|
||||
Type::Bool,
|
||||
m_tb->fp(),
|
||||
cns(iterId),
|
||||
cns(valLocalId),
|
||||
cns(keyLocalId)
|
||||
);
|
||||
emitJmpCondHelper(offset, false, res);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitIterFree(uint32_t iterId) {
|
||||
gen(IterFree, IterId(iterId), m_tb->fp());
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitMIterFree(uint32_t iterId) {
|
||||
gen(MIterFree, IterId(iterId), m_tb->fp());
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitDecodeCufIter(uint32_t iterId, int offset) {
|
||||
SSATmp* src = popC();
|
||||
Type type = src->type();
|
||||
@@ -1002,6 +1081,30 @@ void HhbcTranslator::emitCIterFree(uint32_t iterId) {
|
||||
gen(CIterFree, IterId(iterId), m_tb->fp());
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitIterBreak(const ImmVector& iv,
|
||||
uint32_t offset,
|
||||
bool breakTracelet,
|
||||
bool noSurprise) {
|
||||
bool backward = (offset - (int32_t)bcOff()) < 0;
|
||||
if (backward && !noSurprise) {
|
||||
gen(ExitWhenSurprised, getExitSlowTrace());
|
||||
}
|
||||
|
||||
int iterIndex;
|
||||
for (iterIndex = 0; iterIndex < iv.size(); iterIndex += 2) {
|
||||
IterKind iterKind = (IterKind)iv.vec32()[iterIndex];
|
||||
Id iterId = iv.vec32()[iterIndex + 1];
|
||||
switch (iterKind) {
|
||||
case KindOfIter: gen(IterFree, IterId(iterId), m_tb->fp()); break;
|
||||
case KindOfMIter: gen(MIterFree, IterId(iterId), m_tb->fp()); break;
|
||||
case KindOfCIter: gen(CIterFree, IterId(iterId), m_tb->fp()); break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!breakTracelet) return;
|
||||
gen(Jmp_, getExitTrace(offset));
|
||||
}
|
||||
|
||||
typedef std::map<int, int> ContParamMap;
|
||||
/*
|
||||
* mapContParams builds a mapping between named locals in origFunc and
|
||||
|
||||
@@ -356,6 +356,16 @@ struct HhbcTranslator {
|
||||
int targetOffset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId);
|
||||
void emitMIterInit(uint32_t iterId, int targetOffset, uint32_t valLocalId);
|
||||
void emitMIterInitK(uint32_t iterId,
|
||||
int targetOffset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId);
|
||||
void emitMIterNext(uint32_t iterId, int targetOffset, uint32_t valLocalId);
|
||||
void emitMIterNextK(uint32_t iterId,
|
||||
int targetOffset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId);
|
||||
void emitWIterInit(uint32_t iterId, int targetOffset, uint32_t valLocalId);
|
||||
void emitWIterInitK(uint32_t iterId,
|
||||
int targetOffset,
|
||||
@@ -368,8 +378,11 @@ struct HhbcTranslator {
|
||||
uint32_t keyLocalId);
|
||||
|
||||
void emitIterFree(uint32_t iterId);
|
||||
void emitMIterFree(uint32_t iterId);
|
||||
void emitDecodeCufIter(uint32_t iterId, int targetOffset);
|
||||
void emitCIterFree(uint32_t iterId);
|
||||
void emitIterBreak(const ImmVector& iv, uint32_t offset, bool breakTracelet,
|
||||
bool noSurprise);
|
||||
void emitVerifyParamType(uint32_t paramId);
|
||||
|
||||
// continuations
|
||||
@@ -662,6 +675,8 @@ private:
|
||||
SSATmp* emitIterInitCommon(int offset, Lambda genFunc);
|
||||
BCMarker makeMarker(Offset bcOff);
|
||||
void updateMarker();
|
||||
template<class Lambda>
|
||||
SSATmp* emitMIterInitCommon(int offset, Lambda genFunc);
|
||||
SSATmp* staticTVCns(const TypedValue*);
|
||||
|
||||
Type interpOutputType(const NormalizedInstruction&) const;
|
||||
|
||||
@@ -508,7 +508,21 @@ 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(MIterInit, D(Bool), S(BoxedCell) \
|
||||
S(FramePtr) \
|
||||
C(Int) \
|
||||
C(Int), E|N|Mem|Refs|CRc) \
|
||||
O(MIterInitK, D(Bool), S(BoxedCell) \
|
||||
S(FramePtr) \
|
||||
C(Int) \
|
||||
C(Int) \
|
||||
C(Int), E|N|Mem|Refs|CRc) \
|
||||
O(MIterNext, D(Bool), S(FramePtr) \
|
||||
C(Int) C(Int), E|N|Mem|Refs) \
|
||||
O(MIterNextK, D(Bool), S(FramePtr) \
|
||||
C(Int) C(Int) C(Int), E|N|Mem|Refs) \
|
||||
O(IterFree, ND, S(FramePtr), E|N|Mem|Refs) \
|
||||
O(MIterFree, ND, S(FramePtr), E|N|Mem|Refs) \
|
||||
O(DecodeCufIter, D(Bool), S(Arr,Obj,Str) \
|
||||
S(FramePtr), E|N|Mem|Refs) \
|
||||
O(CIterFree, ND, S(FramePtr), E|N|Mem|Refs) \
|
||||
|
||||
@@ -1361,6 +1361,42 @@ Translator::translateIterNextK(const NormalizedInstruction& i) {
|
||||
i.imm[3].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
Translator::translateMIterInit(const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(MIterInit,
|
||||
i.imm[0].u_IVA,
|
||||
i.offset() + i.imm[1].u_BA,
|
||||
i.imm[2].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
Translator::translateMIterInitK(const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(MIterInitK,
|
||||
i.imm[0].u_IVA,
|
||||
i.offset() + i.imm[1].u_BA,
|
||||
i.imm[2].u_IVA,
|
||||
i.imm[3].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
Translator::translateMIterNext(const NormalizedInstruction& i) {
|
||||
|
||||
HHIR_EMIT(MIterNext,
|
||||
i.imm[0].u_IVA,
|
||||
i.offset() + i.imm[1].u_BA,
|
||||
i.imm[2].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
Translator::translateMIterNextK(const NormalizedInstruction& i) {
|
||||
|
||||
HHIR_EMIT(MIterNextK,
|
||||
i.imm[0].u_IVA,
|
||||
i.offset() + i.imm[1].u_BA,
|
||||
i.imm[2].u_IVA,
|
||||
i.imm[3].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
Translator::translateWIterInit(const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(WIterInit,
|
||||
@@ -1403,6 +1439,20 @@ Translator::translateIterFree(const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(IterFree, i.imm[0].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
Translator::translateMIterFree(const NormalizedInstruction& i) {
|
||||
|
||||
HHIR_EMIT(MIterFree, i.imm[0].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
Translator::translateIterBreak(const NormalizedInstruction& i) {
|
||||
|
||||
assert(i.breaksTracelet);
|
||||
HHIR_EMIT(IterBreak, i.immVec, i.offset() + i.imm[1].u_BA, i.breaksTracelet,
|
||||
i.noSurprise);
|
||||
}
|
||||
|
||||
void
|
||||
Translator::translateDecodeCufIter(const NormalizedInstruction& i) {
|
||||
|
||||
|
||||
@@ -117,6 +117,10 @@
|
||||
CASE(WIterInitK) \
|
||||
CASE(WIterNext) \
|
||||
CASE(WIterNextK) \
|
||||
CASE(MIterInit) \
|
||||
CASE(MIterInitK) \
|
||||
CASE(MIterNext) \
|
||||
CASE(MIterNextK) \
|
||||
CASE(ReqDoc) \
|
||||
CASE(DefCls) \
|
||||
CASE(DefFunc) \
|
||||
@@ -155,6 +159,8 @@
|
||||
CASE(BindS) \
|
||||
CASE(BindG) \
|
||||
CASE(IterFree) \
|
||||
CASE(MIterFree) \
|
||||
CASE(IterBreak) \
|
||||
CASE(FPassV) \
|
||||
CASE(UnsetN) \
|
||||
CASE(DecodeCufIter) \
|
||||
|
||||
@@ -1220,6 +1220,7 @@ static const struct {
|
||||
{ OpIterFree, {None, None, OutNone, 0 }},
|
||||
{ OpMIterFree, {None, None, OutNone, 0 }},
|
||||
{ OpCIterFree, {None, None, OutNone, 0 }},
|
||||
{ OpIterBreak, {None, None, OutNone, 0 }},
|
||||
|
||||
/*** 12. Include, eval, and define instructions ***/
|
||||
|
||||
|
||||
@@ -1004,6 +1004,7 @@ opcodeControlFlowInfo(const Op instr) {
|
||||
case OpWIterInit: // Ditto
|
||||
case OpWIterInitK: // Ditto
|
||||
case OpDecodeCufIter: // Ditto
|
||||
case OpIterBreak:
|
||||
case OpThrow:
|
||||
case OpUnwind:
|
||||
case OpEval:
|
||||
|
||||
@@ -469,11 +469,12 @@ bool FuncChecker::checkImmediates(const char* name, const Op* instr) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ILA:
|
||||
case BLA:
|
||||
case SLA: { // vec of offsets for Switch/SSwitch
|
||||
case SLA: { // vec of offsets for Switch/SSwitch/IterBreak
|
||||
int len = *(int*)pc;
|
||||
if (len < 1) {
|
||||
error("invalid length of jump table %d at Offset %d\n",
|
||||
error("invalid length of immediate vector %d at Offset %d\n",
|
||||
len, offset(pc));
|
||||
return false;
|
||||
}
|
||||
@@ -917,9 +918,7 @@ bool FuncChecker::checkFlow() {
|
||||
ok &= checkInputs(&cur, pc, b);
|
||||
if (isTF(pc)) ok &= checkTerminal(&cur, pc);
|
||||
if (isFF(pc)) ok &= checkFpi(&cur, pc, b);
|
||||
// TODO(#1097182) Iterator checking is disabled, because
|
||||
// systemlib currently doesn't pass it.
|
||||
/* if (isIter(pc)) ok &= checkIter(&cur, pc); */
|
||||
if (isIter(pc)) ok &= checkIter(&cur, pc);
|
||||
ok &= checkOutputs(&cur, pc, b);
|
||||
}
|
||||
ok &= checkSuccEdges(b, &cur);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
|
||||
.main {
|
||||
FPushFuncD 0 "main"
|
||||
FCall 0
|
||||
PopR
|
||||
Int 1
|
||||
RetC
|
||||
}
|
||||
|
||||
.function main() {
|
||||
.numiters 2;
|
||||
NewArray
|
||||
|
||||
String "hello"
|
||||
String "world"
|
||||
AddElemC
|
||||
|
||||
String "this"
|
||||
String "is"
|
||||
AddElemC
|
||||
|
||||
String "a"
|
||||
String "test"
|
||||
AddElemC
|
||||
|
||||
SetL $0
|
||||
PopC
|
||||
|
||||
CGetL $0
|
||||
IterInitK 0 endOuter $1 $2
|
||||
startOuter:
|
||||
|
||||
String "\n"
|
||||
CGetL2 $1
|
||||
Concat
|
||||
Print
|
||||
PopC
|
||||
|
||||
String "\n"
|
||||
CGetL2 $2
|
||||
Concat
|
||||
Print
|
||||
PopC
|
||||
|
||||
CGetL $0
|
||||
IterInitK 1 endInner $3 $4
|
||||
startInner:
|
||||
String "\n"
|
||||
CGetL2 $3
|
||||
Concat
|
||||
Print
|
||||
PopC
|
||||
|
||||
String "\n"
|
||||
CGetL2 $4
|
||||
Concat
|
||||
Print
|
||||
PopC
|
||||
|
||||
IterBreak <(Iter) 1, (Iter) 0> endOuter
|
||||
IterNextK 1 startInner $3 $4
|
||||
endInner:
|
||||
IterNextK 0 startOuter $1 $2
|
||||
endOuter:
|
||||
|
||||
Int 1
|
||||
RetC
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
world
|
||||
hello
|
||||
world
|
||||
hello
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
function main() {
|
||||
$arr = null;
|
||||
print "start iter loop\n";
|
||||
foreach ($arr as $x => $y) {
|
||||
print "fail";
|
||||
}
|
||||
print "end iter loop\n";
|
||||
$ref = &$arr;
|
||||
print "start witer loop\n";
|
||||
foreach ($arr as $x => $y) {
|
||||
print "fail";
|
||||
}
|
||||
print "end witer loop\n";
|
||||
print "start miter loop\n";
|
||||
foreach ($arr as $x => &$y) {
|
||||
print "fail";
|
||||
}
|
||||
print "end of miter loop\n";
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
start iter loop
|
||||
HipHop Warning: Invalid argument supplied for foreach() in %s on line 8
|
||||
end iter loop
|
||||
start witer loop
|
||||
HipHop Warning: Invalid argument supplied for foreach() in %s on line 14
|
||||
end witer loop
|
||||
start miter loop
|
||||
end of miter loop
|
||||
Referência em uma Nova Issue
Bloquear um usuário