// Form a trace of the async stack starting with the currently running // generator, if any. For now we just toss in the function name and // id, as well as the pseudo-frames for context breaks at explicit // joins. Later we'll add more, like file and line, hopefully function // args, wait handle status, etc. static Array createAsyncStacktrace() { Array trace; auto currentWaitHandle = HHVM_FN(asio_get_running)(); if (currentWaitHandle.isNull()) return trace; Array depStack = objToWaitableWaitHandle(currentWaitHandle)->t_getdependencystack(); for (ArrayIter iter(depStack); iter; ++iter) { if (iter.secondRef().isNull()) { trace.append(Array(staticEmptyArray())); } else { auto wh = objToWaitableWaitHandle(iter.secondRef().toObject()); auto parents = wh->t_getparents(); Array ancestors; for (ArrayIter piter(parents); piter; ++piter) { // Note: the parent list contains no nulls. auto parent = objToWaitableWaitHandle(piter.secondRef().toObject()); ancestors.append(parent->t_getname()); } Array frameData; frameData.set(s_function, wh->t_getname(), true); frameData.set(s_id, wh->t_getid(), true); frameData.set(s_ancestors, ancestors, true); // Async function wait handles may have a source location to add. if (wh->getKind() == c_WaitHandle::Kind::AsyncFunction) { auto afwh = static_cast<c_AsyncFunctionWaitHandle*>(wh); addAsyncFunctionLocation(frameData, *afwh); } trace.append(frameData); } } return trace; }
void emitNewMixedArray(HTS& env, int32_t capacity) { if (capacity == 0) { push(env, cns(env, staticEmptyArray())); } else { push(env, gen(env, NewMixedArray, cns(env, capacity))); } }
void emitNewArray(HTS& env, int32_t capacity) { if (capacity == 0) { push(env, cns(env, staticEmptyArray())); } else { if (auto newCap = PackedArray::getMaxCapInPlaceFast(capacity)) { assert(newCap > static_cast<uint32_t>(capacity)); capacity = newCap; } push(env, gen(env, NewArray, cns(env, capacity))); } }
void autoTypecheck(const Unit* unit) { if (RuntimeOption::RepoAuthoritative || !RuntimeOption::AutoTypecheck || tl_doneAutoTypecheck || !unit->isHHFile()) { return; } tl_doneAutoTypecheck = true; vm_call_user_func("\\HH\\Client\\typecheck_and_error", staticEmptyArray()); }
void emitCastArray(HTS& env) { auto const src = popC(env); push( env, [&] { if (src->isA(Type::Arr)) return src; if (src->isA(Type::Null)) return cns(env, staticEmptyArray()); if (src->isA(Type::Bool)) return gen(env, ConvBoolToArr, src); if (src->isA(Type::Dbl)) return gen(env, ConvDblToArr, src); if (src->isA(Type::Int)) return gen(env, ConvIntToArr, src); if (src->isA(Type::Str)) return gen(env, ConvStrToArr, src); if (src->isA(Type::Obj)) return gen(env, ConvObjToArr, src); return gen(env, ConvCellToArr, src); }() ); }
TEST(Type, RuntimeType) { auto sd = StringData::MakeMalloced("", 0); SCOPE_EXIT { sd->destruct(); }; HPHP::JIT::RuntimeType rt(sd); Type t = Type(rt); EXPECT_TRUE(t.subtypeOf(Type::Str)); EXPECT_FALSE(t.subtypeOf(Type::Int)); rt = HPHP::JIT::RuntimeType(staticEmptyArray()); t = Type(rt); EXPECT_TRUE(t.subtypeOf(Type::Arr)); EXPECT_FALSE(t.subtypeOf(Type::Str)); rt = HPHP::JIT::RuntimeType(true); t = Type(rt); EXPECT_TRUE(t.subtypeOf(Type::Bool)); EXPECT_FALSE(t.subtypeOf(Type::Obj)); rt = HPHP::JIT::RuntimeType((int64_t) 1); t = Type(rt); EXPECT_TRUE(t.subtypeOf(Type::Int)); EXPECT_FALSE(t.subtypeOf(Type::Dbl)); rt = HPHP::JIT::RuntimeType(DataType::KindOfObject, DataType::KindOfInvalid); rt = rt.setKnownClass(SystemLib::s_TraversableClass); t = Type(rt); EXPECT_TRUE(t.subtypeOf(Type::Obj)); EXPECT_FALSE(Type::Obj.subtypeOf(t)); EXPECT_FALSE(Type::Int.subtypeOf(t)); HPHP::JIT::RuntimeType rt1 = HPHP::JIT::RuntimeType(DataType::KindOfObject, DataType::KindOfInvalid); rt1 = rt1.setKnownClass(SystemLib::s_IteratorClass); Type t1 = Type(rt1); EXPECT_TRUE(t1.subtypeOf(Type::Obj)); EXPECT_TRUE(t1.subtypeOf(t)); EXPECT_FALSE(Type::Obj.subtypeOf(t1)); EXPECT_FALSE(t.subtypeOf(t1)); EXPECT_FALSE(t.subtypeOf(Type::Str)); EXPECT_FALSE(Type::Int.subtypeOf(t)); }
ArrayData* ArrayData::GetScalarArray(ArrayData* arr, const ScalarArrayKey& key) { if (arr->empty() && !arr->isDict()) return staticEmptyArray(); assert(key == GetScalarArrayKey(arr)); ArrayDataMap::accessor acc; if (s_arrayDataMap.insert(acc, key)) { ArrayData* ad; if (arr->isVectorData() && !arr->isPacked() && !arr->isDict()) { ad = PackedArray::ConvertStatic(arr); } else { ad = arr->copyStatic(); } assert(ad->isStatic()); ad->onSetEvalScalar(); acc->second = ad; } return acc->second; }
GlobalNameValueTableWrapper::GlobalNameValueTableWrapper( NameValueTable* tab) : NameValueTableWrapper(tab) { Variant arr(staticEmptyArray()); #define X(s,v) tab->set(makeStaticString(#s), v.asTypedValue()); X(argc, init_null_variant); X(argv, init_null_variant); X(_SERVER, arr); X(_GET, arr); X(_POST, arr); X(_COOKIE, arr); X(_FILES, arr); X(_ENV, arr); X(_REQUEST, arr); X(HTTP_RAW_POST_DATA, init_null_variant); X(http_response_header, init_null_variant); #undef X g_variables = this; }
GlobalsArray::GlobalsArray(NameValueTable* tab) : ArrayData(kGlobalsKind) , m_tab(tab) { Variant arr(staticEmptyArray()); #define X(s,v) tab->set(makeStaticString(#s), v.asTypedValue()); X(argc, init_null_variant); X(argv, init_null_variant); X(_SERVER, arr); X(_GET, arr); X(_POST, arr); X(_COOKIE, arr); X(_FILES, arr); X(_ENV, arr); X(_REQUEST, arr); X(_SESSION, arr); X(HTTP_RAW_POST_DATA, init_null_variant); #undef X g_variables.set(this); assertx(hasExactlyOneRef()); }
/* * Attempts to begin inlining, and returns whether or not it successed. * * When doing gen-time inlining, we set up a series of IR instructions * that looks like this: * * fp0 = DefFP * sp = DefSP<offset> * * // ... normal stuff happens ... * * // FPI region: * SpillFrame sp, ... * // ... probably some StStks due to argument expressions * fp2 = DefInlineFP<func,retBC,retSP,off> sp * * // ... callee body ... * * InlineReturn fp2 * * In DCE we attempt to remove the InlineReturn and DefInlineFP instructions if * they aren't needed. */ bool beginInlining(IRGS& env, unsigned numParams, const Func* target, Offset returnBcOffset) { auto const& fpiStack = env.irb->fpiStack(); assertx(!fpiStack.empty() && "Inlining does not support calls with the FPush* in a different Tracelet"); assertx(returnBcOffset >= 0 && "returnBcOffset before beginning of caller"); assertx(curFunc(env)->base() + returnBcOffset < curFunc(env)->past() && "returnBcOffset past end of caller"); FTRACE(1, "[[[ begin inlining: {}\n", target->fullName()->data()); SSATmp** params = (SSATmp**)alloca(sizeof(SSATmp*) * numParams); for (unsigned i = 0; i < numParams; ++i) { params[numParams - i - 1] = popF(env); } auto const prevSP = fpiStack.front().returnSP; auto const prevSPOff = fpiStack.front().returnSPOff; spillStack(env); auto const calleeSP = sp(env); always_assert_flog( prevSP == calleeSP, "FPI stack pointer and callee stack pointer didn't match in beginInlining" ); auto const& info = fpiStack.front(); always_assert(!isFPushCuf(info.fpushOpc) && !info.interp); auto ctx = [&] { if (info.ctx || isFPushFunc(info.fpushOpc)) { return info.ctx; } constexpr int32_t adjust = offsetof(ActRec, m_r) - offsetof(ActRec, m_this); IRSPOffset ctxOff{invSPOff(env) - info.returnSPOff - adjust}; return gen(env, LdStk, TCtx, IRSPOffsetData{ctxOff}, sp(env)); }(); DefInlineFPData data; data.target = target; data.retBCOff = returnBcOffset; data.fromFPushCtor = isFPushCtor(info.fpushOpc); data.ctx = ctx; data.retSPOff = prevSPOff; data.spOffset = offsetFromIRSP(env, BCSPOffset{0}); // Push state and update the marker before emitting any instructions so // they're all given markers in the callee. auto const key = SrcKey { target, target->getEntryForNumArgs(numParams), false }; env.bcStateStack.emplace_back(key); env.inlineLevel++; updateMarker(env); auto const calleeFP = gen(env, DefInlineFP, data, calleeSP, fp(env)); for (unsigned i = 0; i < numParams; ++i) { stLocRaw(env, i, calleeFP, params[i]); } const bool hasVariadicArg = target->hasVariadicCaptureParam(); for (unsigned i = numParams; i < target->numLocals() - hasVariadicArg; ++i) { /* * Here we need to be generating hopefully-dead stores to initialize * non-parameter locals to KindOfUninit in case we have to leave the trace. */ stLocRaw(env, i, calleeFP, cns(env, TUninit)); } if (hasVariadicArg) { auto argNum = target->numLocals() - 1; always_assert(numParams <= argNum); stLocRaw(env, argNum, calleeFP, cns(env, staticEmptyArray())); } return true; }
NEVER_INLINE ArrayData* EmptyArray::Copy(const ArrayData*) { return staticEmptyArray(); }
ArrayData *ArrayData::Create() { return staticEmptyArray(); }
/* * Attempts to begin inlining, and returns whether or not it successed. * * When doing gen-time inlining, we set up a series of IR instructions * that looks like this: * * fp0 = DefFP * sp = DefSP<offset> * * // ... normal stuff happens ... * * // FPI region: * SpillFrame sp, ... * // ... probably some StStks due to argument expressions * fp2 = DefInlineFP<func,retBC,retSP,off> sp * * // ... callee body ... * * InlineReturn fp2 * * In DCE we attempt to remove the InlineReturn and DefInlineFP instructions if * they aren't needed. */ bool beginInlining(IRGS& env, unsigned numParams, const Func* target, Offset returnBcOffset) { assertx(!env.fpiStack.empty() && "Inlining does not support calls with the FPush* in a different Tracelet"); assertx(returnBcOffset >= 0 && "returnBcOffset before beginning of caller"); assertx(curFunc(env)->base() + returnBcOffset < curFunc(env)->past() && "returnBcOffset past end of caller"); FTRACE(1, "[[[ begin inlining: {}\n", target->fullName()->data()); SSATmp* params[numParams]; for (unsigned i = 0; i < numParams; ++i) { params[numParams - i - 1] = popF(env); } auto const prevSP = env.fpiStack.top().returnSP; auto const prevSPOff = env.fpiStack.top().returnSPOff; spillStack(env); auto const calleeSP = sp(env); always_assert_flog( prevSP == calleeSP, "FPI stack pointer and callee stack pointer didn't match in beginInlining" ); // This can only happen if the code is unreachable, in which case // the FPush* can punt if it gets a TBottom. if (env.fpiStack.top().spillFrame == nullptr) return false; auto const sframe = env.fpiStack.top().spillFrame; DefInlineFPData data; data.target = target; data.retBCOff = returnBcOffset; data.fromFPushCtor = sframe->extra<ActRecInfo>()->fromFPushCtor; data.ctx = sframe->src(2); data.retSPOff = prevSPOff; data.spOffset = offsetFromIRSP(env, BCSPOffset{0}); // Push state and update the marker before emitting any instructions so // they're all given markers in the callee. auto const key = SrcKey { target, target->getEntryForNumArgs(numParams), false }; env.bcStateStack.emplace_back(key); env.inlineLevel++; updateMarker(env); auto const calleeFP = gen(env, DefInlineFP, data, calleeSP, fp(env)); for (unsigned i = 0; i < numParams; ++i) { stLocRaw(env, i, calleeFP, params[i]); } const bool hasVariadicArg = target->hasVariadicCaptureParam(); for (unsigned i = numParams; i < target->numLocals() - hasVariadicArg; ++i) { /* * Here we need to be generating hopefully-dead stores to initialize * non-parameter locals to KindOfUninit in case we have to leave the trace. */ stLocRaw(env, i, calleeFP, cns(env, TUninit)); } if (hasVariadicArg) { auto argNum = target->numLocals() - 1; always_assert(numParams <= argNum); stLocRaw(env, argNum, calleeFP, cns(env, staticEmptyArray())); } env.fpiActiveStack.push(std::make_pair(env.fpiStack.top().returnSP, env.fpiStack.top().returnSPOff)); env.fpiStack.pop(); return true; }
ArrayData* ArrayData::GetScalarArray(ArrayData* arr) { if (arr->empty() && !arr->isDict()) return staticEmptyArray(); auto key = GetScalarArrayKey(arr); return GetScalarArray(arr, key); }