ArrayIdx bytecode and IR instruction
- Implemented ArrayIdx bytecode and IR instruction for common pattern
Esse commit está contido em:
@@ -3162,6 +3162,17 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
|
||||
e.AKExists();
|
||||
return true;
|
||||
}
|
||||
} else if (call->isCallToFunction("hphp_array_idx")) {
|
||||
if (params && params->getCount() == 3) {
|
||||
visit((*params)[0]);
|
||||
emitConvertToCell(e);
|
||||
visit((*params)[1]);
|
||||
emitConvertToCell(e);
|
||||
visit((*params)[2]);
|
||||
emitConvertToCell(e);
|
||||
e.ArrayIdx();
|
||||
return true;
|
||||
}
|
||||
} else if (call->isCallToFunction("strlen")) {
|
||||
if (params && params->getCount() == 1) {
|
||||
visit((*params)[0]);
|
||||
|
||||
@@ -3625,6 +3625,11 @@ CreateCl <num args> <class name> [F..F] -> [C]
|
||||
defined at the point of the CreateCl opcode, or the behavior is
|
||||
undefined.
|
||||
|
||||
ArrayIdx [C C C] -> [C]
|
||||
|
||||
Checks if array in $3 contains key in $2 and pushes the result onto the stack
|
||||
if found. Otherwise, $1 is pushed onto the stack. A fatal error will be
|
||||
thrown if $3 is not an array.
|
||||
|
||||
14. Continuation creation and execution
|
||||
---------------------------------------
|
||||
|
||||
@@ -1230,6 +1230,7 @@ ArrayAdd
|
||||
DefCls
|
||||
DefFunc
|
||||
AKExists
|
||||
ArrayIdx
|
||||
|
||||
D:Int LdSwitchDblIndex S0:Dbl S1:Int S2:Int
|
||||
D:Int LdSwitchStrIndex S0:Str S1:Int S2:Int
|
||||
|
||||
@@ -2039,6 +2039,36 @@
|
||||
},
|
||||
"args": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "hphp_array_idx",
|
||||
"desc": "hphp_array_idx() returns the value at the given key in the given array or the given default value if it is not found. An error will be raised if the search parameter is not an array.",
|
||||
"flags": [
|
||||
"HasDocComment",
|
||||
"HipHopSpecific",
|
||||
"FunctionIsFoldable"
|
||||
],
|
||||
"return": {
|
||||
"type": "Variant",
|
||||
"desc": "Returns the value at 'key' in 'search' or 'def' if it is not found."
|
||||
},
|
||||
"args": [
|
||||
{
|
||||
"name": "key",
|
||||
"type": "Variant",
|
||||
"desc": "Value to check."
|
||||
},
|
||||
{
|
||||
"name": "search",
|
||||
"type": "Variant",
|
||||
"desc": "An array with keys to check."
|
||||
},
|
||||
{
|
||||
"name": "def",
|
||||
"type": "Variant",
|
||||
"desc": "The value to return if key is not found in search."
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"classes": [
|
||||
|
||||
@@ -1339,5 +1339,20 @@ Variant f_i18n_loc_get_error_code() {
|
||||
return s_collator->getErrorCode();
|
||||
}
|
||||
|
||||
Variant f_hphp_array_idx(CVarRef key, CVarRef search, CVarRef def) {
|
||||
if (UNLIKELY(!search.isArray())) {
|
||||
raise_error("hphp_array_idx: search must be an array");
|
||||
} else if (LIKELY(!key.isNull())) {
|
||||
ArrayData *arr = search.getArrayData();
|
||||
VarNR index = key.toKey();
|
||||
if (LIKELY(!index.isNull())) {
|
||||
CVarRef ret = arr->get(index, false);
|
||||
return (&ret != &null_variant) ? ret : def;
|
||||
}
|
||||
}
|
||||
return def;
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
@@ -173,6 +173,8 @@ bool f_i18n_loc_set_attribute(int64_t attr, int64_t val);
|
||||
bool f_i18n_loc_set_strength(int64_t strength);
|
||||
Variant f_i18n_loc_get_error_code();
|
||||
|
||||
Variant f_hphp_array_idx(CVarRef key, CVarRef search, CVarRef def);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
|
||||
@@ -5104,6 +5104,20 @@ inline void OPTBLD_INLINE VMExecutionContext::iopAKExists(PC& pc) {
|
||||
key->m_type = KindOfBoolean;
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopArrayIdx(PC& pc) {
|
||||
NEXT();
|
||||
TypedValue* def = m_stack.topTV();
|
||||
TypedValue* arr = m_stack.indTV(1);
|
||||
TypedValue* key = m_stack.indTV(2);
|
||||
|
||||
Variant result = f_hphp_array_idx(tvAsCVarRef(key),
|
||||
tvAsCVarRef(arr),
|
||||
tvAsCVarRef(def));
|
||||
m_stack.popTV();
|
||||
m_stack.popTV();
|
||||
tvAsVariant(key) = result;
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopSetL(PC& pc) {
|
||||
NEXT();
|
||||
DECODE_HA(local);
|
||||
|
||||
@@ -571,6 +571,7 @@ enum SetOpOp {
|
||||
O(ContHandle, NA, ONE(CV), NOV, CF_TF) \
|
||||
O(Strlen, NA, ONE(CV), ONE(CV), NF) \
|
||||
O(IncStat, TWO(IVA,IVA), NOV, NOV, NF) \
|
||||
O(ArrayIdx, NA, THREE(CV,CV,CV), ONE(CV), NF) \
|
||||
O(HighInvalid, NA, NOV, NOV, NF) \
|
||||
|
||||
enum Op {
|
||||
|
||||
@@ -415,6 +415,7 @@ CALL_OPCODE(IncStatGrouped)
|
||||
CALL_OPCODE(StaticLocInit)
|
||||
CALL_OPCODE(StaticLocInitCached)
|
||||
CALL_OPCODE(OpMod)
|
||||
CALL_OPCODE(ArrayIdx)
|
||||
|
||||
// Vector instruction helpers
|
||||
CALL_OPCODE(BaseG)
|
||||
@@ -5233,4 +5234,34 @@ void genCodeForTrace(Trace* trace,
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE
|
||||
TypedValue& getDefaultIfNullCell(TypedValue* tv, TypedValue& def) {
|
||||
if (UNLIKELY(nullptr == tv)) {
|
||||
// refcount is already correct since def was never decrefed
|
||||
return def;
|
||||
}
|
||||
tvRefcountedDecRef(&def);
|
||||
TypedValue* ret = tvToCell(tv);
|
||||
tvRefcountedIncRef(ret);
|
||||
return *ret;
|
||||
}
|
||||
|
||||
HOT_FUNC_VM
|
||||
TypedValue arrayIdxS(ArrayData* a, StringData* key, TypedValue def) {
|
||||
return getDefaultIfNullCell(a->nvGet(key), def);
|
||||
}
|
||||
|
||||
HOT_FUNC_VM
|
||||
TypedValue arrayIdxSi(ArrayData* a, StringData* key, TypedValue def) {
|
||||
int64_t i;
|
||||
return UNLIKELY(key->isStrictlyInteger(i)) ?
|
||||
getDefaultIfNullCell(a->nvGet(i), def) :
|
||||
getDefaultIfNullCell(a->nvGet(key), def);
|
||||
}
|
||||
|
||||
HOT_FUNC_VM
|
||||
TypedValue arrayIdxI(ArrayData* a, int64_t key, TypedValue def) {
|
||||
return getDefaultIfNullCell(a->nvGet(key), def);
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
@@ -561,6 +561,10 @@ void genCodeForTrace(Trace* trace,
|
||||
const LifetimeInfo* lifetime = nullptr,
|
||||
AsmInfo* asmInfo = nullptr);
|
||||
|
||||
TypedValue arrayIdxI(ArrayData*, int64_t, TypedValue);
|
||||
TypedValue arrayIdxS(ArrayData*, StringData*, TypedValue);
|
||||
TypedValue arrayIdxSi(ArrayData*, StringData*, TypedValue);
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "hphp/runtime/vm/unit.h"
|
||||
#include "hphp/runtime/vm/runtime.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/irfactory.h"
|
||||
#include "hphp/runtime/vm/translator/hopt/codegen.h" // ArrayIdx helpers
|
||||
|
||||
// Include last to localize effects to this file
|
||||
#include "hphp/util/assert_throw.h"
|
||||
@@ -1124,6 +1125,46 @@ void HhbcTranslator::emitIncStat(int32_t counter, int32_t value, bool force) {
|
||||
}
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitArrayIdx() {
|
||||
SSATmp* def = popC();
|
||||
SSATmp* arr = popC();
|
||||
SSATmp* key = popC();
|
||||
|
||||
if (UNLIKELY(!arr->isA(Type::Arr))) {
|
||||
// raise fatal
|
||||
emitInterpOne(Type::Cell, 3);
|
||||
return;
|
||||
}
|
||||
if (UNLIKELY(key->isA(Type::Null))) {
|
||||
// if the key is null it will not be found so just return the default
|
||||
push(def);
|
||||
gen(DecRef, arr);
|
||||
gen(DecRef, key);
|
||||
return;
|
||||
}
|
||||
if (UNLIKELY(!(key->isA(Type::Int) || key->isA(Type::Str)))) {
|
||||
PUNT(ArrayIdx_unsupportedKey);
|
||||
}
|
||||
|
||||
KeyType keyType;
|
||||
bool checkForInt;
|
||||
checkStrictlyInteger(key, keyType, checkForInt);
|
||||
|
||||
TCA opFunc;
|
||||
if (checkForInt) {
|
||||
opFunc = (TCA)&arrayIdxSi;
|
||||
} else if (IntKey == keyType) {
|
||||
opFunc = (TCA)&arrayIdxI;
|
||||
} else {
|
||||
assert(StrKey == keyType);
|
||||
opFunc = (TCA)&arrayIdxS;
|
||||
}
|
||||
|
||||
push(gen(ArrayIdx, cns(opFunc), arr, key, def));
|
||||
gen(DecRef, arr);
|
||||
gen(DecRef, key);
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitIncTransCounter() {
|
||||
m_tb->gen(IncTransCounter);
|
||||
}
|
||||
@@ -3206,4 +3247,24 @@ void HhbcTranslator::end(int nextPc) {
|
||||
m_tb->genTraceEnd(nextPc);
|
||||
}
|
||||
|
||||
void HhbcTranslator::checkStrictlyInteger(
|
||||
SSATmp*& key, KeyType& keyType, bool& checkForInt) {
|
||||
checkForInt = false;
|
||||
if (key->isA(Type::Int)) {
|
||||
keyType = IntKey;
|
||||
} else {
|
||||
assert(key->isA(Type::Str));
|
||||
keyType = StrKey;
|
||||
if (key->isConst()) {
|
||||
int64_t i;
|
||||
if (key->getValStr()->isStrictlyInteger(i)) {
|
||||
keyType = IntKey;
|
||||
key = cns(i);
|
||||
}
|
||||
} else {
|
||||
checkForInt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}} // namespace HPHP::JIT
|
||||
|
||||
@@ -350,6 +350,7 @@ struct HhbcTranslator {
|
||||
void emitStrlen();
|
||||
void emitIncStat(int32_t counter, int32_t value, bool force = false);
|
||||
void emitIncTransCounter();
|
||||
void emitArrayIdx();
|
||||
|
||||
// tracelet guards
|
||||
Trace* guardTypeStack(uint32_t stackIndex,
|
||||
@@ -371,6 +372,9 @@ struct HhbcTranslator {
|
||||
void emitInterpOne(Type type, int numPopped, int numExtraPushed = 0);
|
||||
void emitInterpOneCF(int numPopped);
|
||||
|
||||
void checkStrictlyInteger(SSATmp*& key, KeyType& keyType,
|
||||
bool& checkForInt);
|
||||
|
||||
private:
|
||||
/*
|
||||
* VectorTranslator is responsible for translating one of the vector
|
||||
@@ -423,8 +427,6 @@ private:
|
||||
void emitArraySet(SSATmp* key, SSATmp* value);
|
||||
void emitArrayGet(SSATmp* key);
|
||||
void emitArrayIsset();
|
||||
void checkStrictlyInteger(SSATmp*& key, KeyType& keyType,
|
||||
bool& checkForInt);
|
||||
|
||||
// Misc Helpers
|
||||
void numberStackInputs();
|
||||
|
||||
@@ -581,6 +581,8 @@ O(EmptyElem, D(Bool), C(TCA) \
|
||||
O(IncStat, ND, C(Int) C(Int) C(Bool), E|Mem) \
|
||||
O(IncStatGrouped, ND, CStr CStr C(Int), E|N|Mem) \
|
||||
O(IncTransCounter, ND, NA, E) \
|
||||
O(ArrayIdx, D(Cell), C(TCA) S(Arr) S(Int,Str) S(Cell), \
|
||||
E|N|CRc|PRc|Refs|Mem) \
|
||||
O(DbgAssertRefCount, ND, SUnk, N|E) \
|
||||
O(DbgAssertPtr, ND, S(PtrToGen), N|E) \
|
||||
O(Nop, ND, NA, NF) \
|
||||
|
||||
@@ -686,6 +686,11 @@ void TranslatorX64::irTranslateIncStat(const Tracelet& t,
|
||||
HHIR_EMIT(IncStat, i.imm[0].u_IVA, i.imm[1].u_IVA);
|
||||
}
|
||||
|
||||
void TranslatorX64::irTranslateArrayIdx(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
HHIR_EMIT(ArrayIdx);
|
||||
}
|
||||
|
||||
void TranslatorX64::irTranslateClassExists(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
const StringData* clsName = i.inputs[1]->rtt.valueStringOrNull();
|
||||
|
||||
@@ -163,6 +163,8 @@ static CallMap s_callMap({
|
||||
{{SSA, 0}}},
|
||||
{CreateCl, (TCA)createClHelper, DSSA, SSync,
|
||||
{{SSA, 0}, {SSA, 1}, {SSA, 2}, {SSA, 3}}},
|
||||
{ArrayIdx, {FSSA, 0}, DTV, SSync,
|
||||
{{SSA, 1}, {SSA, 2}, {TV, 3}}},
|
||||
|
||||
/* Switch helpers */
|
||||
{LdSwitchDblIndex, (TCA)switchDoubleHelper, DSSA, SSync,
|
||||
|
||||
@@ -1419,26 +1419,6 @@ void HhbcTranslator::VectorTranslator::emitUnsetProp() {
|
||||
}
|
||||
#undef HELPER_TABLE
|
||||
|
||||
void HhbcTranslator::VectorTranslator::checkStrictlyInteger(
|
||||
SSATmp*& key, KeyType& keyType, bool& checkForInt) {
|
||||
checkForInt = false;
|
||||
if (key->isA(Type::Int)) {
|
||||
keyType = IntKey;
|
||||
} else {
|
||||
assert(key->isA(Type::Str));
|
||||
keyType = StrKey;
|
||||
if (key->isConst()) {
|
||||
int64_t i;
|
||||
if (key->getValStr()->isStrictlyInteger(i)) {
|
||||
keyType = IntKey;
|
||||
key = cns(i);
|
||||
}
|
||||
} else {
|
||||
checkForInt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static inline TypedValue* checkedGetCell(ArrayData* a, StringData* key) {
|
||||
int64_t i;
|
||||
return UNLIKELY(key->isStrictlyInteger(i)) ? a->nvGetCell(i)
|
||||
@@ -1477,7 +1457,7 @@ HELPER_TABLE(ELEM)
|
||||
void HhbcTranslator::VectorTranslator::emitArrayGet(SSATmp* key) {
|
||||
KeyType keyType;
|
||||
bool checkForInt;
|
||||
checkStrictlyInteger(key, keyType, checkForInt);
|
||||
m_ht.checkStrictlyInteger(key, keyType, checkForInt);
|
||||
|
||||
typedef TypedValue (*OpFunc)(ArrayData*, TypedValue*);
|
||||
BUILD_OPTAB_HOT(keyType, checkForInt);
|
||||
@@ -1650,7 +1630,7 @@ void HhbcTranslator::VectorTranslator::emitArrayIsset() {
|
||||
SSATmp* key = getKey();
|
||||
KeyType keyType;
|
||||
bool checkForInt;
|
||||
checkStrictlyInteger(key, keyType, checkForInt);
|
||||
m_ht.checkStrictlyInteger(key, keyType, checkForInt);
|
||||
|
||||
typedef uint64_t (*OpFunc)(ArrayData*, TypedValue*);
|
||||
BUILD_OPTAB(keyType, checkForInt);
|
||||
@@ -1726,7 +1706,7 @@ void HhbcTranslator::VectorTranslator::emitArraySet(SSATmp* key,
|
||||
assert(value->type().notBoxed());
|
||||
KeyType keyType;
|
||||
bool checkForInt;
|
||||
checkStrictlyInteger(key, keyType, checkForInt);
|
||||
m_ht.checkStrictlyInteger(key, keyType, checkForInt);
|
||||
const DynLocation& base = *m_ni.inputs[m_mii.valCount()];
|
||||
bool setRef = base.outerType() == KindOfRef;
|
||||
typedef ArrayData* (*OpFunc)(ArrayData*, TypedValue*, TypedValue, RefData*);
|
||||
|
||||
@@ -7519,7 +7519,6 @@ void TranslatorX64::translateStrlen(const Tracelet& t,
|
||||
not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
void TranslatorX64::translateIncStat(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
int32_t counter = i.imm[0].u_IVA;
|
||||
@@ -7527,6 +7526,11 @@ void TranslatorX64::translateIncStat(const Tracelet& t,
|
||||
Stats::emitInc(a, Stats::StatCounter(counter), value);
|
||||
}
|
||||
|
||||
void TranslatorX64::translateArrayIdx(const Tracelet& t,
|
||||
const NormalizedInstruction& i) {
|
||||
not_reached();
|
||||
}
|
||||
|
||||
static void analyzeClassExistsImpl(NormalizedInstruction& i) {
|
||||
const int nameIdx = 1;
|
||||
const int autoIdx = 0;
|
||||
@@ -12247,6 +12251,7 @@ bool TranslatorX64::dumpTCData() {
|
||||
* Always-interp instructions,
|
||||
*/ \
|
||||
INTERP_OP(ContHandle) \
|
||||
INTERP_OP(ArrayIdx) \
|
||||
|
||||
|
||||
// Define the trivial analyze methods
|
||||
|
||||
@@ -629,6 +629,7 @@ MINSTRS
|
||||
CASE(ContHandle) \
|
||||
CASE(Strlen) \
|
||||
CASE(IncStat) \
|
||||
CASE(ArrayIdx) \
|
||||
|
||||
// These are instruction-like functions which cover more than one
|
||||
// opcode.
|
||||
|
||||
@@ -1263,6 +1263,9 @@ static const struct {
|
||||
{ OpLateBoundCls,{None, Stack1, OutClassRef, 1 }},
|
||||
{ OpNativeImpl, {None, None, OutNone, 0 }},
|
||||
{ OpCreateCl, {BStackN, Stack1, OutObject, 1 }},
|
||||
{ OpStrlen, {Stack1, Stack1, OutStrlen, 0 }},
|
||||
{ OpIncStat, {None, None, OutNone, 0 }},
|
||||
{ OpArrayIdx, {StackTop3, Stack1, OutUnknown, -2 }},
|
||||
|
||||
/*** 14. Continuation instructions ***/
|
||||
|
||||
@@ -1280,8 +1283,6 @@ static const struct {
|
||||
{ OpContCurrent, {None, Stack1, OutUnknown, 1 }},
|
||||
{ OpContStopped, {None, None, OutNone, 0 }},
|
||||
{ OpContHandle, {Stack1, None, OutNone, -1 }},
|
||||
{ OpStrlen, {Stack1, Stack1, OutStrlen, 0 }},
|
||||
{ OpIncStat, {None, None, OutNone, 0 }},
|
||||
};
|
||||
|
||||
static hphp_hash_map<Opcode, InstrInfo> instrInfo;
|
||||
|
||||
@@ -13216,6 +13216,14 @@ const char *g_class_map[] = {
|
||||
(const char *)0xffffffff /* KindOfUnknown: $t: Variant */, NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
(const char *)0x10116040, "hphp_array_idx", "", (const char*)0, (const char*)0,
|
||||
"/**\n * ( HipHop specific )\n *\n * hphp_array_idx() returns the value at the given key in the given array\n * or the given default value if it is not found. An error will be raised\n * if the search parameter is not an array.\n *\n * @key mixed Value to check.\n * @search mixed An array with keys to check.\n * @def mixed The value to return if key is not found in search.\n *\n * @return mixed Returns the value at 'key' in 'search' or 'def' if\n * it is not found.\n */",
|
||||
(const char *)0xffffffff /* KindOfUnknown: $t: Variant */, (const char *)0x2000, "key", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL,
|
||||
(const char *)0x2000, "search", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL,
|
||||
(const char *)0x2000, "def", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
(const char *)0x10706040, "is_bool", "", (const char*)0, (const char*)0,
|
||||
"/**\n * ( excerpt from http://php.net/manual/en/function.is-bool.php )\n *\n * Finds whether the given variable is a boolean.\n *\n * @var mixed The variable being evaluated.\n *\n * @return bool Returns TRUE if var is a boolean, FALSE otherwise.\n */",
|
||||
(const char *)0x9 /* KindOfBoolean */, (const char *)0x2000, "var", "", (const char *)0xffffffff /* KindOfUnknown: $t: Variant */, "", (const char *)0, "", (const char *)0, NULL,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
function main() {
|
||||
|
||||
$a = array('1' => '2', 'hello' => 'world', '' => 'empty');
|
||||
$b = null;
|
||||
|
||||
var_dump(hphp_array_idx('1', $a, 3));
|
||||
var_dump(hphp_array_idx('0', $a, 3));
|
||||
var_dump(hphp_array_idx(1, $a, 3));
|
||||
var_dump(hphp_array_idx(0, $a, 3));
|
||||
var_dump(hphp_array_idx(1.01, $a, 3));
|
||||
var_dump(hphp_array_idx(true, $a, 3));
|
||||
var_dump(hphp_array_idx(false, $a, 3));
|
||||
|
||||
var_dump(hphp_array_idx('hello', $a, 3));
|
||||
var_dump(hphp_array_idx('world', $a, 3));
|
||||
var_dump(hphp_array_idx('', $a, 3));
|
||||
var_dump(hphp_array_idx(null, $a, 3));
|
||||
|
||||
// should fatal
|
||||
var_dump(hphp_array_idx('not_reached', $b, 3));
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,12 @@
|
||||
string(1) "2"
|
||||
int(3)
|
||||
string(1) "2"
|
||||
int(3)
|
||||
string(1) "2"
|
||||
string(1) "2"
|
||||
int(3)
|
||||
string(5) "world"
|
||||
int(3)
|
||||
string(5) "empty"
|
||||
int(3)
|
||||
HipHop Fatal error: hphp_array_idx: search must be an array in %s on line 22
|
||||
Referência em uma Nova Issue
Bloquear um usuário