From db22257620fc4e28eb9c7539b7a8d3a00c87cf42 Mon Sep 17 00:00:00 2001 From: Rick Lavoie Date: Tue, 16 Aug 2016 13:28:51 -0700 Subject: [PATCH] Add JIT support for vec/dict/keyset minstrs and related ops Summary: Add support in the JIT for vec/dict/keyset member operations. Most dict operations simply call out to the runtime functions provided in a previous diff, but for vec, many of the get operations can be emitted inline. Reviewed By: paulbiss Differential Revision: D3490225 fbshipit-source-id: dfce899de6a050eb480aff9ab0e26f637bde6c8a --- hphp/doc/ir.specification | 201 ++++++++- hphp/runtime/base/array-data-defs.h | 2 + hphp/runtime/base/array-data.cpp | 6 +- hphp/runtime/base/array-init.h | 18 +- hphp/runtime/base/mixed-array-defs.h | 7 + hphp/runtime/base/mixed-array.cpp | 11 + hphp/runtime/base/mixed-array.h | 11 +- hphp/runtime/vm/jit/check.cpp | 8 +- hphp/runtime/vm/jit/code-gen-x64.cpp | 129 +++++- hphp/runtime/vm/jit/code-gen-x64.h | 2 + hphp/runtime/vm/jit/dce.cpp | 45 +- hphp/runtime/vm/jit/extra-data.h | 10 +- hphp/runtime/vm/jit/gvn.cpp | 2 + hphp/runtime/vm/jit/ir-builder.cpp | 17 +- hphp/runtime/vm/jit/ir-builder.h | 3 + hphp/runtime/vm/jit/ir-instruction.cpp | 47 +- hphp/runtime/vm/jit/ir-opcode.cpp | 8 +- hphp/runtime/vm/jit/ir-opcode.h | 7 +- hphp/runtime/vm/jit/ir-unit.cpp | 1 + hphp/runtime/vm/jit/irgen-builtin.cpp | 119 +++++ hphp/runtime/vm/jit/irgen-create.cpp | 30 +- hphp/runtime/vm/jit/irgen-minstr.cpp | 502 +++++++++++++++++++-- hphp/runtime/vm/jit/irgen-minstr.h | 57 ++- hphp/runtime/vm/jit/irlower-exception.cpp | 3 +- hphp/runtime/vm/jit/irlower-minstr.cpp | 381 +++++++++++++++- hphp/runtime/vm/jit/memory-effects.cpp | 85 +++- hphp/runtime/vm/jit/minstr-effects.cpp | 9 +- hphp/runtime/vm/jit/minstr-helpers.h | 292 ++++++++++++ hphp/runtime/vm/jit/mixed-array-offset-profile.cpp | 4 +- hphp/runtime/vm/jit/native-calls.cpp | 12 +- hphp/runtime/vm/jit/reg-alloc.cpp | 16 +- hphp/runtime/vm/jit/simplify.cpp | 364 +++++++++++++-- hphp/runtime/vm/jit/simplify.h | 18 + hphp/runtime/vm/jit/translator-runtime.cpp | 81 ++++ hphp/runtime/vm/jit/translator-runtime.h | 15 + hphp/runtime/vm/member-operations.h | 80 ++-- 36 files changed, 2414 insertions(+), 189 deletions(-) diff --git a/hphp/doc/ir.specification b/hphp/doc/ir.specification index e540130a1f9..ce95e9dd149 100644 --- a/hphp/doc/ir.specification +++ b/hphp/doc/ir.specification @@ -1741,13 +1741,16 @@ To string conversions: | CountArrayFast, D(Int), S(Arr), NF +| CountVec, D(Int), S(Vec), NF + | CountCollection, D(Int), S(Obj), NF Computes the number of elements in S0 using the same definition as Count, but with a restriction on the input type. CountArray expects any array. CountArrayFast expects an array whose kind is - not kGlobalsKind or kProxyKind. CountCollection expects a collection object. + not kGlobalsKind or kProxyKind. CountVec expects a vec. CountCollection + expects a collection object. | Nop, ND, NA, NF @@ -1892,6 +1895,14 @@ To string conversions: IR because many eval stack slots are not entirely typed wrt initness right now.) +| DictAddElemStrKey, D(Dict), S(Dict) S(Str) S(Cell), Er|CRc|PRc + + Add S2 to the dict S0 for the key S1, and return the resulting dict. + +| DictAddElemIntKey, D(Dict), S(Dict) S(Int) S(Cell), Er|CRc|PRc + + Add S2 to the dict S0 for the key S1, and return the resulting dict. + | ArrayAdd, D(Arr), S(Arr) S(Arr), Er|CRc|PRc Has the effects of the php + operator on the two source arrays. @@ -1900,6 +1911,14 @@ To string conversions: Has the effects of array_key_exists(S0, S1). +| AKExistsDict, D(Bool), S(Dict) S(Int,Str), NF + + Has the effects of array_key_exists(S0, S1). + +| AKExistsKeyset, D(Bool), S(Keyset) S(Int,Str), NF + + Has the effects of array_key_exists(S0, S1). + | AKExistsObj, D(Bool), S(Obj) S(Int,Str), Er Has the effects of array_key_exists(S0, S1) on an object S0. This either does @@ -1916,6 +1935,16 @@ To string conversions: Checks if S0 contains the key S1, and returns the result if found. Otherwise S2 is returned. +| DictIdx, DDictElem, S(Dict) S(Int,Str) S(Cell), NF + + Checks if S0 contains the key S1 and returns the result if found. Otherwise + S2 is returned. + +| KeysetIdx, DKeysetElem, S(Keyset) S(Int,Str) S(Cell), NF + + Checks if S0 contains the key S1 and returns the result if found. Otherwise + S2 is returned. + | MapIdx, D(Gen), S(Obj) S(Str) S(Cell), NF Checks if S0, which must be an HH\Map or an HH\ImmMap, contains the key S1, @@ -2460,10 +2489,30 @@ fields of that struct for holding intermediate values. fail, branch to B. This check is allowed to have false negatives, in the case of int-like strings. -| CheckArrayCOW, ND, S(Arr), B +| CheckArrayCOW, ND, S(ArrLike), B Check that S0 has a refcount of exactly 1; if not, branch to B. +| ProfileDictOffset, ND, S(Dict) S(Int,Str), NF + + Profile the offset of the element keyed by S1 in S0. + +| CheckDictOffset, ND, S(Dict) S(Int,Str), B + + Check that `pos' is within the usage bounds of S0 (including tombstones), and + that S1 exactly matches the element key of S0 at `pos'. If any of the checks + fail, branch to B. This check is allowed to have false negatives. + +| ProfileKeysetOffset, ND, S(Keyset) S(Int,Str), NF + + Profile the offset of the element keyed by S1 in S0. + +| CheckKeysetOffset, ND, S(Keyset) S(Int,Str), B + + Check that `pos' is within the usage bounds of S0 (including tombstones), and + that S1 exactly matches the element key of S0 at `pos'. If any of the checks + fail, branch to B. This check is allowed to have false negatives. + | ElemArray, D(PtrToMembGen), S(Arr) S(Int,Str), Er | ElemArrayW, D(PtrToMembGen), S(Arr) S(Int,Str), Er @@ -2479,10 +2528,56 @@ fields of that struct for holding intermediate values. ElemArray{D,U} both take a PtrToGen for the base operand, but expect it to be a PtrToArr or PtrToBoxedArr. T is the type of the base array. -| ElemMixedArrayK, D(PtrToMembGen), AK(Mixed) S(Int,Str), NF +| ElemMixedArrayK, D(PtrToElemGen), AK(Mixed) S(Int,Str), NF Like ElemArray, but the element for S1 is at a known position `pos' in S0. +| ElemVecD, D(PtrToElemCell), S(PtrToGen) S(Int), MElem|Er + +| ElemVecU, D(PtrToMembCell), S(PtrToGen) S(Int), MElem|Er + + Similar to ElemX, but the base S0 is a vec and the key S1 is an int. ElemVecD + is for Define member instrs and ElemVecU is for Unset. (Other variations can + be implemented without special IR instructions). + + ElemVec{D,U} both take a PtrToGen for the base operand, but expect it to be a + PtrToVec or PtrToBoxedVec. T is the type of the base vec. + +| ElemDict, D(PtrToMembCell), S(Dict) S(Int,Str), Er + +| ElemDictW, D(PtrToMembCell), S(Dict) S(Int,Str), Er + +| ElemDictD, D(PtrToElemCell), S(PtrToGen) S(Int,Str), MElem|Er + +| ElemDictU, D(PtrToMembCell), S(PtrToGen) S(Int,Str), MElem|Er + + Similar to ElemX, but the base S0 is a dict and the key S1 is an int/str. + ElemDictW is for Warn member instrs, ElemDictD is for Define member instrs, + and ElemDictU is for Unset. + + ElemDict{D,U} both take a PtrToGen for the base operand, but expect it to be + a PtrToDict or PtrToBoxedDict. T is the type of the base array. + +| ElemDictK, D(PtrToElemCell), S(Dict) S(Int,Str), NF + + Like ElemDict, but the element for S1 is at a known position `pos' in S0. + +| ElemKeyset, D(PtrToMembCell), S(Keyset) S(Int,Str), Er + +| ElemKeysetW, D(PtrToMembCell), S(Keyset) S(Int,Str), Er + +| ElemKeysetU, D(PtrToMembCell), S(PtrToGen) S(Int,Str), MElem|Er + + Similar to ElemX, but the base S0 is a keyset and the key S1 is an int/str. + ElemKeysetW is for Warn member instrs and ElemKeysetU is for Unset. + + ElemKeysetU both take a PtrToGen for the base operand, but expect it to be + a PtrToKeyset or PtrToBoxedKeyset. T is the type of the base array. + +| ElemKeysetK, D(PtrToElemCell), S(Keyset) S(Int,Str), NF + + Like ElemKeyset, but the element for S1 is at a known position `pos' in S0. + | ElemDX, D(PtrToMembGen), S(PtrToGen) S(Cell) S(PtrToMISGen), MElem|Er Like ElemX, but used for intermediate element lookups that may modify the @@ -2501,6 +2596,32 @@ fields of that struct for holding intermediate values. Like ArrayGet, but the element for S1 is at a known position `pos' in S0. +| DictGet, DDictElem, S(Dict) S(Int,Str), Er + + Get element with key S1 from base S0, throwing if the element is not present. + +| DictGetQuiet, DDictElem, S(Dict) S(Int,Str), NF + + Get element with key S1 from base S0, returning null if the element is not + present. + +| DictGetK, DDictElem, S(Dict) S(Int,Str), NF + + Like DictGet, but the element for S1 is at a known position `pos' in S0. + +| KeysetGet, DKeysetElem, S(Keyset) S(Int,Str), Er + + Get element with key S1 from base S0, throwing if the element is not present. + +| KeysetGetQuiet, DKeysetElem, S(Keyset) S(Int,Str), NF + + Get element with key S1 from base S0, returning null if the element is not + present. + +| KeysetGetK, DKeysetElem, S(Keyset) S(Int,Str), NF + + Like KeysetGet, but the element for S1 is at a known position `pos' in S0. + | StringGet, D(StaticStr), S(Str) S(Int), PRc|Er Get string representing character at position S1 from base string S0. Raises @@ -2533,6 +2654,28 @@ fields of that struct for holding intermediate values. RefData with an array type when this instruction is executed, and it must be the same array that is in S0. +| VecSet, D(Vec), S(Vec) S(Int) S(Cell), PRc|CRc|Er + + Set element with key S1 in S0 to S2. The dest will be a new Vec that should + replace S0. + +| VecSetRef, ND, S(Vec) S(Int) S(Cell) S(BoxedCell), CRc|Er + + Like VecSet, but for binding operations on the vec. S3 must point to a + RefData with a vec type when this instruction is executed, and it must be the + same vec that is in S0. + +| DictSet, D(Dict), S(Dict) S(Int,Str) S(Cell), PRc|CRc|Er + + Set element with key S1 in S0 to S2. The dest will be a new Dict that should + replace S0. + +| DictSetRef, ND, S(Dict) S(Int,Str) S(Cell) S(BoxedCell), CRc|Er + + Like DictSet, but for binding operations on the dict. S3 must point to a + RefData with a vec type when this instruction is executed, and it must be the + same dict that is in S0. + | MapSet, ND, S(Obj) S(Int,Str) S(Cell), Er Set element with key S1 in S0 to S2. @@ -2572,13 +2715,29 @@ fields of that struct for holding intermediate values. | SetNewElemArray, ND, S(PtrToGen) S(Cell), MElem|Er - Append the value in S1 to S0, where S0 must be a pointer to an array. + Append the value in S1 to S0, where S0 must be a pointer to a array. + +| SetNewElemVec, ND, S(PtrToGen) S(Cell), MElem|Er + + Append the value in S1 to S0, where S0 must be a pointer to a vec. + +| SetNewElemKeyset, ND, S(PtrToGen) S(Int,Str), MElem|Er + + Append the value in S1 to S0, where S0 must be a pointer to a keyset. | BindNewElem, ND, S(PtrToGen) S(BoxedCell), MElem|Er Append the reference in S1 to S0. -| ArrayIsset, D(Bool), S(Arr) S(Int,Str), Er +| ArrayIsset, D(Bool), S(Arr) S(Int,Str), NF + + Returns true iff the element at key S1 in the base S0 is set. + +| DictIsset, D(Bool), S(Dict) S(Int,Str), NF + + Returns true iff the element at key S1 in the base S0 is set. + +| KeysetIsset, D(Bool), S(Keyset) S(Int,Str), NF Returns true iff the element at key S1 in the base S0 is set. @@ -2607,13 +2766,27 @@ fields of that struct for holding intermediate values. Returns true iff the element at key S1 in S0 is set and not equal (as defined by the hhbc Eq instruction) to false. +| DictEmptyElem, D(Bool), S(Dict) S(Int,Str), NF + + Like EmptyElem, but specialized for dicts. + +| KeysetEmptyElem, D(Bool), S(Keyset) S(Int,Str), NF + + Like EmptyElem, but specialized for dicts. + | CheckRange, D(Bool), S(Int) S(Int), NF Returns true iff S0 is in the range [0, S1). -| ThrowOutOfBounds, ND, S(Int), Er|T +| ThrowOutOfBounds, ND, S(ArrLike|Obj) S(Gen), Er|T + + Throws an OutOfBoundsException corresponding to an access of S0 with the key + S1. - Throws an OutOfBoundsException with a message indicating S0. +| ThrowInvalidArrayKey, ND, S(ArrLike) S(Gen), Er|T + + Throws an InvalidArgumentException corresponding to an access of S0 with the + key S1, which has a type invalid for that array. | ThrowInvalidOperation, ND, S(Str), Er|T @@ -2648,9 +2821,21 @@ fields of that struct for holding intermediate values. Load the address of the element at index S1 of the packed array in S0. +| LdVecElem, DVecElem, S(Vec) S(Int), NF + + Loads the element of the vec array in S0 at offset S1. This instruction + assumes that the vec actually contains an element at that offset (IE, the vec + has the proper length). + +| LdVecElemAddr, DParamPtr(Elem), S(Vec) S(Int), NF + + Loads the address of the element at index S1 of the vec array in S0. This + instruction assumes the vec actually contains an element at that offset (IE, + the vec has the proper length). + | LdVectorSize, D(Int), S(Obj), NF - Returns the size of the given Vector in S0. + Returns the size of the given Vector collection in S0. | VectorDoCow, ND, S(Obj), NF diff --git a/hphp/runtime/base/array-data-defs.h b/hphp/runtime/base/array-data-defs.h index 7423b397d3c..dfd2622f27c 100644 --- a/hphp/runtime/base/array-data-defs.h +++ b/hphp/runtime/base/array-data-defs.h @@ -27,6 +27,8 @@ namespace HPHP { /////////////////////////////////////////////////////////////////////////////// +extern const StaticString s_InvalidKeysetOperationMsg; + namespace { inline bool isIntKey(const Cell* cell) { return isIntKeyType(cell->m_type); diff --git a/hphp/runtime/base/array-data.cpp b/hphp/runtime/base/array-data.cpp index c31a699a010..03672c47db8 100644 --- a/hphp/runtime/base/array-data.cpp +++ b/hphp/runtime/base/array-data.cpp @@ -51,6 +51,8 @@ using ArrayDataMap = tbb::concurrent_hash_map; static ArrayDataMap s_arrayDataMap; +const StaticString s_InvalidKeysetOperationMsg("Invalid operation on keyset"); + ArrayData::ScalarArrayKey ArrayData::GetScalarArrayKey(const char* str, size_t sz) { return MD5(string_md5(folly::StringPiece{str, sz})); @@ -1189,9 +1191,7 @@ void throwRefInvalidArrayValueException(const Array& arr) { } void throwInvalidKeysetOperation() { - SystemLib::throwInvalidOperationExceptionObject( - "Invalid operation on keyset" - ); + SystemLib::throwInvalidOperationExceptionObject(s_InvalidKeysetOperationMsg); } void throwInvalidAdditionException(const ArrayData* ad) { diff --git a/hphp/runtime/base/array-init.h b/hphp/runtime/base/array-init.h index 6174a95e3ea..7d0a78e1a2b 100644 --- a/hphp/runtime/base/array-init.h +++ b/hphp/runtime/base/array-init.h @@ -748,8 +748,14 @@ struct KeysetInit { /* * Add a new element to the keyset. */ - KeysetInit& add(int64_t v) { return add(Variant{v}); } - KeysetInit& add(StringData* v) { return add(Variant{v}); } + KeysetInit& add(int64_t v) { + performOp([&]{ return MixedArray::AddToKeyset(m_keyset, v, false); }); + return *this; + } + KeysetInit& add(StringData* v) { + performOp([&]{ return MixedArray::AddToKeyset(m_keyset, v, false); }); + return *this; + } KeysetInit& add(const Variant& v) { performOp([&]{ return MixedArray::AppendKeyset(m_keyset, v.asInitCellTmp(), false); @@ -843,11 +849,17 @@ namespace make_array_detail { map_impl(init, std::forward(kvpairs)...); } + inline String keyset_init_key(const char* s) { return String(s); } + inline int64_t keyset_init_key(int k) { return k; } + inline int64_t keyset_init_key(int64_t k) { return k; } + inline StringData* keyset_init_key(const String& k) { return k.get(); } + inline StringData* keyset_init_key(StringData* k) { return k; } + inline void keyset_impl(KeysetInit&) {} template void keyset_impl(KeysetInit& init, Val&& val, Vals&&... vals) { - init.add(Variant(std::forward(val))); + init.add(keyset_init_key(std::forward(val))); keyset_impl(init, std::forward(vals)...); } diff --git a/hphp/runtime/base/mixed-array-defs.h b/hphp/runtime/base/mixed-array-defs.h index 5949260e6eb..7fb981d97d8 100644 --- a/hphp/runtime/base/mixed-array-defs.h +++ b/hphp/runtime/base/mixed-array-defs.h @@ -225,6 +225,13 @@ void MixedArray::getArrayElm(ssize_t pos, TypedValue* valOut) const { } ALWAYS_INLINE +const TypedValue& MixedArray::getArrayElmRef(ssize_t pos) const { + assert(size_t(pos) < m_used); + auto& elm = data()[pos]; + return elm.data; +} + +ALWAYS_INLINE void MixedArray::dupArrayElmWithRef(ssize_t pos, TypedValue* valOut, TypedValue* keyOut) const { diff --git a/hphp/runtime/base/mixed-array.cpp b/hphp/runtime/base/mixed-array.cpp index d6c05110495..fde26751165 100644 --- a/hphp/runtime/base/mixed-array.cpp +++ b/hphp/runtime/base/mixed-array.cpp @@ -2089,6 +2089,7 @@ MixedArray::AppendWithRefDict(ArrayData* adIn, const Variant& v, bool copy) { ////////////////////////////////////////////////////////////////////// ArrayData* MixedArray::AppendKeyset(ArrayData* ad, Cell v, bool copy) { + assert(ad->isKeyset()); if (isIntType(v.m_type)) { return SetInt(ad, v.m_data.num, v, copy); } else if (isStringType(v.m_type)) { @@ -2098,6 +2099,16 @@ ArrayData* MixedArray::AppendKeyset(ArrayData* ad, Cell v, bool copy) { } } +ArrayData* MixedArray::AddToKeyset(ArrayData* ad, int64_t i, bool copy) { + assert(ad->isKeyset()); + return SetInt(ad, i, make_tv(i), copy); +} + +ArrayData* MixedArray::AddToKeyset(ArrayData* ad, StringData* s, bool copy) { + assert(ad->isKeyset()); + return SetStr(ad, s, make_tv(s), copy); +} + ArrayData* MixedArray::AppendWithRefKeyset(ArrayData* adIn, const Variant& v, bool copy) { return AppendWithRefNoRef(adIn, v, copy, AppendKeyset); diff --git a/hphp/runtime/base/mixed-array.h b/hphp/runtime/base/mixed-array.h index ec2473f0199..207722904b5 100644 --- a/hphp/runtime/base/mixed-array.h +++ b/hphp/runtime/base/mixed-array.h @@ -136,6 +136,10 @@ struct MixedArray final : private ArrayData, return offsetof(MixedArray, m_used); } + static constexpr ptrdiff_t elmOff(uint32_t pos) { + return dataOff() + pos * sizeof(Elm); + } + struct ElmKey { ElmKey() {} ElmKey(strhash_t hash, StringData* key) @@ -468,8 +472,9 @@ public: static constexpr auto LvalSilentIntDict = &LvalSilentInt; static constexpr auto LvalSilentStrDict = &LvalSilentStr; - static constexpr auto LvalSilentIntKeyset = &LvalSilentInt; - static constexpr auto LvalSilentStrKeyset = &LvalSilentStr; + + static ArrayData* AddToKeyset(ArrayData*, int64_t, bool); + static ArrayData* AddToKeyset(ArrayData*, StringData*, bool); ////////////////////////////////////////////////////////////////////// @@ -518,6 +523,8 @@ public: void dupArrayElmWithRef(ssize_t pos, TypedValue* valOut, TypedValue* keyOut) const; + const TypedValue& getArrayElmRef(ssize_t pos) const; + bool isTombstone(ssize_t pos) const; size_t hashSize() const; diff --git a/hphp/runtime/vm/jit/check.cpp b/hphp/runtime/vm/jit/check.cpp index 6100afbce49..c73fe3c576e 100644 --- a/hphp/runtime/vm/jit/check.cpp +++ b/hphp/runtime/vm/jit/check.cpp @@ -510,8 +510,10 @@ bool checkOperandTypes(const IRInstruction* inst, const IRUnit* unit) { #define DBoxPtr #define DAllocObj #define DArrElem +#define DVecElem +#define DDictElem +#define DKeysetElem #define DArrPacked -#define DArrVec #define DCol #define DThis #define DCtx @@ -550,7 +552,9 @@ bool checkOperandTypes(const IRInstruction* inst, const IRUnit* unit) { #undef DBoxPtr #undef DAllocObj #undef DArrElem -#undef DArrVec +#undef DVecElem +#undef DDictElem +#undef DKeysetElem #undef DArrPacked #undef DCol #undef DThis diff --git a/hphp/runtime/vm/jit/code-gen-x64.cpp b/hphp/runtime/vm/jit/code-gen-x64.cpp index 1702e1c8403..0ccd628b7ff 100644 --- a/hphp/runtime/vm/jit/code-gen-x64.cpp +++ b/hphp/runtime/vm/jit/code-gen-x64.cpp @@ -143,6 +143,8 @@ NOOP_OPCODE(FinishMemberOp) CALL_OPCODE(AddElemStrKey) CALL_OPCODE(AddElemIntKey) CALL_OPCODE(AddNewElem) +CALL_OPCODE(DictAddElemStrKey) +CALL_OPCODE(DictAddElemIntKey) CALL_OPCODE(ArrayAdd) CALL_OPCODE(MapAddElemC) CALL_OPCODE(ColAddNewElemC) @@ -211,6 +213,7 @@ CALL_OPCODE(SetOpElem) CALL_OPCODE(IncDecElem) CALL_OPCODE(SetNewElem) CALL_OPCODE(SetNewElemArray) +CALL_OPCODE(SetNewElemVec) CALL_OPCODE(BindNewElem) CALL_OPCODE(VectorIsset) CALL_OPCODE(PairIsset) @@ -242,6 +245,9 @@ DELEGATE_OPCODE(IncTransCounter) DELEGATE_OPCODE(IncProfCounter) DELEGATE_OPCODE(CheckCold) +CALL_OPCODE(ElemVecD) +CALL_OPCODE(ElemVecU) + DELEGATE_OPCODE(Box) DELEGATE_OPCODE(BoxPtr) DELEGATE_OPCODE(UnboxPtr) @@ -504,17 +510,47 @@ DELEGATE_OPCODE(EmptyElem) DELEGATE_OPCODE(ProfileMixedArrayOffset) DELEGATE_OPCODE(CheckMixedArrayOffset) DELEGATE_OPCODE(CheckArrayCOW) +DELEGATE_OPCODE(ProfileDictOffset) +DELEGATE_OPCODE(CheckDictOffset) +DELEGATE_OPCODE(ProfileKeysetOffset) +DELEGATE_OPCODE(CheckKeysetOffset) DELEGATE_OPCODE(ElemArray) DELEGATE_OPCODE(ElemArrayW) DELEGATE_OPCODE(ElemArrayD) DELEGATE_OPCODE(ElemArrayU) DELEGATE_OPCODE(ElemMixedArrayK) +DELEGATE_OPCODE(ElemDict) +DELEGATE_OPCODE(ElemDictW) +DELEGATE_OPCODE(ElemDictD) +DELEGATE_OPCODE(ElemDictU) +DELEGATE_OPCODE(ElemDictK) +DELEGATE_OPCODE(ElemKeyset) +DELEGATE_OPCODE(ElemKeysetW) +DELEGATE_OPCODE(ElemKeysetU) +DELEGATE_OPCODE(ElemKeysetK) DELEGATE_OPCODE(ArrayGet) -DELEGATE_OPCODE(MixedArrayGetK) DELEGATE_OPCODE(ArraySet) DELEGATE_OPCODE(ArraySetRef) DELEGATE_OPCODE(ArrayIsset) DELEGATE_OPCODE(ArrayIdx) +DELEGATE_OPCODE(MixedArrayGetK) +DELEGATE_OPCODE(DictGet) +DELEGATE_OPCODE(DictGetQuiet) +DELEGATE_OPCODE(DictGetK) +DELEGATE_OPCODE(DictIsset) +DELEGATE_OPCODE(DictEmptyElem) +DELEGATE_OPCODE(DictSet) +DELEGATE_OPCODE(DictSetRef) +DELEGATE_OPCODE(DictIdx) +DELEGATE_OPCODE(VecSet) +DELEGATE_OPCODE(VecSetRef) +DELEGATE_OPCODE(KeysetGet) +DELEGATE_OPCODE(KeysetGetQuiet) +DELEGATE_OPCODE(KeysetGetK) +DELEGATE_OPCODE(SetNewElemKeyset) +DELEGATE_OPCODE(KeysetIsset) +DELEGATE_OPCODE(KeysetEmptyElem) +DELEGATE_OPCODE(KeysetIdx) DELEGATE_OPCODE(MapGet) DELEGATE_OPCODE(MapSet) DELEGATE_OPCODE(MapIsset) @@ -607,6 +643,7 @@ DELEGATE_OPCODE(RaiseUninitLoc) DELEGATE_OPCODE(RaiseWarning) DELEGATE_OPCODE(ThrowArithmeticError) DELEGATE_OPCODE(ThrowDivisionByZeroError) +DELEGATE_OPCODE(ThrowInvalidArrayKey) DELEGATE_OPCODE(ThrowInvalidOperation); DELEGATE_OPCODE(ThrowOutOfBounds) @@ -1059,26 +1096,27 @@ void CodeGenerator::cgCheckPackedArrayBounds(IRInstruction* inst) { v << jcc{CC_BE, sf, {label(inst->next()), label(inst->taken())}}; } -void CodeGenerator::cgLdPackedArrayElemAddr(IRInstruction* inst) { - auto const idx = inst->src(1); - auto const rArr = srcLoc(inst, 0).reg(); - auto const rIdx = srcLoc(inst, 1).reg(); - auto const dst = dstLoc(inst, 0).reg(); +Vptr CodeGenerator::emitPackedLayoutAddr(SSATmp* idx, Vloc idxLoc, + Vloc arrLoc) { + auto const rArr = arrLoc.reg(); + auto const rIdx = idxLoc.reg(); auto& v = vmain(); + static_assert(sizeof(TypedValue) == 16, ""); + if (idx->hasConstVal()) { - auto const offset = sizeof(ArrayData) + idx->intVal() * sizeof(TypedValue); + auto const offset = + PackedArray::entriesOffset() + idx->intVal() * sizeof(TypedValue); if (deltaFits(offset, sz::dword)) { - v << lea{rArr[offset], dst}; - return; + return rArr[offset]; } } - static_assert(sizeof(TypedValue) == 16 && sizeof(ArrayData) == 16, ""); /* - * This computes `rArr + rIdx * sizeof(TypedValue) + sizeof(ArrayData)`. The - * logic of `scaledIdx * 16` is split in the following two instructions, in - * order to save a byte in the shl instruction. + * This computes `rArr + rIdx * sizeof(TypedValue) + + * PackedArray::entriesOffset()`. The logic of `scaledIdx * 16` is split in + * the following two instructions, in order to save a byte in the shl + * instruction. * * TODO(#7728856): We should really move this into vasm-x64.cpp... */ @@ -1088,8 +1126,27 @@ void CodeGenerator::cgLdPackedArrayElemAddr(IRInstruction* inst) { v << movtql{rIdx, idxl}; v << shlli{1, idxl, scaled_idxl, v.makeReg()}; v << movzlq{scaled_idxl, scaled_idx}; - v << lea{rArr[scaled_idx * int(sizeof(TypedValue) / 2) + - int(sizeof(ArrayData))], dst}; + return rArr[scaled_idx * int(sizeof(TypedValue) / 2) + + PackedArray::entriesOffset()]; +} + +void CodeGenerator::cgLdPackedArrayElemAddr(IRInstruction* inst) { + auto const addr = + emitPackedLayoutAddr(inst->src(1), srcLoc(inst, 1), srcLoc(inst, 0)); + vmain() << lea{addr, dstLoc(inst, 0).reg()}; +} + + +void CodeGenerator::cgLdVecElem(IRInstruction* inst) { + auto const addr = + emitPackedLayoutAddr(inst->src(1), srcLoc(inst, 1), srcLoc(inst, 0)); + loadTV(vmain(), inst->dst(), dstLoc(inst, 0), addr); +} + +void CodeGenerator::cgLdVecElemAddr(IRInstruction* inst) { + auto const addr = + emitPackedLayoutAddr(inst->src(1), srcLoc(inst, 1), srcLoc(inst, 0)); + vmain() << lea{addr, dstLoc(inst, 0).reg()}; } void CodeGenerator::cgLdVectorSize(IRInstruction* inst) { @@ -1313,6 +1370,40 @@ void CodeGenerator::cgAKExistsArr(IRInstruction* inst) { ); } +void CodeGenerator::cgAKExistsDict(IRInstruction* inst) { + auto const keyTy = inst->src(1)->type(); + auto& v = vmain(); + + auto const target = (keyTy <= TInt) + ? CallSpec::direct(MixedArray::ExistsIntDict) + : CallSpec::direct(MixedArray::ExistsStrDict); + + cgCallHelper( + v, + target, + callDest(inst), + SyncOptions::None, + argGroup(inst).ssa(0).ssa(1) + ); +} + +void CodeGenerator::cgAKExistsKeyset(IRInstruction* inst) { + auto const keyTy = inst->src(1)->type(); + auto& v = vmain(); + + auto const target = (keyTy <= TInt) + ? CallSpec::direct(MixedArray::ExistsIntKeyset) + : CallSpec::direct(MixedArray::ExistsStrKeyset); + + cgCallHelper( + v, + target, + callDest(inst), + SyncOptions::None, + argGroup(inst).ssa(0).ssa(1) + ); +} + void CodeGenerator::cgAKExistsObj(IRInstruction* inst) { auto const keyTy = inst->src(1)->type(); auto& v = vmain(); @@ -1426,6 +1517,14 @@ void CodeGenerator::cgCountCollection(IRInstruction* inst) { v << loadzlq{baseReg[collections::FAST_SIZE_OFFSET], dstReg}; } +void CodeGenerator::cgCountVec(IRInstruction* inst) { + static_assert(ArrayData::sizeofSize() == 4, ""); + vmain() << loadzlq{ + srcLoc(inst, 0).reg()[ArrayData::offsetofSize()], + dstLoc(inst, 0).reg() + }; +} + void CodeGenerator::cgInitPackedLayoutArray(IRInstruction* inst) { auto const arrReg = srcLoc(inst, 0).reg(); auto const index = inst->extra()->index; diff --git a/hphp/runtime/vm/jit/code-gen-x64.h b/hphp/runtime/vm/jit/code-gen-x64.h index 2e8457957e4..5811120581e 100644 --- a/hphp/runtime/vm/jit/code-gen-x64.h +++ b/hphp/runtime/vm/jit/code-gen-x64.h @@ -80,6 +80,8 @@ private: bool emitIncDec(Vout& v, Vloc dst, SSATmp* src0, Vloc loc0, SSATmp* src1, Vloc loc1, Vreg& sf); + Vptr emitPackedLayoutAddr(SSATmp* idx, Vloc idxLoc, Vloc arrLoc); + private: Vreg selectScratchReg(IRInstruction* inst); RegSet findFreeRegs(IRInstruction* inst); diff --git a/hphp/runtime/vm/jit/dce.cpp b/hphp/runtime/vm/jit/dce.cpp index 8ccf7ce48d2..551cedd7d56 100644 --- a/hphp/runtime/vm/jit/dce.cpp +++ b/hphp/runtime/vm/jit/dce.cpp @@ -203,6 +203,8 @@ bool canDCE(IRInstruction* inst) { case LdARNumParams: case LdFuncNumParams: case LdStrLen: + case LdVecElem: + case LdVecElemAddr: case LdClosureStaticLoc: case NewInstanceRaw: case NewArray: @@ -217,9 +219,12 @@ bool canDCE(IRInstruction* inst) { case Mov: case CountArray: case CountArrayFast: + case CountVec: case CountCollection: case Nop: case AKExistsArr: + case AKExistsDict: + case AKExistsKeyset: case LdBindAddr: case LdSwitchDblIndex: case LdSwitchStrIndex: @@ -247,6 +252,18 @@ bool canDCE(IRInstruction* inst) { case LdMBase: case MethodExists: case LdTVAux: + case ArrayIdx: + case ArrayIsset: + case DictGetQuiet: + case DictGetK: + case DictIsset: + case DictEmptyElem: + case DictIdx: + case KeysetGetQuiet: + case KeysetGetK: + case KeysetIsset: + case KeysetEmptyElem: + case KeysetIdx: assertx(!inst->isControlFlow()); return true; @@ -438,8 +455,9 @@ bool canDCE(IRInstruction* inst) { case AddElemStrKey: case AddElemIntKey: case AddNewElem: + case DictAddElemStrKey: + case DictAddElemIntKey: case ArrayAdd: - case ArrayIdx: case GetMemoKey: case LdSwitchObjIndex: case LdSSwitchDestSlow: @@ -516,15 +534,32 @@ bool canDCE(IRInstruction* inst) { case ProfileMixedArrayOffset: case CheckMixedArrayOffset: case CheckArrayCOW: + case ProfileDictOffset: + case CheckDictOffset: + case ProfileKeysetOffset: + case CheckKeysetOffset: case ElemArray: case ElemArrayD: case ElemArrayW: case ElemArrayU: case ElemMixedArrayK: + case ElemVecD: + case ElemVecU: + case ElemDict: + case ElemDictD: + case ElemDictW: + case ElemDictU: + case ElemDictK: + case ElemKeyset: + case ElemKeysetW: + case ElemKeysetU: + case ElemKeysetK: case ElemDX: case ElemUX: case ArrayGet: case MixedArrayGetK: + case DictGet: + case KeysetGet: case StringGet: case OrdStrIdx: case MapGet: @@ -533,6 +568,10 @@ bool canDCE(IRInstruction* inst) { case BindElem: case ArraySet: case ArraySetRef: + case VecSet: + case VecSetRef: + case DictSet: + case DictSetRef: case MapSet: case SetElem: case SetWithRefElem: @@ -541,8 +580,9 @@ bool canDCE(IRInstruction* inst) { case IncDecElem: case SetNewElem: case SetNewElemArray: + case SetNewElemVec: + case SetNewElemKeyset: case BindNewElem: - case ArrayIsset: case VectorIsset: case PairIsset: case MapIsset: @@ -575,6 +615,7 @@ bool canDCE(IRInstruction* inst) { case StARInvName: case ExitPlaceholder: case ThrowOutOfBounds: + case ThrowInvalidArrayKey: case ThrowInvalidOperation: case ThrowArithmeticError: case ThrowDivisionByZeroError: diff --git a/hphp/runtime/vm/jit/extra-data.h b/hphp/runtime/vm/jit/extra-data.h index 21354f4f116..4fd25ce4816 100644 --- a/hphp/runtime/vm/jit/extra-data.h +++ b/hphp/runtime/vm/jit/extra-data.h @@ -1,4 +1,4 @@ - /* +/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ @@ -1236,10 +1236,18 @@ X(NewKeysetArray, NewKeysetArrayData); X(InitPackedLayoutArrayLoop, InitPackedArrayLoopData); X(InitPackedLayoutArray, IndexData); X(CheckMixedArrayOffset, IndexData); +X(CheckDictOffset, IndexData); +X(CheckKeysetOffset, IndexData); X(ElemMixedArrayK, IndexData); X(MixedArrayGetK, IndexData); +X(DictGetK, IndexData); +X(KeysetGetK, IndexData); +X(ElemDictK, IndexData); +X(ElemKeysetK, IndexData); X(ProfileArrayKind, RDSHandleData); X(ProfileMixedArrayOffset, RDSHandleData); +X(ProfileDictOffset, RDSHandleData); +X(ProfileKeysetOffset, RDSHandleData); X(ProfileType, RDSHandleData); X(ProfileMethod, ProfileMethodData); X(LdRDSAddr, RDSHandleData); diff --git a/hphp/runtime/vm/jit/gvn.cpp b/hphp/runtime/vm/jit/gvn.cpp index 6e8798a26b5..1302583ca9e 100644 --- a/hphp/runtime/vm/jit/gvn.cpp +++ b/hphp/runtime/vm/jit/gvn.cpp @@ -352,9 +352,11 @@ bool supportsGVN(const IRInstruction* inst) { case LdAFWHActRec: case LdResumableArObj: case LdPackedArrayElemAddr: + case LdVecElemAddr: case OrdStr: case CheckRange: case CountArrayFast: + case CountVec: return true; default: return false; diff --git a/hphp/runtime/vm/jit/ir-builder.cpp b/hphp/runtime/vm/jit/ir-builder.cpp index 4e8288ece2b..93dc273af4d 100644 --- a/hphp/runtime/vm/jit/ir-builder.cpp +++ b/hphp/runtime/vm/jit/ir-builder.cpp @@ -80,9 +80,12 @@ SSATmp* fwdGuardSource(IRInstruction* inst) { #define DBoxPtr return false; #define DAllocObj return false; // fixed type from ExtraData #define DArrPacked return false; // fixed type -#define DArrVec return false; // fixed type #define DArrElem assertx(inst->is(ArrayGet)); \ return typeMightRelax(inst->src(0)); +#define DVecElem assertx(inst->is(LdVecElem)); \ + return false; +#define DDictElem return dictElemMightRelax(inst); +#define DKeysetElem return keysetElemMightRelax(inst); #define DCol return false; // fixed in bytecode #define DThis return false; // fixed type from ctx class #define DCtx return false; @@ -109,6 +112,18 @@ bool typeMightRelax(const SSATmp* tmp) { return true; } +bool keysetElemMightRelax(const IRInstruction* inst) { + assertx(inst->is(KeysetGet, KeysetGetK, KeysetGetQuiet, KeysetIdx)); + if (inst->is(KeysetIdx)) return typeMightRelax(inst->src(2)); + return false; +} + +bool dictElemMightRelax(const IRInstruction* inst) { + assertx(inst->is(DictGet, DictGetK, DictGetQuiet, DictIdx)); + if (inst->is(DictIdx)) return typeMightRelax(inst->src(2)); + return false; +} + /////////////////////////////////////////////////////////////////////////////// IRBuilder::IRBuilder(IRUnit& unit, BCMarker initMarker) diff --git a/hphp/runtime/vm/jit/ir-builder.h b/hphp/runtime/vm/jit/ir-builder.h index 67972c0c5b5..7a391d513fe 100644 --- a/hphp/runtime/vm/jit/ir-builder.h +++ b/hphp/runtime/vm/jit/ir-builder.h @@ -383,6 +383,9 @@ struct BlockPusher { bool typeMightRelax(const SSATmp* tmp); +bool dictElemMightRelax(const IRInstruction* inst); +bool keysetElemMightRelax(const IRInstruction* inst); + /////////////////////////////////////////////////////////////////////////////// }}} diff --git a/hphp/runtime/vm/jit/ir-instruction.cpp b/hphp/runtime/vm/jit/ir-instruction.cpp index 231be20dece..2f66e7a2536 100644 --- a/hphp/runtime/vm/jit/ir-instruction.cpp +++ b/hphp/runtime/vm/jit/ir-instruction.cpp @@ -26,6 +26,7 @@ #include "hphp/runtime/vm/jit/ir-opcode.h" #include "hphp/runtime/vm/jit/minstr-effects.h" #include "hphp/runtime/vm/jit/print.h" +#include "hphp/runtime/vm/jit/simplify.h" #include "hphp/runtime/vm/jit/ssa-tmp.h" #include "hphp/runtime/vm/jit/type.h" @@ -144,6 +145,10 @@ bool IRInstruction::consumesReference(int srcNo) const { case ArraySet: case ArraySetRef: + case VecSet: + case VecSetRef: + case DictSet: + case DictSetRef: // Only consumes the reference to its input array return srcNo == 0; @@ -279,6 +284,40 @@ Type arrElemReturn(const IRInstruction* inst) { return resultType; } +Type vecElemReturn(const IRInstruction* inst) { + assertx(inst->is(LdVecElem)); + assertx(inst->src(0)->isA(TVec)); + assertx(inst->src(1)->isA(TInt)); + + auto resultType = vecElemType(inst->src(0), inst->src(1)); + if (inst->hasTypeParam()) resultType &= inst->typeParam(); + return resultType; +} + +Type dictElemReturn(const IRInstruction* inst) { + assertx(inst->is(DictGet, DictGetK, DictGetQuiet, DictIdx)); + assertx(inst->src(0)->isA(TDict)); + assertx(inst->src(1)->isA(TInt | TStr)); + + auto resultType = dictElemType(inst->src(0), inst->src(1)); + if (inst->is(DictGetQuiet)) resultType |= TInitNull; + if (inst->is(DictIdx)) resultType |= inst->src(2)->type(); + if (inst->hasTypeParam()) resultType &= inst->typeParam(); + return resultType; +} + +Type keysetElemReturn(const IRInstruction* inst) { + assertx(inst->is(KeysetGet, KeysetGetK, KeysetGetQuiet, KeysetIdx)); + assertx(inst->src(0)->isA(TKeyset)); + assertx(inst->src(1)->isA(TInt | TStr)); + + auto resultType = keysetElemType(inst->src(0), inst->src(1)); + if (inst->is(KeysetGetQuiet)) resultType |= TInitNull; + if (inst->is(KeysetIdx)) resultType |= inst->src(2)->type(); + if (inst->hasTypeParam()) resultType &= inst->typeParam(); + return resultType; +} + Type thisReturn(const IRInstruction* inst) { auto const func = inst->func(); @@ -375,8 +414,10 @@ Type outputType(const IRInstruction* inst, int dstId) { #define DBoxPtr return boxPtr(inst->src(0)->type()); #define DAllocObj return allocObjReturn(inst); #define DArrElem return arrElemReturn(inst); +#define DVecElem return vecElemReturn(inst); +#define DDictElem return dictElemReturn(inst); +#define DKeysetElem return keysetElemReturn(inst); #define DArrPacked return Type::Array(ArrayData::kPackedKind); -#define DArrVec return Type::Array(ArrayData::kVecKind); #define DCol return newColReturn(inst); #define DThis return thisReturn(inst); #define DCtx return ctxReturn(inst); @@ -408,8 +449,10 @@ Type outputType(const IRInstruction* inst, int dstId) { #undef DBoxPtr #undef DAllocObj #undef DArrElem +#undef DVecElem +#undef DDictElem +#undef DKeysetElem #undef DArrPacked -#undef DArrVec #undef DCol #undef DThis #undef DCtx diff --git a/hphp/runtime/vm/jit/ir-opcode.cpp b/hphp/runtime/vm/jit/ir-opcode.cpp index 658bfc09a13..1830315af41 100644 --- a/hphp/runtime/vm/jit/ir-opcode.cpp +++ b/hphp/runtime/vm/jit/ir-opcode.cpp @@ -55,8 +55,10 @@ TRACE_SET_MOD(hhir); #define DBoxPtr HasDest #define DAllocObj HasDest #define DArrElem HasDest +#define DVecElem HasDest +#define DDictElem HasDest +#define DKeysetElem HasDest #define DArrPacked HasDest -#define DArrVec HasDest #define DCol HasDest #define DThis HasDest #define DCtx HasDest @@ -113,8 +115,10 @@ OpInfo g_opInfo[] = { #undef DUnboxPtr #undef DBoxPtr #undef DArrElem +#undef DVecElem +#undef DDictElem +#undef DKeysetElem #undef DArrPacked -#undef DArrVec #undef DCol #undef DAllocObj #undef DThis diff --git a/hphp/runtime/vm/jit/ir-opcode.h b/hphp/runtime/vm/jit/ir-opcode.h index 1bf53d54d4b..e037062abf9 100644 --- a/hphp/runtime/vm/jit/ir-opcode.h +++ b/hphp/runtime/vm/jit/ir-opcode.h @@ -60,9 +60,14 @@ struct SSATmp; * DAllocObj single dst has a type of a newly allocated object; may be a * specialized object type if the class is known * DArrPacked single dst has a packed array type - * DArrVec single dst has a vec array type * DArrElem single dst has type based on reading an array element, * intersected with an optional type parameter + * DVecElem single dst has type based on reading a vec element, + * intersected with an optional type parameter + * DDictElem single dst has type based on reading a dict element, + intersected with an optional type parameter + * DKeysetElem single dst has type int or string, intersected with an + * optional type parameter. * DThis single dst has type Obj, where ctx is the * current context class * DCtx single dst has type Cctx|Obj<=ctx, where ctx is the diff --git a/hphp/runtime/vm/jit/ir-unit.cpp b/hphp/runtime/vm/jit/ir-unit.cpp index a6868a62b6f..e8dcfcca1b8 100644 --- a/hphp/runtime/vm/jit/ir-unit.cpp +++ b/hphp/runtime/vm/jit/ir-unit.cpp @@ -120,6 +120,7 @@ static bool endsUnitAtSrcKey(const Block* block, SrcKey sk) { case JmpSwitchDest: case RaiseError: case ThrowOutOfBounds: + case ThrowInvalidArrayKey: case ThrowInvalidOperation: case ThrowArithmeticError: case ThrowDivisionByZeroError: diff --git a/hphp/runtime/vm/jit/irgen-builtin.cpp b/hphp/runtime/vm/jit/irgen-builtin.cpp index 0b7fa3d1e30..d33cd619f9d 100644 --- a/hphp/runtime/vm/jit/irgen-builtin.cpp +++ b/hphp/runtime/vm/jit/irgen-builtin.cpp @@ -1528,6 +1528,77 @@ void implMapIdx(IRGS& env) { finish(pelem); } +void implVecIdx(IRGS& env) { + auto const def = popC(env); + auto const key = popC(env); + auto const vec = popC(env); + + assertx(vec->isA(TVec)); + + auto const finish = [&](SSATmp* elem) { + pushIncRef(env, elem); + decRef(env, def); + decRef(env, key); + decRef(env, vec); + }; + + if (key->isA(TNull | TStr)) return finish(def); + + if (!key->isA(TInt)) { + gen(env, ThrowInvalidArrayKey, vec, key); + return; + } + + auto const elem = cond( + env, + [&] (Block* taken) { + auto const length = gen(env, CountVec, vec); + auto const cmp = gen(env, CheckRange, key, length); + gen(env, JmpZero, taken, cmp); + }, + [&] { return gen(env, LdVecElem, vec, key); }, + [&] { return def; } + ); + + auto const pelem = profiledType(env, elem, [&] { finish(elem); } ); + finish(pelem); +} + +void implDictKeysetIdx(IRGS& env, bool is_dict) { + auto const def = popC(env); + auto const key = popC(env); + auto const base = popC(env); + + assertx(base->isA(is_dict ? TDict : TKeyset)); + + auto const finish = [&](SSATmp* elem) { + pushIncRef(env, elem); + decRef(env, def); + decRef(env, key); + decRef(env, base); + }; + + if (key->isA(TNull)) return finish(def); + + if (!key->isA(TInt) && !key->isA(TStr)) { + gen(env, ThrowInvalidArrayKey, base, key); + return; + } + + auto const elem = profiledArrayAccess(env, base, key, + [&] (SSATmp* base, SSATmp* key, uint32_t pos) { + return gen(env, is_dict ? DictGetK : KeysetGetK, IndexData { pos }, + base, key); + }, + [&] (SSATmp* key) { + return gen(env, is_dict ? DictIdx : KeysetIdx, base, key, def); + } + ); + + auto const pelem = profiledType(env, elem, [&] { finish(elem); }); + finish(pelem); +} + const StaticString s_idx("hh\\idx"); void implGenericIdx(IRGS& env) { @@ -1591,6 +1662,10 @@ TypeConstraint idxBaseConstraint(Type baseType, Type keyType, void emitArrayIdx(IRGS& env) { auto const arrType = topC(env, BCSPRelOffset{2}, DataTypeGeneric)->type(); + if (arrType <= TVec) return implVecIdx(env); + if (arrType <= TDict) return implDictKeysetIdx(env, true); + if (arrType <= TKeyset) return implDictKeysetIdx(env, false); + if (!(arrType <= TArr)) { // raise fatal interpOne(env, TCell, 3); @@ -1606,6 +1681,10 @@ void emitIdx(IRGS& env) { auto const keyType = key->type(); auto const baseType = base->type(); + if (baseType <= TVec) return implVecIdx(env); + if (baseType <= TDict) return implDictKeysetIdx(env, true); + if (baseType <= TKeyset) return implDictKeysetIdx(env, false); + if (keyType <= TNull || !baseType.maybe(TArr | TObj | TStr)) { auto const def = popC(env, DataTypeGeneric); popC(env, keyType <= TNull ? DataTypeSpecific : DataTypeGeneric); @@ -1651,6 +1730,46 @@ void emitAKExists(IRGS& env) { auto const arr = popC(env); auto key = popC(env); + if (arr->isA(TVec)) { + if (key->isA(TNull | TStr)) { + push(env, cns(env, false)); + decRef(env, arr); + decRef(env, key); + return; + } + if (!key->isA(TInt)) { + gen(env, ThrowInvalidArrayKey, arr, key); + return; + } + auto const length = gen(env, CountVec, arr); + push(env, gen(env, CheckRange, key, length)); + decRef(env, arr); + return; + } + + if (arr->isA(TDict) || arr->isA(TKeyset)) { + if (key->isA(TNull)) { + push(env, cns(env, false)); + decRef(env, arr); + decRef(env, key); + return; + } + if (!key->isA(TInt) && !key->isA(TStr)) { + gen(env, ThrowInvalidArrayKey, arr, key); + return; + } + auto const val = gen( + env, + arr->isA(TDict) ? AKExistsDict : AKExistsKeyset, + arr, + key + ); + push(env, val); + decRef(env, arr); + decRef(env, key); + return; + } + if (!arr->isA(TArr) && !arr->isA(TObj)) PUNT(AKExists_badArray); if (key->isA(TInitNull)) { diff --git a/hphp/runtime/vm/jit/irgen-create.cpp b/hphp/runtime/vm/jit/irgen-create.cpp index f56aa2c382f..d59fd8e8873 100644 --- a/hphp/runtime/vm/jit/irgen-create.cpp +++ b/hphp/runtime/vm/jit/irgen-create.cpp @@ -364,17 +364,31 @@ void emitNewStructArray(IRGS& env, const ImmVector& immVec) { } void emitAddElemC(IRGS& env) { - // This is just to peek at the type; it'll be consumed for real down below and - // we don't want to constrain it if we're just going to InterpOne. + // This is just to peek at the types; they'll be consumed for real down below + // and we don't want to constrain it if we're just going to InterpOne. auto const kt = topC(env, BCSPRelOffset{1}, DataTypeGeneric)->type(); + auto const at = topC(env, BCSPRelOffset{2}, DataTypeGeneric)->type(); Opcode op; - if (kt <= TInt) { - op = AddElemIntKey; - } else if (kt <= TStr) { - op = AddElemStrKey; + if (at <= TArr) { + if (kt <= TInt) { + op = AddElemIntKey; + } else if (kt <= TStr) { + op = AddElemStrKey; + } else { + interpOne(env, TArr, 3); + return; + } + } else if (at <= TDict) { + if (kt <= TInt) { + op = DictAddElemIntKey; + } else if (kt <= TStr) { + op = DictAddElemStrKey; + } else { + interpOne(env, TDict, 3); + return; + } } else { - interpOne(env, TArr, 3); - return; + PUNT(AddElemC-BadArr); } // val is teleported from the stack to the array, so we don't have to do any diff --git a/hphp/runtime/vm/jit/irgen-minstr.cpp b/hphp/runtime/vm/jit/irgen-minstr.cpp index 65c9f181221..eb2419ee2e0 100644 --- a/hphp/runtime/vm/jit/irgen-minstr.cpp +++ b/hphp/runtime/vm/jit/irgen-minstr.cpp @@ -68,6 +68,9 @@ enum class SimpleOp { Array, ProfiledPackedArray, PackedArray, + VecArray, + Dict, + Keyset, String, Vector, // c_Vector* or c_ImmVector* Map, // c_Map* @@ -198,6 +201,9 @@ folly::Optional simpleOpConstraint(SimpleOp op) { case SimpleOp::Array: case SimpleOp::ProfiledPackedArray: + case SimpleOp::VecArray: + case SimpleOp::Dict: + case SimpleOp::Keyset: case SimpleOp::String: return TypeConstraint(DataTypeSpecific); @@ -458,6 +464,22 @@ SSATmp* emitPropSpecialized( ////////////////////////////////////////////////////////////////////// // "Simple op" handlers. +void checkBounds(IRGS& env, SSATmp* base, SSATmp* idx, SSATmp* limit) { + assertx(base->isA(TArrLike) || base->isA(TObj)); + + ifThen( + env, + [&](Block* taken) { + auto ok = gen(env, CheckRange, idx, limit); + gen(env, JmpZero, taken, ok); + }, + [&] { + hint(env, Block::Hint::Unlikely); + gen(env, ThrowOutOfBounds, base, idx); + } + ); +} + template SSATmp* emitPackedArrayGet(IRGS& env, SSATmp* base, SSATmp* key, Finish finish) { @@ -520,6 +542,96 @@ SSATmp* emitPackedArrayGet(IRGS& env, SSATmp* base, SSATmp* key, } template +SSATmp* emitVecArrayGet(IRGS& env, SSATmp* base, SSATmp* key, + Finish finish) { + assertx(base->isA(TVec)); + + if (!key->isA(TInt)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + auto const limit = gen(env, CountVec, base); + checkBounds(env, base, key, limit); + + auto finishMe = [&](SSATmp* elem) { + gen(env, IncRef, elem); + return elem; + }; + + auto const elem = gen(env, LdVecElem, base, key); + auto const pelem = profiledType(env, elem, [&] { finish(finishMe(elem)); }); + return finishMe(pelem); +} + +template +SSATmp* emitVecArrayQuietGet(IRGS& env, SSATmp* base, SSATmp* key, + Finish finish) { + assertx(base->isA(TVec)); + + if (key->isA(TStr)) return cns(env, TInitNull); + if (!key->isA(TInt)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + auto const elem = cond( + env, + [&] (Block* taken) { + auto const length = gen(env, CountVec, base); + auto const cmp = gen(env, CheckRange, key, length); + gen(env, JmpZero, taken, cmp); + }, + [&] { return gen(env, LdVecElem, base, key); }, + [&] { return cns(env, TInitNull); } + ); + + auto finishMe = [&](SSATmp* elem) { + gen(env, IncRef, elem); + return elem; + }; + + auto const pelem = profiledType(env, elem, [&] { finish(finishMe(elem)); }); + return finishMe(pelem); +} + +template +SSATmp* emitDictKeysetGet(IRGS& env, SSATmp* base, SSATmp* key, + bool quiet, bool is_dict, Finish finish) { + assertx(base->isA(is_dict ? TDict : TKeyset)); + + if (!key->isA(TInt | TStr)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + auto finishMe = [&](SSATmp* elem) { + gen(env, IncRef, elem); + return elem; + }; + + auto const elem = profiledArrayAccess( + env, base, key, + [&] (SSATmp* base, SSATmp* key, uint32_t pos) { + return gen(env, is_dict ? DictGetK : KeysetGetK, IndexData { pos }, + base, key); + }, + [&] (SSATmp* key) { + return gen( + env, + is_dict + ? (quiet ? DictGetQuiet : DictGet) + : (quiet ? KeysetGetQuiet : KeysetGet), + base, + key + ); + } + ); + auto const pelem = profiledType(env, elem, [&] { finish(finishMe(elem)); }); + return finishMe(pelem); +} + +template SSATmp* emitArrayGet(IRGS& env, SSATmp* base, SSATmp* key, Finish finish) { auto const elem = profiledArrayAccess(env, base, key, [&] (SSATmp* arr, SSATmp* key, uint32_t pos) { @@ -572,23 +684,9 @@ SSATmp* emitProfiledPackedArrayGet(IRGS& env, SSATmp* base, SSATmp* key, return emitArrayGet(env, base, key, finish); } -void checkBounds(IRGS& env, SSATmp* idx, SSATmp* limit) { - ifThen( - env, - [&](Block* taken) { - auto ok = gen(env, CheckRange, idx, limit); - gen(env, JmpZero, taken, ok); - }, - [&] { - hint(env, Block::Hint::Unlikely); - gen(env, ThrowOutOfBounds, idx); - } - ); -} - SSATmp* emitVectorGet(IRGS& env, SSATmp* base, SSATmp* key) { auto const size = gen(env, LdVectorSize, base); - checkBounds(env, key, size); + checkBounds(env, base, key, size); base = gen(env, LdVectorBase, base); static_assert(sizeof(TypedValue) == 16, "TypedValue size expected to be 16 bytes"); @@ -612,7 +710,7 @@ SSATmp* emitPairGet(IRGS& env, SSATmp* base, SSATmp* key) { static_assert(sizeof(TypedValue) == 16, "TypedValue size expected to be 16 bytes"); - checkBounds(env, key, cns(env, 2)); + checkBounds(env, base, key, cns(env, 2)); return gen(env, Shl, key, cns(env, 4)); }(); @@ -661,9 +759,94 @@ SSATmp* emitPackedArrayIsset(IRGS& env, SSATmp* base, SSATmp* key) { ); } +SSATmp* emitVecArrayIsset(IRGS& env, SSATmp* base, SSATmp* key) { + assertx(base->isA(TVec)); + + if (key->isA(TStr)) return cns(env, false); + if (!key->isA(TInt)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + return cond( + env, + [&] (Block* taken) { + auto const length = gen(env, CountVec, base); + auto const cmp = gen(env, CheckRange, key, length); + gen(env, JmpZero, taken, cmp); + }, + [&] { + auto const elem = gen(env, LdVecElem, base, key); + return gen(env, IsNType, TInitNull, elem); + }, + [&] { return cns(env, false); } + ); +} + +SSATmp* emitDictIsset(IRGS& env, SSATmp* base, SSATmp* key) { + assertx(base->isA(TDict)); + if (!key->isA(TInt | TStr)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + return gen(env, DictIsset, base, key); +} + +SSATmp* emitKeysetIsset(IRGS& env, SSATmp* base, SSATmp* key) { + assertx(base->isA(TKeyset)); + if (!key->isA(TInt | TStr)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + return gen(env, KeysetIsset, base, key); +} + +SSATmp* emitVecArrayEmptyElem(IRGS& env, SSATmp* base, SSATmp* key) { + assertx(base->isA(TVec)); + + if (key->isA(TStr)) return cns(env, true); + if (!key->isA(TInt)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + return cond( + env, + [&] (Block* taken) { + auto const length = gen(env, CountVec, base); + auto const cmp = gen(env, CheckRange, key, length); + gen(env, JmpZero, taken, cmp); + }, + [&] { + auto const elem = gen(env, LdVecElem, base, key); + auto const b = gen(env, ConvCellToBool, elem); + return gen(env, XorBool, b, cns(env, true)); + }, + [&] { return cns(env, true); } + ); +} + +SSATmp* emitDictEmptyElem(IRGS& env, SSATmp* base, SSATmp* key) { + assertx(base->isA(TDict)); + if (!key->isA(TInt | TStr)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + return gen(env, DictEmptyElem, base, key); +} + +SSATmp* emitKeysetEmptyElem(IRGS& env, SSATmp* base, SSATmp* key) { + assertx(base->isA(TKeyset)); + if (!key->isA(TInt | TStr)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + return gen(env, KeysetEmptyElem, base, key); +} + void emitVectorSet(IRGS& env, SSATmp* base, SSATmp* key, SSATmp* value) { auto const size = gen(env, LdVectorSize, base); - checkBounds(env, key, size); + checkBounds(env, base, key, size); ifThen( env, @@ -715,6 +898,7 @@ SSATmp* emitIncDecProp(IRGS& env, IncDecOp op, SSATmp* base, SSATmp* key) { template SSATmp* emitCGetElem(IRGS& env, SSATmp* base, SSATmp* key, MOpFlags flags, SimpleOp simpleOp, Finish finish) { + assertx(flags & MOpFlags::Warn); switch (simpleOp) { case SimpleOp::Array: return emitArrayGet(env, base, key, finish); @@ -722,6 +906,12 @@ SSATmp* emitCGetElem(IRGS& env, SSATmp* base, SSATmp* key, return emitPackedArrayGet(env, base, key, finish); case SimpleOp::ProfiledPackedArray: return emitProfiledPackedArrayGet(env, base, key, finish); + case SimpleOp::VecArray: + return emitVecArrayGet(env, base, key, finish); + case SimpleOp::Dict: + return emitDictKeysetGet(env, base, key, false, true, finish); + case SimpleOp::Keyset: + return emitDictKeysetGet(env, base, key, false, false, finish); case SimpleOp::String: return gen(env, StringGet, base, key); case SimpleOp::Vector: @@ -736,6 +926,30 @@ SSATmp* emitCGetElem(IRGS& env, SSATmp* base, SSATmp* key, always_assert(false); } +template +SSATmp* emitCGetElemQuiet(IRGS& env, SSATmp* base, SSATmp* basePtr, SSATmp* key, + MOpFlags flags, SimpleOp simpleOp, Finish finish) { + assertx(!(flags & MOpFlags::Warn)); + switch (simpleOp) { + case SimpleOp::VecArray: + return emitVecArrayQuietGet(env, base, key, finish); + case SimpleOp::Dict: + return emitDictKeysetGet(env, base, key, true, true, finish); + case SimpleOp::Keyset: + return emitDictKeysetGet(env, base, key, true, false, finish); + case SimpleOp::Array: + case SimpleOp::PackedArray: + case SimpleOp::ProfiledPackedArray: + case SimpleOp::String: + case SimpleOp::Vector: + case SimpleOp::Pair: + case SimpleOp::Map: + case SimpleOp::None: + return gen(env, CGetElem, MOpFlagsData{flags}, basePtr, key); + } + always_assert(false); +} + SSATmp* emitIssetElem(IRGS& env, SSATmp* base, SSATmp* key, SimpleOp simpleOp) { switch (simpleOp) { case SimpleOp::Array: @@ -743,6 +957,12 @@ SSATmp* emitIssetElem(IRGS& env, SSATmp* base, SSATmp* key, SimpleOp simpleOp) { return gen(env, ArrayIsset, base, key); case SimpleOp::PackedArray: return emitPackedArrayIsset(env, base, key); + case SimpleOp::VecArray: + return emitVecArrayIsset(env, base, key); + case SimpleOp::Dict: + return emitDictIsset(env, base, key); + case SimpleOp::Keyset: + return emitKeysetIsset(env, base, key); case SimpleOp::String: return gen(env, StringIsset, base, key); case SimpleOp::Vector: @@ -758,6 +978,29 @@ SSATmp* emitIssetElem(IRGS& env, SSATmp* base, SSATmp* key, SimpleOp simpleOp) { always_assert(false); } +SSATmp* emitEmptyElem(IRGS& env, SSATmp* base, SSATmp* basePtr, + SSATmp* key, SimpleOp simpleOp) { + switch (simpleOp) { + case SimpleOp::VecArray: + return emitVecArrayEmptyElem(env, base, key); + case SimpleOp::Dict: + return emitDictEmptyElem(env, base, key); + case SimpleOp::Keyset: + return emitKeysetEmptyElem(env, base, key); + case SimpleOp::Array: + case SimpleOp::PackedArray: + case SimpleOp::ProfiledPackedArray: + case SimpleOp::String: + case SimpleOp::Vector: + case SimpleOp::Pair: + case SimpleOp::Map: + case SimpleOp::None: + return gen(env, EmptyElem, basePtr, key); + } + + always_assert(false); +} + void setWithRefImpl(IRGS& env, int32_t keyLoc, SSATmp* value) { auto const key = ldLoc(env, keyLoc, nullptr, DataTypeGeneric); gen(env, SetWithRefElem, gen(env, LdMBase, TPtrToGen), key, value); @@ -779,6 +1022,12 @@ SimpleOp simpleCollectionOp(Type baseType, Type keyType, bool readInst) { } return SimpleOp::Array; } + } else if (baseType <= TVec) { + return SimpleOp::VecArray; + } else if (baseType <= TDict) { + return SimpleOp::Dict; + } else if (baseType <= TKeyset) { + return SimpleOp::Keyset; } else if (baseType <= TStr) { if (keyType <= TInt) { // Don't bother with SetM on strings, because profile data shows it @@ -951,6 +1200,129 @@ SSATmp* propImpl(IRGS& env, MOpFlags flags, SSATmp* key, bool nullsafe) { return emitPropSpecialized(env, base, key, nullsafe, flags, propInfo); } +SSATmp* vecElemImpl(IRGS& env, MOpFlags flags, SSATmp* base, + SSATmp* basePtr, SSATmp* key) { + assertx(base->isA(TVec)); + + auto const warn = flags & MOpFlags::Warn; + auto const unset = flags & MOpFlags::Unset; + auto const define = flags & MOpFlags::Define; + + env.irb->constrainValue(base, DataTypeSpecific); + + if (define) { + if (key->isA(TInt)) return gen(env, ElemVecD, base->type(), basePtr, key); + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + if (unset) { + if (key->isA(TInt)) return gen(env, ElemVecU, base->type(), basePtr, key); + if (key->isA(TStr)) return ptrToInitNull(env); + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + if (warn) { + if (key->isA(TInt)) { + auto const elemType = vecElemType(base, key).ptr(Ptr::Elem); + auto const length = gen(env, CountVec, base); + checkBounds(env, base, key, length); + return gen(env, LdVecElemAddr, elemType, base, key); + } + + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + if (key->isA(TInt)) { + auto const elemType = vecElemType(base, key).ptr(Ptr::Elem); + return cond( + env, + [&] (Block* taken) { + auto const length = gen(env, CountVec, base); + auto const cmp = gen(env, CheckRange, key, length); + gen(env, JmpZero, taken, cmp); + }, + [&] { return gen(env, LdVecElemAddr, elemType, base, key); }, + [&] { return ptrToInitNull(env); } + ); + } + + if (key->isA(TStr)) return ptrToInitNull(env); + + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); +} + +SSATmp* dictElemImpl(IRGS& env, MOpFlags flags, SSATmp* base, + SSATmp* basePtr, SSATmp* key) { + assertx(base->isA(TDict)); + + auto const warn = flags & MOpFlags::Warn; + auto const unset = flags & MOpFlags::Unset; + auto const define = flags & MOpFlags::Define; + + env.irb->constrainValue(base, DataTypeSpecific); + + if (!key->isA(TInt | TStr)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + return profiledArrayAccess( + env, base, key, + [&] (SSATmp* dict, SSATmp* key, uint32_t pos) { + return gen(env, ElemDictK, IndexData { pos }, dict, key); + }, + [&] (SSATmp* key) { + if (define || unset) { + return gen(env, unset ? ElemDictU : ElemDictD, + base->type(), basePtr, key); + } + return gen(env, warn ? ElemDictW : ElemDict, base, key); + }, + define || unset // cow check + ); +} + +SSATmp* keysetElemImpl(IRGS& env, MOpFlags flags, SSATmp* base, + SSATmp* basePtr, SSATmp* key) { + assertx(base->isA(TKeyset)); + + auto const warn = flags & MOpFlags::Warn; + auto const unset = flags & MOpFlags::Unset; + auto const define = flags & MOpFlags::Define; + + env.irb->constrainValue(base, DataTypeSpecific); + + if (!key->isA(TInt | TStr)) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + if (define) { + gen( + env, + ThrowInvalidOperation, + cns(env, s_InvalidKeysetOperationMsg.get()) + ); + return cns(env, TBottom); + } + + return profiledArrayAccess( + env, base, key, + [&] (SSATmp* keyset, SSATmp* key, uint32_t pos) { + return gen(env, ElemKeysetK, IndexData { pos }, keyset, key); + }, + [&] (SSATmp* key) { + if (unset) return gen(env, ElemKeysetU, base->type(), basePtr, key); + return gen(env, warn ? ElemKeysetW : ElemKeyset, base, key); + }, + unset // cow check + ); +} + const StaticString s_OP_NOT_SUPPORTED_STRING(Strings::OP_NOT_SUPPORTED_STRING); SSATmp* elemImpl(IRGS& env, MOpFlags flags, SSATmp* key) { auto const warn = flags & MOpFlags::Warn; @@ -963,6 +1335,14 @@ SSATmp* elemImpl(IRGS& env, MOpFlags flags, SSATmp* key) { assertx(!define || !unset); assertx(!define || !warn); + if (base) { + if (base->isA(TVec)) return vecElemImpl(env, flags, base, basePtr, key); + if (base->isA(TDict)) return dictElemImpl(env, flags, base, basePtr, key); + if (base->isA(TKeyset)) { + return keysetElemImpl(env, flags, base, basePtr, key); + } + } + if (base && base->isA(TArr) && key->type().subtypeOfAny(TInt, TStr)) { env.irb->constrainValue(base, DataTypeSpecific); @@ -977,7 +1357,7 @@ SSATmp* elemImpl(IRGS& env, MOpFlags flags, SSATmp* key) { } return gen(env, warn ? ElemArrayW : ElemArray, base, key); }, - true // cow_check + define || unset // cow check ); } @@ -988,7 +1368,7 @@ SSATmp* elemImpl(IRGS& env, MOpFlags flags, SSATmp* key) { return ptrToUninit(env); } - if (!baseType.maybe(TArr | TObj)) { + if (!baseType.maybe(TArrLike | TObj)) { return ptrToUninit(env); } } @@ -1152,7 +1532,7 @@ void handleStrTestResult(IRGS& env, SSATmp* strTestResult) { ); } -SSATmp* emitArraySet(IRGS& env, SSATmp* key, SSATmp* value) { +SSATmp* emitArrayLikeSet(IRGS& env, SSATmp* key, SSATmp* value) { // We need to store to a local after doing some user-visible operations, so // don't go down this path for pseudomains. if (curFunc(env)->isPseudoMain()) return nullptr; @@ -1161,6 +1541,27 @@ SSATmp* emitArraySet(IRGS& env, SSATmp* key, SSATmp* value) { auto const basePtr = gen(env, LdMBase, TPtrToGen); auto const ptrInst = basePtr->inst(); + assertx(base->isA(TArrLike)); + + auto const isVec = base->isA(TVec); + auto const isDict = base->isA(TDict); + auto const isKeyset = base->isA(TKeyset); + + if ((isVec && !key->isA(TInt)) || + (isDict && !key->isA(TInt | TStr))) { + gen(env, ThrowInvalidArrayKey, base, key); + return cns(env, TBottom); + } + + if (isKeyset) { + gen( + env, + ThrowInvalidOperation, + cns(env, s_InvalidKeysetOperationMsg.get()) + ); + return cns(env, TBottom); + } + auto const baseLoc = [&]() -> folly::Optional { switch (ptrInst->op()) { case LdLocAddr: { @@ -1193,14 +1594,18 @@ SSATmp* emitArraySet(IRGS& env, SSATmp* key, SSATmp* value) { } not_reached(); }(); - gen(env, ArraySetRef, base, key, value, box); + gen(env, + isVec ? VecSetRef : isDict ? DictSetRef : ArraySetRef, + base, key, value, box); // Unlike the non-ref case, we don't need to do anything to the stack/local // because any load of the box will be guarded. return value; } - auto const newArr = gen(env, ArraySet, base, key, value); + auto const newArr = gen(env, + isVec ? VecSet : isDict ? DictSet : ArraySet, + base, key, value); // Update the base's location with the new array. switch (baseLoc->tag()) { @@ -1226,6 +1631,16 @@ SSATmp* setNewElemImpl(IRGS& env) { if (base && base->isA(TArr)) { env.irb->constrainValue(base, DataTypeSpecific); gen(env, SetNewElemArray, makeCatchSet(env), basePtr, value); + } else if (base && base->isA(TVec)) { + env.irb->constrainValue(base, DataTypeSpecific); + gen(env, SetNewElemVec, makeCatchSet(env), basePtr, value); + } else if (base && base->isA(TKeyset)) { + env.irb->constrainValue(base, DataTypeSpecific); + if (!value->isA(TInt | TStr)) { + gen(env, ThrowInvalidArrayKey, makeCatchSet(env), base, value); + } else { + gen(env, SetNewElemKeyset, makeCatchSet(env), basePtr, value); + } } else { gen(env, SetNewElem, makeCatchSet(env), basePtr, value); } @@ -1259,7 +1674,10 @@ SSATmp* setElemImpl(IRGS& env, SSATmp* key) { case SimpleOp::Array: case SimpleOp::ProfiledPackedArray: - if (auto result = emitArraySet(env, key, value)) { + case SimpleOp::VecArray: + case SimpleOp::Dict: + case SimpleOp::Keyset: + if (auto result = emitArrayLikeSet(env, key, value)) { return result; } // If we couldn't emit ArraySet, fall through to the generic path. @@ -1449,7 +1867,10 @@ void emitQueryM(IRGS& env, int32_t nDiscard, QueryMOp query, MemberKey mk) { // If we don't have the base available, we might still be able to get a value // with a good type from its pointer. - if (base == nullptr && (basePtr->type().subtypeOfAny(TPtrToArr, TPtrToObj))) { + if (base == nullptr && + (basePtr->type().subtypeOfAny( + TPtrToArr, TPtrToVec, TPtrToDict, TPtrToKeyset, TPtrToObj + ))) { base = gen(env, LdMem, basePtr->type().deref(), basePtr); } @@ -1457,19 +1878,18 @@ void emitQueryM(IRGS& env, int32_t nDiscard, QueryMOp query, MemberKey mk) { auto key = memberKey(env, mk); auto simpleOp = SimpleOp::None; - if (base && mcodeIsElem(mk.mcode) && - query != QueryMOp::Empty && query != QueryMOp::CGetQuiet) { + if (base && mcodeIsElem(mk.mcode)) { simpleOp = simpleCollectionOp(base->type(), key->type(), true); - - if (auto tc = simpleOpConstraint(simpleOp)) { - env.irb->constrainValue(base, *tc); + if (simpleOp != SimpleOp::None) { + if (auto tc = simpleOpConstraint(simpleOp)) { + env.irb->constrainValue(base, *tc); + } } } auto const result = [&] { switch (query) { - case QueryMOp::CGet: - case QueryMOp::CGetQuiet: { + case QueryMOp::CGet: { auto const flags = getQueryMOpFlags(query); if (mcodeIsProp(mk.mcode)) { return cGetPropImpl(env, objBase, key, flags, mk.mcode == MQT); @@ -1479,6 +1899,17 @@ void emitQueryM(IRGS& env, int32_t nDiscard, QueryMOp query, MemberKey mk) { [&](SSATmp* el) { mFinalImpl(env, nDiscard, el); }); } + case QueryMOp::CGetQuiet: { + auto const flags = getQueryMOpFlags(query); + if (mcodeIsProp(mk.mcode)) { + return cGetPropImpl(env, objBase, key, flags, mk.mcode == MQT); + } + return emitCGetElemQuiet( + env, base, basePtr, key, flags, simpleOp, + [&](SSATmp* el) { mFinalImpl(env, nDiscard, el); } + ); + } + case QueryMOp::Isset: { if (mcodeIsProp(mk.mcode)) { return gen(env, IssetProp, objBase, key); @@ -1487,9 +1918,12 @@ void emitQueryM(IRGS& env, int32_t nDiscard, QueryMOp query, MemberKey mk) { return emitIssetElem(env, realBase, key, simpleOp); } - case QueryMOp::Empty: - return mcodeIsProp(mk.mcode) ? gen(env, EmptyProp, objBase, key) - : gen(env, EmptyElem, basePtr, key); + case QueryMOp::Empty: { + if (mcodeIsProp(mk.mcode)) { + return gen(env, EmptyProp, objBase, key); + } + return emitEmptyElem(env, base, basePtr, key, simpleOp); + } } not_reached(); }(); diff --git a/hphp/runtime/vm/jit/irgen-minstr.h b/hphp/runtime/vm/jit/irgen-minstr.h index 116293dadee..22b67a9de63 100644 --- a/hphp/runtime/vm/jit/irgen-minstr.h +++ b/hphp/runtime/vm/jit/irgen-minstr.h @@ -39,8 +39,8 @@ namespace HPHP { namespace jit { namespace irgen { * * For profiling translations, this generates the profiling instructions, then * falls back to `generic'. If we can perform the optimization, this branches - * on a CheckMixedArrayOffset to either `direct' or `generic'; otherwise, we - * fall back to `generic'. + * on a CheckMixedArrayOffset/CheckDictOffset/CheckKeysetOffset to either + * `direct' or `generic'; otherwise, we fall back to `generic'. * * The callback function signatures should be: * @@ -51,15 +51,34 @@ template SSATmp* profiledArrayAccess(IRGS& env, SSATmp* arr, SSATmp* key, DirectFn direct, GenericFn generic, bool cow_check = false) { + const bool is_dict = arr->isA(TDict); + const bool is_keyset = arr->isA(TKeyset); + assertx(is_dict || is_keyset || arr->isA(TArr)); + + // If the access is statically known, don't bother profiling as we'll probably + // optimize it away completely. + if (arr->hasConstVal() && key->hasConstVal()) return generic(key); + auto const profile = TargetProfile { env.context, env.irb->curMarker(), - makeStaticString("MixedArrayOffset") + makeStaticString( + is_dict ? "DictOffset" : + is_keyset ? "KeysetOffset" : + "MixedArrayOffset" + ) }; if (profile.profiling()) { - gen(env, ProfileMixedArrayOffset, - RDSHandleData { profile.handle() }, arr, key); + gen( + env, + is_dict ? ProfileDictOffset : + is_keyset ? ProfileKeysetOffset : + ProfileMixedArrayOffset, + RDSHandleData { profile.handle() }, + arr, + key + ); return generic(key); } @@ -70,15 +89,29 @@ SSATmp* profiledArrayAccess(IRGS& env, SSATmp* arr, SSATmp* key, return cond( env, [&] (Block* taken) { - env.irb->constrainValue( - arr, - TypeConstraint(DataTypeSpecialized).setWantArrayKind() - ); - auto const TMixedArr = Type::Array(ArrayData::kMixedKind); - auto const marr = gen(env, CheckType, TMixedArr, taken, arr); + SSATmp* marr; + if (!is_dict && !is_keyset) { + env.irb->constrainValue( + arr, + TypeConstraint(DataTypeSpecialized).setWantArrayKind() + ); + auto const TMixedArr = Type::Array(ArrayData::kMixedKind); + marr = gen(env, CheckType, TMixedArr, taken, arr); + } else { + marr = arr; + } auto const extra = IndexData { *pos }; - gen(env, CheckMixedArrayOffset, extra, taken, marr, key); + gen( + env, + is_dict ? CheckDictOffset : + is_keyset ? CheckKeysetOffset : + CheckMixedArrayOffset, + extra, + taken, + marr, + key + ); if (cow_check) { gen(env, CheckArrayCOW, taken, marr); diff --git a/hphp/runtime/vm/jit/irlower-exception.cpp b/hphp/runtime/vm/jit/irlower-exception.cpp index 3b4d4152eb5..bee1392f559 100644 --- a/hphp/runtime/vm/jit/irlower-exception.cpp +++ b/hphp/runtime/vm/jit/irlower-exception.cpp @@ -89,7 +89,8 @@ IMPL_OPCODE_CALL(RaiseUninitLoc) IMPL_OPCODE_CALL(RaiseWarning) IMPL_OPCODE_CALL(ThrowArithmeticError) IMPL_OPCODE_CALL(ThrowDivisionByZeroError) -IMPL_OPCODE_CALL(ThrowInvalidOperation); +IMPL_OPCODE_CALL(ThrowInvalidArrayKey) +IMPL_OPCODE_CALL(ThrowInvalidOperation) IMPL_OPCODE_CALL(ThrowOutOfBounds) /////////////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/vm/jit/irlower-minstr.cpp b/hphp/runtime/vm/jit/irlower-minstr.cpp index 24e7ce3fe04..aba909b8e11 100644 --- a/hphp/runtime/vm/jit/irlower-minstr.cpp +++ b/hphp/runtime/vm/jit/irlower-minstr.cpp @@ -326,10 +326,6 @@ void cgEmptyElem(IRLS& env, const IRInstruction* i) { namespace { -ptrdiff_t elmOff(uint32_t pos) { - return sizeof(MixedArray) + pos * sizeof(MixedArray::Elm); -} - /* * Make an ArgGroup for array elem instructions that takes: * 1/ the ArrayData* (or pointer to a KindOfArray TV) @@ -378,6 +374,111 @@ void implArraySet(IRLS& env, const IRInstruction* inst) { SyncOptions::Sync, args); } +void implVecSet(IRLS& env, const IRInstruction* inst) { + bool const setRef = inst->op() == VecSetRef; + BUILD_OPTAB(VECSET_HELPER_TABLE, setRef); + + auto args = argGroup(env, inst). + ssa(0). + ssa(1). + typedValue(2); + if (setRef) args.ssa(3); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void implDictSet(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + bool const setRef = inst->op() == DictSetRef; + BUILD_OPTAB(DICTSET_HELPER_TABLE, getKeyType(key), setRef); + + auto args = argGroup(env, inst). + ssa(0). + ssa(1). + typedValue(2); + if (setRef) args.ssa(3); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void implElemDict(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + auto const flags = inst->op() == ElemDictW ? MOpFlags::Warn : MOpFlags::None; + BUILD_OPTAB(ELEM_DICT_HELPER_TABLE, getKeyType(key), flags); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void implDictGet(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + auto const flags = + (inst->op() == DictGetQuiet) ? MOpFlags::None : MOpFlags::Warn; + BUILD_OPTAB(DICTGET_HELPER_TABLE, getKeyType(key), flags); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDestTV(env, inst), + SyncOptions::Sync, args); +} + +void implDictIsset(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + auto const empty = inst->op() == DictEmptyElem; + BUILD_OPTAB(DICT_ISSET_EMPTY_ELEM_HELPER_TABLE, getKeyType(key), empty); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void implKeysetGet(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + auto const flags = + (inst->op() == KeysetGetQuiet) ? MOpFlags::None : MOpFlags::Warn; + BUILD_OPTAB(KEYSETGET_HELPER_TABLE, getKeyType(key), flags); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDestTV(env, inst), + SyncOptions::Sync, args); +} + +void implElemKeyset(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + auto const flags = inst->op() == ElemKeysetW ? MOpFlags::Warn : MOpFlags::None; + BUILD_OPTAB(ELEM_KEYSET_HELPER_TABLE, getKeyType(key), flags); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void implKeysetIsset(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + auto const empty = inst->op() == KeysetEmptyElem; + BUILD_OPTAB(KEYSET_ISSET_EMPTY_ELEM_HELPER_TABLE, getKeyType(key), empty); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + } /////////////////////////////////////////////////////////////////////////////// @@ -413,10 +514,10 @@ void cgCheckMixedArrayOffset(IRLS& env, const IRInstruction* inst) { } { // Fail if the Elm key value doesn't match. auto const sf = v.makeReg(); - v << cmpqm{key, arr[elmOff(pos) + MixedArray::Elm::keyOff()], sf}; + v << cmpqm{key, arr[MixedArray::elmOff(pos) + MixedArray::Elm::keyOff()], sf}; ifThen(v, CC_NE, sf, branch); } - auto const dataOff = elmOff(pos) + MixedArray::Elm::dataOff(); + auto const dataOff = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); { // Fail if the Elm key type doesn't match. auto const sf = v.makeReg(); @@ -452,6 +553,116 @@ void cgCheckArrayCOW(IRLS& env, const IRInstruction* inst) { /////////////////////////////////////////////////////////////////////////////// +void cgProfileDictOffset(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + + BUILD_OPTAB(PROFILE_DICT_OFFSET_HELPER_TABLE, getKeyType(key)); + auto& v = vmain(env); + + auto const rprof = v.makeReg(); + v << lea{rvmtl()[inst->extra()->handle], rprof}; + + auto args = argGroup(env, inst).ssa(0).ssa(1).reg(rprof); + + cgCallHelper(v, env, CallSpec::direct(opFunc), kVoidDest, SyncOptions::Sync, + args); +} + +void cgCheckDictOffset(IRLS& env, const IRInstruction* inst) { + auto const dict = srcLoc(env, inst, 0).reg(); + auto const key = srcLoc(env, inst, 1).reg(); + auto const branch = label(env, inst->taken()); + auto const pos = inst->extra()->index; + auto& v = vmain(env); + + { // Also fail if our predicted position exceeds bounds. + auto const sf = v.makeReg(); + v << cmplim{safe_cast(pos), dict[MixedArray::usedOff()], sf}; + ifThen(v, CC_LE, sf, branch); + } + { // Fail if the Elm key value doesn't match. + auto const sf = v.makeReg(); + v << cmpqm{key, dict[MixedArray::elmOff(pos) + MixedArray::Elm::keyOff()], sf}; + ifThen(v, CC_NE, sf, branch); + } + auto const dataOff = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); + + { // Fail if the Elm key type doesn't match. + auto const sf = v.makeReg(); + v << cmplim{0, dict[dataOff + TVOFF(m_aux)], sf}; + + auto const key_type = getKeyType(inst->src(1)); + assertx(key_type != KeyType::Any); + + // Note that if `key' actually is an integer-ish string, we'd fail this + // check (and most likely would have failed the previous check also), but + // this false negative is allowed. + auto const is_str_key = key_type == KeyType::Str; + ifThen(v, is_str_key ? CC_L : CC_GE, sf, branch); + } + { // Fail if the Elm is a tombstone. See MixedArray::isTombstone(). + auto const sf = v.makeReg(); + v << cmpbim{KindOfUninit, dict[dataOff + TVOFF(m_type)], sf}; + ifThen(v, CC_L, sf, branch); + } +} + +void cgProfileKeysetOffset(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + + BUILD_OPTAB(PROFILE_KEYSET_OFFSET_HELPER_TABLE, getKeyType(key)); + auto& v = vmain(env); + + auto const rprof = v.makeReg(); + v << lea{rvmtl()[inst->extra()->handle], rprof}; + + auto args = argGroup(env, inst).ssa(0).ssa(1).reg(rprof); + + cgCallHelper(v, env, CallSpec::direct(opFunc), kVoidDest, SyncOptions::Sync, + args); +} + +void cgCheckKeysetOffset(IRLS& env, const IRInstruction* inst) { + auto const keyset = srcLoc(env, inst, 0).reg(); + auto const key = srcLoc(env, inst, 1).reg(); + auto const branch = label(env, inst->taken()); + auto const pos = inst->extra()->index; + auto& v = vmain(env); + + { // Also fail if our predicted position exceeds bounds. + auto const sf = v.makeReg(); + v << cmplim{safe_cast(pos), keyset[MixedArray::usedOff()], sf}; + ifThen(v, CC_LE, sf, branch); + } + { // Fail if the Elm key value doesn't match. + auto const sf = v.makeReg(); + v << cmpqm{key, keyset[MixedArray::elmOff(pos) + MixedArray::Elm::keyOff()], sf}; + ifThen(v, CC_NE, sf, branch); + } + auto const dataOff = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); + + { // Fail if the Elm key type doesn't match. + auto const sf = v.makeReg(); + v << cmplim{0, keyset[dataOff + TVOFF(m_aux)], sf}; + + auto const key_type = getKeyType(inst->src(1)); + assertx(key_type != KeyType::Any); + + // Note that if `key' actually is an integer-ish string, we'd fail this + // check (and most likely would have failed the previous check also), but + // this false negative is allowed. + auto const is_str_key = key_type == KeyType::Str; + ifThen(v, is_str_key ? CC_L : CC_GE, sf, branch); + } + { // Fail if the Elm is a tombstone. See MixedArray::isTombstone(). + auto const sf = v.makeReg(); + v << cmpbim{KindOfUninit, keyset[dataOff + TVOFF(m_type)], sf}; + ifThen(v, CC_L, sf, branch); + } +} + +/////////////////////////////////////////////////////////////////////////////// + void cgElemArray(IRLS& env, const IRInstruction* i) { implElemArray(env, i); } void cgElemArrayW(IRLS& env, const IRInstruction* i) { implElemArray(env, i); } @@ -479,7 +690,7 @@ void cgElemMixedArrayK(IRLS& env, const IRInstruction* inst) { auto const arr = srcLoc(env, inst, 0).reg(); auto const dst = dstLoc(env, inst, 0); auto const pos = inst->extra()->index; - auto const off = elmOff(pos) + MixedArray::Elm::dataOff(); + auto const off = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); auto& v = vmain(env); @@ -501,7 +712,7 @@ void cgArrayGet(IRLS& env, const IRInstruction* inst) { void cgMixedArrayGetK(IRLS& env, const IRInstruction* inst) { auto const arr = srcLoc(env, inst, 0).reg(); auto const pos = inst->extra()->index; - auto const off = elmOff(pos) + MixedArray::Elm::dataOff(); + auto const off = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); auto& v = vmain(env); loadTV(v, inst->dst(0), dstLoc(env, inst, 0), arr[off]); @@ -541,6 +752,160 @@ void cgArrayIdx(IRLS& env, const IRInstruction* inst) { arrArgs(env, inst, keyInfo).typedValue(2)); } +///////////////////////////////////////////////////////////////////////////// + +void cgVecSet(IRLS& env, const IRInstruction* i) { implVecSet(env, i); } +void cgVecSetRef(IRLS& env, const IRInstruction* i) { implVecSet(env, i); } + +////////////////////////////////////////////////////////////////////////////// + +void cgElemDict(IRLS& env, const IRInstruction* i) { implElemDict(env, i); } +void cgElemDictW(IRLS& env, const IRInstruction* i) { implElemDict(env, i); } + +void cgElemDictD(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + BUILD_OPTAB(ELEM_DICT_D_HELPER_TABLE, getKeyType(key)); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void cgElemDictU(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + BUILD_OPTAB(ELEM_DICT_U_HELPER_TABLE, getKeyType(key)); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void cgElemDictK(IRLS& env, const IRInstruction* inst) { + auto const dict = srcLoc(env, inst, 0).reg(); + auto const dst = dstLoc(env, inst, 0); + auto const pos = inst->extra()->index; + auto const off = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); + + auto& v = vmain(env); + + assertx(dst.numAllocated() == 1); + v << lea{dict[off], dst.reg()}; +} + +void cgDictGet(IRLS& env, const IRInstruction* inst) { + implDictGet(env, inst); +} +void cgDictGetQuiet(IRLS& env, const IRInstruction* inst) { + implDictGet(env, inst); +} + +void cgDictGetK(IRLS& env, const IRInstruction* inst) { + auto const dict = srcLoc(env, inst, 0).reg(); + auto const pos = inst->extra()->index; + auto const off = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); + + auto& v = vmain(env); + loadTV(v, inst->dst(0), dstLoc(env, inst, 0), dict[off]); +} + +void cgDictSet(IRLS& env, const IRInstruction* i) { implDictSet(env, i); } +void cgDictSetRef(IRLS& env, const IRInstruction* i) { implDictSet(env, i); } + +void cgDictIsset(IRLS& env, const IRInstruction* inst) { + implDictIsset(env, inst); +} +void cgDictEmptyElem(IRLS& env, const IRInstruction* inst) { + implDictIsset(env, inst); +} + +void cgDictIdx(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + auto const target = getKeyType(key) == KeyType::Int + ? CallSpec::direct(dictIdxI) + : CallSpec::direct(dictIdxS); + auto args = argGroup(env, inst).ssa(0).ssa(1).typedValue(2); + auto& v = vmain(env); + cgCallHelper(v, env, target, callDestTV(env, inst), + SyncOptions::Sync, args); +} + +//////////////////////////////////////////////////////////////////////////////// + +void cgElemKeyset(IRLS& env, const IRInstruction* i) { implElemKeyset(env, i); } +void cgElemKeysetW(IRLS& env, const IRInstruction* i) { implElemKeyset(env, i); } + +void cgElemKeysetU(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + BUILD_OPTAB(ELEM_KEYSET_U_HELPER_TABLE, getKeyType(key)); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void cgElemKeysetK(IRLS& env, const IRInstruction* inst) { + auto const keyset = srcLoc(env, inst, 0).reg(); + auto const dst = dstLoc(env, inst, 0); + auto const pos = inst->extra()->index; + auto const off = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); + + auto& v = vmain(env); + + assertx(dst.numAllocated() == 1); + v << lea{keyset[off], dst.reg()}; +} + +void cgKeysetGet(IRLS& env, const IRInstruction* inst) { + implKeysetGet(env, inst); +} +void cgKeysetGetQuiet(IRLS& env, const IRInstruction* inst) { + implKeysetGet(env, inst); +} + +void cgKeysetGetK(IRLS& env, const IRInstruction* inst) { + auto const keyset = srcLoc(env, inst, 0).reg(); + auto const pos = inst->extra()->index; + auto const off = MixedArray::elmOff(pos) + MixedArray::Elm::dataOff(); + + auto& v = vmain(env); + loadTV(v, inst->dst(0), dstLoc(env, inst, 0), keyset[off]); +} + +void cgSetNewElemKeyset(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + BUILD_OPTAB(KEYSET_SETNEWELEM_HELPER_TABLE, getKeyType(key)); + + auto args = argGroup(env, inst).ssa(0).ssa(1); + + auto& v = vmain(env); + cgCallHelper(v, env, CallSpec::direct(opFunc), callDest(env, inst), + SyncOptions::Sync, args); +} + +void cgKeysetIsset(IRLS& env, const IRInstruction* inst) { + implKeysetIsset(env, inst); +} +void cgKeysetEmptyElem(IRLS& env, const IRInstruction* inst) { + implKeysetIsset(env, inst); +} + +void cgKeysetIdx(IRLS& env, const IRInstruction* inst) { + auto const key = inst->src(1); + auto const target = getKeyType(key) == KeyType::Int + ? CallSpec::direct(keysetIdxI) + : CallSpec::direct(keysetIdxS); + auto args = argGroup(env, inst).ssa(0).ssa(1).typedValue(2); + auto& v = vmain(env); + cgCallHelper(v, env, target, callDestTV(env, inst), + SyncOptions::Sync, args); +} + /////////////////////////////////////////////////////////////////////////////// void cgMapGet(IRLS& env, const IRInstruction* inst) { diff --git a/hphp/runtime/vm/jit/memory-effects.cpp b/hphp/runtime/vm/jit/memory-effects.cpp index 6b2e85063e7..a6241f7920f 100644 --- a/hphp/runtime/vm/jit/memory-effects.cpp +++ b/hphp/runtime/vm/jit/memory-effects.cpp @@ -152,18 +152,21 @@ AliasClass pointee( }; if (typeNR <= TPtrToElemGen) { - if (sinst->is(LdPackedArrayElemAddr)) return elem(); + if (sinst->is(LdPackedArrayElemAddr, LdVecElemAddr)) return elem(); return AElemAny; } // The result of ElemArray{,W,U} is either the address of an array element, // or &init_null_variant(). if (typeNR <= TPtrToMembGen) { - if (sinst->is(ElemArray, ElemArrayW)) return elem(); + if (sinst->is(ElemArray, ElemArrayW, ElemDict, + ElemDictW, ElemKeyset, ElemKeysetW)) return elem(); // Takes a PtrToGen as its first operand, so we can't easily grab an array // base. - if (sinst->is(ElemArrayU)) return AElemAny; + if (sinst->is(ElemArrayU, ElemVecU, ElemDictU, ElemKeysetU)) { + return AElemAny; + } // These instructions can only get at tvRef when given it as a // src. Otherwise they can only return pointers to properties or @@ -907,6 +910,16 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { inst.src(1) }; + case LdVecElem: { + auto const base = inst.src(0); + auto const key = inst.src(1); + always_assert(base->isA(TVec)); + always_assert(key->isA(TInt)); + return PureLoad { + key->hasConstVal() ? AElemI { base, key->intVal() } : AElemIAny + }; + } + case InitPackedLayoutArrayLoop: { auto const extra = inst.extra(); @@ -944,14 +957,51 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { return may_load_store_move(stack_in, AEmpty, stack_in); } + case MixedArrayGetK: + case DictGetK: + case KeysetGetK: { + auto const base = inst.src(0); + auto const key = inst.src(1); + always_assert(key->isA(TInt | TStr)); + if (key->isA(TInt)) { + return PureLoad { + key->hasConstVal() ? AElemI { base, key->intVal() } : AElemIAny + }; + } + if (key->isA(TStr)) { + return PureLoad { + key->hasConstVal() ? AElemS { base, key->strVal() } : AElemSAny + }; + } + return PureLoad { AElemAny }; + } + + case ElemMixedArrayK: + case ElemDictK: + case ElemKeysetK: + return IrrelevantEffects {}; + case ProfileMixedArrayOffset: case CheckMixedArrayOffset: case CheckArrayCOW: + case ProfileDictOffset: + case ProfileKeysetOffset: + case CheckDictOffset: + case CheckKeysetOffset: return may_load_store(AHeapAny, AEmpty); - case ElemMixedArrayK: - case MixedArrayGetK: + case ArrayIsset: + case DictGetQuiet: + case DictIsset: + case DictEmptyElem: + case DictIdx: + case KeysetGetQuiet: + case KeysetIsset: + case KeysetEmptyElem: + case KeysetIdx: case AKExistsArr: + case AKExistsDict: + case AKExistsKeyset: return may_load_store(AElemAny, AEmpty); case SameVec: @@ -993,6 +1043,8 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { case VGetElem: case SetElem: case SetNewElemArray: + case SetNewElemVec: + case SetNewElemKeyset: case SetNewElem: case SetOpElem: case SetWithRefElem: @@ -1002,6 +1054,11 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { case IncDecElem: case ElemArrayD: case ElemArrayU: + case ElemVecD: + case ElemVecU: + case ElemDictD: + case ElemDictU: + case ElemKeysetU: case VGetProp: case UnsetProp: case IncDecProp: @@ -1017,7 +1074,7 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { /* * Intermediate minstr operations. In addition to a base pointer like the - * operations agove, these may take a pointer to MInstrState::tvRef, which + * operations above, these may take a pointer to MInstrState::tvRef, which * they may store to (but not read from). */ case ElemX: @@ -1182,6 +1239,7 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { case LdPropAddr: case LdStkAddr: case LdPackedArrayElemAddr: + case LdVecElemAddr: case LteBool: case LteDbl: case LteInt: @@ -1313,6 +1371,7 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { case ConvBoolToStr: case CountArray: case CountArrayFast: + case CountVec: case StAsyncArResult: case StAsyncArResume: case StAsyncArSucceeded: @@ -1552,12 +1611,23 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { case AddElemIntKey: // decrefs value case AddElemStrKey: // decrefs value case AddNewElem: // decrefs value + case DictAddElemIntKey: // decrefs value + case DictAddElemStrKey: // decrefs value case ArrayGet: // kVPackedKind warnings - case ArrayIsset: // kVPackedKind warnings case ArraySet: // kVPackedKind warnings case ArraySetRef: // kVPackedKind warnings + case DictGet: + case KeysetGet: + case VecSet: + case VecSetRef: + case DictSet: + case DictSetRef: case ElemArray: case ElemArrayW: + case ElemDict: + case ElemDictW: + case ElemKeyset: + case ElemKeysetW: case GetMemoKey: // re-enters to call getInstanceKey() in some cases case LdClsCtor: case ConcatStrStr: @@ -1587,6 +1657,7 @@ MemEffects memory_effects_impl(const IRInstruction& inst) { case ConvObjToDict: case ConvObjToKeyset: case ThrowOutOfBounds: + case ThrowInvalidArrayKey: case ThrowInvalidOperation: case ThrowArithmeticError: case ThrowDivisionByZeroError: diff --git a/hphp/runtime/vm/jit/minstr-effects.cpp b/hphp/runtime/vm/jit/minstr-effects.cpp index c931b02c5ef..4f8134e75ce 100644 --- a/hphp/runtime/vm/jit/minstr-effects.cpp +++ b/hphp/runtime/vm/jit/minstr-effects.cpp @@ -73,12 +73,19 @@ void getBaseType(Opcode rawOp, bool predict, } if ((op == SetElem || op == UnsetElem || op == SetWithRefElem) && - baseType.maybe(TArr | TStr)) { + baseType.maybe(TArrLike | TStr)) { /* Modifying an array or string element, even when COW doesn't kick in, * produces a new SSATmp for the base. StaticArr/StaticStr may be promoted * to CountedArr/CountedStr. */ baseValChanged = true; if (baseType.maybe(TArr)) baseType |= TCountedArr; + if (baseType.maybe(TVec)) { + baseType |= TCountedVec; + /* Unsetting a vec element can turn it into a dict */ + if (op == UnsetElem) baseType |= TCountedDict; + } + if (baseType.maybe(TDict)) baseType |= TCountedDict; + if (baseType.maybe(TKeyset)) baseType |= TCountedKeyset; if (baseType.maybe(TStr)) baseType |= TCountedStr; } } diff --git a/hphp/runtime/vm/jit/minstr-helpers.h b/hphp/runtime/vm/jit/minstr-helpers.h index 0b78d145fc0..00c1ad1e273 100644 --- a/hphp/runtime/vm/jit/minstr-helpers.h +++ b/hphp/runtime/vm/jit/minstr-helpers.h @@ -396,6 +396,54 @@ PROFILE_MIXED_ARRAY_OFFSET_HELPER_TABLE(X) ////////////////////////////////////////////////////////////////////// +inline void profileDictOffsetHelper(const ArrayData* ad, int64_t i, + MixedArrayOffsetProfile* prof) { + prof->update(ad, i); +} +inline void profileDictOffsetHelper(const ArrayData* ad, const StringData* sd, + MixedArrayOffsetProfile* prof) { + prof->update(ad, sd, false); +} + +#define PROFILE_DICT_OFFSET_HELPER_TABLE(m) \ + /* name keyType */ \ + m(profileDictOffsetS, KeyType::Str) \ + m(profileDictOffsetI, KeyType::Int) \ + +#define X(nm, keyType) \ +inline void nm(const ArrayData* a, key_type k, \ + MixedArrayOffsetProfile* p) { \ + profileDictOffsetHelper(a, k, p); \ +} +PROFILE_DICT_OFFSET_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + +inline void profileKeysetOffsetHelper(const ArrayData* ad, int64_t i, + MixedArrayOffsetProfile* prof) { + prof->update(ad, i); +} +inline void profileKeysetOffsetHelper(const ArrayData* ad, const StringData* sd, + MixedArrayOffsetProfile* prof) { + prof->update(ad, sd, false); +} + +#define PROFILE_KEYSET_OFFSET_HELPER_TABLE(m) \ + /* name keyType */ \ + m(profileKeysetOffsetS, KeyType::Str) \ + m(profileKeysetOffsetI, KeyType::Int) \ + +#define X(nm, keyType) \ +inline void nm(const ArrayData* a, key_type k, \ + MixedArrayOffsetProfile* p) { \ + profileKeysetOffsetHelper(a, k, p); \ +} +PROFILE_KEYSET_OFFSET_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + template TypedValue* elemImpl(TypedValue* base, key_type key, @@ -552,6 +600,112 @@ ARRAYGET_HELPER_TABLE(X) ////////////////////////////////////////////////////////////////////// +#define ELEM_DICT_D_HELPER_TABLE(m) \ + /* name keyType */ \ + m(elemDictSD, KeyType::Str) \ + m(elemDictID, KeyType::Int) \ + +#define X(nm, keyType) \ +inline TypedValue* nm(TypedValue* base, key_type key) { \ + auto cbase = tvToCell(base); \ + assertx(isDictType(cbase->m_type)); \ + return ElemDDict(cbase, key); \ +} +ELEM_DICT_D_HELPER_TABLE(X) +#undef X + +#define ELEM_DICT_U_HELPER_TABLE(m) \ + /* name keyType */ \ + m(elemDictSU, KeyType::Str) \ + m(elemDictIU, KeyType::Int) \ + +#define X(nm, keyType) \ +inline TypedValue* nm(TypedValue* base, key_type key) { \ + auto cbase = tvToCell(base); \ + assertx(isDictType(cbase->m_type)); \ + return ElemUDict(cbase, key); \ +} +ELEM_DICT_U_HELPER_TABLE(X) +#undef X + +#define ELEM_DICT_HELPER_TABLE(m) \ + /* name keyType warn */ \ + m(elemDictS, KeyType::Str, MOpFlags::None) \ + m(elemDictI, KeyType::Int, MOpFlags::None) \ + m(elemDictSW, KeyType::Str, MOpFlags::Warn) \ + m(elemDictIW, KeyType::Int, MOpFlags::Warn) \ + +#define X(nm, keyType, flags) \ +inline const TypedValue* nm(ArrayData* ad, key_type key) { \ + return HPHP::ElemDict(ad, key); \ +} +ELEM_DICT_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + +#define ELEM_KEYSET_U_HELPER_TABLE(m) \ + /* name keyType */ \ + m(elemKeysetSU, KeyType::Str) \ + m(elemKeysetIU, KeyType::Int) \ + +#define X(nm, keyType) \ +inline TypedValue* nm(TypedValue* base, key_type key) { \ + auto cbase = tvToCell(base); \ + assertx(isKeysetType(cbase->m_type)); \ + return ElemUKeyset(cbase, key); \ +} +ELEM_KEYSET_U_HELPER_TABLE(X) +#undef X + +#define ELEM_KEYSET_HELPER_TABLE(m) \ + /* name keyType warn */ \ + m(elemKeysetS, KeyType::Str, MOpFlags::None) \ + m(elemKeysetI, KeyType::Int, MOpFlags::None) \ + m(elemKeysetSW, KeyType::Str, MOpFlags::Warn) \ + m(elemKeysetIW, KeyType::Int, MOpFlags::Warn) \ + +#define X(nm, keyType, flags) \ +inline const TypedValue* nm(ArrayData* ad, key_type key) { \ + return HPHP::ElemKeyset(ad, key); \ +} +ELEM_KEYSET_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + +#define DICTGET_HELPER_TABLE(m) \ + /* name keyType flags */ \ + m(dictGetS, KeyType::Str, MOpFlags::Warn) \ + m(dictGetI, KeyType::Int, MOpFlags::Warn) \ + m(dictGetSQuiet, KeyType::Str, MOpFlags::None) \ + m(dictGetIQuiet, KeyType::Int, MOpFlags::None) \ + +#define X(nm, keyType, flags) \ +inline TypedValue nm(ArrayData* a, key_type key) { \ + return *HPHP::ElemDict(a, key); \ +} +DICTGET_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + +#define KEYSETGET_HELPER_TABLE(m) \ + /* name keyType flags */ \ + m(keysetGetS, KeyType::Str, MOpFlags::Warn) \ + m(keysetGetI, KeyType::Int, MOpFlags::Warn) \ + m(keysetGetSQuiet, KeyType::Str, MOpFlags::None) \ + m(keysetGetIQuiet, KeyType::Int, MOpFlags::None) \ + +#define X(nm, keyType, flags) \ +inline TypedValue nm(ArrayData* a, key_type key) { \ + return *HPHP::ElemKeyset(a, key); \ +} +KEYSETGET_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + template TypedValue cGetElemImpl(TypedValue* base, key_type key) { TypedValue localTvRef; @@ -620,6 +774,7 @@ arraySetImpl(ArrayData* a, key_type key, Cell value, RefData* ref) { static_assert(keyType != KeyType::Any, "KeyType::Any is not supported in arraySetMImpl"); assertx(cellIsPlausible(value)); + assertx(a->isPHPArray()); const bool copy = a->cowCheck(); ArrayData* ret = checkForInt ? checkedSet(a, key, value, copy) : a->set(key, value, copy); @@ -646,6 +801,101 @@ ARRAYSET_HELPER_TABLE(X) ////////////////////////////////////////////////////////////////////// +template +typename ShuffleReturn::return_type +vecSetImpl(ArrayData* a, int64_t key, Cell value, RefData* ref) { + assertx(cellIsPlausible(value)); + assertx(a->isVecArray()); + const bool copy = a->cowCheck(); + ArrayData* ret = PackedArray::SetIntVec(a, key, value, copy); + return arrayRefShuffle(a, ret, + setRef ? ref->tv() : nullptr); +} + +#define VECSET_HELPER_TABLE(m) \ + /* name setRef */ \ + m(vecSetI, false) \ + m(vecSetIR, true) \ + +#define X(nm, setRef) \ +ShuffleReturn::return_type \ +inline nm(ArrayData* a, int64_t key, Cell value, RefData* ref) { \ + return vecSetImpl(a, key, value, ref); \ +} +VECSET_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + +inline ArrayData* dictSetImplPre(ArrayData* a, int64_t i, Cell val) { + return MixedArray::SetIntDict(a, i, val, a->cowCheck()); +} +inline ArrayData* dictSetImplPre(ArrayData* a, StringData* s, Cell val) { + return MixedArray::SetStrDict(a, s, val, a->cowCheck()); +} + +template +typename ShuffleReturn::return_type +dictSetImpl(ArrayData* a, key_type key, Cell value, RefData* ref) { + assertx(cellIsPlausible(value)); + assertx(a->isDict()); + auto ret = dictSetImplPre(a, key, value); + return arrayRefShuffle(a, ret, + setRef ? ref->tv() : nullptr); +} + +#define DICTSET_HELPER_TABLE(m) \ + /* name keyType setRef */ \ + m(dictSetI, KeyType::Int, false) \ + m(dictSetIR, KeyType::Int, true) \ + m(dictSetS, KeyType::Str, false) \ + m(dictSetSR, KeyType::Str, true) \ + +#define X(nm, keyType, setRef) \ +ShuffleReturn::return_type \ +inline nm(ArrayData* a, key_type key, Cell val, RefData* ref) { \ + return dictSetImpl(a, key, val, ref); \ +} +DICTSET_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + +inline ArrayData* keysetSetNewElemImplPre(ArrayData* a, int64_t i) { + return MixedArray::AddToKeyset(a, i, a->cowCheck()); +} + +inline ArrayData* keysetSetNewElemImplPre(ArrayData* a, StringData* s) { + return MixedArray::AddToKeyset(a, s, a->cowCheck()); +} + +template +void keysetSetNewElemImpl(TypedValue* tv, key_type key) { + assertx(tvIsPlausible(*tv)); + assertx(tvIsKeyset(tv)); + auto oldArr = tv->m_data.parr; + auto newArr = keysetSetNewElemImplPre(oldArr, key); + if (oldArr != newArr) { + tv->m_type = KindOfKeyset; + tv->m_data.parr = newArr; + assertx(tvIsPlausible(*tv)); + decRefArr(oldArr); + } +} + +#define KEYSET_SETNEWELEM_HELPER_TABLE(m) \ + /* name keyType */ \ + m(keysetSetNewElemI, KeyType::Int) \ + m(keysetSetNewElemS, KeyType::Str) \ + +#define X(nm, keyType) \ +inline void nm(TypedValue* tv, key_type key) { \ + keysetSetNewElemImpl(tv, key); \ +} +KEYSET_SETNEWELEM_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// template StringData* setElemImpl(TypedValue* base, key_type key, Cell val) { return HPHP::SetElem(base, key, &val); @@ -689,6 +939,48 @@ ARRAY_ISSET_HELPER_TABLE(X) ////////////////////////////////////////////////////////////////////// +template +uint64_t dictIssetImpl(ArrayData* a, key_type key) { + return IssetEmptyElemDict(a, key); +} + +#define DICT_ISSET_EMPTY_ELEM_HELPER_TABLE(m) \ + /* name keyType isEmpty */ \ + m(dictIssetElemS, KeyType::Str, false) \ + m(dictIssetElemSE, KeyType::Str, true) \ + m(dictIssetElemI, KeyType::Int, false) \ + m(dictIssetElemIE, KeyType::Int, true) \ + +#define X(nm, keyType, isEmpty) \ +inline uint64_t nm(ArrayData* a, key_type key) { \ + return dictIssetImpl(a, key); \ +} +DICT_ISSET_EMPTY_ELEM_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + +template +uint64_t keysetIssetImpl(ArrayData* a, key_type key) { + return IssetEmptyElemKeyset(a, key); +} + +#define KEYSET_ISSET_EMPTY_ELEM_HELPER_TABLE(m) \ + /* name keyType isEmpty */ \ + m(keysetIssetElemS, KeyType::Str, false) \ + m(keysetIssetElemSE, KeyType::Str, true) \ + m(keysetIssetElemI, KeyType::Int, false) \ + m(keysetIssetElemIE, KeyType::Int, true) \ + +#define X(nm, keyType, isEmpty) \ +inline uint64_t nm(ArrayData* a, key_type key) { \ + return keysetIssetImpl(a, key); \ +} +KEYSET_ISSET_EMPTY_ELEM_HELPER_TABLE(X) +#undef X + +////////////////////////////////////////////////////////////////////// + template void unsetElemImpl(TypedValue* base, key_type key) { HPHP::UnsetElem(base, key); diff --git a/hphp/runtime/vm/jit/mixed-array-offset-profile.cpp b/hphp/runtime/vm/jit/mixed-array-offset-profile.cpp index 8636f624d9f..51984ce5732 100644 --- a/hphp/runtime/vm/jit/mixed-array-offset-profile.cpp +++ b/hphp/runtime/vm/jit/mixed-array-offset-profile.cpp @@ -93,7 +93,7 @@ bool MixedArrayOffsetProfile::update(int32_t pos, uint32_t count) { } void MixedArrayOffsetProfile::update(const ArrayData* ad, int64_t i) { - auto const pos = ad->isMixed() + auto const pos = ad->isMixedLayout() ? MixedArray::asMixed(ad)->find(i, hashint(i)) : -1; update(pos, 1); @@ -103,7 +103,7 @@ void MixedArrayOffsetProfile::update(const ArrayData* ad, const StringData* sd, bool checkForInt) { auto const pos = [&]() -> int32_t { - if (!ad->isMixed()) return -1; + if (!ad->isMixedLayout()) return -1; auto const a = MixedArray::asMixed(ad); int64_t i; diff --git a/hphp/runtime/vm/jit/native-calls.cpp b/hphp/runtime/vm/jit/native-calls.cpp index 009d616e53e..da81f8666bd 100644 --- a/hphp/runtime/vm/jit/native-calls.cpp +++ b/hphp/runtime/vm/jit/native-calls.cpp @@ -244,6 +244,11 @@ static CallMap s_callMap { {{SSA, 0}, {SSA, 1}, {TV, 2}}}, {AddNewElem, addNewElemHelper, DSSA, SSync, {{SSA, 0}, {TV, 1}}}, + {DictAddElemStrKey, dictAddElemStringKeyHelper, DSSA, SSync, + {{SSA, 0}, {SSA, 1}, {TV, 2}}}, + {DictAddElemIntKey, dictAddElemIntKeyHelper, DSSA, SSync, + {{SSA, 0}, {SSA, 1}, {TV, 2}}}, + {ArrayAdd, arrayAdd, DSSA, SSync, {{SSA, 0}, {SSA, 1}}}, {Box, boxValue, DSSA, SNone, {{TV, 0}}}, {Clone, &ObjectData::clone, DSSA, SSync, {{SSA, 0}}}, @@ -477,6 +482,7 @@ static CallMap s_callMap { {{SSA, 0}, {TV, 1}, extra(&IncDecData::op)}}, {SetNewElem, setNewElem, DNone, SSync, {{SSA, 0}, {TV, 1}}}, {SetNewElemArray, setNewElemArray, DNone, SSync, {{SSA, 0}, {TV, 1}}}, + {SetNewElemVec, setNewElemVec, DNone, SSync, {{SSA, 0}, {TV, 1}}}, {BindNewElem, MInstrHelpers::bindNewElem, DNone, SSync, {{SSA, 0}, {SSA, 1}}}, {StringGet, MInstrHelpers::stringGetI, DSSA, SSync, {{SSA, 0}, {SSA, 1}}}, @@ -488,7 +494,11 @@ static CallMap s_callMap { {{SSA, 0}, {TV, 1}, {SSA, 2}}}, {SetWithRefElem, MInstrHelpers::setWithRefElem, DNone, SSync, {{SSA, 0}, {TV, 1}, {TV, 2}}}, - {ThrowOutOfBounds, collections::throwOOB, DNone, SSync, {{SSA, 0}}}, + {ElemVecD, MInstrHelpers::elemVecID, DSSA, SSync, {{SSA, 0}, {SSA, 1}}}, + {ElemVecU, MInstrHelpers::elemVecIU, DSSA, SSync, {{SSA, 0}, {SSA, 1}}}, + {ThrowOutOfBounds, throwOOBException, DNone, SSync, {{TV, 0}, {TV, 1}}}, + {ThrowInvalidArrayKey, invalidArrayKeyHelper, DNone, SSync, + {{SSA, 0}, {TV, 1}}}, /* instanceof checks */ {InstanceOf, &Class::classof, DSSA, SNone, {{SSA, 0}, {SSA, 1}}}, diff --git a/hphp/runtime/vm/jit/reg-alloc.cpp b/hphp/runtime/vm/jit/reg-alloc.cpp index a06e1cf53f8..7e1f47ac05a 100644 --- a/hphp/runtime/vm/jit/reg-alloc.cpp +++ b/hphp/runtime/vm/jit/reg-alloc.cpp @@ -58,11 +58,25 @@ bool loadsCell(Opcode op) { case CGetProp: case VGetProp: case ArrayGet: + case DictGet: + case DictGetQuiet: + case DictGetK: + case KeysetGet: + case KeysetGetQuiet: + case KeysetGetK: case MapGet: case CGetElem: case VGetElem: case ArrayIdx: - return true; + case DictIdx: + case KeysetIdx: + case LdVecElem: + switch (arch()) { + case Arch::X64: return true; + case Arch::ARM: return true; + case Arch::PPC64: not_implemented(); break; + } + not_reached(); default: return false; diff --git a/hphp/runtime/vm/jit/simplify.cpp b/hphp/runtime/vm/jit/simplify.cpp index 3a29fcda1d7..bce5fa3eea8 100644 --- a/hphp/runtime/vm/jit/simplify.cpp +++ b/hphp/runtime/vm/jit/simplify.cpp @@ -25,7 +25,10 @@ #include "hphp/runtime/base/array-data-defs.h" #include "hphp/runtime/base/comparisons.h" +#include "hphp/runtime/base/mixed-array.h" +#include "hphp/runtime/base/mixed-array-defs.h" #include "hphp/runtime/base/packed-array.h" +#include "hphp/runtime/base/packed-array-defs.h" #include "hphp/runtime/base/repo-auth-type-array.h" #include "hphp/runtime/base/type-conversions.h" #include "hphp/runtime/vm/hhbc.h" @@ -2382,9 +2385,7 @@ SSATmp* arrIntKeyImpl(State& env, const IRInstruction* inst) { auto const idx = inst->src(1); assertx(arr->hasConstVal(TArr)); assertx(idx->hasConstVal(TInt)); - assertx(!arr->arrVal()->isDict()); - assertx(!arr->arrVal()->isVecArray()); - assertx(!arr->arrVal()->isKeyset()); + assertx(arr->arrVal()->isPHPArray()); auto const value = arr->arrVal()->nvGet(idx->intVal()); return value ? cns(env, *value) : nullptr; } @@ -2394,9 +2395,7 @@ SSATmp* arrStrKeyImpl(State& env, const IRInstruction* inst) { auto const idx = inst->src(1); assertx(arr->hasConstVal(TArr)); assertx(idx->hasConstVal(TStr)); - assertx(!arr->arrVal()->isDict()); - assertx(!arr->arrVal()->isVecArray()); - assertx(!arr->arrVal()->isKeyset()); + assertx(arr->arrVal()->isPHPArray()); auto const value = [&] { int64_t val; if (arr->arrVal()->convertKey(idx->strVal(), val)) { @@ -2407,26 +2406,8 @@ SSATmp* arrStrKeyImpl(State& env, const IRInstruction* inst) { return value ? cns(env, *value) : nullptr; } -SSATmp* hackArrayGetImpl(State& env, const ArrayData* arr, SSATmp* key) { - assertx(key->hasConstVal(TStr) || key->hasConstVal(TInt)); - assertx(arr->isDict() || arr->isVecArray() || arr->isKeyset()); - if (key->type() <= TInt) { - auto r = arr->nvGet(key->intVal()); - return r ? cns(env, *r) : nullptr; - } - if (key->type() <= TStr) { - auto r = arr->nvGet(key->strVal()); - return r ? cns(env, *r) : nullptr; - } - return nullptr; -} - SSATmp* simplifyArrayGet(State& env, const IRInstruction* inst) { if (inst->src(0)->hasConstVal() && inst->src(1)->hasConstVal()) { - auto const arr = inst->src(0)->arrVal(); - if (arr->isDict() || arr->isVecArray() || arr->isKeyset()) { - return hackArrayGetImpl(env, arr, inst->src(1)); - } if (inst->src(1)->type() <= TInt) { if (auto result = arrIntKeyImpl(env, inst)) { return result; @@ -2445,17 +2426,19 @@ SSATmp* simplifyArrayGet(State& env, const IRInstruction* inst) { return nullptr; } -SSATmp* simplifyMixedArrayGetK(State& env, const IRInstruction* inst) { +SSATmp* simplifyArrayIsset(State& env, const IRInstruction* inst) { if (inst->src(0)->hasConstVal() && inst->src(1)->hasConstVal()) { if (inst->src(1)->type() <= TInt) { if (auto result = arrIntKeyImpl(env, inst)) { - return result; + return cns(env, !result->isA(TInitNull)); } + return cns(env, false); } if (inst->src(1)->type() <= TStr) { if (auto result = arrStrKeyImpl(env, inst)) { - return result; + return cns(env, !result->isA(TInitNull)); } + return cns(env, false); } } return nullptr; @@ -2463,11 +2446,6 @@ SSATmp* simplifyMixedArrayGetK(State& env, const IRInstruction* inst) { SSATmp* simplifyArrayIdx(State& env, const IRInstruction* inst) { if (inst->src(0)->hasConstVal() && inst->src(1)->hasConstVal()) { - auto const arr = inst->src(0)->arrVal(); - if (arr->isDict() || arr->isVecArray() || arr->isKeyset()) { - auto r = hackArrayGetImpl(env, arr, inst->src(1)); - return r ? r : inst->src(2); - } if (inst->src(1)->isA(TInt)) { if (auto result = arrIntKeyImpl(env, inst)) { return result; @@ -2486,10 +2464,6 @@ SSATmp* simplifyArrayIdx(State& env, const IRInstruction* inst) { SSATmp* simplifyAKExistsArr(State& env, const IRInstruction* inst) { if (inst->src(0)->hasConstVal() && inst->src(1)->hasConstVal()) { - auto const arr = inst->src(0)->arrVal(); - if (arr->isDict() || arr->isVecArray() || arr->isKeyset()) { - return cns(env, hackArrayGetImpl(env, arr, inst->src(1)) != nullptr); - } if (inst->src(1)->isA(TInt)) { if (arrIntKeyImpl(env, inst)) { return cns(env, true); @@ -2504,6 +2478,155 @@ SSATmp* simplifyAKExistsArr(State& env, const IRInstruction* inst) { return nullptr; } +namespace { + +template +SSATmp* arrGetKImpl(State& env, const IRInstruction* inst, G get) { + auto const arr = inst->src(0); + auto const& extra = inst->extra(); + + if (!arr->hasConstVal()) return nullptr; + auto const mixed = MixedArray::asMixed(get(arr)); + auto const& tv = mixed->getArrayElmRef(extra->index); + assertx(!MixedArray::isTombstone(tv.m_type)); + assertx(tvIsPlausible(tv)); + return cns(env, tv); +} + +template +SSATmp* hackArrQueryImpl(State& env, const IRInstruction* inst, + I getInt, S getStr, F finish) { + auto const arr = inst->src(0); + auto const key = inst->src(1); + + if (!arr->hasConstVal()) return nullptr; + if (!key->hasConstVal(TInt) && !key->hasConstVal(TStr)) return nullptr; + + auto const value = key->hasConstVal(TInt) + ? getInt(arr, key->intVal()) + : getStr(arr, key->strVal()); + return finish(value); +} + +template +SSATmp* hackArrGetImpl(State& env, const IRInstruction* inst, + I getInt, S getStr) { + return hackArrQueryImpl( + env, inst, + getInt, getStr, + [&](const TypedValue* tv) { + if (tv) return cns(env, *tv); + gen(env, ThrowOutOfBounds, inst->taken(), inst->src(0), inst->src(1)); + return cns(env, TBottom); + } + ); +} + +template +SSATmp* hackArrGetQuietImpl(State& env, const IRInstruction* inst, + I getInt, S getStr) { + return hackArrQueryImpl( + env, inst, + getInt, getStr, + [&](const TypedValue* tv) { + return tv ? cns(env, *tv) : cns(env, TInitNull); + } + ); +} + +template +SSATmp* hackArrIssetImpl(State& env, const IRInstruction* inst, + I getInt, S getStr) { + return hackArrQueryImpl( + env, inst, + getInt, getStr, + [&](const TypedValue* tv) { return cns(env, tv && !cellIsNull(tv)); } + ); +} + +template +SSATmp* hackArrEmptyElemImpl(State& env, const IRInstruction* inst, + I getInt, S getStr) { + return hackArrQueryImpl( + env, inst, + getInt, getStr, + [&](const TypedValue* tv) { return cns(env, !tv || !cellToBool(*tv)); } + ); +} + +template +SSATmp* hackArrIdxImpl(State& env, const IRInstruction* inst, + I getInt, S getStr) { + return hackArrQueryImpl( + env, inst, + getInt, getStr, + [&](const TypedValue* tv) { return tv ? cns(env, *tv) : inst->src(2); } + ); +} + +template +SSATmp* hackArrAKExistsImpl(State& env, const IRInstruction* inst, + I getInt, S getStr) { + return hackArrQueryImpl( + env, inst, + getInt, getStr, + [&](const TypedValue* tv) { return cns(env, !!tv); } + ); +} + +} + +#define X(Name, Action, Ty, Get) \ +SSATmp* simplify##Name(State& env, const IRInstruction* inst) { \ + return hackArr##Action##Impl( \ + env, inst, \ + [](SSATmp* a, int64_t k) { \ + return MixedArray::NvGetInt##Ty(a->Get(), k); \ + }, \ + [](SSATmp* a, const StringData* k) { \ + return MixedArray::NvGetStr##Ty(a->Get(), k); \ + } \ + ); \ +} + +X(DictGet, Get, Dict, dictVal) +X(KeysetGet, Get, Keyset, keysetVal) + +X(DictGetQuiet, GetQuiet, Dict, dictVal) +X(KeysetGetQuiet, GetQuiet, Keyset, keysetVal) + +X(DictIsset, Isset, Dict, dictVal) +X(KeysetIsset, Isset, Keyset, keysetVal) + +X(DictEmptyElem, EmptyElem, Dict, dictVal) +X(KeysetEmptyElem, EmptyElem, Keyset, keysetVal) + +X(DictIdx, Idx, Dict, dictVal) +X(KeysetIdx, Idx, Keyset, keysetVal) + +X(AKExistsDict, AKExists, Dict, dictVal) +X(AKExistsKeyset, AKExists, Keyset, keysetVal) + +#undef X + +SSATmp* simplifyMixedArrayGetK(State& env, const IRInstruction* inst) { + return arrGetKImpl(env, inst, [](SSATmp* a) { return a->arrVal(); }); +} + +SSATmp* simplifyDictGetK(State& env, const IRInstruction* inst) { + return arrGetKImpl(env, inst, [](SSATmp* a) { return a->dictVal(); }); +} + +SSATmp* simplifyKeysetGetK(State& env, const IRInstruction* inst) { + return arrGetKImpl(env, inst, [](SSATmp* a) { return a->keysetVal(); }); +} + +SSATmp* simplifyCheckArrayCOW(State& env, const IRInstruction* inst) { + auto const arr = inst->src(0); + if (arr->isA(TPersistentArrLike)) return gen(env, Jmp, inst->taken()); + return nullptr; +} + SSATmp* simplifyCount(State& env, const IRInstruction* inst) { auto const val = inst->src(0); auto const ty = val->type(); @@ -2514,6 +2637,7 @@ SSATmp* simplifyCount(State& env, const IRInstruction* inst) { if (ty <= oneTy) return cns(env, 1); if (ty <= TArr) return gen(env, CountArray, val); + if (ty <= TVec) return gen(env, CountVec, val); if (ty < TObj) { auto const cls = ty.clsSpec().cls(); @@ -2557,6 +2681,11 @@ SSATmp* simplifyCountArray(State& env, const IRInstruction* inst) { return nullptr; } +SSATmp* simplifyCountVec(State& env, const IRInstruction* inst) { + auto const vec = inst->src(0); + return vec->hasConstVal(TVec) ? cns(env, vec->vecVal()->size()) : nullptr; +} + SSATmp* simplifyLdClsName(State& env, const IRInstruction* inst) { auto const src = inst->src(0); return src->hasConstVal(TCls) ? cns(env, src->clsVal()->name()) : nullptr; @@ -2575,6 +2704,21 @@ SSATmp* simplifyLdStrLen(State& env, const IRInstruction* inst) { return src->hasConstVal(TStr) ? cns(env, src->strVal()->size()) : nullptr; } +SSATmp* simplifyLdVecElem(State& env, const IRInstruction* inst) { + auto const src0 = inst->src(0); + auto const src1 = inst->src(1); + if (src0->hasConstVal(TVec) && src1->hasConstVal(TInt)) { + auto const vec = src0->vecVal(); + auto const idx = src1->intVal(); + assertx(vec->isVecArray()); + if (idx >= 0) { + auto const tv = PackedArray::NvGetIntVec(vec, idx); + return tv ? cns(env, *tv) : nullptr; + } + } + return nullptr; +} + template SSATmp* simplifyByClass(State& env, const SSATmp* src, F f) { if (!src->isA(TObj) || mightRelax(env, src)) return nullptr; @@ -2802,6 +2946,7 @@ SSATmp* simplifyWork(State& env, const IRInstruction* inst) { X(Count) X(CountArray) X(CountArrayFast) + X(CountVec) X(DecRef) X(DecRefNZ) X(DefLabel) @@ -2825,6 +2970,7 @@ SSATmp* simplifyWork(State& env, const IRInstruction* inst) { X(LookupClsRDS) X(LdClsMethod) X(LdStrLen) + X(LdVecElem) X(MethodExists) X(CheckCtxThis) X(CastCtxThis) @@ -2918,8 +3064,24 @@ SSATmp* simplifyWork(State& env, const IRInstruction* inst) { X(EqCls) X(ArrayGet) X(MixedArrayGetK) + X(DictGet) + X(DictGetQuiet) + X(DictGetK) + X(KeysetGet) + X(KeysetGetQuiet) + X(KeysetGetK) + X(CheckArrayCOW) + X(ArrayIsset) + X(DictIsset) + X(KeysetIsset) + X(DictEmptyElem) + X(KeysetEmptyElem) X(ArrayIdx) X(AKExistsArr) + X(DictIdx) + X(AKExistsDict) + X(KeysetIdx) + X(AKExistsKeyset) X(OrdStr) X(LdLoc) X(LdStk) @@ -3157,6 +3319,8 @@ void copyProp(IRInstruction* inst) { } } +//////////////////////////////////////////////////////////////////////////////// + PackedBounds packedArrayBoundsStaticCheck(Type arrayType, int64_t idxVal) { if (idxVal < 0 || idxVal > PackedArray::MaxSize) return PackedBounds::Out; @@ -3184,6 +3348,8 @@ PackedBounds packedArrayBoundsStaticCheck(Type arrayType, int64_t idxVal) { return PackedBounds::Unknown; } +//////////////////////////////////////////////////////////////////////////////// + Type packedArrayElemType(SSATmp* arr, SSATmp* idx, const Class* ctx) { assertx(arr->isA(TArr) && arr->type().arrSpec().kind() == ArrayData::kPackedKind && @@ -3224,6 +3390,132 @@ Type packedArrayElemType(SSATmp* arr, SSATmp* idx, const Class* ctx) { not_reached(); } +Type vecElemType(SSATmp* arr, SSATmp* idx) { + assertx(arr->isA(TVec)); + assertx(!idx || idx->isA(TInt)); + + if (arr->hasConstVal()) { + // If both the array and idx are known statically, we can resolve it to the + // precise type. + if (idx && idx->hasConstVal()) { + auto const idxVal = idx->intVal(); + if (idxVal >= 0 && idxVal < arr->vecVal()->size()) { + auto const val = PackedArray::NvGetIntVec(arr->vecVal(), idxVal); + return val ? Type(val->m_type) : TBottom; + } + return TBottom; + } + + // Otherwise we can constrain the type according to the union of all the + // types present in the vec. + Type type{TBottom}; + PackedArray::IterateV( + arr->vecVal(), + [&](const TypedValue* v) { type |= Type(v->m_type); } + ); + return type; + } + + // Vecs always contain initialized cells + return arr->isA(TPersistentVec) ? TUncountedInit : TInitCell; +} + +Type dictElemType(SSATmp* arr, SSATmp* idx) { + assertx(arr->isA(TDict)); + assertx(!idx || idx->isA(TInt | TStr)); + + if (arr->hasConstVal()) { + // If both the array and idx are known statically, we can resolve it to the + // precise type. + if (idx && idx->hasConstVal(TInt)) { + auto const idxVal = idx->intVal(); + auto const val = MixedArray::NvGetIntDict(arr->dictVal(), idxVal); + return val ? Type(val->m_type) : TBottom; + } + + if (idx && idx->hasConstVal(TStr)) { + auto const idxVal = idx->strVal(); + auto const val = MixedArray::NvGetStrDict(arr->dictVal(), idxVal); + return val ? Type(val->m_type) : TBottom; + } + + // Otherwise we can constrain the type according to the union of all the + // types present in the dict. + Type type{TBottom}; + MixedArray::IterateKV( + MixedArray::asMixed(arr->dictVal()), + [&](const TypedValue* k, const TypedValue* v) { + // Ignore values which can't correspond to the key's type + if (isIntType(k->m_type)) { + if (!idx || idx->type().maybe(TInt)) type |= Type(v->m_type); + } else if (isStringType(k->m_type)) { + if (!idx || idx->type().maybe(TStr)) type |= Type(v->m_type); + } + } + ); + return type; + } + + // Dicts always contain initialized cells + return arr->isA(TPersistentDict) ? TUncountedInit : TInitCell; +} + +Type keysetElemType(SSATmp* arr, SSATmp* idx) { + assertx(arr->isA(TKeyset)); + assertx(!idx || idx->isA(TInt | TStr)); + + if (arr->hasConstVal()) { + // If both the array and idx are known statically, we can resolve it to the + // precise type. + if (idx && idx->hasConstVal(TInt)) { + auto const idxVal = idx->intVal(); + auto const val = MixedArray::NvGetIntKeyset(arr->keysetVal(), idxVal); + return val ? Type(val->m_type) : TBottom; + } + + if (idx && idx->hasConstVal(TStr)) { + auto const idxVal = idx->strVal(); + auto const val = MixedArray::NvGetStrKeyset(arr->keysetVal(), idxVal); + return val ? Type(val->m_type) : TBottom; + } + + // Otherwise we can constrain the type according to the union of all the + // types present in the keyset. + Type type{TBottom}; + MixedArray::IterateKV( + MixedArray::asMixed(arr->keysetVal()), + [&](const TypedValue* k, const TypedValue* v) { + // Ignore values which can't correspond to the key's type + if (isIntType(k->m_type)) { + assertx(isIntType(v->m_type)); + if (!idx || idx->type().maybe(TInt)) type |= Type(v->m_type); + } else if (isStringType(k->m_type)) { + assertx(isStringType(v->m_type)); + if (!idx || idx->type().maybe(TStr)) type |= Type(v->m_type); + } + } + ); + + // The key is always the value, so, for instance, if there's nothing but + // strings in the keyset, we know an int idx can't access a valid value. + if (idx) { + if (idx->isA(TInt)) type &= TInt; + if (idx->isA(TStr)) type &= TStr; + } + return type; + } + + // Keysets always contain strings or integers. We can further constrain this + // if we know the idx type, as the key is always the value. + auto type = TStr | TInt; + if (idx) { + if (idx->isA(TInt)) type &= TInt; + if (idx->isA(TStr)) type &= TStr; + } + if (arr->isA(TPersistentKeyset)) type &= TUncountedInit; + return type; +} + ////////////////////////////////////////////////////////////////////// }} diff --git a/hphp/runtime/vm/jit/simplify.h b/hphp/runtime/vm/jit/simplify.h index f7cf9851d03..410068d0284 100644 --- a/hphp/runtime/vm/jit/simplify.h +++ b/hphp/runtime/vm/jit/simplify.h @@ -138,6 +138,24 @@ PackedBounds packedArrayBoundsStaticCheck(Type, int64_t key); */ Type packedArrayElemType(SSATmp* arr, SSATmp* idx, const Class* ctx); +/* + * Get the type of `arr[idx]` for different Hack array types, considering + * constness, staticness, and RAT types. + * + * Note that these functions do not require the existence of `arr[idx]`. If we + * can statically determine that the access is out of bounds, TBottom is + * returned. Otherwise we return a type `t`, such that when the access is within + * bounds, `arr[idx].isA(t)` holds. (This, if this function is used in contexts + * where the bounds are not statically known, one must account for the opcode + * specific behavior of the failure case). + * + * `idx` is optional. If not provided, a more conservative type is returned + * which holds for all elements in the array. + */ +Type vecElemType(SSATmp* arr, SSATmp* idx); +Type dictElemType(SSATmp* arr, SSATmp* idx); +Type keysetElemType(SSATmp* arr, SSATmp* idx); + ////////////////////////////////////////////////////////////////////// }} diff --git a/hphp/runtime/vm/jit/translator-runtime.cpp b/hphp/runtime/vm/jit/translator-runtime.cpp index 1bec04c6b42..19b2ac371f9 100644 --- a/hphp/runtime/vm/jit/translator-runtime.cpp +++ b/hphp/runtime/vm/jit/translator-runtime.cpp @@ -138,6 +138,37 @@ ArrayData* addElemStringKeyHelper(ArrayData* ad, return arrayRefShuffle(ad, retval, nullptr); } +ArrayData* dictAddElemIntKeyHelper(ArrayData* ad, + int64_t key, + TypedValue value) { + assertx(ad->isDict()); + // set will decRef any old value that may have been overwritten + // if appropriate + ArrayData* retval = + MixedArray::SetIntDict(ad, key, *tvAssertCell(&value), ad->cowCheck()); + // TODO Task #1970153: It would be great if there were set() + // methods that didn't bump up the refcount so that we didn't + // have to decrement it here + tvRefcountedDecRef(&value); + return arrayRefShuffle(ad, retval, nullptr); +} + +ArrayData* dictAddElemStringKeyHelper(ArrayData* ad, + StringData* key, + TypedValue value) { + assertx(ad->isDict()); + // set will decRef any old value that may have been overwritten + // if appropriate + ArrayData* retval = + MixedArray::SetStrDict(ad, key, *tvAssertCell(&value), ad->cowCheck()); + // TODO Task #1970153: It would be great if there were set() + // methods that didn't bump up the refcount so that we didn't + // have to decrement it here + decRefStr(key); + tvRefcountedDecRef(&value); + return arrayRefShuffle(ad, retval, nullptr); +} + ArrayData* arrayAdd(ArrayData* a1, ArrayData* a2) { assertx(a1->isPHPArray()); assertx(a2->isPHPArray()); @@ -170,6 +201,10 @@ void setNewElemArray(TypedValue* base, Cell val) { HPHP::SetNewElemArray(base, &val); } +void setNewElemVec(TypedValue* base, Cell val) { + HPHP::SetNewElemVec(base, &val); +} + RefData* boxValue(TypedValue tv) { assertx(tv.m_type != KindOfRef); if (tv.m_type == KindOfUninit) tv = make_tv(); @@ -717,6 +752,26 @@ TypedValue arrayIdxIc(ArrayData* a, int64_t key, TypedValue def) { return arrayIdxI(a, key, def); } +TypedValue dictIdxI(ArrayData* a, int64_t key, TypedValue def) { + assertx(a->isDict()); + return getDefaultIfNullCell(MixedArray::NvGetIntDict(a, key), def); +} + +TypedValue dictIdxS(ArrayData* a, StringData* key, TypedValue def) { + assertx(a->isDict()); + return getDefaultIfNullCell(MixedArray::NvGetStrDict(a, key), def); +} + +TypedValue keysetIdxI(ArrayData* a, int64_t key, TypedValue def) { + assertx(a->isKeyset()); + return getDefaultIfNullCell(MixedArray::NvGetIntKeyset(a, key), def); +} + +TypedValue keysetIdxS(ArrayData* a, StringData* key, TypedValue def) { + assertx(a->isKeyset()); + return getDefaultIfNullCell(MixedArray::NvGetStrKeyset(a, key), def); +} + TypedValue mapIdx(ObjectData* mapOD, StringData* key, TypedValue def) { assert(collections::isType(mapOD->getVMClass(), CollectionType::Map) || collections::isType(mapOD->getVMClass(), CollectionType::ImmMap)); @@ -1236,6 +1291,20 @@ int64_t decodeCufIterHelper(Iter* it, TypedValue func) { return true; } +void throwOOBException(TypedValue base, TypedValue key) { + if (isArrayLikeType(base.m_type)) { + throwOOBArrayKeyException(key, base.m_data.parr); + } else if (base.m_type == KindOfObject) { + assertx(isIntType(key.m_type)); + collections::throwOOB(key.m_data.num); + } + not_reached(); +} + +void invalidArrayKeyHelper(const ArrayData* ad, TypedValue key) { + throwInvalidArrayKeyException(&key, ad); +} + namespace MInstrHelpers { TypedValue setOpElem(TypedValue* base, TypedValue key, @@ -1315,6 +1384,18 @@ void bindNewElem(TypedValue* base, RefData* val) { tvBindRef(val, elem); } +TypedValue* elemVecID(TypedValue* base, int64_t key) { + auto cbase = tvToCell(base); + assertx(isVecType(cbase->m_type)); + return ElemDVec(cbase, key); +} + +TypedValue* elemVecIU(TypedValue* base, int64_t key) { + auto cbase = tvToCell(base); + assertx(isVecType(cbase->m_type)); + return ElemUVec(cbase, key); +} + } ////////////////////////////////////////////////////////////////////// diff --git a/hphp/runtime/vm/jit/translator-runtime.h b/hphp/runtime/vm/jit/translator-runtime.h index 38d5e0d502c..9809dc0b428 100644 --- a/hphp/runtime/vm/jit/translator-runtime.h +++ b/hphp/runtime/vm/jit/translator-runtime.h @@ -84,8 +84,12 @@ ArrayData* addNewElemHelper(ArrayData* a, TypedValue value); ArrayData* addElemIntKeyHelper(ArrayData* ad, int64_t key, TypedValue val); ArrayData* addElemStringKeyHelper(ArrayData* ad, StringData* key, TypedValue val); +ArrayData* dictAddElemIntKeyHelper(ArrayData* ad, int64_t key, TypedValue val); +ArrayData* dictAddElemStringKeyHelper(ArrayData* ad, StringData* key, + TypedValue val); void setNewElem(TypedValue* base, Cell val); void setNewElemArray(TypedValue* base, Cell val); +void setNewElemVec(TypedValue* base, Cell val); RefData* boxValue(TypedValue tv); ArrayData* arrayAdd(ArrayData* a1, ArrayData* a2); /* Helper functions for conversion instructions that are too @@ -158,6 +162,12 @@ TypedValue arrayIdxIc(ArrayData*, int64_t, TypedValue); TypedValue arrayIdxS(ArrayData*, StringData*, TypedValue); TypedValue arrayIdxSi(ArrayData*, StringData*, TypedValue); +TypedValue dictIdxI(ArrayData*, int64_t, TypedValue); +TypedValue dictIdxS(ArrayData*, StringData*, TypedValue); + +TypedValue keysetIdxI(ArrayData*, int64_t, TypedValue); +TypedValue keysetIdxS(ArrayData*, StringData*, TypedValue); + TypedValue mapIdx(ObjectData*, StringData*, TypedValue); TypedValue getMemoKeyHelper(TypedValue tv); @@ -216,6 +226,9 @@ int64_t decodeCufIterHelper(Iter* it, TypedValue func); */ [[noreturn]] void throwSwitchMode(); +[[noreturn]] void throwOOBException(TypedValue base, TypedValue key); +[[noreturn]] void invalidArrayKeyHelper(const ArrayData* ad, TypedValue key); + namespace MInstrHelpers { TypedValue setOpElem(TypedValue* base, TypedValue key, Cell val, SetOpOp op); StringData* stringGetI(StringData*, uint64_t); @@ -225,6 +238,8 @@ void bindElemC(TypedValue*, TypedValue, RefData*); void setWithRefElem(TypedValue*, TypedValue, TypedValue); TypedValue incDecElem(TypedValue* base, TypedValue key, IncDecOp op); void bindNewElem(TypedValue* base, RefData* val); +TypedValue* elemVecID(TypedValue* base, int64_t key); +TypedValue* elemVecIU(TypedValue* base, int64_t key); } /* diff --git a/hphp/runtime/vm/member-operations.h b/hphp/runtime/vm/member-operations.h index 2a4694b9487..cac8b166d22 100644 --- a/hphp/runtime/vm/member-operations.h +++ b/hphp/runtime/vm/member-operations.h @@ -645,6 +645,40 @@ inline TypedValue* ElemDDict(TypedValue* base, key_type key) { } /** + * ElemD when base is a Keyset + */ +template +[[noreturn]] +inline TypedValue* ElemDKeysetPre(TypedValue* base, int64_t key) { + if (reffy) throwRefInvalidArrayValueException(base->m_data.parr); + throwInvalidKeysetOperation(); +} + +template +[[noreturn]] +inline TypedValue* ElemDKeysetPre(TypedValue* base, StringData* key) { + if (reffy) throwRefInvalidArrayValueException(base->m_data.parr); + throwInvalidKeysetOperation(); +} + +template +[[noreturn]] +inline TypedValue* ElemDKeysetPre(TypedValue* base, TypedValue key) { + auto const dt = key.m_type; + if (isIntType(dt)) ElemDKeysetPre(base, key.m_data.num); + if (isStringType(dt)) ElemDKeysetPre(base, key.m_data.pstr); + throwInvalidArrayKeyException(&key, base->m_data.parr); +} + +template +[[noreturn]] +inline TypedValue* ElemDKeyset(TypedValue* base, key_type key) { + assertx(tvIsKeyset(base)); + assertx(tvIsPlausible(*base)); + ElemDKeysetPre(base, key); +} + +/** * ElemD when base is Null */ template @@ -757,7 +791,7 @@ TypedValue* ElemD(TypedValue& tvRef, TypedValue* base, key_type key) { return ElemDDict(base, key); case KindOfPersistentKeyset: case KindOfKeyset: - throwInvalidKeysetOperation(); + return ElemDKeyset(base, key); case KindOfPersistentArray: case KindOfArray: return ElemDArray(base, key); @@ -899,56 +933,30 @@ inline TypedValue* ElemUDict(TypedValue* base, key_type key) { /** * ElemU when base is a Keyset */ +[[noreturn]] inline TypedValue* ElemUKeysetPre(TypedValue* base, int64_t key) { - Variant* ret = nullptr; - ArrayData* oldArr = base->m_data.parr; - ArrayData* newArr = - MixedArray::LvalSilentIntKeyset(oldArr, key, ret, oldArr->cowCheck()); - if (UNLIKELY(!ret)) { - return const_cast(init_null_variant.asTypedValue()); - } - if (newArr != oldArr) { - base->m_type = KindOfKeyset; - base->m_data.parr = newArr; - assertx(cellIsPlausible(*base)); - decRefArr(oldArr); - } - return ret->asTypedValue(); + throwInvalidKeysetOperation(); } +[[noreturn]] inline TypedValue* ElemUKeysetPre(TypedValue* base, StringData* key) { - Variant* ret = nullptr; - ArrayData* oldArr = base->m_data.parr; - ArrayData* newArr = - MixedArray::LvalSilentStrKeyset(oldArr, key, ret, oldArr->cowCheck()); - if (UNLIKELY(!ret)) { - return const_cast(init_null_variant.asTypedValue()); - } - if (newArr != oldArr) { - base->m_type = KindOfKeyset; - base->m_data.parr = newArr; - assertx(cellIsPlausible(*base)); - decRefArr(oldArr); - } - return ret->asTypedValue(); + throwInvalidKeysetOperation(); } +[[noreturn]] inline TypedValue* ElemUKeysetPre(TypedValue* base, TypedValue key) { auto const dt = key.m_type; - if (isIntType(dt)) return ElemUKeysetPre(base, key.m_data.num); - if (isStringType(dt)) return ElemUKeysetPre(base, key.m_data.pstr); + if (isIntType(dt)) ElemUKeysetPre(base, key.m_data.num); + if (isStringType(dt)) ElemUKeysetPre(base, key.m_data.pstr); throwInvalidArrayKeyException(&key, base->m_data.parr); } template +[[noreturn]] inline TypedValue* ElemUKeyset(TypedValue* base, key_type key) { assertx(tvIsKeyset(base)); assertx(tvIsPlausible(*base)); - auto* result = ElemUKeysetPre(base, key); - assertx(tvIsKeyset(base)); - assertx(tvIsPlausible(*base)); - assertx(isIntType(result->m_type) || isStringType(result->m_type)); - return result; + ElemUKeysetPre(base, key); } /** -- 2.11.4.GIT