Add bytecodes for some "withRef" operations
Functions like array_filter and array_map need "withRef" semantics,
and while it can be simulated in php via copy-on-write, doing so
is very inefficient.
This adds
SetWithRefLM
SetWithRefRM
- similar to SetM but binds the value if it was a reference. L reads a local, R reads a return value
WIterInit
WIterInitK
WIterNext
WIterNextK
- essentially the same as the corresponding opcodes without W, but the value local is set by reference if the array element was a reference.
Esse commit está contido em:
@@ -283,6 +283,7 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#define COUNT_FOUR(t1,t2,t3,t4) 4
|
||||
#define COUNT_LMANY() 0
|
||||
#define COUNT_C_LMANY() 0
|
||||
#define COUNT_R_LMANY() 0
|
||||
#define COUNT_V_LMANY() 0
|
||||
#define COUNT_FMANY 0
|
||||
#define COUNT_CMANY 0
|
||||
@@ -332,6 +333,9 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#define POP_V_LMANY() \
|
||||
getEmitterVisitor().popEvalStack(StackSym::V); \
|
||||
getEmitterVisitor().popEvalStackLMany()
|
||||
#define POP_R_LMANY() \
|
||||
getEmitterVisitor().popEvalStack(StackSym::R); \
|
||||
getEmitterVisitor().popEvalStackLMany()
|
||||
#define POP_FMANY \
|
||||
getEmitterVisitor().popEvalStackMany(a1, StackSym::F)
|
||||
#define POP_CMANY \
|
||||
@@ -549,6 +553,7 @@ static int32_t countStackValues(const std::vector<uchar>& immVec) {
|
||||
#undef POP_LMANY
|
||||
#undef POP_C_LMANY
|
||||
#undef POP_V_LMANY
|
||||
#undef POP_R_LMANY
|
||||
#undef POP_CV
|
||||
#undef POP_VV
|
||||
#undef POP_HV
|
||||
@@ -7231,9 +7236,13 @@ static Unit* emitHHBCNativeClassUnit(const HhbcExtClassInfo* builtinClasses,
|
||||
const ClassInfo::PropertyInfo* propInfo = propVec[i];
|
||||
assert(propInfo);
|
||||
int attr = AttrNone;
|
||||
if (propInfo->attribute & ClassInfo::IsProtected) attr |= AttrProtected;
|
||||
else if (propInfo->attribute & ClassInfo::IsPrivate) attr |= AttrPrivate;
|
||||
else attr |= AttrPublic;
|
||||
if (propInfo->attribute & ClassInfo::IsProtected) {
|
||||
attr |= AttrProtected;
|
||||
} else if (propInfo->attribute & ClassInfo::IsPrivate) {
|
||||
attr |= AttrPrivate;
|
||||
} else {
|
||||
attr |= AttrPublic;
|
||||
}
|
||||
if (propInfo->attribute & ClassInfo::IsStatic) attr |= AttrStatic;
|
||||
|
||||
TypedValue tvNull;
|
||||
@@ -7242,7 +7251,8 @@ static Unit* emitHHBCNativeClassUnit(const HhbcExtClassInfo* builtinClasses,
|
||||
propInfo->name.get(),
|
||||
Attr(attr),
|
||||
nullptr,
|
||||
propInfo->docComment ? StringData::GetStaticString(propInfo->docComment) : nullptr,
|
||||
propInfo->docComment ?
|
||||
StringData::GetStaticString(propInfo->docComment) : nullptr,
|
||||
&tvNull,
|
||||
KindOfInvalid
|
||||
);
|
||||
|
||||
@@ -3150,6 +3150,40 @@ SetM <loc-desc/M-vector> [C..C C] -> [C]
|
||||
SC | BaseSC
|
||||
SL | BaseSL
|
||||
|
||||
SetWithRefLM <loc-desc/M-vector> <local variable id> [C..C] -> []
|
||||
|
||||
Set member preserving the reffiness of the local.
|
||||
|
||||
location Base* member intermediate final
|
||||
descriptor operation code operation operation
|
||||
-----------+---------- -------+--------------+-----------
|
||||
C | BaseC EC | ElemCD | SetWithRefElemC
|
||||
R | BaseR PC | PropCD | SetPropC
|
||||
L | BaseLD EL | ElemLD | SetWithRefElemL
|
||||
NC | BaseNCD PL | PropLD | SetPropL
|
||||
NL | BaseNLD W | NewElem | SetWithRefNewElem
|
||||
GC | BaseGCD
|
||||
GL | BaseGLD
|
||||
SC | BaseSC
|
||||
SL | BaseSL
|
||||
|
||||
SetWithRefRM <loc-desc/M-vector> [C..C R] -> []
|
||||
|
||||
Set member preserving the reffiness of the stack element.
|
||||
|
||||
location Base* member intermediate final
|
||||
descriptor operation code operation operation
|
||||
-----------+---------- -------+--------------+-----------
|
||||
C | BaseC EC | ElemCD | SetWithRefElemC
|
||||
R | BaseR PC | PropCD | SetPropC
|
||||
L | BaseLD EL | ElemLD | SetWithRefElemL
|
||||
NC | BaseNCD PL | PropLD | SetPropL
|
||||
NL | BaseNLD W | NewElem | SetWithRefNewElem
|
||||
GC | BaseGCD
|
||||
GL | BaseGLD
|
||||
SC | BaseSC
|
||||
SL | BaseSL
|
||||
|
||||
SetOpM <op> <loc-desc/M-vector> [C..C C] -> [C]
|
||||
|
||||
Set op member.
|
||||
@@ -3224,6 +3258,8 @@ UnsetM <loc-desc/M-vector> [C..C] -> []
|
||||
|
||||
IterInit <iterator id> <rel offset> <local id> [C] -> []
|
||||
IterInitK <iterator id> <rel offset> <local id> <local id> [C] -> []
|
||||
WIterInit <iterator id> <rel offset> <local id> [C] -> []
|
||||
WIterInitK <iterator id> <rel offset> <local id> <local id> [C] -> []
|
||||
|
||||
Initialize iterator. If $1 is an array, these instructions create an array
|
||||
iterator, rewind the array iterator to point to the beginning of the array,
|
||||
@@ -3264,8 +3300,9 @@ IterInitK <iterator id> <rel offset> <local id> <local id> [C] -> []
|
||||
transfer control to the location specified by %2.
|
||||
|
||||
If the iterator specified by %1 is a non-mutable array iterator or an
|
||||
extension class iterator, these instructions store a copy of the current
|
||||
value in %3 as a cell.
|
||||
extension class iterator, IterInit and IterInitK store a copy of the current
|
||||
value in %3 as a cell; WIterInit and WIterInitK will store a reference
|
||||
if the current element is a reference, or a value otherwise in %3
|
||||
|
||||
If the iterator specified by %1 is a user class iterator for object $x, these
|
||||
instructions store the return value of $x->current() in %3 as a cell.
|
||||
@@ -3331,6 +3368,8 @@ MIterInitK <iterator id> <rel offset> <local id> <local id> [V] -> []
|
||||
|
||||
IterNext <iterator id> <rel offset> <local id> [] -> []
|
||||
IterNextK <iterator id> <rel offset> <local id> <local id> [] -> []
|
||||
WIterNext <iterator id> <rel offset> <local id> [] -> []
|
||||
WIterNextK <iterator id> <rel offset> <local id> <local id> [] -> []
|
||||
|
||||
Iterator next. If the specified iterator is a non-mutable array iterator
|
||||
or an extension class iterator, advance the iterator to point to the next
|
||||
@@ -3353,8 +3392,10 @@ IterNextK <iterator id> <rel offset> <local id> <local id> [] -> []
|
||||
If the specified iterator is not at the end, retrieve the key and value:
|
||||
|
||||
If the iterator specified by %1 is a non-mutable array iterator or an
|
||||
extension class iterator, these instructions store a copy of the new current
|
||||
value in %3 as a cell.
|
||||
extension class iterator, IterNext and IterNextK store a copy of the new current
|
||||
value in %3 as a cell; WIterNext and WIterNextK store a reference to the
|
||||
new current value in %3 if the current value is a reference, otherwise
|
||||
they store the new current value in %3 as a cell.
|
||||
|
||||
If the iterator specified by %1 is a user class iterator for object $x, these
|
||||
instructions store the return value of $x->current() in %3 as a cell.
|
||||
|
||||
@@ -1549,6 +1549,8 @@ DbgAssertType<T> S0:Cell
|
||||
|
||||
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
|
||||
|
||||
Initializes the iterator variable whose index is given by S2. This
|
||||
instruction creates the appropriate iterator for the array or object
|
||||
@@ -1563,12 +1565,20 @@ D:Bool = IterInitK S0:{Arr|Obj} S1:FramePtr S2:ConstInt S3:ConstInt S4:ConstInt
|
||||
instruction stores the iterator's first value (and key) into the
|
||||
local variable S3 (and S4, respectively).
|
||||
|
||||
The IterInit and IterInitK instructions always copy the array
|
||||
element by value.
|
||||
|
||||
The WIterInit and WIterInitK instructions copy referenced array
|
||||
elements by reference, and non-referenced array elements by value.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
Advances the iterator variable whose index is given by S1. S2 and S3
|
||||
are local variable indices. S0 points to the stack frame containing
|
||||
@@ -1580,6 +1590,11 @@ D:Bool = IterNextK S0:FramePtr S1:ConstInt S2:ConstInt S3:ConstInt
|
||||
the iterator's next value (and key) into the local variable S3 (and
|
||||
S4, respectively).
|
||||
|
||||
The IterInit and IterInitK instructions always copy the array
|
||||
element by value.
|
||||
The WIterInit and WIterInitK instructions copy referenced array
|
||||
elements by reference, and non-referenced array elements by value.
|
||||
|
||||
IterFree S0:FramePtr S1:ConstInt
|
||||
|
||||
Free the iterator variable whose index is given by S1 in the stack
|
||||
@@ -1729,6 +1744,12 @@ D:Vector, D:StkPtr = SetElemStk S0:ConstTCA S1:PtrToGen S2:Gen S3:Cell
|
||||
|
||||
Set element with key S2 in S1 to S3.
|
||||
|
||||
SetWithRefElem S0:ConstTCA S1:PtrToGen S2:Gen S3:PtrToGen S4:PtrToCell
|
||||
D:StkPtr = SetWithRefElemStk S0:ConstTCA S1:PtrToGen S2:Gen S3:PtrToGen S4:PtrToCell
|
||||
|
||||
Set element with key S2 in S1 to S3. S4 should point to
|
||||
an MInstrState struct.
|
||||
|
||||
UnsetElem S0:ConstTCA S1:PtrToGen S2:Gen
|
||||
D:StkPtr = UnsetElemStk S0:ConstTCA S1:PtrToGen S2:Gen
|
||||
|
||||
@@ -1752,6 +1773,12 @@ D:Vector, D:StkPtr = SetNewElemStk S0:PtrToGen S1:Cell
|
||||
|
||||
Append the value in S1 to S0.
|
||||
|
||||
SetWithRefNewElem S0:ConstTCA S1:PtrToGen S2:PtrToGen S3:PtrToCell
|
||||
D:StkPtr = SetWithRefNewElemStk S0:ConstTCA S1:PtrToGen S2:PtrToGen
|
||||
S3:PtrToCell
|
||||
|
||||
AppendWithRef the value in S2 to S1. S3 should point to an MInstrState struct.
|
||||
|
||||
BindNewElem S0:ConstTCA S0:PtrToGen S1:BoxedCell S2:PtrToCell
|
||||
D:StkPtr = BindNewElemStk S0:PtrToGen S1:BoxedCell S2:PtrToCell
|
||||
|
||||
|
||||
@@ -716,10 +716,10 @@ void Iter::mfree() {
|
||||
* to make sure the hot part is a good-looking leaf function; otherwise,
|
||||
* you're likely to get a performance regression.
|
||||
*/
|
||||
template <bool typeArray>
|
||||
template <bool typeArray, bool withRef>
|
||||
static inline void iter_value_cell_local_impl(Iter* iter, TypedValue* out) {
|
||||
DataType oldType = out->m_type;
|
||||
assert(oldType != KindOfRef);
|
||||
assert(withRef || oldType != KindOfRef);
|
||||
uint64_t oldDatum = out->m_data.num;
|
||||
TRACE(2, "%s: typeArray: %s, I %p, out %p\n",
|
||||
__func__, typeArray ? "true" : "false", iter, out);
|
||||
@@ -727,8 +727,13 @@ static inline void iter_value_cell_local_impl(Iter* iter, TypedValue* out) {
|
||||
(!typeArray && iter->arr().getIterType() == ArrayIter::TypeIterator));
|
||||
ArrayIter& arrIter = iter->arr();
|
||||
if (typeArray) {
|
||||
TypedValue* cur = tvToCell(arrIter.nvSecond());
|
||||
tvDupCell(cur, out);
|
||||
TypedValue* cur = arrIter.nvSecond();
|
||||
if (cur->m_type == KindOfRef) {
|
||||
if (!withRef || cur->m_data.pref->getCount() == 1) {
|
||||
cur = cur->m_data.pref->tv();
|
||||
}
|
||||
}
|
||||
tvDup(cur, out);
|
||||
} else {
|
||||
Variant val = arrIter.second();
|
||||
assert(val.getRawType() != KindOfRef);
|
||||
@@ -737,10 +742,10 @@ static inline void iter_value_cell_local_impl(Iter* iter, TypedValue* out) {
|
||||
tvRefcountedDecRefHelper(oldType, oldDatum);
|
||||
}
|
||||
|
||||
template <bool typeArray>
|
||||
template <bool typeArray, bool withRef>
|
||||
static inline void iter_key_cell_local_impl(Iter* iter, TypedValue* out) {
|
||||
DataType oldType = out->m_type;
|
||||
assert(oldType != KindOfRef);
|
||||
assert(withRef || oldType != KindOfRef);
|
||||
uint64_t oldDatum = out->m_data.num;
|
||||
TRACE(2, "%s: I %p, out %p\n", __func__, iter, out);
|
||||
assert((typeArray && iter->arr().getIterType() == ArrayIter::TypeArray) ||
|
||||
@@ -761,18 +766,19 @@ static inline void iter_key_cell_local_impl(Iter* iter, TypedValue* out) {
|
||||
* refcount of the specified array. If new_iter_array does not create an
|
||||
* iterator, it decRefs the array.
|
||||
*/
|
||||
static NEVER_INLINE
|
||||
template <bool withRef>
|
||||
NEVER_INLINE
|
||||
int64_t new_iter_array_cold(Iter* dest, ArrayData* arr, TypedValue* valOut,
|
||||
TypedValue* keyOut) {
|
||||
TypedValue* keyOut) {
|
||||
TRACE(2, "%s: I %p, arr %p\n", __func__, dest, arr);
|
||||
if (!arr->empty()) {
|
||||
// We are transferring ownership of the array to the iterator, therefore
|
||||
// we do not need to adjust the refcount.
|
||||
(void) new (&dest->arr()) ArrayIter(arr, ArrayIter::noInc);
|
||||
dest->arr().setIterType(ArrayIter::TypeArray);
|
||||
iter_value_cell_local_impl<true>(dest, valOut);
|
||||
iter_value_cell_local_impl<true, withRef>(dest, valOut);
|
||||
if (keyOut) {
|
||||
iter_key_cell_local_impl<true>(dest, keyOut);
|
||||
iter_key_cell_local_impl<true, withRef>(dest, keyOut);
|
||||
}
|
||||
return 1LL;
|
||||
}
|
||||
@@ -782,12 +788,24 @@ int64_t new_iter_array_cold(Iter* dest, ArrayData* arr, TypedValue* valOut,
|
||||
return 0LL;
|
||||
}
|
||||
|
||||
static inline void getHphpArrayElm(HphpArray::Elm* elm, TypedValue* valOut,
|
||||
TypedValue* keyOut) {
|
||||
TypedValue* cur = tvToCell(&elm->data);
|
||||
tvDupCell(cur, valOut);
|
||||
if (keyOut) {
|
||||
HphpArray::getElmKey(elm, keyOut);
|
||||
template <bool withRef>
|
||||
inline ALWAYS_INLINE
|
||||
void getHphpArrayElm(HphpArray::Elm* elm, TypedValue* valOut,
|
||||
TypedValue* keyOut) {
|
||||
if (withRef) {
|
||||
tvAsVariant(valOut) = withRefBind(tvAsVariant(&elm->data));
|
||||
if (LIKELY(keyOut != nullptr)) {
|
||||
DataType t = keyOut->m_type;
|
||||
uint64_t d = keyOut->m_data.num;
|
||||
HphpArray::getElmKey(elm, keyOut);
|
||||
tvRefcountedDecRefHelper(t, d);
|
||||
}
|
||||
} else {
|
||||
TypedValue* cur = tvToCell(&elm->data);
|
||||
tvDupCell(cur, valOut);
|
||||
if (keyOut) {
|
||||
HphpArray::getElmKey(elm, keyOut);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,7 +828,7 @@ int64_t new_iter_array(Iter* dest, ArrayData* ad, TypedValue* valOut) {
|
||||
(void) new (&dest->arr()) ArrayIter(arr, ArrayIter::noIncNonNull);
|
||||
dest->arr().setIterType(ArrayIter::TypeArray);
|
||||
HphpArray::Elm* elm = arr->getElm(dest->arr().m_pos);
|
||||
getHphpArrayElm(elm, valOut, nullptr);
|
||||
getHphpArrayElm<false>(elm, valOut, nullptr);
|
||||
return 1LL;
|
||||
}
|
||||
// We did not transfer ownership of the array to an iterator, so we need
|
||||
@@ -822,33 +840,38 @@ int64_t new_iter_array(Iter* dest, ArrayData* ad, TypedValue* valOut) {
|
||||
return 0LL;
|
||||
}
|
||||
cold:
|
||||
return new_iter_array_cold(dest, ad, valOut, nullptr);
|
||||
return new_iter_array_cold<false>(dest, ad, valOut, nullptr);
|
||||
}
|
||||
|
||||
template <bool withRef>
|
||||
HOT_FUNC
|
||||
int64_t new_iter_array_key(Iter* dest, ArrayData* ad, TypedValue* valOut,
|
||||
TypedValue* keyOut) {
|
||||
int64_t new_iter_array_key(Iter* dest, ArrayData* ad,
|
||||
TypedValue* valOut, TypedValue* keyOut) {
|
||||
TRACE(2, "%s: I %p, ad %p\n", __func__, dest, ad);
|
||||
valOut = tvToCell(valOut);
|
||||
keyOut = tvToCell(keyOut);
|
||||
if (!withRef) {
|
||||
valOut = tvToCell(valOut);
|
||||
keyOut = tvToCell(keyOut);
|
||||
}
|
||||
if (UNLIKELY(!ad->isHphpArray())) {
|
||||
goto cold;
|
||||
}
|
||||
{
|
||||
HphpArray* arr = (HphpArray*)ad;
|
||||
if (LIKELY(arr->getSize() != 0)) {
|
||||
if (UNLIKELY(tvWillBeReleased(valOut)) ||
|
||||
UNLIKELY(tvWillBeReleased(keyOut))) {
|
||||
goto cold;
|
||||
if (!withRef) {
|
||||
if (UNLIKELY(tvWillBeReleased(valOut)) ||
|
||||
UNLIKELY(tvWillBeReleased(keyOut))) {
|
||||
goto cold;
|
||||
}
|
||||
tvDecRefOnly(valOut);
|
||||
tvDecRefOnly(keyOut);
|
||||
}
|
||||
tvDecRefOnly(valOut);
|
||||
tvDecRefOnly(keyOut);
|
||||
// We are transferring ownership of the array to the iterator, therefore
|
||||
// we do not need to adjust the refcount.
|
||||
(void) new (&dest->arr()) ArrayIter(arr, ArrayIter::noIncNonNull);
|
||||
dest->arr().setIterType(ArrayIter::TypeArray);
|
||||
HphpArray::Elm* elm = arr->getElm(dest->arr().m_pos);
|
||||
getHphpArrayElm(elm, valOut, keyOut);
|
||||
getHphpArrayElm<withRef>(elm, valOut, keyOut);
|
||||
return 1LL;
|
||||
}
|
||||
// We did not transfer ownership of the array to an iterator, so we need
|
||||
@@ -860,9 +883,16 @@ int64_t new_iter_array_key(Iter* dest, ArrayData* ad, TypedValue* valOut,
|
||||
return 0LL;
|
||||
}
|
||||
cold:
|
||||
return new_iter_array_cold(dest, ad, valOut, keyOut);
|
||||
return new_iter_array_cold<withRef>(dest, ad, valOut, keyOut);
|
||||
}
|
||||
|
||||
template int64_t new_iter_array_key<false>(Iter* dest, ArrayData* ad,
|
||||
TypedValue* valOut,
|
||||
TypedValue* keyOut);
|
||||
template int64_t new_iter_array_key<true>(Iter* dest, ArrayData* ad,
|
||||
TypedValue* valOut,
|
||||
TypedValue* keyOut);
|
||||
|
||||
class FreeObj {
|
||||
public:
|
||||
FreeObj() : m_obj(0) {}
|
||||
@@ -943,14 +973,14 @@ int64_t new_iter_object(Iter* dest, ObjectData* obj, Class* ctx,
|
||||
|
||||
dest->arr().setIterType(itType);
|
||||
if (itType == ArrayIter::TypeIterator) {
|
||||
iter_value_cell_local_impl<false>(dest, valOut);
|
||||
iter_value_cell_local_impl<false, false>(dest, valOut);
|
||||
if (keyOut) {
|
||||
iter_key_cell_local_impl<false>(dest, keyOut);
|
||||
iter_key_cell_local_impl<false, false>(dest, keyOut);
|
||||
}
|
||||
} else {
|
||||
iter_value_cell_local_impl<true>(dest, valOut);
|
||||
iter_value_cell_local_impl<true, false>(dest, valOut);
|
||||
if (keyOut) {
|
||||
iter_key_cell_local_impl<true>(dest, keyOut);
|
||||
iter_key_cell_local_impl<true, false>(dest, keyOut);
|
||||
}
|
||||
}
|
||||
return 1LL;
|
||||
@@ -970,7 +1000,8 @@ int64_t new_iter_object(Iter* dest, ObjectData* obj, Class* ctx,
|
||||
* to make sure the hot part is a good-looking leaf function; otherwise,
|
||||
* you're likely to get a performance regression.
|
||||
*/
|
||||
static NEVER_INLINE
|
||||
template <bool withRef>
|
||||
NEVER_INLINE
|
||||
int64_t iter_next_cold(Iter* iter, TypedValue* valOut, TypedValue* keyOut) {
|
||||
TRACE(2, "iter_next_cold: I %p\n", iter);
|
||||
assert(iter->arr().getIterType() == ArrayIter::TypeArray ||
|
||||
@@ -983,14 +1014,14 @@ int64_t iter_next_cold(Iter* iter, TypedValue* valOut, TypedValue* keyOut) {
|
||||
return 0;
|
||||
}
|
||||
if (iter->arr().getIterType() == ArrayIter::TypeArray) {
|
||||
iter_value_cell_local_impl<true>(iter, valOut);
|
||||
iter_value_cell_local_impl<true, withRef>(iter, valOut);
|
||||
if (keyOut) {
|
||||
iter_key_cell_local_impl<true>(iter, keyOut);
|
||||
iter_key_cell_local_impl<true, withRef>(iter, keyOut);
|
||||
}
|
||||
} else {
|
||||
iter_value_cell_local_impl<false>(iter, valOut);
|
||||
iter_value_cell_local_impl<false, withRef>(iter, valOut);
|
||||
if (keyOut) {
|
||||
iter_key_cell_local_impl<false>(iter, keyOut);
|
||||
iter_key_cell_local_impl<false, withRef>(iter, keyOut);
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
@@ -1013,41 +1044,45 @@ int64_t iter_next(Iter* iter, TypedValue* valOut) {
|
||||
}
|
||||
const HphpArray* arr = (HphpArray*)ad;
|
||||
ssize_t pos = arrIter->getPos();
|
||||
if (size_t(pos) >= size_t(arr->getLastE())) {
|
||||
if (UNLIKELY(arr->getCount() == 1)) {
|
||||
goto cold;
|
||||
|
||||
HphpArray::Elm* elm;
|
||||
do {
|
||||
if (size_t(pos) >= size_t(arr->getLastE())) {
|
||||
if (UNLIKELY(arr->getCount() == 1)) {
|
||||
goto cold;
|
||||
}
|
||||
arr->decRefCount();
|
||||
if (debug) {
|
||||
iter->arr().setIterType(ArrayIter::TypeUndefined);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
arr->decRefCount();
|
||||
if (debug) {
|
||||
iter->arr().setIterType(ArrayIter::TypeUndefined);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
pos = pos + 1;
|
||||
HphpArray::Elm* elm = arr->getElm(pos);
|
||||
if (UNLIKELY(elm->data.m_type >= HphpArray::KindOfTombstone)) {
|
||||
goto cold;
|
||||
}
|
||||
pos = pos + 1;
|
||||
elm = arr->getElm(pos);
|
||||
} while (elm->data.m_type >= HphpArray::KindOfTombstone);
|
||||
if (UNLIKELY(tvWillBeReleased(valOut))) {
|
||||
goto cold;
|
||||
}
|
||||
tvDecRefOnly(valOut);
|
||||
arrIter->setPos(pos);
|
||||
getHphpArrayElm(elm, valOut, nullptr);
|
||||
getHphpArrayElm<false>(elm, valOut, nullptr);
|
||||
return 1;
|
||||
}
|
||||
cold:
|
||||
return iter_next_cold(iter, valOut, nullptr);
|
||||
return iter_next_cold<false>(iter, valOut, nullptr);
|
||||
}
|
||||
|
||||
template <bool withRef>
|
||||
HOT_FUNC
|
||||
int64_t iter_next_key(Iter* iter, TypedValue* valOut, TypedValue* keyOut) {
|
||||
TRACE(2, "iter_next: I %p\n", iter);
|
||||
TRACE(2, "iter_next_key: I %p\n", iter);
|
||||
assert(iter->arr().getIterType() == ArrayIter::TypeArray ||
|
||||
iter->arr().getIterType() == ArrayIter::TypeIterator);
|
||||
ArrayIter* arrIter = &iter->arr();
|
||||
valOut = tvToCell(valOut);
|
||||
keyOut = tvToCell(keyOut);
|
||||
if (!withRef) {
|
||||
valOut = tvToCell(valOut);
|
||||
keyOut = tvToCell(keyOut);
|
||||
}
|
||||
if (UNLIKELY(!arrIter->hasArrayData())) {
|
||||
goto cold;
|
||||
}
|
||||
@@ -1058,36 +1093,45 @@ int64_t iter_next_key(Iter* iter, TypedValue* valOut, TypedValue* keyOut) {
|
||||
}
|
||||
const HphpArray* arr = (HphpArray*)ad;
|
||||
ssize_t pos = arrIter->getPos();
|
||||
if (size_t(pos) >= size_t(arr->getLastE())) {
|
||||
if (UNLIKELY(arr->getCount() == 1)) {
|
||||
HphpArray::Elm* elm;
|
||||
do {
|
||||
if (size_t(pos) >= size_t(arr->getLastE())) {
|
||||
if (UNLIKELY(arr->getCount() == 1)) {
|
||||
goto cold;
|
||||
}
|
||||
arr->decRefCount();
|
||||
if (debug) {
|
||||
iter->arr().setIterType(ArrayIter::TypeUndefined);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
pos = pos + 1;
|
||||
elm = arr->getElm(pos);
|
||||
} while (elm->data.m_type >= HphpArray::KindOfTombstone);
|
||||
if (!withRef) {
|
||||
if (UNLIKELY(tvWillBeReleased(valOut))) {
|
||||
goto cold;
|
||||
}
|
||||
arr->decRefCount();
|
||||
if (debug) {
|
||||
iter->arr().setIterType(ArrayIter::TypeUndefined);
|
||||
if (UNLIKELY(tvWillBeReleased(keyOut))) {
|
||||
goto cold;
|
||||
}
|
||||
return 0;
|
||||
tvDecRefOnly(valOut);
|
||||
tvDecRefOnly(keyOut);
|
||||
}
|
||||
pos = pos + 1;
|
||||
HphpArray::Elm* elm = arr->getElm(pos);
|
||||
if (UNLIKELY(elm->data.m_type >= HphpArray::KindOfTombstone)) {
|
||||
goto cold;
|
||||
}
|
||||
if (UNLIKELY(tvWillBeReleased(valOut))) {
|
||||
goto cold;
|
||||
}
|
||||
if (UNLIKELY(tvWillBeReleased(keyOut))) {
|
||||
goto cold;
|
||||
}
|
||||
tvDecRefOnly(valOut);
|
||||
tvDecRefOnly(keyOut);
|
||||
arrIter->setPos(pos);
|
||||
getHphpArrayElm(elm, valOut, keyOut);
|
||||
getHphpArrayElm<withRef>(elm, valOut, keyOut);
|
||||
return 1;
|
||||
}
|
||||
cold:
|
||||
return iter_next_cold(iter, valOut, keyOut);
|
||||
cold:
|
||||
return iter_next_cold<withRef>(iter, valOut, keyOut);
|
||||
}
|
||||
|
||||
template int64_t iter_next_key<false>(Iter* dest,
|
||||
TypedValue* valOut,
|
||||
TypedValue* keyOut);
|
||||
template int64_t iter_next_key<true>(Iter* dest,
|
||||
TypedValue* valOut,
|
||||
TypedValue* keyOut);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
@@ -469,11 +469,13 @@ bool interp_iter_next(Iter* it);
|
||||
bool interp_iter_next_m(Iter* it);
|
||||
|
||||
int64_t new_iter_array(Iter* dest, ArrayData* arr, TypedValue* val);
|
||||
template <bool withRef>
|
||||
int64_t new_iter_array_key(Iter* dest, ArrayData* arr, TypedValue* val,
|
||||
TypedValue* key);
|
||||
int64_t new_iter_object(Iter* dest, ObjectData* obj, Class* ctx,
|
||||
TypedValue* val, TypedValue* key);
|
||||
int64_t iter_next(Iter* dest, TypedValue* val);
|
||||
template <bool withRef>
|
||||
int64_t iter_next_key(Iter* dest, TypedValue* val, TypedValue* key);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -280,7 +280,8 @@ string FileRepository::unitMd5(const string& fileMd5) {
|
||||
string t = fileMd5 + '\0'
|
||||
+ (RuntimeOption::EnableHipHopSyntax ? '1' : '0')
|
||||
+ (RuntimeOption::EnableEmitSwitch ? '1' : '0')
|
||||
+ (RuntimeOption::EvalJitEnableRenameFunction ? '1' : '0');
|
||||
+ (RuntimeOption::EvalJitEnableRenameFunction ? '1' : '0')
|
||||
+ (RuntimeOption::EvalAllowHhas ? '1' : '0');
|
||||
md5str = string_md5(t.c_str(), t.size(), false, md5len);
|
||||
string s = string(md5str, md5len);
|
||||
free(md5str);
|
||||
|
||||
@@ -1020,6 +1020,7 @@ OpcodeParserMap opcode_parsers;
|
||||
#define NUM_POP_THREE(a,b,c) 3
|
||||
#define NUM_POP_LMANY() vecImmStackValues
|
||||
#define NUM_POP_V_LMANY() (1 + vecImmStackValues)
|
||||
#define NUM_POP_R_LMANY() (1 + vecImmStackValues)
|
||||
#define NUM_POP_C_LMANY() (1 + vecImmStackValues)
|
||||
#define NUM_POP_FMANY immIVA /* number of arguments */
|
||||
#define NUM_POP_CMANY immIVA /* number of arguments */
|
||||
@@ -1094,6 +1095,7 @@ OPCODES
|
||||
#undef NUM_POP_POS_N
|
||||
#undef NUM_POP_LMANY
|
||||
#undef NUM_POP_V_LMANY
|
||||
#undef NUM_POP_R_LMANY
|
||||
#undef NUM_POP_C_LMANY
|
||||
#undef NUM_POP_FMANY
|
||||
#undef NUM_POP_CMANY
|
||||
|
||||
@@ -5246,6 +5246,32 @@ inline void OPTBLD_INLINE VMExecutionContext::iopSetM(PC& pc) {
|
||||
setHelperPost<1>(SETHELPERPOST_ARGS);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopSetWithRefLM(PC& pc) {
|
||||
NEXT();
|
||||
DECLARE_SETHELPER_ARGS
|
||||
bool skip = setHelperPre<false, true, false, false, 0,
|
||||
ConsumeAll>(MEMBERHELPERPRE_ARGS);
|
||||
DECODE_HA(local);
|
||||
if (!skip) {
|
||||
TypedValue* from = frame_local(m_fp, local);
|
||||
tvAsVariant(base) = withRefBind(tvAsVariant(from));
|
||||
}
|
||||
setHelperPost<0>(SETHELPERPOST_ARGS);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopSetWithRefRM(PC& pc) {
|
||||
NEXT();
|
||||
DECLARE_SETHELPER_ARGS
|
||||
bool skip = setHelperPre<false, true, false, false, 1,
|
||||
ConsumeAll>(MEMBERHELPERPRE_ARGS);
|
||||
if (!skip) {
|
||||
TypedValue* from = m_stack.top();
|
||||
tvAsVariant(base) = withRefBind(tvAsVariant(from));
|
||||
}
|
||||
m_stack.popTV();
|
||||
setHelperPost<1>(SETHELPERPOST_ARGS);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopSetOpL(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_HA(local);
|
||||
@@ -6460,6 +6486,37 @@ inline void OPTBLD_INLINE VMExecutionContext::iopIterInitK(PC& pc) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopWIterInit(PC& pc) {
|
||||
PC origPc = pc;
|
||||
NEXT();
|
||||
DECODE_IA(itId);
|
||||
DECODE(Offset, offset);
|
||||
DECODE_HA(val);
|
||||
Cell* c1 = m_stack.topC();
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
TypedValue* tv1 = frame_local(m_fp, val);
|
||||
if (initIterator(pc, origPc, it, offset, c1)) {
|
||||
tvAsVariant(tv1) = withRefBind(it->arr().secondRef());
|
||||
}
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopWIterInitK(PC& pc) {
|
||||
PC origPc = pc;
|
||||
NEXT();
|
||||
DECODE_IA(itId);
|
||||
DECODE(Offset, offset);
|
||||
DECODE_HA(val);
|
||||
DECODE_HA(key);
|
||||
Cell* c1 = m_stack.topC();
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
TypedValue* tv1 = frame_local(m_fp, val);
|
||||
TypedValue* tv2 = frame_local(m_fp, key);
|
||||
if (initIterator(pc, origPc, it, offset, c1)) {
|
||||
tvAsVariant(tv1) = withRefBind(it->arr().secondRef());
|
||||
tvAsVariant(tv2) = it->arr().first();
|
||||
}
|
||||
}
|
||||
|
||||
inline bool VMExecutionContext::initIteratorM(PC& pc, PC& origPc, Iter* it,
|
||||
Offset offset, Var* v1) {
|
||||
bool hasElems = it->minit(v1);
|
||||
@@ -6534,6 +6591,37 @@ inline void OPTBLD_INLINE VMExecutionContext::iopIterNextK(PC& pc) {
|
||||
}
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopWIterNext(PC& pc) {
|
||||
PC origPc = pc;
|
||||
NEXT();
|
||||
DECODE_IA(itId);
|
||||
DECODE(Offset, offset);
|
||||
DECODE_HA(val);
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
TypedValue* tv1 = frame_local(m_fp, val);
|
||||
if (it->next()) {
|
||||
ITER_SKIP(offset);
|
||||
tvAsVariant(tv1) = withRefBind(it->arr().secondRef());
|
||||
}
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopWIterNextK(PC& pc) {
|
||||
PC origPc = pc;
|
||||
NEXT();
|
||||
DECODE_IA(itId);
|
||||
DECODE(Offset, offset);
|
||||
DECODE_HA(val);
|
||||
DECODE_HA(key);
|
||||
Iter* it = frame_iter(m_fp, itId);
|
||||
TypedValue* tv1 = frame_local(m_fp, val);
|
||||
TypedValue* tv2 = frame_local(m_fp, key);
|
||||
if (it->next()) {
|
||||
ITER_SKIP(offset);
|
||||
tvAsVariant(tv1) = withRefBind(it->arr().secondRef());
|
||||
tvAsVariant(tv2) = it->arr().first();
|
||||
}
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopMIterNext(PC& pc) {
|
||||
PC origPc = pc;
|
||||
NEXT();
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include "hphp/runtime/ext/ext_variable.h"
|
||||
#include "hphp/runtime/vm/hhbc.h"
|
||||
#include "hphp/runtime/ext/ext_variable.h"
|
||||
#include "hphp/runtime/vm/unit.h"
|
||||
#include "hphp/runtime/base/stats.h"
|
||||
#include <sstream>
|
||||
@@ -364,6 +364,7 @@ int instrNumPops(const Opcode* opcode) {
|
||||
#define LMANY(...) -1
|
||||
#define C_LMANY(...) -2
|
||||
#define V_LMANY(...) -2
|
||||
#define R_LMANY(...) -2
|
||||
#define FMANY -3
|
||||
#define CMANY -3
|
||||
#define O(name, imm, pop, push, flags) pop,
|
||||
@@ -376,6 +377,7 @@ int instrNumPops(const Opcode* opcode) {
|
||||
#undef LMANY
|
||||
#undef C_LMANY
|
||||
#undef V_LMANY
|
||||
#undef R_LMANY
|
||||
#undef FMANY
|
||||
#undef CMANY
|
||||
#undef O
|
||||
|
||||
@@ -204,18 +204,22 @@ enum MInstrAttr {
|
||||
// fN) final new element operation name
|
||||
#define MINSTRS \
|
||||
MII(CGet, MIA_warn|MIA_final_get, W, W, 0, NotSuppNewElem) \
|
||||
MII(VGet, MIA_define|MIA_reffy|MIA_new|MIA_final_get, \
|
||||
MII(VGet, MIA_define|MIA_reffy|MIA_new|MIA_final_get, \
|
||||
D, D, 0, VGetNewElem) \
|
||||
MII(Isset, MIA_final_get, , , 0, NotSuppNewElem) \
|
||||
MII(Empty, MIA_final_get, , , 0, NotSuppNewElem) \
|
||||
MII(Set, MIA_define|MIA_new, D, D, 1, SetNewElem) \
|
||||
MII(SetOp, MIA_more_warn|MIA_define|MIA_new|MIA_final_get, \
|
||||
MII(Set, MIA_define|MIA_new, D, D, 1, SetNewElem) \
|
||||
MII(SetOp, MIA_more_warn|MIA_define|MIA_new|MIA_final_get, \
|
||||
WD, WD, 1, SetOpNewElem) \
|
||||
MII(IncDec, MIA_more_warn|MIA_define|MIA_new|MIA_final_get, \
|
||||
MII(IncDec, MIA_more_warn|MIA_define|MIA_new|MIA_final_get, \
|
||||
WD, WD, 0, IncDecNewElem) \
|
||||
MII(Bind, MIA_define|MIA_reffy|MIA_new|MIA_final_get, \
|
||||
MII(Bind, MIA_define|MIA_reffy|MIA_new|MIA_final_get, \
|
||||
D, D, 1, BindNewElem) \
|
||||
MII(Unset, MIA_unset, , U, 0, NotSuppNewElem) \
|
||||
MII(SetWithRefL,MIA_define|MIA_reffy|MIA_new|MIA_final_get, \
|
||||
D, D, 1, SetWithRefNewElem) \
|
||||
MII(SetWithRefR,MIA_define|MIA_reffy|MIA_new|MIA_final_get, \
|
||||
D, D, 1, SetWithRefNewElem)
|
||||
|
||||
enum MInstr {
|
||||
#define MII(instr, attrs, bS, iS, vC, fN) \
|
||||
@@ -469,6 +473,8 @@ enum SetOpOp {
|
||||
O(SetG, NA, TWO(CV,CV), ONE(CV), NF) \
|
||||
O(SetS, NA, THREE(CV,AV,CV), ONE(CV), NF) \
|
||||
O(SetM, ONE(MA), C_LMANY(), ONE(CV), NF) \
|
||||
O(SetWithRefLM, TWO(MA, HA), LMANY(), NOV, NF) \
|
||||
O(SetWithRefRM, ONE(MA), R_LMANY(), NOV, NF) \
|
||||
O(SetOpL, TWO(HA, OA), ONE(CV), ONE(CV), NF) \
|
||||
O(SetOpN, ONE(OA), TWO(CV,CV), ONE(CV), NF) \
|
||||
O(SetOpG, ONE(OA), TWO(CV,CV), ONE(CV), NF) \
|
||||
@@ -521,12 +527,16 @@ enum SetOpOp {
|
||||
O(CufSafeReturn, NA, THREE(RV,CV,CV), ONE(RV), NF) \
|
||||
O(IterInit, THREE(IA,BA,HA), ONE(CV), NOV, CF) \
|
||||
O(MIterInit, THREE(IA,BA,HA), ONE(VV), NOV, CF) \
|
||||
O(WIterInit, THREE(IA,BA,HA), ONE(CV), NOV, CF) \
|
||||
O(IterInitK, FOUR(IA,BA,HA,HA),ONE(CV), NOV, CF) \
|
||||
O(MIterInitK, FOUR(IA,BA,HA,HA),ONE(VV), NOV, CF) \
|
||||
O(WIterInitK, FOUR(IA,BA,HA,HA),ONE(CV), NOV, CF) \
|
||||
O(IterNext, THREE(IA,BA,HA), NOV, NOV, CF) \
|
||||
O(MIterNext, THREE(IA,BA,HA), NOV, NOV, CF) \
|
||||
O(WIterNext, THREE(IA,BA,HA), NOV, NOV, CF) \
|
||||
O(IterNextK, FOUR(IA,BA,HA,HA),NOV, NOV, CF) \
|
||||
O(MIterNextK, FOUR(IA,BA,HA,HA),NOV, NOV, CF) \
|
||||
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(Incl, NA, ONE(CV), ONE(CV), CF) \
|
||||
|
||||
@@ -435,6 +435,8 @@ CALL_OPCODE(ArrayGet)
|
||||
CALL_OPCODE(CGetElem)
|
||||
CALL_STK_OPCODE(VGetElem)
|
||||
CALL_STK_OPCODE(BindElem)
|
||||
CALL_STK_OPCODE(SetWithRefElem)
|
||||
CALL_STK_OPCODE(SetWithRefNewElem)
|
||||
CALL_OPCODE(ArraySet)
|
||||
CALL_OPCODE(ArraySetRef)
|
||||
CALL_STK_OPCODE(SetElem)
|
||||
@@ -5049,46 +5051,26 @@ void CodeGenerator::cgContStartedCheck(IRInstruction* inst) {
|
||||
emitFwdJcc(CC_L, inst->taken());
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterNextK(IRInstruction* inst) {
|
||||
cgIterNextCommon(inst, true);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterNext(IRInstruction* inst) {
|
||||
cgIterNextCommon(inst, false);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterNextCommon(IRInstruction* inst, bool isNextK) {
|
||||
PhysReg fpReg = m_regs[inst->src(0)].getReg();
|
||||
ArgGroup args(m_regs);
|
||||
args.addr(fpReg, getIterOffset(inst->src(1)))
|
||||
.addr(fpReg, getLocalOffset(inst->src(2)));
|
||||
if (isNextK) {
|
||||
args.addr(fpReg, getLocalOffset(inst->src(3)));
|
||||
}
|
||||
TCA helperAddr = isNextK ? (TCA)iter_next_key : (TCA)iter_next;
|
||||
cgCallHelper(m_as, helperAddr, inst->dst(), kSyncPoint, args);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterInit(IRInstruction* inst) {
|
||||
cgIterInitCommon(inst, false);
|
||||
}
|
||||
|
||||
void iterFreeHelper(Iter* iter) {
|
||||
iter->free();
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterFree(IRInstruction* inst) {
|
||||
PhysReg fpReg = m_regs[inst->src(0)].getReg();
|
||||
int64_t offset = getIterOffset(inst->src(1));
|
||||
cgCallHelper(m_as, (TCA)iterFreeHelper, InvalidReg, kSyncPoint,
|
||||
ArgGroup(m_regs).addr(fpReg, offset));
|
||||
cgIterInitCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterInitK(IRInstruction* inst) {
|
||||
cgIterInitCommon(inst, true);
|
||||
cgIterInitCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterInitCommon(IRInstruction* inst, bool isInitK) {
|
||||
void CodeGenerator::cgWIterInit(IRInstruction* inst) {
|
||||
cgIterInitCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgWIterInitK(IRInstruction* inst) {
|
||||
cgIterInitCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterInitCommon(IRInstruction* inst) {
|
||||
bool isInitK = inst->op() == IterInitK || inst->op() == WIterInitK;
|
||||
bool isWInit = inst->op() == WIterInit || inst->op() == WIterInitK;
|
||||
|
||||
PhysReg fpReg = m_regs[inst->src(1)].getReg();
|
||||
int64_t iterOffset = getIterOffset(inst->src(2));
|
||||
int64_t valLocalOffset = getLocalOffset(inst->src(3));
|
||||
@@ -5099,8 +5081,11 @@ void CodeGenerator::cgIterInitCommon(IRInstruction* inst, bool isInitK) {
|
||||
args.addr(fpReg, valLocalOffset);
|
||||
if (isInitK) {
|
||||
args.addr(fpReg, getLocalOffset(inst->src(4)));
|
||||
} else if (isWInit) {
|
||||
args.imm(0);
|
||||
}
|
||||
TCA helperAddr = isInitK ? (TCA)new_iter_array_key : (TCA)new_iter_array;
|
||||
TCA helperAddr = isWInit ? (TCA)new_iter_array_key<true> :
|
||||
isInitK ? (TCA)new_iter_array_key<false> : (TCA)new_iter_array;
|
||||
cgCallHelper(m_as, helperAddr, inst->dst(), kSyncPoint, args);
|
||||
} else {
|
||||
assert(src->type() == Type::Obj);
|
||||
@@ -5119,6 +5104,50 @@ void CodeGenerator::cgIterInitCommon(IRInstruction* inst, bool isInitK) {
|
||||
}
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterNext(IRInstruction* inst) {
|
||||
cgIterNextCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterNextK(IRInstruction* inst) {
|
||||
cgIterNextCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgWIterNext(IRInstruction* inst) {
|
||||
cgIterNextCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgWIterNextK(IRInstruction* inst) {
|
||||
cgIterNextCommon(inst);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterNextCommon(IRInstruction* inst) {
|
||||
bool isNextK = inst->op() == IterNextK || inst->op() == WIterNextK;
|
||||
bool isWNext = inst->op() == WIterNext || inst->op() == WIterNextK;
|
||||
PhysReg fpReg = m_regs[inst->src(0)].getReg();
|
||||
ArgGroup args(m_regs);
|
||||
args.addr(fpReg, getIterOffset(inst->src(1)))
|
||||
.addr(fpReg, getLocalOffset(inst->src(2)));
|
||||
if (isNextK) {
|
||||
args.addr(fpReg, getLocalOffset(inst->src(3)));
|
||||
} else if (isWNext) {
|
||||
args.imm(0);
|
||||
}
|
||||
TCA helperAddr = isWNext ? (TCA)iter_next_key<true> :
|
||||
isNextK ? (TCA)iter_next_key<false> : (TCA)iter_next;
|
||||
cgCallHelper(m_as, helperAddr, inst->dst(), kSyncPoint, args);
|
||||
}
|
||||
|
||||
void iterFreeHelper(Iter* iter) {
|
||||
iter->free();
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIterFree(IRInstruction* inst) {
|
||||
PhysReg fpReg = m_regs[inst->src(0)].getReg();
|
||||
int64_t offset = getIterOffset(inst->src(1));
|
||||
cgCallHelper(m_as, (TCA)iterFreeHelper, InvalidReg, kSyncPoint,
|
||||
ArgGroup(m_regs).addr(fpReg, offset));
|
||||
}
|
||||
|
||||
void CodeGenerator::cgIncStat(IRInstruction *inst) {
|
||||
Stats::emitInc(m_as,
|
||||
Stats::StatCounter(inst->src(0)->getValInt()),
|
||||
|
||||
@@ -310,8 +310,8 @@ private:
|
||||
int64_t offset,
|
||||
Block* exit);
|
||||
|
||||
void cgIterNextCommon(IRInstruction* inst, bool isNextK);
|
||||
void cgIterInitCommon(IRInstruction* inst, bool isInitK);
|
||||
void cgIterNextCommon(IRInstruction* inst);
|
||||
void cgIterInitCommon(IRInstruction* inst);
|
||||
void cgLdFuncCachedCommon(IRInstruction* inst);
|
||||
TargetCache::CacheHandle cgLdClsCachedCommon(IRInstruction* inst);
|
||||
void emitFwdJcc(ConditionCode cc, Block* target);
|
||||
|
||||
@@ -892,6 +892,70 @@ void HhbcTranslator::emitIterNextK(uint32_t iterId,
|
||||
emitJmpCondHelper(offset, false, res);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitWIterInit(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId) {
|
||||
emitIterInitCommon(
|
||||
offset, [=] (SSATmp* src) {
|
||||
return gen(
|
||||
WIterInit,
|
||||
Type::Bool,
|
||||
src,
|
||||
m_tb->getFp(),
|
||||
cns(iterId),
|
||||
cns(valLocalId)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitWIterInitK(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId) {
|
||||
emitIterInitCommon(
|
||||
offset, [=] (SSATmp* src) {
|
||||
return gen(
|
||||
WIterInitK,
|
||||
Type::Bool,
|
||||
src,
|
||||
m_tb->getFp(),
|
||||
cns(iterId),
|
||||
cns(valLocalId),
|
||||
cns(keyLocalId)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitWIterNext(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId) {
|
||||
SSATmp* res = gen(
|
||||
WIterNext,
|
||||
Type::Bool,
|
||||
m_tb->getFp(),
|
||||
cns(iterId),
|
||||
cns(valLocalId)
|
||||
);
|
||||
emitJmpCondHelper(offset, false, res);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitWIterNextK(uint32_t iterId,
|
||||
int offset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId) {
|
||||
SSATmp* res = gen(
|
||||
WIterNextK,
|
||||
Type::Bool,
|
||||
m_tb->getFp(),
|
||||
cns(iterId),
|
||||
cns(valLocalId),
|
||||
cns(keyLocalId)
|
||||
);
|
||||
emitJmpCondHelper(offset, false, res);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitIterFree(uint32_t iterId) {
|
||||
gen(IterFree, m_tb->getFp(), cns(iterId));
|
||||
}
|
||||
|
||||
@@ -339,6 +339,16 @@ struct HhbcTranslator {
|
||||
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,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId);
|
||||
void emitWIterNext(uint32_t iterId, int targetOffset, uint32_t valLocalId);
|
||||
void emitWIterNextK(uint32_t iterId,
|
||||
int targetOffset,
|
||||
uint32_t valLocalId,
|
||||
uint32_t keyLocalId);
|
||||
|
||||
void emitIterFree(uint32_t iterId);
|
||||
void emitVerifyParamType(uint32_t paramId);
|
||||
@@ -412,6 +422,7 @@ private:
|
||||
void emitNotSuppNewElem();
|
||||
void emitVGetNewElem();
|
||||
void emitSetNewElem();
|
||||
void emitSetWithRefNewElem();
|
||||
void emitSetOpNewElem();
|
||||
void emitIncDecNewElem();
|
||||
void emitBindNewElem();
|
||||
@@ -427,6 +438,7 @@ private:
|
||||
SSATmp* getBase();
|
||||
SSATmp* getKey();
|
||||
SSATmp* getValue();
|
||||
SSATmp* getValAddr();
|
||||
SSATmp* checkInitProp(SSATmp* baseAsObj,
|
||||
SSATmp* propAddr,
|
||||
Transl::PropInfo propOffset,
|
||||
|
||||
@@ -458,6 +458,19 @@ O(IterNext, D(Bool), S(FramePtr) \
|
||||
C(Int) C(Int), E|N|Mem|Refs) \
|
||||
O(IterNextK, D(Bool), S(FramePtr) \
|
||||
C(Int) C(Int) C(Int), E|N|Mem|Refs) \
|
||||
O(WIterInit, D(Bool), S(Arr,Obj) \
|
||||
S(FramePtr) \
|
||||
C(Int) \
|
||||
C(Int), E|N|Mem|Refs|CRc) \
|
||||
O(WIterInitK, D(Bool), S(Arr,Obj) \
|
||||
S(FramePtr) \
|
||||
C(Int) \
|
||||
C(Int) \
|
||||
C(Int), E|N|Mem|Refs|CRc) \
|
||||
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(DefMIStateBase, D(PtrToCell), NA, NF) \
|
||||
O(BaseG, D(PtrToGen), C(TCA) \
|
||||
@@ -558,6 +571,11 @@ O_STK(SetElem, DVector, C(TCA) \
|
||||
S(PtrToGen) \
|
||||
S(Cell) \
|
||||
S(Cell), VElem|E|N|Mem|Refs|Er) \
|
||||
O_STK(SetWithRefElem, ND, C(TCA) \
|
||||
S(PtrToGen) \
|
||||
S(Cell) \
|
||||
S(PtrToGen) \
|
||||
S(PtrToCell),VElem|E|N|Mem|Refs|Er) \
|
||||
O_STK(UnsetElem, ND, C(TCA) \
|
||||
S(PtrToGen) \
|
||||
S(Cell), VElem|E|N|Mem|Refs|Er) \
|
||||
@@ -572,6 +590,10 @@ O_STK(IncDecElem, D(Cell), C(TCA) \
|
||||
S(PtrToCell),VElem|E|N|Mem|Refs|Er) \
|
||||
O_STK(SetNewElem, DVector, S(PtrToGen) \
|
||||
S(Cell), VElem|E|N|Mem|Refs|Er) \
|
||||
O_STK(SetWithRefNewElem, ND, C(TCA) \
|
||||
S(PtrToGen) \
|
||||
S(PtrToGen) \
|
||||
S(PtrToCell),VElem|E|N|Mem|Refs|Er) \
|
||||
O_STK(BindNewElem, ND, S(PtrToGen) \
|
||||
S(BoxedCell) \
|
||||
S(PtrToCell),VElem|E|N|Mem|Refs|Er) \
|
||||
|
||||
@@ -799,7 +799,6 @@ TranslatorX64::irTranslateLateBoundCls(const Tracelet&,
|
||||
HHIR_EMIT(LateBoundCls);
|
||||
}
|
||||
|
||||
|
||||
void TranslatorX64::irTranslateFPassL(const Tracelet& t,
|
||||
const NormalizedInstruction& ni) {
|
||||
if (ni.preppedByRef) {
|
||||
@@ -1403,6 +1402,46 @@ TranslatorX64::irTranslateIterNextK(const Tracelet& t,
|
||||
i.imm[3].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateWIterInit(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(WIterInit,
|
||||
i.imm[0].u_IVA,
|
||||
i.offset() + i.imm[1].u_BA,
|
||||
i.imm[2].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateWIterInitK(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(WIterInitK,
|
||||
i.imm[0].u_IVA,
|
||||
i.offset() + i.imm[1].u_BA,
|
||||
i.imm[2].u_IVA,
|
||||
i.imm[3].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateWIterNext(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
|
||||
HHIR_EMIT(WIterNext,
|
||||
i.imm[0].u_IVA,
|
||||
i.offset() + i.imm[1].u_BA,
|
||||
i.imm[2].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateWIterNextK(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
|
||||
HHIR_EMIT(WIterNextK,
|
||||
i.imm[0].u_IVA,
|
||||
i.offset() + i.imm[1].u_BA,
|
||||
i.imm[2].u_IVA,
|
||||
i.imm[3].u_IVA);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::irTranslateIterFree(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
|
||||
@@ -837,6 +837,7 @@ void LinearScan::computePreColoringHint() {
|
||||
}
|
||||
break;
|
||||
case IterInit:
|
||||
case WIterInit:
|
||||
{
|
||||
m_preColoringHint.add(inst->src(0), 0, 1);
|
||||
}
|
||||
@@ -1397,14 +1398,14 @@ void LinearScan::rematerializeAux() {
|
||||
}
|
||||
// Other instructions that may have side effects on locals must
|
||||
// kill the local variable values.
|
||||
else if (opc == IterInit) {
|
||||
else if (opc == IterInit || opc == WIterInit) {
|
||||
killLocal(inst, 3);
|
||||
} else if (opc == IterInitK) {
|
||||
} else if (opc == IterInitK || opc == WIterInitK) {
|
||||
killLocal(inst, 3);
|
||||
killLocal(inst, 4);
|
||||
} else if (opc == IterNext) {
|
||||
} else if (opc == IterNext || opc == WIterNext) {
|
||||
killLocal(inst, 2);
|
||||
} else if (opc == IterNextK) {
|
||||
} else if (opc == IterNextK || opc == WIterNextK) {
|
||||
killLocal(inst, 2);
|
||||
killLocal(inst, 3);
|
||||
}
|
||||
|
||||
@@ -221,6 +221,8 @@ static CallMap s_callMap({
|
||||
{{SSA, 1}, {VecKeyIS, 2}, {SSA, 3}}},
|
||||
{BindElem, {FSSA, 0}, DNone, SSync,
|
||||
{{SSA, 1}, {TV, 2}, {SSA, 3}, {SSA, 4}}},
|
||||
{SetWithRefElem, {FSSA, 0}, DNone, SSync,
|
||||
{{SSA, 1}, {TV, 2}, {SSA, 3}, {SSA, 4}}},
|
||||
{ArraySet, {FSSA, 0}, DSSA, SSync,
|
||||
{{SSA, 1}, {SSA, 2}, {TV, 3}}},
|
||||
{ArraySetRef, {FSSA, 0}, DSSA, SSync,
|
||||
@@ -234,6 +236,8 @@ static CallMap s_callMap({
|
||||
{IncDecElem, {FSSA, 0}, DTV, SSync,
|
||||
{{SSA, 1}, {TV, 2}, {SSA, 3}}},
|
||||
{SetNewElem, (TCA)setNewElem, DTV, SSync, {{SSA, 0}, {TV, 1}}},
|
||||
{SetWithRefNewElem, {FSSA, 0}, DNone, SSync,
|
||||
{{SSA, 1}, {SSA, 2}, {SSA, 3}}},
|
||||
{BindNewElem, (TCA)bindNewElemIR, DNone, SSync,
|
||||
{{SSA, 0}, {SSA, 1}, {SSA, 2}}},
|
||||
{ArrayIsset, {FSSA, 0}, DSSA, SSync, {{SSA, 1}, {SSA, 2}}},
|
||||
|
||||
@@ -288,23 +288,27 @@ void TraceBuilder::updateTrackedState(IRInstruction* inst) {
|
||||
break;
|
||||
|
||||
case IterInitK:
|
||||
case WIterInitK:
|
||||
// kill the locals to which this instruction stores iter's key and value
|
||||
killLocalValue(inst->src(3)->getValInt());
|
||||
killLocalValue(inst->src(4)->getValInt());
|
||||
break;
|
||||
|
||||
case IterInit:
|
||||
case WIterInit:
|
||||
// kill the local to which this instruction stores iter's value
|
||||
killLocalValue(inst->src(3)->getValInt());
|
||||
break;
|
||||
|
||||
case IterNextK:
|
||||
case WIterNextK:
|
||||
// kill the locals to which this instruction stores iter's key and value
|
||||
killLocalValue(inst->src(2)->getValInt());
|
||||
killLocalValue(inst->src(3)->getValInt());
|
||||
break;
|
||||
|
||||
case IterNext:
|
||||
case WIterNext:
|
||||
// kill the local to which this instruction stores iter's value
|
||||
killLocalValue(inst->src(2)->getValInt());
|
||||
break;
|
||||
|
||||
@@ -84,6 +84,12 @@ Opcode canonicalOp(Opcode op) {
|
||||
op == UnsetElem || op == UnsetElemStk) {
|
||||
return UnsetElem;
|
||||
}
|
||||
if (op == SetWithRefElem ||
|
||||
op == SetWithRefElemStk ||
|
||||
op == SetWithRefNewElem ||
|
||||
op == SetWithRefNewElemStk) {
|
||||
return SetWithRefElem;
|
||||
}
|
||||
return opcodeHasFlags(op, VectorProp) ? SetProp
|
||||
: opcodeHasFlags(op, VectorElem) || op == ArraySet ? SetElem
|
||||
: bad_value<Opcode>();
|
||||
@@ -132,7 +138,7 @@ void VectorEffects::init(Opcode op, const Type origBase,
|
||||
valType = origVal;
|
||||
baseTypeChanged = baseValChanged = valTypeChanged = false;
|
||||
|
||||
// Canonicalize the op to SetProp or SetElem
|
||||
// Canonicalize the op to SetProp/SetElem/UnsetElem/SetWithRefElem
|
||||
op = canonicalOp(op);
|
||||
|
||||
// We're not expecting types other than specific known data types
|
||||
@@ -141,9 +147,13 @@ void VectorEffects::init(Opcode op, const Type origBase,
|
||||
// since this shouldn't actually happen.)
|
||||
assert(key.equals(Type::None) || key.isKnownDataType() ||
|
||||
key.equals(Type::Cell));
|
||||
assert(origVal.equals(Type::None) || origVal.isKnownDataType());
|
||||
if (op == SetWithRefElem) {
|
||||
assert(origVal.isPtr());
|
||||
} else {
|
||||
assert(origVal.equals(Type::None) || origVal.isKnownDataType());
|
||||
}
|
||||
|
||||
if ((op == SetElem || op == SetProp) &&
|
||||
if ((op == SetElem || op == SetProp || op == SetWithRefElem) &&
|
||||
baseType.maybe(Type::Null | Type::Bool | Type::Str)) {
|
||||
// stdClass or array promotion might happen
|
||||
auto newBase = op == SetElem ? Type::Arr : Type::Obj;
|
||||
@@ -163,17 +173,19 @@ void VectorEffects::init(Opcode op, const Type origBase,
|
||||
}
|
||||
baseValChanged = !baseBoxed;
|
||||
}
|
||||
if ((op == SetElem || op == UnsetElem) && baseType.maybe(Type::Arr)) {
|
||||
// possible COW when modifying an array, unless the base was boxed. If the
|
||||
// base is a box then the value of the box itself won't change, just what
|
||||
// it points to.
|
||||
baseValChanged = !baseBoxed;
|
||||
}
|
||||
if ((op == SetElem || op == UnsetElem) && baseType.maybe(Type::StaticArr)) {
|
||||
// the base might get promoted to a CountedArr. We can ignore the change if
|
||||
// the base is boxed, (for the same reasons as above).
|
||||
baseType = baseType | Type::CountedArr;
|
||||
baseValChanged = !baseBoxed;
|
||||
if (op == SetElem || op == UnsetElem || op == SetWithRefElem) {
|
||||
if (baseType.maybe(Type::Arr)) {
|
||||
// possible COW when modifying an array, unless the base was boxed. If the
|
||||
// base is a box then the value of the box itself won't change, just what
|
||||
// it points to.
|
||||
baseValChanged = !baseBoxed;
|
||||
}
|
||||
if (baseType.maybe(Type::StaticArr)) {
|
||||
// the base might get promoted to a CountedArr. We can ignore the change
|
||||
// if the base is boxed, (for the same reasons as above).
|
||||
baseType = baseType | Type::CountedArr;
|
||||
baseValChanged = !baseBoxed;
|
||||
}
|
||||
}
|
||||
|
||||
// Setting an element with a base of one of these types will either succeed
|
||||
@@ -233,6 +245,7 @@ int vectorBaseIdx(const IRInstruction* inst) {
|
||||
// vectorKeyIdx returns the src index for inst's key operand.
|
||||
int vectorKeyIdx(Opcode opc) {
|
||||
return opc == SetNewElem || opc == SetNewElemStk ? -1
|
||||
: opc == SetWithRefNewElem || opc == SetWithRefNewElemStk ? -1
|
||||
: opc == BindNewElem || opc == BindNewElem ? -1
|
||||
: opc == ArraySet ? 2
|
||||
: opc == SetOpProp || opc == SetOpPropStk ? 2
|
||||
@@ -259,6 +272,7 @@ int vectorValIdx(Opcode opc) {
|
||||
|
||||
case ArraySet: return 3;
|
||||
case SetNewElem: case SetNewElemStk: return 1;
|
||||
case SetWithRefNewElem: case SetWithRefNewElemStk: return 2;
|
||||
case BindNewElem: case BindNewElemStk: return 1;
|
||||
case SetOpProp: case SetOpPropStk: return 3;
|
||||
|
||||
@@ -454,7 +468,9 @@ void HhbcTranslator::VectorTranslator::numberStackInputs() {
|
||||
// higher offsets in the stack. m_mii.valCount() tells us how many
|
||||
// rvals the instruction takes on the stack; they're pushed after
|
||||
// any vector elements and we want to ignore them here.
|
||||
int stackIdx = m_mii.valCount() + m_ni.immVec.numStackValues() - 1;
|
||||
bool stackRhs = m_mii.valCount() &&
|
||||
m_ni.inputs[0]->location.space == Location::Stack;
|
||||
int stackIdx = (int)stackRhs + m_ni.immVec.numStackValues() - 1;
|
||||
for (unsigned i = m_mii.valCount(); i < m_ni.inputs.size(); ++i) {
|
||||
const Location& l = m_ni.inputs[i]->location;
|
||||
switch (l.space) {
|
||||
@@ -467,9 +483,9 @@ void HhbcTranslator::VectorTranslator::numberStackInputs() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(stackIdx == (m_mii.valCount() - 1));
|
||||
assert(stackIdx == (stackRhs ? 0 : -1));
|
||||
|
||||
if (m_mii.valCount()) {
|
||||
if (stackRhs) {
|
||||
// If this instruction does have an RHS, it will be input 0 at
|
||||
// stack offset 0.
|
||||
assert(m_mii.valCount() == 1);
|
||||
@@ -499,6 +515,20 @@ SSATmp* HhbcTranslator::VectorTranslator::getValue() {
|
||||
return getInput(kValIdx);
|
||||
}
|
||||
|
||||
SSATmp* HhbcTranslator::VectorTranslator::getValAddr() {
|
||||
assert(m_mii.valCount() == 1);
|
||||
const DynLocation& dl = *m_ni.inputs[0];
|
||||
const Location& l = dl.location;
|
||||
if (l.space == Location::Local) {
|
||||
assert(!mapContains(m_stackInputs, 0));
|
||||
return m_ht.ldLocAddr(l.offset);
|
||||
} else {
|
||||
assert(l.space == Location::Stack);
|
||||
assert(mapContains(m_stackInputs, 0));
|
||||
return m_ht.ldStackAddr(m_stackInputs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
SSATmp* HhbcTranslator::VectorTranslator::getInput(unsigned i) {
|
||||
const DynLocation& dl = *m_ni.inputs[i];
|
||||
const Location& l = dl.location;
|
||||
@@ -1779,6 +1809,65 @@ void HhbcTranslator::VectorTranslator::emitArraySet(SSATmp* key,
|
||||
}
|
||||
#undef HELPER_TABLE
|
||||
|
||||
namespace VectorHelpers {
|
||||
void setWithRefElemC(TypedValue* base, TypedValue keyVal, TypedValue* val,
|
||||
MInstrState* mis) {
|
||||
base = HPHP::ElemD<false, false>(mis->tvScratch, mis->tvRef, base, &keyVal);
|
||||
if (!(base == &mis->tvScratch && base->m_type == KindOfUninit)) {
|
||||
tvDup(val, base);
|
||||
}
|
||||
}
|
||||
|
||||
void setWithRefNewElem(TypedValue* base, TypedValue* val,
|
||||
MInstrState* mis) {
|
||||
base = NewElem(mis->tvScratch, mis->tvRef, base);
|
||||
if (!(base == &mis->tvScratch && base->m_type == KindOfUninit)) {
|
||||
tvDup(val, base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HhbcTranslator::VectorTranslator::emitSetWithRefLElem() {
|
||||
SSATmp* key = getKey();
|
||||
SSATmp* locAddr = getValAddr();
|
||||
m_ht.exceptionBarrier();
|
||||
if (m_base->type().strip().subtypeOf(Type::Arr) &&
|
||||
!locAddr->type().deref().maybeBoxed()) {
|
||||
emitSetElem();
|
||||
m_predictedResult = nullptr;
|
||||
} else {
|
||||
genStk(SetWithRefElem, cns((TCA)VectorHelpers::setWithRefElemC),
|
||||
m_base, key, locAddr, genMisPtr());
|
||||
}
|
||||
m_result = nullptr;
|
||||
}
|
||||
|
||||
void HhbcTranslator::VectorTranslator::emitSetWithRefLProp() {
|
||||
SPUNT(__func__);
|
||||
}
|
||||
|
||||
void HhbcTranslator::VectorTranslator::emitSetWithRefRElem() {
|
||||
emitSetWithRefLElem();
|
||||
}
|
||||
|
||||
void HhbcTranslator::VectorTranslator::emitSetWithRefRProp() {
|
||||
emitSetWithRefLProp();
|
||||
}
|
||||
|
||||
void HhbcTranslator::VectorTranslator::emitSetWithRefNewElem() {
|
||||
SSATmp* locAddr = getValAddr();
|
||||
m_ht.exceptionBarrier();
|
||||
if (m_base->type().strip().subtypeOf(Type::Arr) &&
|
||||
!locAddr->type().deref().maybeBoxed()) {
|
||||
emitSetNewElem();
|
||||
m_predictedResult = nullptr;
|
||||
} else {
|
||||
genStk(SetWithRefNewElem, cns((TCA)VectorHelpers::setWithRefNewElem),
|
||||
m_base, locAddr, genMisPtr());
|
||||
}
|
||||
m_result = nullptr;
|
||||
}
|
||||
|
||||
template <KeyType keyType>
|
||||
static inline TypedValue setElemImpl(TypedValue* base, TypedValue keyVal,
|
||||
Cell val) {
|
||||
@@ -2029,7 +2118,9 @@ void HhbcTranslator::VectorTranslator::emitMPost() {
|
||||
if (m_result) {
|
||||
m_ht.push(usePredictedResult() ? m_predictedResult : m_result);
|
||||
} else {
|
||||
assert(m_ni.mInstrOp() == OpUnsetM);
|
||||
assert(m_ni.mInstrOp() == OpUnsetM ||
|
||||
m_ni.mInstrOp() == OpSetWithRefLM ||
|
||||
m_ni.mInstrOp() == OpSetWithRefRM);
|
||||
}
|
||||
|
||||
// Clean up tvRef(2)
|
||||
|
||||
@@ -10398,6 +10398,18 @@ TranslatorX64::analyzeIterInitK(Tracelet& t, NormalizedInstruction& ni) {
|
||||
ni.m_txFlags = supportedPlan(inType == KindOfArray || inType == KindOfObject);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::analyzeWIterInit(Tracelet& t, NormalizedInstruction& ni) {
|
||||
DataType inType = ni.inputs[0]->valueType();
|
||||
ni.m_txFlags = supportedPlan(inType == KindOfArray || inType == KindOfObject);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::analyzeWIterInitK(Tracelet& t, NormalizedInstruction& ni) {
|
||||
DataType inType = ni.inputs[0]->valueType();
|
||||
ni.m_txFlags = supportedPlan(inType == KindOfArray || inType == KindOfObject);
|
||||
}
|
||||
|
||||
void TranslatorX64::translateBasicIterInit(const Tracelet& t,
|
||||
const NormalizedInstruction& ni) {
|
||||
const int kValIdx = 0;
|
||||
@@ -10416,11 +10428,15 @@ void TranslatorX64::translateBasicIterInit(const Tracelet& t,
|
||||
TypedValue *val = nullptr;
|
||||
TypedValue *key = nullptr;
|
||||
new_iter_array(dest, arr, val);
|
||||
new_iter_array_key(dest, arr, val, key);
|
||||
new_iter_array_key<false>(dest, arr, val, key);
|
||||
new_iter_array_key<true>(dest, arr, val, key);
|
||||
}
|
||||
if (ni.outLocal2) {
|
||||
EMIT_RCALL(a, ni, new_iter_array_key, A(iterLoc), R(src),
|
||||
A(ni.outLocal->location), A(ni.outLocal2->location));
|
||||
if (ni.outLocal2 || ni.op() == OpWIterInit) {
|
||||
EMIT_RCALL(a, ni,
|
||||
ni.op() == OpIterInitK ?
|
||||
(TCA)new_iter_array_key<false> : (TCA)new_iter_array_key<true>,
|
||||
A(iterLoc), R(src), A(ni.outLocal->location),
|
||||
ni.outLocal2 ? A(ni.outLocal2->location) : IMM(0));
|
||||
} else {
|
||||
EMIT_RCALL(a, ni, new_iter_array, A(iterLoc), R(src),
|
||||
A(ni.outLocal->location));
|
||||
@@ -10473,6 +10489,22 @@ void TranslatorX64::translateIterInitK(const Tracelet& t,
|
||||
translateBasicIterInit(t, ni);
|
||||
}
|
||||
|
||||
void TranslatorX64::translateWIterInit(const Tracelet& t,
|
||||
const NormalizedInstruction& ni) {
|
||||
assert(ni.inputs.size() == 1);
|
||||
assert(ni.outLocal);
|
||||
assert(!ni.outStack && !ni.outLocal2);
|
||||
translateBasicIterInit(t, ni);
|
||||
}
|
||||
|
||||
void TranslatorX64::translateWIterInitK(const Tracelet& t,
|
||||
const NormalizedInstruction& ni) {
|
||||
assert(ni.inputs.size() == 1);
|
||||
assert(ni.outLocal && ni.outLocal2);
|
||||
assert(!ni.outStack);
|
||||
translateBasicIterInit(t, ni);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::analyzeIterNext(Tracelet& t, NormalizedInstruction& i) {
|
||||
assert(i.inputs.size() == 0);
|
||||
@@ -10485,6 +10517,17 @@ TranslatorX64::analyzeIterNextK(Tracelet& t, NormalizedInstruction& i) {
|
||||
i.m_txFlags = Supported;
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::analyzeWIterNext(Tracelet& t, NormalizedInstruction& i) {
|
||||
i.m_txFlags = Supported;
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::analyzeWIterNextK(Tracelet& t, NormalizedInstruction& i) {
|
||||
i.m_txFlags = Supported;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TranslatorX64::translateBasicIterNext(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
@@ -10493,19 +10536,23 @@ TranslatorX64::translateBasicIterNext(const Tracelet& t,
|
||||
TypedValue* val = nullptr;
|
||||
TypedValue* key = nullptr;
|
||||
int64_t ret = iter_next(it, val);
|
||||
ret = iter_next_key(it, val, key);
|
||||
ret = iter_next_key<false>(it, val, key);
|
||||
ret = iter_next_key<true>(it, val, key);
|
||||
if (ret) printf("\n");
|
||||
}
|
||||
m_regMap.cleanAll(); // input might be in-flight
|
||||
// If the iterator reaches the end, iter_next will handle
|
||||
// freeing the iterator and it will decRef the array
|
||||
Location iterLoc(Location::Iter, i.imm[0].u_IVA);
|
||||
if (i.outLocal2) {
|
||||
EMIT_CALL(a, iter_next_key, A(iterLoc),
|
||||
A(i.outLocal->location), A(i.outLocal2->location));
|
||||
if (i.outLocal2 || i.op() == OpWIterNext) {
|
||||
EMIT_CALL(a,
|
||||
i.op() == OpIterNextK ?
|
||||
(TCA)iter_next_key<false> : (TCA)iter_next_key<true>,
|
||||
A(iterLoc),
|
||||
A(i.outLocal->location),
|
||||
i.op() == OpWIterNext ? IMM(0) : A(i.outLocal2->location));
|
||||
} else {
|
||||
EMIT_CALL(a, iter_next, A(iterLoc),
|
||||
A(i.outLocal->location));
|
||||
EMIT_CALL(a, iter_next, A(iterLoc), A(i.outLocal->location));
|
||||
}
|
||||
recordReentrantCall(a, i);
|
||||
ScratchReg raxScratch(m_regMap, rax);
|
||||
@@ -10538,6 +10585,24 @@ TranslatorX64::translateIterNextK(const Tracelet& t,
|
||||
translateBasicIterNext(t, i);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::translateWIterNext(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
assert(i.inputs.size() == 0);
|
||||
assert(!i.outStack && !i.outLocal2);
|
||||
assert(i.outLocal);
|
||||
translateBasicIterNext(t, i);
|
||||
}
|
||||
|
||||
void
|
||||
TranslatorX64::translateWIterNextK(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
assert(i.inputs.size() == 0);
|
||||
assert(!i.outStack);
|
||||
assert(i.outLocal && i.outLocal2);
|
||||
translateBasicIterNext(t, i);
|
||||
}
|
||||
|
||||
// PSEUDOINSTR_DISPATCH is a switch() fragment that routes opcodes to their
|
||||
// shared handlers, as per the PSEUDOINSTRS macro.
|
||||
#define PSEUDOINSTR_DISPATCH(func) \
|
||||
@@ -11866,6 +11931,8 @@ bool TranslatorX64::dumpTCData() {
|
||||
* Always-interp instructions,
|
||||
*/ \
|
||||
INTERP_OP(ContHandle) \
|
||||
INTERP_OP(SetWithRefLM) \
|
||||
INTERP_OP(SetWithRefRM) \
|
||||
INTERP_OP(ArrayIdx) \
|
||||
INTERP_OP(CGetM) \
|
||||
INTERP_OP(FPassM) \
|
||||
|
||||
@@ -490,6 +490,8 @@ private:
|
||||
CASE(SetS) \
|
||||
CASE(SetG) \
|
||||
CASE(SetM) \
|
||||
CASE(SetWithRefLM) \
|
||||
CASE(SetWithRefRM) \
|
||||
CASE(SetOpL) \
|
||||
CASE(SetOpM) \
|
||||
CASE(IncDecL) \
|
||||
@@ -523,6 +525,10 @@ private:
|
||||
CASE(IterInitK) \
|
||||
CASE(IterNext) \
|
||||
CASE(IterNextK) \
|
||||
CASE(WIterInit) \
|
||||
CASE(WIterInitK) \
|
||||
CASE(WIterNext) \
|
||||
CASE(WIterNextK) \
|
||||
CASE(ReqDoc) \
|
||||
CASE(DefCls) \
|
||||
CASE(DefFunc) \
|
||||
|
||||
@@ -1242,6 +1242,8 @@ static const struct {
|
||||
{ OpSetG, {StackTop2, Stack1|Local, OutSameAsInput, -1 }},
|
||||
{ OpSetS, {StackTop3, Stack1, OutSameAsInput, -2 }},
|
||||
{ OpSetM, {MVector|Stack1, Stack1|Local, OutSameAsInput, 0 }},
|
||||
{ OpSetWithRefLM,{MVector|Local , Local, OutNone, 0 }},
|
||||
{ OpSetWithRefRM,{MVector|Stack1, Local, OutNone, -1 }},
|
||||
{ OpSetOpL, {Stack1|Local, Stack1|Local, OutSetOp, 0 }},
|
||||
{ OpSetOpN, {StackTop2, Stack1|Local, OutUnknown, -1 }},
|
||||
{ OpSetOpG, {StackTop2, Stack1|Local, OutUnknown, -1 }},
|
||||
@@ -1328,12 +1330,16 @@ static const struct {
|
||||
|
||||
{ OpIterInit, {Stack1, Local, OutUnknown, -1 }},
|
||||
{ OpMIterInit, {Stack1, Local, OutUnknown, -1 }},
|
||||
{ OpWIterInit, {Stack1, Local, OutUnknown, -1 }},
|
||||
{ OpIterInitK, {Stack1, Local, OutUnknown, -1 }},
|
||||
{ OpMIterInitK, {Stack1, Local, OutUnknown, -1 }},
|
||||
{ OpWIterInitK, {Stack1, Local, OutUnknown, -1 }},
|
||||
{ OpIterNext, {None, Local, OutUnknown, 0 }},
|
||||
{ OpMIterNext, {None, Local, OutUnknown, 0 }},
|
||||
{ OpWIterNext, {None, Local, OutUnknown, 0 }},
|
||||
{ OpIterNextK, {None, Local, OutUnknown, 0 }},
|
||||
{ OpMIterNextK, {None, Local, OutUnknown, 0 }},
|
||||
{ OpWIterNextK, {None, Local, OutUnknown, 0 }},
|
||||
{ OpIterFree, {None, None, OutNone, 0 }},
|
||||
{ OpMIterFree, {None, None, OutNone, 0 }},
|
||||
|
||||
@@ -2137,6 +2143,7 @@ void Translator::getInputs(Tracelet& t,
|
||||
// instructions that take a Local have its index at their first
|
||||
// immediate.
|
||||
int loc;
|
||||
auto insertAt = inputs.end();
|
||||
switch (ni->op()) {
|
||||
case OpUnpackCont:
|
||||
case OpPackCont:
|
||||
@@ -2147,6 +2154,9 @@ void Translator::getInputs(Tracelet& t,
|
||||
loc = 0;
|
||||
break;
|
||||
|
||||
case OpSetWithRefLM:
|
||||
insertAt = inputs.begin();
|
||||
// fallthrough
|
||||
case OpFPassL:
|
||||
loc = ni->imm[1].u_IVA;
|
||||
break;
|
||||
@@ -2156,7 +2166,7 @@ void Translator::getInputs(Tracelet& t,
|
||||
break;
|
||||
}
|
||||
SKTRACE(1, sk, "getInputs: local %d\n", loc);
|
||||
inputs.emplace_back(Location(Location::Local, loc));
|
||||
inputs.emplace(insertAt, Location(Location::Local, loc));
|
||||
if (input & DontGuardLocal) inputs.back().dontGuard = true;
|
||||
if (input & DontBreakLocal) inputs.back().dontBreak = true;
|
||||
}
|
||||
@@ -2360,6 +2370,7 @@ void Translator::getOutputs(/*inout*/ Tracelet& t,
|
||||
ASSERT_NOT_IMPLEMENTED(op == OpSetOpL ||
|
||||
op == OpSetM || op == OpSetOpM ||
|
||||
op == OpBindM ||
|
||||
op == OpSetWithRefLM || op == OpSetWithRefRM ||
|
||||
op == OpIncDecL || op == OpIncDecG ||
|
||||
op == OpUnsetG || op == OpBindG ||
|
||||
op == OpSetG || op == OpSetOpG ||
|
||||
@@ -2369,8 +2380,10 @@ void Translator::getOutputs(/*inout*/ Tracelet& t,
|
||||
op == OpUnsetL ||
|
||||
op == OpIterInit || op == OpIterInitK ||
|
||||
op == OpMIterInit || op == OpMIterInitK ||
|
||||
op == OpWIterInit || op == OpWIterInitK ||
|
||||
op == OpIterNext || op == OpIterNextK ||
|
||||
op == OpMIterNext || op == OpMIterNextK);
|
||||
op == OpMIterNext || op == OpMIterNextK ||
|
||||
op == OpWIterNext || op == OpWIterNextK);
|
||||
if (op == OpIncDecL) {
|
||||
assert(ni->inputs.size() == 1);
|
||||
const RuntimeType &inRtt = ni->inputs[0]->rtt;
|
||||
@@ -2407,49 +2420,62 @@ void Translator::getOutputs(/*inout*/ Tracelet& t,
|
||||
KindOfInvalid);
|
||||
continue;
|
||||
}
|
||||
if (op == OpSetM || op == OpSetOpM || op == OpVGetM || op == OpBindM) {
|
||||
// TODO(#1069330): This code assumes that the location is
|
||||
// LH. We need to figure out how to handle cases where the
|
||||
// location is LN or LG or LR.
|
||||
// XXX: analogous garbage needed for OpSetOpM.
|
||||
if (ni->immVec.locationCode() == LL) {
|
||||
const int kVecStart = (op == OpSetM ||
|
||||
op == OpSetOpM ||
|
||||
op == OpBindM) ?
|
||||
1 : 0; // 0 is rhs for SetM/SetOpM
|
||||
DynLocation* inLoc = ni->inputs[kVecStart];
|
||||
assert(inLoc->location.isLocal());
|
||||
Location locLoc = inLoc->location;
|
||||
if (inLoc->rtt.isString() ||
|
||||
inLoc->rtt.valueType() == KindOfBoolean) {
|
||||
// Strings and bools produce value-dependent results; "" and
|
||||
// false upgrade to an array successfully, while other values
|
||||
// fail and leave the lhs unmodified.
|
||||
DynLocation* baseLoc = t.newDynLocation(locLoc, KindOfInvalid);
|
||||
assert(baseLoc->isLocal());
|
||||
ni->outLocal = baseLoc;
|
||||
} else if (inLoc->rtt.valueType() == KindOfUninit ||
|
||||
inLoc->rtt.valueType() == KindOfNull) {
|
||||
RuntimeType newLhsRtt = inLoc->rtt.setValueType(
|
||||
mcodeMaybePropName(ni->immVecM[0]) ?
|
||||
KindOfObject : KindOfArray);
|
||||
SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
|
||||
locLoc.spaceName(), locLoc.offset, newLhsRtt.valueType());
|
||||
DynLocation* baseLoc = t.newDynLocation(locLoc, newLhsRtt);
|
||||
assert(baseLoc->location.isLocal());
|
||||
ni->outLocal = baseLoc;
|
||||
if (op == OpSetM || op == OpSetOpM ||
|
||||
op == OpVGetM || op == OpBindM ||
|
||||
op == OpSetWithRefLM || op == OpSetWithRefRM) {
|
||||
switch (ni->immVec.locationCode()) {
|
||||
case LL: {
|
||||
const int kVecStart = (op == OpSetM ||
|
||||
op == OpSetOpM ||
|
||||
op == OpBindM ||
|
||||
op == OpSetWithRefLM ||
|
||||
op == OpSetWithRefRM) ?
|
||||
1 : 0; // 0 is rhs for SetM/SetOpM
|
||||
DynLocation* inLoc = ni->inputs[kVecStart];
|
||||
assert(inLoc->location.isLocal());
|
||||
Location locLoc = inLoc->location;
|
||||
if (inLoc->rtt.isString() ||
|
||||
inLoc->rtt.valueType() == KindOfBoolean) {
|
||||
// Strings and bools produce value-dependent results; "" and
|
||||
// false upgrade to an array successfully, while other values
|
||||
// fail and leave the lhs unmodified.
|
||||
DynLocation* baseLoc = t.newDynLocation(locLoc, KindOfInvalid);
|
||||
assert(baseLoc->isLocal());
|
||||
ni->outLocal = baseLoc;
|
||||
} else if (inLoc->rtt.valueType() == KindOfUninit ||
|
||||
inLoc->rtt.valueType() == KindOfNull) {
|
||||
RuntimeType newLhsRtt = inLoc->rtt.setValueType(
|
||||
mcodeMaybePropName(ni->immVecM[0]) ?
|
||||
KindOfObject : KindOfArray);
|
||||
SKTRACE(2, ni->source, "(%s, %d) <- type %d\n",
|
||||
locLoc.spaceName(), locLoc.offset,
|
||||
newLhsRtt.valueType());
|
||||
DynLocation* baseLoc = t.newDynLocation(locLoc, newLhsRtt);
|
||||
assert(baseLoc->location.isLocal());
|
||||
ni->outLocal = baseLoc;
|
||||
}
|
||||
// Note (if we start translating pseudo-mains):
|
||||
//
|
||||
// A SetM in pseudo-main might alias a local whose type we're
|
||||
// remembering:
|
||||
//
|
||||
// $GLOBALS['a'] = 123; // $a :: Int
|
||||
//
|
||||
// and more deviously:
|
||||
//
|
||||
// $loc['b'][17] = $GLOBALS; $x = 'b'; $y = 17;
|
||||
// $loc[$x][$y]['a'] = 123; // $a :: Int
|
||||
break;
|
||||
}
|
||||
// Note (if we start translating pseudo-mains):
|
||||
//
|
||||
// A SetM in pseudo-main might alias a local whose type we're
|
||||
// remembering:
|
||||
//
|
||||
// $GLOBALS['a'] = 123; // $a :: Int
|
||||
//
|
||||
// and more deviously:
|
||||
//
|
||||
// $loc['b'][17] = $GLOBALS; $x = 'b'; $y = 17;
|
||||
// $loc[$x][$y]['a'] = 123; // $a :: Int
|
||||
case LNL:
|
||||
case LNC:
|
||||
varEnvTaint = true;
|
||||
break;
|
||||
case LGL:
|
||||
case LGC:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -2470,11 +2496,13 @@ void Translator::getOutputs(/*inout*/ Tracelet& t,
|
||||
ni->outLocal = dl;
|
||||
continue;
|
||||
}
|
||||
if (op >= OpIterInit && op <= OpMIterNextK) {
|
||||
if (op >= OpIterInit && op <= OpWIterNextK) {
|
||||
assert(op == OpIterInit || op == OpIterInitK ||
|
||||
op == OpMIterInit || op == OpMIterInitK ||
|
||||
op == OpWIterInit || op == OpWIterInitK ||
|
||||
op == OpIterNext || op == OpIterNextK ||
|
||||
op == OpMIterNext || op == OpMIterNextK);
|
||||
op == OpMIterNext || op == OpMIterNextK ||
|
||||
op == OpWIterNext || op == OpWIterNextK);
|
||||
const int kValImmIdx = 2;
|
||||
const int kKeyImmIdx = 3;
|
||||
DynLocation* outVal = t.newDynLocation();
|
||||
@@ -2487,18 +2515,14 @@ void Translator::getOutputs(/*inout*/ Tracelet& t,
|
||||
outVal->rtt = RuntimeType(KindOfInvalid);
|
||||
}
|
||||
ni->outLocal = outVal;
|
||||
if (op == OpIterInitK || op == OpIterNextK) {
|
||||
if (op == OpIterInitK || op == OpIterNextK ||
|
||||
op == OpWIterInitK || op == OpWIterNextK ||
|
||||
op == OpMIterInitK || op == OpMIterNextK) {
|
||||
DynLocation* outKey = t.newDynLocation();
|
||||
int keyOff = getImm(ni->pc(), kKeyImmIdx).u_IVA;
|
||||
outKey->location = Location(Location::Local, keyOff);
|
||||
outKey->rtt = RuntimeType(KindOfInvalid);
|
||||
ni->outLocal2 = outKey;
|
||||
} else if (op == OpMIterInitK || op == OpMIterNextK) {
|
||||
DynLocation* outKey = t.newDynLocation();
|
||||
int keyOff = getImm(ni->pc(), kKeyImmIdx).u_IVA;
|
||||
outKey->location = Location(Location::Local, keyOff);
|
||||
outKey->rtt = RuntimeType(KindOfRef, KindOfInvalid);
|
||||
ni->outLocal2 = outKey;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1079,6 +1079,8 @@ opcodeControlFlowInfo(const Opcode instr) {
|
||||
case OpIterInitK: // Ditto
|
||||
case OpMIterInit: // Ditto
|
||||
case OpMIterInitK: // Ditto
|
||||
case OpWIterInit: // Ditto
|
||||
case OpWIterInitK: // Ditto
|
||||
case OpThrow:
|
||||
case OpUnwind:
|
||||
case OpEval:
|
||||
|
||||
@@ -310,7 +310,7 @@ Offset decodeOffset(PC* ppc) {
|
||||
*/
|
||||
class ImmVecRange {
|
||||
public:
|
||||
ImmVecRange(const Opcode* instr) : v(getImmVector(instr)),
|
||||
explicit ImmVecRange(const Opcode* instr) : v(getImmVector(instr)),
|
||||
vecp(v.vec() + 1), // skip location code
|
||||
loc(v.locationCode()),
|
||||
loc_local(numLocationCodeImms(loc) ? decodeVariableSizeImm(&vecp) : -1) {
|
||||
@@ -574,11 +574,13 @@ const FlavorDesc* FuncChecker::sig(PC pc) {
|
||||
#define LMANY() { },
|
||||
#define C_LMANY() { },
|
||||
#define V_LMANY() { },
|
||||
#define R_LMANY() { },
|
||||
#define O(name, imm, pop, push, flags) pop
|
||||
OPCODES
|
||||
#undef O
|
||||
#undef C_LMANY
|
||||
#undef V_LMANY
|
||||
#undef R_LMANY
|
||||
#undef LMANY
|
||||
#undef FMANY
|
||||
#undef CMANY
|
||||
@@ -602,6 +604,10 @@ const FlavorDesc* FuncChecker::sig(PC pc) {
|
||||
case OpSetM: // ONE(LA), C_LMANY(), ONE(CV)
|
||||
case OpSetOpM: // TWO(OA,LA), C_LMANY(), ONE(CV)
|
||||
return vectorSig(pc, CV);
|
||||
case OpSetWithRefLM://TWO(MA, HA), LMANY(), NOV
|
||||
return vectorSig(pc, NOV);
|
||||
case OpSetWithRefRM://ONE(MA), R_LMANY(), NOV
|
||||
return vectorSig(pc, RV);
|
||||
case OpFCall: // ONE(IVA), FMANY, ONE(RV)
|
||||
case OpFCallArray:// NA, ONE(FV), ONE(RV)
|
||||
case OpFCallBuiltin: //TWO(IVA, SA) FMANY, ONE(RV)
|
||||
@@ -700,6 +706,7 @@ bool FuncChecker::checkIter(State* cur, PC pc) {
|
||||
int id = getImmIva(pc);
|
||||
bool ok = true;
|
||||
if (Op(*pc) == OpIterInit || Op(*pc) == OpIterInitK ||
|
||||
Op(*pc) == OpWIterInit || Op(*pc) == OpWIterInitK ||
|
||||
Op(*pc) == OpMIterInit || Op(*pc) == OpMIterInitK) {
|
||||
if (cur->iters[id]) {
|
||||
verify_error(
|
||||
@@ -732,11 +739,13 @@ bool FuncChecker::checkOutputs(State* cur, PC pc, Block* b) {
|
||||
#define LMANY() { },
|
||||
#define C_LMANY() { },
|
||||
#define V_LMANY() { },
|
||||
#define R_LMANY() { },
|
||||
#define O(name, imm, pop, push, flags) push
|
||||
OPCODES
|
||||
#undef O
|
||||
#undef C_LMANY
|
||||
#undef V_LMANY
|
||||
#undef R_LMANY
|
||||
#undef LMANY
|
||||
#undef FMANY
|
||||
#undef CMANY
|
||||
@@ -911,7 +920,8 @@ bool FuncChecker::checkSuccEdges(Block* b, State* cur) {
|
||||
int id = getImmIva(b->last);
|
||||
bool taken_state =
|
||||
(Op(*b->last) == OpIterNext || Op(*b->last) == OpIterNextK ||
|
||||
Op(*b->last) == OpMIterNext || Op(*b->last) == OpMIterNextK);
|
||||
Op(*b->last) == OpMIterNext || Op(*b->last) == OpMIterNextK ||
|
||||
Op(*b->last) == OpWIterNext || Op(*b->last) == OpWIterNextK);
|
||||
bool save = cur->iters[id];
|
||||
cur->iters[id] = taken_state;
|
||||
if (m_verbose) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
require_once "support.hhas";
|
||||
|
||||
class X {
|
||||
function __destruct() { var_dump(__METHOD__); }
|
||||
static function test($x) { return $x !== true; }
|
||||
}
|
||||
|
||||
function test() {
|
||||
$a = array();
|
||||
var_dump(test2($a, "foo", new X));
|
||||
|
||||
$a = array();
|
||||
var_dump(test2($a, "foo", "bar"));
|
||||
|
||||
$a = array();
|
||||
$b = "bar";
|
||||
var_dump(test3($a, $b));
|
||||
|
||||
$a = array(1, 'foo' => 'bar', 0, true, false);
|
||||
$b =& $a['foo'];
|
||||
|
||||
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_map(function($a) { return $a.$a; }, $a));
|
||||
var_dump(fast_array_map(function&(&$a) { return $a; }, $a));
|
||||
}
|
||||
|
||||
test();
|
||||
@@ -0,0 +1,62 @@
|
||||
array(1) {
|
||||
["foo"]=>
|
||||
object(X)#1 (0) {
|
||||
}
|
||||
}
|
||||
string(13) "X::__destruct"
|
||||
array(1) {
|
||||
["foo"]=>
|
||||
string(3) "bar"
|
||||
}
|
||||
array(1) {
|
||||
[0]=>
|
||||
&string(3) "bar"
|
||||
}
|
||||
array(3) {
|
||||
[0]=>
|
||||
int(1)
|
||||
["foo"]=>
|
||||
&string(3) "bar"
|
||||
[2]=>
|
||||
bool(true)
|
||||
}
|
||||
array(2) {
|
||||
[1]=>
|
||||
int(0)
|
||||
[3]=>
|
||||
bool(false)
|
||||
}
|
||||
array(4) {
|
||||
[0]=>
|
||||
int(1)
|
||||
["foo"]=>
|
||||
&string(3) "bar"
|
||||
[1]=>
|
||||
int(0)
|
||||
[3]=>
|
||||
bool(false)
|
||||
}
|
||||
array(5) {
|
||||
[0]=>
|
||||
string(2) "11"
|
||||
["foo"]=>
|
||||
string(6) "barbar"
|
||||
[1]=>
|
||||
string(2) "00"
|
||||
[2]=>
|
||||
string(2) "11"
|
||||
[3]=>
|
||||
string(0) ""
|
||||
}
|
||||
array(5) {
|
||||
[0]=>
|
||||
int(1)
|
||||
["foo"]=>
|
||||
&string(3) "bar"
|
||||
[1]=>
|
||||
int(0)
|
||||
[2]=>
|
||||
bool(true)
|
||||
[3]=>
|
||||
bool(false)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
.main {
|
||||
Int 1
|
||||
RetC
|
||||
}
|
||||
|
||||
.function test1($arr, $k, $v) {
|
||||
SetWithRefLM <L:$arr EL:$k> $v
|
||||
CGetL $arr
|
||||
RetC
|
||||
}
|
||||
|
||||
.function test2(&$arr, $k, $v) {
|
||||
CGetL $v
|
||||
SetM <L:$arr EL:$k>
|
||||
PopC
|
||||
CGetL $arr
|
||||
RetC
|
||||
}
|
||||
|
||||
.function test3($arr, &$v) {
|
||||
SetWithRefLM <L:$arr W> $v
|
||||
CGetL $arr
|
||||
RetC
|
||||
}
|
||||
|
||||
.function fast_array_filter($arr, $func) {
|
||||
.numiters 1;
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
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
|
||||
RetC
|
||||
}
|
||||
|
||||
.function fast_array_map($func, $arr) {
|
||||
.numiters 1;
|
||||
|
||||
IssetL $func
|
||||
JmpZ ident
|
||||
|
||||
NewArray
|
||||
SetL $res
|
||||
PopC
|
||||
|
||||
CGetL $arr
|
||||
WIterInitK 0 endloop $v $k
|
||||
|
||||
loop_x: CGetL $func
|
||||
FPushCuf 1
|
||||
FPassL 0 $v
|
||||
FCall 1
|
||||
SetWithRefRM <L:$res EL:$k>
|
||||
WIterNextK 0 loop_x $v $k
|
||||
|
||||
endloop: CGetL $res
|
||||
RetC
|
||||
|
||||
ident: CGetL $arr
|
||||
RetC
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
Eval {
|
||||
AllowHhas = true
|
||||
}
|
||||
|
||||
Log {
|
||||
AlwaysLogUnhandledExceptions = false
|
||||
|
||||
+1
-1
@@ -378,7 +378,7 @@ sub run_test
|
||||
|
||||
|
||||
if ($opt_repo) {
|
||||
if ($test =~ m/\.hhas/) {
|
||||
if ($test =~ m/\.hhas/ || -e $test . ".norepo") {
|
||||
# We don't have a way to skip, I guess run non-repo?
|
||||
} else {
|
||||
my ($hphp_opts) = "";
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário