Store num args instead of offset in prologue and func entry SrcKeys
[hiphop-php.git] / hphp / runtime / vm / jit / irgen-types.cpp
blob3a94a09b7b274996c1965631bab3d22f95f905fb
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"
24 #include "hphp/runtime/vm/super-inlining-bros.h"
26 #include "hphp/runtime/vm/jit/is-type-struct-profile.h"
27 #include "hphp/runtime/vm/jit/guard-constraint.h"
28 #include "hphp/runtime/vm/jit/target-profile.h"
29 #include "hphp/runtime/vm/jit/decref-profile.h"
30 #include "hphp/runtime/vm/jit/type.h"
32 #include "hphp/runtime/vm/jit/ir-opcode.h"
33 #include "hphp/runtime/vm/jit/irgen-exit.h"
34 #include "hphp/runtime/vm/jit/irgen-interpone.h"
35 #include "hphp/runtime/vm/jit/irgen-builtin.h"
37 #include "hphp/runtime/vm/jit/irgen-internal.h"
39 namespace HPHP::jit::irgen {
41 namespace {
43 //////////////////////////////////////////////////////////////////////
45 const StaticString
46 s_StringishObject("StringishObject"),
47 s_Awaitable("HH\\Awaitable"),
48 s_CLASS_TO_STRING_IMPLICIT(Strings::CLASS_TO_STRING_IMPLICIT),
49 s_CLASS_TO_CLASSNAME(Strings::CLASS_TO_CLASSNAME);
51 //////////////////////////////////////////////////////////////////////
54 * Returns a {Cls|Nullptr} suitable for use in instance checks.
56 SSATmp* ldClassSafe(IRGS& env, const StringData* className) {
57 if (auto const knownCls = lookupUniqueClass(env, className)) {
58 return cns(env, knownCls);
61 return cond(
62 env,
63 [&] (Block* taken) {
64 return gen(env, LdClsCachedSafe, taken, cns(env, className));
66 [&] (SSATmp* cls) { // next
67 return cls;
69 [&] { // taken
70 hint(env, Block::Hint::Unlikely);
71 return cns(env, nullptr);
77 * Returns a Bool value indicating if src (which must be <= TObj) is an
78 * instance of the class given in className, or nullptr if we don't have an
79 * efficient translation of the required check. checkCls must be the TCls for
80 * className (but it doesn't have to be constant).
82 SSATmp* implInstanceCheck(IRGS& env, SSATmp* src, const StringData* className,
83 SSATmp* checkCls) {
84 assertx(src->isA(TObj));
85 if (s_Awaitable.get()->isame(className)) {
86 return gen(env, IsWaitHandle, src);
88 if (s_StringishObject.get()->isame(className)) {
89 return gen(env, HasToString, src);
92 auto knownCls = checkCls->hasConstVal(TCls) ? checkCls->clsVal() : nullptr;
93 assertx(IMPLIES(knownCls, classIsUniqueOrCtxParent(env, knownCls)));
94 assertx(IMPLIES(knownCls, knownCls->name()->isame(className)));
96 auto const srcType = src->type();
99 * If the value is a specialized object type and we don't have to constrain a
100 * guard to get it, we can avoid emitting runtime checks if we know the
101 * result is true. If we don't know, we still have to emit a runtime check
102 * because src might be a subtype of the specialized type.
104 if (srcType < TObj && srcType.clsSpec()) {
105 auto const cls = srcType.clsSpec().cls();
106 if (!env.irb->constrainValue(src, GuardConstraint(cls).setWeak()) &&
107 ((knownCls && cls->classof(knownCls)) ||
108 cls->name()->isame(className))) {
109 return cns(env, true);
113 // Every case after this point requires knowing things about knownCls.
114 if (knownCls == nullptr) return nullptr;
116 auto const ssaClassName = cns(env, className);
117 auto const objClass = gen(env, LdObjClass, src);
119 if (env.context.kind == TransKind::Profile && !InstanceBits::initted()) {
120 gen(env, ProfileInstanceCheck, cns(env, className));
121 } else if (env.context.kind == TransKind::Optimize ||
122 InstanceBits::initted()) {
123 InstanceBits::init();
124 if (InstanceBits::lookup(className) != 0) {
125 return gen(env, InstanceOfBitmask, objClass, ssaClassName);
129 // If the class is an interface, we can just hit the class's vtable or
130 // interface map and call it a day.
131 if (isInterface(knownCls)) {
132 auto const slot = knownCls->preClass()->ifaceVtableSlot();
133 if (slot != kInvalidSlot) {
134 assertx(RO::RepoAuthoritative);
135 return gen(env,
136 InstanceOfIfaceVtable,
137 InstanceOfIfaceVtableData{knownCls, true},
138 objClass);
141 return gen(env, InstanceOfIface, objClass, ssaClassName);
144 // If knownCls isn't a normal class, our caller may want to do something
145 // different.
146 return isNormalClass(knownCls) ?
147 gen(env, ExtendsClass, ExtendsClassData{ knownCls }, objClass) : nullptr;
150 constexpr size_t kNumDataTypes = 17;
151 constexpr std::array<DataType, kNumDataTypes> computeDataTypes() {
152 std::array<DataType, kNumDataTypes> result = {};
153 size_t index = 0;
154 #define DT(name, value, ...) { \
155 auto constexpr dt = KindOf##name; \
156 if (dt == dt_modulo_persistence(dt)) result[index++] = dt; \
158 DATATYPES
159 #undef DT
160 #ifdef __clang__
161 always_assert(index == kNumDataTypes);
162 #endif
163 return result;
166 constexpr std::array<DataType, kNumDataTypes> kDataTypes = computeDataTypes();
169 * Emit a type-check for the given type-constraint. Since the details can vary
170 * quite a bit depending on what the type-constraint represents, this function
171 * is heavily templatized.
173 * The lambda parameters are as follows:
175 * - GetVal: Return the SSATmp of the value to test
176 * - GetThisCls: Return the SSATmp of the the class of `this'
177 * - SetVal: Emit code to update the value with the coerced value.
178 * - Fail: Emit code to deal with the type check failing.
179 * - Callable: Emit code to verify that the given value is callable.
180 * - VerifyCls: Emit code to verify that the given value is an instance of the
181 * given Class.
182 * - Fallback: Called when the type check cannot be resolved statically.
183 * Call a runtime helper to do the check.
185 template <typename TGetVal,
186 typename TGetThisCls,
187 typename TSetVal,
188 typename TFail,
189 typename TCallable,
190 typename TVerifyCls,
191 typename TFallback>
192 void verifyTypeImpl(IRGS& env,
193 const TypeConstraint& tc,
194 bool onlyCheckNullability,
195 TGetVal getVal,
196 TGetThisCls getThisCls,
197 TSetVal setVal,
198 TFail fail,
199 TCallable callable,
200 TVerifyCls verifyCls,
201 TFallback fallback) {
203 if (!tc.isCheckable()) return;
204 assertx(!tc.isUpperBound() || RuntimeOption::EvalEnforceGenericsUB != 0);
206 auto const genThisCls = [&]() {
207 return tc.isThis() ? getThisCls() : cns(env, nullptr);
210 auto const genFail = [&](SSATmp* val, SSATmp* thisCls = nullptr) {
211 if (thisCls == nullptr) thisCls = genThisCls();
213 auto const failHard = RuntimeOption::RepoAuthoritative
214 && !tc.isSoft()
215 && (!tc.isThis() || !tc.couldSeeMockObject())
216 && (!tc.isUpperBound() || RuntimeOption::EvalEnforceGenericsUB >= 2);
217 return fail(val, thisCls, failHard);
220 auto const checkOneType = [&](SSATmp* val, AnnotAction result) {
221 assertx(val->type().isKnownDataType());
223 switch (result) {
224 case AnnotAction::Pass:
225 return;
227 case AnnotAction::Fail:
228 genFail(val);
229 return;
231 case AnnotAction::Fallback:
232 fallback(val, genThisCls(), false);
233 return;
235 case AnnotAction::FallbackCoerce:
236 setVal(fallback(val, genThisCls(), true));
237 return;
239 case AnnotAction::CallableCheck:
240 callable(val);
241 return;
243 case AnnotAction::ObjectCheck:
244 break;
246 case AnnotAction::WarnClass:
247 case AnnotAction::ConvertClass:
248 assertx(val->type() <= TCls);
249 setVal(gen(env, LdClsName, val));
250 if (result == AnnotAction::WarnClass) {
251 gen(env, RaiseNotice, cns(env, s_CLASS_TO_STRING_IMPLICIT.get()));
253 return;
255 case AnnotAction::WarnLazyClass:
256 case AnnotAction::ConvertLazyClass:
257 assertx(val->type() <= TLazyCls);
258 setVal(gen(env, LdLazyClsName, val));
259 if (result == AnnotAction::WarnLazyClass) {
260 gen(env, RaiseNotice, cns(env, s_CLASS_TO_STRING_IMPLICIT.get()));
262 return;
264 case AnnotAction::WarnClassname:
265 assertx(val->type() <= TCls || val->type() <= TLazyCls);
266 gen(env, RaiseNotice, cns(env, s_CLASS_TO_CLASSNAME.get()));
267 return;
269 assertx(result == AnnotAction::ObjectCheck);
270 assertx(val->type() <= TObj);
271 if (onlyCheckNullability) return;
273 // At this point, we know that val is a TObj.
274 if (tc.isThis()) {
275 // For this type checks, the class needs to be an exact match.
276 auto const thisCls = getThisCls();
277 auto const objClass = gen(env, LdObjClass, val);
278 ifThen(
279 env,
280 [&] (Block* taken) {
281 gen(env, JmpZero, taken, gen(env, EqCls, thisCls, objClass));
283 [&] {
284 hint(env, Block::Hint::Unlikely);
285 genFail(val, thisCls);
288 return;
291 // At this point, we know that val is a TObj and that tc is an Object.
292 assertx(tc.isObject() || tc.isUnresolved());
293 auto const clsName = tc.isObject() ? tc.clsName() : tc.typeName();
294 auto const checkCls = ldClassSafe(env, clsName);
295 auto const fastIsInstance = implInstanceCheck(env, val, clsName, checkCls);
296 if (fastIsInstance) {
297 ifThen(
298 env,
299 [&] (Block* taken) {
300 gen(env, JmpZero, taken, fastIsInstance);
302 [&] {
303 hint(env, Block::Hint::Unlikely);
304 genFail(val);
307 return;
310 verifyCls(val, checkCls);
313 auto const genericVal = getVal();
314 assertx(genericVal->type() <= TCell);
315 auto const genericValType = genericVal->type();
317 auto const computeAction = [&](DataType dt) {
318 if (dt == KindOfNull && tc.isNullable()) return AnnotAction::Pass;
319 auto const name = tc.isObject() ? tc.clsName() : tc.typeName();
320 return annotCompat(dt, tc.type(), name);
323 if (genericValType.isKnownDataType()) {
324 auto const dt = genericValType.toDataType();
325 return checkOneType(genericVal, computeAction(dt));
328 enum { None, Fail, Fallback, FallbackCoerce } fallbackAction = None;
330 auto const options = [&]{
331 TinyVector<std::pair<DataType, AnnotAction>, kNumDataTypes> result;
332 for (auto const dt : kDataTypes) {
333 auto const type = Type(dt);
334 if (!genericValType.maybe(type)) continue;
335 auto const action = computeAction(dt);
336 switch (action) {
337 case AnnotAction::Fail:
338 fallbackAction = std::max(fallbackAction, Fail);
339 break;
340 case AnnotAction::Fallback:
341 fallbackAction = std::max(fallbackAction, Fallback);
342 break;
343 case AnnotAction::FallbackCoerce:
344 fallbackAction = std::max(fallbackAction, FallbackCoerce);
345 break;
346 default:
347 result.emplace_back(dt, action);
348 break;
351 return result;
352 }();
354 if (fallbackAction == None &&
355 std::all_of(options.begin(), options.end(), [] (auto const& pair) {
356 return pair.second == AnnotAction::Pass;
357 })) {
358 return;
361 // TODO(kshaunak): If we were a bit more sophisticated here, we could
362 // merge the cases for certain types, like TVec|TDict, or TArrLike.
363 MultiCond mc{env};
364 for (auto const& pair : options) {
365 mc.ifTypeThen(genericVal, Type(pair.first), [&](SSATmp* val) {
366 checkOneType(val, pair.second);
367 return cns(env, TBottom);
370 mc.elseDo([&]{
371 switch (fallbackAction) {
372 case None:
373 gen(env, Unreachable, ASSERT_REASON);
374 break;
375 case Fail:
376 genFail(genericVal);
377 break;
378 case Fallback:
379 fallback(genericVal, genThisCls(), false);
380 break;
381 case FallbackCoerce:
382 setVal(fallback(genericVal, genThisCls(), true));
383 break;
385 return cns(env, TBottom);
389 Type typeOpToType(IsTypeOp op) {
390 switch (op) {
391 case IsTypeOp::Null: return TInitNull;
392 case IsTypeOp::Int: return TInt;
393 case IsTypeOp::Dbl: return TDbl;
394 case IsTypeOp::Bool: return TBool;
395 case IsTypeOp::Str: return TStr;
396 case IsTypeOp::Keyset: return TKeyset;
397 case IsTypeOp::Obj: return TObj;
398 case IsTypeOp::Res: return TRes;
399 case IsTypeOp::ClsMeth:
400 case IsTypeOp::Func:
401 case IsTypeOp::Class:
402 case IsTypeOp::Vec:
403 case IsTypeOp::Dict:
404 case IsTypeOp::ArrLike:
405 case IsTypeOp::LegacyArrLike:
406 case IsTypeOp::Scalar: not_reached();
408 not_reached();
411 SSATmp* isScalarImpl(IRGS& env, SSATmp* val) {
412 // The simplifier works fine when val has a known DataType, but do some
413 // checks first in case val has a type like {Int|Str}.
414 auto const scalar = TBool | TInt | TDbl | TStr | TCls | TLazyCls;
415 if (val->isA(scalar)) return cns(env, true);
416 if (!val->type().maybe(scalar)) return cns(env, false);
418 SSATmp* result = nullptr;
419 for (auto t : {TBool, TInt, TDbl, TStr, TCls, TLazyCls}) {
420 auto const is_t = gen(env, ConvBoolToInt, gen(env, IsType, t, val));
421 result = result ? gen(env, OrInt, result, is_t) : is_t;
423 return gen(env, ConvIntToBool, result);
426 const StaticString s_FUNC_CONVERSION(Strings::FUNC_TO_STRING);
427 const StaticString s_FUNC_IS_STRING("Func used in is_string");
428 const StaticString s_CLASS_CONVERSION(Strings::CLASS_TO_STRING);
429 const StaticString s_CLASS_IS_STRING("Class used in is_string");
430 const StaticString s_TYPE_STRUCT_NOT_DARR("Type-structure is not a darray");
432 SSATmp* isStrImpl(IRGS& env, SSATmp* src) {
433 MultiCond mc{env};
435 mc.ifTypeThen(src, TStr, [&](SSATmp*) { return cns(env, true); });
437 mc.ifTypeThen(src, TLazyCls, [&](SSATmp*) {
438 if (RuntimeOption::EvalClassIsStringNotices) {
439 gen(env, RaiseNotice, cns(env, s_CLASS_IS_STRING.get()));
441 return cns(env, true);
444 mc.ifTypeThen(src, TCls, [&](SSATmp*) {
445 if (RuntimeOption::EvalClassIsStringNotices) {
446 gen(env, RaiseNotice, cns(env, s_CLASS_IS_STRING.get()));
448 return cns(env, true);
451 return mc.elseDo([&]{ return cns(env, false); });
454 SSATmp* isClassImpl(IRGS& env, SSATmp* src) {
455 MultiCond mc{env};
456 mc.ifTypeThen(src, TLazyCls, [&](SSATmp*) { return cns(env, true); });
457 mc.ifTypeThen(src, TCls, [&](SSATmp*) { return cns(env, true); });
458 return mc.elseDo([&]{ return cns(env, false); });
461 SSATmp* isFuncImpl(IRGS& env, SSATmp* src) {
462 MultiCond mc{env};
463 mc.ifTypeThen(src, TFunc, [&](SSATmp* func) {
464 auto const attr = AttrData { AttrIsMethCaller };
465 auto const isMC = gen(env, FuncHasAttr, attr, func);
466 return gen(env, EqBool, isMC, cns(env, false));
468 mc.ifTypeThen(src, TRFunc, [&](SSATmp*) { return cns(env, true); });
469 return mc.elseDo([&]{ return cns(env, false); });
472 SSATmp* isClsMethImpl(IRGS& env, SSATmp* src) {
473 MultiCond mc{env};
474 mc.ifTypeThen(src, TClsMeth, [&](SSATmp*) { return cns(env, true); });
475 mc.ifTypeThen(src, TRClsMeth, [&](SSATmp*) { return cns(env, true); });
476 return mc.elseDo([&]{ return cns(env, false); });
479 SSATmp* isVecImpl(IRGS& env, SSATmp* src) {
480 MultiCond mc{env};
482 mc.ifTypeThen(src, TVec, [&](SSATmp* src) {
483 return cns(env, true);
486 return mc.elseDo([&]{ return cns(env, false); });
489 SSATmp* isDictImpl(IRGS& env, SSATmp* src) {
490 MultiCond mc{env};
492 mc.ifTypeThen(src, TDict, [&](SSATmp* src) {
493 return cns(env, true);
496 return mc.elseDo([&]{ return cns(env, false); });
499 SSATmp* isArrLikeImpl(IRGS& env, SSATmp* src) {
500 MultiCond mc{env};
501 return mc.elseDo([&]{ return gen(env, IsType, TArrLike, src); });
504 SSATmp* isLegacyArrLikeImpl(IRGS& env, SSATmp* src) {
505 MultiCond mc{env};
507 mc.ifTypeThen(src, TVec|TDict, [&](SSATmp* src) {
508 return gen(env, IsLegacyArrLike, src);
511 return mc.elseDo([&]{ return cns(env, false); });
514 //////////////////////////////////////////////////////////////////////
518 SSATmp* implInstanceOfD(IRGS& env, SSATmp* src, const StringData* className) {
520 * InstanceOfD is always false if it's not an object.
522 * We're prepared to generate translations for known non-object types, but if
523 * it's Gen/Cell we're going to PUNT because it's natural to translate that
524 * case with control flow TODO(#16781576)
526 if (TObj < src->type()) {
527 PUNT(InstanceOfD_MaybeObj);
529 if (!src->isA(TObj)) {
530 if (src->isA(TCls | TLazyCls)) {
531 if (!interface_supports_string(className)) return cns(env, false);
532 if (RuntimeOption::EvalClassIsStringNotices && src->isA(TCls)) {
533 gen(
534 env,
535 RaiseNotice,
536 cns(env, s_CLASS_IS_STRING.get())
539 return cns(env, true);
542 auto const res =
543 (src->isA(TArrLike) && interface_supports_arrlike(className)) ||
544 (src->isA(TStr) && interface_supports_string(className)) ||
545 (src->isA(TInt) && interface_supports_int(className)) ||
546 (src->isA(TDbl) && interface_supports_double(className));
547 return cns(env, res);
550 auto const checkCls = ldClassSafe(env, className);
551 if (auto isInstance = implInstanceCheck(env, src, className, checkCls)) {
552 return isInstance;
555 return gen(env, InstanceOf, gen(env, LdObjClass, src), checkCls);
558 //////////////////////////////////////////////////////////////////////
560 void emitInstanceOfD(IRGS& env, const StringData* className) {
561 auto const src = popC(env);
562 push(env, implInstanceOfD(env, src, className));
563 decRef(env, src, DecRefProfileId::Default);
566 void emitInstanceOf(IRGS& env) {
567 auto const t1 = popC(env);
568 auto const t2 = popC(env); // t2 instanceof t1
570 if (t1->isA(TObj) && t2->isA(TObj)) {
571 auto const c2 = gen(env, LdObjClass, t2);
572 auto const c1 = gen(env, LdObjClass, t1);
573 push(env, gen(env, InstanceOf, c2, c1));
574 decRef(env, t2, DecRefProfileId::InstanceOfSrc2);
575 decRef(env, t1, DecRefProfileId::InstanceOfSrc1);
576 return;
579 if (!t1->isA(TStr)) PUNT(InstanceOf-NotStr);
581 if (t2->isA(TObj)) {
582 auto const c1 = gen(env, LookupClsRDS, t1);
583 auto const c2 = gen(env, LdObjClass, t2);
584 push(env, gen(env, InstanceOf, c2, c1));
585 decRef(env, t2, DecRefProfileId::InstanceOfSrc2);
586 decRef(env, t1, DecRefProfileId::InstanceOfSrc1);
587 return;
590 auto const res = [&]() -> SSATmp* {
591 if (t2->isA(TArrLike)) return gen(env, InterfaceSupportsArrLike, t1);
592 if (t2->isA(TInt)) return gen(env, InterfaceSupportsInt, t1);
593 if (t2->isA(TStr)) return gen(env, InterfaceSupportsStr, t1);
594 if (t2->isA(TDbl)) return gen(env, InterfaceSupportsDbl, t1);
595 if (t2->isA(TCls)) {
596 if (!RO::EvalRaiseClassConversionWarning) {
597 return gen(env, InterfaceSupportsStr, t1);
599 return cond(
600 env,
601 [&] (Block* taken) {
602 gen(env, JmpZero, taken, gen(env, InterfaceSupportsStr, t1));
604 [&] {
605 gen(env, RaiseNotice, cns(env, s_CLASS_CONVERSION.get()));
606 return cns(env, true);
608 [&] { return cns(env, false); }
611 if (!t2->type().maybe(TObj|TArrLike|TInt|TStr|TDbl)) return cns(env, false);
612 return nullptr;
613 }();
615 if (!res) PUNT(InstanceOf-Unknown);
617 push(env, res);
618 decRef(env, t2, DecRefProfileId::InstanceOfSrc2);
619 decRef(env, t1, DecRefProfileId::InstanceOfSrc1);
622 void emitIsLateBoundCls(IRGS& env) {
623 auto const cls = curClass(env);
624 if (!cls) PUNT(IsLateBoundCls-NoClassContext);
625 if (isTrait(cls)) PUNT(IsLateBoundCls-Trait);
626 auto const obj = popC(env);
627 if (obj->isA(TObj)) {
628 auto const rhs = ldCtxCls(env);
629 auto const lhs = gen(env, LdObjClass, obj);
630 push(env, gen(env, InstanceOf, lhs, rhs));
631 } else if (!obj->type().maybe(TObj)) {
632 push(env, cns(env, false));
633 } else {
634 PUNT(IsLateBoundCls-MaybeObject);
636 decRef(env, obj, DecRefProfileId::Default);
639 namespace {
641 template<typename F>
642 SSATmp* resolveTypeStructureAndCacheInRDS(
643 IRGS& env,
644 F resolveTypeStruct,
645 bool typeStructureCouldBeNonStatic
647 if (typeStructureCouldBeNonStatic) return resolveTypeStruct();
648 auto const handle = rds::alloc<TypedValue>().handle();
649 auto const data = RDSHandleAndType { handle, TDict };
650 auto const addr = gen(env, LdRDSAddr, data, TPtrToOther);
651 ifThen(
652 env,
653 [&] (Block* taken) {
654 gen(env, CheckRDSInitialized, taken, RDSHandleData { handle });
656 [&] {
657 hint(env, Block::Hint::Unlikely);
658 gen(env, StMem, addr, resolveTypeStruct());
659 gen(env, MarkRDSInitialized, RDSHandleData { handle });
662 return gen(env, LdMem, TDict, addr);
665 SSATmp* resolveTypeStructImpl(
666 IRGS& env,
667 bool typeStructureCouldBeNonStatic,
668 bool suppress,
669 uint32_t n,
670 bool isOrAsOp
672 auto const declaringCls = curFunc(env) ? curClass(env) : nullptr;
673 auto const calledCls =
674 declaringCls && typeStructureCouldBeNonStatic
675 ? ldCtxCls(env)
676 : cns(env, nullptr);
677 auto const result = resolveTypeStructureAndCacheInRDS(
678 env,
679 [&] {
680 return gen(
681 env,
682 ResolveTypeStruct,
683 ResolveTypeStructData {
684 declaringCls,
685 suppress,
686 spOffBCFromIRSP(env),
687 static_cast<uint32_t>(n),
688 isOrAsOp
690 sp(env),
691 calledCls
694 typeStructureCouldBeNonStatic
696 popC(env);
697 discard(env, n - 1);
698 return result;
701 const ArrayData* staticallyResolveTypeStructure(
702 IRGS& env,
703 const ArrayData* ts,
704 bool& partial,
705 bool& invalidType
707 auto const declaringCls = curFunc(env) ? curClass(env) : nullptr;
708 bool persistent = false;
709 // This shouldn't do a difference, but does on GCC 8.3 on Ubuntu 19.04;
710 // if we take the catch then return `ts`, it's a bogus value and we
711 // segfault... sometimes...
712 const ArrayData* ts_copy = ts;
713 try {
714 auto newTS = TypeStructure::resolvePartial(
715 ArrNR(ts), nullptr, declaringCls, persistent, partial, invalidType);
716 if (persistent) return ArrayData::GetScalarArray(std::move(newTS));
717 } catch (Exception& e) {}
718 // We are here because either we threw in the resolution or it wasn't
719 // persistent resolution which means we didn't really resolve it
720 partial = true;
721 return ts_copy;
724 SSATmp* check_nullable(IRGS& env, SSATmp* res, SSATmp* var) {
725 return cond(
726 env,
727 [&] (Block* taken) { gen(env, JmpNZero, taken, res); },
728 [&] { return gen(env, IsType, TNull, var); },
729 [&] { return cns(env, true); }
733 void chain_is_type(IRGS& env, SSATmp* c, bool nullable, Type ty) {
734 always_assert(false);
737 template<typename... Types>
738 void chain_is_type(IRGS& env, SSATmp* c, bool nullable,
739 Type ty1, Type ty2, Types&&... rest) {
740 ifThenElse(
741 env,
742 [&](Block* taken) {
743 auto const res = gen(env, IsType, ty1, c);
744 gen(env, JmpNZero, taken, res);
746 [&] {
747 if (sizeof...(rest) == 0) {
748 auto const res = gen(env, IsType, ty2, c);
749 push(env, nullable ? check_nullable(env, res, c) : res);
750 } else {
751 chain_is_type(env, c, nullable, ty2, rest...);
754 [&] { // taken block
755 push(env, cns(env, true));
761 * This function tries to emit is type struct operations without resolving
762 * the type structure when that's possible.
763 * When it returns true, it has popped two values from the stack, namely the
764 * type structure and the cell, and pushed one value back to stack, namely
765 * true/false if it is an is-operation or the cell if it is an as operation.
766 * This function does not modify the reference counts of these stack values,
767 * leaving that responsibility to the caller.
768 * When it returns false, it does not modify anything.
770 bool emitIsTypeStructWithoutResolvingIfPossible(
771 IRGS& env,
772 const ArrayData* ts
774 // Top of the stack is the type structure, so the thing we are checking is
775 // the next element
776 auto const t = topC(env, BCSPRelOffset { 1 });
777 auto const is_nullable_ts = is_ts_nullable(ts);
779 auto const cnsResult = [&] (bool value) {
780 popC(env); // pop the ts that's on the stack
781 popC(env); // pop the cell
782 push(env, cns(env, value));
783 return true;
786 auto const success = [&] { return cnsResult(true); };
787 auto const fail = [&] { return cnsResult(false); };
789 auto const primitive = [&] (Type ty, bool should_negate = false) {
790 auto const nty = is_nullable_ts ? ty|TNull : ty;
791 if (t->isA(nty)) return should_negate ? fail() : success();
792 if (!t->type().maybe(nty)) return should_negate ? success() : fail();
793 popC(env); // pop the ts that's on the stack
794 auto const c = popC(env);
795 auto const res = gen(env, should_negate ? IsNType : IsType, ty, c);
796 push(env, is_nullable_ts ? check_nullable(env, res, c) : res);
797 return true;
800 // We explicitly bind is_nullable_ts because failing to do so causes a
801 // spurious compiler error on some g++ versions.
802 auto const unionOf = [&,is_nullable_ts] (Type ty1, Type ty2,
803 auto&&... rest) {
804 auto const ty = Type::unionAll(ty1, ty2, rest...) |
805 (is_nullable_ts ? TNull : TBottom);
806 if (t->isA(ty)) return success();
807 if (!t->type().maybe(ty)) return fail();
809 popC(env); // pop the ts that's on the stack
810 auto const c = popC(env);
811 chain_is_type(env, c, is_nullable_ts, ty1, ty2, rest...);
812 return true;
815 if (t->isA(TNull) && is_nullable_ts) return success();
817 auto kind = get_ts_kind(ts);
818 switch (kind) {
819 case TypeStructure::Kind::T_int: return primitive(TInt);
820 case TypeStructure::Kind::T_bool: return primitive(TBool);
821 case TypeStructure::Kind::T_float: return primitive(TDbl);
822 case TypeStructure::Kind::T_string: {
823 if (t->type().maybe(TLazyCls) &&
824 RuntimeOption::EvalClassIsStringNotices) {
825 ifElse(env,
826 [&] (Block* taken) {
827 gen(env, CheckType, TLazyCls, taken, t);
829 [&] {
830 gen(env, RaiseNotice, cns(env, s_CLASS_IS_STRING.get()));
834 if (t->type().maybe(TCls) &&
835 RuntimeOption::EvalClassIsStringNotices) {
836 ifElse(env,
837 [&] (Block* taken) {
838 gen(env, CheckType, TCls, taken, t);
840 [&] {
841 gen(env, RaiseNotice, cns(env, s_CLASS_IS_STRING.get()));
845 return unionOf(TStr, TLazyCls, TCls);
847 case TypeStructure::Kind::T_null: return primitive(TNull);
848 case TypeStructure::Kind::T_void: return primitive(TNull);
849 case TypeStructure::Kind::T_keyset: return primitive(TKeyset);
850 case TypeStructure::Kind::T_nonnull: return primitive(TNull, true);
851 case TypeStructure::Kind::T_mixed:
852 case TypeStructure::Kind::T_dynamic:
853 return success();
854 case TypeStructure::Kind::T_num: return unionOf(TInt, TDbl);
855 case TypeStructure::Kind::T_arraykey: {
856 if (t->type().maybe(TLazyCls) &&
857 RuntimeOption::EvalClassIsStringNotices) {
858 ifElse(env,
859 [&] (Block* taken) {
860 gen(env, CheckType, TLazyCls, taken, t);
862 [&] {
863 gen(env, RaiseNotice, cns(env, s_CLASS_IS_STRING.get()));
867 if (t->type().maybe(TCls) &&
868 RuntimeOption::EvalClassIsStringNotices) {
869 ifElse(env,
870 [&] (Block* taken) {
871 gen(env, CheckType, TCls, taken, t);
873 [&] {
874 gen(env, RaiseNotice, cns(env, s_CLASS_IS_STRING.get()));
878 return unionOf(TInt, TStr, TLazyCls, TCls);
880 case TypeStructure::Kind::T_any_array:
881 return unionOf(TVec, TDict, TKeyset);
882 case TypeStructure::Kind::T_vec_or_dict:
883 case TypeStructure::Kind::T_varray_or_darray:
884 case TypeStructure::Kind::T_dict:
885 case TypeStructure::Kind::T_vec:
886 case TypeStructure::Kind::T_darray:
887 case TypeStructure::Kind::T_varray: {
888 popC(env); // pop the ts that's on the stack
889 auto const c = popC(env);
890 auto const res = [&]{
891 if (kind == TypeStructure::Kind::T_dict ||
892 kind == TypeStructure::Kind::T_darray) {
893 return isDictImpl(env, c);
894 } else if (kind == TypeStructure::Kind::T_vec ||
895 kind == TypeStructure::Kind::T_varray) {
896 return isVecImpl(env, c);
897 } else {
898 assertx(kind == TypeStructure::Kind::T_vec_or_dict ||
899 kind == TypeStructure::Kind::T_varray_or_darray);
900 return cond(
901 env,
902 [&](Block* taken) { gen(env, JmpZero, taken, isVecImpl(env, c)); },
903 [&] { return cns(env, true); },
904 [&] { return isDictImpl(env, c); }
907 }();
908 push(env, is_nullable_ts ? check_nullable(env, res, c) : res);
909 return true;
911 case TypeStructure::Kind::T_class:
912 case TypeStructure::Kind::T_interface:
913 case TypeStructure::Kind::T_xhp: {
914 auto const clsname = get_ts_classname(ts);
915 auto cls = lookupUniqueClass(env, clsname);
916 if (ts->exists(s_generic_types) &&
917 ((classIsPersistentOrCtxParent(env, cls) &&
918 cls->hasReifiedGenerics()) ||
919 !isTSAllWildcards(ts))) {
920 // If it is a reified class or has non wildcard generics,
921 // we need to bail
922 return false;
924 popC(env); // pop the ts that's on the stack
925 auto const c = popC(env);
926 auto const res = implInstanceOfD(env, c, clsname);
927 push(env, is_nullable_ts ? check_nullable(env, res, c) : res);
928 return true;
930 case TypeStructure::Kind::T_nothing:
931 case TypeStructure::Kind::T_noreturn:
932 return fail();
933 case TypeStructure::Kind::T_typevar:
934 case TypeStructure::Kind::T_fun:
935 case TypeStructure::Kind::T_trait:
936 // Not supported, will throw an error on these at the resolution phase
937 return false;
938 case TypeStructure::Kind::T_enum:
939 case TypeStructure::Kind::T_tuple:
940 case TypeStructure::Kind::T_shape:
941 case TypeStructure::Kind::T_typeaccess:
942 case TypeStructure::Kind::T_unresolved:
943 case TypeStructure::Kind::T_resource:
944 case TypeStructure::Kind::T_reifiedtype:
945 // TODO(T28423611): Implement these
946 return false;
948 not_reached();
952 * shouldDefRef is set iff the resulting SSATmp is a newly allocated type
953 * structure
954 * This function does not modify the reference count of its inputs, leaving that
955 * to the caller
957 SSATmp* handleIsResolutionAndCommonOpts(
958 IRGS& env,
959 TypeStructResolveOp op,
960 bool& done,
961 bool& shouldDecRef,
962 bool& checkValid
964 auto const a = topC(env);
965 if (!a->isA(TDict)) PUNT(IsTypeStructC-NotArrayTypeStruct);
966 if (!a->hasConstVal(TDict)) {
967 if (op == TypeStructResolveOp::Resolve) {
968 return resolveTypeStructImpl(env, true, true, 1, true);
970 shouldDecRef = false;
971 checkValid = true;
972 return popC(env);
974 auto const ts = a->arrLikeVal();
975 auto maybe_resolved = ts;
976 bool partial = true;
977 bool invalidType = true;
978 if (op == TypeStructResolveOp::Resolve) {
979 maybe_resolved =
980 staticallyResolveTypeStructure(env, ts, partial, invalidType);
981 shouldDecRef = maybe_resolved != ts;
983 if (emitIsTypeStructWithoutResolvingIfPossible(env, maybe_resolved)) {
984 done = true;
985 return nullptr;
987 if (op == TypeStructResolveOp::Resolve && (partial || invalidType)) {
988 shouldDecRef = true;
989 return resolveTypeStructImpl(
990 env, typeStructureCouldBeNonStatic(ts), true, 1, true);
992 popC(env);
993 if (op == TypeStructResolveOp::DontResolve) checkValid = true;
994 return cns(env, maybe_resolved);
997 } // namespace
999 void emitIsTypeStructC(IRGS& env, TypeStructResolveOp op) {
1000 auto const a = topC(env);
1001 auto const c = topC(env, BCSPRelOffset { 1 });
1002 bool done = false, shouldDecRef = true, checkValid = false;
1003 SSATmp* tc =
1004 handleIsResolutionAndCommonOpts(env, op, done, shouldDecRef, checkValid);
1005 if (done) {
1006 decRef(env, c, DecRefProfileId::IsTypeStructCc);
1007 decRef(env, a, DecRefProfileId::IsTypeStructCa);
1008 return;
1010 popC(env);
1011 auto block = opcodeMayRaise(IsTypeStruct) && shouldDecRef
1012 ? create_catch_block(env, [&]{
1013 decRef(env, tc, DecRefProfileId::IsTypeStructCTc);
1015 : nullptr;
1016 auto const data = RDSHandleData { rds::bindTSCache(curFunc(env)).handle() };
1018 static const StaticString s_IsTypeStruct{"IsTypeStruct"};
1019 auto const profile = TargetProfile<IsTypeStructProfile> {
1020 env.context,
1021 env.irb->curMarker(),
1022 s_IsTypeStruct.get()
1025 auto const generic = [&] {
1026 if (checkValid) gen(env, RaiseErrorOnInvalidIsAsExpressionType, tc);
1027 return gen(env, IsTypeStruct, block, data, tc, c);
1030 auto const finish = [&] (SSATmp* result) {
1031 push(env, result);
1032 decRef(env, c, DecRefProfileId::IsTypeStructCc);
1033 decRef(env, a, DecRefProfileId::IsTypeStructCa);
1036 if (profile.profiling()) {
1037 gen(env, ProfileIsTypeStruct, RDSHandleData { profile.handle() }, a);
1038 finish(generic());
1039 return;
1042 if (!profile.optimizing() || !profile.data().shouldOptimize()) {
1043 finish(generic());
1044 return;
1047 finish(cond(
1048 env,
1049 [&] (Block* taken) {
1050 return gen(env, IsTypeStructCached, taken, a, c);
1052 [&] (SSATmp* result) { // next
1053 return result;
1055 [&] { // taken
1056 hint(env, Block::Hint::Unlikely);
1057 return generic();
1062 void emitThrowAsTypeStructException(IRGS& env) {
1063 auto const arr = topC(env);
1064 auto const c = topC(env, BCSPRelOffset { 1 });
1065 auto const tsAndBlock = [&]() -> std::pair<SSATmp*, Block*> {
1066 if (arr->hasConstVal(TDict)) {
1067 auto const ts = arr->arrLikeVal();
1068 auto maybe_resolved = ts;
1069 bool partial = true, invalidType = true;
1070 maybe_resolved =
1071 staticallyResolveTypeStructure(env, ts, partial, invalidType);
1072 if (!ts->same(maybe_resolved)) {
1073 auto const inputTS = cns(env, maybe_resolved);
1074 return {inputTS, create_catch_block(
1075 env, [&]{ decRef(env, inputTS, DecRefProfileId::Default); })};
1078 auto const ts = resolveTypeStructImpl(env, true, false, 1, true);
1079 return {ts, nullptr};
1080 }();
1081 // No need to decref inputs as this instruction will throw
1082 gen(env, ThrowAsTypeStructException, tsAndBlock.second, tsAndBlock.first, c);
1085 void emitRecordReifiedGeneric(IRGS& env) {
1086 auto const ts = popC(env);
1087 if (!ts->isA(TVec)) {
1088 PUNT(RecordReifiedGeneric-InvalidTS);
1090 // RecordReifiedGenericsAndGetTSList decrefs the ts
1091 auto const result = gen(env, RecordReifiedGenericsAndGetTSList, ts);
1092 push(env, result);
1095 void emitCombineAndResolveTypeStruct(IRGS& env, uint32_t n) {
1096 push(env, resolveTypeStructImpl(env, true, false, n, false));
1099 void raiseClsmethCompatTypeHint(
1100 IRGS& env, int32_t id, const Func* func, const TypeConstraint& tc) {
1101 auto name = tc.displayName(func->cls());
1102 if (id == TypeConstraint::ReturnId) {
1103 gen(env, RaiseNotice, cns(env, makeStaticString(
1104 folly::sformat("class_meth Compat: Value returned from function {}() "
1105 "must be of type {}, clsmeth given",
1106 func->fullName(), name))));
1107 } else {
1108 gen(env, RaiseNotice, cns(env, makeStaticString(
1109 folly::sformat("class_meth Compat: Argument {} passed to {}() "
1110 "must be of type {}, clsmeth given",
1111 id + 1, func->fullName(), name))));
1115 namespace {
1117 void verifyRetTypeImpl(IRGS& env, int32_t id, int32_t ind,
1118 bool onlyCheckNullability) {
1119 auto const func = curFunc(env);
1120 auto const verifyFunc = [&] (const TypeConstraint& tc) {
1121 verifyTypeImpl(
1122 env,
1124 onlyCheckNullability,
1125 [&] { // Get value to test
1126 return topC(env, BCSPRelOffset { ind });
1128 [&] { // Get the class representing `this' type
1129 return ldCtxCls(env);
1131 [&] (SSATmp* updated) { // Set the potentially coerced value
1132 auto const offset = offsetFromIRSP(env, BCSPRelOffset { ind });
1133 gen(env, StStk, IRSPRelOffsetData{offset}, sp(env), updated);
1134 env.irb->exceptionStackBoundary();
1136 [&] (SSATmp* val, SSATmp* thisCls, bool hard) { // Check failure
1137 updateMarker(env);
1138 env.irb->exceptionStackBoundary();
1139 gen(
1140 env,
1141 hard ? VerifyRetFailHard : VerifyRetFail,
1142 FuncParamWithTCData { func, id, &tc },
1143 val,
1144 thisCls
1147 [&] (SSATmp* val) { // Callable check
1148 gen(
1149 env,
1150 VerifyRetCallable,
1151 FuncParamData { func, id },
1155 [&] (SSATmp* val, SSATmp* checkCls) {
1156 // Class/type-alias check
1157 gen(
1158 env,
1159 VerifyRetCls,
1160 FuncParamWithTCData { func, id, &tc },
1161 val,
1162 gen(env, LdObjClass, val),
1163 checkCls
1166 [&] (SSATmp* val, SSATmp* thisCls, bool mayCoerce) { // Fallback
1167 return gen(
1168 env,
1169 mayCoerce ? VerifyRetCoerce : VerifyRet,
1170 FuncParamWithTCData { func, id, &tc },
1171 val,
1172 thisCls
1177 auto const& tc = (id == TypeConstraint::ReturnId)
1178 ? func->returnTypeConstraint()
1179 : func->params()[id].typeConstraint;
1180 assertx(ind >= 0);
1181 verifyFunc(tc);
1182 if (id == TypeConstraint::ReturnId && func->hasReturnWithMultiUBs()) {
1183 auto& ubs = const_cast<Func::UpperBoundVec&>(func->returnUBs());
1184 for (auto& ub : ubs) {
1185 applyFlagsToUB(ub, tc);
1186 verifyFunc(ub);
1188 } else if (func->hasParamsWithMultiUBs()) {
1189 auto& ubs = const_cast<Func::ParamUBMap&>(func->paramUBs());
1190 auto it = ubs.find(id);
1191 if (it != ubs.end()) {
1192 for (auto& ub : it->second) {
1193 applyFlagsToUB(ub, tc);
1194 verifyFunc(ub);
1200 void verifyParamTypeImpl(IRGS& env, int32_t id) {
1201 auto const func = curFunc(env);
1202 auto const verifyFunc = [&](const TypeConstraint& tc) {
1203 verifyTypeImpl(
1204 env,
1206 false,
1207 [&] { // Get value to test
1208 return ldLoc(env, id, DataTypeSpecific);
1210 [&] { // Get the class representing `this' type
1211 return ldCtxCls(env);
1213 [&] (SSATmp* updated) { // Set the potentially coerced value
1214 stLocRaw(env, id, fp(env), updated);
1216 [&] (SSATmp* val, SSATmp* thisCls, bool hard) { // Check failure
1217 gen(
1218 env,
1219 hard ? VerifyParamFailHard : VerifyParamFail,
1220 FuncParamWithTCData { func, id, &tc },
1221 val,
1222 thisCls
1225 [&] (SSATmp* val) { // Callable check
1226 gen(
1227 env,
1228 VerifyParamCallable,
1229 FuncParamData { func, id },
1233 [&] (SSATmp* val, SSATmp* checkCls) {
1234 // Class/type-alias check
1235 gen(
1236 env,
1237 VerifyParamCls,
1238 FuncParamWithTCData { func, id, &tc },
1239 val,
1240 gen(env, LdObjClass, val),
1241 checkCls
1244 [&] (SSATmp* val, SSATmp* thisCls, bool mayCoerce) { // Fallback
1245 return gen(
1246 env,
1247 mayCoerce ? VerifyParamCoerce : VerifyParam,
1248 FuncParamWithTCData { func, id, &tc },
1249 val,
1250 thisCls
1255 auto const& tc = func->params()[id].typeConstraint;
1256 verifyFunc(tc);
1257 if (func->hasParamsWithMultiUBs()) {
1258 auto& ubs = const_cast<Func::ParamUBMap&>(func->paramUBs());
1259 auto it = ubs.find(id);
1260 if (it != ubs.end()) {
1261 for (auto& ub : it->second) {
1262 applyFlagsToUB(ub, tc);
1263 verifyFunc(ub);
1271 void verifyPropType(IRGS& env,
1272 SSATmp* cls,
1273 const HPHP::TypeConstraint* tc,
1274 const Class::UpperBoundVec* ubs,
1275 Slot slot,
1276 SSATmp* val,
1277 SSATmp* name,
1278 bool isSProp,
1279 SSATmp** coerce /* = nullptr */) {
1280 assertx(cls->isA(TCls));
1281 assertx(val->isA(TCell));
1283 if (coerce) *coerce = val;
1284 if (RuntimeOption::EvalCheckPropTypeHints <= 0) return;
1286 auto const verifyFunc = [&](const TypeConstraint* tc) {
1287 if (!tc || !tc->isCheckable()) return;
1288 assertx(tc->validForProp());
1290 auto const fallback = [&](SSATmp* val, SSATmp*, bool mayCoerce) {
1291 return gen(
1292 env,
1293 mayCoerce ? VerifyPropCoerce : VerifyProp,
1294 TypeConstraintData { tc },
1295 cls,
1296 cns(env, slot),
1297 val,
1298 cns(env, isSProp)
1302 // For non-DataTypeSpecific values, verifyTypeImpl handles the different
1303 // cases separately. However, our callers want a single coerced value,
1304 // which we don't track, so we punt if we're going to split it up.
1305 if (!val->type().isKnownDataType()) {
1306 auto const updated = fallback(val, nullptr, tc->mayCoerce());
1307 if (coerce && tc->mayCoerce()) *coerce = updated;
1308 return;
1311 verifyTypeImpl(
1312 env,
1313 *tc,
1314 false,
1315 [&] { // Get value to check
1316 env.irb->constrainValue(val, DataTypeSpecific);
1317 return val;
1319 [&] { // Get the class representing `this' type
1320 return cls;
1322 [&] (SSATmp* updated) { // Set the potentially coerced value
1323 if (coerce) *coerce = updated;
1325 [&] (SSATmp* val, SSATmp*, bool hard) { // Check failure
1326 auto const failHard =
1327 hard && RuntimeOption::EvalCheckPropTypeHints >= 3 &&
1328 (!tc->isUpperBound() || RuntimeOption::EvalEnforceGenericsUB >= 2);
1329 gen(
1330 env,
1331 failHard ? VerifyPropFailHard : VerifyPropFail,
1332 TypeConstraintData{ tc },
1333 cls,
1334 cns(env, slot),
1335 val,
1336 cns(env, isSProp)
1339 // We don't allow callable as a property type-hint, so we should never
1340 // need to check callability.
1341 [&] (SSATmp*) { always_assert(false); },
1342 [&] (SSATmp* val, SSATmp* checkCls) { // Class/type-alias check
1343 gen(
1344 env,
1345 VerifyPropCls,
1346 TypeConstraintData{ tc },
1347 cls,
1348 cns(env, slot),
1349 checkCls,
1350 val,
1351 cns(env, isSProp)
1354 fallback
1357 verifyFunc(tc);
1358 if (RuntimeOption::EvalEnforceGenericsUB > 0) {
1359 for (auto const& ub : *ubs) {
1360 verifyFunc(&ub);
1365 void verifyMysteryBoxConstraint(IRGS& env, const MysteryBoxConstraint& c,
1366 SSATmp* val, Block* fail) {
1367 auto const genFail = [&] {
1368 gen(env, Jmp, fail);
1370 UNUSED auto const& valType = val->type();
1372 FTRACE_MOD(Trace::sib, 3, "Verifying constraint {} {}\n", valType.toString(),
1373 c.tc.displayName());
1375 verifyTypeImpl(
1376 env,
1377 c.tc,
1378 false,
1379 [&] { // Get value to test
1380 return val;
1382 [&] { // Get the class representing `this' type
1383 if (c.propDecl) return cns(env, c.propDecl);
1384 if (c.ctx) return cns(env, c.ctx);
1385 return cns(env, nullptr);
1387 [&] (SSATmp*) { // Set the potentially coerced value
1389 [&] (SSATmp*, SSATmp*, bool) { // Check failure
1390 genFail();
1392 [&] (SSATmp*) { // Callable check
1393 genFail();
1395 [&] (SSATmp*, SSATmp*) {
1396 genFail();
1398 [&] (SSATmp*, SSATmp*, bool) { // Fallback
1399 genFail();
1400 return nullptr;
1405 void emitVerifyRetTypeC(IRGS& env) {
1406 verifyRetTypeImpl(env, TypeConstraint::ReturnId, 0, false);
1409 void emitVerifyRetTypeTS(IRGS& env) {
1410 verifyRetTypeImpl(env, TypeConstraint::ReturnId, 1, false);
1411 auto const ts = popC(env);
1412 auto const cell = topC(env);
1413 auto const reified = tcCouldBeReified(curFunc(env), TypeConstraint::ReturnId);
1414 if (reified || cell->isA(TObj)) {
1415 auto const funcData = FuncData { curFunc(env) };
1416 gen(env, VerifyReifiedReturnType, funcData, cell, ts, ldCtxCls(env));
1417 } else if (cell->type().maybe(TObj) && !reified) {
1418 // Meaning we did not not guard on the stack input correctly
1419 PUNT(VerifyRetTypeTS-UnguardedObj);
1423 void emitVerifyRetNonNullC(IRGS& env) {
1424 auto const func = curFunc(env);
1425 auto const& tc = func->returnTypeConstraint();
1426 always_assert(!tc.isNullable());
1427 verifyRetTypeImpl(env, TypeConstraint::ReturnId, 0, true);
1430 void emitVerifyOutType(IRGS& env, int32_t paramId) {
1431 verifyRetTypeImpl(env, paramId, 0, false);
1434 void emitVerifyParamType(IRGS& env, int32_t paramId) {
1435 verifyParamTypeImpl(env, paramId);
1438 void emitVerifyParamTypeTS(IRGS& env, int32_t paramId) {
1439 verifyParamTypeImpl(env, paramId);
1440 auto const ts = popC(env);
1441 auto const cell = ldLoc(env, paramId, DataTypeSpecific);
1442 auto const reified = tcCouldBeReified(curFunc(env), paramId);
1443 if (cell->isA(TObj) || reified) {
1444 cond(
1445 env,
1446 [&] (Block* taken) {
1447 return gen(env, CheckType, TDict, taken, ts);
1449 [&] (SSATmp* dts) {
1450 auto const fpData = FuncParamData { curFunc(env), paramId };
1451 gen(env, VerifyReifiedLocalType, fpData, cell, dts, ldCtxCls(env));
1452 return nullptr;
1454 [&] {
1455 gen(env, RaiseError, cns(env, s_TYPE_STRUCT_NOT_DARR.get()));
1456 return nullptr;
1459 } else if (cell->type().maybe(TObj)) {
1460 // Meaning we did not not guard on the stack input correctly
1461 PUNT(VerifyReifiedLocalType-UnguardedObj);
1465 void emitOODeclExists(IRGS& env, OODeclExistsOp subop) {
1466 auto const tAutoload = topC(env);
1467 auto const tCls = topC(env, BCSPRelOffset{1});
1469 if (!tCls->isA(TStr) || !tAutoload->isA(TBool)){ // result of Cast
1470 PUNT(OODeclExists-BadTypes);
1473 ClassKind kind;
1474 switch (subop) {
1475 case OODeclExistsOp::Class: kind = ClassKind::Class; break;
1476 case OODeclExistsOp::Trait: kind = ClassKind::Trait; break;
1477 case OODeclExistsOp::Interface: kind = ClassKind::Interface; break;
1480 auto const val = gen(
1481 env,
1482 OODeclExists,
1483 ClassKindData { kind },
1484 tCls,
1485 tAutoload
1487 discard(env, 2);
1488 push(env, val);
1489 decRef(env, tCls, DecRefProfileId::Default);
1492 void emitIssetL(IRGS& env, int32_t id) {
1493 auto const ld = ldLoc(env, id, DataTypeSpecific);
1494 push(env, gen(env, IsNType, TNull, ld));
1497 void emitIsUnsetL(IRGS& env, int32_t id) {
1498 auto const ld = ldLoc(env, id, DataTypeSpecific);
1499 push(env, gen(env, IsType, TUninit, ld));
1502 SSATmp* isTypeHelper(IRGS& env, IsTypeOp subop, SSATmp* val) {
1503 switch (subop) {
1504 case IsTypeOp::Vec: return isVecImpl(env, val);
1505 case IsTypeOp::Dict: return isDictImpl(env, val);
1506 case IsTypeOp::Scalar: return isScalarImpl(env, val);
1507 case IsTypeOp::Str: return isStrImpl(env, val);
1508 case IsTypeOp::ArrLike: return isArrLikeImpl(env, val);
1509 case IsTypeOp::LegacyArrLike: return isLegacyArrLikeImpl(env, val);
1510 case IsTypeOp::Class: return isClassImpl(env, val);
1511 case IsTypeOp::Func: return isFuncImpl(env, val);
1512 case IsTypeOp::ClsMeth: return isClsMethImpl(env, val);
1513 default: break;
1516 auto const t = typeOpToType(subop);
1517 return t <= TObj ? optimizedCallIsObject(env, val) : gen(env, IsType, t, val);
1520 void emitIsTypeC(IRGS& env, IsTypeOp subop) {
1521 auto const val = popC(env, DataTypeSpecific);
1522 push(env, isTypeHelper(env, subop, val));
1523 decRef(env, val, DecRefProfileId::Default);
1526 void emitIsTypeL(IRGS& env, NamedLocal loc, IsTypeOp subop) {
1527 auto const val = ldLocWarn(env, loc, DataTypeSpecific);
1528 push(env, isTypeHelper(env, subop, val));
1531 //////////////////////////////////////////////////////////////////////
1533 void emitAssertRATL(IRGS& env, int32_t loc, RepoAuthType rat) {
1534 assertTypeLocal(env, loc, typeFromRAT(rat, curClass(env)));
1537 void emitAssertRATStk(IRGS& env, uint32_t offset, RepoAuthType rat) {
1538 assertTypeStack(
1539 env,
1540 BCSPRelOffset{safe_cast<int32_t>(offset)},
1541 typeFromRAT(rat, curClass(env))
1545 //////////////////////////////////////////////////////////////////////