2 +----------------------------------------------------------------------+
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/hhbbc/interp.h"
23 #include <folly/Optional.h>
25 #include "hphp/util/trace.h"
26 #include "hphp/runtime/base/array-init.h"
27 #include "hphp/runtime/base/collections.h"
28 #include "hphp/runtime/base/static-string-table.h"
29 #include "hphp/runtime/base/tv-arith.h"
30 #include "hphp/runtime/base/tv-comparisons.h"
31 #include "hphp/runtime/base/tv-conversions.h"
32 #include "hphp/runtime/base/type-structure.h"
33 #include "hphp/runtime/base/type-structure-helpers.h"
34 #include "hphp/runtime/base/type-structure-helpers-defs.h"
35 #include "hphp/runtime/vm/runtime.h"
36 #include "hphp/runtime/vm/unit-util.h"
38 #include "hphp/runtime/ext/hh/ext_hh.h"
40 #include "hphp/hhbbc/analyze.h"
41 #include "hphp/hhbbc/bc.h"
42 #include "hphp/hhbbc/cfg.h"
43 #include "hphp/hhbbc/class-util.h"
44 #include "hphp/hhbbc/eval-cell.h"
45 #include "hphp/hhbbc/index.h"
46 #include "hphp/hhbbc/interp-state.h"
47 #include "hphp/hhbbc/optimize.h"
48 #include "hphp/hhbbc/representation.h"
49 #include "hphp/hhbbc/type-builtins.h"
50 #include "hphp/hhbbc/type-ops.h"
51 #include "hphp/hhbbc/type-system.h"
52 #include "hphp/hhbbc/unit-util.h"
54 #include "hphp/hhbbc/interp-internal.h"
56 namespace HPHP
{ namespace HHBBC
{
58 //////////////////////////////////////////////////////////////////////
62 const StaticString
s_Throwable("Throwable");
63 const StaticString
s_construct("__construct");
64 const StaticString
s_86ctor("86ctor");
65 const StaticString
s_PHP_Incomplete_Class("__PHP_Incomplete_Class");
66 const StaticString
s_IMemoizeParam("HH\\IMemoizeParam");
67 const StaticString
s_getInstanceKey("getInstanceKey");
68 const StaticString
s_Closure("Closure");
69 const StaticString
s_byRefWarn("Only variables should be passed by reference");
70 const StaticString
s_byRefError("Only variables can be passed by reference");
71 const StaticString
s_trigger_error("trigger_error");
74 //////////////////////////////////////////////////////////////////////
76 void impl_vec(ISS
& env
, bool reduce
, BytecodeVec
&& bcs
) {
77 BytecodeVec currentReduction
;
78 if (!options
.StrengthReduce
) reduce
= false;
80 env
.flags
.wasPEI
= false;
81 env
.flags
.canConstProp
= true;
82 env
.flags
.effectFree
= true;
84 for (auto it
= begin(bcs
); it
!= end(bcs
); ++it
) {
85 assert(env
.flags
.jmpDest
== NoBlockId
&&
86 "you can't use impl with branching opcodes before last position");
88 auto const wasPEI
= env
.flags
.wasPEI
;
89 auto const canConstProp
= env
.flags
.canConstProp
;
90 auto const effectFree
= env
.flags
.effectFree
;
92 FTRACE(3, " (impl {}\n", show(env
.ctx
.func
, *it
));
93 env
.flags
.wasPEI
= true;
94 env
.flags
.canConstProp
= false;
95 env
.flags
.effectFree
= false;
96 env
.flags
.strengthReduced
= folly::none
;
97 default_dispatch(env
, *it
);
99 if (env
.flags
.strengthReduced
) {
100 if (instrFlags(env
.flags
.strengthReduced
->back().op
) & TF
) {
104 std::move(begin(*env
.flags
.strengthReduced
),
105 end(*env
.flags
.strengthReduced
),
106 std::back_inserter(currentReduction
));
109 if (instrFlags(it
->op
) & TF
) {
112 auto applyConstProp
= [&] {
113 if (env
.flags
.effectFree
&& !env
.flags
.wasPEI
) return;
114 auto stk
= env
.state
.stack
.end();
115 for (auto i
= it
->numPush(); i
--; ) {
117 if (!is_scalar(stk
->type
)) return;
119 env
.flags
.effectFree
= true;
120 env
.flags
.wasPEI
= false;
124 if (env
.flags
.canConstProp
) {
125 if (env
.collect
.propagate_constants
) {
126 if (env
.collect
.propagate_constants(*it
, env
.state
,
129 env
.flags
.canConstProp
= false;
130 env
.flags
.wasPEI
= false;
131 env
.flags
.effectFree
= true;
137 if (!added
) currentReduction
.push_back(std::move(*it
));
138 } else if (env
.flags
.canConstProp
) {
143 // If any of the opcodes in the impl list said they could throw,
144 // then the whole thing could throw.
145 env
.flags
.wasPEI
= env
.flags
.wasPEI
|| wasPEI
;
146 env
.flags
.canConstProp
= env
.flags
.canConstProp
&& canConstProp
;
147 env
.flags
.effectFree
= env
.flags
.effectFree
&& effectFree
;
148 if (env
.state
.unreachable
) break;
152 env
.flags
.strengthReduced
= std::move(currentReduction
);
154 env
.flags
.strengthReduced
= folly::none
;
158 LocalId
equivLocalRange(ISS
& env
, const LocalRange
& range
) {
159 auto bestRange
= range
.first
;
160 auto equivFirst
= findLocEquiv(env
, range
.first
);
161 if (equivFirst
== NoLocalId
) return bestRange
;
163 if (equivFirst
< bestRange
) {
164 auto equivRange
= [&] {
165 // local equivalency includes differing by Uninit, so we need
166 // to check the types.
167 if (peekLocRaw(env
, equivFirst
) != peekLocRaw(env
, range
.first
)) {
171 for (uint32_t i
= 1; i
< range
.count
; ++i
) {
172 if (!locsAreEquiv(env
, equivFirst
+ i
, range
.first
+ i
) ||
173 peekLocRaw(env
, equivFirst
+ i
) !=
174 peekLocRaw(env
, range
.first
+ i
)) {
183 bestRange
= equivFirst
;
186 equivFirst
= findLocEquiv(env
, equivFirst
);
187 assert(equivFirst
!= NoLocalId
);
188 } while (equivFirst
!= range
.first
);
193 SString
getNameFromType(const Type
& t
) {
194 if (!t
.subtypeOf(BStr
)) return nullptr;
195 if (is_specialized_reifiedname(t
)) return dreifiedname_of(t
).name
;
196 if (is_specialized_string(t
)) return sval_of(t
);
200 namespace interp_step
{
202 void in(ISS
& env
, const bc::Nop
&) { effect_free(env
); }
203 void in(ISS
& env
, const bc::DiscardClsRef
& op
) {
205 takeClsRefSlot(env
, op
.slot
);
207 void in(ISS
& env
, const bc::PopC
&) {
211 void in(ISS
& env
, const bc::PopU
&) { effect_free(env
); popU(env
); }
212 void in(ISS
& env
, const bc::PopU2
&) {
214 auto equiv
= topStkEquiv(env
);
215 auto val
= popC(env
);
217 push(env
, std::move(val
), equiv
!= StackDupId
? equiv
: NoLocalId
);
219 void in(ISS
& env
, const bc::PopV
&) { nothrow(env
); popV(env
); }
221 void in(ISS
& env
, const bc::EntryNop
&) { effect_free(env
); }
223 void in(ISS
& env
, const bc::Dup
& /*op*/) {
225 auto equiv
= topStkEquiv(env
);
226 auto val
= popC(env
);
227 push(env
, val
, equiv
);
228 push(env
, std::move(val
), StackDupId
);
231 void in(ISS
& env
, const bc::AssertRATL
& op
) {
232 mayReadLocal(env
, op
.loc1
);
236 void in(ISS
& env
, const bc::AssertRATStk
&) {
240 void in(ISS
& env
, const bc::BreakTraceHint
&) { effect_free(env
); }
242 void in(ISS
& env
, const bc::Box
&) {
248 void in(ISS
& env
, const bc::Unbox
&) {
251 push(env
, TInitCell
);
254 void in(ISS
& env
, const bc::CGetCUNop
&) {
256 auto const t
= popCU(env
);
257 push(env
, remove_uninit(t
));
260 void in(ISS
& env
, const bc::UGetCUNop
&) {
266 void in(ISS
& env
, const bc::Null
&) {
268 push(env
, TInitNull
);
271 void in(ISS
& env
, const bc::NullUninit
&) {
276 void in(ISS
& env
, const bc::True
&) {
281 void in(ISS
& env
, const bc::False
&) {
286 void in(ISS
& env
, const bc::Int
& op
) {
288 push(env
, ival(op
.arg1
));
291 void in(ISS
& env
, const bc::Double
& op
) {
293 push(env
, dval(op
.dbl1
));
296 void in(ISS
& env
, const bc::String
& op
) {
298 push(env
, sval(op
.str1
));
301 void in(ISS
& env
, const bc::Array
& op
) {
302 assert(op
.arr1
->isPHPArray());
303 assertx(!RuntimeOption::EvalHackArrDVArrs
|| op
.arr1
->isNotDVArray());
305 push(env
, aval(op
.arr1
));
308 void in(ISS
& env
, const bc::Vec
& op
) {
309 assert(op
.arr1
->isVecArray());
311 push(env
, vec_val(op
.arr1
));
314 void in(ISS
& env
, const bc::Dict
& op
) {
315 assert(op
.arr1
->isDict());
317 push(env
, dict_val(op
.arr1
));
320 void in(ISS
& env
, const bc::Keyset
& op
) {
321 assert(op
.arr1
->isKeyset());
323 push(env
, keyset_val(op
.arr1
));
326 void in(ISS
& env
, const bc::NewArray
& op
) {
327 push(env
, op
.arg1
== 0 ?
328 effect_free(env
), aempty() : some_aempty());
331 void in(ISS
& env
, const bc::NewDictArray
& op
) {
332 push(env
, op
.arg1
== 0 ?
333 effect_free(env
), dict_empty() : some_dict_empty());
336 void in(ISS
& env
, const bc::NewMixedArray
& op
) {
337 push(env
, op
.arg1
== 0 ?
338 effect_free(env
), aempty() : some_aempty());
341 void in(ISS
& env
, const bc::NewPackedArray
& op
) {
342 auto elems
= std::vector
<Type
>{};
343 elems
.reserve(op
.arg1
);
344 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
345 elems
.push_back(std::move(topC(env
, op
.arg1
- i
- 1)));
347 discard(env
, op
.arg1
);
348 push(env
, arr_packed(std::move(elems
)));
352 void in(ISS
& env
, const bc::NewVArray
& op
) {
353 assertx(!RuntimeOption::EvalHackArrDVArrs
);
354 auto elems
= std::vector
<Type
>{};
355 elems
.reserve(op
.arg1
);
356 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
357 elems
.push_back(std::move(topC(env
, op
.arg1
- i
- 1)));
359 discard(env
, op
.arg1
);
360 push(env
, arr_packed_varray(std::move(elems
)));
364 void in(ISS
& env
, const bc::NewDArray
& op
) {
365 assertx(!RuntimeOption::EvalHackArrDVArrs
);
366 push(env
, op
.arg1
== 0 ?
367 effect_free(env
), aempty_darray() : some_aempty_darray());
370 void in(ISS
& env
, const bc::NewStructArray
& op
) {
371 auto map
= MapElems
{};
372 for (auto it
= op
.keys
.end(); it
!= op
.keys
.begin(); ) {
373 map
.emplace_front(make_tv
<KindOfPersistentString
>(*--it
), popC(env
));
375 push(env
, arr_map(std::move(map
)));
379 void in(ISS
& env
, const bc::NewStructDArray
& op
) {
380 assertx(!RuntimeOption::EvalHackArrDVArrs
);
381 auto map
= MapElems
{};
382 for (auto it
= op
.keys
.end(); it
!= op
.keys
.begin(); ) {
383 map
.emplace_front(make_tv
<KindOfPersistentString
>(*--it
), popC(env
));
385 push(env
, arr_map_darray(std::move(map
)));
389 void in(ISS
& env
, const bc::NewStructDict
& op
) {
390 auto map
= MapElems
{};
391 for (auto it
= op
.keys
.end(); it
!= op
.keys
.begin(); ) {
392 map
.emplace_front(make_tv
<KindOfPersistentString
>(*--it
), popC(env
));
394 push(env
, dict_map(std::move(map
)));
398 void in(ISS
& env
, const bc::NewVecArray
& op
) {
399 auto elems
= std::vector
<Type
>{};
400 elems
.reserve(op
.arg1
);
401 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
402 elems
.push_back(std::move(topC(env
, op
.arg1
- i
- 1)));
404 discard(env
, op
.arg1
);
406 push(env
, vec(std::move(elems
)));
409 void in(ISS
& env
, const bc::NewKeysetArray
& op
) {
411 auto map
= MapElems
{};
415 for (auto i
= uint32_t{0}; i
< op
.arg1
; ++i
) {
416 auto k
= disect_strict_key(popC(env
));
417 if (k
.type
== TBottom
) {
422 if (auto const v
= k
.tv()) {
423 map
.emplace_front(*v
, k
.type
);
428 ty
|= std::move(k
.type
);
431 push(env
, keyset_map(std::move(map
)));
434 push(env
, keyset_n(ty
));
441 void in(ISS
& env
, const bc::NewLikeArrayL
& op
) {
442 locAsCell(env
, op
.loc1
);
443 push(env
, some_aempty());
446 void in(ISS
& env
, const bc::AddElemC
& /*op*/) {
447 auto const v
= popC(env
);
448 auto const k
= popC(env
);
450 auto const outTy
= [&] (Type ty
) ->
451 folly::Optional
<std::pair
<Type
,ThrowMode
>> {
452 if (ty
.subtypeOf(BArr
)) {
453 return array_set(std::move(ty
), k
, v
);
455 if (ty
.subtypeOf(BDict
)) {
456 return dict_set(std::move(ty
), k
, v
);
462 return push(env
, union_of(TArr
, TDict
));
465 if (outTy
->first
.subtypeOf(BBottom
)) {
467 } else if (outTy
->second
== ThrowMode::None
) {
469 if (any(env
.collect
.opts
& CollectionOpts::TrackConstantArrays
)) {
473 push(env
, std::move(outTy
->first
));
476 void in(ISS
& env
, const bc::AddElemV
& /*op*/) {
477 popV(env
); popC(env
);
482 void in(ISS
& env
, const bc::AddNewElemC
&) {
485 auto const outTy
= [&] (Type ty
) -> folly::Optional
<Type
> {
486 if (ty
.subtypeOf(BArr
)) {
487 return array_newelem(std::move(ty
), std::move(v
)).first
;
489 if (ty
.subtypeOf(BVec
)) {
490 return vec_newelem(std::move(ty
), std::move(v
)).first
;
492 if (ty
.subtypeOf(BKeyset
)) {
493 return keyset_newelem(std::move(ty
), std::move(v
)).first
;
499 return push(env
, TInitCell
);
502 if (outTy
->subtypeOf(BBottom
)) {
505 if (any(env
.collect
.opts
& CollectionOpts::TrackConstantArrays
)) {
509 push(env
, std::move(*outTy
));
512 void in(ISS
& env
, const bc::AddNewElemV
&) {
518 void in(ISS
& env
, const bc::NewCol
& op
) {
519 auto const type
= static_cast<CollectionType
>(op
.subop1
);
520 auto const name
= collections::typeToString(type
);
521 push(env
, objExact(env
.index
.builtin_class(name
)));
524 void in(ISS
& env
, const bc::NewPair
& /*op*/) {
525 popC(env
); popC(env
);
526 auto const name
= collections::typeToString(CollectionType::Pair
);
527 push(env
, objExact(env
.index
.builtin_class(name
)));
530 void in(ISS
& env
, const bc::ColFromArray
& op
) {
532 auto const type
= static_cast<CollectionType
>(op
.subop1
);
533 auto const name
= collections::typeToString(type
);
534 push(env
, objExact(env
.index
.builtin_class(name
)));
537 void doCns(ISS
& env
, SString str
, SString fallback
) {
538 if (!options
.HardConstProp
) return push(env
, TInitCell
);
539 if (RuntimeOption::UndefinedConstFallback
> 0) {
540 // Disable optimization to force the fallback to raise a notice.
544 auto t
= env
.index
.lookup_constant(env
.ctx
, str
, fallback
);
546 // There's no entry for this constant in the index. It must be
547 // the first iteration, so we'll add a dummy entry to make sure
548 // there /is/ something next time around.
550 val
.m_type
= kReadOnlyConstant
;
551 env
.collect
.cnsMap
.emplace(str
, val
);
553 // make sure we're re-analyzed
554 env
.collect
.readsUntrackedConstants
= true;
555 } else if (t
->strictSubtypeOf(TInitCell
)) {
556 // constprop will take care of nothrow *if* its a constant; and if
557 // its not, we might trigger autoload.
560 push(env
, std::move(*t
));
563 void in(ISS
& env
, const bc::Cns
& op
) { doCns(env
, op
.str1
, nullptr); }
564 void in(ISS
& env
, const bc::CnsE
& op
) { doCns(env
, op
.str1
, nullptr); }
565 void in(ISS
& env
, const bc::CnsU
& op
) { doCns(env
, op
.str1
, op
.str2
); }
566 void in(ISS
& env
, const bc::CnsUE
& op
) { doCns(env
, op
.str1
, op
.str2
); }
568 void in(ISS
& env
, const bc::ClsCns
& op
) {
569 auto const& t1
= peekClsRefSlot(env
, op
.slot
);
570 if (is_specialized_cls(t1
)) {
571 auto const dcls
= dcls_of(t1
);
572 if (dcls
.type
== DCls::Exact
) {
573 return reduce(env
, bc::DiscardClsRef
{ op
.slot
},
574 bc::ClsCnsD
{ op
.str1
, dcls
.cls
.name() });
577 takeClsRefSlot(env
, op
.slot
);
578 push(env
, TInitCell
);
581 void in(ISS
& env
, const bc::ClsCnsD
& op
) {
582 if (auto const rcls
= env
.index
.resolve_class(env
.ctx
, op
.str2
)) {
583 auto t
= env
.index
.lookup_class_constant(env
.ctx
, *rcls
, op
.str1
);
584 if (options
.HardConstProp
) constprop(env
);
585 push(env
, std::move(t
));
588 push(env
, TInitCell
);
591 void in(ISS
& env
, const bc::File
&) { effect_free(env
); push(env
, TSStr
); }
592 void in(ISS
& env
, const bc::Dir
&) { effect_free(env
); push(env
, TSStr
); }
593 void in(ISS
& env
, const bc::Method
&) { effect_free(env
); push(env
, TSStr
); }
595 void in(ISS
& env
, const bc::ClsRefName
& op
) {
596 auto ty
= peekClsRefSlot(env
, op
.slot
);
597 if (is_specialized_cls(ty
)) {
598 auto const dcls
= dcls_of(ty
);
599 if (dcls
.type
== DCls::Exact
) {
601 bc::DiscardClsRef
{ op
.slot
},
602 bc::String
{ dcls
.cls
.name() });
606 takeClsRefSlot(env
, op
.slot
);
610 void concatHelper(ISS
& env
, uint32_t n
) {
612 StringData
* result
= nullptr;
614 auto const t
= topC(env
, i
);
615 auto const v
= tv(t
);
617 if (!isStringType(v
->m_type
) &&
618 v
->m_type
!= KindOfNull
&&
619 v
->m_type
!= KindOfBoolean
&&
620 v
->m_type
!= KindOfInt64
&&
621 v
->m_type
!= KindOfDouble
) {
624 auto const cell
= eval_cell_value([&] {
625 auto const s
= makeStaticString(
627 StringData::Make(tvAsCVarRef(&*v
).toString().get(), result
) :
628 tvAsCVarRef(&*v
).toString().get());
629 return make_tv
<KindOfString
>(s
);
632 result
= cell
->m_data
.pstr
;
635 if (result
&& i
>= 2) {
636 BytecodeVec
bcs(i
, bc::PopC
{});
637 bcs
.push_back(gen_constant(make_tv
<KindOfString
>(result
)));
639 bcs
.push_back(bc::ConcatN
{ n
- i
+ 1 });
641 return reduce(env
, std::move(bcs
));
647 void in(ISS
& env
, const bc::Concat
& /*op*/) {
648 concatHelper(env
, 2);
651 void in(ISS
& env
, const bc::ConcatN
& op
) {
652 if (op
.arg1
== 2) return reduce(env
, bc::Concat
{});
653 concatHelper(env
, op
.arg1
);
656 template <class Op
, class Fun
>
657 void arithImpl(ISS
& env
, const Op
& /*op*/, Fun fun
) {
659 auto const t1
= popC(env
);
660 auto const t2
= popC(env
);
661 push(env
, fun(t2
, t1
));
664 void in(ISS
& env
, const bc::Add
& op
) { arithImpl(env
, op
, typeAdd
); }
665 void in(ISS
& env
, const bc::Sub
& op
) { arithImpl(env
, op
, typeSub
); }
666 void in(ISS
& env
, const bc::Mul
& op
) { arithImpl(env
, op
, typeMul
); }
667 void in(ISS
& env
, const bc::Div
& op
) { arithImpl(env
, op
, typeDiv
); }
668 void in(ISS
& env
, const bc::Mod
& op
) { arithImpl(env
, op
, typeMod
); }
669 void in(ISS
& env
, const bc::Pow
& op
) { arithImpl(env
, op
, typePow
); }
670 void in(ISS
& env
, const bc::BitAnd
& op
) { arithImpl(env
, op
, typeBitAnd
); }
671 void in(ISS
& env
, const bc::BitOr
& op
) { arithImpl(env
, op
, typeBitOr
); }
672 void in(ISS
& env
, const bc::BitXor
& op
) { arithImpl(env
, op
, typeBitXor
); }
673 void in(ISS
& env
, const bc::AddO
& op
) { arithImpl(env
, op
, typeAddO
); }
674 void in(ISS
& env
, const bc::SubO
& op
) { arithImpl(env
, op
, typeSubO
); }
675 void in(ISS
& env
, const bc::MulO
& op
) { arithImpl(env
, op
, typeMulO
); }
676 void in(ISS
& env
, const bc::Shl
& op
) { arithImpl(env
, op
, typeShl
); }
677 void in(ISS
& env
, const bc::Shr
& op
) { arithImpl(env
, op
, typeShr
); }
679 void in(ISS
& env
, const bc::BitNot
& /*op*/) {
680 auto const t
= popC(env
);
681 auto const v
= tv(t
);
684 auto cell
= eval_cell([&] {
689 if (cell
) return push(env
, std::move(*cell
));
691 push(env
, TInitCell
);
696 bool couldBeHackArr(Type t
) {
697 return t
.couldBe(BVec
| BDict
| BKeyset
);
701 std::pair
<Type
,bool> resolveSame(ISS
& env
) {
702 auto const l1
= topStkEquiv(env
, 0);
703 auto const t1
= topC(env
, 0);
704 auto const l2
= topStkEquiv(env
, 1);
705 auto const t2
= topC(env
, 1);
707 // EvalHackArrCompatNotices will notice on === and !== between PHP arrays and
708 // Hack arrays. We can't really do better than this in general because of
709 // arrays inside these arrays.
710 auto warningsEnabled
=
711 (RuntimeOption::EvalHackArrCompatNotices
||
712 RuntimeOption::EvalHackArrCompatDVCmpNotices
);
714 auto const result
= [&] {
715 auto const v1
= tv(t1
);
716 auto const v2
= tv(t2
);
718 if (l1
== StackDupId
||
719 (l1
== l2
&& l1
!= NoLocalId
) ||
720 (l1
<= MaxLocalId
&& l2
<= MaxLocalId
&& locsAreEquiv(env
, l1
, l2
))) {
721 if (!t1
.couldBe(BDbl
) || !t2
.couldBe(BDbl
) ||
722 (v1
&& (v1
->m_type
!= KindOfDouble
|| !std::isnan(v1
->m_data
.dbl
))) ||
723 (v2
&& (v2
->m_type
!= KindOfDouble
|| !std::isnan(v2
->m_data
.dbl
)))) {
724 return NSame
? TFalse
: TTrue
;
729 if (auto r
= eval_cell_value([&]{ return cellSame(*v2
, *v1
); })) {
730 // we wouldn't get here if cellSame raised a warning
731 warningsEnabled
= false;
732 return r
!= NSame
? TTrue
: TFalse
;
736 return NSame
? typeNSame(t1
, t2
) : typeSame(t1
, t2
);
739 if (warningsEnabled
&& result
== (NSame
? TFalse
: TTrue
)) {
740 warningsEnabled
= false;
742 return { result
, warningsEnabled
&& compare_might_raise(t1
, t2
) };
745 template<bool Negate
>
746 void sameImpl(ISS
& env
) {
747 auto pair
= resolveSame
<Negate
>(env
);
755 push(env
, std::move(pair
.first
));
758 template<class Same
, class JmpOp
>
759 void sameJmpImpl(ISS
& env
, const Same
& same
, const JmpOp
& jmp
) {
760 auto bail
= [&] { impl(env
, same
, jmp
); };
762 constexpr auto NSame
= Same::op
== Op::NSame
;
764 if (resolveSame
<NSame
>(env
).first
!= TBool
) return bail();
766 auto const loc0
= topStkEquiv(env
, 0);
767 auto const loc1
= topStkEquiv(env
, 1);
768 // If loc0 == loc1, either they're both NoLocalId, so there's
769 // nothing for us to deduce, or both stack elements are the same
770 // value, so the only thing we could deduce is that they are or are
771 // not NaN. But we don't track that, so just bail.
772 if (loc0
== loc1
|| loc0
== StackDupId
) return bail();
774 auto const ty0
= topC(env
, 0);
775 auto const ty1
= topC(env
, 1);
776 auto const val0
= tv(ty0
);
777 auto const val1
= tv(ty1
);
779 if ((val0
&& val1
) ||
780 (loc0
== NoLocalId
&& !val0
&& ty1
.subtypeOf(ty0
)) ||
781 (loc1
== NoLocalId
&& !val1
&& ty0
.subtypeOf(ty1
))) {
785 // We need to loosen away the d/varray bits here because array comparison does
786 // not take into account the difference.
787 auto isect
= intersection_of(
788 loosen_dvarrayness(ty0
),
789 loosen_dvarrayness(ty1
)
793 auto handle_same
= [&] {
794 // Currently dce uses equivalency to prove that something isn't
795 // the last reference - so we can only assert equivalency here if
796 // we know that won't be affected. Its irrelevant for uncounted
797 // things, and for TObj and TRes, $x === $y iff $x and $y refer to
799 if (loc0
<= MaxLocalId
&&
800 (ty0
.subtypeOf(BObj
| BRes
| BPrim
) ||
801 ty1
.subtypeOf(BObj
| BRes
| BPrim
) ||
802 (ty0
.subtypeOf(BUnc
) && ty1
.subtypeOf(BUnc
)))) {
803 if (loc1
== StackDupId
) {
804 setStkLocal(env
, loc0
, 1);
805 } else if (loc1
<= MaxLocalId
&& !locsAreEquiv(env
, loc0
, loc1
)) {
808 auto const other
= findLocEquiv(env
, loc
);
809 if (other
== NoLocalId
) break;
810 killLocEquiv(env
, loc
);
811 addLocEquiv(env
, loc
, loc1
);
814 addLocEquiv(env
, loc
, loc1
);
817 return refineLocation(env
, loc1
!= NoLocalId
? loc1
: loc0
, [&] (Type ty
) {
818 if (!ty
.couldBe(BUninit
) || !isect
.couldBe(BNull
)) {
819 auto ret
= intersection_of(std::move(ty
), isect
);
820 return ty
.subtypeOf(BUnc
) ? ret
: loosen_staticness(ret
);
823 if (isect
.subtypeOf(BNull
)) {
824 return ty
.couldBe(BInitNull
) ? TNull
: TUninit
;
831 auto handle_differ_side
= [&] (LocalId location
, const Type
& ty
) {
832 if (!ty
.subtypeOf(BInitNull
) && !ty
.strictSubtypeOf(TBool
)) return true;
833 return refineLocation(env
, location
, [&] (Type t
) {
834 if (ty
.subtypeOf(BNull
)) {
835 t
= remove_uninit(std::move(t
));
836 if (is_opt(t
)) t
= unopt(std::move(t
));
838 } else if (ty
.strictSubtypeOf(TBool
) && t
.subtypeOf(BBool
)) {
839 return ty
== TFalse
? TTrue
: TFalse
;
845 auto handle_differ
= [&] {
847 (loc0
== NoLocalId
|| handle_differ_side(loc0
, ty1
)) &&
848 (loc1
== NoLocalId
|| handle_differ_side(loc1
, ty0
));
851 auto const sameIsJmpTarget
=
852 (Same::op
== Op::Same
) == (JmpOp::op
== Op::JmpNZ
);
854 auto save
= env
.state
;
855 auto const target_reachable
= sameIsJmpTarget
?
856 handle_same() : handle_differ();
857 if (!target_reachable
) jmp_nevertaken(env
);
858 // swap, so we can restore this state if the branch is always taken.
859 std::swap(env
.state
, save
);
860 if (!(sameIsJmpTarget
? handle_differ() : handle_same())) {
861 jmp_setdest(env
, jmp
.target1
);
862 env
.state
= std::move(save
);
863 } else if (target_reachable
) {
864 env
.propagate(jmp
.target1
, &save
);
868 bc::JmpNZ
invertJmp(const bc::JmpZ
& jmp
) { return bc::JmpNZ
{ jmp
.target1
}; }
869 bc::JmpZ
invertJmp(const bc::JmpNZ
& jmp
) { return bc::JmpZ
{ jmp
.target1
}; }
873 template<class Same
, class JmpOp
>
874 void group(ISS
& env
, const Same
& same
, const JmpOp
& jmp
) {
875 sameJmpImpl(env
, same
, jmp
);
878 template<class Same
, class JmpOp
>
879 void group(ISS
& env
, const Same
& same
, const bc::Not
&, const JmpOp
& jmp
) {
880 sameJmpImpl(env
, same
, invertJmp(jmp
));
883 void in(ISS
& env
, const bc::Same
&) { sameImpl
<false>(env
); }
884 void in(ISS
& env
, const bc::NSame
&) { sameImpl
<true>(env
); }
887 void binOpBoolImpl(ISS
& env
, Fun fun
) {
888 auto const t1
= popC(env
);
889 auto const t2
= popC(env
);
890 auto const v1
= tv(t1
);
891 auto const v2
= tv(t2
);
893 if (auto r
= eval_cell_value([&]{ return fun(*v2
, *v1
); })) {
895 return push(env
, *r
? TTrue
: TFalse
);
898 // TODO_4: evaluate when these can throw, non-constant type stuff.
903 void binOpInt64Impl(ISS
& env
, Fun fun
) {
904 auto const t1
= popC(env
);
905 auto const t2
= popC(env
);
906 auto const v1
= tv(t1
);
907 auto const v2
= tv(t2
);
909 if (auto r
= eval_cell_value([&]{ return ival(fun(*v2
, *v1
)); })) {
911 return push(env
, std::move(*r
));
914 // TODO_4: evaluate when these can throw, non-constant type stuff.
918 void in(ISS
& env
, const bc::Eq
&) {
919 auto rs
= resolveSame
<false>(env
);
920 if (rs
.first
== TTrue
) {
921 if (!rs
.second
) constprop(env
);
923 return push(env
, TTrue
);
925 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) { return cellEqual(c1
, c2
); });
927 void in(ISS
& env
, const bc::Neq
&) {
928 auto rs
= resolveSame
<false>(env
);
929 if (rs
.first
== TTrue
) {
930 if (!rs
.second
) constprop(env
);
932 return push(env
, TFalse
);
934 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) { return !cellEqual(c1
, c2
); });
936 void in(ISS
& env
, const bc::Lt
&) {
937 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) { return cellLess(c1
, c2
); });
939 void in(ISS
& env
, const bc::Gt
&) {
940 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) { return cellGreater(c1
, c2
); });
942 void in(ISS
& env
, const bc::Lte
&) { binOpBoolImpl(env
, cellLessOrEqual
); }
943 void in(ISS
& env
, const bc::Gte
&) { binOpBoolImpl(env
, cellGreaterOrEqual
); }
945 void in(ISS
& env
, const bc::Cmp
&) {
946 binOpInt64Impl(env
, [&] (Cell c1
, Cell c2
) { return cellCompare(c1
, c2
); });
949 void in(ISS
& env
, const bc::Xor
&) {
950 binOpBoolImpl(env
, [&] (Cell c1
, Cell c2
) {
951 return cellToBool(c1
) ^ cellToBool(c2
);
955 void castBoolImpl(ISS
& env
, const Type
& t
, bool negate
) {
959 auto const e
= emptiness(t
);
961 case Emptiness::Empty
:
962 case Emptiness::NonEmpty
:
963 return push(env
, (e
== Emptiness::Empty
) == negate
? TTrue
: TFalse
);
964 case Emptiness::Maybe
:
971 void in(ISS
& env
, const bc::Not
&) {
972 castBoolImpl(env
, popC(env
), true);
975 void in(ISS
& env
, const bc::CastBool
&) {
976 auto const t
= topC(env
);
977 if (t
.subtypeOf(BBool
)) return reduce(env
, bc::Nop
{});
978 castBoolImpl(env
, popC(env
), false);
981 void in(ISS
& env
, const bc::CastInt
&) {
983 auto const t
= topC(env
);
984 if (t
.subtypeOf(BInt
)) return reduce(env
, bc::Nop
{});
986 // Objects can raise a warning about converting to int.
987 if (!t
.couldBe(BObj
)) nothrow(env
);
988 if (auto const v
= tv(t
)) {
989 auto cell
= eval_cell([&] {
990 return make_tv
<KindOfInt64
>(cellToInt(*v
));
992 if (cell
) return push(env
, std::move(*cell
));
997 // Handle a casting operation, where "target" is the type being casted to. If
998 // "fn" is provided, it will be called to cast any constant inputs. If "elide"
999 // is set to true, if the source type is the same as the destination, the cast
1000 // will be optimized away.
1001 void castImpl(ISS
& env
, Type target
, void(*fn
)(TypedValue
*)) {
1002 auto const t
= topC(env
);
1003 if (t
.subtypeOf(target
)) return reduce(env
, bc::Nop
{});
1006 if (auto val
= tv(t
)) {
1007 if (auto result
= eval_cell([&] { fn(&*val
); return *val
; })) {
1013 push(env
, std::move(target
));
1016 void in(ISS
& env
, const bc::CastDouble
&) {
1017 castImpl(env
, TDbl
, tvCastToDoubleInPlace
);
1020 void in(ISS
& env
, const bc::CastString
&) {
1021 castImpl(env
, TStr
, tvCastToStringInPlace
);
1024 void in(ISS
& env
, const bc::CastArray
&) {
1025 castImpl(env
, TPArr
, tvCastToArrayInPlace
);
1028 void in(ISS
& env
, const bc::CastObject
&) { castImpl(env
, TObj
, nullptr); }
1030 void in(ISS
& env
, const bc::CastDict
&) {
1031 castImpl(env
, TDict
, tvCastToDictInPlace
);
1034 void in(ISS
& env
, const bc::CastVec
&) {
1035 castImpl(env
, TVec
, tvCastToVecInPlace
);
1038 void in(ISS
& env
, const bc::CastKeyset
&) {
1039 castImpl(env
, TKeyset
, tvCastToKeysetInPlace
);
1042 void in(ISS
& env
, const bc::CastVArray
&) {
1043 assertx(!RuntimeOption::EvalHackArrDVArrs
);
1044 castImpl(env
, TVArr
, tvCastToVArrayInPlace
);
1047 void in(ISS
& env
, const bc::CastDArray
&) {
1048 assertx(!RuntimeOption::EvalHackArrDVArrs
);
1049 castImpl(env
, TDArr
, tvCastToDArrayInPlace
);
1052 void in(ISS
& env
, const bc::DblAsBits
&) {
1056 auto const ty
= popC(env
);
1057 if (!ty
.couldBe(BDbl
)) return push(env
, ival(0));
1059 if (auto val
= tv(ty
)) {
1060 assertx(isDoubleType(val
->m_type
));
1061 val
->m_type
= KindOfInt64
;
1062 push(env
, from_cell(*val
));
1069 void in(ISS
& env
, const bc::Print
& /*op*/) {
1074 void in(ISS
& env
, const bc::Clone
& /*op*/) {
1075 auto val
= popC(env
);
1076 if (!val
.subtypeOf(BObj
)) {
1077 val
= is_opt(val
) ? unopt(std::move(val
)) : TObj
;
1079 push(env
, std::move(val
));
1082 void in(ISS
& env
, const bc::Exit
&) { popC(env
); push(env
, TInitNull
); }
1083 void in(ISS
& env
, const bc::Fatal
&) { popC(env
); }
1085 void in(ISS
& /*env*/, const bc::JmpNS
&) {
1086 always_assert(0 && "blocks should not contain JmpNS instructions");
1089 void in(ISS
& /*env*/, const bc::Jmp
&) {
1090 always_assert(0 && "blocks should not contain Jmp instructions");
1093 template<bool Negate
, class JmpOp
>
1094 void jmpImpl(ISS
& env
, const JmpOp
& op
) {
1095 auto const location
= topStkEquiv(env
);
1096 auto const e
= emptiness(popC(env
));
1097 if (e
== (Negate
? Emptiness::NonEmpty
: Emptiness::Empty
)) {
1099 jmp_setdest(env
, op
.target1
);
1103 if (e
== (Negate
? Emptiness::Empty
: Emptiness::NonEmpty
)) {
1105 jmp_nevertaken(env
);
1109 if (next_real_block(*env
.ctx
.func
, env
.blk
.fallthrough
) ==
1110 next_real_block(*env
.ctx
.func
, op
.target1
)) {
1112 jmp_nevertaken(env
);
1118 if (location
== NoLocalId
) return env
.propagate(op
.target1
, &env
.state
);
1120 refineLocation(env
, location
,
1121 Negate
? assert_nonemptiness
: assert_emptiness
,
1123 Negate
? assert_emptiness
: assert_nonemptiness
);
1126 void in(ISS
& env
, const bc::JmpNZ
& op
) { jmpImpl
<true>(env
, op
); }
1127 void in(ISS
& env
, const bc::JmpZ
& op
) { jmpImpl
<false>(env
, op
); }
1129 void in(ISS
& env
, const bc::Select
& op
) {
1130 auto const cond
= topC(env
);
1131 auto const t
= topC(env
, 1);
1132 auto const f
= topC(env
, 2);
1137 switch (emptiness(cond
)) {
1138 case Emptiness::Maybe
:
1140 push(env
, union_of(t
, f
));
1142 case Emptiness::NonEmpty
:
1146 case Emptiness::Empty
:
1147 return reduce(env
, bc::PopC
{}, bc::PopC
{});
1154 template<class IsType
, class JmpOp
>
1155 void isTypeHelper(ISS
& env
,
1156 IsTypeOp typeOp
, LocalId location
,
1157 const IsType
& istype
, const JmpOp
& jmp
) {
1158 auto bail
= [&] { impl(env
, istype
, jmp
); };
1159 if (typeOp
== IsTypeOp::Scalar
|| typeOp
== IsTypeOp::ArrLike
) {
1163 auto const val
= istype
.op
== Op::IsTypeC
? topT(env
) : locRaw(env
, location
);
1165 // If the type could be ClsMeth and Arr/Vec, skip location refining.
1166 // Otherwise, refine location based on the testType.
1167 auto testTy
= type_of_istype(typeOp
);
1168 if (val
.couldBe(BClsMeth
)) {
1169 if (RuntimeOption::EvalHackArrDVArrs
) {
1170 if ((typeOp
== IsTypeOp::Vec
) || (typeOp
== IsTypeOp::VArray
)) {
1171 if (val
.couldBe(BVec
| BVArr
)) return bail();
1175 if ((typeOp
== IsTypeOp::Arr
) || (typeOp
== IsTypeOp::VArray
)) {
1176 if (val
.couldBe(BArr
| BVArr
)) return bail();
1182 if (!val
.subtypeOf(BCell
) || val
.subtypeOf(testTy
) || !val
.couldBe(testTy
)) {
1186 if (istype
.op
== Op::IsTypeC
) {
1187 if (!is_type_might_raise(testTy
, val
)) nothrow(env
);
1189 } else if (!locCouldBeUninit(env
, location
) &&
1190 !is_type_might_raise(testTy
, val
)) {
1194 auto const negate
= jmp
.op
== Op::JmpNZ
;
1195 auto const was_true
= [&] (Type t
) {
1196 if (testTy
.subtypeOf(BNull
)) return intersection_of(t
, TNull
);
1197 assertx(!testTy
.couldBe(BNull
));
1198 return intersection_of(t
, testTy
);
1200 auto const was_false
= [&] (Type t
) {
1201 auto tinit
= remove_uninit(t
);
1202 if (testTy
.subtypeOf(BNull
)) {
1203 return is_opt(tinit
) ? unopt(tinit
) : tinit
;
1205 if (is_opt(tinit
)) {
1206 assertx(!testTy
.couldBe(BNull
));
1207 if (unopt(tinit
).subtypeOf(testTy
)) return TNull
;
1212 auto const pre
= [&] (Type t
) {
1213 return negate
? was_true(std::move(t
)) : was_false(std::move(t
));
1216 auto const post
= [&] (Type t
) {
1217 return negate
? was_false(std::move(t
)) : was_true(std::move(t
));
1220 refineLocation(env
, location
, pre
, jmp
.target1
, post
);
1223 folly::Optional
<Cell
> staticLocHelper(ISS
& env
, LocalId l
, Type init
) {
1224 if (is_volatile_local(env
.ctx
.func
, l
)) return folly::none
;
1225 unbindLocalStatic(env
, l
);
1226 setLocRaw(env
, l
, TRef
);
1227 bindLocalStatic(env
, l
, std::move(init
));
1228 if (!env
.ctx
.func
->isMemoizeWrapper
&&
1229 !env
.ctx
.func
->isClosureBody
&&
1230 env
.collect
.localStaticTypes
.size() > l
) {
1231 auto t
= env
.collect
.localStaticTypes
[l
];
1232 if (auto v
= tv(t
)) {
1233 useLocalStatic(env
, l
);
1234 setLocRaw(env
, l
, t
);
1238 useLocalStatic(env
, l
);
1242 // If the current function is a memoize wrapper, return the inferred return type
1243 // of the function being wrapped along with if the wrapped function is effect
1245 std::pair
<Type
, bool> memoizeImplRetType(ISS
& env
) {
1246 always_assert(env
.ctx
.func
->isMemoizeWrapper
);
1248 // Lookup the wrapped function. This should always resolve to a precise
1249 // function but we don't rely on it.
1250 auto const memo_impl_func
= [&] {
1251 if (env
.ctx
.func
->cls
) {
1252 auto const clsTy
= selfClsExact(env
);
1253 return env
.index
.resolve_method(
1255 clsTy
? *clsTy
: TCls
,
1256 memoize_impl_name(env
.ctx
.func
)
1259 return env
.index
.resolve_func(env
.ctx
, memoize_impl_name(env
.ctx
.func
));
1262 // Infer the return type of the wrapped function, taking into account the
1263 // types of the parameters for context sensitive types.
1264 auto const numArgs
= env
.ctx
.func
->params
.size();
1265 CompactVector
<Type
> args
{numArgs
};
1266 for (auto i
= LocalId
{0}; i
< numArgs
; ++i
) {
1267 args
[i
] = locAsCell(env
, i
);
1270 // Determine the context the wrapped function will be called on.
1271 auto const ctxType
= [&]() -> Type
{
1272 if (env
.ctx
.func
->cls
) {
1273 if (env
.ctx
.func
->attrs
& AttrStatic
) {
1274 // The class context for static methods is the method's class,
1275 // if LSB is not specified.
1277 env
.ctx
.func
->isMemoizeWrapperLSB
?
1280 return clsTy
? *clsTy
: TCls
;
1282 auto const s
= thisType(env
);
1283 return s
? *s
: TObj
;
1289 auto retTy
= env
.index
.lookup_return_type(
1295 auto const effectFree
= env
.index
.is_effect_free(memo_impl_func
);
1296 // Regardless of anything we know the return type will be an InitCell (this is
1297 // a requirement of memoize functions).
1298 if (!retTy
.subtypeOf(BInitCell
)) return { TInitCell
, effectFree
};
1299 return { retTy
, effectFree
};
1302 // After a StaticLocCheck, we know the local is bound on the true path,
1303 // and not changed on the false path.
1304 template<class JmpOp
>
1305 void staticLocCheckJmpImpl(ISS
& env
,
1306 const bc::StaticLocCheck
& slc
,
1308 auto const takenOnInit
= jmp
.op
== Op::JmpNZ
;
1309 auto save
= env
.state
;
1311 if (auto const v
= staticLocHelper(env
, slc
.loc1
, TBottom
)) {
1312 return impl(env
, slc
, jmp
);
1315 if (env
.collect
.localStaticTypes
.size() > slc
.loc1
&&
1316 env
.collect
.localStaticTypes
[slc
.loc1
].subtypeOf(BBottom
)) {
1317 env
.state
= std::move(save
);
1319 jmp_nevertaken(env
);
1321 jmp_setdest(env
, jmp
.target1
);
1327 env
.propagate(jmp
.target1
, &env
.state
);
1328 env
.state
= std::move(save
);
1330 env
.propagate(jmp
.target1
, &save
);
1336 template<class JmpOp
>
1337 void group(ISS
& env
, const bc::StaticLocCheck
& slc
, const JmpOp
& jmp
) {
1338 staticLocCheckJmpImpl(env
, slc
, jmp
);
1341 template<class JmpOp
>
1342 void group(ISS
& env
, const bc::StaticLocCheck
& slc
,
1343 const bc::Not
&, const JmpOp
& jmp
) {
1344 staticLocCheckJmpImpl(env
, slc
, invertJmp(jmp
));
1347 template<class JmpOp
>
1348 void group(ISS
& env
, const bc::IsTypeL
& istype
, const JmpOp
& jmp
) {
1349 isTypeHelper(env
, istype
.subop2
, istype
.loc1
, istype
, jmp
);
1352 template<class JmpOp
>
1353 void group(ISS
& env
, const bc::IsTypeL
& istype
,
1354 const bc::Not
&, const JmpOp
& jmp
) {
1355 isTypeHelper(env
, istype
.subop2
, istype
.loc1
, istype
, invertJmp(jmp
));
1358 // If we duplicate a value, and then test its type and Jmp based on that result,
1359 // we can narrow the type of the top of the stack. Only do this for null checks
1360 // right now (because its useful in memoize wrappers).
1361 template<class JmpOp
>
1362 void group(ISS
& env
, const bc::IsTypeC
& istype
, const JmpOp
& jmp
) {
1363 auto const location
= topStkEquiv(env
);
1364 if (location
== NoLocalId
) return impl(env
, istype
, jmp
);
1365 isTypeHelper(env
, istype
.subop1
, location
, istype
, jmp
);
1368 template<class JmpOp
>
1369 void group(ISS
& env
, const bc::IsTypeC
& istype
,
1370 const bc::Not
& negate
, const JmpOp
& jmp
) {
1371 auto const location
= topStkEquiv(env
);
1372 if (location
== NoLocalId
) return impl(env
, istype
, negate
, jmp
);
1373 isTypeHelper(env
, istype
.subop1
, location
, istype
, invertJmp(jmp
));
1378 template<class JmpOp
>
1379 void instanceOfJmpImpl(ISS
& env
,
1380 const bc::InstanceOfD
& inst
,
1382 auto bail
= [&] { impl(env
, inst
, jmp
); };
1384 auto const locId
= topStkEquiv(env
);
1385 if (locId
== NoLocalId
|| interface_supports_non_objects(inst
.str1
)) {
1388 auto const rcls
= env
.index
.resolve_class(env
.ctx
, inst
.str1
);
1389 if (!rcls
) return bail();
1391 auto const val
= topC(env
);
1392 auto const instTy
= subObj(*rcls
);
1393 if (val
.subtypeOf(instTy
) || !val
.couldBe(instTy
)) {
1397 // If we have an optional type, whose unopt is guaranteed to pass
1398 // the instanceof check, then failing to pass implies it was null.
1399 auto const fail_implies_null
= is_opt(val
) && unopt(val
).subtypeOf(instTy
);
1402 auto const negate
= jmp
.op
== Op::JmpNZ
;
1403 auto const result
= [&] (Type t
, bool pass
) {
1404 return pass
? instTy
: fail_implies_null
? TNull
: t
;
1406 auto const pre
= [&] (Type t
) { return result(t
, negate
); };
1407 auto const post
= [&] (Type t
) { return result(t
, !negate
); };
1408 refineLocation(env
, locId
, pre
, jmp
.target1
, post
);
1413 template<class JmpOp
>
1414 void group(ISS
& env
,
1415 const bc::InstanceOfD
& inst
,
1417 instanceOfJmpImpl(env
, inst
, jmp
);
1420 template<class JmpOp
>
1421 void group(ISS
& env
,
1422 const bc::InstanceOfD
& inst
,
1425 instanceOfJmpImpl(env
, inst
, invertJmp(jmp
));
1430 template<class JmpOp
>
1431 void isTypeStructCJmpImpl(ISS
& env
,
1432 const bc::IsTypeStructC
& inst
,
1434 auto bail
= [&] { impl(env
, inst
, jmp
); };
1435 auto const a
= tv(topC(env
));
1436 if (!a
|| !isValidTSType(*a
, false)) return bail();
1438 auto const is_nullable_ts
= is_ts_nullable(a
->m_data
.parr
);
1439 auto const ts_kind
= get_ts_kind(a
->m_data
.parr
);
1440 // type_of_type_structure does not resolve these types. It is important we
1441 // do resolve them here, or we may have issues when we reduce the checks to
1442 // InstanceOfD checks. This logic performs the same exact refinement as
1443 // instanceOfD will.
1444 if (!is_nullable_ts
&&
1445 (ts_kind
== TypeStructure::Kind::T_class
||
1446 ts_kind
== TypeStructure::Kind::T_interface
||
1447 ts_kind
== TypeStructure::Kind::T_xhp
||
1448 ts_kind
== TypeStructure::Kind::T_unresolved
)) {
1449 auto const clsName
= get_ts_classname(a
->m_data
.parr
);
1450 auto const rcls
= env
.index
.resolve_class(env
.ctx
, clsName
);
1451 if (!rcls
|| !rcls
->resolved() || rcls
->cls()->attrs
& AttrEnum
) {
1455 auto const locId
= topStkEquiv(env
, 1);
1456 if (locId
== NoLocalId
|| interface_supports_non_objects(clsName
)) {
1460 auto const val
= topC(env
, 1);
1461 auto const instTy
= subObj(*rcls
);
1462 if (val
.subtypeOf(instTy
) || !val
.couldBe(instTy
)) {
1466 // If we have an optional type, whose unopt is guaranteed to pass
1467 // the instanceof check, then failing to pass implies it was null.
1468 auto const fail_implies_null
= is_opt(val
) && unopt(val
).subtypeOf(instTy
);
1472 auto const negate
= jmp
.op
== Op::JmpNZ
;
1473 auto const result
= [&] (Type t
, bool pass
) {
1474 return pass
? instTy
: fail_implies_null
? TNull
: t
;
1476 auto const pre
= [&] (Type t
) { return result(t
, negate
); };
1477 auto const post
= [&] (Type t
) { return result(t
, !negate
); };
1478 refineLocation(env
, locId
, pre
, jmp
.target1
, post
);
1486 template<class JmpOp
>
1487 void group(ISS
& env
,
1488 const bc::IsTypeStructC
& inst
,
1490 isTypeStructCJmpImpl(env
, inst
, jmp
);
1493 template<class JmpOp
>
1494 void group(ISS
& env
,
1495 const bc::IsTypeStructC
& inst
,
1498 isTypeStructCJmpImpl(env
, inst
, invertJmp(jmp
));
1501 void in(ISS
& env
, const bc::Switch
& op
) {
1502 auto v
= tv(popC(env
));
1505 auto go
= [&] (BlockId blk
) {
1507 jmp_setdest(env
, blk
);
1509 auto num_elems
= op
.targets
.size();
1510 if (op
.subop1
== SwitchKind::Unbounded
) {
1511 if (v
->m_type
== KindOfInt64
&&
1512 v
->m_data
.num
>= 0 && v
->m_data
.num
< num_elems
) {
1513 return go(op
.targets
[v
->m_data
.num
]);
1516 assertx(num_elems
> 2);
1518 for (auto i
= size_t{}; ; i
++) {
1519 if (i
== num_elems
) {
1520 return go(op
.targets
.back());
1522 auto match
= eval_cell_value([&] {
1523 return cellEqual(*v
, static_cast<int64_t>(op
.arg2
+ i
));
1527 return go(op
.targets
[i
]);
1533 forEachTakenEdge(op
, [&] (BlockId id
) {
1534 env
.propagate(id
, &env
.state
);
1538 void in(ISS
& env
, const bc::SSwitch
& op
) {
1539 auto v
= tv(popC(env
));
1542 for (auto& kv
: op
.targets
) {
1543 auto match
= eval_cell_value([&] {
1544 return !kv
.first
|| cellEqual(*v
, kv
.first
);
1549 jmp_setdest(env
, kv
.second
);
1555 forEachTakenEdge(op
, [&] (BlockId id
) {
1556 env
.propagate(id
, &env
.state
);
1560 void in(ISS
& env
, const bc::RetC
& /*op*/) {
1561 auto const locEquiv
= topStkLocal(env
);
1562 doRet(env
, popC(env
), false);
1563 if (locEquiv
!= NoLocalId
&& locEquiv
< env
.ctx
.func
->params
.size()) {
1564 env
.flags
.retParam
= locEquiv
;
1567 void in(ISS
& env
, const bc::RetM
& op
) {
1568 std::vector
<Type
> ret(op
.arg1
);
1569 for (int i
= 0; i
< op
.arg1
; i
++) {
1570 ret
[op
.arg1
- i
- 1] = popC(env
);
1572 doRet(env
, vec(std::move(ret
)), false);
1575 void in(ISS
& env
, const bc::RetCSuspended
&) {
1576 always_assert(env
.ctx
.func
->isAsync
&& !env
.ctx
.func
->isGenerator
);
1578 auto const t
= popC(env
);
1581 is_specialized_wait_handle(t
) ? wait_handle_inner(t
) : TInitCell
,
1586 void in(ISS
& env
, const bc::Unwind
&) {
1587 nothrow(env
); // Don't propagate to throw edges
1588 for (auto exit
: env
.blk
.unwindExits
) {
1589 auto const stackLess
= without_stacks(env
.state
);
1590 env
.propagate(exit
, &stackLess
);
1594 void in(ISS
& env
, const bc::Throw
& /*op*/) {
1598 void in(ISS
& env
, const bc::Catch
&) {
1600 return push(env
, subObj(env
.index
.builtin_class(s_Throwable
.get())));
1603 void in(ISS
& env
, const bc::ChainFaults
&) {
1607 void in(ISS
& env
, const bc::NativeImpl
&) {
1611 if (is_collection_method_returning_this(env
.ctx
.cls
, env
.ctx
.func
)) {
1612 auto const resCls
= env
.index
.builtin_class(env
.ctx
.cls
->name
);
1613 return doRet(env
, objExact(resCls
), true);
1616 if (env
.ctx
.func
->nativeInfo
) {
1617 return doRet(env
, native_function_return_type(env
.ctx
.func
), true);
1619 doRet(env
, TInitCell
, true);
1622 void in(ISS
& env
, const bc::CGetL
& op
) {
1623 if (locIsThis(env
, op
.loc1
)) {
1624 auto const subop
= peekLocRaw(env
, op
.loc1
).couldBe(BUninit
) ?
1625 BareThisOp::Notice
: BareThisOp::NoNotice
;
1626 return reduce(env
, bc::BareThis
{ subop
});
1628 if (!locCouldBeUninit(env
, op
.loc1
)) {
1632 push(env
, locAsCell(env
, op
.loc1
), op
.loc1
);
1635 void in(ISS
& env
, const bc::CGetQuietL
& op
) {
1636 if (locIsThis(env
, op
.loc1
)) {
1637 return reduce(env
, bc::BareThis
{ BareThisOp::NoNotice
});
1641 push(env
, locAsCell(env
, op
.loc1
), op
.loc1
);
1644 void in(ISS
& env
, const bc::CUGetL
& op
) {
1645 auto ty
= locRaw(env
, op
.loc1
);
1646 if (ty
.subtypeOf(BUninit
)) {
1647 return reduce(env
, bc::NullUninit
{});
1650 if (!ty
.couldBe(BUninit
)) constprop(env
);
1651 if (!ty
.subtypeOf(BCell
)) ty
= TCell
;
1652 push(env
, std::move(ty
), op
.loc1
);
1655 void in(ISS
& env
, const bc::PushL
& op
) {
1656 if (auto val
= tv(locRaw(env
, op
.loc1
))) {
1657 return reduce(env
, gen_constant(*val
), bc::UnsetL
{ op
.loc1
});
1659 impl(env
, bc::CGetL
{ op
.loc1
}, bc::UnsetL
{ op
.loc1
});
1662 void in(ISS
& env
, const bc::CGetL2
& op
) {
1663 // Can't constprop yet because of no INS_1 support in bc.h
1664 if (!locCouldBeUninit(env
, op
.loc1
)) effect_free(env
);
1665 auto loc
= locAsCell(env
, op
.loc1
);
1666 auto topEquiv
= topStkLocal(env
);
1667 auto top
= popT(env
);
1668 push(env
, std::move(loc
), op
.loc1
);
1669 push(env
, std::move(top
), topEquiv
);
1672 void in(ISS
& env
, const bc::CGetG
&) { popC(env
); push(env
, TInitCell
); }
1673 void in(ISS
& env
, const bc::CGetQuietG
&) { popC(env
); push(env
, TInitCell
); }
1675 void in(ISS
& env
, const bc::CGetS
& op
) {
1676 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1677 auto const tname
= popC(env
);
1678 auto const vname
= tv(tname
);
1679 auto const self
= selfCls(env
);
1681 if (vname
&& vname
->m_type
== KindOfPersistentString
&&
1682 self
&& tcls
.subtypeOf(*self
)) {
1683 if (auto ty
= selfPropAsCell(env
, vname
->m_data
.pstr
)) {
1684 // Only nothrow when we know it's a private declared property (and thus
1685 // accessible here), class initialization won't throw, and its not a
1686 // LateInit prop (which will throw if not initialized).
1687 if (!classInitMightRaise(env
, tcls
) &&
1688 !isMaybeLateInitSelfProp(env
, vname
->m_data
.pstr
)) {
1691 // We can only constprop here if we know for sure this is exactly the
1692 // correct class. The reason for this is that you could have a LSB
1693 // class attempting to access a private static in a derived class with
1694 // the same name as a private static in this class, which is supposed to
1695 // fatal at runtime (for an example see test/quick/static_sprop2.php).
1696 auto const selfExact
= selfClsExact(env
);
1697 if (selfExact
&& tcls
.subtypeOf(*selfExact
)) constprop(env
);
1700 if (ty
->subtypeOf(BBottom
)) unreachable(env
);
1701 return push(env
, std::move(*ty
));
1705 auto indexTy
= env
.index
.lookup_public_static(env
.ctx
, tcls
, tname
);
1706 if (indexTy
.subtypeOf(BInitCell
)) {
1708 * Constant propagation here can change when we invoke autoload, so it's
1709 * considered HardConstProp. It's safe not to check anything about private
1710 * or protected static properties, because you can't override a public
1711 * static property with a private or protected one---if the index gave us
1712 * back a constant type, it's because it found a public static and it must
1713 * be the property this would have read dynamically.
1715 if (options
.HardConstProp
&&
1716 !classInitMightRaise(env
, tcls
) &&
1717 !env
.index
.lookup_public_static_maybe_late_init(tcls
, tname
)) {
1720 if (indexTy
.subtypeOf(BBottom
)) unreachable(env
);
1721 return push(env
, std::move(indexTy
));
1724 push(env
, TInitCell
);
1727 void in(ISS
& env
, const bc::VGetL
& op
) {
1729 setLocRaw(env
, op
.loc1
, TRef
);
1733 void in(ISS
& env
, const bc::VGetG
&) { popC(env
); push(env
, TRef
); }
1735 void in(ISS
& env
, const bc::VGetS
& op
) {
1736 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
1737 auto const tname
= popC(env
);
1738 auto const vname
= tv(tname
);
1739 auto const self
= selfCls(env
);
1741 if (!self
|| tcls
.couldBe(*self
)) {
1742 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
1743 boxSelfProp(env
, vname
->m_data
.pstr
);
1749 env
.collect
.publicSPropMutations
.merge(
1750 env
.index
, env
.ctx
, tcls
, tname
, TRef
1756 void clsRefGetImpl(ISS
& env
, Type t1
, ClsRefSlotId slot
) {
1758 if (auto const clsname
= getNameFromType(t1
)) {
1759 auto const rcls
= env
.index
.resolve_class(env
.ctx
, clsname
);
1760 if (rcls
) return clsExact(*rcls
);
1762 if (t1
.subtypeOf(BObj
)) {
1768 putClsRefSlot(env
, slot
, std::move(cls
));
1771 void in(ISS
& env
, const bc::ClsRefGetC
& op
) {
1772 clsRefGetImpl(env
, popC(env
), op
.slot
);
1775 void in(ISS
& env
, const bc::ClsRefGetTS
& op
) {
1776 // TODO(T31677864): implement real optimizations
1777 auto const ts
= popC(env
);
1778 auto const requiredTSType
= RuntimeOption::EvalHackArrDVArrs
? BDict
: BDArr
;
1779 if (!ts
.couldBe(requiredTSType
)) {
1783 clsRefGetImpl(env
, TStr
, op
.slot
);
1786 void in(ISS
& env
, const bc::AKExists
& /*op*/) {
1787 auto const base
= popC(env
);
1788 auto const key
= popC(env
);
1790 // Bases other than array-like or object will raise a warning and return
1792 if (!base
.couldBeAny(TArr
, TVec
, TDict
, TKeyset
, TObj
)) {
1793 return push(env
, TFalse
);
1796 // Push the returned type and annotate effects appropriately, taking into
1797 // account if the base might be null. Allowing for a possibly null base lets
1798 // us capture more cases.
1799 auto const finish
= [&] (const Type
& t
, bool mayThrow
) {
1800 if (base
.couldBe(BInitNull
)) return push(env
, union_of(t
, TFalse
));
1805 if (base
.subtypeOf(BBottom
)) unreachable(env
);
1806 return push(env
, t
);
1809 // Helper for Hack arrays. "validKey" is the set of key types which can return
1810 // a value from AKExists. "silentKey" is the set of key types which will
1811 // silently return false (anything else throws). The Hack array elem functions
1812 // will treat values of "silentKey" as throwing, so we must identify those
1813 // cases and deal with them.
1814 auto const hackArr
= [&] (std::pair
<Type
, ThrowMode
> elem
,
1815 const Type
& validKey
,
1816 const Type
& silentKey
) {
1817 switch (elem
.second
) {
1818 case ThrowMode::None
:
1819 assertx(key
.subtypeOf(validKey
));
1820 return finish(TTrue
, false);
1821 case ThrowMode::MaybeMissingElement
:
1822 assertx(key
.subtypeOf(validKey
));
1823 return finish(TBool
, false);
1824 case ThrowMode::MissingElement
:
1825 assertx(key
.subtypeOf(validKey
));
1826 return finish(TFalse
, false);
1827 case ThrowMode::MaybeBadKey
:
1828 assertx(key
.couldBe(validKey
));
1830 elem
.first
.subtypeOf(BBottom
) ? TFalse
: TBool
,
1831 !key
.subtypeOf(BOptArrKey
)
1833 case ThrowMode::BadOperation
:
1834 assertx(!key
.couldBe(validKey
));
1835 return finish(key
.couldBe(silentKey
) ? TFalse
: TBottom
, true);
1839 // Vecs will throw for any key other than Int, Str, or Null, and will silently
1840 // return false for the latter two.
1841 if (base
.subtypeOrNull(BVec
)) {
1842 if (key
.subtypeOrNull(BStr
)) return finish(TFalse
, false);
1843 return hackArr(vec_elem(base
, key
, TBottom
), TInt
, TOptStr
);
1846 // Dicts and keysets will throw for any key other than Int, Str, or Null,
1847 // and will silently return false for Null.
1848 if (base
.subtypeOfAny(TOptDict
, TOptKeyset
)) {
1849 if (key
.subtypeOf(BInitNull
)) return finish(TFalse
, false);
1850 auto const elem
= base
.subtypeOrNull(BDict
)
1851 ? dict_elem(base
, key
, TBottom
)
1852 : keyset_elem(base
, key
, TBottom
);
1853 return hackArr(elem
, TArrKey
, TInitNull
);
1856 if (base
.subtypeOrNull(BArr
)) {
1857 // Unlike Idx, AKExists will transform a null key on arrays into the static
1858 // empty string, so we don't need to do any fixups here.
1859 auto const elem
= array_elem(base
, key
, TBottom
);
1860 switch (elem
.second
) {
1861 case ThrowMode::None
: return finish(TTrue
, false);
1862 case ThrowMode::MaybeMissingElement
: return finish(TBool
, false);
1863 case ThrowMode::MissingElement
: return finish(TFalse
, false);
1864 case ThrowMode::MaybeBadKey
:
1865 return finish(elem
.first
.subtypeOf(BBottom
) ? TFalse
: TBool
, true);
1866 case ThrowMode::BadOperation
: always_assert(false);
1870 // Objects or other unions of possible bases
1874 void in(ISS
& env
, const bc::GetMemoKeyL
& op
) {
1875 always_assert(env
.ctx
.func
->isMemoizeWrapper
);
1877 auto const rclsIMemoizeParam
= env
.index
.builtin_class(s_IMemoizeParam
.get());
1878 auto const tyIMemoizeParam
= subObj(rclsIMemoizeParam
);
1880 auto const inTy
= locAsCell(env
, op
.loc1
);
1882 // If the local could be uninit, we might raise a warning (as
1883 // usual). Converting an object to a memo key might invoke PHP code if it has
1884 // the IMemoizeParam interface, and if it doesn't, we'll throw.
1885 if (!locCouldBeUninit(env
, op
.loc1
) &&
1886 !inTy
.couldBeAny(TObj
, TArr
, TVec
, TDict
)) {
1887 nothrow(env
); constprop(env
);
1890 // If type constraints are being enforced and the local being turned into a
1891 // memo key is a parameter, then we can possibly using the type constraint to
1892 // infer a more efficient memo key mode.
1893 using MK
= MemoKeyConstraint
;
1894 folly::Optional
<res::Class
> resolvedCls
;
1895 auto const mkc
= [&] {
1896 if (!RuntimeOption::EvalHardTypeHints
) return MK::None
;
1897 if (op
.loc1
>= env
.ctx
.func
->params
.size()) return MK::None
;
1898 auto tc
= env
.ctx
.func
->params
[op
.loc1
].typeConstraint
;
1899 if (tc
.type() == AnnotType::Object
) {
1900 auto res
= env
.index
.resolve_type_name(tc
.typeName());
1901 if (res
.type
!= AnnotType::Object
) {
1902 tc
.resolveType(res
.type
, res
.nullable
|| tc
.isNullable());
1904 resolvedCls
= env
.index
.resolve_class(env
.ctx
, tc
.typeName());
1907 return memoKeyConstraintFromTC(tc
);
1910 // Use the type-constraint to reduce this operation to a more efficient memo
1911 // mode. Some of the modes can be reduced to simple bytecode operations
1912 // inline. Even with the type-constraints, we still need to check the inferred
1913 // type of the local. Something may have possibly clobbered the local between
1914 // the type-check and this op.
1917 // Always an int, so the key is always an identity mapping
1918 if (inTy
.subtypeOf(BInt
)) return reduce(env
, bc::CGetL
{ op
.loc1
});
1921 // Always a bool, so the key is the bool cast to an int
1922 if (inTy
.subtypeOf(BBool
)) {
1923 return reduce(env
, bc::CGetL
{ op
.loc1
}, bc::CastInt
{});
1927 // Always a string, so the key is always an identity mapping
1928 if (inTy
.subtypeOf(BStr
)) return reduce(env
, bc::CGetL
{ op
.loc1
});
1931 // Either an int or string, so the key can be an identity mapping
1932 if (inTy
.subtypeOf(BArrKey
)) return reduce(env
, bc::CGetL
{ op
.loc1
});
1935 // A nullable string. The key will either be the string or the integer
1937 if (inTy
.subtypeOrNull(BStr
)) {
1940 bc::CGetL
{ op
.loc1
},
1942 bc::IsTypeL
{ op
.loc1
, IsTypeOp::Null
},
1948 // A nullable int. The key will either be the integer, or the static empty
1950 if (inTy
.subtypeOrNull(BInt
)) {
1953 bc::CGetL
{ op
.loc1
},
1954 bc::String
{ staticEmptyString() },
1955 bc::IsTypeL
{ op
.loc1
, IsTypeOp::Null
},
1960 case MK::BoolOrNull
:
1961 // A nullable bool. The key will either be 0, 1, or 2.
1962 if (inTy
.subtypeOrNull(BBool
)) {
1965 bc::CGetL
{ op
.loc1
},
1968 bc::IsTypeL
{ op
.loc1
, IsTypeOp::Null
},
1974 // The double will be converted (losslessly) to an integer.
1975 if (inTy
.subtypeOf(BDbl
)) {
1976 return reduce(env
, bc::CGetL
{ op
.loc1
}, bc::DblAsBits
{});
1980 // A nullable double. The key will be an integer, or the static empty
1982 if (inTy
.subtypeOrNull(BDbl
)) {
1985 bc::CGetL
{ op
.loc1
},
1987 bc::String
{ staticEmptyString() },
1988 bc::IsTypeL
{ op
.loc1
, IsTypeOp::Null
},
1994 // An object. If the object is definitely known to implement IMemoizeParam
1995 // we can simply call that method, casting the output to ensure its always
1996 // a string (which is what the generic mode does). If not, it will use the
1997 // generic mode, which can handle collections or classes which don't
1998 // implement getInstanceKey.
2000 resolvedCls
->subtypeOf(rclsIMemoizeParam
) &&
2001 inTy
.subtypeOf(tyIMemoizeParam
)) {
2004 bc::CGetL
{ op
.loc1
},
2005 bc::FPushObjMethodD
{
2007 s_getInstanceKey
.get(),
2008 ObjMethodOp::NullThrows
,
2011 bc::FCall
{ FCallArgs(0), staticEmptyString(), staticEmptyString() },
2016 case MK::ObjectOrNull
:
2017 // An object or null. We can use the null safe version of a function call
2018 // when invoking getInstanceKey and then select from the result of that,
2019 // or the integer 0. This might seem wasteful, but the JIT does a good job
2020 // inlining away the call in the null case.
2022 resolvedCls
->subtypeOf(rclsIMemoizeParam
) &&
2023 inTy
.subtypeOf(opt(tyIMemoizeParam
))) {
2026 bc::CGetL
{ op
.loc1
},
2027 bc::FPushObjMethodD
{
2029 s_getInstanceKey
.get(),
2030 ObjMethodOp::NullSafe
,
2033 bc::FCall
{ FCallArgs(0), staticEmptyString(), staticEmptyString() },
2036 bc::IsTypeL
{ op
.loc1
, IsTypeOp::Null
},
2045 // No type constraint, or one that isn't usuable. Use the generic memoization
2046 // scheme which can handle any type:
2048 if (auto const val
= tv(inTy
)) {
2049 auto const key
= eval_cell(
2050 [&]{ return HHVM_FN(serialize_memoize_param
)(*val
); }
2052 if (key
) return push(env
, *key
);
2055 // Integer keys are always mapped to themselves
2056 if (inTy
.subtypeOf(BInt
)) return reduce(env
, bc::CGetL
{ op
.loc1
});
2057 if (inTy
.subtypeOrNull(BInt
)) {
2060 bc::CGetL
{ op
.loc1
},
2061 bc::String
{ s_nullMemoKey
.get() },
2062 bc::IsTypeL
{ op
.loc1
, IsTypeOp::Null
},
2066 if (inTy
.subtypeOf(BBool
)) {
2069 bc::String
{ s_falseMemoKey
.get() },
2070 bc::String
{ s_trueMemoKey
.get() },
2071 bc::CGetL
{ op
.loc1
},
2076 // A memo key can be an integer if the input might be an integer, and is a
2077 // string otherwise. Booleans and nulls are always static strings.
2079 if (inTy
.subtypeOrNull(BBool
)) return TSStr
;
2080 if (inTy
.couldBe(BInt
)) return union_of(TInt
, TStr
);
2083 push(env
, std::move(keyTy
));
2086 void in(ISS
& env
, const bc::IssetL
& op
) {
2087 if (locIsThis(env
, op
.loc1
)) {
2089 bc::BareThis
{ BareThisOp::NoNotice
},
2090 bc::IsTypeC
{ IsTypeOp::Null
},
2095 auto const loc
= locAsCell(env
, op
.loc1
);
2096 if (loc
.subtypeOf(BNull
)) return push(env
, TFalse
);
2097 if (!loc
.couldBe(BNull
)) return push(env
, TTrue
);
2101 void in(ISS
& env
, const bc::EmptyL
& op
) {
2104 castBoolImpl(env
, locAsCell(env
, op
.loc1
), true);
2107 void in(ISS
& env
, const bc::EmptyS
& op
) {
2108 takeClsRefSlot(env
, op
.slot
);
2113 void in(ISS
& env
, const bc::IssetS
& op
) {
2114 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
2115 auto const tname
= popC(env
);
2116 auto const vname
= tv(tname
);
2117 auto const self
= selfCls(env
);
2119 if (self
&& tcls
.subtypeOf(*self
) &&
2120 vname
&& vname
->m_type
== KindOfPersistentString
) {
2121 if (auto const t
= selfPropAsCell(env
, vname
->m_data
.pstr
)) {
2122 if (isMaybeLateInitSelfProp(env
, vname
->m_data
.pstr
)) {
2123 if (!classInitMightRaise(env
, tcls
)) constprop(env
);
2124 return push(env
, t
->subtypeOf(BBottom
) ? TFalse
: TBool
);
2126 if (t
->subtypeOf(BNull
)) {
2127 if (!classInitMightRaise(env
, tcls
)) constprop(env
);
2128 return push(env
, TFalse
);
2130 if (!t
->couldBe(BNull
)) {
2131 if (!classInitMightRaise(env
, tcls
)) constprop(env
);
2132 return push(env
, TTrue
);
2137 auto const indexTy
= env
.index
.lookup_public_static(env
.ctx
, tcls
, tname
);
2138 if (indexTy
.subtypeOf(BInitCell
)) {
2139 // See the comments in CGetS about constprop for public statics.
2140 if (options
.HardConstProp
&& !classInitMightRaise(env
, tcls
)) {
2143 if (env
.index
.lookup_public_static_maybe_late_init(tcls
, tname
)) {
2144 return push(env
, indexTy
.subtypeOf(BBottom
) ? TFalse
: TBool
);
2146 if (indexTy
.subtypeOf(BNull
)) { return push(env
, TFalse
); }
2147 if (!indexTy
.couldBe(BNull
)) { return push(env
, TTrue
); }
2153 void in(ISS
& env
, const bc::EmptyG
&) { popC(env
); push(env
, TBool
); }
2154 void in(ISS
& env
, const bc::IssetG
&) { popC(env
); push(env
, TBool
); }
2156 void isTypeImpl(ISS
& env
, const Type
& locOrCell
, const Type
& test
) {
2157 if (locOrCell
.subtypeOf(test
)) return push(env
, TTrue
);
2158 if (!locOrCell
.couldBe(test
)) return push(env
, TFalse
);
2162 void isTypeObj(ISS
& env
, const Type
& ty
) {
2163 if (!ty
.couldBe(BObj
)) return push(env
, TFalse
);
2164 if (ty
.subtypeOf(BObj
)) {
2165 auto const incompl
= objExact(
2166 env
.index
.builtin_class(s_PHP_Incomplete_Class
.get()));
2167 if (!ty
.couldBe(incompl
)) return push(env
, TTrue
);
2168 if (ty
.subtypeOf(incompl
)) return push(env
, TFalse
);
2173 void isTypeArrLike(ISS
& env
, const Type
& ty
) {
2174 if (ty
.subtypeOf(BArr
| BVec
| BDict
| BKeyset
| BClsMeth
)) {
2175 return push(env
, TTrue
);
2177 if (!ty
.couldBe(BArr
| BVec
| BDict
| BKeyset
| BClsMeth
)) {
2178 return push(env
, TFalse
);
2184 bool isCompactTypeClsMeth(ISS
& env
, IsTypeOp op
, const Type
& t
) {
2185 if (t
.couldBe(BClsMeth
)) {
2186 if (RuntimeOption::EvalHackArrDVArrs
) {
2187 if (op
== IsTypeOp::Vec
|| op
== IsTypeOp::VArray
) {
2189 op
== IsTypeOp::Vec
? BClsMeth
| BVec
: BClsMeth
| BVArr
)) {
2191 } else if (t
.couldBe(op
== IsTypeOp::Vec
? BVec
: BVArr
)) {
2194 isTypeImpl(env
, t
, TClsMeth
);
2199 if (op
== IsTypeOp::Arr
|| op
== IsTypeOp::VArray
) {
2201 op
== IsTypeOp::VArray
? BClsMeth
| BVArr
: BClsMeth
| BArr
)) {
2203 } else if (t
.couldBe(op
== IsTypeOp::VArray
? BVArr
: BArr
)) {
2206 isTypeImpl(env
, t
, TClsMeth
);
2217 void isTypeLImpl(ISS
& env
, const Op
& op
) {
2218 auto const loc
= locAsCell(env
, op
.loc1
);
2219 if (!locCouldBeUninit(env
, op
.loc1
) && !is_type_might_raise(op
.subop2
, loc
)) {
2224 if (isCompactTypeClsMeth(env
, op
.subop2
, loc
)) return;
2226 switch (op
.subop2
) {
2227 case IsTypeOp::Scalar
: return push(env
, TBool
);
2228 case IsTypeOp::Obj
: return isTypeObj(env
, loc
);
2229 case IsTypeOp::ArrLike
: return isTypeArrLike(env
, loc
);
2230 default: return isTypeImpl(env
, loc
, type_of_istype(op
.subop2
));
2235 void isTypeCImpl(ISS
& env
, const Op
& op
) {
2236 auto const t1
= popC(env
);
2237 if (!is_type_might_raise(op
.subop1
, t1
)) {
2242 if (isCompactTypeClsMeth(env
, op
.subop1
, t1
)) return;
2244 switch (op
.subop1
) {
2245 case IsTypeOp::Scalar
: return push(env
, TBool
);
2246 case IsTypeOp::Obj
: return isTypeObj(env
, t1
);
2247 case IsTypeOp::ArrLike
: return isTypeArrLike(env
, t1
);
2248 default: return isTypeImpl(env
, t1
, type_of_istype(op
.subop1
));
2252 void in(ISS
& env
, const bc::IsTypeC
& op
) { isTypeCImpl(env
, op
); }
2253 void in(ISS
& env
, const bc::IsTypeL
& op
) { isTypeLImpl(env
, op
); }
2255 void in(ISS
& env
, const bc::InstanceOfD
& op
) {
2256 auto t1
= topC(env
);
2257 // Note: InstanceOfD can do autoload if the type might be a type
2258 // alias, so it's not nothrow unless we know it's an object type.
2259 if (auto const rcls
= env
.index
.resolve_class(env
.ctx
, op
.str1
)) {
2260 auto result
= [&] (const Type
& r
) {
2262 if (r
!= TBool
) constprop(env
);
2266 if (!interface_supports_non_objects(rcls
->name())) {
2267 auto testTy
= subObj(*rcls
);
2268 if (t1
.subtypeOf(testTy
)) return result(TTrue
);
2269 if (!t1
.couldBe(testTy
)) return result(TFalse
);
2271 t1
= unopt(std::move(t1
));
2272 if (t1
.subtypeOf(testTy
)) {
2273 return reduce(env
, bc::IsTypeC
{ IsTypeOp::Null
}, bc::Not
{});
2276 return result(TBool
);
2283 void in(ISS
& env
, const bc::InstanceOf
& /*op*/) {
2284 auto const t1
= topC(env
);
2285 auto const v1
= tv(t1
);
2286 if (v1
&& v1
->m_type
== KindOfPersistentString
) {
2287 return reduce(env
, bc::PopC
{},
2288 bc::InstanceOfD
{ v1
->m_data
.pstr
});
2291 if (t1
.subtypeOf(BObj
) && is_specialized_obj(t1
)) {
2292 auto const dobj
= dobj_of(t1
);
2293 switch (dobj
.type
) {
2297 return reduce(env
, bc::PopC
{},
2298 bc::InstanceOfD
{ dobj
.cls
.name() });
2309 bool isValidTypeOpForIsAs(const IsTypeOp
& op
) {
2311 case IsTypeOp::Null
:
2312 case IsTypeOp::Bool
:
2321 case IsTypeOp::Dict
:
2322 case IsTypeOp::Keyset
:
2323 case IsTypeOp::VArray
:
2324 case IsTypeOp::DArray
:
2325 case IsTypeOp::ArrLike
:
2326 case IsTypeOp::Scalar
:
2327 case IsTypeOp::ClsMeth
:
2333 template<bool asExpression
>
2334 void isAsTypeStructImpl(ISS
& env
, SArray ts
) {
2335 auto const t
= topC(env
, 1); // operand to is/as
2339 const folly::Optional
<Type
>& test
= folly::none
2341 if (asExpression
&& out
.subtypeOf(BTrue
)) {
2343 return reduce(env
, bc::PopC
{}, bc::Nop
{});
2345 auto const location
= topStkEquiv(env
, 1);
2346 popC(env
); // type structure
2347 popC(env
); // operand to is/as
2348 if (!asExpression
) {
2350 return push(env
, out
);
2352 if (out
.subtypeOf(BFalse
)) {
2354 return unreachable(env
);
2357 assertx(out
== TBool
);
2358 if (!test
) return push(env
, t
);
2359 auto const newT
= intersection_of(*test
, t
);
2360 if (newT
== TBottom
|| !refineLocation(env
, location
, [&] (Type t
) {
2361 auto ret
= intersection_of(*test
, t
);
2362 if (test
->couldBe(BInitNull
) && t
.couldBe(BUninit
)) {
2369 return push(env
, newT
);
2373 const folly::Optional
<Type
> type
,
2374 const folly::Optional
<Type
> deopt
= folly::none
2376 if (!type
|| is_type_might_raise(*type
, t
)) return result(TBool
);
2377 auto test
= type
.value();
2378 if (t
.couldBe(BClsMeth
)) {
2379 if (RuntimeOption::EvalHackArrDVArrs
) {
2381 if (t
.subtypeOf(BClsMeth
| BVec
)) return result(TTrue
);
2382 else if (t
.couldBe(BVec
)) return result(TBool
);
2383 else test
= TClsMeth
;
2384 } else if (test
== TVArr
) {
2385 if (t
.subtypeOf(BClsMeth
| BVArr
)) return result(TTrue
);
2386 else if (t
.couldBe(BVArr
)) return result(TBool
);
2387 else test
= TClsMeth
;
2390 if (test
== TVArr
) {
2391 if (t
.subtypeOf(BClsMeth
| BVArr
)) return result(TTrue
);
2392 else if (t
.couldBe(BVArr
)) return result(TBool
);
2393 else test
= TClsMeth
;
2394 } else if (test
== TArr
) {
2395 if (t
.subtypeOf(BClsMeth
| BArr
)) return result(TTrue
);
2396 else if (t
.couldBe(BArr
)) return result(TBool
);
2397 else test
= TClsMeth
;
2401 if (t
.subtypeOf(test
)) return result(TTrue
);
2402 if (!t
.couldBe(test
) && (!deopt
|| !t
.couldBe(deopt
.value()))) {
2403 return result(TFalse
);
2405 auto const op
= type_to_istypeop(test
);
2406 if (asExpression
|| !op
|| !isValidTypeOpForIsAs(op
.value())) {
2407 return result(TBool
, test
);
2409 return reduce(env
, bc::PopC
{}, bc::IsTypeC
{ *op
});
2412 auto const is_nullable_ts
= is_ts_nullable(ts
);
2413 auto const is_definitely_null
= t
.subtypeOf(BNull
);
2414 auto const is_definitely_not_null
= !t
.couldBe(BNull
);
2416 if (is_nullable_ts
&& is_definitely_null
) return result(TTrue
);
2418 auto const ts_type
= type_of_type_structure(ts
);
2420 if (is_nullable_ts
&& !is_definitely_not_null
&& ts_type
== folly::none
) {
2421 // Ts is nullable and we know that t could be null but we dont know for sure
2422 // Also we didn't get a type out of the type structure
2423 return result(TBool
);
2426 if (!asExpression
) {
2427 if (ts_type
&& !is_type_might_raise(*ts_type
, t
)) nothrow(env
);
2430 switch (get_ts_kind(ts
)) {
2431 case TypeStructure::Kind::T_int
:
2432 case TypeStructure::Kind::T_bool
:
2433 case TypeStructure::Kind::T_float
:
2434 case TypeStructure::Kind::T_string
:
2435 case TypeStructure::Kind::T_num
:
2436 case TypeStructure::Kind::T_arraykey
:
2437 case TypeStructure::Kind::T_keyset
:
2438 case TypeStructure::Kind::T_void
:
2439 case TypeStructure::Kind::T_null
:
2440 return check(ts_type
);
2441 case TypeStructure::Kind::T_tuple
:
2442 return RuntimeOption::EvalHackArrCompatIsArrayNotices
2443 ? check(ts_type
, TDArr
)
2445 case TypeStructure::Kind::T_shape
:
2446 return RuntimeOption::EvalHackArrCompatIsArrayNotices
2447 ? check(ts_type
, TVArr
)
2449 case TypeStructure::Kind::T_dict
:
2450 return check(ts_type
, TDArr
);
2451 case TypeStructure::Kind::T_vec
:
2452 return check(ts_type
, TVArr
);
2453 case TypeStructure::Kind::T_noreturn
:
2454 return result(TFalse
);
2455 case TypeStructure::Kind::T_mixed
:
2456 return result(TTrue
);
2457 case TypeStructure::Kind::T_nonnull
:
2458 if (is_definitely_null
) return result(TFalse
);
2459 if (is_definitely_not_null
) return result(TTrue
);
2460 if (!asExpression
) {
2463 bc::IsTypeC
{ IsTypeOp::Null
},
2466 return result(TBool
);
2467 case TypeStructure::Kind::T_class
:
2468 case TypeStructure::Kind::T_interface
:
2469 case TypeStructure::Kind::T_xhp
: {
2470 if (asExpression
) return result(TBool
);
2471 auto clsname
= get_ts_classname(ts
);
2472 auto const rcls
= env
.index
.resolve_class(env
.ctx
, clsname
);
2473 if (!rcls
|| !rcls
->resolved() || (ts
->exists(s_generic_types
) &&
2474 (rcls
->cls()->hasReifiedGenerics
||
2475 !isTSAllWildcards(ts
)))) {
2476 // If it is a reified class or has non wildcard generics,
2478 return result(TBool
);
2480 return reduce(env
, bc::PopC
{}, bc::InstanceOfD
{ clsname
});
2482 case TypeStructure::Kind::T_unresolved
: {
2483 if (asExpression
) return result(TBool
);
2484 auto const rcls
= env
.index
.resolve_class(env
.ctx
, get_ts_classname(ts
));
2485 // We can only reduce to instance of if we know for sure that this class
2486 // can be resolved since instanceof undefined class does not throw
2487 if (!rcls
|| !rcls
->resolved() || rcls
->cls()->attrs
& AttrEnum
) {
2488 return result(TBool
);
2490 if (ts
->exists(s_generic_types
) &&
2491 (rcls
->cls()->hasReifiedGenerics
|| !isTSAllWildcards(ts
))) {
2492 // If it is a reified class or has non wildcard generics,
2494 return result(TBool
);
2496 return reduce(env
, bc::PopC
{}, bc::InstanceOfD
{ rcls
->name() });
2498 case TypeStructure::Kind::T_enum
:
2499 case TypeStructure::Kind::T_resource
:
2500 case TypeStructure::Kind::T_vec_or_dict
:
2501 case TypeStructure::Kind::T_arraylike
:
2502 // TODO(T29232862): implement
2503 return result(TBool
);
2504 case TypeStructure::Kind::T_typeaccess
:
2505 case TypeStructure::Kind::T_array
:
2506 case TypeStructure::Kind::T_darray
:
2507 case TypeStructure::Kind::T_varray
:
2508 case TypeStructure::Kind::T_varray_or_darray
:
2509 case TypeStructure::Kind::T_reifiedtype
:
2510 return result(TBool
);
2511 case TypeStructure::Kind::T_fun
:
2512 case TypeStructure::Kind::T_typevar
:
2513 case TypeStructure::Kind::T_trait
:
2514 // We will error on these at the JIT
2515 return result(TBool
);
2521 bool canReduceToDontResolve(SArray ts
) {
2522 switch (get_ts_kind(ts
)) {
2523 case TypeStructure::Kind::T_int
:
2524 case TypeStructure::Kind::T_bool
:
2525 case TypeStructure::Kind::T_float
:
2526 case TypeStructure::Kind::T_string
:
2527 case TypeStructure::Kind::T_num
:
2528 case TypeStructure::Kind::T_arraykey
:
2529 case TypeStructure::Kind::T_void
:
2530 case TypeStructure::Kind::T_null
:
2531 case TypeStructure::Kind::T_noreturn
:
2532 case TypeStructure::Kind::T_mixed
:
2533 case TypeStructure::Kind::T_nonnull
:
2534 case TypeStructure::Kind::T_resource
:
2535 // Following ones don't reify, so no need to check the generics
2536 case TypeStructure::Kind::T_dict
:
2537 case TypeStructure::Kind::T_vec
:
2538 case TypeStructure::Kind::T_keyset
:
2539 case TypeStructure::Kind::T_vec_or_dict
:
2540 case TypeStructure::Kind::T_arraylike
:
2542 case TypeStructure::Kind::T_class
:
2543 case TypeStructure::Kind::T_interface
:
2544 case TypeStructure::Kind::T_xhp
:
2545 case TypeStructure::Kind::T_enum
:
2546 // If it does not have generics, then we can reduce
2547 // If it has generics but all of them are wildcards, then we can reduce
2548 // Otherwise, we can't.
2549 return isTSAllWildcards(ts
);
2550 case TypeStructure::Kind::T_tuple
: {
2553 get_ts_elem_types(ts
),
2555 assertx(isArrayLikeType(v
.m_type
));
2556 result
&= canReduceToDontResolve(v
.m_data
.parr
);
2557 // when result is false, we can short circuit
2563 case TypeStructure::Kind::T_shape
: {
2568 assertx(isArrayLikeType(v
.m_type
));
2569 auto const arr
= v
.m_data
.parr
;
2570 if (arr
->exists(s_is_cls_cns
)) {
2572 return true; // short circuit
2574 result
&= canReduceToDontResolve(get_ts_value_field(arr
));
2575 // when result is false, we can short circuit
2581 // Following needs to be resolved
2582 case TypeStructure::Kind::T_unresolved
:
2583 case TypeStructure::Kind::T_typeaccess
:
2584 // Following cannot be used in is/as expressions, we need to error on them
2585 // Currently erroring happens as a part of the resolving phase,
2586 // so keep resolving them
2587 case TypeStructure::Kind::T_array
:
2588 case TypeStructure::Kind::T_darray
:
2589 case TypeStructure::Kind::T_varray
:
2590 case TypeStructure::Kind::T_varray_or_darray
:
2591 case TypeStructure::Kind::T_reifiedtype
:
2592 case TypeStructure::Kind::T_fun
:
2593 case TypeStructure::Kind::T_typevar
:
2594 case TypeStructure::Kind::T_trait
:
2602 void in(ISS
& env
, const bc::IsTypeStructC
& op
) {
2603 auto const requiredTSType
= RuntimeOption::EvalHackArrDVArrs
? BDict
: BDArr
;
2604 if (!topC(env
).couldBe(requiredTSType
)) {
2607 return unreachable(env
);
2609 auto const a
= tv(topC(env
));
2610 if (!a
|| !isValidTSType(*a
, false)) {
2613 return push(env
, TBool
);
2615 if (op
.subop1
== TypeStructResolveOp::Resolve
&&
2616 canReduceToDontResolve(a
->m_data
.parr
)) {
2617 return reduce(env
, bc::IsTypeStructC
{ TypeStructResolveOp::DontResolve
});
2619 isAsTypeStructImpl
<false>(env
, a
->m_data
.parr
);
2622 void in(ISS
& env
, const bc::AsTypeStructC
& op
) {
2623 auto const requiredTSType
= RuntimeOption::EvalHackArrDVArrs
? BDict
: BDArr
;
2624 if (!topC(env
).couldBe(requiredTSType
)) {
2627 return unreachable(env
);
2629 auto const a
= tv(topC(env
));
2630 if (!a
|| !isValidTSType(*a
, false)) {
2632 push(env
, popC(env
));
2635 if (op
.subop1
== TypeStructResolveOp::Resolve
&&
2636 canReduceToDontResolve(a
->m_data
.parr
)) {
2637 return reduce(env
, bc::AsTypeStructC
{ TypeStructResolveOp::DontResolve
});
2639 isAsTypeStructImpl
<true>(env
, a
->m_data
.parr
);
2642 void in(ISS
& env
, const bc::CombineAndResolveTypeStruct
& op
) {
2643 // TODO(T31677864): implement real optimizations
2644 assertx(op
.arg1
> 0);
2646 auto const requiredTSType
= RuntimeOption::EvalHackArrDVArrs
? BDict
: BDArr
;
2647 for (int i
= 0; i
< op
.arg1
; ++i
) {
2648 auto const t
= popC(env
);
2649 valid
&= t
.couldBe(requiredTSType
);
2651 if (!valid
) return unreachable(env
);
2653 push(env
, Type
{requiredTSType
});
2656 void in(ISS
& env
, const bc::RecordReifiedGeneric
& op
) {
2657 // TODO(T31677864): implement real optimizations
2658 assertx(op
.arg1
> 0);
2660 auto const requiredTSType
= RuntimeOption::EvalHackArrDVArrs
? BDict
: BDArr
;
2661 auto const resultingArray
= RuntimeOption::EvalHackArrDVArrs
? TVec
: TVArr
;
2662 for (int i
= 0; i
< op
.arg1
; ++i
) {
2663 auto const t
= popC(env
);
2664 valid
&= t
.couldBe(requiredTSType
);
2666 if (!valid
) return unreachable(env
);
2668 push(env
, resultingArray
);
2671 void in(ISS
& env
, const bc::ReifiedName
& op
) {
2672 // TODO(T31677864): implement real optimizations
2673 assertx(op
.arg1
> 0);
2675 auto const requiredTSType
= RuntimeOption::EvalHackArrDVArrs
? BDict
: BDArr
;
2676 for (int i
= 0; i
< op
.arg1
; ++i
) {
2677 auto const t
= popC(env
);
2678 valid
&= t
.couldBe(requiredTSType
);
2680 if (!valid
) return unreachable(env
);
2682 return push(env
, rname(op
.str2
));
2685 void in(ISS
& env
, const bc::CheckReifiedGenericMismatch
& op
) {
2686 // TODO(T31677864): implement real optimizations
2693 * If the value on the top of the stack is known to be equivalent to the local
2694 * its being moved/copied to, return folly::none without modifying any
2695 * state. Otherwise, pop the stack value, perform the set, and return a pair
2696 * giving the value's type, and any other local its known to be equivalent to.
2698 template <typename Op
>
2699 folly::Optional
<std::pair
<Type
, LocalId
>> moveToLocImpl(ISS
& env
,
2702 auto equivLoc
= topStkEquiv(env
);
2703 // If the local could be a Ref, don't record equality because the stack
2704 // element and the local won't actually have the same type.
2705 if (!locCouldBeRef(env
, op
.loc1
)) {
2706 if (equivLoc
== StackThisId
&& env
.state
.thisLoc
!= NoLocalId
) {
2707 if (env
.state
.thisLoc
== op
.loc1
||
2708 locsAreEquiv(env
, env
.state
.thisLoc
, op
.loc1
)) {
2711 equivLoc
= env
.state
.thisLoc
;
2714 assertx(!is_volatile_local(env
.ctx
.func
, op
.loc1
));
2715 if (equivLoc
<= MaxLocalId
) {
2716 if (equivLoc
== op
.loc1
||
2717 locsAreEquiv(env
, equivLoc
, op
.loc1
)) {
2718 // We allow equivalency to ignore Uninit, so we need to check
2720 if (peekLocRaw(env
, op
.loc1
) == topC(env
)) {
2724 } else if (equivLoc
== NoLocalId
) {
2727 if (any(env
.collect
.opts
& CollectionOpts::Inlining
)) {
2731 equivLoc
= NoLocalId
;
2733 auto val
= popC(env
);
2734 setLoc(env
, op
.loc1
, val
);
2735 if (equivLoc
== StackThisId
) {
2736 assertx(env
.state
.thisLoc
== NoLocalId
);
2737 equivLoc
= env
.state
.thisLoc
= op
.loc1
;
2739 if (equivLoc
== StackDupId
) {
2740 setStkLocal(env
, op
.loc1
);
2741 } else if (equivLoc
!= op
.loc1
&& equivLoc
!= NoLocalId
) {
2742 addLocEquiv(env
, op
.loc1
, equivLoc
);
2744 return { std::make_pair(std::move(val
), equivLoc
) };
2749 void in(ISS
& env
, const bc::PopL
& op
) {
2750 // If the same value is already in the local, do nothing but pop
2751 // it. Otherwise, the set has been done by moveToLocImpl.
2752 if (!moveToLocImpl(env
, op
)) return reduce(env
, bc::PopC
{});
2755 void in(ISS
& env
, const bc::SetL
& op
) {
2756 // If the same value is already in the local, do nothing because SetL keeps
2757 // the value on the stack. If it isn't, we need to push it back onto the stack
2758 // because moveToLocImpl popped it.
2759 if (auto p
= moveToLocImpl(env
, op
)) {
2760 push(env
, std::move(p
->first
), p
->second
);
2762 reduce(env
, bc::Nop
{});
2766 void in(ISS
& env
, const bc::SetG
&) {
2767 auto t1
= popC(env
);
2769 push(env
, std::move(t1
));
2772 void in(ISS
& env
, const bc::SetS
& op
) {
2773 auto const t1
= popC(env
);
2774 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
2775 auto const tname
= popC(env
);
2776 auto const vname
= tv(tname
);
2777 auto const self
= selfCls(env
);
2779 if (!self
|| tcls
.couldBe(*self
)) {
2780 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
2781 mergeSelfProp(env
, vname
->m_data
.pstr
, t1
);
2783 mergeEachSelfPropRaw(env
, [&] (Type
) { return t1
; });
2787 env
.collect
.publicSPropMutations
.merge(env
.index
, env
.ctx
, tcls
, tname
, t1
);
2789 push(env
, std::move(t1
));
2792 void in(ISS
& env
, const bc::SetOpL
& op
) {
2793 auto const t1
= popC(env
);
2794 auto const v1
= tv(t1
);
2795 auto const loc
= locAsCell(env
, op
.loc1
);
2796 auto const locVal
= tv(loc
);
2798 // Can't constprop at this eval_cell, because of the effects on
2800 auto resultTy
= eval_cell([&] {
2803 setopBody(&c
, op
.subop2
, &rhs
);
2806 if (!resultTy
) resultTy
= TInitCell
;
2808 // We may have inferred a TSStr or TSArr with a value here, but
2809 // at runtime it will not be static. For now just throw that
2810 // away. TODO(#3696042): should be able to loosen_staticness here.
2811 if (resultTy
->subtypeOf(BStr
)) resultTy
= TStr
;
2812 else if (resultTy
->subtypeOf(BArr
)) resultTy
= TArr
;
2813 else if (resultTy
->subtypeOf(BVec
)) resultTy
= TVec
;
2814 else if (resultTy
->subtypeOf(BDict
)) resultTy
= TDict
;
2815 else if (resultTy
->subtypeOf(BKeyset
)) resultTy
= TKeyset
;
2817 setLoc(env
, op
.loc1
, *resultTy
);
2818 push(env
, *resultTy
);
2822 auto resultTy
= typeSetOp(op
.subop2
, loc
, t1
);
2823 setLoc(env
, op
.loc1
, resultTy
);
2824 push(env
, std::move(resultTy
));
2827 void in(ISS
& env
, const bc::SetOpG
&) {
2828 popC(env
); popC(env
);
2829 push(env
, TInitCell
);
2832 void in(ISS
& env
, const bc::SetOpS
& op
) {
2834 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
2835 auto const tname
= popC(env
);
2836 auto const vname
= tv(tname
);
2837 auto const self
= selfCls(env
);
2839 if (!self
|| tcls
.couldBe(*self
)) {
2840 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
2841 mergeSelfProp(env
, vname
->m_data
.pstr
, TInitCell
);
2843 loseNonRefSelfPropTypes(env
);
2847 env
.collect
.publicSPropMutations
.merge(
2848 env
.index
, env
.ctx
, tcls
, tname
, TInitCell
2851 push(env
, TInitCell
);
2854 void in(ISS
& env
, const bc::IncDecL
& op
) {
2855 auto loc
= locAsCell(env
, op
.loc1
);
2856 auto newT
= typeIncDec(op
.subop2
, loc
);
2857 auto const pre
= isPre(op
.subop2
);
2859 // If it's a non-numeric string, this may cause it to exceed the max length.
2860 if (!locCouldBeUninit(env
, op
.loc1
) &&
2861 !loc
.couldBe(BStr
)) {
2865 if (!pre
) push(env
, std::move(loc
));
2866 setLoc(env
, op
.loc1
, newT
);
2867 if (pre
) push(env
, std::move(newT
));
2870 void in(ISS
& env
, const bc::IncDecG
&) { popC(env
); push(env
, TInitCell
); }
2872 void in(ISS
& env
, const bc::IncDecS
& op
) {
2873 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
2874 auto const tname
= popC(env
);
2875 auto const vname
= tv(tname
);
2876 auto const self
= selfCls(env
);
2878 if (!self
|| tcls
.couldBe(*self
)) {
2879 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
2880 mergeSelfProp(env
, vname
->m_data
.pstr
, TInitCell
);
2882 loseNonRefSelfPropTypes(env
);
2886 env
.collect
.publicSPropMutations
.merge(
2887 env
.index
, env
.ctx
, tcls
, tname
, TInitCell
2890 push(env
, TInitCell
);
2893 void in(ISS
& env
, const bc::BindL
& op
) {
2894 // If the op.loc1 was bound to a local static, its going to be
2895 // unbound from it. If the thing its being bound /to/ is a local
2896 // static, we've already marked it as modified via the VGetL, so
2897 // there's nothing more to track.
2898 // Unbind it before any updates.
2899 modifyLocalStatic(env
, op
.loc1
, TUninit
);
2901 auto t1
= popV(env
);
2902 setLocRaw(env
, op
.loc1
, t1
);
2903 push(env
, std::move(t1
));
2906 void in(ISS
& env
, const bc::BindG
&) {
2907 auto t1
= popV(env
);
2909 push(env
, std::move(t1
));
2912 void in(ISS
& env
, const bc::BindS
& op
) {
2914 auto const tcls
= takeClsRefSlot(env
, op
.slot
);
2915 auto const tname
= popC(env
);
2916 auto const vname
= tv(tname
);
2917 auto const self
= selfCls(env
);
2919 if (!self
|| tcls
.couldBe(*self
)) {
2920 if (vname
&& vname
->m_type
== KindOfPersistentString
) {
2921 boxSelfProp(env
, vname
->m_data
.pstr
);
2927 env
.collect
.publicSPropMutations
.merge(
2928 env
.index
, env
.ctx
, tcls
, tname
, TRef
2934 void in(ISS
& env
, const bc::UnsetL
& op
) {
2935 if (locRaw(env
, op
.loc1
).subtypeOf(TUninit
)) {
2936 return reduce(env
, bc::Nop
{});
2939 setLocRaw(env
, op
.loc1
, TUninit
);
2942 void in(ISS
& env
, const bc::UnsetG
& /*op*/) {
2943 auto const t1
= popC(env
);
2944 if (!t1
.couldBe(BObj
| BRes
)) nothrow(env
);
2947 void in(ISS
& env
, const bc::FPushFuncD
& op
) {
2948 auto const rfunc
= env
.index
.resolve_func(env
.ctx
, op
.str2
);
2949 if (!any(env
.collect
.opts
& CollectionOpts::Speculating
)) {
2950 if (auto const func
= rfunc
.exactFunc()) {
2951 if (can_emit_builtin(func
, op
.arg1
, op
.has_unpack
)) {
2954 ActRec
{ FPIKind::Builtin
, TBottom
, folly::none
, rfunc
}
2956 return reduce(env
, bc::Nop
{});
2960 if (fpiPush(env
, ActRec
{ FPIKind::Func
, TBottom
, folly::none
, rfunc
},
2962 return reduce(env
, bc::Nop
{});
2966 void in(ISS
& env
, const bc::FPushFunc
& op
) {
2967 auto const t1
= topC(env
);
2968 folly::Optional
<res::Func
> rfunc
;
2969 // FPushFuncD and FPushFuncU require that the names of inout functions be
2970 // mangled, so skip those for now.
2971 auto const name
= getNameFromType(t1
);
2972 if (name
&& op
.argv
.size() == 0) {
2973 auto const nname
= normalizeNS(name
);
2974 // FPushFuncD doesn't support class-method pair strings yet.
2975 if (isNSNormalized(nname
) && notClassMethodPair(nname
)) {
2976 rfunc
= env
.index
.resolve_func(env
.ctx
, nname
);
2977 // If the function might distinguish being called dynamically from not,
2978 // don't turn a dynamic call into a static one.
2979 if (rfunc
&& !rfunc
->mightCareAboutDynCalls() &&
2980 !rfunc
->couldHaveReifiedGenerics()) {
2981 return reduce(env
, bc::PopC
{},
2982 bc::FPushFuncD
{ op
.arg1
, nname
, op
.has_unpack
});
2987 if (t1
.subtypeOf(BObj
)) {
2988 fpiPushNoFold(env
, ActRec
{ FPIKind::ObjInvoke
, t1
});
2989 } else if (t1
.subtypeOf(BArr
)) {
2990 fpiPushNoFold(env
, ActRec
{ FPIKind::CallableArr
, TTop
});
2991 } else if (t1
.subtypeOf(BStr
)) {
2992 fpiPushNoFold(env
, ActRec
{ FPIKind::Func
, TTop
, folly::none
, rfunc
});
2994 fpiPushNoFold(env
, ActRec
{ FPIKind::Unknown
, TTop
});
2998 void in(ISS
& env
, const bc::FPushFuncU
& op
) {
2999 auto const rfuncPair
=
3000 env
.index
.resolve_func_fallback(env
.ctx
, op
.str2
, op
.str3
);
3001 if (options
.ElideAutoloadInvokes
) {
3002 auto const fpushfuncd
= [&](auto const& fn
) {
3003 return reduce(env
, bc::FPushFuncD
{ op
.arg1
, fn
->name(), op
.has_unpack
});
3005 if (rfuncPair
.first
&& !rfuncPair
.second
) {
3006 return fpushfuncd(rfuncPair
.first
);
3008 if (!rfuncPair
.first
&& rfuncPair
.second
&&
3009 RuntimeOption::UndefinedFunctionFallback
== 0) {
3010 return fpushfuncd(rfuncPair
.second
);
3025 void in(ISS
& env
, const bc::ResolveFunc
& op
) {
3030 void in(ISS
& env
, const bc::ResolveObjMethod
& op
) {
3034 if (RuntimeOption::EvalHackArrDVArrs
) {
3041 void in(ISS
& env
, const bc::ResolveClsMethod
& op
) {
3044 push(env
, TClsMeth
);
3047 const StaticString s_nullFunc
{ "__SystemLib\\__86null" };
3049 void in(ISS
& env
, const bc::FPushObjMethodD
& op
) {
3050 auto const nullThrows
= op
.subop3
== ObjMethodOp::NullThrows
;
3051 auto const input
= topC(env
);
3052 auto const mayCallMethod
= input
.couldBe(BObj
);
3053 auto const mayCallNullsafe
= !nullThrows
&& input
.couldBe(BNull
);
3054 auto const mayThrowNonObj
= !input
.subtypeOf(nullThrows
? BObj
: BOptObj
);
3056 if (!mayCallMethod
&& !mayCallNullsafe
) {
3057 // This FPush may only throw, make sure it's not optimized away.
3058 fpiPushNoFold(env
, ActRec
{ FPIKind::ObjMeth
, TBottom
});
3060 return unreachable(env
);
3063 if (!mayCallMethod
&& !mayThrowNonObj
) {
3064 // Null input, this may only call the nullsafe helper, so do that.
3068 bc::FPushFuncD
{ op
.arg1
, s_nullFunc
.get(), op
.has_unpack
}
3072 auto const ar
= [&] {
3073 assertx(mayCallMethod
);
3074 auto const kind
= mayCallNullsafe
? FPIKind::ObjMethNS
: FPIKind::ObjMeth
;
3075 auto const ctxTy
= intersection_of(input
, TObj
);
3076 auto const clsTy
= objcls(ctxTy
);
3077 auto const rcls
= is_specialized_cls(clsTy
)
3078 ? folly::Optional
<res::Class
>(dcls_of(clsTy
).cls
)
3080 auto const func
= env
.index
.resolve_method(env
.ctx
, clsTy
, op
.str2
);
3081 return ActRec
{ kind
, ctxTy
, rcls
, func
};
3084 if (!mayCallMethod
) {
3085 // Calls nullsafe helper, but can't fold as we may still throw.
3086 assertx(mayCallNullsafe
&& mayThrowNonObj
);
3087 auto const func
= env
.index
.resolve_func(env
.ctx
, s_nullFunc
.get());
3088 assertx(func
.exactFunc());
3089 fpiPushNoFold(env
, ActRec
{ FPIKind::Func
, TBottom
, folly::none
, func
});
3090 } else if (mayCallNullsafe
|| mayThrowNonObj
) {
3091 // Can't optimize away as FCall may push null instead of the folded value
3092 // or FCall may throw.
3093 fpiPushNoFold(env
, ar());
3094 } else if (fpiPush(env
, ar(), op
.arg1
, false)) {
3095 return reduce(env
, bc::PopC
{});
3098 auto const location
= topStkEquiv(env
);
3099 if (location
!= NoLocalId
) {
3100 if (!refineLocation(env
, location
, [&] (Type t
) {
3101 if (nullThrows
) return intersection_of(t
, TObj
);
3102 if (!t
.couldBe(BUninit
)) return intersection_of(t
, TOptObj
);
3103 if (!t
.couldBe(BObj
)) return intersection_of(t
, TNull
);
3113 void in(ISS
& env
, const bc::FPushObjMethod
& op
) {
3114 auto const t1
= topC(env
); // meth name
3115 auto const t2
= topC(env
, 1); // object
3116 auto const clsTy
= objcls(t2
);
3117 folly::Optional
<res::Func
> rfunc
;
3118 auto const name
= getNameFromType(t1
);
3119 if (name
&& op
.argv
.size() == 0) {
3120 rfunc
= env
.index
.resolve_method(env
.ctx
, clsTy
, name
);
3121 if (rfunc
&& !rfunc
->mightCareAboutDynCalls() &&
3122 !rfunc
->couldHaveReifiedGenerics()) {
3126 bc::FPushObjMethodD
{
3127 op
.arg1
, name
, op
.subop2
, op
.has_unpack
3138 is_specialized_cls(clsTy
)
3139 ? folly::Optional
<res::Class
>(dcls_of(clsTy
).cls
)
3146 void in(ISS
& env
, const bc::FPushClsMethodD
& op
) {
3147 auto const rcls
= env
.index
.resolve_class(env
.ctx
, op
.str3
);
3148 auto clsType
= rcls
? clsExact(*rcls
) : TCls
;
3149 auto const rfun
= env
.index
.resolve_method(
3154 if (fpiPush(env
, ActRec
{ FPIKind::ClsMeth
, clsType
, rcls
, rfun
}, op
.arg1
,
3156 return reduce(env
, bc::Nop
{});
3162 Type
ctxCls(ISS
& env
) {
3163 auto const s
= selfCls(env
);
3164 return setctx(s
? *s
: TCls
);
3167 Type
specialClsRefToCls(ISS
& env
, SpecialClsRef ref
) {
3168 if (!env
.ctx
.cls
) return TCls
;
3169 auto const op
= [&]()-> folly::Optional
<Type
> {
3171 case SpecialClsRef::Static
: return ctxCls(env
);
3172 case SpecialClsRef::Self
: return selfClsExact(env
);
3173 case SpecialClsRef::Parent
: return parentClsExact(env
);
3175 always_assert(false);
3177 return op
? *op
: TCls
;
3182 void in(ISS
& env
, const bc::FPushClsMethod
& op
) {
3183 auto const t1
= peekClsRefSlot(env
, op
.slot
);
3184 auto const t2
= topC(env
);
3185 folly::Optional
<res::Class
> rcls
;
3186 auto exactCls
= false;
3187 if (is_specialized_cls(t1
)) {
3188 auto dcls
= dcls_of(t1
);
3190 exactCls
= dcls
.type
== DCls::Exact
;
3192 folly::Optional
<res::Func
> rfunc
;
3193 auto const name
= getNameFromType(t2
);
3194 if (name
&& op
.argv
.size() == 0) {
3195 rfunc
= env
.index
.resolve_method(env
.ctx
, t1
, name
);
3196 if (exactCls
&& rcls
&& !rfunc
->mightCareAboutDynCalls() &&
3197 !rfunc
->couldHaveReifiedGenerics()) {
3200 bc::DiscardClsRef
{ op
.slot
},
3202 bc::FPushClsMethodD
{
3203 op
.arg1
, name
, rcls
->name(), op
.has_unpack
3208 if (fpiPush(env
, ActRec
{ FPIKind::ClsMeth
, t1
, rcls
, rfunc
}, op
.arg1
,
3211 bc::DiscardClsRef
{ op
.slot
},
3214 takeClsRefSlot(env
, op
.slot
);
3218 void in(ISS
& env
, const bc::FPushClsMethodS
& op
) {
3219 auto const t1
= topC(env
);
3220 auto const cls
= specialClsRefToCls(env
, op
.subop2
);
3221 folly::Optional
<res::Func
> rfunc
;
3222 auto const name
= getNameFromType(t1
);
3223 if (name
&& op
.argv
.size() == 0) {
3224 rfunc
= env
.index
.resolve_method(env
.ctx
, cls
, name
);
3225 if (!rfunc
->mightCareAboutDynCalls() &&
3226 !rfunc
->couldHaveReifiedGenerics()) {
3230 bc::FPushClsMethodSD
{
3231 op
.arg1
, op
.subop2
, name
, op
.has_unpack
3236 auto const rcls
= is_specialized_cls(cls
)
3237 ? folly::Optional
<res::Class
>{dcls_of(cls
).cls
}
3239 if (fpiPush(env
, ActRec
{
3244 }, op
.arg1
, true)) {
3245 return reduce(env
, bc::PopC
{});
3250 void in(ISS
& env
, const bc::FPushClsMethodSD
& op
) {
3251 auto const cls
= specialClsRefToCls(env
, op
.subop2
);
3253 folly::Optional
<res::Class
> rcls
;
3254 auto exactCls
= false;
3255 if (is_specialized_cls(cls
)) {
3256 auto dcls
= dcls_of(cls
);
3258 exactCls
= dcls
.type
== DCls::Exact
;
3261 if (op
.subop2
== SpecialClsRef::Static
&& rcls
&& exactCls
) {
3264 bc::FPushClsMethodD
{
3265 op
.arg1
, op
.str3
, rcls
->name(), op
.has_unpack
3270 auto const rfun
= env
.index
.resolve_method(env
.ctx
, cls
, op
.str3
);
3271 if (fpiPush(env
, ActRec
{
3276 }, op
.arg1
, false)) {
3277 return reduce(env
, bc::Nop
{});
3281 void newObjHelper(ISS
& env
, SString name
) {
3282 auto const rcls
= env
.index
.resolve_class(env
.ctx
, name
);
3288 auto const isCtx
= !rcls
->couldBeOverriden() && env
.ctx
.cls
&&
3289 rcls
->same(env
.index
.resolve_class(env
.ctx
.cls
));
3290 push(env
, setctx(objExact(*rcls
), isCtx
));
3293 void in(ISS
& env
, const bc::NewObjD
& op
) {
3294 newObjHelper(env
, op
.str1
);
3297 void in(ISS
& env
, const bc::NewObjI
& op
) {
3298 newObjHelper(env
, env
.ctx
.unit
->classes
[op
.arg1
]->name
);
3301 void in(ISS
& env
, const bc::NewObjS
& op
) {
3302 auto const cls
= specialClsRefToCls(env
, op
.subop1
);
3303 if (!is_specialized_cls(cls
)) {
3308 auto const dcls
= dcls_of(cls
);
3309 auto const exact
= dcls
.type
== DCls::Exact
;
3310 if (exact
&& !dcls
.cls
.couldHaveReifiedGenerics() &&
3311 (!dcls
.cls
.couldBeOverriden() || equivalently_refined(cls
, unctx(cls
)))) {
3312 return reduce(env
, bc::NewObjD
{ dcls
.cls
.name() });
3315 push(env
, toobj(cls
));
3318 void in(ISS
& env
, const bc::NewObj
& op
) {
3319 auto const cls
= peekClsRefSlot(env
, op
.slot
);
3320 if (!is_specialized_cls(cls
) || op
.subop2
== HasGenericsOp::MaybeGenerics
) {
3321 takeClsRefSlot(env
, op
.slot
);
3326 auto const dcls
= dcls_of(cls
);
3327 auto const exact
= dcls
.type
== DCls::Exact
;
3328 if (exact
&& !dcls
.cls
.mightCareAboutDynConstructs() &&
3329 !dcls
.cls
.couldHaveReifiedGenerics()) {
3332 bc::DiscardClsRef
{ op
.slot
},
3333 bc::NewObjD
{ dcls
.cls
.name(), }
3337 takeClsRefSlot(env
, op
.slot
);
3338 push(env
, toobj(cls
));
3341 void in(ISS
& env
, const bc::FPushCtor
& op
) {
3342 auto const obj
= popC(env
);
3343 if (!is_specialized_obj(obj
)) {
3344 return fpiPushNoFold(env
, ActRec
{ FPIKind::Ctor
, TObj
});
3347 auto const dobj
= dobj_of(obj
);
3348 auto const exact
= dobj
.type
== DObj::Exact
;
3349 auto const rfunc
= env
.index
.resolve_ctor(env
.ctx
, dobj
.cls
, exact
);
3350 if (!rfunc
|| !rfunc
->exactFunc()) {
3351 return fpiPushNoFold(env
, ActRec
{ FPIKind::Ctor
, obj
});
3353 fpiPushNoFold(env
, ActRec
{ FPIKind::Ctor
, obj
, dobj
.cls
, rfunc
});
3356 Type
typeFromWH(Type t
) {
3357 if (!t
.couldBe(BObj
)) {
3358 // Exceptions will be thrown if a non-object is awaited.
3362 // Throw away non-obj component.
3365 // If we aren't even sure this is a wait handle, there's nothing we can
3367 if (!is_specialized_wait_handle(t
)) {
3371 return wait_handle_inner(t
);
3374 void pushCallReturnType(ISS
& env
, Type
&& ty
, const FCallArgs
& fca
) {
3375 if (ty
== TBottom
) {
3376 // The callee function never returns. It might throw, or loop forever.
3379 if (fca
.numRets
!= 1) {
3380 assertx(fca
.asyncEagerTarget
== NoBlockId
);
3381 for (auto i
= uint32_t{0}; i
< fca
.numRets
- 1; ++i
) popU(env
);
3382 if (is_specialized_vec(ty
)) {
3383 for (int32_t i
= 1; i
< fca
.numRets
; i
++) {
3384 push(env
, vec_elem(ty
, ival(i
)).first
);
3386 push(env
, vec_elem(ty
, ival(0)).first
);
3388 for (int32_t i
= 0; i
< fca
.numRets
; i
++) push(env
, TInitCell
);
3392 if (fca
.asyncEagerTarget
!= NoBlockId
) {
3393 push(env
, typeFromWH(ty
));
3394 assertx(topC(env
) != TBottom
);
3395 env
.propagate(fca
.asyncEagerTarget
, &env
.state
);
3398 return push(env
, std::move(ty
));
3401 const StaticString s_defined
{ "defined" };
3402 const StaticString s_function_exists
{ "function_exists" };
3404 folly::Optional
<FCallArgs
> fcallKnownImpl(ISS
& env
, const FCallArgs
& fca
) {
3405 auto const ar
= fpiTop(env
);
3406 always_assert(ar
.func
.hasValue());
3408 if (options
.ConstantFoldBuiltins
&& ar
.foldable
) {
3409 if (!fca
.hasUnpack() && fca
.numRets
== 1) {
3411 auto const func
= ar
.func
->exactFunc();
3413 if (func
->attrs
& AttrBuiltin
&& func
->attrs
& AttrIsFoldable
) {
3414 auto ret
= const_fold(env
, fca
.numArgs
, *ar
.func
);
3415 return ret
? *ret
: TBottom
;
3417 CompactVector
<Type
> args(fca
.numArgs
);
3418 for (auto i
= uint32_t{0}; i
< fca
.numArgs
; ++i
) {
3419 auto const& arg
= topT(env
, i
);
3420 auto const argNum
= fca
.numArgs
- i
- 1;
3421 auto const isScalar
= is_scalar(arg
);
3423 (env
.index
.func_depends_on_arg(func
, argNum
) ||
3424 !arg
.subtypeOf(BInitCell
))) {
3427 args
[argNum
] = isScalar
? scalarize(arg
) : arg
;
3430 return env
.index
.lookup_foldable_return_type(
3431 env
.ctx
, func
, ar
.context
, std::move(args
));
3433 if (auto v
= tv(ty
)) {
3434 BytecodeVec repl
{ fca
.numArgs
, bc::PopC
{} };
3435 repl
.push_back(gen_constant(*v
));
3437 reduce(env
, std::move(repl
));
3441 fpiNotFoldable(env
);
3443 discard(env
, fca
.numArgs
+ (fca
.hasUnpack() ? 1 : 0));
3444 for (auto i
= uint32_t{0}; i
< fca
.numRets
; ++i
) push(env
, TBottom
);
3448 auto returnType
= [&] {
3449 CompactVector
<Type
> args(fca
.numArgs
);
3450 auto const firstArgPos
= fca
.numArgs
+ (fca
.hasUnpack() ? 1 : 0) - 1;
3451 for (auto i
= uint32_t{0}; i
< fca
.numArgs
; ++i
) {
3452 args
[i
] = topCV(env
, firstArgPos
- i
);
3455 auto ty
= fca
.hasUnpack()
3456 ? env
.index
.lookup_return_type(env
.ctx
, *ar
.func
)
3457 : env
.index
.lookup_return_type(env
.ctx
, args
, ar
.context
, *ar
.func
);
3458 if (ar
.kind
== FPIKind::ObjMethNS
) {
3459 ty
= union_of(std::move(ty
), TInitNull
);
3461 if (!ar
.fallbackFunc
) {
3464 auto ty2
= fca
.hasUnpack()
3465 ? env
.index
.lookup_return_type(env
.ctx
, *ar
.fallbackFunc
)
3466 : env
.index
.lookup_return_type(env
.ctx
, args
, ar
.context
,
3468 return union_of(std::move(ty
), std::move(ty2
));
3471 if (fca
.asyncEagerTarget
!= NoBlockId
&& typeFromWH(returnType
) == TBottom
) {
3472 // Kill the async eager target if the function never returns.
3474 newFCA
.asyncEagerTarget
= NoBlockId
;
3475 newFCA
.flags
= static_cast<FCallArgs::Flags
>(
3476 newFCA
.flags
& ~FCallArgs::SupportsAsyncEagerReturn
);
3481 specialFunctionEffects(env
, ar
);
3483 if (!fca
.hasUnpack() && ar
.func
->name()->isame(s_function_exists
.get())) {
3484 handle_function_exists(env
, fca
.numArgs
, false);
3487 if (options
.HardConstProp
&&
3490 ar
.func
->name()->isame(s_defined
.get())) {
3491 // If someone calls defined('foo') they probably want foo to be
3492 // defined normally; ie not a persistent constant.
3493 if (auto const v
= tv(topCV(env
))) {
3494 if (isStringType(v
->m_type
) &&
3495 !env
.index
.lookup_constant(env
.ctx
, v
->m_data
.pstr
)) {
3496 env
.collect
.cnsMap
[v
->m_data
.pstr
].m_type
= kDynamicConstant
;
3501 if (fca
.hasUnpack()) popC(env
);
3502 for (auto i
= uint32_t{0}; i
< fca
.numArgs
; ++i
) popCV(env
);
3503 pushCallReturnType(env
, std::move(returnType
), fca
);
3507 void in(ISS
& env
, const bc::FCall
& op
) {
3508 auto const fca
= op
.fca
;
3509 auto const ar
= fpiTop(env
);
3510 if (ar
.func
&& !ar
.fallbackFunc
) {
3511 if (fca
.enforceReffiness()) {
3513 for (auto i
= 0; i
< fca
.numArgs
; ++i
) {
3514 auto const kind
= env
.index
.lookup_param_prep(env
.ctx
, *ar
.func
, i
);
3515 if (ar
.foldable
&& kind
!= PrepKind::Val
) {
3516 fpiNotFoldable(env
);
3518 discard(env
, fca
.numArgs
+ (fca
.hasUnpack() ? 1 : 0));
3519 for (auto j
= uint32_t{0}; j
< fca
.numRets
; ++j
) push(env
, TBottom
);
3523 if (kind
== PrepKind::Unknown
) {
3528 if (kind
!= (fca
.byRef(i
) ? PrepKind::Ref
: PrepKind::Val
)) {
3529 // Reffiness mismatch
3530 auto const exCls
= makeStaticString("InvalidArgumentException");
3531 auto const err
= makeStaticString(formatParamRefMismatch(
3532 ar
.func
->name()->data(), i
, !fca
.byRef(i
)));
3536 bc::NewObjD
{ exCls
},
3538 bc::FPushCtor
{ 1, false },
3541 FCallArgs(1), staticEmptyString(), staticEmptyString() },
3549 // Optimize away the runtime reffiness check.
3550 return reduce(env
, bc::FCall
{
3551 FCallArgs(fca
.flags
, fca
.numArgs
, fca
.numRets
, nullptr,
3552 fca
.asyncEagerTarget
),
3558 // Infer whether the callee supports async eager return.
3559 if (fca
.asyncEagerTarget
!= NoBlockId
&&
3560 !fca
.supportsAsyncEagerReturn()) {
3561 auto const status
= env
.index
.supports_async_eager_return(*ar
.func
);
3565 // Callee supports async eager return.
3566 newFCA
.flags
= static_cast<FCallArgs::Flags
>(
3567 newFCA
.flags
| FCallArgs::SupportsAsyncEagerReturn
);
3569 // Callee doesn't support async eager return.
3570 newFCA
.asyncEagerTarget
= NoBlockId
;
3572 return reduce(env
, bc::FCall
{ newFCA
, op
.str2
, op
.str3
});
3577 case FPIKind::Unknown
:
3578 case FPIKind::CallableArr
:
3579 case FPIKind::ObjInvoke
:
3582 assertx(op
.str2
->empty());
3583 if (ar
.func
->name() != op
.str3
) {
3584 // We've found a more precise type for the call, so update it
3585 return reduce(env
, bc::FCall
{
3586 fca
, staticEmptyString(), ar
.func
->name() });
3588 if (auto const newFCA
= fcallKnownImpl(env
, fca
)) {
3589 return reduce(env
, bc::FCall
{ *newFCA
, op
.str2
, op
.str3
});
3592 case FPIKind::Builtin
:
3593 assertx(fca
.numRets
== 1);
3594 return finish_builtin(
3595 env
, ar
.func
->exactFunc(), fca
.numArgs
, fca
.hasUnpack());
3597 assertx(fca
.numRets
== 1);
3599 * Need to be wary of old-style ctors. We could get into the situation
3600 * where we're constructing class D extends B, and B has an old-style
3601 * ctor but D::B also exists. (So in this case we'll skip the
3602 * fcallKnownImpl stuff.)
3604 if (!ar
.func
->name()->isame(s_construct
.get())) {
3608 case FPIKind::ObjMeth
:
3609 case FPIKind::ClsMeth
:
3610 assertx(op
.str2
->empty() == op
.str3
->empty());
3611 if (ar
.cls
.hasValue() && ar
.func
->cantBeMagicCall() &&
3612 (ar
.cls
->name() != op
.str2
|| ar
.func
->name() != op
.str3
)) {
3613 // We've found a more precise type for the call, so update it
3614 return reduce(env
, bc::FCall
{
3615 fca
, ar
.cls
->name(), ar
.func
->name() });
3618 case FPIKind::ObjMethNS
:
3619 // If we didn't return a reduce above, we still can compute a
3620 // partially-known FCall effect with our res::Func.
3621 if (auto const newFCA
= fcallKnownImpl(env
, fca
)) {
3622 return reduce(env
, bc::FCall
{ *newFCA
, op
.str2
, op
.str3
});
3628 if (fca
.hasUnpack()) popC(env
);
3629 for (auto i
= uint32_t{0}; i
< fca
.numArgs
; ++i
) popCV(env
);
3631 specialFunctionEffects(env
, ar
);
3632 if (fca
.asyncEagerTarget
!= NoBlockId
) {
3633 assertx(fca
.numRets
== 1);
3634 push(env
, TInitCell
);
3635 env
.propagate(fca
.asyncEagerTarget
, &env
.state
);
3638 for (auto i
= uint32_t{0}; i
< fca
.numRets
- 1; ++i
) popU(env
);
3639 for (auto i
= uint32_t{0}; i
< fca
.numRets
; ++i
) push(env
, TInitCell
);
3644 void iterInitImpl(ISS
& env
, IterId iter
, LocalId valueLoc
,
3645 BlockId target
, const Type
& base
, LocalId baseLoc
) {
3646 assert(iterIsDead(env
, iter
));
3648 auto ity
= iter_types(base
);
3649 if (!ity
.mayThrowOnInit
) nothrow(env
);
3651 auto const taken
= [&]{
3652 // Take the branch before setting locals if the iter is already
3653 // empty, but after popping. Similar for the other IterInits
3655 freeIter(env
, iter
);
3656 env
.propagate(target
, &env
.state
);
3659 auto const fallthrough
= [&]{
3660 setIter(env
, iter
, LiveIter
{ ity
, baseLoc
, NoLocalId
, env
.bid
});
3661 // Do this after setting the iterator, in case it clobbers the base local
3663 setLoc(env
, valueLoc
, std::move(ity
.value
));
3666 switch (ity
.count
) {
3667 case IterTypes::Count::Empty
:
3668 freeIter(env
, iter
);
3669 mayReadLocal(env
, valueLoc
);
3670 jmp_setdest(env
, target
);
3672 case IterTypes::Count::Single
:
3673 case IterTypes::Count::NonEmpty
:
3675 jmp_nevertaken(env
);
3677 case IterTypes::Count::ZeroOrOne
:
3678 case IterTypes::Count::Any
:
3685 void iterInitKImpl(ISS
& env
, IterId iter
, LocalId valueLoc
, LocalId keyLoc
,
3686 BlockId target
, const Type
& base
, LocalId baseLoc
) {
3687 assert(iterIsDead(env
, iter
));
3689 auto ity
= iter_types(base
);
3690 if (!ity
.mayThrowOnInit
) nothrow(env
);
3692 auto const taken
= [&]{
3693 freeIter(env
, iter
);
3694 env
.propagate(target
, &env
.state
);
3697 auto const fallthrough
= [&]{
3698 setIter(env
, iter
, LiveIter
{ ity
, baseLoc
, NoLocalId
, env
.bid
});
3699 // Do this after setting the iterator, in case it clobbers the base local
3701 setLoc(env
, valueLoc
, std::move(ity
.value
));
3702 setLoc(env
, keyLoc
, std::move(ity
.key
));
3703 if (!locCouldBeRef(env
, keyLoc
)) setIterKey(env
, iter
, keyLoc
);
3706 switch (ity
.count
) {
3707 case IterTypes::Count::Empty
:
3708 freeIter(env
, iter
);
3709 mayReadLocal(env
, valueLoc
);
3710 mayReadLocal(env
, keyLoc
);
3711 jmp_setdest(env
, target
);
3713 case IterTypes::Count::Single
:
3714 case IterTypes::Count::NonEmpty
:
3716 jmp_nevertaken(env
);
3718 case IterTypes::Count::ZeroOrOne
:
3719 case IterTypes::Count::Any
:
3726 void iterNextImpl(ISS
& env
, IterId iter
, LocalId valueLoc
, BlockId target
) {
3727 auto const curLoc
= locRaw(env
, valueLoc
);
3729 auto const noTaken
= match
<bool>(
3730 env
.state
.iters
[iter
],
3732 always_assert(false && "IterNext on dead iter");
3735 [&] (const LiveIter
& ti
) {
3736 if (!ti
.types
.mayThrowOnNext
) nothrow(env
);
3737 if (ti
.baseLocal
!= NoLocalId
) hasInvariantIterBase(env
);
3738 switch (ti
.types
.count
) {
3739 case IterTypes::Count::Single
:
3740 case IterTypes::Count::ZeroOrOne
:
3742 case IterTypes::Count::NonEmpty
:
3743 case IterTypes::Count::Any
:
3744 setLoc(env
, valueLoc
, ti
.types
.value
);
3746 case IterTypes::Count::Empty
:
3747 always_assert(false);
3753 jmp_nevertaken(env
);
3754 freeIter(env
, iter
);
3758 env
.propagate(target
, &env
.state
);
3760 freeIter(env
, iter
);
3761 setLocRaw(env
, valueLoc
, curLoc
);
3764 void iterNextKImpl(ISS
& env
, IterId iter
, LocalId valueLoc
,
3765 LocalId keyLoc
, BlockId target
) {
3766 auto const curValue
= locRaw(env
, valueLoc
);
3767 auto const curKey
= locRaw(env
, keyLoc
);
3769 auto const noTaken
= match
<bool>(
3770 env
.state
.iters
[iter
],
3772 always_assert(false && "IterNextK on dead iter");
3775 [&] (const LiveIter
& ti
) {
3776 if (!ti
.types
.mayThrowOnNext
) nothrow(env
);
3777 if (ti
.baseLocal
!= NoLocalId
) hasInvariantIterBase(env
);
3778 switch (ti
.types
.count
) {
3779 case IterTypes::Count::Single
:
3780 case IterTypes::Count::ZeroOrOne
:
3782 case IterTypes::Count::NonEmpty
:
3783 case IterTypes::Count::Any
:
3784 setLoc(env
, valueLoc
, ti
.types
.value
);
3785 setLoc(env
, keyLoc
, ti
.types
.key
);
3786 if (!locCouldBeRef(env
, keyLoc
)) setIterKey(env
, iter
, keyLoc
);
3788 case IterTypes::Count::Empty
:
3789 always_assert(false);
3795 jmp_nevertaken(env
);
3796 freeIter(env
, iter
);
3800 env
.propagate(target
, &env
.state
);
3802 freeIter(env
, iter
);
3803 setLocRaw(env
, valueLoc
, curValue
);
3804 setLocRaw(env
, keyLoc
, curKey
);
3809 void in(ISS
& env
, const bc::IterInit
& op
) {
3810 auto const baseLoc
= topStkLocal(env
);
3811 auto base
= popC(env
);
3812 iterInitImpl(env
, op
.iter1
, op
.loc3
, op
.target2
, std::move(base
), baseLoc
);
3815 void in(ISS
& env
, const bc::LIterInit
& op
) {
3821 locAsCell(env
, op
.loc2
),
3826 void in(ISS
& env
, const bc::IterInitK
& op
) {
3827 auto const baseLoc
= topStkLocal(env
);
3828 auto base
= popC(env
);
3840 void in(ISS
& env
, const bc::LIterInitK
& op
) {
3847 locAsCell(env
, op
.loc2
),
3852 void in(ISS
& env
, const bc::IterNext
& op
) {
3853 iterNextImpl(env
, op
.iter1
, op
.loc3
, op
.target2
);
3856 void in(ISS
& env
, const bc::LIterNext
& op
) {
3857 mayReadLocal(env
, op
.loc2
);
3858 iterNextImpl(env
, op
.iter1
, op
.loc4
, op
.target3
);
3861 void in(ISS
& env
, const bc::IterNextK
& op
) {
3862 iterNextKImpl(env
, op
.iter1
, op
.loc3
, op
.loc4
, op
.target2
);
3865 void in(ISS
& env
, const bc::LIterNextK
& op
) {
3866 mayReadLocal(env
, op
.loc2
);
3867 iterNextKImpl(env
, op
.iter1
, op
.loc4
, op
.loc5
, op
.target3
);
3870 void in(ISS
& env
, const bc::IterFree
& op
) {
3871 // IterFree is used for weak iterators too, so we can't assert !iterIsDead.
3875 env
.state
.iters
[op
.iter1
],
3877 [&] (const LiveIter
& ti
) {
3878 if (ti
.baseLocal
!= NoLocalId
) hasInvariantIterBase(env
);
3882 freeIter(env
, op
.iter1
);
3884 void in(ISS
& env
, const bc::LIterFree
& op
) {
3886 mayReadLocal(env
, op
.loc2
);
3887 freeIter(env
, op
.iter1
);
3890 void in(ISS
& env
, const bc::IterBreak
& op
) {
3893 for (auto const& it
: op
.iterTab
) {
3894 if (it
.kind
== KindOfIter
|| it
.kind
== KindOfLIter
) {
3896 env
.state
.iters
[it
.id
],
3898 [&] (const LiveIter
& ti
) {
3899 if (ti
.baseLocal
!= NoLocalId
) hasInvariantIterBase(env
);
3903 if (it
.kind
== KindOfLIter
) mayReadLocal(env
, it
.local
);
3904 freeIter(env
, it
.id
);
3907 env
.propagate(op
.target1
, &env
.state
);
3911 * Any include/require (or eval) op kills all locals, and private properties.
3913 void inclOpImpl(ISS
& env
) {
3919 push(env
, TInitCell
);
3922 void in(ISS
& env
, const bc::Incl
&) { inclOpImpl(env
); }
3923 void in(ISS
& env
, const bc::InclOnce
&) { inclOpImpl(env
); }
3924 void in(ISS
& env
, const bc::Req
&) { inclOpImpl(env
); }
3925 void in(ISS
& env
, const bc::ReqOnce
&) { inclOpImpl(env
); }
3926 void in(ISS
& env
, const bc::ReqDoc
&) { inclOpImpl(env
); }
3927 void in(ISS
& env
, const bc::Eval
&) { inclOpImpl(env
); }
3929 void in(ISS
& /*env*/, const bc::DefCls
&) {}
3930 void in(ISS
& /*env*/, const bc::DefClsNop
&) {}
3931 void in(ISS
& env
, const bc::AliasCls
&) {
3936 void in(ISS
& env
, const bc::DefCns
& op
) {
3937 auto const t
= popC(env
);
3938 if (options
.HardConstProp
) {
3939 auto const v
= tv(t
);
3940 auto const val
= v
&& tvAsCVarRef(&*v
).isAllowedAsConstantValue() ?
3941 *v
: make_tv
<KindOfUninit
>();
3942 auto const res
= env
.collect
.cnsMap
.emplace(op
.str1
, val
);
3944 if (res
.first
->second
.m_type
== kReadOnlyConstant
) {
3945 // we only saw a read of this constant
3946 res
.first
->second
= val
;
3948 // more than one definition in this function
3949 res
.first
->second
.m_type
= kDynamicConstant
;
3956 void in(ISS
& /*env*/, const bc::DefTypeAlias
&) {}
3958 void in(ISS
& env
, const bc::This
&) {
3959 if (thisAvailable(env
)) {
3960 return reduce(env
, bc::BareThis
{ BareThisOp::NeverNull
});
3962 auto const ty
= thisType(env
);
3963 push(env
, ty
? *ty
: TObj
, StackThisId
);
3964 setThisAvailable(env
);
3967 void in(ISS
& env
, const bc::LateBoundCls
& op
) {
3968 if (env
.ctx
.cls
) effect_free(env
);
3969 auto const ty
= selfCls(env
);
3970 putClsRefSlot(env
, op
.slot
, setctx(ty
? *ty
: TCls
));
3973 void in(ISS
& env
, const bc::CheckThis
&) {
3974 if (thisAvailable(env
)) {
3975 return reduce(env
, bc::Nop
{});
3977 setThisAvailable(env
);
3980 void in(ISS
& env
, const bc::BareThis
& op
) {
3981 if (thisAvailable(env
)) {
3982 if (op
.subop1
!= BareThisOp::NeverNull
) {
3983 return reduce(env
, bc::BareThis
{ BareThisOp::NeverNull
});
3987 auto const ty
= thisType(env
);
3988 switch (op
.subop1
) {
3989 case BareThisOp::Notice
:
3991 case BareThisOp::NoNotice
:
3994 case BareThisOp::NeverNull
:
3995 setThisAvailable(env
);
3996 if (!env
.state
.unreachable
) effect_free(env
);
3997 return push(env
, ty
? *ty
: TObj
, StackThisId
);
4000 push(env
, ty
? opt(*ty
) : TOptObj
, StackThisId
);
4003 void in(ISS
& env
, const bc::InitThisLoc
& op
) {
4004 setLocRaw(env
, op
.loc1
, TCell
);
4005 env
.state
.thisLoc
= op
.loc1
;
4008 void in(ISS
& env
, const bc::FuncNumArgs
& op
) {
4013 void in(ISS
& env
, const bc::StaticLocDef
& op
) {
4014 if (staticLocHelper(env
, op
.loc1
, topC(env
))) {
4015 return reduce(env
, bc::SetL
{ op
.loc1
}, bc::PopC
{});
4020 void in(ISS
& env
, const bc::StaticLocCheck
& op
) {
4021 auto const l
= op
.loc1
;
4022 if (!env
.ctx
.func
->isMemoizeWrapper
&&
4023 !env
.ctx
.func
->isClosureBody
&&
4024 env
.collect
.localStaticTypes
.size() > l
) {
4025 auto t
= env
.collect
.localStaticTypes
[l
];
4026 if (auto v
= tv(t
)) {
4027 useLocalStatic(env
, l
);
4028 setLocRaw(env
, l
, t
);
4031 bc::SetL
{ op
.loc1
}, bc::PopC
{},
4035 setLocRaw(env
, l
, TGen
);
4036 maybeBindLocalStatic(env
, l
);
4040 void in(ISS
& env
, const bc::StaticLocInit
& op
) {
4041 if (staticLocHelper(env
, op
.loc1
, topC(env
))) {
4042 return reduce(env
, bc::SetL
{ op
.loc1
}, bc::PopC
{});
4048 * Amongst other things, we use this to mark units non-persistent.
4050 void in(ISS
& env
, const bc::OODeclExists
& op
) {
4051 auto flag
= popC(env
);
4052 auto name
= popC(env
);
4054 if (!name
.strictSubtypeOf(TStr
)) return TBool
;
4055 auto const v
= tv(name
);
4056 if (!v
) return TBool
;
4057 auto rcls
= env
.index
.resolve_class(env
.ctx
, v
->m_data
.pstr
);
4058 if (!rcls
|| !rcls
->cls()) return TBool
;
4059 auto const mayExist
= [&] () -> bool {
4060 switch (op
.subop1
) {
4061 case OODeclExistsOp::Class
:
4062 return !(rcls
->cls()->attrs
& (AttrInterface
| AttrTrait
));
4063 case OODeclExistsOp::Interface
:
4064 return rcls
->cls()->attrs
& AttrInterface
;
4065 case OODeclExistsOp::Trait
:
4066 return rcls
->cls()->attrs
& AttrTrait
;
4070 auto unit
= rcls
->cls()->unit
;
4071 auto canConstProp
= [&] {
4072 // Its generally not safe to constprop this, because of
4073 // autoload. We're safe if its part of systemlib, or a
4074 // superclass of the current context.
4075 if (is_systemlib_part(*unit
)) return true;
4076 if (!env
.ctx
.cls
) return false;
4077 auto thisClass
= env
.index
.resolve_class(env
.ctx
.cls
);
4078 return thisClass
.subtypeOf(*rcls
);
4080 if (canConstProp()) {
4082 return mayExist
? TTrue
: TFalse
;
4084 if (!any(env
.collect
.opts
& CollectionOpts::Inlining
)) {
4085 unit
->persistent
.store(false, std::memory_order_relaxed
);
4087 // At this point, if it mayExist, we still don't know that it
4088 // *does* exist, but if not we know that it either doesn't
4089 // exist, or it doesn't have the right type.
4090 return mayExist
? TBool
: TFalse
;
4095 bool couldBeMocked(const Type
& t
) {
4096 if (is_specialized_cls(t
)) {
4097 return dcls_of(t
).cls
.couldBeMocked();
4098 } else if (is_specialized_obj(t
)) {
4099 return dobj_of(t
).cls
.couldBeMocked();
4101 // In practice this should not occur since this is used mostly on the result
4102 // of looked up type constraints.
4107 void in(ISS
& env
, const bc::VerifyParamType
& op
) {
4108 IgnoreUsedParams _
{env
};
4110 if (env
.ctx
.func
->isMemoizeImpl
&&
4111 !locCouldBeRef(env
, op
.loc1
) &&
4112 RuntimeOption::EvalHardTypeHints
) {
4113 // a MemoizeImpl's params have already been checked by the wrapper
4114 return reduce(env
, bc::Nop
{});
4117 // Generally we won't know anything about the params, but
4118 // analyze_func_inline does - and this can help with effect-free analysis
4119 auto const constraint
= env
.ctx
.func
->params
[op
.loc1
].typeConstraint
;
4120 if (env
.index
.satisfies_constraint(env
.ctx
,
4121 locAsCell(env
, op
.loc1
),
4123 if (!locAsCell(env
, op
.loc1
).couldBe(BFunc
| BCls
)) {
4124 return reduce(env
, bc::Nop
{});
4128 if (!RuntimeOption::EvalHardTypeHints
) return;
4131 * In HardTypeHints mode, we assume that if this opcode doesn't
4132 * throw, the parameter was of the specified type (although it may
4133 * have been a Ref if the parameter was by reference).
4135 * The env.setLoc here handles dealing with a parameter that was
4136 * already known to be a reference.
4138 * NB: VerifyParamType of a reference parameter can kill any
4139 * references if it re-enters, even if Option::HardTypeHints is
4142 if (RuntimeOption::EvalThisTypeHintLevel
!= 3 && constraint
.isThis()) {
4145 if (constraint
.hasConstraint() && !constraint
.isTypeVar() &&
4146 !constraint
.isTypeConstant()) {
4148 loosen_dvarrayness(env
.index
.lookup_constraint(env
.ctx
, constraint
));
4149 if (constraint
.isThis() && couldBeMocked(t
)) {
4150 t
= unctx(std::move(t
));
4152 if (t
.subtypeOf(BBottom
)) unreachable(env
);
4153 FTRACE(2, " {} ({})\n", constraint
.fullName(), show(t
));
4154 setLoc(env
, op
.loc1
, std::move(t
));
4158 void in(ISS
& env
, const bc::VerifyParamTypeTS
& op
) {
4159 auto const a
= topC(env
);
4160 auto const requiredTSType
= RuntimeOption::EvalHackArrDVArrs
? BDict
: BDArr
;
4161 if (!a
.couldBe(requiredTSType
)) {
4166 auto const constraint
= env
.ctx
.func
->params
[op
.loc1
].typeConstraint
;
4167 // TODO(T31677864): We are being extremely pessimistic here, relax it
4168 if (!env
.ctx
.func
->isReified
&&
4169 (!env
.ctx
.cls
|| !env
.ctx
.cls
->hasReifiedGenerics
) &&
4170 !env
.index
.could_have_reified_type(constraint
)) {
4171 return reduce(env
, bc::PopC
{}, bc::VerifyParamType
{ op
.loc1
});
4176 void verifyRetImpl(ISS
& env
, const TypeConstraint
& constraint
,
4177 bool reduce_this
, bool ts_flavor
) {
4178 // If it is the ts flavor, then second thing on the stack, otherwise first
4179 auto stackT
= topC(env
, (int)ts_flavor
);
4181 // If there is no return type constraint, or if the return type
4182 // constraint is a typevar, or if the top of stack is the same
4183 // or a subtype of the type constraint, then this is a no-op.
4184 if (env
.index
.satisfies_constraint(env
.ctx
, stackT
, constraint
)) {
4186 // this is handled differently in ts flavor
4190 reduce(env
, bc::Nop
{});
4194 // For CheckReturnTypeHints >= 3 AND the constraint is not soft.
4195 // We can safely assume that either VerifyRetTypeC will
4196 // throw or it will produce a value whose type is compatible with the
4197 // return type constraint.
4198 auto tcT
= remove_uninit(
4199 loosen_dvarrayness(env
.index
.lookup_constraint(env
.ctx
, constraint
)));
4201 // If tcT could be an interface or trait, we upcast it to TObj/TOptObj.
4202 // Why? Because we want uphold the invariant that we only refine return
4203 // types and never widen them, and if we allow tcT to be an interface then
4204 // it's possible for violations of this invariant to arise. For an example,
4205 // see "hphp/test/slow/hhbbc/return-type-opt-bug.php".
4206 // Note: It's safe to use TObj/TOptObj because lookup_constraint() only
4207 // returns classes or interfaces or traits (it never returns something that
4208 // could be an enum or type alias) and it never returns anything that could
4209 // be a "magic" interface that supports non-objects. (For traits the return
4210 // typehint will always throw at run time, so it's safe to use TObj/TOptObj.)
4211 if (is_specialized_obj(tcT
) && dobj_of(tcT
).cls
.couldBeInterfaceOrTrait()) {
4212 tcT
= is_opt(tcT
) ? TOptObj
: TObj
;
4215 if (!constraint
.isSoft()) {
4216 // VerifyRetType will convert a TFunc to a TStr implicitly
4217 // (and possibly warn)
4218 if (tcT
.subtypeOf(TStr
) && stackT
.couldBe(BFunc
| BCls
)) {
4219 topC(env
, (int)ts_flavor
) = stackT
|= TStr
;
4222 // VerifyRetType will convert TClsMeth to TVec/TVArr/TArr implicitly
4223 if (stackT
.couldBe(BClsMeth
)) {
4224 if (tcT
.couldBe(BVec
)) {
4225 topC(env
, (int)ts_flavor
) = stackT
|= TVec
;
4227 if (tcT
.couldBe(BVArr
)) {
4228 topC(env
, (int)ts_flavor
) = stackT
|= TVArr
;
4230 if (tcT
.couldBe(TArr
)) {
4231 topC(env
, (int)ts_flavor
) = stackT
|= TArr
;
4236 // If CheckReturnTypeHints < 3 OR if the constraint is soft,
4237 // then there are no optimizations we can safely do here, so
4238 // just leave the top of stack as is.
4239 if (RuntimeOption::EvalCheckReturnTypeHints
< 3 || constraint
.isSoft()
4240 || (RuntimeOption::EvalThisTypeHintLevel
!= 3 && constraint
.isThis())) {
4241 if (ts_flavor
) popC(env
);
4245 // In cases where we have a `this` hint where stackT is an TOptObj known to
4246 // be this, we can replace the check with a non null check. These cases are
4247 // likely from a BareThis that could return Null. Since the runtime will
4248 // split these translations, it will rarely in practice return null.
4249 if (constraint
.isThis() && !constraint
.isNullable() && is_opt(stackT
) &&
4250 env
.index
.satisfies_constraint(env
.ctx
, unopt(stackT
), constraint
)) {
4253 reduce(env
, bc::PopC
{}, bc::VerifyRetNonNullC
{});
4256 reduce(env
, bc::VerifyRetNonNullC
{});
4261 auto retT
= intersection_of(std::move(tcT
), std::move(stackT
));
4262 if (retT
.subtypeOf(BBottom
)) {
4264 if (ts_flavor
) popC(env
); // the type structure
4268 if (ts_flavor
) popC(env
); // the type structure
4270 push(env
, std::move(retT
));
4273 void in(ISS
& env
, const bc::VerifyOutType
& op
) {
4274 verifyRetImpl(env
, env
.ctx
.func
->params
[op
.arg1
].typeConstraint
,
4278 void in(ISS
& env
, const bc::VerifyRetTypeC
& /*op*/) {
4279 verifyRetImpl(env
, env
.ctx
.func
->retTypeConstraint
, true, false);
4282 void in(ISS
& env
, const bc::VerifyRetTypeTS
& /*op*/) {
4283 auto const a
= topC(env
);
4284 auto const requiredTSType
= RuntimeOption::EvalHackArrDVArrs
? BDict
: BDArr
;
4285 if (!a
.couldBe(requiredTSType
)) {
4290 auto const constraint
= env
.ctx
.func
->retTypeConstraint
;
4291 // TODO(T31677864): We are being extremely pessimistic here, relax it
4292 if (!env
.ctx
.func
->isReified
&&
4293 (!env
.ctx
.cls
|| !env
.ctx
.cls
->hasReifiedGenerics
) &&
4294 !env
.index
.could_have_reified_type(constraint
)) {
4295 return reduce(env
, bc::PopC
{}, bc::VerifyRetTypeC
{});
4297 verifyRetImpl(env
, constraint
, true, true);
4300 void in(ISS
& env
, const bc::VerifyRetNonNullC
& /*op*/) {
4301 auto const constraint
= env
.ctx
.func
->retTypeConstraint
;
4302 if (RuntimeOption::EvalCheckReturnTypeHints
< 3 || constraint
.isSoft()
4303 || (RuntimeOption::EvalThisTypeHintLevel
!= 3 && constraint
.isThis())) {
4307 auto stackT
= topC(env
);
4309 if (!stackT
.couldBe(BInitNull
)) {
4310 reduce(env
, bc::Nop
{});
4314 if (stackT
.subtypeOf(BNull
)) return unreachable(env
);
4316 auto const equiv
= topStkEquiv(env
);
4318 if (is_opt(stackT
)) stackT
= unopt(std::move(stackT
));
4321 push(env
, stackT
, equiv
);
4324 void in(ISS
& env
, const bc::Self
& op
) {
4325 auto self
= selfClsExact(env
);
4326 putClsRefSlot(env
, op
.slot
, self
? *self
: TCls
);
4329 void in(ISS
& env
, const bc::Parent
& op
) {
4330 auto parent
= parentClsExact(env
);
4331 putClsRefSlot(env
, op
.slot
, parent
? *parent
: TCls
);
4334 void in(ISS
& env
, const bc::CreateCl
& op
) {
4335 auto const nargs
= op
.arg1
;
4336 auto const clsPair
= env
.index
.resolve_closure_class(env
.ctx
, op
.arg2
);
4339 * Every closure should have a unique allocation site, but we may see it
4340 * multiple times in a given round of analyzing this function. Each time we
4341 * may have more information about the used variables; the types should only
4342 * possibly grow. If it's already there we need to merge the used vars in
4343 * with what we saw last time.
4346 CompactVector
<Type
> usedVars(nargs
);
4347 for (auto i
= uint32_t{0}; i
< nargs
; ++i
) {
4348 usedVars
[nargs
- i
- 1] = unctx(popT(env
));
4350 merge_closure_use_vars_into(
4351 env
.collect
.closureUseTypes
,
4357 // Closure classes can be cloned and rescoped at runtime, so it's not safe to
4358 // assert the exact type of closure objects. The best we can do is assert
4359 // that it's a subclass of Closure.
4360 auto const closure
= env
.index
.builtin_class(s_Closure
.get());
4362 return push(env
, subObj(closure
));
4365 void in(ISS
& env
, const bc::CreateCont
& /*op*/) {
4366 // First resume is always next() which pushes null.
4367 push(env
, TInitNull
);
4370 void in(ISS
& env
, const bc::ContEnter
&) { popC(env
); push(env
, TInitCell
); }
4371 void in(ISS
& env
, const bc::ContRaise
&) { popC(env
); push(env
, TInitCell
); }
4373 void in(ISS
& env
, const bc::Yield
&) {
4375 push(env
, TInitCell
);
4378 void in(ISS
& env
, const bc::YieldK
&) {
4381 push(env
, TInitCell
);
4384 void in(ISS
& env
, const bc::ContAssignDelegate
&) {
4388 void in(ISS
& env
, const bc::ContEnterDelegate
&) {
4392 void in(ISS
& env
, const bc::YieldFromDelegate
&) {
4393 push(env
, TInitCell
);
4396 void in(ISS
& /*env*/, const bc::ContUnsetDelegate
&) {}
4398 void in(ISS
& /*env*/, const bc::ContCheck
&) {}
4399 void in(ISS
& env
, const bc::ContValid
&) { push(env
, TBool
); }
4400 void in(ISS
& env
, const bc::ContKey
&) { push(env
, TInitCell
); }
4401 void in(ISS
& env
, const bc::ContCurrent
&) { push(env
, TInitCell
); }
4402 void in(ISS
& env
, const bc::ContGetReturn
&) { push(env
, TInitCell
); }
4404 void pushTypeFromWH(ISS
& env
, Type t
) {
4405 auto inner
= typeFromWH(t
);
4406 // The next opcode is unreachable if awaiting a non-object or WaitH<Bottom>.
4407 if (inner
.subtypeOf(BBottom
)) unreachable(env
);
4408 push(env
, std::move(inner
));
4411 void in(ISS
& env
, const bc::WHResult
&) {
4412 pushTypeFromWH(env
, popC(env
));
4415 void in(ISS
& env
, const bc::Await
&) {
4416 pushTypeFromWH(env
, popC(env
));
4419 void in(ISS
& env
, const bc::AwaitAll
& op
) {
4420 auto const equiv
= equivLocalRange(env
, op
.locrange
);
4421 if (equiv
!= op
.locrange
.first
) {
4424 bc::AwaitAll
{LocalRange
{equiv
, op
.locrange
.count
}}
4428 for (uint32_t i
= 0; i
< op
.locrange
.count
; ++i
) {
4429 mayReadLocal(env
, op
.locrange
.first
+ i
);
4432 push(env
, TInitNull
);
4437 void idxImpl(ISS
& env
, bool arraysOnly
) {
4438 auto const def
= popC(env
);
4439 auto const key
= popC(env
);
4440 auto const base
= popC(env
);
4442 if (key
.subtypeOf(BInitNull
)) {
4443 // A null key, regardless of whether we're ArrayIdx or Idx will always
4444 // silently return the default value, regardless of the base type.
4447 return push(env
, def
);
4450 // Push the returned type and annotate effects appropriately, taking into
4451 // account if the base might be null. Allowing for a possibly null base lets
4452 // us capture more cases.
4453 auto const finish
= [&] (const Type
& t
, bool canThrow
) {
4454 // A null base will raise if we're ArrayIdx. For Idx, it will silently
4455 // return the default value.
4456 auto const baseMaybeNull
= base
.couldBe(BInitNull
);
4457 if (!canThrow
&& (!arraysOnly
|| !baseMaybeNull
)) {
4461 if (!arraysOnly
&& baseMaybeNull
) return push(env
, union_of(t
, def
));
4462 if (t
.subtypeOf(BBottom
)) unreachable(env
);
4463 return push(env
, t
);
4467 // If ArrayIdx, we'll raise an error for anything other than array-like and
4468 // null. This op is only terminal if null isn't possible.
4469 if (!base
.couldBe(BArr
| BVec
| BDict
| BKeyset
| BClsMeth
)) {
4470 return finish(key
.couldBe(BInitNull
) ? def
: TBottom
, true);
4473 !base
.couldBe(BArr
| BVec
| BDict
| BKeyset
| BStr
| BObj
| BClsMeth
)) {
4474 // Otherwise, any strange bases for Idx will just return the default value
4476 return finish(def
, false);
4479 // Helper for Hack arrays. "validKey" is the set key types which can return a
4480 // value from Idx. "silentKey" is the set of key types which will silently
4481 // return null (anything else throws). The Hack array elem functions will
4482 // treat values of "silentKey" as throwing, so we must identify those cases
4483 // and deal with them.
4484 auto const hackArr
= [&] (std::pair
<Type
, ThrowMode
> elem
,
4485 const Type
& validKey
,
4486 const Type
& silentKey
) {
4487 switch (elem
.second
) {
4488 case ThrowMode::None
:
4489 case ThrowMode::MaybeMissingElement
:
4490 case ThrowMode::MissingElement
:
4491 assertx(key
.subtypeOf(validKey
));
4492 return finish(elem
.first
, false);
4493 case ThrowMode::MaybeBadKey
:
4494 assertx(key
.couldBe(validKey
));
4495 if (key
.couldBe(silentKey
)) elem
.first
|= def
;
4496 return finish(elem
.first
, !key
.subtypeOf(BOptArrKey
));
4497 case ThrowMode::BadOperation
:
4498 assertx(!key
.couldBe(validKey
));
4499 return finish(key
.couldBe(silentKey
) ? def
: TBottom
, true);
4503 if (base
.subtypeOrNull(BVec
)) {
4504 // Vecs will throw for any key other than Int, Str, or Null, and will
4505 // silently return the default value for the latter two.
4506 if (key
.subtypeOrNull(BStr
)) return finish(def
, false);
4507 return hackArr(vec_elem(base
, key
, def
), TInt
, TOptStr
);
4510 if (base
.subtypeOfAny(TOptDict
, TOptKeyset
)) {
4511 // Dicts and keysets will throw for any key other than Int, Str, or Null,
4512 // and will silently return the default value for Null.
4513 auto const elem
= base
.subtypeOrNull(BDict
)
4514 ? dict_elem(base
, key
, def
)
4515 : keyset_elem(base
, key
, def
);
4516 return hackArr(elem
, TArrKey
, TInitNull
);
4519 if (base
.subtypeOrNull(BArr
)) {
4520 // A possibly null key is more complicated for arrays. array_elem() will
4521 // transform a null key into an empty string (matching the semantics of
4522 // array access), but that's not what Idx does. So, attempt to remove
4523 // nullish from the key first. If we can't, it just means we'll get a more
4524 // conservative value.
4525 auto maybeNull
= false;
4526 auto const fixedKey
= [&]{
4527 if (key
.couldBe(TInitNull
)) {
4529 if (is_nullish(key
)) return unnullish(key
);
4534 auto elem
= array_elem(base
, fixedKey
, def
);
4535 // If the key was null, Idx will return the default value, so add to the
4537 if (maybeNull
) elem
.first
|= def
;
4539 switch (elem
.second
) {
4540 case ThrowMode::None
:
4541 case ThrowMode::MaybeMissingElement
:
4542 case ThrowMode::MissingElement
:
4543 return finish(elem
.first
, false);
4544 case ThrowMode::MaybeBadKey
:
4545 return finish(elem
.first
, true);
4546 case ThrowMode::BadOperation
:
4547 always_assert(false);
4551 if (!arraysOnly
&& base
.subtypeOrNull(BStr
)) {
4552 // Idx on a string always produces a string or the default value (without
4554 return finish(union_of(TStr
, def
), false);
4557 // Objects or other unions of possible bases
4558 push(env
, TInitCell
);
4563 void in(ISS
& env
, const bc::Idx
&) { idxImpl(env
, false); }
4564 void in(ISS
& env
, const bc::ArrayIdx
&) { idxImpl(env
, true); }
4566 void in(ISS
& env
, const bc::CheckProp
&) {
4567 if (env
.ctx
.cls
->attrs
& AttrNoOverride
) {
4568 return reduce(env
, bc::False
{});
4574 void in(ISS
& env
, const bc::InitProp
& op
) {
4575 auto const t
= topC(env
);
4576 switch (op
.subop2
) {
4577 case InitPropOp::Static
:
4578 mergeSelfProp(env
, op
.str1
, t
);
4579 env
.collect
.publicSPropMutations
.merge(
4580 env
.index
, env
.ctx
, *env
.ctx
.cls
, sval(op
.str1
), t
4583 case InitPropOp::NonStatic
:
4584 mergeThisProp(env
, op
.str1
, t
);
4588 for (auto& prop
: env
.ctx
.func
->cls
->properties
) {
4589 if (prop
.name
!= op
.str1
) continue;
4591 ITRACE(1, "InitProp: {} = {}\n", op
.str1
, show(t
));
4593 if (env
.index
.satisfies_constraint(env
.ctx
, t
, prop
.typeConstraint
)) {
4594 prop
.attrs
|= AttrInitialSatisfiesTC
;
4596 badPropInitialValue(env
);
4597 prop
.attrs
= (Attr
)(prop
.attrs
& ~AttrInitialSatisfiesTC
);
4600 auto const v
= tv(t
);
4601 if (v
|| !could_contain_objects(t
)) {
4602 prop
.attrs
= (Attr
)(prop
.attrs
& ~AttrDeepInit
);
4605 return reduce(env
, bc::PopC
{});
4612 void in(ISS
& env
, const bc::Silence
& op
) {
4614 switch (op
.subop2
) {
4615 case SilenceOp::Start
:
4616 setLoc(env
, op
.loc1
, TInt
);
4618 case SilenceOp::End
:
4625 template <typename Op
, typename Rebind
>
4626 bool memoGetImpl(ISS
& env
, const Op
& op
, Rebind
&& rebind
) {
4627 always_assert(env
.ctx
.func
->isMemoizeWrapper
);
4628 always_assert(op
.locrange
.first
+ op
.locrange
.count
4629 <= env
.ctx
.func
->locals
.size());
4631 // If we can use an equivalent, earlier range, then use that instead.
4632 auto const equiv
= equivLocalRange(env
, op
.locrange
);
4633 if (equiv
!= op
.locrange
.first
) {
4634 reduce(env
, rebind(LocalRange
{ equiv
, op
.locrange
.count
}));
4638 auto retTy
= memoizeImplRetType(env
);
4639 if (retTy
.second
) constprop(env
);
4641 // MemoGet can raise if we give a non arr-key local, or if we're in a method
4642 // and $this isn't available.
4643 auto allArrKey
= true;
4644 for (uint32_t i
= 0; i
< op
.locrange
.count
; ++i
) {
4645 allArrKey
&= locRaw(env
, op
.locrange
.first
+ i
).subtypeOf(BArrKey
);
4648 (!env
.ctx
.func
->cls
||
4649 (env
.ctx
.func
->attrs
& AttrStatic
) ||
4650 thisAvailable(env
))) {
4654 if (retTy
.first
== TBottom
) {
4655 jmp_setdest(env
, op
.target1
);
4659 env
.propagate(op
.target1
, &env
.state
);
4660 push(env
, std::move(retTy
.first
));
4666 void in(ISS
& env
, const bc::MemoGet
& op
) {
4669 [&] (const LocalRange
& l
) { return bc::MemoGet
{ op
.target1
, l
}; }
4673 void in(ISS
& env
, const bc::MemoGetEager
& op
) {
4674 always_assert(env
.ctx
.func
->isAsync
&& !env
.ctx
.func
->isGenerator
);
4676 auto const reduced
= memoGetImpl(
4678 [&] (const LocalRange
& l
) {
4679 return bc::MemoGetEager
{ op
.target1
, op
.target2
, l
};
4682 if (reduced
) return;
4684 env
.propagate(op
.target2
, &env
.state
);
4685 auto const t
= popC(env
);
4688 is_specialized_wait_handle(t
) ? wait_handle_inner(t
) : TInitCell
4694 template <typename Op
>
4695 void memoSetImpl(ISS
& env
, const Op
& op
) {
4696 always_assert(env
.ctx
.func
->isMemoizeWrapper
);
4697 always_assert(op
.locrange
.first
+ op
.locrange
.count
4698 <= env
.ctx
.func
->locals
.size());
4700 // If we can use an equivalent, earlier range, then use that instead.
4701 auto const equiv
= equivLocalRange(env
, op
.locrange
);
4702 if (equiv
!= op
.locrange
.first
) {
4705 Op
{ LocalRange
{ equiv
, op
.locrange
.count
} }
4709 // MemoSet can raise if we give a non arr-key local, or if we're in a method
4710 // and $this isn't available.
4711 auto allArrKey
= true;
4712 for (uint32_t i
= 0; i
< op
.locrange
.count
; ++i
) {
4713 allArrKey
&= locRaw(env
, op
.locrange
.first
+ i
).subtypeOf(BArrKey
);
4716 (!env
.ctx
.func
->cls
||
4717 (env
.ctx
.func
->attrs
& AttrStatic
) ||
4718 thisAvailable(env
))) {
4721 push(env
, popC(env
));
4726 void in(ISS
& env
, const bc::MemoSet
& op
) {
4727 memoSetImpl(env
, op
);
4730 void in(ISS
& env
, const bc::MemoSetEager
& op
) {
4731 always_assert(env
.ctx
.func
->isAsync
&& !env
.ctx
.func
->isGenerator
);
4732 memoSetImpl(env
, op
);
4737 //////////////////////////////////////////////////////////////////////
4739 void dispatch(ISS
& env
, const Bytecode
& op
) {
4740 #define O(opcode, ...) case Op::opcode: interp_step::in(env, op.opcode); return;
4741 switch (op
.op
) { OPCODES
}
4746 //////////////////////////////////////////////////////////////////////
4748 template<class Iterator
, class... Args
>
4749 void group(ISS
& env
, Iterator
& it
, Args
&&... args
) {
4750 FTRACE(2, " {}\n", [&]() -> std::string
{
4751 auto ret
= std::string
{};
4752 for (auto i
= size_t{0}; i
< sizeof...(Args
); ++i
) {
4753 ret
+= " " + show(env
.ctx
.func
, it
[i
]);
4754 if (i
!= sizeof...(Args
) - 1) ret
+= ';';
4758 it
+= sizeof...(Args
);
4759 return interp_step::group(env
, std::forward
<Args
>(args
)...);
4762 template<class Iterator
>
4763 void interpStep(ISS
& env
, Iterator
& it
, Iterator stop
) {
4765 * During the analysis phase, we analyze some common bytecode
4766 * patterns involving conditional jumps as groups to be able to
4767 * add additional information to the type environment depending on
4768 * whether the branch is taken or not.
4770 auto const o1
= it
->op
;
4771 auto const o2
= it
+ 1 != stop
? it
[1].op
: Op::Nop
;
4772 auto const o3
= it
+ 1 != stop
&&
4773 it
+ 2 != stop
? it
[2].op
: Op::Nop
;
4781 return group(env, it, it[0].y, it[1].Not, it[2].JmpZ); \
4783 return group(env, it, it[0].y, it[1].Not, it[2].JmpNZ); \
4788 return group(env, it, it[0].y, it[1].JmpZ); \
4790 return group(env, it, it[0].y, it[1].JmpNZ); \
4807 FTRACE(2, " {}\n", show(env
.ctx
.func
, *it
));
4808 dispatch(env
, *it
++);
4811 template<class Iterator
>
4812 StepFlags
interpOps(Interp
& interp
,
4813 Iterator
& iter
, Iterator stop
,
4814 PropagateFn propagate
) {
4815 auto flags
= StepFlags
{};
4816 ISS env
{ interp
, flags
, propagate
};
4818 // If there are throw exit edges, make a copy of the state (except
4819 // stacks) in case we need to propagate across throw exits (if
4821 auto const stateBefore
= interp
.blk
->throwExits
.empty()
4823 : without_stacks(interp
.state
);
4825 auto const numPushed
= iter
->numPush();
4826 interpStep(env
, iter
, stop
);
4828 auto fix_const_outputs
= [&] {
4829 auto elems
= &interp
.state
.stack
.back();
4830 constexpr auto numCells
= 4;
4831 Cell cells
[numCells
];
4834 while (i
< numPushed
) {
4836 auto const v
= tv(elems
->type
);
4837 if (!v
) return false;
4839 } else if (!is_scalar(elems
->type
)) {
4845 while (++elems
, i
--) {
4846 elems
->type
= from_cell(i
< numCells
?
4847 cells
[i
] : *tv(elems
->type
));
4852 if (options
.ConstantProp
&& flags
.canConstProp
&& fix_const_outputs()) {
4854 FTRACE(2, " nothrow (due to constprop)\n");
4855 flags
.wasPEI
= false;
4857 if (!flags
.effectFree
) {
4858 FTRACE(2, " effect_free (due to constprop)\n");
4859 flags
.effectFree
= true;
4863 assertx(!flags
.effectFree
|| !flags
.wasPEI
);
4865 FTRACE(2, " PEI.\n");
4866 for (auto exit
: interp
.blk
->throwExits
) {
4867 propagate(exit
, &stateBefore
);
4875 BlockId
speculate(Interp
& interp
) {
4876 auto low_water
= interp
.state
.stack
.size();
4878 interp
.collect
.opts
= interp
.collect
.opts
| CollectionOpts::Speculating
;
4880 interp
.collect
.opts
= interp
.collect
.opts
- CollectionOpts::Speculating
;
4883 FTRACE(4, " Speculate B{}\n", interp
.bid
);
4884 auto const stop
= end(interp
.blk
->hhbcs
);
4885 auto iter
= begin(interp
.blk
->hhbcs
);
4886 while (iter
!= stop
) {
4887 auto const numPop
= iter
->numPop() +
4888 (iter
->op
== Op::CGetL2
? 1 :
4889 iter
->op
== Op::Dup
? -1 : 0);
4890 if (interp
.state
.stack
.size() - numPop
< low_water
) {
4891 low_water
= interp
.state
.stack
.size() - numPop
;
4894 auto const flags
= interpOps(interp
, iter
, stop
,
4895 [] (BlockId
, const State
*) {});
4896 if (!flags
.effectFree
) {
4897 FTRACE(3, " Bailing from speculate because not effect free\n");
4901 if (flags
.usedLocalStatics
) {
4902 FTRACE(3, " Bailing from speculate because local statics were used\n");
4906 assertx(!flags
.returned
);
4907 assertx(!interp
.state
.unreachable
);
4909 if (flags
.jmpDest
!= NoBlockId
&& interp
.state
.stack
.size() == low_water
) {
4910 FTRACE(2, " Speculate found destination block {}\n", flags
.jmpDest
);
4911 return flags
.jmpDest
;
4918 BlockId
speculateHelper(Interp
& interpIn
, BlockId target
, bool unconditional
) {
4919 if (target
== NoBlockId
|| !options
.RemoveDeadBlocks
) return target
;
4922 auto const fallthrough
= target
== interpIn
.blk
->fallthrough
;
4924 folly::Optional
<State
> state
;
4926 auto const func
= interpIn
.ctx
.func
;
4927 auto const targetBlk
= func
->blocks
[target
].get();
4928 if (!targetBlk
->multiPred
) return target
;
4929 switch (targetBlk
->hhbcs
.back().op
) {
4939 if (!state
) state
.emplace(interpIn
.state
);
4942 interpIn
.index
, interpIn
.ctx
, interpIn
.collect
, target
, targetBlk
, *state
4945 auto const new_target
= speculate(interp
);
4946 if (new_target
== NoBlockId
) return target
;
4948 pops
+= interpIn
.state
.stack
.size() - state
->stack
.size();
4949 if (pops
> std::numeric_limits
<decltype(
4950 interpIn
.state
.speculatedPops
)>::max()) {
4953 target
= new_target
;
4954 interpIn
.state
= *state
;
4955 interpIn
.state
.speculated
= target
;
4956 interpIn
.state
.speculatedPops
= pops
;
4957 interpIn
.state
.speculatedIsUnconditional
= unconditional
;
4958 interpIn
.state
.speculatedIsFallThrough
= fallthrough
;
4966 //////////////////////////////////////////////////////////////////////
4968 RunFlags
run(Interp
& interp
, PropagateFn propagate
) {
4970 FTRACE(2, "out {}{}\n",
4971 state_string(*interp
.ctx
.func
, interp
.state
, interp
.collect
),
4972 property_state_string(interp
.collect
.props
));
4975 interp
.state
.speculated
= NoBlockId
;
4976 interp
.state
.speculatedPops
= 0;
4977 interp
.state
.speculatedIsFallThrough
= false;
4978 interp
.state
.speculatedIsUnconditional
= false;
4980 auto ret
= RunFlags
{};
4981 auto const stop
= end(interp
.blk
->hhbcs
);
4982 auto iter
= begin(interp
.blk
->hhbcs
);
4983 while (iter
!= stop
) {
4984 auto const flags
= interpOps(interp
, iter
, stop
, propagate
);
4985 if (interp
.collect
.effectFree
&& !flags
.effectFree
) {
4986 interp
.collect
.effectFree
= false;
4987 if (any(interp
.collect
.opts
& CollectionOpts::EffectFreeOnly
)) {
4988 FTRACE(2, " Bailing because not effect free\n");
4993 if (flags
.usedLocalStatics
) {
4994 if (!ret
.usedLocalStatics
) {
4995 ret
.usedLocalStatics
= std::move(flags
.usedLocalStatics
);
4997 for (auto& elm
: *flags
.usedLocalStatics
) {
4998 ret
.usedLocalStatics
->insert(std::move(elm
));
5003 if (interp
.state
.unreachable
) {
5004 FTRACE(2, " <bytecode fallthrough is unreachable>\n");
5008 if (flags
.jmpDest
!= NoBlockId
) {
5009 if (flags
.jmpDest
!= interp
.blk
->fallthrough
) {
5010 FTRACE(2, " <took branch; no fallthrough>\n");
5012 FTRACE(2, " <branch never taken>\n");
5014 propagate(speculateHelper(interp
, flags
.jmpDest
, true), &interp
.state
);
5018 if (flags
.returned
) {
5019 FTRACE(2, " returned {}\n", show(*flags
.returned
));
5020 always_assert(iter
== stop
);
5021 always_assert(interp
.blk
->fallthrough
== NoBlockId
);
5022 if (!ret
.returned
) {
5023 ret
.retParam
= flags
.retParam
;
5024 } else if (ret
.retParam
!= flags
.retParam
) {
5025 ret
.retParam
= NoLocalId
;
5027 ret
.returned
= flags
.returned
;
5032 FTRACE(2, " <end block>\n");
5033 if (interp
.blk
->fallthrough
!= NoBlockId
) {
5034 propagate(speculateHelper(interp
, interp
.blk
->fallthrough
, false), &interp
.state
);
5039 StepFlags
step(Interp
& interp
, const Bytecode
& op
) {
5040 auto flags
= StepFlags
{};
5041 auto noop
= [] (BlockId
, const State
*) {};
5042 ISS env
{ interp
, flags
, noop
};
5047 void default_dispatch(ISS
& env
, const Bytecode
& op
) {
5051 folly::Optional
<Type
> thisType(const Index
& index
, Context ctx
) {
5052 return thisTypeFromContext(index
, ctx
);
5055 //////////////////////////////////////////////////////////////////////