Cell cellArith(Op o, Cell c1, Cell c2) { again: if (c1.m_type == KindOfInt64) { for (;;) { if (c2.m_type == KindOfInt64) return o(c1.m_data.num, c2.m_data.num); if (c2.m_type == KindOfDouble) return o(c1.m_data.num, c2.m_data.dbl); cellCopy(numericConvHelper(c2), c2); assert(c2.m_type == KindOfInt64 || c2.m_type == KindOfDouble); } } if (c1.m_type == KindOfDouble) { for (;;) { if (c2.m_type == KindOfDouble) return o(c1.m_data.dbl, c2.m_data.dbl); if (c2.m_type == KindOfInt64) return o(c1.m_data.dbl, c2.m_data.num); cellCopy(numericConvHelper(c2), c2); assert(c2.m_type == KindOfInt64 || c2.m_type == KindOfDouble); } } if (c1.m_type == KindOfArray && c2.m_type == KindOfArray) { return make_tv<KindOfArray>(o(c1.m_data.parr, c2.m_data.parr)); } cellCopy(numericConvHelper(c1), c1); assert(c1.m_type == KindOfInt64 || c1.m_type == KindOfDouble); goto again; }
void c_AsyncFunctionWaitHandle::ret(Cell& result) { assert(isRunning()); auto parentChain = getParentChain(); setState(STATE_SUCCEEDED); cellCopy(result, m_resultOrException); parentChain.unblock(); }
void c_AsyncFunctionWaitHandle::ret(Cell& result) { auto const parentChain = getFirstParent(); setState(STATE_SUCCEEDED); cellCopy(result, m_resultOrException); UnblockChain(parentChain); decRefObj(this); }
/** * Create succeeded StaticWaitHandle object. * * - consumes reference of the given cell * - produces reference for the returned StaticWaitHandle object * * Both the JIT and bytecode.cpp assume this function gives the nothrow * guarantee. */ c_StaticWaitHandle* c_StaticWaitHandle::CreateSucceeded(const Cell result) { auto waitHandle = newobj<c_StaticWaitHandle>(); waitHandle->setState(STATE_SUCCEEDED); waitHandle->incRefCount(); cellCopy(result, waitHandle->m_resultOrException); return waitHandle; }
TEST(Cell, copyConstructor) { Cell cell(1,2); Cell cellCopy(cell); ASSERT_EQ(cell.getCol(), 1); ASSERT_EQ(cell.getRow(), 2); }
void c_AsyncGeneratorWaitHandle::ret(Cell& result) { auto parentChain = getParentChain(); setState(STATE_SUCCEEDED); cellCopy(result, m_resultOrException); parentChain.unblock(); decRefObj(m_generator); decRefObj(this); }
/** * Create failed StaticWaitHandle object. * * - consumes reference of the given Exception object * - produces reference for the returned StaticWaitHandle object */ c_StaticWaitHandle* c_StaticWaitHandle::CreateFailed(ObjectData* exception) { assert(exception); assert(exception->instanceof(SystemLib::s_ExceptionClass)); auto waitHandle = makeSmartPtr<c_StaticWaitHandle>(); waitHandle->setState(STATE_FAILED); cellCopy(make_tv<KindOfObject>(exception), waitHandle->m_resultOrException); return waitHandle.detach(); }
void c_AsyncGeneratorWaitHandle::fail(ObjectData* exception) { AsioSession* session = AsioSession::Get(); if (UNLIKELY(session->hasOnResumableFailCallback())) { session->onResumableFail(this, exception); } auto parentChain = getParentChain(); setState(STATE_FAILED); cellCopy(make_tv<KindOfObject>(exception), m_resultOrException); parentChain.unblock(); decRefObj(m_generator); decRefObj(this); }
/** * Mark the wait handle as failed due to PHP exception. * * - consumes reference of the given Exception object */ void c_AsyncFunctionWaitHandle::fail(ObjectData* exception) { assert(isRunning()); assert(exception); assert(exception->instanceof(SystemLib::s_ExceptionClass)); AsioSession* session = AsioSession::Get(); if (UNLIKELY(session->hasOnResumableFailCallback())) { session->onResumableFail(this, exception); } auto const parentChain = getFirstParent(); setState(STATE_FAILED); cellCopy(make_tv<KindOfObject>(exception), m_resultOrException); UnblockChain(parentChain); decRefObj(this); }
RefData* closureStaticLocInit(StringData* name, ActRec* fp, TypedValue val) { auto const func = fp->m_func; assert(func->isClosureBody() || func->isGeneratorFromClosure()); auto const closureLoc = LIKELY(func->isClosureBody()) ? frame_local(fp, func->numParams()) : frame_local(fp, frame_continuation(fp)->m_origFunc->numParams()); bool inited; auto const refData = lookupStaticFromClosure( closureLoc->m_data.pobj, name, inited); if (!inited) { cellCopy(val, *refData->tv()); } refData->incRefCount(); return refData; }
void c_ExternalThreadEventWaitHandle::process() { assert(getState() == STATE_WAITING); if (isInContext()) { unregisterFromContext(); } // clean up once event is processed auto exit_guard = folly::makeGuard([&] { destroyEvent(); }); Cell result; try { m_event->unserialize(result); } catch (const Object& exception) { assert(exception->instanceof(SystemLib::s_ExceptionClass)); auto const parentChain = getFirstParent(); setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); c_BlockableWaitHandle::UnblockChain(parentChain); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventFailCallback())) { session->onExternalThreadEventFail(this, exception); } return; } catch (...) { auto const parentChain = getFirstParent(); setState(STATE_FAILED); tvWriteObject(AsioSession::Get()->getAbruptInterruptException(), &m_resultOrException); c_BlockableWaitHandle::UnblockChain(parentChain); throw; } assert(cellIsPlausible(result)); auto const parentChain = getFirstParent(); setState(STATE_SUCCEEDED); cellCopy(result, m_resultOrException); c_BlockableWaitHandle::UnblockChain(parentChain); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventSuccessCallback())) { session->onExternalThreadEventSuccess(this, tvAsCVarRef(&result)); } }
Cell cellArithO(Op o, Check ck, Over ov, Cell c1, Cell c2) { if (c1.m_type == KindOfArray && c2.m_type == KindOfArray) { return cellArith(o, c1, c2); } auto ensure_num = [](Cell& c) { if (c.m_type != KindOfInt64 && c.m_type != KindOfDouble) { cellCopy(numericConvHelper(c), c); } }; ensure_num(c1); ensure_num(c2); auto both_ints = (c1.m_type == KindOfInt64 && c2.m_type == KindOfInt64); int64_t a = c1.m_data.num; int64_t b = c2.m_data.num; return (both_ints && ck(a,b)) ? ov(a,b) : cellArith(o, c1, c2); }
/** * Mark the wait handle as failed due to PHP exception. * * - consumes reference of the given Exception object */ void c_AsyncFunctionWaitHandle::fail(ObjectData* exception) { assert(isRunning()); assert(exception); assert(exception->instanceof(SystemLib::s_ThrowableClass)); AsioSession* session = AsioSession::Get(); if (UNLIKELY(session->hasOnResumableFail())) { try { session->onResumableFail(this, Object{exception}); } catch (...) { // TODO(#4557954) Make unwinder able to deal with new exceptions better. handle_destructor_exception("AsyncFunctionWaitHandle fail callback"); } } auto parentChain = getParentChain(); setState(STATE_FAILED); cellCopy(make_tv<KindOfObject>(exception), m_resultOrException); parentChain.unblock(); }
void c_ExternalThreadEventWaitHandle::process() { assertx(getState() == STATE_WAITING); if (isInContext()) { unregisterFromContext(); } // Store the finish time of the underlying IO operation // So we can pass it in the finish callbacks // clean up once event is processed auto exit_guard = folly::makeGuard([&] { destroyEvent(); }); Cell result; try { try { m_event->unserialize(result); } catch (ExtendedException& exception) { exception.recomputeBacktraceFromWH(this); throw exception; } } catch (const Object& exception) { assertx(exception->instanceof(SystemLib::s_ThrowableClass)); throwable_recompute_backtrace_from_wh(exception.get(), this); auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(exception.get(), &m_resultOrException); parentChain.unblock(); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventFail())) { session->onExternalThreadEventFail( this, exception, std::chrono::duration_cast<std::chrono::nanoseconds>( m_event->getFinishTime().time_since_epoch() ).count() ); } return; } catch (...) { auto parentChain = getParentChain(); setState(STATE_FAILED); tvWriteObject(AsioSession::Get()->getAbruptInterruptException(), &m_resultOrException); parentChain.unblock(); throw; } assertx(cellIsPlausible(result)); auto parentChain = getParentChain(); setState(STATE_SUCCEEDED); cellCopy(result, m_resultOrException); parentChain.unblock(); auto session = AsioSession::Get(); if (UNLIKELY(session->hasOnExternalThreadEventSuccess())) { session->onExternalThreadEventSuccess( this, tvAsCVarRef(&result), std::chrono::duration_cast<std::chrono::nanoseconds>( m_event->getFinishTime().time_since_epoch() ).count() ); } }
/** * Create succeeded StaticWaitHandle object. * * - consumes reference of the given cell * - produces reference for the returned StaticWaitHandle object * * Both the JIT and bytecode.cpp assume this function gives the nothrow * guarantee. */ c_StaticWaitHandle* c_StaticWaitHandle::CreateSucceeded(const Cell result) { auto waitHandle = makeSmartPtr<c_StaticWaitHandle>(); waitHandle->setState(STATE_SUCCEEDED); cellCopy(result, waitHandle->m_resultOrException); return waitHandle.detach(); }
void c_AsyncFunctionWaitHandle::ret(Cell& result) { setState(STATE_SUCCEEDED); cellCopy(result, m_resultOrException); }
/** * Discard the current frame, assuming that a PHP exception given in * phpException argument, or C++ exception (phpException == nullptr) * is being thrown. Returns an exception to propagate, or nulltpr * if the VM execution should be resumed. */ ObjectData* tearDownFrame(ActRec*& fp, Stack& stack, PC& pc, ObjectData* phpException) { auto const func = fp->func(); auto const curOp = peek_op(pc); auto const prevFp = fp->sfp(); auto const soff = fp->m_soff; ITRACE(1, "tearDownFrame: {} ({})\n", func->fullName()->data(), func->unit()->filepath()->data()); ITRACE(1, " fp {} prevFp {}\n", implicit_cast<void*>(fp), implicit_cast<void*>(prevFp)); // When throwing from a constructor, we normally want to avoid running the // destructor on an object that hasn't been fully constructed yet. But if // we're unwinding through the constructor's RetC, the constructor has // logically finished and we're unwinding for some internal reason (timeout // or user profiler, most likely). More importantly, fp->m_this may have // already been destructed and/or overwritten due to sharing space with // fp->m_r. if (curOp != OpRetC && fp->hasThis() && fp->getThis()->getVMClass()->getCtor() == func && fp->getThis()->getVMClass()->getDtor()) { /* * Looks like an FPushCtor call, but it could still have been called * directly. Check the fpi region to be sure. */ Offset prevPc; auto outer = g_context->getPrevVMState(fp, &prevPc); if (outer) { auto fe = outer->func()->findPrecedingFPI(prevPc); if (fe && isFPushCtor(outer->func()->unit()->getOp(fe->m_fpushOff))) { fp->getThis()->setNoDestruct(); } } } auto const decRefLocals = [&] { /* * It is possible that locals have already been decref'd. * * Here's why: * * - If a destructor for any of these things throws a php * exception, it's swallowed at the dtor boundary and we keep * running php. * * - If the destructor for any of these things throws a fatal, * it's swallowed, and we set surprise flags to throw a fatal * from now on. * * - If the second case happened and we have to run another * destructor, its enter hook will throw, but it will be * swallowed again. * * - Finally, the exit hook for the returning function can * throw, but this happens last so everything is destructed. * * - When that happens, exit hook sets localsDecRefd flag. */ if (!fp->localsDecRefd()) { try { // Note that we must convert locals and the $this to // uninit/zero during unwind. This is because a backtrace // from another destructing object during this unwind may try // to read them. frame_free_locals_unwind(fp, func->numLocals(), phpException); } catch (...) {} } }; if (LIKELY(!fp->resumed())) { decRefLocals(); if (UNLIKELY(func->isAsyncFunction()) && phpException && !fp->isFCallAwait()) { // If in an eagerly executed async function, wrap the user exception // into a failed StaticWaitHandle and return it to the caller. auto const waitHandle = c_StaticWaitHandle::CreateFailed(phpException); phpException = nullptr; stack.ndiscard(func->numSlotsInFrame()); stack.ret(); assert(stack.topTV() == &fp->m_r); cellCopy(make_tv<KindOfObject>(waitHandle), fp->m_r); } else { // Free ActRec. stack.ndiscard(func->numSlotsInFrame()); stack.discardAR(); } } else if (func->isAsyncFunction()) { auto const waitHandle = frame_afwh(fp); if (phpException) { // Handle exception thrown by async function. decRefLocals(); waitHandle->fail(phpException); phpException = nullptr; } else if (waitHandle->isRunning()) { // Let the C++ exception propagate. If the current frame represents async // function that is running, mark it as abruptly interrupted. Some opcodes // like Await may change state of the async function just before exit hook // decides to throw C++ exception. decRefLocals(); waitHandle->failCpp(); } } else if (func->isAsyncGenerator()) { auto const gen = frame_async_generator(fp); if (phpException) { // Handle exception thrown by async generator. decRefLocals(); auto eagerResult = gen->fail(phpException); phpException = nullptr; if (eagerResult) { stack.pushObjectNoRc(eagerResult); } } else if (gen->isEagerlyExecuted() || gen->getWaitHandle()->isRunning()) { // Fail the async generator and let the C++ exception propagate. decRefLocals(); gen->failCpp(); } } else if (func->isNonAsyncGenerator()) { // Mark the generator as finished. decRefLocals(); frame_generator(fp)->fail(); } else { not_reached(); } /* * At the final ActRec in this nesting level. */ if (UNLIKELY(!prevFp)) { pc = nullptr; fp = nullptr; return phpException; } assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) || prevFp->resumed()); auto const prevOff = soff + prevFp->func()->base(); pc = prevFp->func()->unit()->at(prevOff); fp = prevFp; return phpException; }