Stop logging in is_varray / is_darray
[hiphop-php.git] / hphp / runtime / vm / jit / irgen-types.cpp
blob93cdd381b387f661c987f118e500271e87fdf715
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/vm/jit/irgen-types.h"
18 #include "hphp/runtime/base/type-structure.h"
19 #include "hphp/runtime/base/type-structure-helpers.h"
20 #include "hphp/runtime/base/type-structure-helpers-defs.h"
22 #include "hphp/runtime/vm/repo-global-data.h"
23 #include "hphp/runtime/vm/runtime.h"
25 #include "hphp/runtime/vm/jit/is-type-struct-profile.h"
26 #include "hphp/runtime/vm/jit/guard-constraint.h"
27 #include "hphp/runtime/vm/jit/target-profile.h"
28 #include "hphp/runtime/vm/jit/type.h"
30 #include "hphp/runtime/vm/jit/ir-opcode.h"
31 #include "hphp/runtime/vm/jit/irgen-exit.h"
32 #include "hphp/runtime/vm/jit/irgen-interpone.h"
33 #include "hphp/runtime/vm/jit/irgen-builtin.h"
35 #include "hphp/runtime/vm/jit/irgen-internal.h"
37 namespace HPHP { namespace jit { namespace irgen {
39 namespace {
41 //////////////////////////////////////////////////////////////////////
43 const StaticString
44 s_Stringish("Stringish"),
45 s_Awaitable("HH\\Awaitable");
47 //////////////////////////////////////////////////////////////////////
50 * Returns a {Cls|Nullptr} suitable for use in instance checks. If knownCls is
51 * not null and is safe to use, that will be returned. Otherwise, className
52 * will be used to look up a class.
54 SSATmp* ldClassSafe(IRGS& env, const StringData* className,
55 const Class* knownCls = nullptr) {
56 if (!knownCls) {
57 knownCls = lookupUniqueClass(env, className);
60 if (knownCls) {
61 return cns(env, knownCls);
64 return cond(
65 env,
66 [&] (Block* taken) {
67 return gen(env, LdClsCachedSafe, taken, cns(env, className));
69 [&] (SSATmp* cls) { // next
70 return cls;
72 [&] { // taken
73 hint(env, Block::Hint::Unlikely);
74 return cns(env, nullptr);
79 SSATmp* ldRecDescSafe(IRGS& env, const StringData* recName) {
80 return cond(
81 env,
82 [&] (Block* taken) {
83 return gen(env, LdRecDescCachedSafe, RecNameData{recName}, taken);
85 [&] (SSATmp* rec) { // next
86 return rec;
88 [&] { // taken
89 return cns(env, nullptr);
95 * Returns a Bool value indicating if src (which must be <= TObj) is an
96 * instance of the class given in className, or nullptr if we don't have an
97 * efficient translation of the required check. checkCls must be the TCls for
98 * className (but it doesn't have to be constant).
100 SSATmp* implInstanceCheck(IRGS& env, SSATmp* src, const StringData* className,
101 SSATmp* checkCls) {
102 assertx(src->isA(TObj));
103 if (s_Awaitable.get()->isame(className)) {
104 return gen(env, IsWaitHandle, src);
106 if (s_Stringish.get()->isame(className)) {
107 return gen(env, HasToString, src);
110 auto knownCls = checkCls->hasConstVal(TCls) ? checkCls->clsVal() : nullptr;
111 assertx(IMPLIES(knownCls, classIsUniqueOrCtxParent(env, knownCls)));
112 assertx(IMPLIES(knownCls, knownCls->name()->isame(className)));
114 auto const srcType = src->type();
117 * If the value is a specialized object type and we don't have to constrain a
118 * guard to get it, we can avoid emitting runtime checks if we know the
119 * result is true. If we don't know, we still have to emit a runtime check
120 * because src might be a subtype of the specialized type.
122 if (srcType < TObj && srcType.clsSpec()) {
123 auto const cls = srcType.clsSpec().cls();
124 if (!env.irb->constrainValue(src, GuardConstraint(cls).setWeak()) &&
125 ((knownCls && cls->classof(knownCls)) ||
126 cls->name()->isame(className))) {
127 return cns(env, true);
131 // Every case after this point requires knowing things about knownCls.
132 if (knownCls == nullptr) return nullptr;
134 auto const ssaClassName = cns(env, className);
135 auto const objClass = gen(env, LdObjClass, src);
137 if (env.context.kind == TransKind::Profile && !InstanceBits::initted()) {
138 gen(env, ProfileInstanceCheck, cns(env, className));
139 } else if (env.context.kind == TransKind::Optimize ||
140 InstanceBits::initted()) {
141 InstanceBits::init();
142 if (InstanceBits::lookup(className) != 0) {
143 return gen(env, InstanceOfBitmask, objClass, ssaClassName);
147 // If the class is an interface, we can just hit the class's vtable or
148 // interface map and call it a day.
149 if (isInterface(knownCls)) {
150 auto const slot = knownCls->preClass()->ifaceVtableSlot();
151 if (slot != kInvalidSlot && RuntimeOption::RepoAuthoritative) {
152 return gen(env,
153 InstanceOfIfaceVtable,
154 InstanceOfIfaceVtableData{knownCls, true},
155 objClass);
158 return gen(env, InstanceOfIface, objClass, ssaClassName);
161 // If knownCls isn't a normal class, our caller may want to do something
162 // different.
163 return isNormalClass(knownCls) ?
164 gen(env, ExtendsClass, ExtendsClassData{ knownCls }, objClass) : nullptr;
168 * Emit a type-check for the given type-constraint. Since the details can vary
169 * quite a bit depending on what the type-constraint represents, this function
170 * is heavily templatized.
172 * The lambda parameters are as follows:
174 * - GetVal: Return the SSATmp of the value to test
175 * - FuncToStr: Emit code to deal with any func to string conversions.
176 * - ClsMethToVec: Emit code to deal with any ClsMeth to array conversions
177 * - Fail: Emit code to deal with the type check failing.
178 * - Callable: Emit code to verify that the given value is callable.
179 * - VerifyCls: Emit code to verify that the given value is an instance of the
180 * given Class.
181 * - VerifyRecordDesc: Emit code to verify that the given value is an instance
182 * of the given record.
183 * - Giveup: Called when the type check cannot be resolved statically. Either
184 * PUNT or call a runtime helper to do the check.
186 * `propCls' should only be non-null for property type-hints, and represents the
187 * runtime class of the object the property belongs to.
189 template <typename GetVal,
190 typename FuncToStr,
191 typename ClassToStr,
192 typename ClsMethToVec,
193 typename Fail,
194 typename Callable,
195 typename VerifyCls,
196 typename VerifyRecordDesc,
197 typename Giveup>
198 void verifyTypeImpl(IRGS& env,
199 const TypeConstraint& tc,
200 bool onlyCheckNullability,
201 SSATmp* propCls,
202 GetVal getVal,
203 FuncToStr funcToStr,
204 ClassToStr classToStr,
205 ClsMethToVec clsMethToVec,
206 Fail fail,
207 Callable callable,
208 VerifyCls verifyCls,
209 VerifyRecordDesc verifyRecDesc,
210 Giveup giveup) {
212 if (!tc.isCheckable()) return;
213 assertx(!tc.isUpperBound() || RuntimeOption::EvalEnforceGenericsUB != 0);
215 auto val = getVal();
216 assertx(val->type() <= TCell);
218 auto const valType = val->type();
220 if (!valType.isKnownDataType()) return giveup();
222 if (tc.isNullable() && valType <= TInitNull) return;
224 auto const genFail = [&] {
225 // If we know there are no mock classes for the current class, it is
226 // okay to fail hard. Otherwise, mock objects may still pass, and we
227 // have to be ready for execution to resume.
228 auto const thisFailsHard = !tc.couldSeeMockObject();
230 auto const failHard = RuntimeOption::RepoAuthoritative
231 && !tc.isSoft()
232 && (!tc.isThis() || thisFailsHard)
233 && (!tc.isUpperBound() || RuntimeOption::EvalEnforceGenericsUB >= 2);
234 return fail(valType, failHard);
237 auto const result =
238 annotCompat(valType.toDataType(), tc.type(), tc.typeName());
239 switch (result) {
240 case AnnotAction::Pass: return;
241 case AnnotAction::Fail: return genFail();
242 case AnnotAction::CallableCheck:
243 return callable(val);
244 case AnnotAction::ObjectCheck:
245 break;
247 case AnnotAction::WarnClass:
248 assertx(valType <= TCls);
249 if (!classToStr(val)) return genFail();
250 gen(
251 env,
252 RaiseNotice,
253 cns(
254 env,
255 makeStaticString(Strings::CLASS_TO_STRING_IMPLICIT)
258 return;
260 case AnnotAction::ConvertClass:
261 assertx(valType <= TCls);
262 if (!classToStr(val)) return genFail();
263 return;
264 case AnnotAction::ClsMethCheck:
265 assertx(valType <= TClsMeth);
266 if (!clsMethToVec(val)) return genFail();
267 return;
268 case AnnotAction::RecordCheck:
269 assertx(valType <= TRecord);
270 auto const rec = Unit::lookupUniqueRecDesc(tc.typeName());
271 auto const isPersistent = recordHasPersistentRDS(rec);
272 auto const checkRecDesc = isPersistent ?
273 cns(env, rec) : ldRecDescSafe(env, tc.typeName());
274 verifyRecDesc(gen(env, LdRecDesc, val), checkRecDesc, val);
275 return;
277 assertx(result == AnnotAction::ObjectCheck);
278 if (onlyCheckNullability) return;
280 if (!(valType <= TObj)) {
281 if (tc.isResolved()) return genFail();
282 // For RepoAuthoritative mode, if tc is a type alias we can optimize in
283 // some cases
284 if (tc.isObject() && RuntimeOption::RepoAuthoritative) {
285 auto const td = tc.namedEntity()->getCachedTypeAlias();
286 if (tc.namedEntity()->isPersistentTypeAlias() && td &&
287 ((td->nullable && valType <= TNull) ||
288 annotCompat(valType.toDataType(), td->type,
289 td->klass ?
290 td->klass->name() :
291 (td->rec ? td->rec->name() : nullptr)) == AnnotAction::Pass)) {
292 env.irb->constrainValue(val, DataTypeSpecific);
293 return;
295 auto const cachedClass = tc.namedEntity()->getCachedClass();
296 if (cachedClass && classHasPersistentRDS(cachedClass) &&
297 cachedClass->enumBaseTy() &&
298 annotCompat(valType.toDataType(),
299 enumDataTypeToAnnotType(*cachedClass->enumBaseTy()),
300 nullptr) == AnnotAction::Pass) {
301 env.irb->constrainValue(val, DataTypeSpecific);
302 return;
305 return giveup();
308 // At this point we know valType is Obj.
309 if (tc.isThis()) {
310 // For this type checks, the class needs to be an exact match.
311 auto const ctxCls = propCls ? propCls : ldCtxCls(env);
312 auto const objClass = gen(env, LdObjClass, val);
313 ifThen(
314 env,
315 [&] (Block* taken) {
316 gen(env, JmpZero, taken, gen(env, EqCls, ctxCls, objClass));
318 [&] {
319 hint(env, Block::Hint::Unlikely);
320 genFail();
323 return;
326 // If we reach here then valType is Obj and tc is Object, Self, or Parent
327 const StringData* clsName;
328 const Class* knownConstraint = nullptr;
329 if (tc.isObject()) {
330 auto const td = tc.namedEntity()->getCachedTypeAlias();
331 if (RuntimeOption::RepoAuthoritative && td &&
332 tc.namedEntity()->isPersistentTypeAlias() &&
333 td->klass) {
334 assertx(classHasPersistentRDS(td->klass));
335 clsName = td->klass->name();
336 knownConstraint = td->klass;
337 } else {
338 clsName = tc.typeName();
340 } else {
341 assertx(!propCls);
342 if (tc.isSelf()) {
343 knownConstraint = curFunc(env)->cls();
344 } else {
345 assertx(tc.isParent());
346 if (auto cls = curFunc(env)->cls()) knownConstraint = cls->parent();
348 if (!knownConstraint) {
349 // The hint was self or parent and there's no corresponding
350 // class for the current func. This typehint will always fail.
351 return genFail();
353 clsName = knownConstraint->preClass()->name();
356 // For "self" and "parent", knownConstraint should always be
357 // non-null at this point
358 assertx(IMPLIES(tc.isSelf() || tc.isParent(), knownConstraint != nullptr));
359 assertx(IMPLIES(tc.isSelf() || tc.isParent(), clsName != nullptr));
360 assertx(IMPLIES(tc.isSelf() || tc.isParent(), !propCls));
362 auto const checkCls = ldClassSafe(env, clsName, knownConstraint);
363 auto const fastIsInstance = implInstanceCheck(env, val, clsName, checkCls);
364 if (fastIsInstance) {
365 ifThen(
366 env,
367 [&] (Block* taken) {
368 gen(env, JmpZero, taken, fastIsInstance);
370 [&] { // taken: the param type does not match
371 hint(env, Block::Hint::Unlikely);
372 genFail();
375 return;
378 verifyCls(val, gen(env, LdObjClass, val), checkCls);
381 Type typeOpToType(IsTypeOp op) {
382 switch (op) {
383 case IsTypeOp::Null: return TInitNull;
384 case IsTypeOp::Int: return TInt;
385 case IsTypeOp::Dbl: return TDbl;
386 case IsTypeOp::Bool: return TBool;
387 case IsTypeOp::Str: return TStr;
388 case IsTypeOp::Keyset: return TKeyset;
389 case IsTypeOp::Obj: return TObj;
390 case IsTypeOp::Res: return TRes;
391 case IsTypeOp::ClsMeth: return TClsMeth;
392 case IsTypeOp::Func: return TFunc;
393 case IsTypeOp::Class: return TCls;
394 case IsTypeOp::Vec:
395 case IsTypeOp::Dict:
396 case IsTypeOp::VArray:
397 case IsTypeOp::DArray:
398 case IsTypeOp::ArrLike:
399 case IsTypeOp::Arr:
400 case IsTypeOp::PHPArr:
401 case IsTypeOp::Scalar: not_reached();
403 not_reached();
406 SSATmp* isScalarImpl(IRGS& env, SSATmp* val) {
407 // The simplifier works fine when val has a known DataType, but do some
408 // checks first in case val has a type like {Int|Str}.
409 auto const scalar = TBool | TInt | TDbl | TStr;
410 if (val->isA(scalar)) return cns(env, true);
411 if (!val->type().maybe(scalar)) return cns(env, false);
413 SSATmp* result = nullptr;
414 for (auto t : {TBool, TInt, TDbl, TStr}) {
415 auto const is_t = gen(env, ConvBoolToInt, gen(env, IsType, t, val));
416 result = result ? gen(env, OrInt, result, is_t) : is_t;
418 return gen(env, ConvIntToBool, result);
421 const StaticString s_FUNC_CONVERSION(Strings::FUNC_TO_STRING);
422 const StaticString s_FUNC_IS_STRING("Func used in is_string");
423 const StaticString s_CLASS_CONVERSION(Strings::CLASS_TO_STRING);
424 const StaticString s_CLASS_IS_STRING("Class used in is_string");
426 SSATmp* isStrImpl(IRGS& env, SSATmp* src) {
427 MultiCond mc{env};
429 mc.ifTypeThen(src, TStr, [&](SSATmp*) { return cns(env, true); });
431 mc.ifTypeThen(src, TCls, [&](SSATmp*) {
432 if (RuntimeOption::EvalClassIsStringNotices) {
433 gen(env, RaiseNotice, cns(env, s_CLASS_IS_STRING.get()));
435 return cns(env, true);
438 return mc.elseDo([&]{ return cns(env, false); });
441 // Checks if the `arr` has provenance, implementing the same logic as in the
442 // runtume helper arrprov::arrayWantsTag. If so, logs a serialization notice.
443 void maybeLogSerialization(IRGS& env, SSATmp* arr, SerializationSite site) {
444 assertx(arr->isA(TArr));
445 if (!RO::EvalArrayProvenance) return;
447 ifThen(env, [&](Block* taken) {
448 arr = gen(env, CheckType, TVArr|TDArr, taken, arr);
449 gen(env, RaiseArraySerializeNotice, cns(env, site), arr);
450 }, [&]{});
453 SSATmp* isArrayImpl(IRGS& env, SSATmp* src, bool log_on_hack_arrays) {
454 MultiCond mc{env};
456 mc.ifTypeThen(src, TArr, [&](SSATmp* src) {
457 maybeLogSerialization(env, src, SerializationSite::IsArray);
458 return cns(env, true);
461 if (!RO::EvalHackArrDVArrs && RO::EvalIsCompatibleClsMethType) {
462 mc.ifTypeThen(src, TClsMeth, [&](SSATmp*) {
463 if (RO::EvalIsVecNotices) {
464 auto const msg = makeStaticString(Strings::CLSMETH_COMPAT_IS_ARR);
465 gen(env, RaiseNotice, cns(env, msg));
467 return cns(env, true);
471 auto const hacLogging = [&](const char* msg) {
472 if (!RO::EvalHackArrCompatIsArrayNotices) return;
473 gen(env, RaiseHackArrCompatNotice, cns(env, makeStaticString(msg)));
476 if (!curFunc(env)->isBuiltin() &&
477 RO::EvalHackArrCompatIsArrayNotices &&
478 log_on_hack_arrays) {
479 mc.ifTypeThen(src, TVec, [&](SSATmp* src) {
480 hacLogging(Strings::HACKARR_COMPAT_VEC_IS_ARR);
481 return cns(env, false);
483 mc.ifTypeThen(src, TDict, [&](SSATmp* src) {
484 hacLogging(Strings::HACKARR_COMPAT_DICT_IS_ARR);
485 return cns(env, false);
487 mc.ifTypeThen(src, TKeyset, [&](SSATmp* src) {
488 hacLogging(Strings::HACKARR_COMPAT_KEYSET_IS_ARR);
489 return cns(env, false);
493 return mc.elseDo([&]{ return cns(env, false); });
496 SSATmp* isVecImpl(IRGS& env, SSATmp* src) {
497 MultiCond mc{env};
499 mc.ifTypeThen(src, TVec, [&](SSATmp* src) {
500 return cns(env, true);
503 auto const hacLogging = [&](const char* msg) {
504 if (!RO::EvalHackArrCompatIsVecDictNotices) return;
505 gen(env, RaiseHackArrCompatNotice, cns(env, makeStaticString(msg)));
508 if (RO::EvalIsCompatibleClsMethType) {
509 if (RO::EvalHackArrDVArrs) {
510 mc.ifTypeThen(src, TClsMeth, [&](SSATmp* src) {
511 if (RO::EvalIsVecNotices) {
512 gen(env, RaiseNotice,
513 cns(env, makeStaticString(Strings::CLSMETH_COMPAT_IS_VEC)));
515 return cns(env, true);
517 } else {
518 mc.ifTypeThen(src, TClsMeth, [&](SSATmp* src) {
519 hacLogging(Strings::HACKARR_COMPAT_VARR_IS_VEC);
520 return cns(env, false);
525 if (RO::EvalHackArrCompatIsVecDictNotices || RO::EvalArrayProvenance) {
526 mc.ifTypeThen(src, TVArr, [&](SSATmp* src) {
527 hacLogging(Strings::HACKARR_COMPAT_VARR_IS_VEC);
528 maybeLogSerialization(env, src, SerializationSite::IsVec);
529 return cns(env, false);
533 return mc.elseDo([&]{ return cns(env, false); });
536 SSATmp* isDictImpl(IRGS& env, SSATmp* src) {
537 MultiCond mc{env};
539 mc.ifTypeThen(src, TDict, [&](SSATmp* src) {
540 return cns(env, true);
543 auto const hacLogging = [&](const char* msg) {
544 if (!RO::EvalHackArrCompatIsVecDictNotices) return;
545 gen(env, RaiseHackArrCompatNotice, cns(env, makeStaticString(msg)));
548 if (RO::EvalHackArrCompatIsVecDictNotices || RO::EvalArrayProvenance) {
549 mc.ifTypeThen(src, TDArr, [&](SSATmp* src) {
550 hacLogging(Strings::HACKARR_COMPAT_DARR_IS_DICT);
551 maybeLogSerialization(env, src, SerializationSite::IsDict);
552 return cns(env, false);
556 return mc.elseDo([&]{ return cns(env, false); });
559 SSATmp* isDVArrayImpl(IRGS& env, SSATmp* src, IsTypeOp subop) {
560 MultiCond mc{env};
562 assertx(subop == IsTypeOp::VArray || subop == IsTypeOp::DArray);
563 auto const varray = subop == IsTypeOp::VArray;
565 mc.ifTypeThen(src, varray ? TVArr : TDArr, [&](SSATmp* src) {
566 return cns(env, true);
569 if (varray && RO::EvalIsCompatibleClsMethType) {
570 mc.ifTypeThen(src, TClsMeth, [&](SSATmp*) {
571 if (RO::EvalIsVecNotices) {
572 auto const msg = makeStaticString(Strings::CLSMETH_COMPAT_IS_VARR);
573 gen(env, RaiseNotice, cns(env, msg));
575 return cns(env, true);
579 return mc.elseDo([&]{ return cns(env, false); });
582 SSATmp* isArrLikeImpl(IRGS& env, SSATmp* src, bool log_on_hack_arrays) {
583 MultiCond mc{env};
585 // We eventually want ClsMeth to be its own DataType, so we must log.
586 if (RO::EvalIsCompatibleClsMethType) {
587 mc.ifTypeThen(src, TClsMeth, [&](SSATmp* src) {
588 if (RO::EvalIsVecNotices) {
589 auto const msg = makeStaticString(Strings::CLSMETH_COMPAT_IS_ANY_ARR);
590 gen(env, RaiseNotice, cns(env, msg));
592 return cns(env, true);
596 if (log_on_hack_arrays && RO::EvalWidenIsArrayLogs) {
597 auto const widenIsArrayLogging = [&](Type t, const char* msg) {
598 mc.ifTypeThen(src, t, [&](SSATmp* src) {
599 gen(env, RaiseHackArrCompatNotice, cns(env, makeStaticString(msg)));
600 return cns(env, true);
603 widenIsArrayLogging(TVec, Strings::HACKARR_COMPAT_VEC_IS_ARR);
604 widenIsArrayLogging(TDict, Strings::HACKARR_COMPAT_DICT_IS_ARR);
605 widenIsArrayLogging(TKeyset, Strings::HACKARR_COMPAT_KEYSET_IS_ARR);
608 return mc.elseDo([&]{ return gen(env, IsType, TArrLike, src); });
611 //////////////////////////////////////////////////////////////////////
615 SSATmp* implInstanceOfD(IRGS& env, SSATmp* src, const StringData* className) {
617 * InstanceOfD is always false if it's not an object.
619 * We're prepared to generate translations for known non-object types, but if
620 * it's Gen/Cell we're going to PUNT because it's natural to translate that
621 * case with control flow TODO(#16781576)
623 if (TObj < src->type()) {
624 PUNT(InstanceOfD_MaybeObj);
626 if (!src->isA(TObj)) {
627 if (src->isA(TCls)) {
628 if (!interface_supports_string(className)) return cns(env, false);
629 if (RuntimeOption::EvalClassIsStringNotices && src->isA(TCls)) {
630 gen(
631 env,
632 RaiseNotice,
633 cns(env, s_CLASS_IS_STRING.get())
636 return cns(env, true);
639 if (src->isA(TClsMeth)) {
640 if (!interface_supports_arrlike(className)) {
641 return cns(env, false);
644 if (RO::EvalIsVecNotices) {
645 gen(
646 env,
647 RaiseNotice,
648 cns(
649 env,
650 makeStaticString(folly::sformat(
651 "Implicit clsmeth to {} conversion", className->data()
657 return cns(env, true);
660 auto const res =
661 (src->isA(TArrLike) && interface_supports_arrlike(className)) ||
662 (src->isA(TStr) && interface_supports_string(className)) ||
663 (src->isA(TInt) && interface_supports_int(className)) ||
664 (src->isA(TDbl) && interface_supports_double(className));
665 return cns(env, res);
668 auto const checkCls = ldClassSafe(env, className);
669 if (auto isInstance = implInstanceCheck(env, src, className, checkCls)) {
670 return isInstance;
673 return gen(env, InstanceOf, gen(env, LdObjClass, src), checkCls);
676 //////////////////////////////////////////////////////////////////////
678 void emitInstanceOfD(IRGS& env, const StringData* className) {
679 auto const src = popC(env);
680 push(env, implInstanceOfD(env, src, className));
681 decRef(env, src);
684 void emitInstanceOf(IRGS& env) {
685 auto const t1 = popC(env);
686 auto const t2 = popC(env); // t2 instanceof t1
688 if (t1->isA(TObj) && t2->isA(TObj)) {
689 auto const c2 = gen(env, LdObjClass, t2);
690 auto const c1 = gen(env, LdObjClass, t1);
691 push(env, gen(env, InstanceOf, c2, c1));
692 decRef(env, t2);
693 decRef(env, t1);
694 return;
697 if (!t1->isA(TStr)) PUNT(InstanceOf-NotStr);
699 if (t2->isA(TObj)) {
700 auto const c1 = gen(env, LookupClsRDS, t1);
701 auto const c2 = gen(env, LdObjClass, t2);
702 push(env, gen(env, InstanceOf, c2, c1));
703 decRef(env, t2);
704 decRef(env, t1);
705 return;
708 auto const res = [&]() -> SSATmp* {
709 if (t2->isA(TArr)) return gen(env, InterfaceSupportsArr, t1);
710 if (t2->isA(TVec)) return gen(env, InterfaceSupportsVec, t1);
711 if (t2->isA(TDict)) return gen(env, InterfaceSupportsDict, t1);
712 if (t2->isA(TKeyset)) return gen(env, InterfaceSupportsKeyset, t1);
713 if (t2->isA(TInt)) return gen(env, InterfaceSupportsInt, t1);
714 if (t2->isA(TStr)) return gen(env, InterfaceSupportsStr, t1);
715 if (t2->isA(TDbl)) return gen(env, InterfaceSupportsDbl, t1);
716 if (t2->isA(TCls)) {
717 if (!RO::EvalRaiseClassConversionWarning) {
718 return gen(env, InterfaceSupportsStr, t1);
720 return cond(
721 env,
722 [&] (Block* taken) {
723 gen(env, JmpZero, taken, gen(env, InterfaceSupportsStr, t1));
725 [&] {
726 gen(env, RaiseNotice, cns(env, s_CLASS_CONVERSION.get()));
727 return cns(env, true);
729 [&] { return cns(env, false); }
732 if (!t2->type().maybe(TObj|TArr|TVec|TDict|TKeyset|
733 TInt|TStr|TDbl)) return cns(env, false);
734 return nullptr;
735 }();
737 if (!res) PUNT(InstanceOf-Unknown);
739 push(env, res);
740 decRef(env, t2);
741 decRef(env, t1);
744 void emitIsLateBoundCls(IRGS& env) {
745 auto const cls = curClass(env);
746 if (!cls) PUNT(IsLateBoundCls-NoClassContext);
747 if (isTrait(cls)) PUNT(IsLateBoundCls-Trait);
748 auto const obj = popC(env);
749 if (obj->isA(TObj)) {
750 auto const rhs = ldCtxCls(env);
751 auto const lhs = gen(env, LdObjClass, obj);
752 push(env, gen(env, InstanceOf, lhs, rhs));
753 } else if (!obj->type().maybe(TObj)) {
754 push(env, cns(env, false));
755 } else {
756 PUNT(IsLateBoundCls-MaybeObject);
758 decRef(env, obj);
761 namespace {
763 template<typename F>
764 SSATmp* resolveTypeStructureAndCacheInRDS(
765 IRGS& env,
766 F resolveTypeStruct,
767 bool typeStructureCouldBeNonStatic
769 if (typeStructureCouldBeNonStatic) return resolveTypeStruct();
770 auto const handle = RDSHandleData { rds::alloc<ArrayData*>().handle() };
771 auto const type = RO::EvalHackArrDVArrs ? TPtrToOtherDict : TPtrToOtherDArr;
772 auto const addr = gen(env, LdRDSAddr, handle, type);
773 ifThen(
774 env,
775 [&] (Block* taken) {
776 gen(env, CheckRDSInitialized, taken, handle);
778 [&] {
779 hint(env, Block::Hint::Unlikely);
780 gen(env, StMem, addr, resolveTypeStruct());
781 gen(env, MarkRDSInitialized, handle);
784 return gen(env, LdMem, RO::EvalHackArrDVArrs ? TDict : TDArr, addr);
787 SSATmp* resolveTypeStructImpl(
788 IRGS& env,
789 bool typeStructureCouldBeNonStatic,
790 bool suppress,
791 uint32_t n,
792 bool isOrAsOp
794 auto const declaringCls = curFunc(env) ? curClass(env) : nullptr;
795 auto const calledCls =
796 declaringCls && typeStructureCouldBeNonStatic
797 ? ldCtxCls(env)
798 : cns(env, nullptr);
799 auto const result = resolveTypeStructureAndCacheInRDS(
800 env,
801 [&] {
802 return gen(
803 env,
804 ResolveTypeStruct,
805 ResolveTypeStructData {
806 declaringCls,
807 suppress,
808 spOffBCFromIRSP(env),
809 static_cast<uint32_t>(n),
810 isOrAsOp
812 sp(env),
813 calledCls
816 typeStructureCouldBeNonStatic
818 popC(env);
819 discard(env, n - 1);
820 return result;
823 const ArrayData* staticallyResolveTypeStructure(
824 IRGS& env,
825 const ArrayData* ts,
826 bool& partial,
827 bool& invalidType
829 auto const declaringCls = curFunc(env) ? curClass(env) : nullptr;
830 bool persistent = false;
831 // This shouldn't do a difference, but does on GCC 8.3 on Ubuntu 19.04;
832 // if we take the catch then return `ts`, it's a bogus value and we
833 // segfault... sometimes...
834 const ArrayData* ts_copy = ts;
835 try {
836 auto newTS = TypeStructure::resolvePartial(
837 ArrNR(ts), nullptr, declaringCls, persistent, partial, invalidType);
838 if (persistent) return ArrayData::GetScalarArray(std::move(newTS));
839 } catch (Exception& e) {}
840 // We are here because either we threw in the resolution or it wasn't
841 // persistent resolution which means we didn't really resolve it
842 partial = true;
843 return ts_copy;
846 SSATmp* check_nullable(IRGS& env, SSATmp* res, SSATmp* var) {
847 return cond(
848 env,
849 [&] (Block* taken) { gen(env, JmpNZero, taken, res); },
850 [&] { return gen(env, IsType, TNull, var); },
851 [&] { return cns(env, true); }
855 void chain_is_type(IRGS& env, SSATmp* c, bool nullable, Type ty) {
856 always_assert(false);
859 template<typename... Types>
860 void chain_is_type(IRGS& env, SSATmp* c, bool nullable,
861 Type ty1, Type ty2, Types&&... rest) {
862 ifThenElse(
863 env,
864 [&](Block* taken) {
865 auto const res = gen(env, IsType, ty1, c);
866 gen(env, JmpNZero, taken, res);
868 [&] {
869 if (sizeof...(rest) == 0) {
870 auto const res = gen(env, IsType, ty2, c);
871 push(env, nullable ? check_nullable(env, res, c) : res);
872 } else {
873 chain_is_type(env, c, nullable, ty2, rest...);
876 [&] { // taken block
877 push(env, cns(env, true));
883 * This function tries to emit is type struct operations without resolving
884 * the type structure when that's possible.
885 * When it returns true, it has popped two values from the stack, namely the
886 * type structure and the cell, and pushed one value back to stack, namely
887 * true/false if it is an is-operation or the cell if it is an as operation.
888 * This function does not modify the reference counts of these stack values,
889 * leaving that responsibility to the caller.
890 * When it returns false, it does not modify anything.
892 bool emitIsTypeStructWithoutResolvingIfPossible(
893 IRGS& env,
894 const ArrayData* ts
896 // Top of the stack is the type structure, so the thing we are checking is
897 // the next element
898 auto const t = topC(env, BCSPRelOffset { 1 });
899 auto const is_nullable_ts = is_ts_nullable(ts);
901 auto const cnsResult = [&] (bool value) {
902 popC(env); // pop the ts that's on the stack
903 popC(env); // pop the cell
904 push(env, cns(env, value));
905 return true;
908 auto const success = [&] { return cnsResult(true); };
909 auto const fail = [&] { return cnsResult(false); };
911 auto const primitive = [&] (Type ty, bool should_negate = false) {
912 auto const nty = is_nullable_ts ? ty|TNull : ty;
913 if (t->isA(nty)) return should_negate ? fail() : success();
914 if (!t->type().maybe(nty)) return should_negate ? success() : fail();
915 popC(env); // pop the ts that's on the stack
916 auto const c = popC(env);
917 auto const res = gen(env, should_negate ? IsNType : IsType, ty, c);
918 push(env, is_nullable_ts ? check_nullable(env, res, c) : res);
919 return true;
922 // We explicitly bind is_nullable_ts because failing to do so causes a
923 // spurious compiler error on some g++ versions.
924 auto const unionOf = [&,is_nullable_ts] (Type ty1, Type ty2,
925 auto&&... rest) {
926 auto const ty = Type::unionAll(ty1, ty2, rest...) |
927 (is_nullable_ts ? TNull : TBottom);
928 if (t->isA(ty)) return success();
929 if (!t->type().maybe(ty)) return fail();
931 popC(env); // pop the ts that's on the stack
932 auto const c = popC(env);
933 chain_is_type(env, c, is_nullable_ts, ty1, ty2, rest...);
934 return true;
937 if (t->isA(TNull) && is_nullable_ts) return success();
939 auto kind = get_ts_kind(ts);
940 switch (kind) {
941 case TypeStructure::Kind::T_int: return primitive(TInt);
942 case TypeStructure::Kind::T_bool: return primitive(TBool);
943 case TypeStructure::Kind::T_float: return primitive(TDbl);
944 case TypeStructure::Kind::T_string: {
945 if (t->type().maybe(TCls) &&
946 RuntimeOption::EvalRaiseClassConversionWarning) {
947 ifElse(env,
948 [&] (Block* taken) {
949 gen(env, CheckType, TCls, taken, t);
951 [&] {
952 gen(env, RaiseWarning, cns(env, s_CLASS_IS_STRING.get()));
956 return unionOf(TStr, TCls);
958 case TypeStructure::Kind::T_null: return primitive(TNull);
959 case TypeStructure::Kind::T_void: return primitive(TNull);
960 case TypeStructure::Kind::T_keyset: return primitive(TKeyset);
961 case TypeStructure::Kind::T_nonnull: return primitive(TNull, true);
962 case TypeStructure::Kind::T_mixed:
963 case TypeStructure::Kind::T_dynamic:
964 return success();
965 case TypeStructure::Kind::T_num: return unionOf(TInt, TDbl);
966 case TypeStructure::Kind::T_arraykey: return unionOf(TInt, TStr);
967 case TypeStructure::Kind::T_arraylike:
968 if (t->type().maybe(TClsMeth)) {
969 if (t->isA(TClsMeth)) {
970 if (RuntimeOption::EvalIsVecNotices) {
971 gen(env, RaiseNotice,
972 cns(env, makeStaticString(Strings::CLSMETH_COMPAT_IS_ANY_ARR)));
974 return success();
975 } else {
976 PUNT(TypeStructC-MaybeClsMeth);
979 return unionOf(TArr, TVec, TDict, TKeyset);
980 case TypeStructure::Kind::T_vec_or_dict:
981 if (t->type().maybe(TClsMeth)) {
982 if (t->isA(TClsMeth)) {
983 if (RuntimeOption::EvalHackArrDVArrs) {
984 if (RuntimeOption::EvalIsVecNotices) {
985 gen(env, RaiseNotice,
986 cns(env, makeStaticString(Strings::CLSMETH_COMPAT_IS_VEC)));
988 return success();
989 } else {
990 return fail();
992 } else {
993 PUNT(TypeStructC-MaybeClsMeth);
996 // fallthrough
997 case TypeStructure::Kind::T_dict:
998 case TypeStructure::Kind::T_vec: {
999 popC(env); // pop the ts that's on the stack
1000 auto const c = popC(env);
1001 auto const res = [&]{
1002 if (kind == TypeStructure::Kind::T_dict) {
1003 return isDictImpl(env, c);
1004 } else if (kind == TypeStructure::Kind::T_vec) {
1005 return isVecImpl(env, c);
1006 } else if (kind == TypeStructure::Kind::T_vec_or_dict) {
1007 return cond(
1008 env,
1009 [&](Block* taken) {
1010 auto vec = isVecImpl(env, c);
1011 gen(env, JmpZero, taken, vec);
1013 [&] {
1014 return cns(env, true);
1016 [&] {
1017 return isDictImpl(env, c);
1020 } else {
1021 not_reached();
1023 }();
1024 push(env, is_nullable_ts ? check_nullable(env, res, c) : res);
1025 return true;
1027 case TypeStructure::Kind::T_class:
1028 case TypeStructure::Kind::T_interface:
1029 case TypeStructure::Kind::T_xhp: {
1030 auto const clsname = get_ts_classname(ts);
1031 auto cls = lookupUniqueClass(env, clsname);
1032 if (ts->exists(s_generic_types) &&
1033 ((classIsPersistentOrCtxParent(env, cls) &&
1034 cls->hasReifiedGenerics()) ||
1035 !isTSAllWildcards(ts))) {
1036 // If it is a reified class or has non wildcard generics,
1037 // we need to bail
1038 return false;
1040 popC(env); // pop the ts that's on the stack
1041 auto const c = popC(env);
1042 auto const res = implInstanceOfD(env, c, clsname);
1043 push(env, is_nullable_ts ? check_nullable(env, res, c) : res);
1044 return true;
1046 case TypeStructure::Kind::T_nothing:
1047 case TypeStructure::Kind::T_noreturn:
1048 return fail();
1049 case TypeStructure::Kind::T_typevar:
1050 case TypeStructure::Kind::T_fun:
1051 case TypeStructure::Kind::T_trait:
1052 case TypeStructure::Kind::T_array:
1053 case TypeStructure::Kind::T_darray:
1054 case TypeStructure::Kind::T_varray:
1055 case TypeStructure::Kind::T_varray_or_darray:
1056 // Not supported, will throw an error on these at the resolution phase
1057 return false;
1058 case TypeStructure::Kind::T_enum:
1059 case TypeStructure::Kind::T_tuple:
1060 case TypeStructure::Kind::T_shape:
1061 case TypeStructure::Kind::T_typeaccess:
1062 case TypeStructure::Kind::T_unresolved:
1063 case TypeStructure::Kind::T_resource:
1064 case TypeStructure::Kind::T_reifiedtype:
1065 // TODO(T28423611): Implement these
1066 return false;
1068 not_reached();
1072 * shouldDefRef is set iff the resulting SSATmp is a newly allocated type
1073 * structure
1074 * This function does not modify the reference count of its inputs, leaving that
1075 * to the caller
1077 SSATmp* handleIsResolutionAndCommonOpts(
1078 IRGS& env,
1079 TypeStructResolveOp op,
1080 bool& done,
1081 bool& shouldDecRef,
1082 bool& checkValid
1084 auto const a = topC(env);
1085 auto const required_ts_type = RO::EvalHackArrDVArrs ? TDict : TDArr;
1086 if (!a->isA(required_ts_type)) PUNT(IsTypeStructC-NotArrayTypeStruct);
1087 if (!a->hasConstVal(required_ts_type)) {
1088 if (op == TypeStructResolveOp::Resolve) {
1089 return resolveTypeStructImpl(env, true, true, 1, true);
1091 shouldDecRef = false;
1092 checkValid = true;
1093 return popC(env);
1095 auto const ts = a->arrLikeVal();
1096 auto maybe_resolved = ts;
1097 bool partial = true;
1098 bool invalidType = true;
1099 if (op == TypeStructResolveOp::Resolve) {
1100 maybe_resolved =
1101 staticallyResolveTypeStructure(env, ts, partial, invalidType);
1102 shouldDecRef = maybe_resolved != ts;
1104 if (emitIsTypeStructWithoutResolvingIfPossible(env, maybe_resolved)) {
1105 done = true;
1106 return nullptr;
1108 if (op == TypeStructResolveOp::Resolve && (partial || invalidType)) {
1109 shouldDecRef = true;
1110 return resolveTypeStructImpl(
1111 env, typeStructureCouldBeNonStatic(ts), true, 1, true);
1113 popC(env);
1114 if (op == TypeStructResolveOp::DontResolve) checkValid = true;
1115 return cns(env, maybe_resolved);
1118 } // namespace
1120 void emitIsTypeStructC(IRGS& env, TypeStructResolveOp op) {
1121 auto const a = topC(env);
1122 auto const c = topC(env, BCSPRelOffset { 1 });
1123 bool done = false, shouldDecRef = true, checkValid = false;
1124 SSATmp* tc =
1125 handleIsResolutionAndCommonOpts(env, op, done, shouldDecRef, checkValid);
1126 if (done) {
1127 decRef(env, c);
1128 decRef(env, a);
1129 return;
1131 popC(env);
1132 auto block = opcodeMayRaise(IsTypeStruct) && shouldDecRef
1133 ? create_catch_block(env, [&]{ decRef(env, tc); })
1134 : nullptr;
1135 auto const data = RDSHandleData { rds::bindTSCache(curFunc(env)).handle() };
1137 static const StaticString s_IsTypeStruct{"IsTypeStruct"};
1138 auto const profile = TargetProfile<IsTypeStructProfile> {
1139 env.context,
1140 env.irb->curMarker(),
1141 s_IsTypeStruct.get()
1144 auto const generic = [&] {
1145 if (checkValid) gen(env, RaiseErrorOnInvalidIsAsExpressionType, tc);
1146 return gen(env, IsTypeStruct, block, data, tc, c);
1149 auto const finish = [&] (SSATmp* result) {
1150 push(env, result);
1151 decRef(env, c);
1152 decRef(env, a);
1155 if (profile.profiling()) {
1156 gen(env, ProfileIsTypeStruct, RDSHandleData { profile.handle() }, a);
1157 finish(generic());
1158 return;
1161 if (!profile.optimizing() || !profile.data().shouldOptimize()) {
1162 finish(generic());
1163 return;
1166 finish(cond(
1167 env,
1168 [&] (Block* taken) {
1169 return gen(env, IsTypeStructCached, taken, a, c);
1171 [&] (SSATmp* result) { // next
1172 return result;
1174 [&] { // taken
1175 hint(env, Block::Hint::Unlikely);
1176 return generic();
1181 void emitThrowAsTypeStructException(IRGS& env) {
1182 auto const arr = topC(env);
1183 auto const c = topC(env, BCSPRelOffset { 1 });
1184 auto const tsAndBlock = [&]() -> std::pair<SSATmp*, Block*> {
1185 if (arr->hasConstVal(RO::EvalHackArrDVArrs ? TDict : TDArr)) {
1186 auto const ts = arr->arrLikeVal();
1187 auto maybe_resolved = ts;
1188 bool partial = true, invalidType = true;
1189 maybe_resolved =
1190 staticallyResolveTypeStructure(env, ts, partial, invalidType);
1191 if (!ts->same(maybe_resolved)) {
1192 auto const inputTS = cns(env, maybe_resolved);
1193 return {inputTS, create_catch_block(env, [&]{ decRef(env, inputTS); })};
1196 auto const ts = resolveTypeStructImpl(env, true, false, 1, true);
1197 return {ts, nullptr};
1198 }();
1199 // No need to decref inputs as this instruction will throw
1200 gen(env, ThrowAsTypeStructException, tsAndBlock.second, tsAndBlock.first, c);
1203 void emitRecordReifiedGeneric(IRGS& env) {
1204 auto const ts = popC(env);
1205 if (!ts->isA(RO::EvalHackArrDVArrs ? TVec : TVArr)) {
1206 PUNT(RecordReifiedGeneric-InvalidTS);
1208 // RecordReifiedGenericsAndGetTSList decrefs the ts
1209 auto const result = gen(env, RecordReifiedGenericsAndGetTSList, ts);
1210 push(env, result);
1213 void emitCombineAndResolveTypeStruct(IRGS& env, uint32_t n) {
1214 push(env, resolveTypeStructImpl(env, true, false, n, false));
1217 void raiseClsmethCompatTypeHint(
1218 IRGS& env, int32_t id, const Func* func, const TypeConstraint& tc) {
1219 auto name = tc.displayName(func->cls());
1220 if (id == TypeConstraint::ReturnId) {
1221 gen(env, RaiseNotice, cns(env, makeStaticString(
1222 folly::sformat("class_meth Compat: Value returned from function {}() "
1223 "must be of type {}, clsmeth given",
1224 func->fullName(), name))));
1225 } else {
1226 gen(env, RaiseNotice, cns(env, makeStaticString(
1227 folly::sformat("class_meth Compat: Argument {} passed to {}() "
1228 "must be of type {}, clsmeth given",
1229 id + 1, func->fullName(), name))));
1233 namespace {
1235 void verifyRetTypeImpl(IRGS& env, int32_t id, int32_t ind,
1236 bool onlyCheckNullability) {
1237 auto const func = curFunc(env);
1238 auto const verifyFunc = [&] (const TypeConstraint& tc) {
1239 verifyTypeImpl(
1240 env,
1242 onlyCheckNullability,
1243 nullptr,
1244 [&] { // Get value to test
1245 return topC(env, BCSPRelOffset { ind });
1247 [&] (SSATmp* val) { // func to string conversions
1248 auto const str = gen(env, LdFuncName, val);
1249 auto const offset = offsetFromIRSP(env, BCSPRelOffset { ind });
1250 gen(env, StStk, IRSPRelOffsetData{offset}, sp(env), str);
1251 env.irb->exceptionStackBoundary();
1252 return true;
1254 [&] (SSATmp* val) { // class to string conversions
1255 auto const str = gen(env, LdClsName, val);
1256 auto const offset = offsetFromIRSP(env, BCSPRelOffset { ind });
1257 gen(env, StStk, IRSPRelOffsetData{offset}, sp(env), str);
1258 env.irb->exceptionStackBoundary();
1259 return true;
1261 [&] (SSATmp* val) { // clsmeth to varray/vec conversions
1262 if (RuntimeOption::EvalVecHintNotices) {
1263 raiseClsmethCompatTypeHint(env, id, func, tc);
1265 auto clsMethArr = convertClsMethToVec(env, val);
1266 discard(env, 1);
1267 push(env, clsMethArr);
1268 decRef(env, val);
1269 return true;
1271 [&] (Type, bool hard) { // Check failure
1272 updateMarker(env);
1273 env.irb->exceptionStackBoundary();
1274 gen(
1275 env,
1276 hard ? VerifyRetFailHard : VerifyRetFail,
1277 ParamWithTCData { id, &tc },
1278 ldStkAddr(env, BCSPRelOffset { ind })
1281 [&] (SSATmp* val) { // Callable check
1282 gen(
1283 env,
1284 VerifyRetCallable,
1285 ParamData { id },
1289 [&] (SSATmp* val, SSATmp* objClass, SSATmp* checkCls) {
1290 // Class/type-alias check
1291 gen(
1292 env,
1293 VerifyRetCls,
1294 ParamData { id },
1295 objClass,
1296 checkCls,
1297 cns(env, uintptr_t(&tc)),
1301 [&] (SSATmp* valRecDesc, SSATmp* checkRec, SSATmp* val) {
1302 // Record/type-alias check
1303 gen(
1304 env,
1305 VerifyRetRecDesc,
1306 ParamData { id },
1307 valRecDesc,
1308 checkRec,
1309 cns(env, uintptr_t(&tc)),
1313 [] { // Giveup
1314 PUNT(VerifyReturnType);
1318 auto const& tc = (id == TypeConstraint::ReturnId)
1319 ? func->returnTypeConstraint()
1320 : func->params()[id].typeConstraint;
1321 assertx(ind >= 0);
1322 verifyFunc(tc);
1323 if (id == TypeConstraint::ReturnId && func->hasReturnWithMultiUBs()) {
1324 auto& ubs = const_cast<Func::UpperBoundVec&>(func->returnUBs());
1325 for (auto& ub : ubs) {
1326 applyFlagsToUB(ub, tc);
1327 verifyFunc(ub);
1329 } else if (func->hasParamsWithMultiUBs()) {
1330 auto& ubs = const_cast<Func::ParamUBMap&>(func->paramUBs());
1331 auto it = ubs.find(id);
1332 if (it != ubs.end()) {
1333 for (auto& ub : it->second) {
1334 applyFlagsToUB(ub, tc);
1335 verifyFunc(ub);
1341 void verifyParamTypeImpl(IRGS& env, int32_t id) {
1342 auto const func = curFunc(env);
1343 auto const verifyFunc = [&](const TypeConstraint& tc) {
1344 verifyTypeImpl(
1345 env,
1347 false,
1348 nullptr,
1349 [&] { // Get value to test
1350 return ldLoc(env, id, nullptr, DataTypeSpecific);
1352 [&] (SSATmp* val) { // func to string conversions
1353 auto const str = gen(env, LdFuncName, val);
1354 stLocRaw(env, id, fp(env), str);
1355 return true;
1357 [&] (SSATmp* val) { // class to string conversions
1358 auto const str = gen(env, LdClsName, val);
1359 stLocRaw(env, id, fp(env), str);
1360 return true;
1362 [&] (SSATmp* val) { // clsmeth to varray/vec conversions
1363 if (RuntimeOption::EvalVecHintNotices) {
1364 raiseClsmethCompatTypeHint(env, id, func, tc);
1366 auto clsMethArr = convertClsMethToVec(env, val);
1367 stLocRaw(env, id, fp(env), clsMethArr);
1368 decRef(env, val);
1369 return true;
1371 [&] (Type valType, bool hard) { // Check failure
1372 auto const failHard = hard &&
1373 !(tc.isArray() && valType.maybe(TObj));
1374 gen(
1375 env,
1376 failHard ? VerifyParamFailHard : VerifyParamFail,
1377 ParamWithTCData { id, &tc }
1380 [&] (SSATmp* val) { // Callable check
1381 gen(
1382 env,
1383 VerifyParamCallable,
1384 val,
1385 cns(env, id)
1388 [&] (SSATmp*, SSATmp* objClass, SSATmp* checkCls) {
1389 // Class/type-alias check
1390 gen(
1391 env,
1392 VerifyParamCls,
1393 objClass,
1394 checkCls,
1395 cns(env, uintptr_t(&tc)),
1396 cns(env, id)
1399 [&] (SSATmp* valRecDesc, SSATmp* checkRec, SSATmp*) {
1400 // Record/type-alias check
1401 gen(
1402 env,
1403 VerifyParamRecDesc,
1404 valRecDesc,
1405 checkRec,
1406 cns(env, uintptr_t(&tc)),
1407 cns(env, id)
1410 [] { // Giveup
1411 PUNT(VerifyParamType);
1415 auto const& tc = func->params()[id].typeConstraint;
1416 verifyFunc(tc);
1417 if (func->hasParamsWithMultiUBs()) {
1418 auto& ubs = const_cast<Func::ParamUBMap&>(func->paramUBs());
1419 auto it = ubs.find(id);
1420 if (it != ubs.end()) {
1421 for (auto& ub : it->second) {
1422 applyFlagsToUB(ub, tc);
1423 verifyFunc(ub);
1431 void verifyPropType(IRGS& env,
1432 SSATmp* cls,
1433 const HPHP::TypeConstraint* tc,
1434 const Class::UpperBoundVec* ubs,
1435 Slot slot,
1436 SSATmp* val,
1437 SSATmp* name,
1438 bool isSProp,
1439 SSATmp** coerce /* = nullptr */) {
1440 assertx(cls->isA(TCls));
1441 assertx(val->isA(TCell));
1443 if (coerce) *coerce = val;
1444 if (RuntimeOption::EvalCheckPropTypeHints <= 0) return;
1446 auto const verifyFunc = [&](const TypeConstraint* tc) {
1447 if (!tc || !tc->isCheckable()) return;
1448 assertx(tc->validForProp());
1450 verifyTypeImpl(
1451 env,
1452 *tc,
1453 false,
1454 cls,
1455 [&] { // Get value to check
1456 env.irb->constrainValue(val, DataTypeSpecific);
1457 return val;
1459 [&] (SSATmp*) { return false; }, // No func to string automatic conversions
1460 [&] (SSATmp*) { // class to string automatic conversions
1461 if (!coerce) return false;
1462 if (RO::EvalCheckPropTypeHints < 3) return false;
1463 *coerce = gen(env, LdClsName, val);
1464 return true;
1466 [&] (SSATmp* val) {
1467 if (!coerce) return false;
1468 // If we're not hard enforcing property type mismatches don't coerce
1469 if (RO::EvalCheckPropTypeHints < 3) return false;
1470 if (tc->isUpperBound() && RO::EvalEnforceGenericsUB < 2) return false;
1471 if (RuntimeOption::EvalVecHintNotices) {
1472 if (cls->hasConstVal(TCls) && name->hasConstVal(TStr)) {
1473 auto const msg = makeStaticString(folly::sformat(
1474 "class_meth Compat: {} '{}::{}' declared as type {}, clsmeth "
1475 "assigned",
1476 isSProp ? "Static property" : "Property",
1477 cls->clsVal()->name()->data(),
1478 name->strVal()->data(),
1479 tc->displayName().c_str()
1481 gen(env, RaiseNotice, cns(env, msg));
1482 } else {
1483 gen(
1484 env,
1485 RaiseClsMethPropConvertNotice,
1486 RaiseClsMethPropConvertNoticeData{tc, isSProp},
1487 cls,
1488 name
1492 *coerce = convertClsMethToVec(env, val);
1493 return true;
1495 [&] (Type, bool hard) { // Check failure
1496 auto const failHard =
1497 hard && RuntimeOption::EvalCheckPropTypeHints >= 3 &&
1498 (!tc->isUpperBound() || RuntimeOption::EvalEnforceGenericsUB >= 2);
1499 gen(
1500 env,
1501 failHard ? VerifyPropFailHard : VerifyPropFail,
1502 TypeConstraintData{ tc },
1503 cls,
1504 cns(env, slot),
1505 val,
1506 cns(env, isSProp)
1509 // We don't allow callable as a property type-hint, so we should never need
1510 // to check callability.
1511 [&] (SSATmp*) { always_assert(false); },
1512 [&] (SSATmp* v, SSATmp*, SSATmp* checkCls) { // Class/type-alias check
1513 gen(
1514 env,
1515 VerifyPropCls,
1516 TypeConstraintData{ tc },
1517 cls,
1518 cns(env, slot),
1519 checkCls,
1521 cns(env, isSProp)
1524 [&] (SSATmp*, SSATmp* checkRec, SSATmp* val) { // Record/type-alias check
1525 gen(
1526 env,
1527 VerifyPropRecDesc,
1528 TypeConstraintData{ tc },
1529 cls,
1530 cns(env, slot),
1531 checkRec,
1532 val,
1533 cns(env, isSProp)
1536 [&] {
1537 // Unlike the other type-hint checks, we don't punt here. We instead do
1538 // the check using a runtime helper. This gives us the freedom to call
1539 // verifyPropType without us worrying about it punting the entire
1540 // operation.
1541 if (coerce && (tc->isArray() || (tc->isObject() && !tc->isResolved()))) {
1542 *coerce = gen(
1543 env,
1544 VerifyPropCoerce,
1545 TypeConstraintData { tc },
1546 cls,
1547 cns(env, slot),
1548 val,
1549 cns(env, isSProp)
1551 } else {
1552 gen(
1553 env,
1554 VerifyProp,
1555 TypeConstraintData { tc },
1556 cls,
1557 cns(env, slot),
1558 val,
1559 cns(env, isSProp)
1565 verifyFunc(tc);
1566 if (RuntimeOption::EvalEnforceGenericsUB > 0) {
1567 for (auto const& ub : *ubs) {
1568 verifyFunc(&ub);
1573 void emitVerifyRetTypeC(IRGS& env) {
1574 verifyRetTypeImpl(env, TypeConstraint::ReturnId, 0, false);
1577 void emitVerifyRetTypeTS(IRGS& env) {
1578 verifyRetTypeImpl(env, TypeConstraint::ReturnId, 1, false);
1579 auto const ts = popC(env);
1580 auto const cell = topC(env);
1581 auto const reified = tcCouldBeReified(curFunc(env), TypeConstraint::ReturnId);
1582 if (reified || cell->isA(TObj)) {
1583 gen(env, VerifyReifiedReturnType, cell, ts);
1584 } else if (cell->type().maybe(TObj) && !reified) {
1585 // Meaning we did not not guard on the stack input correctly
1586 PUNT(VerifyRetTypeTS-UnguardedObj);
1590 void emitVerifyRetNonNullC(IRGS& env) {
1591 auto const func = curFunc(env);
1592 auto const& tc = func->returnTypeConstraint();
1593 always_assert(!tc.isNullable());
1594 verifyRetTypeImpl(env, TypeConstraint::ReturnId, 0, true);
1597 void emitVerifyOutType(IRGS& env, uint32_t paramId) {
1598 verifyRetTypeImpl(env, paramId, 0, false);
1601 void emitVerifyParamType(IRGS& env, int32_t paramId) {
1602 verifyParamTypeImpl(env, paramId);
1605 void emitVerifyParamTypeTS(IRGS& env, int32_t paramId) {
1606 verifyParamTypeImpl(env, paramId);
1607 auto const ts = popC(env);
1608 auto const cell = ldLoc(env, paramId, nullptr, DataTypeSpecific);
1609 auto const reified = tcCouldBeReified(curFunc(env), paramId);
1610 if (cell->isA(TObj) || reified) {
1611 gen(env, VerifyReifiedLocalType, ParamData { paramId }, ts);
1612 } else if (cell->type().maybe(TObj)) {
1613 // Meaning we did not not guard on the stack input correctly
1614 PUNT(VerifyReifiedLocalType-UnguardedObj);
1618 void emitOODeclExists(IRGS& env, OODeclExistsOp subop) {
1619 auto const tAutoload = topC(env);
1620 auto const tCls = topC(env, BCSPRelOffset{1});
1622 if (!tCls->isA(TStr) || !tAutoload->isA(TBool)){ // result of Cast
1623 PUNT(OODeclExists-BadTypes);
1626 ClassKind kind;
1627 switch (subop) {
1628 case OODeclExistsOp::Class: kind = ClassKind::Class; break;
1629 case OODeclExistsOp::Trait: kind = ClassKind::Trait; break;
1630 case OODeclExistsOp::Interface: kind = ClassKind::Interface; break;
1633 auto const val = gen(
1634 env,
1635 OODeclExists,
1636 ClassKindData { kind },
1637 tCls,
1638 tAutoload
1640 discard(env, 2);
1641 push(env, val);
1642 decRef(env, tCls);
1645 void emitIssetL(IRGS& env, int32_t id) {
1646 auto const ld = ldLoc(env, id, nullptr, DataTypeSpecific);
1647 if (ld->isA(TClsMeth)) {
1648 PUNT(IssetL_is_ClsMeth);
1650 push(env, gen(env, IsNType, TNull, ld));
1653 void emitIsUnsetL(IRGS& env, int32_t id) {
1654 auto const ld = ldLoc(env, id, nullptr, DataTypeSpecific);
1655 push(env, gen(env, IsType, TUninit, ld));
1658 SSATmp* isTypeHelper(IRGS& env, IsTypeOp subop, SSATmp* val) {
1659 switch (subop) {
1660 case IsTypeOp::VArray: /* intentional fallthrough */
1661 case IsTypeOp::DArray: return isDVArrayImpl(env, val, subop);
1662 case IsTypeOp::PHPArr:
1663 return isArrayImpl(env, val, /*log_on_hack_arrays=*/false);
1664 case IsTypeOp::Arr:
1665 return RO::EvalWidenIsArray
1666 ? isArrLikeImpl(env, val, /* log_on_hack_arrays = */ true)
1667 : isArrayImpl(env, val, /*log_on_hack_arrays=*/true);
1668 case IsTypeOp::Vec: return isVecImpl(env, val);
1669 case IsTypeOp::Dict: return isDictImpl(env, val);
1670 case IsTypeOp::Scalar: return isScalarImpl(env, val);
1671 case IsTypeOp::Str: return isStrImpl(env, val);
1672 case IsTypeOp::ArrLike:
1673 return isArrLikeImpl(env, val, /*log_on_hack_arrays = */ false);
1674 default: break;
1677 auto const t = typeOpToType(subop);
1678 return t <= TObj ? optimizedCallIsObject(env, val) : gen(env, IsType, t, val);
1681 void emitIsTypeC(IRGS& env, IsTypeOp subop) {
1682 auto const val = popC(env, DataTypeSpecific);
1683 push(env, isTypeHelper(env, subop, val));
1684 decRef(env, val);
1687 void emitIsTypeL(IRGS& env, NamedLocal loc, IsTypeOp subop) {
1688 auto const val = ldLocWarn(env, loc, nullptr, DataTypeSpecific);
1689 push(env, isTypeHelper(env, subop, val));
1692 //////////////////////////////////////////////////////////////////////
1694 void emitAssertRATL(IRGS& env, int32_t loc, RepoAuthType rat) {
1695 assertTypeLocal(env, loc, typeFromRAT(rat, curClass(env)));
1698 void emitAssertRATStk(IRGS& env, uint32_t offset, RepoAuthType rat) {
1699 assertTypeStack(
1700 env,
1701 BCSPRelOffset{safe_cast<int32_t>(offset)},
1702 typeFromRAT(rat, curClass(env))
1706 //////////////////////////////////////////////////////////////////////
1708 SSATmp* doDVArrChecks(IRGS& env, SSATmp* arr, Block* taken,
1709 const TypeConstraint& tc) {
1710 assertx(tc.isArray());
1711 auto const type = [&]{
1712 if (tc.isVArray()) return TVArr;
1713 if (tc.isDArray()) return TDArr;
1714 assertx(tc.isVArrayOrDArray());
1715 return TVArr|TDArr;
1716 }();
1717 return gen(env, CheckType, type, taken, arr);
1720 //////////////////////////////////////////////////////////////////////