hphp/runtime/vm/jit/translator-runtime.cpp (560 lines of code) (raw):

/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/vm/jit/translator-runtime.h" #include "hphp/runtime/base/array-common.h" #include "hphp/runtime/base/array-init.h" #include "hphp/runtime/base/autoload-handler.h" #include "hphp/runtime/base/builtin-functions.h" #include "hphp/runtime/base/collections.h" #include "hphp/runtime/base/datatype.h" #include "hphp/runtime/base/execution-context.h" #include "hphp/runtime/base/object-data.h" #include "hphp/runtime/base/stats.h" #include "hphp/runtime/base/string-data.h" #include "hphp/runtime/base/tv-mutate.h" #include "hphp/runtime/base/tv-refcount.h" #include "hphp/runtime/base/tv-type.h" #include "hphp/runtime/base/tv-variant.h" #include "hphp/runtime/base/typed-value.h" #include "hphp/runtime/base/type-structure-helpers-defs.h" #include "hphp/runtime/base/type-structure-helpers.h" #include "hphp/runtime/base/vanilla-dict.h" #include "hphp/runtime/base/vanilla-keyset.h" #include "hphp/runtime/base/vanilla-vec.h" #include "hphp/runtime/base/zend-functions.h" #include "hphp/runtime/ext/collections/ext_collections-map.h" #include "hphp/runtime/ext/collections/ext_collections-pair.h" #include "hphp/runtime/ext/collections/ext_collections-set.h" #include "hphp/runtime/ext/collections/ext_collections-vector.h" #include "hphp/runtime/ext/hh/ext_hh.h" #include "hphp/runtime/ext/std/ext_std_function.h" #include "hphp/runtime/vm/act-rec.h" #include "hphp/runtime/vm/class.h" #include "hphp/runtime/vm/class-meth-data-ref.h" #include "hphp/runtime/vm/func.h" #include "hphp/runtime/vm/member-operations.h" #include "hphp/runtime/vm/method-lookup.h" #include "hphp/runtime/vm/reified-generics.h" #include "hphp/runtime/vm/resumable.h" #include "hphp/runtime/vm/type-constraint.h" #include "hphp/runtime/vm/unit.h" #include "hphp/runtime/vm/unit-util.h" #include "hphp/runtime/vm/unwind.h" #include "hphp/runtime/vm/vm-regs.h" #include "hphp/runtime/vm/jit/minstr-helpers.h" #include "hphp/runtime/vm/jit/target-cache.h" #include "hphp/runtime/vm/jit/target-profile.h" #include "hphp/runtime/vm/jit/translator-inline.h" #include "hphp/runtime/vm/jit/unwind-itanium.h" #include "hphp/system/systemlib.h" #include "hphp/util/portability.h" #include "hphp/util/string-vsnprintf.h" namespace HPHP { TRACE_SET_MOD(runtime); ////////////////////////////////////////////////////////////////////// namespace jit { ////////////////////////////////////////////////////////////////////// void setNewElem(tv_lval base, TypedValue val) { HPHP::SetNewElem<false>(base, &val); } void setNewElemVec(tv_lval base, TypedValue val) { HPHP::SetNewElemVec(base, &val); } void setNewElemDict(tv_lval base, TypedValue val) { HPHP::SetNewElemDict(base, &val); } ////////////////////////////////////////////////////////////////////// ArrayData* addNewElemVec(ArrayData* vec, TypedValue v) { assertx(vec->isVanillaVec()); tvIncRefGen(v); return VanillaVec::AppendMove(vec, v); } ArrayData* addNewElemKeyset(ArrayData* keyset, TypedValue v) { assertx(keyset->isVanillaKeyset()); tvIncRefGen(v); return VanillaKeyset::AppendMove(keyset, v); } ////////////////////////////////////////////////////////////////////// ArrayData* convArrLikeToVecHelper(ArrayData* adIn) { auto a = adIn->toVec(adIn->cowCheck()); assertx(a->isVecType()); if (a != adIn) decRefArr(adIn); return a; } ArrayData* convObjToVecHelper(ObjectData* obj) { auto a = castObjToVec(obj); assertx(a->isVecType()); decRefObj(obj); return a; } ArrayData* convArrLikeToDictHelper(ArrayData* adIn) { auto a = adIn->toDict(adIn->cowCheck()); assertx(a->isDictType()); if (a != adIn) decRefArr(adIn); return a; } ArrayData* convObjToDictHelper(ObjectData* obj) { auto a = castObjToDict(obj); assertx(a->isDictType()); decRefObj(obj); return a; } ArrayData* convArrLikeToKeysetHelper(ArrayData* adIn) { auto a = adIn->toKeyset(adIn->cowCheck()); assertx(a->isKeysetType()); if (a != adIn) decRefArr(adIn); return a; } ArrayData* convObjToKeysetHelper(ObjectData* obj) { auto a = castObjToKeyset(obj); assertx(a->isKeysetType()); decRefObj(obj); return a; } double convObjToDblHelper(const ObjectData* o) { return o->toDouble(); } double convStrToDblHelper(const StringData* s) { return s->toDouble(); } double convResToDblHelper(const ResourceHdr* r) { return r->getId(); } double convTVToDblHelper(TypedValue tv) { return tvCastToDouble(tv); } StringData* convDblToStrHelper(double d) { return buildStringData(d); } StringData* convIntToStrHelper(int64_t i) { return buildStringData(i); } StringData* convObjToStrHelper(ObjectData* o) { // toString() returns a counted String; detach() it to move ownership // of the count to the caller return o->invokeToString().detach(); } void throwUndefPropException(ObjectData* base, const StringData* name) { base->throwUndefPropException(name); } void throwUndefVariable(StringData* nm) { SCOPE_EXIT { decRefStr(nm); }; SystemLib::throwUndefinedVariableExceptionObject( folly::sformat("Undefined variable: {}", nm->data())); } void raise_error_sd(const StringData *msg) { raise_error("%s", msg->data()); } ALWAYS_INLINE static bool VerifyTypeSlowImpl(const Class* cls, const Class* constraint, const TypeConstraint* expected) { // This helper should only be called for the Object and This cases assertx(expected->isObject() || expected->isThis()); // For the This case, we must always have a resolved class for the constraint assertx(IMPLIES(expected->isThis(), constraint != nullptr)); // If we have a resolved class for the constraint, all we have to do is // check if the value's class is compatible with it if (LIKELY(constraint != nullptr)) { if (expected->isThis()) { return cls == constraint; } return cls->classof(constraint); } // The This case should never reach here because it was handled above assertx(expected->isObject()); // Handle the case where the constraint is a type alias return expected->checkTypeAliasObj(cls); } void VerifyParamTypeSlow(ObjectData* obj, const Class* constraint, const Func* func, int32_t paramId, const TypeConstraint* expected) { if (!VerifyTypeSlowImpl(obj->getVMClass(), constraint, expected)) { assertx(expected->isObject()); VerifyParamTypeFail( make_tv<KindOfObject>(obj), nullptr, func, paramId, expected); } } void VerifyParamTypeCallable(TypedValue value, const Func* func, int32_t paramId) { if (UNLIKELY(!is_callable(tvAsCVarRef(&value)))) { auto const& tc = func->params()[paramId].typeConstraint; assertx(tc.isCallable()); VerifyParamTypeFail(value, nullptr, func, paramId, &tc); } } TypedValue VerifyParamTypeFail(TypedValue value, const Class* ctx, const Func* func, int32_t paramId, const TypeConstraint* tc) { assertx(!tc->check(&value, ctx)); tc->verifyParamFail(&value, ctx, func, paramId); return value; } void VerifyRetTypeSlow(ObjectData* obj, const Class* constraint, const Func* func, int32_t retId, const TypeConstraint* expected) { if (!VerifyTypeSlowImpl(obj->getVMClass(), constraint, expected)) { assertx(expected->isObject()); VerifyRetTypeFail( make_tv<KindOfObject>(obj), nullptr, func, retId, expected); } } void VerifyRetTypeCallable(TypedValue value, const Func* func, int32_t retId) { if (UNLIKELY(!is_callable(tvAsCVarRef(&value)))) { auto const& tc = retId == TypeConstraint::ReturnId ? func->returnTypeConstraint() : func->params()[retId].typeConstraint; assertx(tc.isCallable()); VerifyRetTypeFail(value, nullptr, func, retId, &tc); } } TypedValue VerifyRetTypeFail(TypedValue value, const Class* ctx, const Func* func, int32_t retId, const TypeConstraint* tc) { if (retId == TypeConstraint::ReturnId) { assertx(!tc->check(&value, ctx)); tc->verifyReturnFail(&value, ctx, func); } else { assertx(!tc->check(&value, ctx)); tc->verifyOutParamFail(&value, ctx, func, retId); } return value; } void VerifyReifiedLocalTypeImpl(TypedValue value, ArrayData* ts, const Class* ctx, const Func* func, int32_t paramId) { auto const couldBeReified = tcCouldBeReified(func, paramId); bool warn = false; if (verifyReifiedLocalType(&value, ts, ctx, func, couldBeReified, warn)) { return; } raise_reified_typehint_error( folly::sformat( "Argument {} passed to {}() must be an instance of {}, {} given", paramId + 1, func->fullName()->data(), TypeStructure::toString(ArrNR(ts), TypeStructure::TSDisplayType::TSDisplayTypeUser).c_str(), describe_actual_type(&value) ), warn ); } void VerifyReifiedReturnTypeImpl(TypedValue value, ArrayData* ts, const Class* ctx, const Func* func) { auto const couldBeReified = tcCouldBeReified(func, TypeConstraint::ReturnId); bool warn = false; if (verifyReifiedLocalType(&value, ts, ctx, func, couldBeReified, warn)) { return; } raise_reified_typehint_error( folly::sformat( "Value returned from function {}() must be of type {}, {} given", func->fullName()->data(), TypeStructure::toString(ArrNR(ts), TypeStructure::TSDisplayType::TSDisplayTypeUser).c_str(), describe_actual_type(&value) ), warn ); } namespace { ALWAYS_INLINE TypedValue getDefaultIfMissing(TypedValue tv, TypedValue def) { return tv.is_init() ? tv : def; } ALWAYS_INLINE TypedValue doScan(const VanillaDict* arr, StringData* key, TypedValue def) { assertx(key->isStatic()); assertx(arr->keyTypes().mustBeStaticStrs()); auto used = arr->iterLimit(); for (auto elm = arr->data(); used; used--, elm++) { assertx(elm->hasStrKey()); assertx(elm->strKey()->isStatic()); if (key == elm->strKey()) return *elm->datatv(); } return def; } } // This helper may also be used when we know we have a VanillaDict in the JIT. TypedValue dictIdxI(ArrayData* a, int64_t key, TypedValue def) { assertx(a->isVanillaDict()); return getDefaultIfMissing(VanillaDict::NvGetInt(a, key), def); } // This helper is also used for VanillaDicts. NEVER_INLINE TypedValue dictIdxS(ArrayData* a, StringData* key, TypedValue def) { assertx(a->isVanillaDict()); return getDefaultIfMissing(VanillaDict::NvGetStr(a, key), def); } // This helper is also used for VanillaDicts. NEVER_INLINE TypedValue dictIdxScan(ArrayData* a, StringData* key, TypedValue def) { assertx(a->isVanillaDict()); auto const ad = VanillaDict::as(a); if (!ad->keyTypes().mustBeStaticStrs()) return dictIdxS(a, key, def); return doScan(ad, key, def); } TypedValue keysetIdxI(ArrayData* a, int64_t key, TypedValue def) { assertx(a->isVanillaKeyset()); return getDefaultIfMissing(VanillaKeyset::NvGetInt(a, key), def); } TypedValue keysetIdxS(ArrayData* a, StringData* key, TypedValue def) { assertx(a->isVanillaKeyset()); return getDefaultIfMissing(VanillaKeyset::NvGetStr(a, key), def); } template <bool isFirst> TypedValue vecFirstLast(ArrayData* a) { assertx(a->isVanillaVec()); auto const size = a->size(); if (UNLIKELY(size == 0)) return make_tv<KindOfNull>(); return VanillaVec::NvGetInt(a, isFirst ? 0 : size - 1); } template TypedValue vecFirstLast<true>(ArrayData*); template TypedValue vecFirstLast<false>(ArrayData*); template <bool isFirst, bool isKey> TypedValue arrFirstLast(ArrayData* a) { if (UNLIKELY(a->empty())) { return make_tv<KindOfNull>(); } auto pos = isFirst ? a->iter_begin() : a->iter_last(); return isKey ? a->nvGetKey(pos) : a->nvGetVal(pos); } template TypedValue arrFirstLast<true, false>(ArrayData*); template TypedValue arrFirstLast<false, false>(ArrayData*); template TypedValue arrFirstLast<true, true>(ArrayData*); template TypedValue arrFirstLast<false, true>(ArrayData*); TypedValue* getSPropOrNull(ReadonlyOp op, const Class* cls, const StringData* name, Class* ctx, bool ignoreLateInit, bool writeMode) { auto const lookup = ignoreLateInit ? cls->getSPropIgnoreLateInit(ctx, name) : cls->getSProp(ctx, name); if (writeMode && UNLIKELY(lookup.constant)) { throw_cannot_modify_static_const_prop(cls->name()->data(), name->data()); } checkReadonly(lookup.val, cls, name, lookup.readonly, op, writeMode); if (UNLIKELY(!lookup.val || !lookup.accessible)) return nullptr; return lookup.val; } TypedValue* getSPropOrRaise(ReadonlyOp op, const Class* cls, const StringData* name, Class* ctx, bool ignoreLateInit, bool writeMode) { auto sprop = getSPropOrNull(op, cls, name, ctx, ignoreLateInit, writeMode); if (UNLIKELY(!sprop)) { raise_error("Invalid static property access: %s::%s", cls->name()->data(), name->data()); } return sprop; } tv_lval ldGblAddrDefHelper(StringData* name) { return g_context->m_globalNVTable->lookupAdd(name); } ////////////////////////////////////////////////////////////////////// void checkFrame(ActRec* fp, TypedValue* sp, bool fullCheck) { const Func* func = fp->func(); func->validate(); if (func->cls()) { assertx(!func->cls()->isZombie()); } int numLocals = func->numLocals(); assertx(sp <= (TypedValue*)fp - func->numSlotsInFrame() || isResumed(fp)); if (!fullCheck) return; int numParams = func->numParams(); for (int i = 0; i < numLocals; i++) { if (i >= numParams && isResumed(fp) && i < func->numNamedLocals() && func->localVarName(i)) { continue; } assertx(tvIsPlausible(*frame_local(fp, i))); } visitStackElems( fp, sp, [](const TypedValue* tv) { assertx(tvIsPlausible(*tv)); } ); } const Func* loadClassCtor(Class* cls, Class* ctx) { const Func* f = cls->getCtor(); if (UNLIKELY(!(f->attrs() & AttrPublic))) { UNUSED auto func = lookupMethodCtx(cls, nullptr, ctx, CallType::CtorMethod, MethodLookupErrorOptions::RaiseOnNotFound); assertx(func == f); } return f; } const Func* lookupClsMethodHelper(const Class* cls, const StringData* methName, ObjectData* obj, const Class* ctx) { const Func* f; auto const res = lookupClsMethod(f, cls, methName, obj, ctx, MethodLookupErrorOptions::RaiseOnNotFound); if (res == LookupResult::MethodFoundWithThis) { // Handled by interpreter. return nullptr; } assertx(res == LookupResult::MethodFoundNoThis); if (!f->isStaticInPrologue()) { throw_missing_this(f); } return f; } ////////////////////////////////////////////////////////////////////// TypedValue lookupClsCns(const Class* cls, const StringData* cnsName) { return cls->clsCnsGet(cnsName); } int lookupClsCtxCns(const Class* cls, const StringData* cnsName) { return cls->clsCtxCnsGet(cnsName, true)->value(); } bool methodExistsHelper(Class* cls, StringData* meth) { assertx(isNormalClass(cls) && !isAbstract(cls)); return cls->lookupMethod(meth) != nullptr; } ArrayData* resolveTypeStructHelper( uint32_t n, const TypedValue* values, const Class* declaringCls, const Class* calledCls, bool suppress, bool isOrAsOp ) { assertx(n != 0); auto const v = *values; isValidTSType(v, true); auto const ts = v.m_data.parr; req::vector<Array> tsList; for (int i = 0; i < n - 1; ++i) { auto const a = values[n - i - 1]; isValidTSType(a, true); tsList.emplace_back(Array::attach(a.m_data.parr)); } auto resolved = [&] { if (isOrAsOp) { return resolveAndVerifyTypeStructure<true>( ArrNR(ts), declaringCls, calledCls, tsList, suppress); } return resolveAndVerifyTypeStructure<false>( ArrNR(ts), declaringCls, calledCls, tsList, suppress); }(); return resolved.detach(); } bool isTypeStructHelper(ArrayData* a, TypedValue c, rds::Handle h) { auto const cls = TSClassCache::write(h, a); if (!cls) return checkTypeStructureMatchesTV(ArrNR(a), c); if (!tvIsObject(c)) return false; return c.m_data.pobj->getVMClass()->classofNonIFace(cls); } void profileIsTypeStructHelper(ArrayData* a, IsTypeStructProfile* prof) { prof->update(a); } void throwAsTypeStructExceptionHelper(ArrayData* a, TypedValue c) { std::string givenType, expectedType, errorKey; auto const ts = ArrNR(a); if (!checkTypeStructureMatchesTV(ts, c, givenType, expectedType, errorKey)) { throwTypeStructureDoesNotMatchTVException( givenType, expectedType, errorKey); } always_assert(false && "Invalid bytecode sequence: Instruction must throw"); } ArrayData* errorOnIsAsExpressionInvalidTypesHelper(ArrayData* a) { errorOnIsAsExpressionInvalidTypes(ArrNR(a), false); return a; } ArrayData* recordReifiedGenericsAndGetTSList(ArrayData* tsList) { auto const mangledName = makeStaticString(mangleReifiedGenericsName(tsList)); auto result = addToReifiedGenericsTable(mangledName, tsList); return result; } ArrayData* loadClsTypeCnsHelper( const Class* cls, const StringData* name, bool no_throw_on_undefined ) { auto const getFake = [] { auto array = make_dict_array( s_kind, Variant(static_cast<uint8_t>(TypeStructure::Kind::T_class)), s_classname, Variant(s_type_structure_non_existant_class) ); array.setEvalScalar(); return array.get(); }; TypedValue typeCns; if (no_throw_on_undefined) { try { typeCns = cls->clsCnsGet(name, ConstModifiers::Kind::Type); } catch (Exception& e) { return getFake(); } catch (Object& e) { return getFake(); } } else { typeCns = cls->clsCnsGet(name, ConstModifiers::Kind::Type); } if (typeCns.m_type == KindOfUninit) { if (no_throw_on_undefined) { return getFake(); } else { if (cls->hasTypeConstant(name, true)) { raise_error("Type constant %s::%s is abstract", cls->name()->data(), name->data()); } else { raise_error("Non-existent type constant %s::%s", cls->name()->data(), name->data()); } } } assertx(isArrayLikeType(typeCns.m_type)); assertx(typeCns.m_data.parr->isDictType()); assertx(typeCns.m_data.parr->isStatic()); return typeCns.m_data.parr; } StringData* loadClsTypeCnsClsNameHelper(const Class* cls, const StringData* name) { auto const ts = loadClsTypeCnsHelper(cls, name, false); auto const classname = ts->get(s_classname.get(), true); assertx(isStringType(type(classname))); assertx(val(classname).pstr->isStatic()); return val(classname).pstr; } void raiseCoeffectsCallViolationHelper(const Func* callee, uint64_t providedCoeffects, uint64_t requiredCoeffects) { raiseCoeffectsCallViolation(callee, RuntimeCoeffects::fromValue(providedCoeffects), RuntimeCoeffects::fromValue(requiredCoeffects)); } 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(tv_lval base, TypedValue key, TypedValue val, SetOpOp op) { auto const result = HPHP::SetOpElem(op, base, key, &val); tvIncRefGen(result); return result; } StringData* stringGetI(StringData* base, uint64_t x) { if (LIKELY(x < base->size())) { return base->getChar(x); } raise_notice("Uninitialized string offset: %" PRId64, static_cast<int64_t>(x)); return staticEmptyString(); } uint64_t pairIsset(c_Pair* pair, int64_t index) { auto result = pair->get(index); return result ? !tvIsNull(result) : false; } uint64_t vectorIsset(c_Vector* vec, int64_t index) { auto result = vec->get(index); return result ? !tvIsNull(*result) : false; } TypedValue incDecElem(tv_lval base, TypedValue key, IncDecOp op) { auto const result = HPHP::IncDecElem(op, base, key); return result; } tv_lval elemVecIU(tv_lval base, int64_t key) { assertx(tvIsVec(base)); return ElemUVec<KeyType::Int>(base, key); } } ////////////////////////////////////////////////////////////////////// }}