diff --git a/hphp/compiler/analysis/emitter.cpp b/hphp/compiler/analysis/emitter.cpp index 46788889c..c4a8a62e7 100644 --- a/hphp/compiler/analysis/emitter.cpp +++ b/hphp/compiler/analysis/emitter.cpp @@ -283,6 +283,7 @@ static int32_t countStackValues(const std::vector& 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& 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& 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 ); diff --git a/hphp/doc/bytecode.specification b/hphp/doc/bytecode.specification index 65d48cc96..dc34e0718 100644 --- a/hphp/doc/bytecode.specification +++ b/hphp/doc/bytecode.specification @@ -3150,6 +3150,40 @@ SetM [C..C C] -> [C] SC | BaseSC SL | BaseSL +SetWithRefLM [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 [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 [C..C C] -> [C] Set op member. @@ -3224,6 +3258,8 @@ UnsetM [C..C] -> [] IterInit [C] -> [] IterInitK [C] -> [] +WIterInit [C] -> [] +WIterInitK [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 [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 [V] -> [] IterNext [] -> [] IterNextK [] -> [] +WIterNext [] -> [] +WIterNextK [] -> [] 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 [] -> [] 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. diff --git a/hphp/doc/ir.specification b/hphp/doc/ir.specification index a4e853cbd..2f6990883 100644 --- a/hphp/doc/ir.specification +++ b/hphp/doc/ir.specification @@ -1549,6 +1549,8 @@ DbgAssertType 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 diff --git a/hphp/runtime/base/array/array_iterator.cpp b/hphp/runtime/base/array/array_iterator.cpp index 1a2f4e2c0..5bca51244 100644 --- a/hphp/runtime/base/array/array_iterator.cpp +++ b/hphp/runtime/base/array/array_iterator.cpp @@ -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 +template 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 +template 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 +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(dest, valOut); + iter_value_cell_local_impl(dest, valOut); if (keyOut) { - iter_key_cell_local_impl(dest, keyOut); + iter_key_cell_local_impl(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 +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(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(dest, ad, valOut, nullptr); } +template 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(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(dest, ad, valOut, keyOut); } +template int64_t new_iter_array_key(Iter* dest, ArrayData* ad, + TypedValue* valOut, + TypedValue* keyOut); +template int64_t new_iter_array_key(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(dest, valOut); + iter_value_cell_local_impl(dest, valOut); if (keyOut) { - iter_key_cell_local_impl(dest, keyOut); + iter_key_cell_local_impl(dest, keyOut); } } else { - iter_value_cell_local_impl(dest, valOut); + iter_value_cell_local_impl(dest, valOut); if (keyOut) { - iter_key_cell_local_impl(dest, keyOut); + iter_key_cell_local_impl(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 +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(iter, valOut); + iter_value_cell_local_impl(iter, valOut); if (keyOut) { - iter_key_cell_local_impl(iter, keyOut); + iter_key_cell_local_impl(iter, keyOut); } } else { - iter_value_cell_local_impl(iter, valOut); + iter_value_cell_local_impl(iter, valOut); if (keyOut) { - iter_key_cell_local_impl(iter, keyOut); + iter_key_cell_local_impl(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(elm, valOut, nullptr); return 1; } cold: - return iter_next_cold(iter, valOut, nullptr); + return iter_next_cold(iter, valOut, nullptr); } +template 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(elm, valOut, keyOut); return 1; } -cold: - return iter_next_cold(iter, valOut, keyOut); + cold: + return iter_next_cold(iter, valOut, keyOut); } +template int64_t iter_next_key(Iter* dest, + TypedValue* valOut, + TypedValue* keyOut); +template int64_t iter_next_key(Iter* dest, + TypedValue* valOut, + TypedValue* keyOut); + /////////////////////////////////////////////////////////////////////////////// } diff --git a/hphp/runtime/base/array/array_iterator.h b/hphp/runtime/base/array/array_iterator.h index b3b3ef954..eaf4464be 100644 --- a/hphp/runtime/base/array/array_iterator.h +++ b/hphp/runtime/base/array/array_iterator.h @@ -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 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 int64_t iter_next_key(Iter* dest, TypedValue* val, TypedValue* key); /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/eval/runtime/file_repository.cpp b/hphp/runtime/eval/runtime/file_repository.cpp index 7379ec17e..e4589f119 100644 --- a/hphp/runtime/eval/runtime/file_repository.cpp +++ b/hphp/runtime/eval/runtime/file_repository.cpp @@ -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); diff --git a/hphp/runtime/vm/as.cpp b/hphp/runtime/vm/as.cpp index a2c751de0..c6cf5942e 100644 --- a/hphp/runtime/vm/as.cpp +++ b/hphp/runtime/vm/as.cpp @@ -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 diff --git a/hphp/runtime/vm/bytecode.cpp b/hphp/runtime/vm/bytecode.cpp index 18d4baf62..a74f90cf0 100644 --- a/hphp/runtime/vm/bytecode.cpp +++ b/hphp/runtime/vm/bytecode.cpp @@ -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(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(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(); diff --git a/hphp/runtime/vm/hhbc.cpp b/hphp/runtime/vm/hhbc.cpp index 0830ec47d..cf2db1056 100644 --- a/hphp/runtime/vm/hhbc.cpp +++ b/hphp/runtime/vm/hhbc.cpp @@ -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 @@ -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 diff --git a/hphp/runtime/vm/hhbc.h b/hphp/runtime/vm/hhbc.h index 9cf2b1c5d..f96247269 100644 --- a/hphp/runtime/vm/hhbc.h +++ b/hphp/runtime/vm/hhbc.h @@ -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) \ diff --git a/hphp/runtime/vm/translator/hopt/codegen.cpp b/hphp/runtime/vm/translator/hopt/codegen.cpp index 42c7eb0a2..28df0a308 100644 --- a/hphp/runtime/vm/translator/hopt/codegen.cpp +++ b/hphp/runtime/vm/translator/hopt/codegen.cpp @@ -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 : + isInitK ? (TCA)new_iter_array_key : (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 : + isNextK ? (TCA)iter_next_key : (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()), diff --git a/hphp/runtime/vm/translator/hopt/codegen.h b/hphp/runtime/vm/translator/hopt/codegen.h index 3a96dbeab..d0f0bafc6 100644 --- a/hphp/runtime/vm/translator/hopt/codegen.h +++ b/hphp/runtime/vm/translator/hopt/codegen.h @@ -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); diff --git a/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp b/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp index 09865bc78..06030ffa9 100644 --- a/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/hhbctranslator.cpp @@ -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)); } diff --git a/hphp/runtime/vm/translator/hopt/hhbctranslator.h b/hphp/runtime/vm/translator/hopt/hhbctranslator.h index 5bfac0c77..a9f9a8acd 100644 --- a/hphp/runtime/vm/translator/hopt/hhbctranslator.h +++ b/hphp/runtime/vm/translator/hopt/hhbctranslator.h @@ -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, diff --git a/hphp/runtime/vm/translator/hopt/ir.h b/hphp/runtime/vm/translator/hopt/ir.h index 68877a0dc..23f9a8861 100644 --- a/hphp/runtime/vm/translator/hopt/ir.h +++ b/hphp/runtime/vm/translator/hopt/ir.h @@ -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) \ diff --git a/hphp/runtime/vm/translator/hopt/irtranslator.cpp b/hphp/runtime/vm/translator/hopt/irtranslator.cpp index f96e9d3ae..d3b236364 100644 --- a/hphp/runtime/vm/translator/hopt/irtranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/irtranslator.cpp @@ -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) { diff --git a/hphp/runtime/vm/translator/hopt/linearscan.cpp b/hphp/runtime/vm/translator/hopt/linearscan.cpp index 4fe66a4f6..6208998c4 100644 --- a/hphp/runtime/vm/translator/hopt/linearscan.cpp +++ b/hphp/runtime/vm/translator/hopt/linearscan.cpp @@ -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); } diff --git a/hphp/runtime/vm/translator/hopt/nativecalls.cpp b/hphp/runtime/vm/translator/hopt/nativecalls.cpp index c8bef0371..65e302ffe 100644 --- a/hphp/runtime/vm/translator/hopt/nativecalls.cpp +++ b/hphp/runtime/vm/translator/hopt/nativecalls.cpp @@ -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}}}, diff --git a/hphp/runtime/vm/translator/hopt/tracebuilder.cpp b/hphp/runtime/vm/translator/hopt/tracebuilder.cpp index 6ce620bf1..5cde31480 100644 --- a/hphp/runtime/vm/translator/hopt/tracebuilder.cpp +++ b/hphp/runtime/vm/translator/hopt/tracebuilder.cpp @@ -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; diff --git a/hphp/runtime/vm/translator/hopt/vectortranslator.cpp b/hphp/runtime/vm/translator/hopt/vectortranslator.cpp index f2e3fb6f6..93e51c8d3 100644 --- a/hphp/runtime/vm/translator/hopt/vectortranslator.cpp +++ b/hphp/runtime/vm/translator/hopt/vectortranslator.cpp @@ -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(); @@ -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(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 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) diff --git a/hphp/runtime/vm/translator/translator-x64.cpp b/hphp/runtime/vm/translator/translator-x64.cpp index 79e69534c..0aceba690 100644 --- a/hphp/runtime/vm/translator/translator-x64.cpp +++ b/hphp/runtime/vm/translator/translator-x64.cpp @@ -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(dest, arr, val, key); + new_iter_array_key(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 : (TCA)new_iter_array_key, + 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(it, val, key); + ret = iter_next_key(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 : (TCA)iter_next_key, + 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) \ diff --git a/hphp/runtime/vm/translator/translator-x64.h b/hphp/runtime/vm/translator/translator-x64.h index 44069e010..2df3520d6 100644 --- a/hphp/runtime/vm/translator/translator-x64.h +++ b/hphp/runtime/vm/translator/translator-x64.h @@ -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) \ diff --git a/hphp/runtime/vm/translator/translator.cpp b/hphp/runtime/vm/translator/translator.cpp index 3ece7afbd..3da884b5c 100644 --- a/hphp/runtime/vm/translator/translator.cpp +++ b/hphp/runtime/vm/translator/translator.cpp @@ -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; } diff --git a/hphp/runtime/vm/translator/translator.h b/hphp/runtime/vm/translator/translator.h index ea2e6025d..d98f57344 100644 --- a/hphp/runtime/vm/translator/translator.h +++ b/hphp/runtime/vm/translator/translator.h @@ -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: diff --git a/hphp/runtime/vm/verifier/check_func.cpp b/hphp/runtime/vm/verifier/check_func.cpp index 7994d401b..15e51c829 100644 --- a/hphp/runtime/vm/verifier/check_func.cpp +++ b/hphp/runtime/vm/verifier/check_func.cpp @@ -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) { diff --git a/hphp/test/slow/builtin_support/setwithref.php b/hphp/test/slow/builtin_support/setwithref.php new file mode 100644 index 000000000..90e971907 --- /dev/null +++ b/hphp/test/slow/builtin_support/setwithref.php @@ -0,0 +1,32 @@ + '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(); diff --git a/hphp/test/slow/builtin_support/setwithref.php.expect b/hphp/test/slow/builtin_support/setwithref.php.expect new file mode 100644 index 000000000..a99e6e3f4 --- /dev/null +++ b/hphp/test/slow/builtin_support/setwithref.php.expect @@ -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) +} diff --git a/hphp/test/slow/builtin_support/setwithref.php.norepo b/hphp/test/slow/builtin_support/setwithref.php.norepo new file mode 100644 index 000000000..e69de29bb diff --git a/hphp/test/slow/builtin_support/support.hhas b/hphp/test/slow/builtin_support/support.hhas new file mode 100644 index 000000000..f2eca79b5 --- /dev/null +++ b/hphp/test/slow/builtin_support/support.hhas @@ -0,0 +1,93 @@ +.main { + Int 1 + RetC +} + +.function test1($arr, $k, $v) { + SetWithRefLM $v + CGetL $arr + RetC +} + +.function test2(&$arr, $k, $v) { + CGetL $v + SetM + PopC + CGetL $arr + RetC +} + +.function test3($arr, &$v) { + SetWithRefLM $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 $v +skip_s: WIterNextK 0 loop_s $v $k + Jmp endloop + +loop_n: CGetL $v + JmpZ skip_n + SetWithRefLM $v +skip_n: WIterNextK 0 loop_n $v $k + Jmp endloop + +loop_a: CGetL $func + FPushCuf 1 + FPassL 0 $v + FCall 1 + UnboxR + JmpZ skip_a + SetWithRefLM $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 + WIterNextK 0 loop_x $v $k + +endloop: CGetL $res + RetC + +ident: CGetL $arr + RetC +} diff --git a/hphp/test/slow/builtin_support/support.hhas.expect b/hphp/test/slow/builtin_support/support.hhas.expect new file mode 100644 index 000000000..e69de29bb diff --git a/hphp/test/slow/config.hdf b/hphp/test/slow/config.hdf index 15eb8c840..5500a98d8 100644 --- a/hphp/test/slow/config.hdf +++ b/hphp/test/slow/config.hdf @@ -1,3 +1,6 @@ +Eval { + AllowHhas = true +} Log { AlwaysLogUnhandledExceptions = false diff --git a/hphp/test/verify b/hphp/test/verify index 9d03d41ee..eaec731cf 100755 --- a/hphp/test/verify +++ b/hphp/test/verify @@ -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) = "";