Move func_num_args() to an opcode
[hiphop-php.git] / hphp / hhbbc / interp.cpp
blob2708f3f5dcabe9e487aa2119e839ed27ad1b1fdf
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/interp.h"
18 #include <algorithm>
19 #include <vector>
20 #include <string>
21 #include <iterator>
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 //////////////////////////////////////////////////////////////////////
60 namespace {
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) {
101 unreachable(env);
103 if (reduce) {
104 std::move(begin(*env.flags.strengthReduced),
105 end(*env.flags.strengthReduced),
106 std::back_inserter(currentReduction));
108 } else {
109 if (instrFlags(it->op) & TF) {
110 unreachable(env);
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--; ) {
116 --stk;
117 if (!is_scalar(stk->type)) return;
119 env.flags.effectFree = true;
120 env.flags.wasPEI = false;
122 if (reduce) {
123 auto added = false;
124 if (env.flags.canConstProp) {
125 if (env.collect.propagate_constants) {
126 if (env.collect.propagate_constants(*it, env.state,
127 currentReduction)) {
128 added = true;
129 env.flags.canConstProp = false;
130 env.flags.wasPEI = false;
131 env.flags.effectFree = true;
133 } else {
134 applyConstProp();
137 if (!added) currentReduction.push_back(std::move(*it));
138 } else if (env.flags.canConstProp) {
139 applyConstProp();
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;
151 if (reduce) {
152 env.flags.strengthReduced = std::move(currentReduction);
153 } else {
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;
162 do {
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)) {
168 return false;
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)) {
175 return false;
179 return true;
180 }();
182 if (equivRange) {
183 bestRange = equivFirst;
186 equivFirst = findLocEquiv(env, equivFirst);
187 assert(equivFirst != NoLocalId);
188 } while (equivFirst != range.first);
190 return bestRange;
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);
197 return nullptr;
200 namespace interp_step {
202 void in(ISS& env, const bc::Nop&) { effect_free(env); }
203 void in(ISS& env, const bc::DiscardClsRef& op) {
204 nothrow(env);
205 takeClsRefSlot(env, op.slot);
207 void in(ISS& env, const bc::PopC&) {
208 effect_free(env);
209 popC(env);
211 void in(ISS& env, const bc::PopU&) { effect_free(env); popU(env); }
212 void in(ISS& env, const bc::PopU2&) {
213 effect_free(env);
214 auto equiv = topStkEquiv(env);
215 auto val = popC(env);
216 popU(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*/) {
224 effect_free(env);
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);
233 effect_free(env);
236 void in(ISS& env, const bc::AssertRATStk&) {
237 effect_free(env);
240 void in(ISS& env, const bc::BreakTraceHint&) { effect_free(env); }
242 void in(ISS& env, const bc::Box&) {
243 effect_free(env);
244 popC(env);
245 push(env, TRef);
248 void in(ISS& env, const bc::Unbox&) {
249 effect_free(env);
250 popV(env);
251 push(env, TInitCell);
254 void in(ISS& env, const bc::CGetCUNop&) {
255 effect_free(env);
256 auto const t = popCU(env);
257 push(env, remove_uninit(t));
260 void in(ISS& env, const bc::UGetCUNop&) {
261 effect_free(env);
262 popCU(env);
263 push(env, TUninit);
266 void in(ISS& env, const bc::Null&) {
267 effect_free(env);
268 push(env, TInitNull);
271 void in(ISS& env, const bc::NullUninit&) {
272 effect_free(env);
273 push(env, TUninit);
276 void in(ISS& env, const bc::True&) {
277 effect_free(env);
278 push(env, TTrue);
281 void in(ISS& env, const bc::False&) {
282 effect_free(env);
283 push(env, TFalse);
286 void in(ISS& env, const bc::Int& op) {
287 effect_free(env);
288 push(env, ival(op.arg1));
291 void in(ISS& env, const bc::Double& op) {
292 effect_free(env);
293 push(env, dval(op.dbl1));
296 void in(ISS& env, const bc::String& op) {
297 effect_free(env);
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());
304 effect_free(env);
305 push(env, aval(op.arr1));
308 void in(ISS& env, const bc::Vec& op) {
309 assert(op.arr1->isVecArray());
310 effect_free(env);
311 push(env, vec_val(op.arr1));
314 void in(ISS& env, const bc::Dict& op) {
315 assert(op.arr1->isDict());
316 effect_free(env);
317 push(env, dict_val(op.arr1));
320 void in(ISS& env, const bc::Keyset& op) {
321 assert(op.arr1->isKeyset());
322 effect_free(env);
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)));
349 constprop(env);
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)));
361 constprop(env);
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)));
376 constprop(env);
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)));
386 constprop(env);
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)));
395 constprop(env);
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);
405 constprop(env);
406 push(env, vec(std::move(elems)));
409 void in(ISS& env, const bc::NewKeysetArray& op) {
410 assert(op.arg1 > 0);
411 auto map = MapElems{};
412 auto ty = TBottom;
413 auto useMap = true;
414 auto bad = false;
415 for (auto i = uint32_t{0}; i < op.arg1; ++i) {
416 auto k = disect_strict_key(popC(env));
417 if (k.type == TBottom) {
418 bad = true;
419 useMap = false;
421 if (useMap) {
422 if (auto const v = k.tv()) {
423 map.emplace_front(*v, k.type);
424 } else {
425 useMap = false;
428 ty |= std::move(k.type);
430 if (useMap) {
431 push(env, keyset_map(std::move(map)));
432 constprop(env);
433 } else if (!bad) {
434 push(env, keyset_n(ty));
435 } else {
436 unreachable(env);
437 push(env, TBottom);
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);
458 return folly::none;
459 }(popC(env));
461 if (!outTy) {
462 return push(env, union_of(TArr, TDict));
465 if (outTy->first.subtypeOf(BBottom)) {
466 unreachable(env);
467 } else if (outTy->second == ThrowMode::None) {
468 nothrow(env);
469 if (any(env.collect.opts & CollectionOpts::TrackConstantArrays)) {
470 constprop(env);
473 push(env, std::move(outTy->first));
476 void in(ISS& env, const bc::AddElemV& /*op*/) {
477 popV(env); popC(env);
478 popC(env);
479 push(env, TArr);
482 void in(ISS& env, const bc::AddNewElemC&) {
483 auto v = popC(env);
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;
495 return folly::none;
496 }(popC(env));
498 if (!outTy) {
499 return push(env, TInitCell);
502 if (outTy->subtypeOf(BBottom)) {
503 unreachable(env);
504 } else {
505 if (any(env.collect.opts & CollectionOpts::TrackConstantArrays)) {
506 constprop(env);
509 push(env, std::move(*outTy));
512 void in(ISS& env, const bc::AddNewElemV&) {
513 popV(env);
514 popC(env);
515 push(env, TArr);
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) {
531 popC(env);
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.
541 fallback = nullptr;
544 auto t = env.index.lookup_constant(env.ctx, str, fallback);
545 if (!t) {
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.
549 Cell val;
550 val.m_type = kReadOnlyConstant;
551 env.collect.cnsMap.emplace(str, val);
552 t = TInitCell;
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.
558 constprop(env);
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));
586 return;
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) {
600 return reduce(env,
601 bc::DiscardClsRef { op.slot },
602 bc::String { dcls.cls.name() });
605 nothrow(env);
606 takeClsRefSlot(env, op.slot);
607 push(env, TSStr);
610 void concatHelper(ISS& env, uint32_t n) {
611 uint32_t i = 0;
612 StringData* result = nullptr;
613 while (i < n) {
614 auto const t = topC(env, i);
615 auto const v = tv(t);
616 if (!v) break;
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) {
622 break;
624 auto const cell = eval_cell_value([&] {
625 auto const s = makeStaticString(
626 result ?
627 StringData::Make(tvAsCVarRef(&*v).toString().get(), result) :
628 tvAsCVarRef(&*v).toString().get());
629 return make_tv<KindOfString>(s);
631 if (!cell) break;
632 result = cell->m_data.pstr;
633 i++;
635 if (result && i >= 2) {
636 BytecodeVec bcs(i, bc::PopC {});
637 bcs.push_back(gen_constant(make_tv<KindOfString>(result)));
638 if (i < n) {
639 bcs.push_back(bc::ConcatN { n - i + 1 });
641 return reduce(env, std::move(bcs));
643 discard(env, n);
644 push(env, TStr);
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) {
658 constprop(env);
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);
682 if (v) {
683 constprop(env);
684 auto cell = eval_cell([&] {
685 auto c = *v;
686 cellBitNot(c);
687 return c;
689 if (cell) return push(env, std::move(*cell));
691 push(env, TInitCell);
694 namespace {
696 bool couldBeHackArr(Type t) {
697 return t.couldBe(BVec | BDict | BKeyset);
700 template<bool NSame>
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;
728 if (v1 && v2) {
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);
737 }();
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);
748 discard(env, 2);
750 if (!pair.second) {
751 nothrow(env);
752 constprop(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))) {
782 return bail();
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)
791 discard(env, 2);
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
798 // the same thing.
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)) {
806 auto loc = loc0;
807 while (true) {
808 auto const other = findLocEquiv(env, loc);
809 if (other == NoLocalId) break;
810 killLocEquiv(env, loc);
811 addLocEquiv(env, loc, loc1);
812 loc = other;
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;
827 return ty;
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));
837 return t;
838 } else if (ty.strictSubtypeOf(TBool) && t.subtypeOf(BBool)) {
839 return ty == TFalse ? TTrue : TFalse;
841 return t;
845 auto handle_differ = [&] {
846 return
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); }
886 template<class Fun>
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);
892 if (v1 && v2) {
893 if (auto r = eval_cell_value([&]{ return fun(*v2, *v1); })) {
894 constprop(env);
895 return push(env, *r ? TTrue : TFalse);
898 // TODO_4: evaluate when these can throw, non-constant type stuff.
899 push(env, TBool);
902 template<class Fun>
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);
908 if (v1 && v2) {
909 if (auto r = eval_cell_value([&]{ return ival(fun(*v2, *v1)); })) {
910 constprop(env);
911 return push(env, std::move(*r));
914 // TODO_4: evaluate when these can throw, non-constant type stuff.
915 push(env, TInt);
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);
922 discard(env, 2);
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);
931 discard(env, 2);
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) {
956 nothrow(env);
957 constprop(env);
959 auto const e = emptiness(t);
960 switch (e) {
961 case Emptiness::Empty:
962 case Emptiness::NonEmpty:
963 return push(env, (e == Emptiness::Empty) == negate ? TTrue : TFalse);
964 case Emptiness::Maybe:
965 break;
968 push(env, TBool);
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&) {
982 constprop(env);
983 auto const t = topC(env);
984 if (t.subtypeOf(BInt)) return reduce(env, bc::Nop {});
985 popC(env);
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));
994 push(env, TInt);
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 {});
1004 popC(env);
1005 if (fn) {
1006 if (auto val = tv(t)) {
1007 if (auto result = eval_cell([&] { fn(&*val); return *val; })) {
1008 constprop(env);
1009 target = *result;
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&) {
1053 nothrow(env);
1054 constprop(env);
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));
1063 return;
1066 push(env, TInt);
1069 void in(ISS& env, const bc::Print& /*op*/) {
1070 popC(env);
1071 push(env, ival(1));
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)) {
1098 effect_free(env);
1099 jmp_setdest(env, op.target1);
1100 return;
1103 if (e == (Negate ? Emptiness::Empty : Emptiness::NonEmpty)) {
1104 effect_free(env);
1105 jmp_nevertaken(env);
1106 return;
1109 if (next_real_block(*env.ctx.func, env.blk.fallthrough) ==
1110 next_real_block(*env.ctx.func, op.target1)) {
1111 effect_free(env);
1112 jmp_nevertaken(env);
1113 return;
1116 nothrow(env);
1118 if (location == NoLocalId) return env.propagate(op.target1, &env.state);
1120 refineLocation(env, location,
1121 Negate ? assert_nonemptiness : assert_emptiness,
1122 op.target1,
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);
1134 nothrow(env);
1135 constprop(env);
1137 switch (emptiness(cond)) {
1138 case Emptiness::Maybe:
1139 discard(env, 3);
1140 push(env, union_of(t, f));
1141 return;
1142 case Emptiness::NonEmpty:
1143 discard(env, 3);
1144 push(env, t);
1145 return;
1146 case Emptiness::Empty:
1147 return reduce(env, bc::PopC {}, bc::PopC {});
1149 not_reached();
1152 namespace {
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) {
1160 return bail();
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();
1172 testTy = TClsMeth;
1174 } else {
1175 if ((typeOp == IsTypeOp::Arr) || (typeOp == IsTypeOp::VArray)) {
1176 if (val.couldBe(BArr | BVArr)) return bail();
1177 testTy = TClsMeth;
1182 if (!val.subtypeOf(BCell) || val.subtypeOf(testTy) || !val.couldBe(testTy)) {
1183 return bail();
1186 if (istype.op == Op::IsTypeC) {
1187 if (!is_type_might_raise(testTy, val)) nothrow(env);
1188 popT(env);
1189 } else if (!locCouldBeUninit(env, location) &&
1190 !is_type_might_raise(testTy, val)) {
1191 nothrow(env);
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;
1209 return t;
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);
1235 return v;
1238 useLocalStatic(env, l);
1239 return folly::none;
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
1244 // free.
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(
1254 env.ctx,
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));
1260 }();
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.
1276 auto const clsTy =
1277 env.ctx.func->isMemoizeWrapperLSB ?
1278 selfCls(env) :
1279 selfClsExact(env);
1280 return clsTy ? *clsTy : TCls;
1281 } else {
1282 auto const s = thisType(env);
1283 return s ? *s : TObj;
1286 return TBottom;
1287 }();
1289 auto retTy = env.index.lookup_return_type(
1290 env.ctx,
1291 args,
1292 ctxType,
1293 memo_impl_func
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,
1307 const JmpOp& jmp) {
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);
1318 if (takenOnInit) {
1319 jmp_nevertaken(env);
1320 } else {
1321 jmp_setdest(env, jmp.target1);
1323 return;
1326 if (takenOnInit) {
1327 env.propagate(jmp.target1, &env.state);
1328 env.state = std::move(save);
1329 } else {
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));
1376 namespace {
1378 template<class JmpOp>
1379 void instanceOfJmpImpl(ISS& env,
1380 const bc::InstanceOfD& inst,
1381 const JmpOp& jmp) {
1382 auto bail = [&] { impl(env, inst, jmp); };
1384 auto const locId = topStkEquiv(env);
1385 if (locId == NoLocalId || interface_supports_non_objects(inst.str1)) {
1386 return bail();
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)) {
1394 return bail();
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);
1401 popC(env);
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,
1416 const JmpOp& jmp) {
1417 instanceOfJmpImpl(env, inst, jmp);
1420 template<class JmpOp>
1421 void group(ISS& env,
1422 const bc::InstanceOfD& inst,
1423 const bc::Not&,
1424 const JmpOp& jmp) {
1425 instanceOfJmpImpl(env, inst, invertJmp(jmp));
1428 namespace {
1430 template<class JmpOp>
1431 void isTypeStructCJmpImpl(ISS& env,
1432 const bc::IsTypeStructC& inst,
1433 const JmpOp& jmp) {
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) {
1452 return bail();
1455 auto const locId = topStkEquiv(env, 1);
1456 if (locId == NoLocalId || interface_supports_non_objects(clsName)) {
1457 return bail();
1460 auto const val = topC(env, 1);
1461 auto const instTy = subObj(*rcls);
1462 if (val.subtypeOf(instTy) || !val.couldBe(instTy)) {
1463 return bail();
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);
1470 popC(env);
1471 popC(env);
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);
1479 } else {
1480 return bail();
1484 } // namespace
1486 template<class JmpOp>
1487 void group(ISS& env,
1488 const bc::IsTypeStructC& inst,
1489 const JmpOp& jmp) {
1490 isTypeStructCJmpImpl(env, inst, jmp);
1493 template<class JmpOp>
1494 void group(ISS& env,
1495 const bc::IsTypeStructC& inst,
1496 const bc::Not&,
1497 const JmpOp& jmp) {
1498 isTypeStructCJmpImpl(env, inst, invertJmp(jmp));
1501 void in(ISS& env, const bc::Switch& op) {
1502 auto v = tv(popC(env));
1504 if (v) {
1505 auto go = [&] (BlockId blk) {
1506 effect_free(env);
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]);
1515 } else {
1516 assertx(num_elems > 2);
1517 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));
1525 if (!match) break;
1526 if (*match) {
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));
1541 if (v) {
1542 for (auto& kv : op.targets) {
1543 auto match = eval_cell_value([&] {
1544 return !kv.first || cellEqual(*v, kv.first);
1546 if (!match) break;
1547 if (*match) {
1548 effect_free(env);
1549 jmp_setdest(env, kv.second);
1550 return;
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);
1579 doRet(
1580 env,
1581 is_specialized_wait_handle(t) ? wait_handle_inner(t) : TInitCell,
1582 false
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*/) {
1595 popC(env);
1598 void in(ISS& env, const bc::Catch&) {
1599 nothrow(env);
1600 return push(env, subObj(env.index.builtin_class(s_Throwable.get())));
1603 void in(ISS& env, const bc::ChainFaults&) {
1604 popC(env);
1607 void in(ISS& env, const bc::NativeImpl&) {
1608 killLocals(env);
1609 mayUseVV(env);
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)) {
1629 nothrow(env);
1630 constprop(env);
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 });
1639 nothrow(env);
1640 constprop(env);
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 {});
1649 nothrow(env);
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)) {
1689 nothrow(env);
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)) {
1718 constprop(env);
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) {
1728 nothrow(env);
1729 setLocRaw(env, op.loc1, TRef);
1730 push(env, 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);
1744 } else {
1745 killSelfProps(env);
1749 env.collect.publicSPropMutations.merge(
1750 env.index, env.ctx, tcls, tname, TRef
1753 push(env, TRef);
1756 void clsRefGetImpl(ISS& env, Type t1, ClsRefSlotId slot) {
1757 auto cls = [&]{
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)) {
1763 nothrow(env);
1764 return objcls(t1);
1766 return TCls;
1767 }();
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)) {
1780 push(env, TBottom);
1781 return;
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
1791 // false.
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));
1801 if (!mayThrow) {
1802 constprop(env);
1803 effect_free(env);
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));
1829 return finish(
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
1871 push(env, TBool);
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());
1903 } else {
1904 resolvedCls = env.index.resolve_class(env.ctx, tc.typeName());
1907 return memoKeyConstraintFromTC(tc);
1908 }();
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.
1915 switch (mkc) {
1916 case MK::Int:
1917 // Always an int, so the key is always an identity mapping
1918 if (inTy.subtypeOf(BInt)) return reduce(env, bc::CGetL { op.loc1 });
1919 break;
1920 case MK::Bool:
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 {});
1925 break;
1926 case MK::Str:
1927 // Always a string, so the key is always an identity mapping
1928 if (inTy.subtypeOf(BStr)) return reduce(env, bc::CGetL { op.loc1 });
1929 break;
1930 case MK::IntOrStr:
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 });
1933 break;
1934 case MK::StrOrNull:
1935 // A nullable string. The key will either be the string or the integer
1936 // zero.
1937 if (inTy.subtypeOrNull(BStr)) {
1938 return reduce(
1939 env,
1940 bc::CGetL { op.loc1 },
1941 bc::Int { 0 },
1942 bc::IsTypeL { op.loc1, IsTypeOp::Null },
1943 bc::Select {}
1946 break;
1947 case MK::IntOrNull:
1948 // A nullable int. The key will either be the integer, or the static empty
1949 // string.
1950 if (inTy.subtypeOrNull(BInt)) {
1951 return reduce(
1952 env,
1953 bc::CGetL { op.loc1 },
1954 bc::String { staticEmptyString() },
1955 bc::IsTypeL { op.loc1, IsTypeOp::Null },
1956 bc::Select {}
1959 break;
1960 case MK::BoolOrNull:
1961 // A nullable bool. The key will either be 0, 1, or 2.
1962 if (inTy.subtypeOrNull(BBool)) {
1963 return reduce(
1964 env,
1965 bc::CGetL { op.loc1 },
1966 bc::CastInt {},
1967 bc::Int { 2 },
1968 bc::IsTypeL { op.loc1, IsTypeOp::Null },
1969 bc::Select {}
1972 break;
1973 case MK::Dbl:
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 {});
1978 break;
1979 case MK::DblOrNull:
1980 // A nullable double. The key will be an integer, or the static empty
1981 // string.
1982 if (inTy.subtypeOrNull(BDbl)) {
1983 return reduce(
1984 env,
1985 bc::CGetL { op.loc1 },
1986 bc::DblAsBits {},
1987 bc::String { staticEmptyString() },
1988 bc::IsTypeL { op.loc1, IsTypeOp::Null },
1989 bc::Select {}
1992 break;
1993 case MK::Object:
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.
1999 if (resolvedCls &&
2000 resolvedCls->subtypeOf(rclsIMemoizeParam) &&
2001 inTy.subtypeOf(tyIMemoizeParam)) {
2002 return reduce(
2003 env,
2004 bc::CGetL { op.loc1 },
2005 bc::FPushObjMethodD {
2007 s_getInstanceKey.get(),
2008 ObjMethodOp::NullThrows,
2009 false
2011 bc::FCall { FCallArgs(0), staticEmptyString(), staticEmptyString() },
2012 bc::CastString {}
2015 break;
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.
2021 if (resolvedCls &&
2022 resolvedCls->subtypeOf(rclsIMemoizeParam) &&
2023 inTy.subtypeOf(opt(tyIMemoizeParam))) {
2024 return reduce(
2025 env,
2026 bc::CGetL { op.loc1 },
2027 bc::FPushObjMethodD {
2029 s_getInstanceKey.get(),
2030 ObjMethodOp::NullSafe,
2031 false
2033 bc::FCall { FCallArgs(0), staticEmptyString(), staticEmptyString() },
2034 bc::CastString {},
2035 bc::Int { 0 },
2036 bc::IsTypeL { op.loc1, IsTypeOp::Null },
2037 bc::Select {}
2040 break;
2041 case MK::None:
2042 break;
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)) {
2058 return reduce(
2059 env,
2060 bc::CGetL { op.loc1 },
2061 bc::String { s_nullMemoKey.get() },
2062 bc::IsTypeL { op.loc1, IsTypeOp::Null },
2063 bc::Select {}
2066 if (inTy.subtypeOf(BBool)) {
2067 return reduce(
2068 env,
2069 bc::String { s_falseMemoKey.get() },
2070 bc::String { s_trueMemoKey.get() },
2071 bc::CGetL { op.loc1 },
2072 bc::Select {}
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.
2078 auto keyTy = [&]{
2079 if (inTy.subtypeOrNull(BBool)) return TSStr;
2080 if (inTy.couldBe(BInt)) return union_of(TInt, TStr);
2081 return TStr;
2082 }();
2083 push(env, std::move(keyTy));
2086 void in(ISS& env, const bc::IssetL& op) {
2087 if (locIsThis(env, op.loc1)) {
2088 return reduce(env,
2089 bc::BareThis { BareThisOp::NoNotice },
2090 bc::IsTypeC { IsTypeOp::Null },
2091 bc::Not {});
2093 nothrow(env);
2094 constprop(env);
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);
2098 push(env, TBool);
2101 void in(ISS& env, const bc::EmptyL& op) {
2102 nothrow(env);
2103 constprop(env);
2104 castBoolImpl(env, locAsCell(env, op.loc1), true);
2107 void in(ISS& env, const bc::EmptyS& op) {
2108 takeClsRefSlot(env, op.slot);
2109 popC(env);
2110 push(env, TBool);
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)) {
2141 constprop(env);
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); }
2150 push(env, TBool);
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);
2159 push(env, TBool);
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);
2170 push(env, TBool);
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);
2180 push(env, TBool);
2183 namespace {
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) {
2188 if (t.subtypeOf(
2189 op == IsTypeOp::Vec ? BClsMeth | BVec : BClsMeth | BVArr)) {
2190 push(env, TTrue);
2191 } else if (t.couldBe(op == IsTypeOp::Vec ? BVec : BVArr)) {
2192 push(env, TBool);
2193 } else {
2194 isTypeImpl(env, t, TClsMeth);
2196 return true;
2198 } else {
2199 if (op == IsTypeOp::Arr || op == IsTypeOp::VArray) {
2200 if (t.subtypeOf(
2201 op == IsTypeOp::VArray ? BClsMeth | BVArr : BClsMeth | BArr)) {
2202 push(env, TTrue);
2203 } else if (t.couldBe(op == IsTypeOp::VArray ? BVArr : BArr)) {
2204 push(env, TBool);
2205 } else {
2206 isTypeImpl(env, t, TClsMeth);
2208 return true;
2212 return false;
2216 template<class Op>
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)) {
2220 constprop(env);
2221 nothrow(env);
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));
2234 template<class Op>
2235 void isTypeCImpl(ISS& env, const Op& op) {
2236 auto const t1 = popC(env);
2237 if (!is_type_might_raise(op.subop1, t1)) {
2238 constprop(env);
2239 nothrow(env);
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) {
2261 nothrow(env);
2262 if (r != TBool) constprop(env);
2263 popC(env);
2264 push(env, r);
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);
2270 if (is_opt(t1)) {
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);
2279 popC(env);
2280 push(env, 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) {
2294 case DObj::Sub:
2295 break;
2296 case DObj::Exact:
2297 return reduce(env, bc::PopC {},
2298 bc::InstanceOfD { dobj.cls.name() });
2302 popC(env);
2303 popC(env);
2304 push(env, TBool);
2307 namespace {
2309 bool isValidTypeOpForIsAs(const IsTypeOp& op) {
2310 switch (op) {
2311 case IsTypeOp::Null:
2312 case IsTypeOp::Bool:
2313 case IsTypeOp::Int:
2314 case IsTypeOp::Dbl:
2315 case IsTypeOp::Str:
2316 case IsTypeOp::Obj:
2317 return true;
2318 case IsTypeOp::Res:
2319 case IsTypeOp::Arr:
2320 case IsTypeOp::Vec:
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:
2328 return false;
2330 not_reached();
2333 template<bool asExpression>
2334 void isAsTypeStructImpl(ISS& env, SArray ts) {
2335 auto const t = topC(env, 1); // operand to is/as
2337 auto result = [&] (
2338 const Type& out,
2339 const folly::Optional<Type>& test = folly::none
2341 if (asExpression && out.subtypeOf(BTrue)) {
2342 constprop(env);
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) {
2349 constprop(env);
2350 return push(env, out);
2352 if (out.subtypeOf(BFalse)) {
2353 push(env, t);
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)) {
2363 ret |= TUninit;
2365 return ret;
2366 })) {
2367 unreachable(env);
2369 return push(env, newT);
2372 auto check = [&] (
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) {
2380 if (test == TVec) {
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;
2389 } else {
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);
2428 constprop(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)
2444 : check(ts_type);
2445 case TypeStructure::Kind::T_shape:
2446 return RuntimeOption::EvalHackArrCompatIsArrayNotices
2447 ? check(ts_type, TVArr)
2448 : check(ts_type);
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) {
2461 return reduce(env,
2462 bc::PopC {},
2463 bc::IsTypeC { IsTypeOp::Null },
2464 bc::Not {});
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,
2477 // we need to bail
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,
2493 // we need to bail
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);
2518 not_reached();
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:
2541 return true;
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: {
2551 auto result = true;
2552 IterateV(
2553 get_ts_elem_types(ts),
2554 [&](TypedValue v) {
2555 assertx(isArrayLikeType(v.m_type));
2556 result &= canReduceToDontResolve(v.m_data.parr);
2557 // when result is false, we can short circuit
2558 return !result;
2561 return result;
2563 case TypeStructure::Kind::T_shape: {
2564 auto result = true;
2565 IterateV(
2566 get_ts_fields(ts),
2567 [&](TypedValue v) {
2568 assertx(isArrayLikeType(v.m_type));
2569 auto const arr = v.m_data.parr;
2570 if (arr->exists(s_is_cls_cns)) {
2571 result = false;
2572 return true; // short circuit
2574 result &= canReduceToDontResolve(get_ts_value_field(arr));
2575 // when result is false, we can short circuit
2576 return !result;
2579 return result;
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:
2595 return false;
2597 not_reached();
2600 } // namespace
2602 void in(ISS& env, const bc::IsTypeStructC& op) {
2603 auto const requiredTSType = RuntimeOption::EvalHackArrDVArrs ? BDict : BDArr;
2604 if (!topC(env).couldBe(requiredTSType)) {
2605 popC(env);
2606 popC(env);
2607 return unreachable(env);
2609 auto const a = tv(topC(env));
2610 if (!a || !isValidTSType(*a, false)) {
2611 popC(env);
2612 popC(env);
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)) {
2625 popC(env);
2626 popC(env);
2627 return unreachable(env);
2629 auto const a = tv(topC(env));
2630 if (!a || !isValidTSType(*a, false)) {
2631 popC(env);
2632 push(env, popC(env));
2633 return;
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);
2645 auto valid = true;
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);
2652 nothrow(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);
2659 auto valid = true;
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);
2667 nothrow(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);
2674 auto valid = true;
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);
2681 nothrow(env);
2682 return push(env, rname(op.str2));
2685 void in(ISS& env, const bc::CheckReifiedGenericMismatch& op) {
2686 // TODO(T31677864): implement real optimizations
2687 popC(env);
2690 namespace {
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,
2700 const Op& op) {
2701 nothrow(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)) {
2709 return folly::none;
2710 } else {
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
2719 // the types here.
2720 if (peekLocRaw(env, op.loc1) == topC(env)) {
2721 return folly::none;
2724 } else if (equivLoc == NoLocalId) {
2725 equivLoc = op.loc1;
2727 if (any(env.collect.opts & CollectionOpts::Inlining)) {
2728 effect_free(env);
2730 } else {
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);
2761 } else {
2762 reduce(env, bc::Nop {});
2766 void in(ISS& env, const bc::SetG&) {
2767 auto t1 = popC(env);
2768 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);
2782 } else {
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);
2797 if (v1 && locVal) {
2798 // Can't constprop at this eval_cell, because of the effects on
2799 // locals.
2800 auto resultTy = eval_cell([&] {
2801 Cell c = *locVal;
2802 Cell rhs = *v1;
2803 setopBody(&c, op.subop2, &rhs);
2804 return c;
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);
2819 return;
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) {
2833 popC(env);
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);
2842 } else {
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)) {
2862 nothrow(env);
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);
2881 } else {
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);
2900 nothrow(env);
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);
2908 popC(env);
2909 push(env, std::move(t1));
2912 void in(ISS& env, const bc::BindS& op) {
2913 popV(env);
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);
2922 } else {
2923 killSelfProps(env);
2927 env.collect.publicSPropMutations.merge(
2928 env.index, env.ctx, tcls, tname, TRef
2931 push(env, TRef);
2934 void in(ISS& env, const bc::UnsetL& op) {
2935 if (locRaw(env, op.loc1).subtypeOf(TUninit)) {
2936 return reduce(env, bc::Nop {});
2938 nothrow(env);
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)) {
2952 fpiPushNoFold(
2953 env,
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 },
2961 op.arg1, false)) {
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 });
2986 popC(env);
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 });
2993 } else {
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);
3013 fpiPushNoFold(
3014 env,
3015 ActRec {
3016 FPIKind::Func,
3017 TBottom,
3018 folly::none,
3019 rfuncPair.first,
3020 rfuncPair.second
3025 void in(ISS& env, const bc::ResolveFunc& op) {
3026 // TODO (T29639296)
3027 push(env, TFunc);
3030 void in(ISS& env, const bc::ResolveObjMethod& op) {
3031 // TODO (T29639296)
3032 popC(env);
3033 popC(env);
3034 if (RuntimeOption::EvalHackArrDVArrs) {
3035 push(env, TVec);
3036 } else {
3037 push(env, TVArr);
3041 void in(ISS& env, const bc::ResolveClsMethod& op) {
3042 popC(env);
3043 popC(env);
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 });
3059 popC(env);
3060 return unreachable(env);
3063 if (!mayCallMethod && !mayThrowNonObj) {
3064 // Null input, this may only call the nullsafe helper, so do that.
3065 return reduce(
3066 env,
3067 bc::PopC {},
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)
3079 : folly::none;
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);
3104 return t;
3105 })) {
3106 unreachable(env);
3110 popC(env);
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()) {
3123 return reduce(
3124 env,
3125 bc::PopC {},
3126 bc::FPushObjMethodD {
3127 op.arg1, name, op.subop2, op.has_unpack
3132 popC(env);
3133 fpiPushNoFold(
3134 env,
3135 ActRec {
3136 FPIKind::ObjMeth,
3137 popC(env),
3138 is_specialized_cls(clsTy)
3139 ? folly::Optional<res::Class>(dcls_of(clsTy).cls)
3140 : folly::none,
3141 rfunc
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(
3150 env.ctx,
3151 clsType,
3152 op.str2
3154 if (fpiPush(env, ActRec { FPIKind::ClsMeth, clsType, rcls, rfun }, op.arg1,
3155 false)) {
3156 return reduce(env, bc::Nop {});
3160 namespace {
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> {
3170 switch (ref) {
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);
3176 }();
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);
3189 rcls = dcls.cls;
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()) {
3198 return reduce(
3199 env,
3200 bc::DiscardClsRef { op.slot },
3201 bc::PopC {},
3202 bc::FPushClsMethodD {
3203 op.arg1, name, rcls->name(), op.has_unpack
3208 if (fpiPush(env, ActRec { FPIKind::ClsMeth, t1, rcls, rfunc }, op.arg1,
3209 true)) {
3210 return reduce(env,
3211 bc::DiscardClsRef { op.slot },
3212 bc::PopC {});
3214 takeClsRefSlot(env, op.slot);
3215 popC(env);
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()) {
3227 return reduce(
3228 env,
3229 bc::PopC {},
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}
3238 : folly::none;
3239 if (fpiPush(env, ActRec {
3240 FPIKind::ClsMeth,
3241 ctxCls(env),
3242 rcls,
3243 rfunc
3244 }, op.arg1, true)) {
3245 return reduce(env, bc::PopC {});
3247 popC(env);
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);
3257 rcls = dcls.cls;
3258 exactCls = dcls.type == DCls::Exact;
3261 if (op.subop2 == SpecialClsRef::Static && rcls && exactCls) {
3262 return reduce(
3263 env,
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 {
3272 FPIKind::ClsMeth,
3273 ctxCls(env),
3274 rcls,
3275 rfun
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);
3283 if (!rcls) {
3284 push(env, TObj);
3285 return;
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)) {
3304 push(env, TObj);
3305 return;
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);
3322 push(env, TObj);
3323 return;
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()) {
3330 return reduce(
3331 env,
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.
3359 return TBottom;
3362 // Throw away non-obj component.
3363 t &= TObj;
3365 // If we aren't even sure this is a wait handle, there's nothing we can
3366 // infer here.
3367 if (!is_specialized_wait_handle(t)) {
3368 return TInitCell;
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.
3377 unreachable(env);
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);
3387 } else {
3388 for (int32_t i = 0; i < fca.numRets; i++) push(env, TInitCell);
3390 return;
3392 if (fca.asyncEagerTarget != NoBlockId) {
3393 push(env, typeFromWH(ty));
3394 assertx(topC(env) != TBottom);
3395 env.propagate(fca.asyncEagerTarget, &env.state);
3396 popC(env);
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) {
3410 auto ty = [&] () {
3411 auto const func = ar.func->exactFunc();
3412 assertx(func);
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);
3422 if (!isScalar &&
3423 (env.index.func_depends_on_arg(func, argNum) ||
3424 !arg.subtypeOf(BInitCell))) {
3425 return TBottom;
3427 args[argNum] = isScalar ? scalarize(arg) : arg;
3430 return env.index.lookup_foldable_return_type(
3431 env.ctx, func, ar.context, std::move(args));
3432 }();
3433 if (auto v = tv(ty)) {
3434 BytecodeVec repl { fca.numArgs, bc::PopC {} };
3435 repl.push_back(gen_constant(*v));
3436 fpiPop(env);
3437 reduce(env, std::move(repl));
3438 return folly::none;
3441 fpiNotFoldable(env);
3442 fpiPop(env);
3443 discard(env, fca.numArgs + (fca.hasUnpack() ? 1 : 0));
3444 for (auto i = uint32_t{0}; i < fca.numRets; ++i) push(env, TBottom);
3445 return folly::none;
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) {
3462 return ty;
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,
3467 *ar.fallbackFunc);
3468 return union_of(std::move(ty), std::move(ty2));
3469 }();
3471 if (fca.asyncEagerTarget != NoBlockId && typeFromWH(returnType) == TBottom) {
3472 // Kill the async eager target if the function never returns.
3473 auto newFCA = fca;
3474 newFCA.asyncEagerTarget = NoBlockId;
3475 newFCA.flags = static_cast<FCallArgs::Flags>(
3476 newFCA.flags & ~FCallArgs::SupportsAsyncEagerReturn);
3477 return newFCA;
3480 fpiPop(env);
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 &&
3488 fca.numArgs == 1 &&
3489 !fca.hasUnpack() &&
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);
3504 return folly::none;
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()) {
3512 bool match = true;
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);
3517 fpiPop(env);
3518 discard(env, fca.numArgs + (fca.hasUnpack() ? 1 : 0));
3519 for (auto j = uint32_t{0}; j < fca.numRets; ++j) push(env, TBottom);
3520 return;
3523 if (kind == PrepKind::Unknown) {
3524 match = false;
3525 break;
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)));
3534 return reduce(
3535 env,
3536 bc::NewObjD { exCls },
3537 bc::Dup {},
3538 bc::FPushCtor { 1, false },
3539 bc::String { err },
3540 bc::FCall {
3541 FCallArgs(1), staticEmptyString(), staticEmptyString() },
3542 bc::PopC {},
3543 bc::Throw {}
3548 if (match) {
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),
3553 op.str2, op.str3
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);
3562 if (status) {
3563 auto newFCA = fca;
3564 if (*status) {
3565 // Callee supports async eager return.
3566 newFCA.flags = static_cast<FCallArgs::Flags>(
3567 newFCA.flags | FCallArgs::SupportsAsyncEagerReturn);
3568 } else {
3569 // Callee doesn't support async eager return.
3570 newFCA.asyncEagerTarget = NoBlockId;
3572 return reduce(env, bc::FCall { newFCA, op.str2, op.str3 });
3576 switch (ar.kind) {
3577 case FPIKind::Unknown:
3578 case FPIKind::CallableArr:
3579 case FPIKind::ObjInvoke:
3580 not_reached();
3581 case FPIKind::Func:
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 });
3591 return;
3592 case FPIKind::Builtin:
3593 assertx(fca.numRets == 1);
3594 return finish_builtin(
3595 env, ar.func->exactFunc(), fca.numArgs, fca.hasUnpack());
3596 case FPIKind::Ctor:
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())) {
3605 break;
3607 // fallthrough
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() });
3617 // fallthrough
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 });
3624 return;
3628 if (fca.hasUnpack()) popC(env);
3629 for (auto i = uint32_t{0}; i < fca.numArgs; ++i) popCV(env);
3630 fpiPop(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);
3636 popC(env);
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);
3642 namespace {
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
3654 // below.
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
3662 // equivalency.
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);
3671 break;
3672 case IterTypes::Count::Single:
3673 case IterTypes::Count::NonEmpty:
3674 fallthrough();
3675 jmp_nevertaken(env);
3676 break;
3677 case IterTypes::Count::ZeroOrOne:
3678 case IterTypes::Count::Any:
3679 taken();
3680 fallthrough();
3681 break;
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
3700 // equivalency.
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);
3712 break;
3713 case IterTypes::Count::Single:
3714 case IterTypes::Count::NonEmpty:
3715 fallthrough();
3716 jmp_nevertaken(env);
3717 break;
3718 case IterTypes::Count::ZeroOrOne:
3719 case IterTypes::Count::Any:
3720 taken();
3721 fallthrough();
3722 break;
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],
3731 [&] (DeadIter) {
3732 always_assert(false && "IterNext on dead iter");
3733 return false;
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:
3741 return true;
3742 case IterTypes::Count::NonEmpty:
3743 case IterTypes::Count::Any:
3744 setLoc(env, valueLoc, ti.types.value);
3745 return false;
3746 case IterTypes::Count::Empty:
3747 always_assert(false);
3749 not_reached();
3752 if (noTaken) {
3753 jmp_nevertaken(env);
3754 freeIter(env, iter);
3755 return;
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],
3771 [&] (DeadIter) {
3772 always_assert(false && "IterNextK on dead iter");
3773 return false;
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:
3781 return true;
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);
3787 return false;
3788 case IterTypes::Count::Empty:
3789 always_assert(false);
3791 not_reached();
3794 if (noTaken) {
3795 jmp_nevertaken(env);
3796 freeIter(env, iter);
3797 return;
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) {
3816 iterInitImpl(
3817 env,
3818 op.iter1,
3819 op.loc4,
3820 op.target3,
3821 locAsCell(env, op.loc2),
3822 op.loc2
3826 void in(ISS& env, const bc::IterInitK& op) {
3827 auto const baseLoc = topStkLocal(env);
3828 auto base = popC(env);
3829 iterInitKImpl(
3830 env,
3831 op.iter1,
3832 op.loc3,
3833 op.loc4,
3834 op.target2,
3835 std::move(base),
3836 baseLoc
3840 void in(ISS& env, const bc::LIterInitK& op) {
3841 iterInitKImpl(
3842 env,
3843 op.iter1,
3844 op.loc4,
3845 op.loc5,
3846 op.target3,
3847 locAsCell(env, op.loc2),
3848 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.
3872 nothrow(env);
3874 match<void>(
3875 env.state.iters[op.iter1],
3876 [] (DeadIter) {},
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) {
3885 nothrow(env);
3886 mayReadLocal(env, op.loc2);
3887 freeIter(env, op.iter1);
3890 void in(ISS& env, const bc::IterBreak& op) {
3891 nothrow(env);
3893 for (auto const& it : op.iterTab) {
3894 if (it.kind == KindOfIter || it.kind == KindOfLIter) {
3895 match<void>(
3896 env.state.iters[it.id],
3897 [] (DeadIter) {},
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) {
3914 popC(env);
3915 killLocals(env);
3916 killThisProps(env);
3917 killSelfProps(env);
3918 mayUseVV(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&) {
3932 popC(env);
3933 push(env, TBool);
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);
3943 if (!res.second) {
3944 if (res.first->second.m_type == kReadOnlyConstant) {
3945 // we only saw a read of this constant
3946 res.first->second = val;
3947 } else {
3948 // more than one definition in this function
3949 res.first->second.m_type = kDynamicConstant;
3953 push(env, TBool);
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:
3990 break;
3991 case BareThisOp::NoNotice:
3992 effect_free(env);
3993 break;
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) {
4009 mayUseVV(env);
4010 push(env, TInt);
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 {});
4017 popC(env);
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);
4029 return reduce(env,
4030 gen_constant(*v),
4031 bc::SetL { op.loc1 }, bc::PopC {},
4032 bc::True {});
4035 setLocRaw(env, l, TGen);
4036 maybeBindLocalStatic(env, l);
4037 push(env, TBool);
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 {});
4044 popC(env);
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);
4053 push(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;
4068 not_reached();
4069 }();
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()) {
4081 constprop(env);
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;
4091 } ());
4094 namespace {
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.
4103 return true;
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),
4122 constraint)) {
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
4140 * on.
4142 if (RuntimeOption::EvalThisTypeHintLevel != 3 && constraint.isThis()) {
4143 return;
4145 if (constraint.hasConstraint() && !constraint.isTypeVar() &&
4146 !constraint.isTypeConstant()) {
4147 auto t =
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)) {
4162 unreachable(env);
4163 popC(env);
4164 return;
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 });
4173 popC(env);
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)) {
4185 if (ts_flavor) {
4186 // this is handled differently in ts flavor
4187 popC(env);
4188 return;
4190 reduce(env, bc::Nop {});
4191 return;
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);
4242 return;
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)) {
4251 if (reduce_this) {
4252 if (ts_flavor) {
4253 reduce(env, bc::PopC {}, bc::VerifyRetNonNullC {});
4254 return;
4256 reduce(env, bc::VerifyRetNonNullC {});
4257 return;
4261 auto retT = intersection_of(std::move(tcT), std::move(stackT));
4262 if (retT.subtypeOf(BBottom)) {
4263 unreachable(env);
4264 if (ts_flavor) popC(env); // the type structure
4265 return;
4268 if (ts_flavor) popC(env); // the type structure
4269 popC(env);
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,
4275 false, false);
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)) {
4286 unreachable(env);
4287 popC(env);
4288 return;
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())) {
4304 return;
4307 auto stackT = topC(env);
4309 if (!stackT.couldBe(BInitNull)) {
4310 reduce(env, bc::Nop {});
4311 return;
4314 if (stackT.subtypeOf(BNull)) return unreachable(env);
4316 auto const equiv = topStkEquiv(env);
4318 if (is_opt(stackT)) stackT = unopt(std::move(stackT));
4320 popC(env);
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.
4345 if (nargs) {
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,
4352 clsPair.second,
4353 std::move(usedVars)
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&) {
4374 popC(env);
4375 push(env, TInitCell);
4378 void in(ISS& env, const bc::YieldK&) {
4379 popC(env);
4380 popC(env);
4381 push(env, TInitCell);
4384 void in(ISS& env, const bc::ContAssignDelegate&) {
4385 popC(env);
4388 void in(ISS& env, const bc::ContEnterDelegate&) {
4389 popC(env);
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) {
4422 return reduce(
4423 env,
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);
4435 namespace {
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.
4445 constprop(env);
4446 effect_free(env);
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)) {
4458 constprop(env);
4459 effect_free(env);
4461 if (!arraysOnly && baseMaybeNull) return push(env, union_of(t, def));
4462 if (t.subtypeOf(BBottom)) unreachable(env);
4463 return push(env, t);
4466 if (arraysOnly) {
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);
4472 } else if (
4473 !base.couldBe(BArr | BVec | BDict | BKeyset | BStr | BObj | BClsMeth)) {
4474 // Otherwise, any strange bases for Idx will just return the default value
4475 // without raising.
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)) {
4528 maybeNull = true;
4529 if (is_nullish(key)) return unnullish(key);
4531 return key;
4532 }();
4534 auto elem = array_elem(base, fixedKey, def);
4535 // If the key was null, Idx will return the default value, so add to the
4536 // return type.
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
4553 // ever raising).
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 {});
4570 nothrow(env);
4571 push(env, TBool);
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
4582 break;
4583 case InitPropOp::NonStatic:
4584 mergeThisProp(env, op.str1, t);
4585 break;
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;
4595 } else {
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);
4603 if (!v) break;
4604 prop.val = *v;
4605 return reduce(env, bc::PopC {});
4609 popC(env);
4612 void in(ISS& env, const bc::Silence& op) {
4613 nothrow(env);
4614 switch (op.subop2) {
4615 case SilenceOp::Start:
4616 setLoc(env, op.loc1, TInt);
4617 break;
4618 case SilenceOp::End:
4619 break;
4623 namespace {
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 }));
4635 return true;
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);
4647 if (allArrKey &&
4648 (!env.ctx.func->cls ||
4649 (env.ctx.func->attrs & AttrStatic) ||
4650 thisAvailable(env))) {
4651 nothrow(env);
4654 if (retTy.first == TBottom) {
4655 jmp_setdest(env, op.target1);
4656 return true;
4659 env.propagate(op.target1, &env.state);
4660 push(env, std::move(retTy.first));
4661 return false;
4666 void in(ISS& env, const bc::MemoGet& op) {
4667 memoGetImpl(
4668 env, 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(
4677 env, op,
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);
4686 push(
4687 env,
4688 is_specialized_wait_handle(t) ? wait_handle_inner(t) : TInitCell
4692 namespace {
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) {
4703 return reduce(
4704 env,
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);
4715 if (allArrKey &&
4716 (!env.ctx.func->cls ||
4717 (env.ctx.func->attrs & AttrStatic) ||
4718 thisAvailable(env))) {
4719 nothrow(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 }
4742 #undef O
4743 not_reached();
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 += ';';
4756 return ret;
4757 }());
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;
4775 #define X(y) \
4776 case Op::y: \
4777 switch (o2) { \
4778 case Op::Not: \
4779 switch (o3) { \
4780 case Op::JmpZ: \
4781 return group(env, it, it[0].y, it[1].Not, it[2].JmpZ); \
4782 case Op::JmpNZ: \
4783 return group(env, it, it[0].y, it[1].Not, it[2].JmpNZ); \
4784 default: break; \
4786 break; \
4787 case Op::JmpZ: \
4788 return group(env, it, it[0].y, it[1].JmpZ); \
4789 case Op::JmpNZ: \
4790 return group(env, it, it[0].y, it[1].JmpNZ); \
4791 default: break; \
4793 break;
4795 switch (o1) {
4796 X(InstanceOfD)
4797 X(IsTypeStructC)
4798 X(IsTypeL)
4799 X(IsTypeC)
4800 X(StaticLocCheck)
4801 X(Same)
4802 X(NSame)
4803 default: break;
4805 #undef X
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
4820 // it's a PEI).
4821 auto const stateBefore = interp.blk->throwExits.empty()
4822 ? State{}
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];
4833 auto i = size_t{0};
4834 while (i < numPushed) {
4835 if (i < numCells) {
4836 auto const v = tv(elems->type);
4837 if (!v) return false;
4838 cells[i] = *v;
4839 } else if (!is_scalar(elems->type)) {
4840 return false;
4842 ++i;
4843 --elems;
4845 while (++elems, i--) {
4846 elems->type = from_cell(i < numCells ?
4847 cells[i] : *tv(elems->type));
4849 return true;
4852 if (options.ConstantProp && flags.canConstProp && fix_const_outputs()) {
4853 if (flags.wasPEI) {
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);
4864 if (flags.wasPEI) {
4865 FTRACE(2, " PEI.\n");
4866 for (auto exit : interp.blk->throwExits) {
4867 propagate(exit, &stateBefore);
4870 return flags;
4873 namespace {
4875 BlockId speculate(Interp& interp) {
4876 auto low_water = interp.state.stack.size();
4878 interp.collect.opts = interp.collect.opts | CollectionOpts::Speculating;
4879 SCOPE_EXIT {
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");
4898 return NoBlockId;
4901 if (flags.usedLocalStatics) {
4902 FTRACE(3, " Bailing from speculate because local statics were used\n");
4903 return NoBlockId;
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;
4915 return NoBlockId;
4918 BlockId speculateHelper(Interp& interpIn, BlockId target, bool unconditional) {
4919 if (target == NoBlockId || !options.RemoveDeadBlocks) return target;
4921 int pops = 0;
4922 auto const fallthrough = target == interpIn.blk->fallthrough;
4924 folly::Optional<State> state;
4925 while (true) {
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) {
4930 case Op::JmpZ:
4931 case Op::JmpNZ:
4932 case Op::SSwitch:
4933 case Op::Switch:
4934 break;
4935 default:
4936 return target;
4939 if (!state) state.emplace(interpIn.state);
4941 Interp interp {
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()) {
4951 return target;
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;
4961 return target;
4966 //////////////////////////////////////////////////////////////////////
4968 RunFlags run(Interp& interp, PropagateFn propagate) {
4969 SCOPE_EXIT {
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");
4989 return ret;
4993 if (flags.usedLocalStatics) {
4994 if (!ret.usedLocalStatics) {
4995 ret.usedLocalStatics = std::move(flags.usedLocalStatics);
4996 } else {
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");
5005 return ret;
5008 if (flags.jmpDest != NoBlockId) {
5009 if (flags.jmpDest != interp.blk->fallthrough) {
5010 FTRACE(2, " <took branch; no fallthrough>\n");
5011 } else {
5012 FTRACE(2, " <branch never taken>\n");
5014 propagate(speculateHelper(interp, flags.jmpDest, true), &interp.state);
5015 return ret;
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;
5028 return ret;
5032 FTRACE(2, " <end block>\n");
5033 if (interp.blk->fallthrough != NoBlockId) {
5034 propagate(speculateHelper(interp, interp.blk->fallthrough, false), &interp.state);
5036 return ret;
5039 StepFlags step(Interp& interp, const Bytecode& op) {
5040 auto flags = StepFlags{};
5041 auto noop = [] (BlockId, const State*) {};
5042 ISS env { interp, flags, noop };
5043 dispatch(env, op);
5044 return flags;
5047 void default_dispatch(ISS& env, const Bytecode& op) {
5048 dispatch(env, op);
5051 folly::Optional<Type> thisType(const Index& index, Context ctx) {
5052 return thisTypeFromContext(index, ctx);
5055 //////////////////////////////////////////////////////////////////////