From 412dce677f0a4b73194b8cdae3d3a3213a821047 Mon Sep 17 00:00:00 2001 From: mwilliams Date: Fri, 7 Jun 2013 07:56:45 -0700 Subject: [PATCH] Better reffiness checks While debugging a sandbox crash, I spent some time looking at a huge sequnce of conditional masks, compares and branches that didnt seem to belong in the code I was debugging. Finally realized that it was a reffiness check. It looked way too complicated, so I investigated. Part of the problem was that we were avoiding a malloc in the case of a zero param function at the expense of an extra check. Instead, this diff always sets up at least 64 bits worth of m_refBitVec. But by using the space set aside for the pointer in Func::m_shared it avoids a malloc for any function with fewer than 65 arguments, and avoids the numParams check for the first 64 parameters. In addition, the existing code was spitting out a generic test for the guard condition - (mask & bits) == value - where mask and value are known constants. Since the most common case is that value == 0 (all the parameters are expected to be by value), we can usually omit the compare. In addition, since most functions only have a small number of parameters, we can usually get away with 8 bit, or 32 bit operations. The result is that for a typical function (fewer than 64 args, args expected to be by value) the reffiness guard is now test , Func::m_refBitVec[0] jne exit Rather than: move , reg1 xor reg2, reg2 cmp 1, Func::m_numParams jnl ok test AttrVarArgs, Func::m_attrs jne exit jmp done ok: load reg3, Func::m_refBitVec[0] and mask, reg3 cmp reg3, reg2 jne exit done: --- hphp/doc/ir.specification | 9 +- hphp/runtime/vm/func.cpp | 78 +- hphp/runtime/vm/func.h | 9 +- hphp/runtime/vm/jit/codegen.cpp | 128 ++- hphp/runtime/vm/jit/hhbctranslator.cpp | 21 +- hphp/runtime/vm/jit/ir.h | 10 +- hphp/runtime/vm/jit/translator-x64.cpp | 7 +- hphp/test/quick/args-test.php | 103 +++ hphp/test/quick/args-test.php.expect | 1124 ++++++++++++++++++++++++ hphp/test/quick/va_poly.php | 12 +- hphp/test/quick/va_poly.php.expect | 4 + 11 files changed, 1394 insertions(+), 111 deletions(-) create mode 100644 hphp/test/quick/args-test.php create mode 100644 hphp/test/quick/args-test.php.expect diff --git a/hphp/doc/ir.specification b/hphp/doc/ir.specification index a732d8b22..98debd10f 100644 --- a/hphp/doc/ir.specification +++ b/hphp/doc/ir.specification @@ -398,16 +398,15 @@ CheckInitMem S0:PtrToGen S1:ConstInt -> L If the value at S0 + S1 (in bytes) has type Uninit, branch to L. -GuardRefs S0:FuncPtr S1:Int S2:Int S3:Int S4:Int S5:Int S6:Int +GuardRefs S0:FuncPtr S1:Int S2:Int S3:Int S4:Int S5:Int Perform reffiness guard checks. Operands: S0 - function pointer for the frame S1 - num params expected in the func - S2 - type Int, but actually a pointer to the func bitvector - S3 - first bit to check, must be a multiple of 64 - S4 - mask to check (RefDeps::Record::m_mask entires) - S5 - values to check (RefDeps::Record::m_vals entires) + S2 - first bit to check, must be a multiple of 64 + S3 - mask to check (RefDeps::Record::m_mask entires) + S4 - values to check (RefDeps::Record::m_vals entires) If any of the checks fail, make a fallback jump. (Jump to a service request that will chain to a retranslation of this tracelet.) diff --git a/hphp/runtime/vm/func.cpp b/hphp/runtime/vm/func.cpp index 9c694a660..8f3cd07d2 100644 --- a/hphp/runtime/vm/func.cpp +++ b/hphp/runtime/vm/func.cpp @@ -185,7 +185,7 @@ Func::Func(Unit& unit, Id id, int line1, int line2, , m_baseCls(nullptr) , m_name(name) , m_namedEntity(nullptr) - , m_refBitVec(nullptr) + , m_refBitVal(0) , m_cachedOffset(0) , m_maxStackCells(0) , m_numParams(0) @@ -208,7 +208,7 @@ Func::Func(Unit& unit, PreClass* preClass, int line1, int line2, Offset base, , m_baseCls(nullptr) , m_name(name) , m_namedEntity(nullptr) - , m_refBitVec(nullptr) + , m_refBitVal(0) , m_cachedOffset(0) , m_maxStackCells(0) , m_numParams(0) @@ -394,56 +394,53 @@ bool Func::isNameBindingImmutable(const Unit* fromUnit) const { } bool Func::byRef(int32_t arg) const { - // Super special case. A handful of builtins are varargs functions where the - // (not formally declared) varargs are pass-by-reference. psychedelic-kitten - if (arg >= m_numParams && info() && - (info()->attribute & (ClassInfo::RefVariableArguments | - ClassInfo::MixedVariableArguments))) { - return true; + const uint64_t* ref = &m_refBitVal; + assert(arg >= 0); + if (UNLIKELY(arg >= kBitsPerQword)) { + // Super special case. A handful of builtins are varargs functions where the + // (not formally declared) varargs are pass-by-reference. psychedelic-kitten + if (arg >= m_numParams) { + return m_attrs & AttrVariadicByRef; + } + ref = &shared()->m_refBitPtr[(uint32_t)arg / kBitsPerQword - 1]; } - int qword = arg / kBitsPerQword; - int bit = arg % kBitsPerQword; - bool retval = arg < m_numParams && (m_refBitVec[qword] & (1ull << bit)) != 0; - return retval; + int bit = (uint32_t)arg % kBitsPerQword; + return *ref & (1ull << bit); } bool Func::mustBeRef(int32_t arg) const { - // return true if the argument is required to be a reference - // (and thus should be an lvalue) - if (arg >= m_numParams && info() && - ((info()->attribute & (ClassInfo::RefVariableArguments | - ClassInfo::MixedVariableArguments)) == - ClassInfo::RefVariableArguments)) { - return true; + if (byRef(arg)) { + return arg < m_numParams || !(m_attrs & AttrVariadicByRef) || + !info() || !(info()->attribute & ClassInfo::MixedVariableArguments); } - int qword = arg / kBitsPerQword; - int bit = arg % kBitsPerQword; - bool retval = arg < m_numParams && (m_refBitVec[qword] & (1ull << bit)) != 0; - return retval; + return false; } void Func::appendParam(bool ref, const Func::ParamInfo& info, std::vector& pBuilder) { int qword = m_numParams / kBitsPerQword; int bit = m_numParams % kBitsPerQword; + m_numParams++; + uint64_t* refBits = &m_refBitVal; // Grow args, if necessary. - if ((m_numParams++ & (kBitsPerQword - 1)) == 0) { - assert(shared()->m_refBitVec == m_refBitVec); - shared()->m_refBitVec = m_refBitVec = (uint64_t*) - realloc(shared()->m_refBitVec, - // E.g., 65th m_numParams -> 2 qwords - (1 + m_numParams / kBitsPerQword) * sizeof(uint64_t)); + if (qword) { + if (bit == 0) { + shared()->m_refBitPtr = (uint64_t*) + realloc(shared()->m_refBitPtr, qword * sizeof(uint64_t)); + } + refBits = shared()->m_refBitPtr + qword - 1; + } + if (bit == 0) { // The new word is either zerod or set to 1, depending on whether // we are one of the special builtins that takes variadic // reference arguments. This is for use in the translator. - shared()->m_refBitVec[m_numParams / kBitsPerQword] = - (m_attrs & AttrVariadicByRef) ? -1ull : 0; + *refBits = (m_attrs & AttrVariadicByRef) ? -1ull : 0; } - assert(!!(shared()->m_refBitVec[qword] & (uint64_t(1) << bit)) == - !!(m_attrs & AttrVariadicByRef)); - shared()->m_refBitVec[qword] &= ~(1ull << bit); - shared()->m_refBitVec[qword] |= uint64_t(ref) << bit; + + assert(!(*refBits & (uint64_t(1) << bit)) == !(m_attrs & AttrVariadicByRef)); + *refBits &= ~(1ull << bit); + *refBits |= uint64_t(ref) << bit; pBuilder.push_back(info); } @@ -642,16 +639,14 @@ Func::SharedData::SharedData(PreClass* preClass, Id id, : m_preClass(preClass), m_id(id), m_base(base), m_numLocals(0), m_numIterators(0), m_past(past), m_line1(line1), m_line2(line2), - m_info(nullptr), m_refBitVec(nullptr), m_builtinFuncPtr(nullptr), + m_info(nullptr), m_refBitPtr(0), m_builtinFuncPtr(nullptr), m_docComment(docComment), m_top(top), m_isClosureBody(false), m_isGenerator(false), m_isGeneratorFromClosure(false), m_hasGeneratorAsBody(false), m_originalFilename(nullptr) { } Func::SharedData::~SharedData() { - if (m_refBitVec) { - free(m_refBitVec); - } + free(m_refBitPtr); } void Func::SharedData::atomicRelease() { @@ -952,6 +947,11 @@ Func* FuncEmitter::create(Unit& unit, PreClass* preClass /* = NULL */) const { pi.setUserType(m_params[i].userType()); f->appendParam(m_params[i].ref(), pi, pBuilder); } + if (!m_params.size()) { + assert(!f->m_refBitVal && !f->shared()->m_refBitPtr); + f->m_refBitVal = attrs & AttrVariadicByRef ? -1uLL : 0uLL; + } + f->shared()->m_params = pBuilder; f->shared()->m_localNames.create(m_localNames); f->shared()->m_numLocals = m_numLocals; diff --git a/hphp/runtime/vm/func.h b/hphp/runtime/vm/func.h index 397eeaecd..9ff60bd62 100644 --- a/hphp/runtime/vm/func.h +++ b/hphp/runtime/vm/func.h @@ -431,12 +431,13 @@ public: // Offset accessors for the translator. X(unit); X(cls); X(numParams); - X(refBitVec); + X(refBitVal); X(fullName); X(prologueTable); X(maybeIntercepted); X(maxStackCells); X(funcBody); + X(shared); #undef X private: @@ -453,7 +454,9 @@ private: int m_line2; DataType m_returnType; const ClassInfo::MethodInfo* m_info; // For builtins. - uint64_t* m_refBitVec; + // bits 64 and up of the reffiness guards (first 64 bits + // are in Func::m_refBitVal) + uint64_t* m_refBitPtr; BuiltinFunction m_builtinFuncPtr; BuiltinFunction m_nativeFuncPtr; ParamInfoVec m_params; // m_params[i] corresponds to parameter i. @@ -508,7 +511,7 @@ private: const NamedEntity* m_namedEntity; Slot m_methodSlot; }; - uint64_t* m_refBitVec; + uint64_t m_refBitVal; public: // used by Unit unsigned m_cachedOffset; private: diff --git a/hphp/runtime/vm/jit/codegen.cpp b/hphp/runtime/vm/jit/codegen.cpp index f15a95ccb..2e8b383cb 100644 --- a/hphp/runtime/vm/jit/codegen.cpp +++ b/hphp/runtime/vm/jit/codegen.cpp @@ -4221,14 +4221,13 @@ void CodeGenerator::cgCheckTypeMem(IRInstruction* inst) { } void CodeGenerator::cgGuardRefs(IRInstruction* inst) { - assert(inst->numSrcs() == 6); + assert(inst->numSrcs() == 5); SSATmp* funcPtrTmp = inst->src(0); SSATmp* nParamsTmp = inst->src(1); - SSATmp* bitsPtrTmp = inst->src(2); - SSATmp* firstBitNumTmp = inst->src(3); - SSATmp* mask64Tmp = inst->src(4); - SSATmp* vals64Tmp = inst->src(5); + SSATmp* firstBitNumTmp = inst->src(2); + SSATmp* mask64Tmp = inst->src(3); + SSATmp* vals64Tmp = inst->src(4); // Get values in place assert(funcPtrTmp->type() == Type::Func); @@ -4237,63 +4236,106 @@ void CodeGenerator::cgGuardRefs(IRInstruction* inst) { assert(nParamsTmp->type() == Type::Int); auto nParamsReg = m_regs[nParamsTmp].reg(); - assert(nParamsReg != InvalidReg); - - assert(bitsPtrTmp->type() == Type::Int); - auto bitsPtrReg = m_regs[bitsPtrTmp].reg(); - assert(bitsPtrReg != InvalidReg); + assert(nParamsReg != InvalidReg || nParamsTmp->isConst()); assert(firstBitNumTmp->isConst() && firstBitNumTmp->type() == Type::Int); uint32_t firstBitNum = (uint32_t)(firstBitNumTmp->getValInt()); assert(mask64Tmp->type() == Type::Int); - assert(mask64Tmp->inst()->op() == LdConst); + assert(mask64Tmp->isConst()); auto mask64Reg = m_regs[mask64Tmp].reg(); - assert(mask64Reg != InvalidReg); - int64_t mask64 = mask64Tmp->getValInt(); + assert(mask64Reg != InvalidReg || mask64Tmp->inst()->op() != LdConst); + uint64_t mask64 = mask64Tmp->getValInt(); + assert(mask64); assert(vals64Tmp->type() == Type::Int); - assert(vals64Tmp->inst()->op() == LdConst); + assert(vals64Tmp->isConst()); auto vals64Reg = m_regs[vals64Tmp].reg(); - assert(vals64Reg != InvalidReg); - int64_t vals64 = vals64Tmp->getValInt(); + assert(vals64Reg != InvalidReg || vals64Tmp->inst()->op() != LdConst); + uint64_t vals64 = vals64Tmp->getValInt(); + assert((vals64 & mask64) == vals64); auto const destSK = SrcKey(curFunc(), m_curTrace->bcOff()); auto const destSR = m_tx64->getSrcRec(destSK); auto thenBody = [&] { - auto bitsValReg = m_rScratch; - // Load the bit values in bitValReg: - // bitsValReg <- [bitsValPtr + (firstBitNum / 64)] - m_as.load_reg64_disp_reg64(bitsPtrReg, - sizeof(uint64_t) * (firstBitNum / 64), - bitsValReg); - // bitsValReg <- bitsValReg & mask64 - m_as.and_reg64_reg64(mask64Reg, bitsValReg); + auto bitsOff = sizeof(uint64_t) * (firstBitNum / 64); + auto cond = CC_NE; + auto bitsPtrReg = m_rScratch; - // If bitsValReg != vals64Reg, then goto Exit - m_as.cmp_reg64_reg64(bitsValReg, vals64Reg); - m_tx64->emitFallbackCondJmp(m_as, *destSR, CC_NE); + if (firstBitNum == 0) { + bitsOff = Func::refBitValOff(); + bitsPtrReg = funcPtrReg; + } else { + m_as.loadq(funcPtrReg[Func::sharedOff()], bitsPtrReg); + bitsOff -= sizeof(uint64_t); + } + + if (vals64 == 0 || (mask64 & (mask64 - 1)) == 0) { + // If vals64 is zero, or we're testing a single + // bit, we can get away with a single test, + // rather than mask-and-compare + if (mask64Reg != InvalidReg) { + m_as.testq (mask64Reg, bitsPtrReg[bitsOff]); + } else { + if (mask64 < 256) { + m_as.testb((int8_t)mask64, bitsPtrReg[bitsOff]); + } else { + m_as.testl((int32_t)mask64, bitsPtrReg[bitsOff]); + } + } + if (vals64) cond = CC_E; + } else { + auto bitsValReg = m_rScratch; + m_as. loadq (bitsPtrReg[bitsOff], bitsValReg); + if (debug) bitsPtrReg = InvalidReg; + + // bitsValReg <- bitsValReg & mask64 + if (mask64Reg != InvalidReg) { + m_as. andq (mask64Reg, bitsValReg); + } else if (mask64 < 256) { + m_as. andb ((int8_t)mask64, rbyte(bitsValReg)); + } else { + m_as. andl ((int32_t)mask64, r32(bitsValReg)); + } + + // If bitsValReg != vals64, then goto Exit + if (vals64Reg != InvalidReg) { + m_as. cmpq (vals64Reg, bitsValReg); + } else if (mask64 < 256) { + assert(vals64 < 256); + m_as. cmpb ((int8_t)vals64, rbyte(bitsValReg)); + } else { + m_as. cmpl ((int32_t)vals64, r32(bitsValReg)); + } + } + m_tx64->emitFallbackCondJmp(m_as, *destSR, cond); }; - // If few enough args... - m_as.cmp_imm32_reg32(firstBitNum + 1, nParamsReg); - if (vals64 == 0 && mask64 == 0) { - ifThen(m_as, CC_NL, thenBody); - } else if (vals64 != 0 && vals64 != mask64) { - m_tx64->emitFallbackCondJmp(m_as, *destSR, CC_L); + if (firstBitNum == 0) { + assert(nParamsReg == InvalidReg); + // This is the first 64 bits. No need to check + // nParams. thenBody(); - } else if (vals64 != 0) { - ifThenElse(CC_NL, thenBody, /* else */ [&] { - // If not special builtin... - m_as.testl(AttrVariadicByRef, funcPtrReg[Func::attrsOff()]); - m_tx64->emitFallbackCondJmp(m_as, *destSR, CC_Z); - }); } else { - ifThenElse(CC_NL, thenBody, /* else */ [&] { - m_as.testl(AttrVariadicByRef, funcPtrReg[Func::attrsOff()]); - m_tx64->emitFallbackCondJmp(m_as, *destSR, CC_NZ); - }); + assert(nParamsReg != InvalidReg); + // Check number of args... + m_as. cmpq (firstBitNum, nParamsReg); + + if (vals64 != 0 && vals64 != mask64) { + // If we're beyond nParams, then either all params + // are refs, or all params are non-refs, so if vals64 + // isn't 0 and isnt mask64, there's no possibility of + // a match + m_tx64->emitFallbackCondJmp(m_as, *destSR, CC_LE); + thenBody(); + } else { + ifThenElse(CC_NLE, thenBody, /* else */ [&] { + // If not special builtin... + m_as.testl(AttrVariadicByRef, funcPtrReg[Func::attrsOff()]); + m_tx64->emitFallbackCondJmp(m_as, *destSR, vals64 ? CC_Z : CC_NZ); + }); + } } } diff --git a/hphp/runtime/vm/jit/hhbctranslator.cpp b/hphp/runtime/vm/jit/hhbctranslator.cpp index e1e4baa8c..ea02e7753 100644 --- a/hphp/runtime/vm/jit/hhbctranslator.cpp +++ b/hphp/runtime/vm/jit/hhbctranslator.cpp @@ -2452,12 +2452,7 @@ void HhbcTranslator::guardRefs(int64_t entryArDelta, const vector& vals) { int32_t actRecOff = cellsToBytes(entryArDelta); SSATmp* funcPtr = gen(LdARFuncPtr, m_tb->sp(), cns(actRecOff)); - SSATmp* nParams = gen( - LdRaw, Type::Int, funcPtr, cns(RawMemSlot::FuncNumParams) - ); - SSATmp* bitsPtr = gen( - LdRaw, Type::Int, funcPtr, cns(RawMemSlot::FuncRefBitVec) - ); + SSATmp* nParams = nullptr; for (unsigned i = 0; i < mask.size(); i += 64) { assert(i < vals.size()); @@ -2468,14 +2463,22 @@ void HhbcTranslator::guardRefs(int64_t entryArDelta, } uint64_t vals64 = packBitVec(vals, i); + if (i == 0) { + nParams = cns(64); + } else if (i == 64) { + nParams = gen( + LdRaw, Type::Int, funcPtr, cns(RawMemSlot::FuncNumParams) + ); + } + SSATmp* maskTmp = !(mask64>>32) ? cns(mask64) : m_tb->genLdConst(mask64); + SSATmp* valsTmp = !(vals64>>32) ? cns(vals64) : m_tb->genLdConst(vals64); gen( GuardRefs, funcPtr, nParams, - bitsPtr, cns(i), - m_tb->genLdConst(mask64), - m_tb->genLdConst(vals64) + maskTmp, + valsTmp ); } } diff --git a/hphp/runtime/vm/jit/ir.h b/hphp/runtime/vm/jit/ir.h index 4975e614f..d2f1e4ac4 100644 --- a/hphp/runtime/vm/jit/ir.h +++ b/hphp/runtime/vm/jit/ir.h @@ -167,7 +167,6 @@ O(GuardRefs, ND, S(Func) \ S(Int) \ S(Int) \ S(Int) \ - S(Int) \ S(Int), E) \ O(AssertLoc, ND, S(FramePtr), E) \ O(OverrideLoc, ND, S(FramePtr), E) \ @@ -775,7 +774,7 @@ class RawMemSlot { enum Kind { ContLabel, ContDone, ContRunning, ContARPtr, - StrLen, FuncNumParams, FuncRefBitVec, ContEntry, MisCtx, + StrLen, FuncNumParams, ContEntry, MisCtx, MaxKind }; @@ -787,7 +786,6 @@ class RawMemSlot { case ContARPtr: return GetContARPtr(); case StrLen: return GetStrLen(); case FuncNumParams: return GetFuncNumParams(); - case FuncRefBitVec: return GetFuncRefBitVec(); case ContEntry: return GetContEntry(); case MisCtx: return GetMisCtx(); default: not_reached(); @@ -826,11 +824,7 @@ class RawMemSlot { return m; } static RawMemSlot& GetFuncNumParams() { - static RawMemSlot m(Func::numParamsOff(), Transl::sz::qword, Type::Int); - return m; - } - static RawMemSlot& GetFuncRefBitVec() { - static RawMemSlot m(Func::refBitVecOff(), Transl::sz::qword, Type::Int); + static RawMemSlot m(Func::numParamsOff(), Transl::sz::dword, Type::Int); return m; } static RawMemSlot& GetContEntry() { diff --git a/hphp/runtime/vm/jit/translator-x64.cpp b/hphp/runtime/vm/jit/translator-x64.cpp index 63cd5234d..14edfb55a 100644 --- a/hphp/runtime/vm/jit/translator-x64.cpp +++ b/hphp/runtime/vm/jit/translator-x64.cpp @@ -2146,9 +2146,10 @@ TranslatorX64::checkRefs(X64Assembler& a, for (RefDeps::ArMap::const_iterator it = refDeps.m_arMap.begin(); it != refDeps.m_arMap.end(); ++it) { // Be careful! The actual Func might have fewer refs than the number - // of args we're passing. To forestall this, we're going to have to - // keep checking i against the number of params. We consider invocations - // with too many arguments to have passed their checks. + // of args we're passing. To forestall this, we always prepare at + // least 64 bits in the Func, and always fill out the refBitVec + // to a multiple of 64 bits + int entryArDelta = it->first; m_hhbcTrans->guardRefs(entryArDelta, diff --git a/hphp/test/quick/args-test.php b/hphp/test/quick/args-test.php new file mode 100644 index 000000000..a6e42df44 --- /dev/null +++ b/hphp/test/quick/args-test.php @@ -0,0 +1,103 @@ + + int(0) + [1]=> + int(1) + [2]=> + int(2) + [3]=> + int(3) + [4]=> + int(4) + [5]=> + int(5) + [6]=> + int(6) + [7]=> + int(7) + [8]=> + int(8) + [9]=> + int(9) + [10]=> + int(10) + [11]=> + int(11) + [12]=> + int(12) + [13]=> + int(13) + [14]=> + int(14) + [15]=> + int(15) + [16]=> + int(16) + [17]=> + int(17) + [18]=> + int(18) + [19]=> + int(19) + [20]=> + int(20) + [21]=> + int(21) + [22]=> + int(22) + [23]=> + int(23) + [24]=> + int(24) + [25]=> + int(25) + [26]=> + int(26) + [27]=> + int(27) + [28]=> + int(28) + [29]=> + int(29) + [30]=> + int(30) + [31]=> + int(31) + [32]=> + int(32) + [33]=> + int(33) + [34]=> + int(34) + [35]=> + int(35) + [36]=> + int(36) + [37]=> + int(37) + [38]=> + int(38) + [39]=> + int(39) + [40]=> + int(40) + [41]=> + int(41) + [42]=> + int(42) + [43]=> + int(43) + [44]=> + int(44) + [45]=> + int(45) + [46]=> + int(46) + [47]=> + int(47) + [48]=> + int(48) + [49]=> + int(49) + [50]=> + int(50) + [51]=> + int(51) + [52]=> + int(52) + [53]=> + int(53) + [54]=> + int(54) + [55]=> + int(55) + [56]=> + int(56) + [57]=> + int(57) + [58]=> + int(58) + [59]=> + int(59) + [60]=> + int(60) + [61]=> + int(61) + [62]=> + int(62) + [63]=> + int(63) + [64]=> + int(64) + [65]=> + int(65) + [66]=> + int(66) + [67]=> + int(67) + [68]=> + int(68) + [69]=> + int(69) + [70]=> + int(70) + [71]=> + int(71) + [72]=> + int(72) + [73]=> + int(73) + [74]=> + int(74) + [75]=> + int(75) + [76]=> + int(76) + [77]=> + int(77) + [78]=> + int(78) + [79]=> + int(79) + [80]=> + int(80) + [81]=> + int(81) + [82]=> + int(82) + [83]=> + int(83) + [84]=> + int(84) + [85]=> + int(85) + [86]=> + int(86) + [87]=> + int(87) + [88]=> + int(88) + [89]=> + int(89) + [90]=> + int(90) + [91]=> + int(91) + [92]=> + int(92) + [93]=> + int(93) + [94]=> + int(94) + [95]=> + int(95) + [96]=> + int(96) + [97]=> + int(97) + [98]=> + int(98) + [99]=> + int(99) + [100]=> + int(100) + [101]=> + int(101) + [102]=> + int(102) + [103]=> + int(103) + [104]=> + int(104) + [105]=> + int(105) + [106]=> + int(106) + [107]=> + int(107) + [108]=> + int(108) + [109]=> + int(109) + [110]=> + int(110) + [111]=> + int(111) + [112]=> + int(112) + [113]=> + int(113) + [114]=> + int(114) + [115]=> + int(115) + [116]=> + int(116) + [117]=> + int(117) + [118]=> + int(118) + [119]=> + int(119) + [120]=> + int(120) + [121]=> + int(121) + [122]=> + int(122) + [123]=> + int(123) + [124]=> + int(124) + [125]=> + int(125) + [126]=> + int(126) + [127]=> + int(127) + [128]=> + int(128) + [129]=> + int(129) + [130]=> + int(130) + [131]=> + int(131) + [132]=> + int(132) + [133]=> + int(133) + [134]=> + int(134) + [135]=> + int(135) + [136]=> + int(136) + [137]=> + int(137) + [138]=> + int(138) + [139]=> + int(139) + [140]=> + int(140) + [141]=> + int(141) + [142]=> + int(142) + [143]=> + int(143) + [144]=> + int(144) + [145]=> + int(145) + [146]=> + int(146) + [147]=> + int(147) + [148]=> + int(148) + [149]=> + int(149) +} +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +array(150) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) + [3]=> + int(3) + [4]=> + int(4) + [5]=> + int(5) + [6]=> + int(6) + [7]=> + int(7) + [8]=> + int(8) + [9]=> + int(9) + [10]=> + int(10) + [11]=> + int(11) + [12]=> + int(12) + [13]=> + int(13) + [14]=> + int(14) + [15]=> + int(15) + [16]=> + int(16) + [17]=> + int(17) + [18]=> + int(18) + [19]=> + int(19) + [20]=> + int(20) + [21]=> + int(21) + [22]=> + int(22) + [23]=> + int(23) + [24]=> + int(24) + [25]=> + int(25) + [26]=> + int(26) + [27]=> + int(27) + [28]=> + int(28) + [29]=> + int(29) + [30]=> + int(30) + [31]=> + int(31) + [32]=> + int(32) + [33]=> + int(33) + [34]=> + int(34) + [35]=> + int(35) + [36]=> + int(36) + [37]=> + int(37) + [38]=> + int(38) + [39]=> + int(39) + [40]=> + int(40) + [41]=> + int(41) + [42]=> + int(42) + [43]=> + int(43) + [44]=> + int(44) + [45]=> + int(45) + [46]=> + int(46) + [47]=> + int(47) + [48]=> + int(48) + [49]=> + int(49) + [50]=> + int(50) + [51]=> + int(51) + [52]=> + int(52) + [53]=> + int(53) + [54]=> + int(54) + [55]=> + int(55) + [56]=> + int(56) + [57]=> + int(57) + [58]=> + int(58) + [59]=> + int(59) + [60]=> + int(60) + [61]=> + int(61) + [62]=> + int(62) + [63]=> + int(63) + [64]=> + int(64) + [65]=> + int(65) + [66]=> + int(66) + [67]=> + int(67) + [68]=> + int(68) + [69]=> + int(69) + [70]=> + int(70) + [71]=> + int(71) + [72]=> + int(72) + [73]=> + int(73) + [74]=> + int(74) + [75]=> + int(75) + [76]=> + int(76) + [77]=> + int(77) + [78]=> + int(78) + [79]=> + int(79) + [80]=> + int(80) + [81]=> + int(81) + [82]=> + int(82) + [83]=> + int(83) + [84]=> + int(84) + [85]=> + int(85) + [86]=> + int(86) + [87]=> + int(87) + [88]=> + int(88) + [89]=> + int(89) + [90]=> + int(90) + [91]=> + int(91) + [92]=> + int(92) + [93]=> + int(93) + [94]=> + int(94) + [95]=> + int(95) + [96]=> + int(96) + [97]=> + int(97) + [98]=> + int(98) + [99]=> + int(99) + [100]=> + int(100) + [101]=> + int(101) + [102]=> + int(102) + [103]=> + int(103) + [104]=> + int(104) + [105]=> + int(105) + [106]=> + int(106) + [107]=> + int(107) + [108]=> + int(108) + [109]=> + int(109) + [110]=> + int(110) + [111]=> + int(111) + [112]=> + int(112) + [113]=> + int(113) + [114]=> + int(114) + [115]=> + int(115) + [116]=> + int(116) + [117]=> + int(117) + [118]=> + int(118) + [119]=> + int(119) + [120]=> + int(120) + [121]=> + int(121) + [122]=> + int(122) + [123]=> + int(123) + [124]=> + int(124) + [125]=> + int(125) + [126]=> + int(126) + [127]=> + int(127) + [128]=> + int(128) + [129]=> + int(129) + [130]=> + int(130) + [131]=> + int(131) + [132]=> + int(132) + [133]=> + int(133) + [134]=> + int(134) + [135]=> + int(135) + [136]=> + int(136) + [137]=> + int(137) + [138]=> + int(138) + [139]=> + int(139) + [140]=> + int(140) + [141]=> + int(141) + [142]=> + int(142) + [143]=> + int(143) + [144]=> + int(144) + [145]=> + int(145) + [146]=> + int(146) + [147]=> + int(147) + [148]=> + int(148) + [149]=> + int(149) +} +array(150) { + [0]=> + int(0) + [1]=> + int(1) + [2]=> + int(2) + [3]=> + int(3) + [4]=> + int(4) + [5]=> + int(5) + [6]=> + int(6) + [7]=> + int(7) + [8]=> + int(8) + [9]=> + int(9) + [10]=> + int(10) + [11]=> + int(11) + [12]=> + int(12) + [13]=> + int(13) + [14]=> + int(14) + [15]=> + int(15) + [16]=> + int(16) + [17]=> + int(17) + [18]=> + int(18) + [19]=> + int(19) + [20]=> + int(20) + [21]=> + int(21) + [22]=> + int(22) + [23]=> + int(23) + [24]=> + int(24) + [25]=> + int(25) + [26]=> + int(26) + [27]=> + int(27) + [28]=> + int(28) + [29]=> + int(29) + [30]=> + int(30) + [31]=> + int(31) + [32]=> + int(32) + [33]=> + int(33) + [34]=> + int(34) + [35]=> + int(35) + [36]=> + int(36) + [37]=> + int(37) + [38]=> + int(38) + [39]=> + int(39) + [40]=> + int(40) + [41]=> + int(41) + [42]=> + int(42) + [43]=> + int(43) + [44]=> + int(44) + [45]=> + int(45) + [46]=> + int(46) + [47]=> + int(47) + [48]=> + int(48) + [49]=> + int(49) + [50]=> + int(50) + [51]=> + int(51) + [52]=> + int(52) + [53]=> + int(53) + [54]=> + int(54) + [55]=> + int(55) + [56]=> + int(56) + [57]=> + int(57) + [58]=> + int(58) + [59]=> + int(59) + [60]=> + int(60) + [61]=> + int(61) + [62]=> + int(62) + [63]=> + int(63) + [64]=> + int(65) + [65]=> + int(66) + [66]=> + int(66) + [67]=> + int(67) + [68]=> + int(68) + [69]=> + int(69) + [70]=> + int(70) + [71]=> + int(71) + [72]=> + int(72) + [73]=> + int(73) + [74]=> + int(74) + [75]=> + int(75) + [76]=> + int(76) + [77]=> + int(77) + [78]=> + int(78) + [79]=> + int(79) + [80]=> + int(80) + [81]=> + int(81) + [82]=> + int(82) + [83]=> + int(83) + [84]=> + int(84) + [85]=> + int(85) + [86]=> + int(86) + [87]=> + int(87) + [88]=> + int(88) + [89]=> + int(89) + [90]=> + int(90) + [91]=> + int(91) + [92]=> + int(92) + [93]=> + int(93) + [94]=> + int(94) + [95]=> + int(95) + [96]=> + int(96) + [97]=> + int(97) + [98]=> + int(98) + [99]=> + int(99) + [100]=> + int(100) + [101]=> + int(101) + [102]=> + int(102) + [103]=> + int(103) + [104]=> + int(104) + [105]=> + int(105) + [106]=> + int(106) + [107]=> + int(107) + [108]=> + int(108) + [109]=> + int(109) + [110]=> + int(110) + [111]=> + int(111) + [112]=> + int(112) + [113]=> + int(113) + [114]=> + int(114) + [115]=> + int(115) + [116]=> + int(116) + [117]=> + int(117) + [118]=> + int(118) + [119]=> + int(119) + [120]=> + int(120) + [121]=> + int(121) + [122]=> + int(122) + [123]=> + int(123) + [124]=> + int(124) + [125]=> + int(125) + [126]=> + int(126) + [127]=> + int(127) + [128]=> + int(128) + [129]=> + int(129) + [130]=> + int(130) + [131]=> + int(131) + [132]=> + int(132) + [133]=> + int(133) + [134]=> + int(134) + [135]=> + int(135) + [136]=> + int(136) + [137]=> + int(137) + [138]=> + int(138) + [139]=> + int(139) + [140]=> + int(140) + [141]=> + int(141) + [142]=> + int(142) + [143]=> + int(143) + [144]=> + int(144) + [145]=> + int(145) + [146]=> + int(146) + [147]=> + int(147) + [148]=> + int(148) + [149]=> + int(149) +} diff --git a/hphp/test/quick/va_poly.php b/hphp/test/quick/va_poly.php index 0176a9d6f..bec3eafad 100644 --- a/hphp/test/quick/va_poly.php +++ b/hphp/test/quick/va_poly.php @@ -2,11 +2,20 @@ class C1 { function __call($a, $b) { + echo '.'; } } class C2 { function __call($a, $b) { + echo '+'; + } +} + +class C3 { + function maul($a,$b,&$c) { + echo '*'; + $c++; } } @@ -21,7 +30,8 @@ function main() { $o->maul(1, 2, $i, 3, 4, 5, 6, 7); $o->maul(1, 2, $i, 3, 4, 5, 6, 7, 8); // Send subsequent passes to C2::__call - $o = new C2(); + $o = $i == 0 ? new C2() : new C3(); + echo "\n"; } } diff --git a/hphp/test/quick/va_poly.php.expect b/hphp/test/quick/va_poly.php.expect index e69de29bb..604938a24 100644 --- a/hphp/test/quick/va_poly.php.expect +++ b/hphp/test/quick/va_poly.php.expect @@ -0,0 +1,4 @@ +...... +++++++ +****** +******