Better type resolution
[hiphop-php.git] / hphp / hhbbc / interp.cpp
blobb4f4cb4803285bac108f5dc22f415759ee1e1797
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/vm/runtime.h"
33 #include "hphp/runtime/vm/unit-util.h"
35 #include "hphp/runtime/ext/hh/ext_hh.h"
37 #include "hphp/hhbbc/analyze.h"
38 #include "hphp/hhbbc/bc.h"
39 #include "hphp/hhbbc/cfg.h"
40 #include "hphp/hhbbc/class-util.h"
41 #include "hphp/hhbbc/eval-cell.h"
42 #include "hphp/hhbbc/index.h"
43 #include "hphp/hhbbc/interp-state.h"
44 #include "hphp/hhbbc/optimize.h"
45 #include "hphp/hhbbc/representation.h"
46 #include "hphp/hhbbc/type-builtins.h"
47 #include "hphp/hhbbc/type-ops.h"
48 #include "hphp/hhbbc/type-system.h"
49 #include "hphp/hhbbc/unit-util.h"
51 #include "hphp/hhbbc/interp-internal.h"
53 namespace HPHP { namespace HHBBC {
55 //////////////////////////////////////////////////////////////////////
57 namespace {
59 const StaticString s_Throwable("__SystemLib\\Throwable");
60 const StaticString s_empty("");
61 const StaticString s_construct("__construct");
62 const StaticString s_86ctor("86ctor");
63 const StaticString s_PHP_Incomplete_Class("__PHP_Incomplete_Class");
64 const StaticString s_IMemoizeParam("HH\\IMemoizeParam");
65 const StaticString s_getInstanceKey("getInstanceKey");
66 const StaticString s_Closure("Closure");
67 const StaticString s_byRefWarn("Only variables should be passed by reference");
68 const StaticString s_byRefError("Only variables can be passed by reference");
69 const StaticString s_trigger_error("trigger_error");
72 //////////////////////////////////////////////////////////////////////
74 void impl_vec(ISS& env, bool reduce, std::vector<Bytecode>&& bcs) {
75 std::vector<Bytecode> currentReduction;
76 if (!options.StrengthReduce) reduce = false;
78 for (auto it = begin(bcs); it != end(bcs); ++it) {
79 assert(env.flags.jmpFlag == StepFlags::JmpFlags::Either &&
80 "you can't use impl with branching opcodes before last position");
82 auto const wasPEI = env.flags.wasPEI;
83 auto const canConstProp = env.flags.canConstProp;
85 FTRACE(3, " (impl {}\n", show(env.ctx.func, *it));
86 env.flags.wasPEI = true;
87 env.flags.canConstProp = false;
88 env.flags.strengthReduced = folly::none;
89 default_dispatch(env, *it);
91 if (env.flags.strengthReduced) {
92 if (instrFlags(env.flags.strengthReduced->back().op) & TF) {
93 unreachable(env);
95 if (reduce) {
96 std::move(begin(*env.flags.strengthReduced),
97 end(*env.flags.strengthReduced),
98 std::back_inserter(currentReduction));
100 } else {
101 if (instrFlags(it->op) & TF) {
102 unreachable(env);
104 if (reduce) {
105 if (env.flags.canConstProp &&
106 env.collect.propagate_constants &&
107 env.collect.propagate_constants(*it, env.state, currentReduction)) {
108 env.flags.canConstProp = false;
109 env.flags.wasPEI = false;
110 } else {
111 currentReduction.push_back(std::move(*it));
116 // If any of the opcodes in the impl list said they could throw,
117 // then the whole thing could throw.
118 env.flags.wasPEI = env.flags.wasPEI || wasPEI;
119 env.flags.canConstProp = env.flags.canConstProp && canConstProp;
120 if (env.state.unreachable) break;
123 if (reduce) {
124 env.flags.strengthReduced = std::move(currentReduction);
125 } else {
126 env.flags.strengthReduced = folly::none;
130 namespace interp_step {
132 void in(ISS& env, const bc::Nop&) { nothrow(env); }
133 void in(ISS& env, const bc::DiscardClsRef& op) {
134 nothrow(env);
135 takeClsRefSlot(env, op.slot);
137 void in(ISS& env, const bc::PopC&) { nothrow(env); popC(env); }
138 void in(ISS& env, const bc::PopV&) { nothrow(env); popV(env); }
139 void in(ISS& env, const bc::PopU&) { nothrow(env); popU(env); }
140 void in(ISS& env, const bc::PopR&) {
141 auto t = topT(env, 0);
142 if (t.subtypeOf(TCell)) {
143 return reduce(env, bc::UnboxRNop {}, bc::PopC {});
145 nothrow(env);
146 popR(env);
149 void in(ISS& env, const bc::EntryNop&) { nothrow(env); }
151 void in(ISS& env, const bc::Dup& op) {
152 nothrow(env);
153 auto val = popC(env);
154 push(env, val);
155 push(env, std::move(val));
158 void in(ISS& env, const bc::AssertRATL& op) {
159 mayReadLocal(env, op.loc1);
160 nothrow(env);
163 void in(ISS& env, const bc::AssertRATStk&) {
164 nothrow(env);
167 void in(ISS& env, const bc::BreakTraceHint&) { nothrow(env); }
169 void in(ISS& env, const bc::Box&) {
170 nothrow(env);
171 popC(env);
172 push(env, TRef);
175 void in(ISS& env, const bc::BoxR&) {
176 nothrow(env);
177 if (topR(env).subtypeOf(TRef)) {
178 return reduce(env, bc::BoxRNop {});
180 popR(env);
181 push(env, TRef);
184 void in(ISS& env, const bc::Unbox&) {
185 nothrow(env);
186 popV(env);
187 push(env, TInitCell);
190 void in(ISS& env, const bc::UnboxR&) {
191 auto const t = topR(env);
192 if (t.subtypeOf(TInitCell)) return reduce(env, bc::UnboxRNop {});
193 nothrow(env);
194 popT(env);
195 push(env, TInitCell);
198 void in(ISS& env, const bc::RGetCNop&) { nothrow(env); }
200 void in(ISS& env, const bc::CGetCUNop&) {
201 nothrow(env);
202 auto const t = popCU(env);
203 push(env, remove_uninit(t));
206 void in(ISS& env, const bc::UGetCUNop&) {
207 nothrow(env);
208 popCU(env);
209 push(env, TUninit);
212 void in(ISS& env, const bc::UnboxRNop&) {
213 nothrow(env);
214 constprop(env);
215 auto t = popR(env);
216 if (!t.subtypeOf(TInitCell)) t = TInitCell;
217 push(env, std::move(t));
220 void in(ISS& env, const bc::BoxRNop&) {
221 nothrow(env);
222 auto t = popR(env);
223 if (!t.subtypeOf(TRef)) t = TRef;
224 push(env, std::move(t));
227 void in(ISS& env, const bc::Null&) { nothrow(env); push(env, TInitNull); }
228 void in(ISS& env, const bc::NullUninit&){ nothrow(env); push(env, TUninit); }
229 void in(ISS& env, const bc::True&) { nothrow(env); push(env, TTrue); }
230 void in(ISS& env, const bc::False&) { nothrow(env); push(env, TFalse); }
232 void in(ISS& env, const bc::Int& op) {
233 nothrow(env);
234 push(env, ival(op.arg1));
237 void in(ISS& env, const bc::Double& op) {
238 nothrow(env);
239 push(env, dval(op.dbl1));
242 void in(ISS& env, const bc::String& op) {
243 nothrow(env);
244 push(env, sval(op.str1));
247 void in(ISS& env, const bc::Array& op) {
248 assert(op.arr1->isPHPArray());
249 nothrow(env);
250 push(env, aval(op.arr1));
253 void in(ISS& env, const bc::Vec& op) {
254 assert(op.arr1->isVecArray());
255 nothrow(env);
256 push(env, vec_val(op.arr1));
259 void in(ISS& env, const bc::Dict& op) {
260 assert(op.arr1->isDict());
261 nothrow(env);
262 push(env, dict_val(op.arr1));
265 void in(ISS& env, const bc::Keyset& op) {
266 assert(op.arr1->isKeyset());
267 nothrow(env);
268 push(env, keyset_val(op.arr1));
271 void in(ISS& env, const bc::NewArray& op) {
272 push(env, op.arg1 == 0 ? aempty() : counted_aempty());
275 void in(ISS& env, const bc::NewDictArray& op) {
276 push(env, op.arg1 == 0 ? dict_empty() : counted_dict_empty());
279 void in(ISS& env, const bc::NewMixedArray& op) {
280 push(env, op.arg1 == 0 ? aempty() : counted_aempty());
283 void in(ISS& env, const bc::NewPackedArray& op) {
284 auto elems = std::vector<Type>{};
285 elems.reserve(op.arg1);
286 for (auto i = uint32_t{0}; i < op.arg1; ++i) {
287 elems.push_back(std::move(topC(env, op.arg1 - i - 1)));
289 discard(env, op.arg1);
290 push(env, arr_packed(std::move(elems)));
291 constprop(env);
294 void in(ISS& env, const bc::NewStructArray& op) {
295 auto map = MapElems{};
296 for (auto it = op.keys.end(); it != op.keys.begin(); ) {
297 map.emplace_front(make_tv<KindOfPersistentString>(*--it), popC(env));
299 push(env, arr_map(std::move(map)));
300 constprop(env);
303 void in(ISS& env, const bc::NewVecArray& op) {
304 auto elems = std::vector<Type>{};
305 elems.reserve(op.arg1);
306 for (auto i = uint32_t{0}; i < op.arg1; ++i) {
307 elems.push_back(std::move(topC(env, op.arg1 - i - 1)));
309 discard(env, op.arg1);
310 constprop(env);
311 push(env, vec(std::move(elems)));
314 void in(ISS& env, const bc::NewKeysetArray& op) {
315 assert(op.arg1 > 0);
316 auto map = MapElems{};
317 auto ty = TBottom;
318 auto useMap = true;
319 for (auto i = uint32_t{0}; i < op.arg1; ++i) {
320 auto t = popC(env);
321 if (useMap) {
322 auto const k = disect_strict_key(t);
323 if (auto const v = k.tv()) {
324 map.emplace_front(*v, k.type);
325 } else {
326 useMap = false;
329 ty |= std::move(t);
331 if (useMap) {
332 push(env, keyset_map(std::move(map)));
333 constprop(env);
334 } else {
335 push(env, keyset_n(ty));
339 void in(ISS& env, const bc::NewLikeArrayL& op) {
340 locAsCell(env, op.loc1);
341 push(env, counted_aempty());
344 void in(ISS& env, const bc::AddElemC& op) {
345 auto const v = popC(env);
346 auto const k = popC(env);
348 auto const outTy = [&] (Type ty) -> folly::Optional<Type> {
349 if (ty.subtypeOf(TArr)) {
350 return array_set(std::move(ty), k, v);
352 if (ty.subtypeOf(TDict)) {
353 return dict_set(std::move(ty), k, v).first;
355 return folly::none;
356 }(popC(env));
358 if (!outTy) {
359 return push(env, union_of(TArr, TDict));
362 if (outTy->subtypeOf(TBottom)) {
363 unreachable(env);
364 } else {
365 if (env.collect.trackConstantArrays) constprop(env);
367 push(env, std::move(*outTy));
370 void in(ISS& env, const bc::AddElemV& op) {
371 popV(env); popC(env);
372 auto const ty = popC(env);
373 auto const outTy =
374 ty.subtypeOf(TArr) ? TArr
375 : ty.subtypeOf(TDict) ? TDict
376 : union_of(TArr, TDict);
377 push(env, outTy);
380 void in(ISS& env, const bc::AddNewElemC&) {
381 auto v = popC(env);
383 auto const outTy = [&] (Type ty) -> folly::Optional<Type> {
384 if (ty.subtypeOf(TArr)) {
385 return array_newelem(std::move(ty), std::move(v));
387 return folly::none;
388 }(popC(env));
390 if (!outTy) {
391 return push(env, TInitCell);
394 if (outTy->subtypeOf(TBottom)) {
395 unreachable(env);
396 } else {
397 if (env.collect.trackConstantArrays) constprop(env);
399 push(env, std::move(*outTy));
402 void in(ISS& env, const bc::AddNewElemV&) {
403 popV(env);
404 popC(env);
405 push(env, TArr);
408 void in(ISS& env, const bc::NewCol& op) {
409 auto const type = static_cast<CollectionType>(op.arg1);
410 auto const name = collections::typeToString(type);
411 push(env, objExact(env.index.builtin_class(name)));
414 void in(ISS& env, const bc::NewPair& op) {
415 popC(env); popC(env);
416 auto const name = collections::typeToString(CollectionType::Pair);
417 push(env, objExact(env.index.builtin_class(name)));
420 void in(ISS& env, const bc::ColFromArray& op) {
421 popC(env);
422 auto const type = static_cast<CollectionType>(op.arg1);
423 auto const name = collections::typeToString(type);
424 push(env, objExact(env.index.builtin_class(name)));
427 void doCns(ISS& env, SString str) {
428 auto t = env.index.lookup_constant(env.ctx, str);
429 if (!t) {
430 // There's no entry for this constant in the index. It must be
431 // the first iteration, so we'll add a dummy entry to make sure
432 // there /is/ something next time around.
433 Cell val;
434 val.m_type = kReadOnlyConstant;
435 env.collect.cnsMap.emplace(str, val);
436 t = TInitCell;
437 // make sure we're re-analyzed
438 env.collect.readsUntrackedConstants = true;
439 } else if (t->strictSubtypeOf(TInitCell)) {
440 nothrow(env);
441 constprop(env);
443 push(env, std::move(*t));
446 void in(ISS& env, const bc::Cns& op) { doCns(env, op.str1); }
447 void in(ISS& env, const bc::CnsE& op) { doCns(env, op.str1); }
448 void in(ISS& env, const bc::CnsU&) { push(env, TInitCell); }
450 void in(ISS& env, const bc::ClsCns& op) {
451 auto const& t1 = peekClsRefSlot(env, op.slot);
452 if (is_specialized_cls(t1)) {
453 auto const dcls = dcls_of(t1);
454 if (dcls.type == DCls::Exact) {
455 return reduce(env, bc::DiscardClsRef { op.slot },
456 bc::ClsCnsD { op.str1, dcls.cls.name() });
459 takeClsRefSlot(env, op.slot);
460 push(env, TInitCell);
463 void in(ISS& env, const bc::ClsCnsD& op) {
464 if (auto const rcls = env.index.resolve_class(env.ctx, op.str2)) {
465 auto t = env.index.lookup_class_constant(env.ctx, *rcls, op.str1);
466 if (options.HardConstProp) constprop(env);
467 push(env, std::move(t));
468 return;
470 push(env, TInitCell);
473 void in(ISS& env, const bc::File&) { nothrow(env); push(env, TSStr); }
474 void in(ISS& env, const bc::Dir&) { nothrow(env); push(env, TSStr); }
475 void in(ISS& env, const bc::Method&) { nothrow(env); push(env, TSStr); }
477 void in(ISS& env, const bc::ClsRefName& op) {
478 nothrow(env);
479 takeClsRefSlot(env, op.slot);
480 push(env, TSStr);
483 void in(ISS& env, const bc::Concat& op) {
484 auto const t1 = popC(env);
485 auto const t2 = popC(env);
486 auto const v1 = tv(t1);
487 auto const v2 = tv(t2);
488 if (v1 && v2) {
489 auto to_string_is_safe = [] (const Cell& cell) {
490 return
491 isStringType(cell.m_type) ||
492 cell.m_type == KindOfNull ||
493 cell.m_type == KindOfBoolean ||
494 cell.m_type == KindOfInt64 ||
495 cell.m_type == KindOfDouble;
497 if (to_string_is_safe(*v1) && to_string_is_safe(*v2)) {
498 constprop(env);
499 auto const cell = eval_cell([&] {
500 auto s = StringData::Make(
501 tvAsCVarRef(&*v2).toString().get(),
502 tvAsCVarRef(&*v1).toString().get());
503 return make_tv<KindOfString>(s);
505 return push(env, cell ? *cell : TInitCell);
508 // Not nothrow even if both are strings: can throw for strings
509 // that are too large.
510 push(env, TStr);
513 void in(ISS& env, const bc::ConcatN& op) {
514 auto n = op.arg1;
515 assert(n > 1);
516 assert(n < 5);
518 for (auto i = 0; i < n; ++i) {
519 popC(env);
521 push(env, TStr);
524 template<class Op, class Fun>
525 void arithImpl(ISS& env, const Op& op, Fun fun) {
526 constprop(env);
527 auto const t1 = popC(env);
528 auto const t2 = popC(env);
529 push(env, fun(t2, t1));
532 void in(ISS& env, const bc::Add& op) { arithImpl(env, op, typeAdd); }
533 void in(ISS& env, const bc::Sub& op) { arithImpl(env, op, typeSub); }
534 void in(ISS& env, const bc::Mul& op) { arithImpl(env, op, typeMul); }
535 void in(ISS& env, const bc::Div& op) { arithImpl(env, op, typeDiv); }
536 void in(ISS& env, const bc::Mod& op) { arithImpl(env, op, typeMod); }
537 void in(ISS& env, const bc::Pow& op) { arithImpl(env, op, typePow); }
538 void in(ISS& env, const bc::BitAnd& op) { arithImpl(env, op, typeBitAnd); }
539 void in(ISS& env, const bc::BitOr& op) { arithImpl(env, op, typeBitOr); }
540 void in(ISS& env, const bc::BitXor& op) { arithImpl(env, op, typeBitXor); }
541 void in(ISS& env, const bc::AddO& op) { arithImpl(env, op, typeAddO); }
542 void in(ISS& env, const bc::SubO& op) { arithImpl(env, op, typeSubO); }
543 void in(ISS& env, const bc::MulO& op) { arithImpl(env, op, typeMulO); }
544 void in(ISS& env, const bc::Shl& op) { arithImpl(env, op, typeShl); }
545 void in(ISS& env, const bc::Shr& op) { arithImpl(env, op, typeShr); }
547 void in(ISS& env, const bc::BitNot& op) {
548 auto const t = popC(env);
549 auto const v = tv(t);
550 if (v) {
551 constprop(env);
552 auto cell = eval_cell([&] {
553 auto c = *v;
554 cellBitNot(c);
555 return c;
557 if (cell) return push(env, std::move(*cell));
559 push(env, TInitCell);
562 namespace {
564 bool couldBeHackArr(Type t) {
565 return t.couldBe(TVec) || t.couldBe(TDict) || t.couldBe(TKeyset);
570 template<bool Negate>
571 void sameImpl(ISS& env) {
572 auto const t1 = popC(env);
573 auto const t2 = popC(env);
574 auto const v1 = tv(t1);
575 auto const v2 = tv(t2);
577 auto const mightWarn = [&]{
578 // EvalHackArrCompatNotices will notice on === and !== between PHP arrays
579 // and Hack arrays.
580 if (!RuntimeOption::EvalHackArrCompatNotices) return false;
581 if (t1.couldBe(TArr) && couldBeHackArr(t2)) return true;
582 if (couldBeHackArr(t1) && t2.couldBe(TArr)) return true;
583 return false;
584 }();
585 if (!mightWarn) {
586 nothrow(env);
587 constprop(env);
590 if (v1 && v2) {
591 if (auto r = eval_cell_value([&]{ return cellSame(*v2, *v1); })) {
592 return push(env, r != Negate ? TTrue : TFalse);
595 push(env, Negate ? typeNSame(t1, t2) : typeSame(t1, t2));
598 void in(ISS& env, const bc::Same&) { sameImpl<false>(env); }
599 void in(ISS& env, const bc::NSame&) { sameImpl<true>(env); }
601 template<class Fun>
602 void binOpBoolImpl(ISS& env, Fun fun) {
603 auto const t1 = popC(env);
604 auto const t2 = popC(env);
605 auto const v1 = tv(t1);
606 auto const v2 = tv(t2);
607 if (v1 && v2) {
608 if (auto r = eval_cell_value([&]{ return fun(*v2, *v1); })) {
609 constprop(env);
610 return push(env, *r ? TTrue : TFalse);
613 // TODO_4: evaluate when these can throw, non-constant type stuff.
614 push(env, TBool);
617 template<class Fun>
618 void binOpInt64Impl(ISS& env, Fun fun) {
619 auto const t1 = popC(env);
620 auto const t2 = popC(env);
621 auto const v1 = tv(t1);
622 auto const v2 = tv(t2);
623 if (v1 && v2) {
624 if (auto r = eval_cell_value([&]{ return ival(fun(*v2, *v1)); })) {
625 constprop(env);
626 return push(env, std::move(*r));
629 // TODO_4: evaluate when these can throw, non-constant type stuff.
630 push(env, TInt);
633 void in(ISS& env, const bc::Eq&) {
634 binOpBoolImpl(env, [&] (Cell c1, Cell c2) { return cellEqual(c1, c2); });
636 void in(ISS& env, const bc::Neq&) {
637 binOpBoolImpl(env, [&] (Cell c1, Cell c2) { return !cellEqual(c1, c2); });
639 void in(ISS& env, const bc::Lt&) {
640 binOpBoolImpl(env, [&] (Cell c1, Cell c2) { return cellLess(c1, c2); });
642 void in(ISS& env, const bc::Gt&) {
643 binOpBoolImpl(env, [&] (Cell c1, Cell c2) { return cellGreater(c1, c2); });
645 void in(ISS& env, const bc::Lte&) { binOpBoolImpl(env, cellLessOrEqual); }
646 void in(ISS& env, const bc::Gte&) { binOpBoolImpl(env, cellGreaterOrEqual); }
648 void in(ISS& env, const bc::Cmp&) {
649 binOpInt64Impl(env, [&] (Cell c1, Cell c2) { return cellCompare(c1, c2); });
652 void in(ISS& env, const bc::Xor&) {
653 binOpBoolImpl(env, [&] (Cell c1, Cell c2) {
654 return cellToBool(c1) ^ cellToBool(c2);
658 void castBoolImpl(ISS& env, bool negate) {
659 nothrow(env);
660 constprop(env);
662 auto const t = popC(env);
663 auto const v = tv(t);
664 if (v) {
665 auto cell = eval_cell([&] {
666 return make_tv<KindOfBoolean>(cellToBool(*v) != negate);
668 always_assert_flog(!!cell, "cellToBool should never throw");
669 return push(env, std::move(*cell));
672 if (t.subtypeOf(TArrE)) return push(env, negate ? TTrue : TFalse);
673 if (t.subtypeOf(TArrN)) return push(env, negate ? TFalse : TTrue);
675 push(env, TBool);
678 void in(ISS& env, const bc::Not&) {
679 castBoolImpl(env, true);
682 void in(ISS& env, const bc::CastBool&) {
683 auto const t = topC(env);
684 if (t.subtypeOf(TBool)) return reduce(env, bc::Nop {});
685 castBoolImpl(env, false);
688 void in(ISS& env, const bc::CastInt&) {
689 constprop(env);
690 auto const t = topC(env);
691 if (t.subtypeOf(TInt)) return reduce(env, bc::Nop {});
692 popC(env);
693 // Objects can raise a warning about converting to int.
694 if (!t.couldBe(TObj)) nothrow(env);
695 if (auto const v = tv(t)) {
696 auto cell = eval_cell([&] {
697 return make_tv<KindOfInt64>(cellToInt(*v));
699 if (cell) return push(env, std::move(*cell));
701 push(env, TInt);
704 void castImpl(ISS& env, Type target, void(*fn)(TypedValue*)) {
705 auto const t = topC(env);
706 if (t.subtypeOf(target)) return reduce(env, bc::Nop {});
707 popC(env);
708 if (fn) {
709 if (auto val = tv(t)) {
710 if (auto result = eval_cell([&] { fn(&*val); return *val; })) {
711 constprop(env);
712 target = *result;
716 push(env, std::move(target));
719 void in(ISS& env, const bc::CastDouble&) {
720 castImpl(env, TDbl, tvCastToDoubleInPlace);
723 void in(ISS& env, const bc::CastString&) {
724 castImpl(env, TStr, tvCastToStringInPlace);
727 void in(ISS& env, const bc::CastArray&) {
728 castImpl(env, TArr, tvCastToArrayInPlace);
731 void in(ISS& env, const bc::CastObject&) { castImpl(env, TObj, nullptr); }
733 void in(ISS& env, const bc::CastDict&) {
734 castImpl(env, TDict, tvCastToDictInPlace);
737 void in(ISS& env, const bc::CastVec&) {
738 castImpl(env, TVec, tvCastToVecInPlace);
741 void in(ISS& env, const bc::CastKeyset&) {
742 castImpl(env, TKeyset, tvCastToKeysetInPlace);
745 void in(ISS& env, const bc::Print& op) { popC(env); push(env, ival(1)); }
747 void in(ISS& env, const bc::Clone& op) {
748 auto val = popC(env);
749 if (!val.subtypeOf(TObj)) {
750 val = is_opt(val) ? unopt(std::move(val)) : TObj;
752 push(env, std::move(val));
755 void in(ISS& env, const bc::Exit&) { popC(env); push(env, TInitNull); }
756 void in(ISS& env, const bc::Fatal&) { popC(env); }
758 void in(ISS& env, const bc::JmpNS&) {
759 always_assert(0 && "blocks should not contain JmpNS instructions");
762 void in(ISS& env, const bc::Jmp&) {
763 always_assert(0 && "blocks should not contain Jmp instructions");
766 template<bool Negate, class JmpOp>
767 void jmpImpl(ISS& env, const JmpOp& op) {
768 nothrow(env);
769 auto const t1 = popC(env);
770 auto const v1 = tv(t1);
771 if (v1) {
772 auto const taken = !cellToBool(*v1) != Negate;
773 if (taken) {
774 jmp_nofallthrough(env);
775 env.propagate(op.target, env.state);
776 } else {
777 jmp_nevertaken(env);
779 return;
781 if (next_real_block(*env.ctx.func, env.blk.fallthrough) ==
782 next_real_block(*env.ctx.func, op.target)) {
783 jmp_nevertaken(env);
784 return;
786 env.propagate(op.target, env.state);
789 void in(ISS& env, const bc::JmpNZ& op) { jmpImpl<true>(env, op); }
790 void in(ISS& env, const bc::JmpZ& op) { jmpImpl<false>(env, op); }
792 template<class JmpOp>
793 void group(ISS& env, const bc::IsTypeL& istype, const JmpOp& jmp) {
794 if (istype.subop2 == IsTypeOp::Scalar) return impl(env, istype, jmp);
796 auto const loc = derefLoc(env, istype.loc1);
797 auto const testTy = type_of_istype(istype.subop2);
798 if (loc.subtypeOf(testTy) || !loc.couldBe(testTy)) {
799 return impl(env, istype, jmp);
802 if (!locCouldBeUninit(env, istype.loc1)) nothrow(env);
804 auto const negate = jmp.op == Op::JmpNZ;
805 auto const was_true = [&] {
806 if (is_opt(loc)) {
807 if (testTy.subtypeOf(TNull)) return TInitNull;
808 auto const unopted = unopt(loc);
809 if (unopted.subtypeOf(testTy)) return unopted;
811 return testTy;
812 }();
813 auto const was_false = [&] {
814 if (is_opt(loc)) {
815 auto const unopted = unopt(loc);
816 if (testTy.subtypeOf(TNull)) return unopted;
817 if (unopted.subtypeOf(testTy)) return TInitNull;
819 return loc;
820 }();
822 refineLoc(env, istype.loc1, negate ? was_true : was_false);
823 env.propagate(jmp.target, env.state);
824 refineLoc(env, istype.loc1, negate ? was_false : was_true);
827 namespace {
829 // If the current function is a memoize wrapper, return the inferred return type
830 // of the function being wrapped.
831 Type memoizeImplRetType(ISS& env) {
832 always_assert(env.ctx.func->isMemoizeWrapper);
834 // Lookup the wrapped function. This should always resolve to a precise
835 // function but we don't rely on it.
836 auto const memo_impl_func = [&]{
837 if (env.ctx.func->cls) {
838 auto const clsTy = selfClsExact(env);
839 return env.index.resolve_method(
840 env.ctx,
841 clsTy ? *clsTy : TCls,
842 memoize_impl_name(env.ctx.func)
845 return env.index.resolve_func(env.ctx, memoize_impl_name(env.ctx.func));
846 }();
848 // Infer the return type of the wrapped function, taking into account the
849 // types of the parameters for context sensitive types.
850 auto const numArgs = env.ctx.func->params.size();
851 std::vector<Type> args{numArgs};
852 for (auto i = LocalId{0}; i < numArgs; ++i) {
853 args[i] = locAsCell(env, i);
856 auto retTy = env.index.lookup_return_type(
857 CallContext { env.ctx, args },
858 memo_impl_func
860 // Regardless of anything we know the return type will be an InitCell (this is
861 // a requirement of memoize functions).
862 if (!retTy.subtypeOf(TInitCell)) return TInitCell;
863 return retTy;
867 * Propagate a more specific type to the taken/fall-through branches of a jmp
868 * operation when the jmp is done because of a type test. Given a type `valTy`,
869 * being tested against the type `testTy`, propagate `failTy` to the branch
870 * representing test failure, and `testTy` to the branch representing test
871 * success.
873 template<class JmpOp>
874 void typeTestPropagate(ISS& env, Type valTy, Type testTy,
875 Type failTy, const JmpOp& jmp) {
876 nothrow(env);
877 auto const takenOnSuccess = jmp.op == Op::JmpNZ;
879 if (valTy.subtypeOf(testTy) || failTy.subtypeOf(TBottom)) {
880 push(env, std::move(valTy));
881 if (takenOnSuccess) {
882 jmp_nofallthrough(env);
883 env.propagate(jmp.target, env.state);
884 } else {
885 jmp_nevertaken(env);
887 return;
889 if (!valTy.couldBe(testTy)) {
890 push(env, failTy);
891 if (takenOnSuccess) {
892 jmp_nevertaken(env);
893 } else {
894 jmp_nofallthrough(env);
895 env.propagate(jmp.target, env.state);
897 return;
900 push(env, std::move(takenOnSuccess ? testTy : failTy));
901 env.propagate(jmp.target, env.state);
902 discard(env, 1);
903 push(env, std::move(takenOnSuccess ? failTy : testTy));
908 // If we duplicate a value, and then test its type and Jmp based on that result,
909 // we can narrow the type of the top of the stack. Only do this for null checks
910 // right now (because its useful in memoize wrappers).
911 template<class JmpOp>
912 void group(ISS& env, const bc::Dup& dup,
913 const bc::IsTypeC& istype, const JmpOp& jmp) {
914 if (istype.subop1 != IsTypeOp::Scalar) {
915 auto const testTy = type_of_istype(istype.subop1);
916 if (testTy.subtypeOf(TNull)) {
917 auto const valTy = popC(env);
918 typeTestPropagate(
919 env, valTy, TInitNull, is_opt(valTy) ? unopt(valTy) : valTy, jmp
921 return;
924 impl(env, dup, istype, jmp);
927 // If we do an IsUninit check and then Jmp based on the check, one branch will
928 // be the original type minus the Uninit, and the other will be
929 // Uninit. (IsUninit does not pop the value).
930 template<class JmpOp>
931 void group(ISS& env, const bc::IsUninit&, const JmpOp& jmp) {
932 auto const valTy = popCU(env);
933 typeTestPropagate(env, valTy, TUninit, remove_uninit(valTy), jmp);
936 // A MemoGet, followed by an IsUninit, followed by a Jmp, can have the type of
937 // the stack inferred very well. The IsUninit success path will be Uninit and
938 // the failure path will be the inferred return type of the wrapped
939 // function. This has to be done as a group and not via individual interp()
940 // calls is because of limitations in HHBBC's type-system. The type that MemoGet
941 // pushes is the inferred return type of the wrapper function with Uninit added
942 // in. Unfortunately HHBBC's type-system cannot exactly represent this
943 // combination, so it gets forced to Cell. By analyzing this triplet as a group,
944 // we can avoid this loss of type precision.
945 template <class JmpOp>
946 void group(ISS& env, const bc::MemoGet& get,
947 const bc::IsUninit& isuninit, const JmpOp& jmp) {
948 impl(env, get);
949 typeTestPropagate(env, popCU(env), TUninit, memoizeImplRetType(env), jmp);
952 template<class JmpOp>
953 void group(ISS& env, const bc::CGetL& cgetl, const JmpOp& jmp) {
954 auto const loc = derefLoc(env, cgetl.loc1);
955 if (tv(loc)) return impl(env, cgetl, jmp);
957 if (!locCouldBeUninit(env, cgetl.loc1)) nothrow(env);
959 auto const negate = jmp.op == Op::JmpNZ;
960 auto const converted_true = [&]() -> const Type {
961 if (is_opt(loc)) return unopt(loc);
962 if (loc.subtypeOf(TBool)) return TTrue;
963 return loc;
964 }();
965 auto const converted_false = [&]() -> const Type {
966 if (!could_have_magic_bool_conversion(loc) && loc.subtypeOf(TOptObj)) {
967 return TInitNull;
969 if (loc.subtypeOf(TInt)) return ival(0);
970 if (loc.subtypeOf(TBool)) return TFalse;
971 if (loc.subtypeOf(TDbl)) return dval(0);
972 // Can't tell if any of the other ?primitives are going to be
973 // null based on this, so leave those types alone. E.g. a Str
974 // might contain "" and be falsey, or an array or collection
975 // could be empty.
976 return loc;
977 }();
979 refineLoc(env, cgetl.loc1, negate ? converted_true : converted_false);
980 env.propagate(jmp.target, env.state);
981 refineLoc(env, cgetl.loc1, negate ? converted_false : converted_true);
984 template<class JmpOp>
985 void group(ISS& env,
986 const bc::CGetL& cgetl,
987 const bc::InstanceOfD& inst,
988 const JmpOp& jmp) {
989 auto bail = [&] { impl(env, cgetl, inst, jmp); };
991 if (interface_supports_non_objects(inst.str1)) return bail();
992 auto const rcls = env.index.resolve_class(env.ctx, inst.str1);
993 if (!rcls) return bail();
995 auto const instTy = subObj(*rcls);
996 auto const loc = derefLoc(env, cgetl.loc1);
997 if (loc.subtypeOf(instTy) || !loc.couldBe(instTy)) {
998 return bail();
1001 auto const negate = jmp.op == Op::JmpNZ;
1002 auto const was_true = instTy;
1003 auto const was_false = loc;
1004 refineLoc(env, cgetl.loc1, negate ? was_true : was_false);
1005 env.propagate(jmp.target, env.state);
1006 refineLoc(env, cgetl.loc1, negate ? was_false : was_true);
1009 void group(ISS& env,
1010 const bc::CGetL& cgetl,
1011 const bc::FPushObjMethodD& fpush) {
1012 auto const obj = locAsCell(env, cgetl.loc1);
1013 impl(env, cgetl, fpush);
1014 if (!is_specialized_obj(obj)) {
1015 refineLoc(env, cgetl.loc1,
1016 fpush.subop3 == ObjMethodOp::NullThrows ? TObj : TOptObj);
1017 } else if (is_opt(obj) && fpush.subop3 == ObjMethodOp::NullThrows) {
1018 refineLoc(env, cgetl.loc1, unopt(obj));
1022 void in(ISS& env, const bc::Switch& op) {
1023 popC(env);
1024 forEachTakenEdge(op, [&] (BlockId id) {
1025 env.propagate(id, env.state);
1029 void in(ISS& env, const bc::SSwitch& op) {
1030 popC(env);
1031 forEachTakenEdge(op, [&] (BlockId id) {
1032 env.propagate(id, env.state);
1036 void in(ISS& env, const bc::RetC& op) { doRet(env, popC(env)); }
1037 void in(ISS& env, const bc::RetV& op) { doRet(env, popV(env)); }
1038 void in(ISS& env, const bc::Unwind& op) {}
1039 void in(ISS& env, const bc::Throw& op) { popC(env); }
1041 void in(ISS& env, const bc::Catch&) {
1042 nothrow(env);
1043 return push(env, subObj(env.index.builtin_class(s_Throwable.get())));
1046 void in(ISS& env, const bc::NativeImpl&) {
1047 killLocals(env);
1048 mayUseVV(env);
1050 if (is_collection_method_returning_this(env.ctx.cls, env.ctx.func)) {
1051 assert(env.ctx.func->attrs & AttrParamCoerceModeNull);
1052 assert(!(env.ctx.func->attrs & AttrReference));
1053 auto const resCls = env.index.builtin_class(env.ctx.cls->name);
1054 // Can still return null if parameter coercion fails
1055 return doRet(env, union_of(objExact(resCls), TInitNull));
1058 if (env.ctx.func->nativeInfo) {
1059 return doRet(env, native_function_return_type(env.ctx.func));
1061 doRet(env, TInitGen);
1064 void in(ISS& env, const bc::CGetL& op) {
1065 LocalId equivLocal = NoLocalId;
1066 // If the local could be Uninit or a Ref, don't record equality because the
1067 // value on the stack won't the same as in the local.
1068 if (!locCouldBeUninit(env, op.loc1)) {
1069 nothrow(env);
1070 constprop(env);
1071 if (!locCouldBeRef(env, op.loc1) &&
1072 !is_volatile_local(env.ctx.func, op.loc1)) {
1073 equivLocal = op.loc1;
1076 push(env, locAsCell(env, op.loc1), equivLocal);
1079 void in(ISS& env, const bc::CGetQuietL& op) {
1080 nothrow(env);
1081 constprop(env);
1082 push(env, locAsCell(env, op.loc1));
1085 void in(ISS& env, const bc::CUGetL& op) {
1086 auto ty = locRaw(env, op.loc1);
1087 if (ty.subtypeOf(TUninit)) {
1088 return reduce(env, bc::NullUninit {});
1090 nothrow(env);
1091 if (!ty.couldBe(TUninit)) constprop(env);
1092 if (!ty.subtypeOf(TCell)) ty = TCell;
1093 push(env, std::move(ty));
1096 void in(ISS& env, const bc::PushL& op) {
1097 impl(env, bc::CGetL { op.loc1 }, bc::UnsetL { op.loc1 });
1100 void in(ISS& env, const bc::CGetL2& op) {
1101 // Can't constprop yet because of no INS_1 support in bc.h
1102 if (!locCouldBeUninit(env, op.loc1)) nothrow(env);
1103 auto loc = locAsCell(env, op.loc1);
1104 auto top = popT(env);
1105 push(env, std::move(loc));
1106 push(env, std::move(top));
1109 namespace {
1111 template <typename Op> void common_cgetn(ISS& env) {
1112 auto const t1 = topC(env);
1113 auto const v1 = tv(t1);
1114 if (v1 && v1->m_type == KindOfPersistentString) {
1115 auto const loc = findLocal(env, v1->m_data.pstr);
1116 if (loc != NoLocalId) {
1117 return reduce(env, bc::PopC {}, Op { loc });
1120 readUnknownLocals(env);
1121 mayUseVV(env);
1122 popC(env); // conversion to string can throw
1123 push(env, TInitCell);
1128 void in(ISS& env, const bc::CGetN&) { common_cgetn<bc::CGetL>(env); }
1129 void in(ISS& env, const bc::CGetQuietN&) { common_cgetn<bc::CGetQuietL>(env); }
1131 void in(ISS& env, const bc::CGetG&) { popC(env); push(env, TInitCell); }
1132 void in(ISS& env, const bc::CGetQuietG&) { popC(env); push(env, TInitCell); }
1134 void in(ISS& env, const bc::CGetS& op) {
1135 auto const tcls = takeClsRefSlot(env, op.slot);
1136 auto const tname = popC(env);
1137 auto const vname = tv(tname);
1138 auto const self = selfCls(env);
1140 if (vname && vname->m_type == KindOfPersistentString &&
1141 self && tcls.subtypeOf(*self)) {
1142 if (auto ty = selfPropAsCell(env, vname->m_data.pstr)) {
1143 // Only nothrow when we know it's a private declared property
1144 // (and thus accessible here).
1145 nothrow(env);
1147 // We can only constprop here if we know for sure this is exactly the
1148 // correct class. The reason for this is that you could have a LSB class
1149 // attempting to access a private static in a derived class with the same
1150 // name as a private static in this class, which is supposed to fatal at
1151 // runtime (for an example see test/quick/static_sprop2.php).
1152 auto const selfExact = selfClsExact(env);
1153 if (selfExact && tcls.subtypeOf(*selfExact)) {
1154 constprop(env);
1157 return push(env, std::move(*ty));
1161 auto indexTy = env.index.lookup_public_static(tcls, tname);
1162 if (indexTy.subtypeOf(TInitCell)) {
1164 * Constant propagation here can change when we invoke autoload, so it's
1165 * considered HardConstProp. It's safe not to check anything about private
1166 * or protected static properties, because you can't override a public
1167 * static property with a private or protected one---if the index gave us
1168 * back a constant type, it's because it found a public static and it must
1169 * be the property this would have read dynamically.
1171 if (options.HardConstProp) constprop(env);
1172 return push(env, std::move(indexTy));
1175 push(env, TInitCell);
1178 void in(ISS& env, const bc::VGetL& op) {
1179 nothrow(env);
1180 setLocRaw(env, op.loc1, TRef);
1181 push(env, TRef);
1184 void in(ISS& env, const bc::VGetN&) {
1185 auto const t1 = topC(env);
1186 auto const v1 = tv(t1);
1187 if (v1 && v1->m_type == KindOfPersistentString) {
1188 auto const loc = findLocal(env, v1->m_data.pstr);
1189 if (loc != NoLocalId) {
1190 return reduce(env, bc::PopC {},
1191 bc::VGetL { loc });
1194 modifyLocalStatic(env, NoLocalId, TRef);
1195 popC(env);
1196 boxUnknownLocal(env);
1197 mayUseVV(env);
1198 push(env, TRef);
1201 void in(ISS& env, const bc::VGetG&) { popC(env); push(env, TRef); }
1203 void in(ISS& env, const bc::VGetS& op) {
1204 auto const tcls = takeClsRefSlot(env, op.slot);
1205 auto const tname = popC(env);
1206 auto const vname = tv(tname);
1207 auto const self = selfCls(env);
1209 if (!self || tcls.couldBe(*self)) {
1210 if (vname && vname->m_type == KindOfPersistentString) {
1211 boxSelfProp(env, vname->m_data.pstr);
1212 } else {
1213 killSelfProps(env);
1217 if (auto c = env.collect.publicStatics) {
1218 c->merge(env.ctx, tcls, tname, TRef);
1221 push(env, TRef);
1224 void clsRefGetImpl(ISS& env, Type t1, ClsRefSlotId slot) {
1225 auto cls = [&]{
1226 if (t1.subtypeOf(TObj)) {
1227 nothrow(env);
1228 return objcls(t1);
1230 auto const v1 = tv(t1);
1231 if (v1 && v1->m_type == KindOfPersistentString) {
1232 if (auto const rcls = env.index.resolve_class(env.ctx, v1->m_data.pstr)) {
1233 return clsExact(*rcls);
1236 return TCls;
1237 }();
1238 putClsRefSlot(env, slot, std::move(cls));
1241 void in(ISS& env, const bc::ClsRefGetL& op) {
1242 clsRefGetImpl(env, locAsCell(env, op.loc1), op.slot);
1244 void in(ISS& env, const bc::ClsRefGetC& op) {
1245 clsRefGetImpl(env, popC(env), op.slot);
1248 void in(ISS& env, const bc::AKExists& op) {
1249 auto const t1 = popC(env);
1250 auto const t2 = popC(env);
1251 auto const t1Ok = t1.subtypeOf(TObj) || t1.subtypeOf(TArr);
1252 auto const t2Ok = t2.subtypeOf(TInt) || t2.subtypeOf(TNull) ||
1253 t2.subtypeOf(TStr);
1254 if (t1Ok && t2Ok) nothrow(env);
1255 push(env, TBool);
1258 void in(ISS& env, const bc::GetMemoKeyL& op) {
1259 always_assert(env.ctx.func->isMemoizeWrapper);
1261 auto const tyIMemoizeParam =
1262 subObj(env.index.builtin_class(s_IMemoizeParam.get()));
1264 auto const inTy = locAsCell(env, op.loc1);
1266 // If the local could be uninit, we might raise a warning (as
1267 // usual). Converting an object to a memo key might invoke PHP code if it has
1268 // the IMemoizeParam interface, and if it doesn't, we'll throw.
1269 if (!locCouldBeUninit(env, op.loc1) && !inTy.couldBe(TObj)) {
1270 nothrow(env); constprop(env);
1273 // If type constraints are being enforced and the local being turned into a
1274 // memo key is a parameter, then we can possibly using the type constraint to
1275 // perform a more efficient memoization scheme. Note that this all needs to
1276 // stay in sync with the interpreter and JIT.
1277 using MK = MemoKeyConstraint;
1278 auto const mkc = [&]{
1279 if (!options.HardTypeHints) return MK::None;
1280 if (op.loc1 >= env.ctx.func->params.size()) return MK::None;
1281 return memoKeyConstraintFromTC(
1282 env.ctx.func->params[op.loc1].typeConstraint
1284 }();
1286 switch (mkc) {
1287 case MK::Null:
1288 // Always null, so the key can always just be 0
1289 always_assert(inTy.subtypeOf(TNull));
1290 return push(env, ival(0));
1291 case MK::Int:
1292 // Always an int, so the key is always an identity mapping
1293 always_assert(inTy.subtypeOf(TInt));
1294 return reduce(env, bc::CGetL { op.loc1 });
1295 case MK::Bool:
1296 // Always a bool, so the key is the bool cast to an int
1297 always_assert(inTy.subtypeOf(TBool));
1298 return reduce(env, bc::CGetL { op.loc1 }, bc::CastInt {});
1299 case MK::Str:
1300 // Always a string, so the key is always an identity mapping
1301 always_assert(inTy.subtypeOf(TStr));
1302 return reduce(env, bc::CGetL { op.loc1 });
1303 case MK::IntOrStr:
1304 // Either an int or string, so the key can be an identity mapping
1305 return reduce(env, bc::CGetL { op.loc1 });
1306 case MK::StrOrNull:
1307 case MK::IntOrNull:
1308 // A nullable string or int. For strings the key will always be 0 or the
1309 // string. For ints the key will be the int or a static string. We can't
1310 // reduce either without introducing control flow.
1311 return push(env, union_of(TInt, TStr));
1312 case MK::BoolOrNull:
1313 // A nullable bool. The key will always be an int (null will be 2), but we
1314 // can't reduce that without introducing control flow.
1315 return push(env, TInt);
1316 case MK::None:
1317 break;
1320 // No type constraint, or one that isn't usuable. Use the generic memoization
1321 // scheme which can handle any type:
1323 // Integer keys are always mapped to themselves
1324 if (inTy.subtypeOf(TInt)) return reduce(env, bc::CGetL { op.loc1 });
1326 if (inTy.subtypeOf(tyIMemoizeParam)) {
1327 return reduce(
1328 env,
1329 bc::CGetL { op.loc1 },
1330 bc::FPushObjMethodD {
1332 s_getInstanceKey.get(),
1333 ObjMethodOp::NullThrows,
1334 false
1336 bc::FCall { 0 },
1337 bc::UnboxR {}
1341 // A memo key can be an integer if the input might be an integer, and is a
1342 // string otherwise. Booleans are always static strings.
1343 auto keyTy = [&]{
1344 if (auto const val = tv(inTy)) {
1345 auto const key = eval_cell(
1346 [&]{ return HHVM_FN(serialize_memoize_param)(*val); }
1348 if (key) return *key;
1350 if (inTy.subtypeOf(TBool)) return TSStr;
1351 if (inTy.couldBe(TInt)) return union_of(TInt, TStr);
1352 return TStr;
1353 }();
1354 push(env, std::move(keyTy));
1357 void in(ISS& env, const bc::IssetL& op) {
1358 nothrow(env);
1359 constprop(env);
1360 auto const loc = locAsCell(env, op.loc1);
1361 if (loc.subtypeOf(TNull)) return push(env, TFalse);
1362 if (!loc.couldBe(TNull)) return push(env, TTrue);
1363 push(env, TBool);
1366 void in(ISS& env, const bc::EmptyL& op) {
1367 nothrow(env);
1368 constprop(env);
1369 if (!locCouldBeUninit(env, op.loc1)) {
1370 return impl(env, bc::CGetL { op.loc1 }, bc::Not {});
1372 locAsCell(env, op.loc1); // read the local
1373 push(env, TBool);
1376 void in(ISS& env, const bc::EmptyS& op) {
1377 takeClsRefSlot(env, op.slot);
1378 popC(env);
1379 push(env, TBool);
1382 void in(ISS& env, const bc::IssetS& op) {
1383 auto const tcls = takeClsRefSlot(env, op.slot);
1384 auto const tname = popC(env);
1385 auto const vname = tv(tname);
1386 auto const self = selfCls(env);
1388 if (self && tcls.subtypeOf(*self) &&
1389 vname && vname->m_type == KindOfPersistentString) {
1390 if (auto const t = selfPropAsCell(env, vname->m_data.pstr)) {
1391 if (t->subtypeOf(TNull)) { constprop(env); return push(env, TFalse); }
1392 if (!t->couldBe(TNull)) { constprop(env); return push(env, TTrue); }
1396 auto const indexTy = env.index.lookup_public_static(tcls, tname);
1397 if (indexTy.subtypeOf(TInitCell)) {
1398 // See the comments in CGetS about constprop for public statics.
1399 if (options.HardConstProp) constprop(env);
1400 if (indexTy.subtypeOf(TNull)) { return push(env, TFalse); }
1401 if (!indexTy.couldBe(TNull)) { return push(env, TTrue); }
1404 push(env, TBool);
1407 template<class ReduceOp>
1408 void issetEmptyNImpl(ISS& env) {
1409 auto const t1 = topC(env);
1410 auto const v1 = tv(t1);
1411 if (v1 && v1->m_type == KindOfPersistentString) {
1412 auto const loc = findLocal(env, v1->m_data.pstr);
1413 if (loc != NoLocalId) {
1414 return reduce(env, bc::PopC {}, ReduceOp { loc });
1416 // Can't push true in the non env.findLocal case unless we know
1417 // whether this function can have a VarEnv.
1419 readUnknownLocals(env);
1420 mayUseVV(env);
1421 popC(env);
1422 push(env, TBool);
1425 void in(ISS& env, const bc::IssetN&) { issetEmptyNImpl<bc::IssetL>(env); }
1426 void in(ISS& env, const bc::EmptyN&) { issetEmptyNImpl<bc::EmptyL>(env); }
1427 void in(ISS& env, const bc::EmptyG&) { popC(env); push(env, TBool); }
1428 void in(ISS& env, const bc::IssetG&) { popC(env); push(env, TBool); }
1430 void isTypeImpl(ISS& env, const Type& locOrCell, const Type& test) {
1431 constprop(env);
1432 if (locOrCell.subtypeOf(test)) return push(env, TTrue);
1433 if (!locOrCell.couldBe(test)) return push(env, TFalse);
1434 push(env, TBool);
1437 void isTypeObj(ISS& env, const Type& ty) {
1438 if (!ty.couldBe(TObj)) return push(env, TFalse);
1439 if (ty.subtypeOf(TObj)) {
1440 auto const incompl = objExact(
1441 env.index.builtin_class(s_PHP_Incomplete_Class.get()));
1442 if (!ty.couldBe(incompl)) return push(env, TTrue);
1443 if (ty.subtypeOf(incompl)) return push(env, TFalse);
1445 push(env, TBool);
1448 template<class Op>
1449 void isTypeLImpl(ISS& env, const Op& op) {
1450 if (!locCouldBeUninit(env, op.loc1)) { nothrow(env); constprop(env); }
1451 auto const loc = locAsCell(env, op.loc1);
1452 switch (op.subop2) {
1453 case IsTypeOp::Scalar: return push(env, TBool);
1454 case IsTypeOp::Obj: return isTypeObj(env, loc);
1455 default: return isTypeImpl(env, loc, type_of_istype(op.subop2));
1459 template<class Op>
1460 void isTypeCImpl(ISS& env, const Op& op) {
1461 nothrow(env);
1462 auto const t1 = popC(env);
1463 switch (op.subop1) {
1464 case IsTypeOp::Scalar: return push(env, TBool);
1465 case IsTypeOp::Obj: return isTypeObj(env, t1);
1466 default: return isTypeImpl(env, t1, type_of_istype(op.subop1));
1470 void in(ISS& env, const bc::IsTypeC& op) { isTypeCImpl(env, op); }
1471 void in(ISS& env, const bc::IsTypeL& op) { isTypeLImpl(env, op); }
1473 void in(ISS& env, const bc::IsUninit& op) {
1474 nothrow(env);
1475 push(env, popCU(env));
1476 isTypeImpl(env, topT(env), TUninit);
1479 void in(ISS& env, const bc::MaybeMemoType& op) {
1480 always_assert(env.ctx.func->isMemoizeWrapper);
1481 nothrow(env);
1482 constprop(env);
1483 auto const memoTy = memoizeImplRetType(env);
1484 auto const ty = popC(env);
1485 push(env, ty.couldBe(memoTy) ? TTrue : TFalse);
1488 void in(ISS& env, const bc::IsMemoType& op) {
1489 always_assert(env.ctx.func->isMemoizeWrapper);
1490 nothrow(env);
1491 constprop(env);
1492 auto const memoTy = memoizeImplRetType(env);
1493 auto const ty = popC(env);
1494 push(env, memoTy.subtypeOf(ty) ? TTrue : TFalse);
1497 void in(ISS& env, const bc::InstanceOfD& op) {
1498 auto const t1 = popC(env);
1499 // Note: InstanceOfD can do autoload if the type might be a type
1500 // alias, so it's not nothrow unless we know it's an object type.
1501 if (auto const rcls = env.index.resolve_class(env.ctx, op.str1)) {
1502 nothrow(env);
1503 if (!interface_supports_non_objects(rcls->name())) {
1504 isTypeImpl(env, t1, subObj(*rcls));
1505 return;
1508 push(env, TBool);
1511 void in(ISS& env, const bc::InstanceOf& op) {
1512 auto const t1 = topC(env);
1513 auto const v1 = tv(t1);
1514 if (v1 && v1->m_type == KindOfPersistentString) {
1515 return reduce(env, bc::PopC {},
1516 bc::InstanceOfD { v1->m_data.pstr });
1519 if (t1.subtypeOf(TObj) && is_specialized_obj(t1)) {
1520 auto const dobj = dobj_of(t1);
1521 switch (dobj.type) {
1522 case DObj::Sub:
1523 break;
1524 case DObj::Exact:
1525 return reduce(env, bc::PopC {},
1526 bc::InstanceOfD { dobj.cls.name() });
1530 popC(env);
1531 popC(env);
1532 push(env, TBool);
1535 void in(ISS& env, const bc::SetL& op) {
1536 nothrow(env);
1537 auto equivLoc = topStkEquiv(env);
1538 // If the local could be a Ref, don't record equality because the stack
1539 // element and the local won't actually have the same type.
1540 if (!locCouldBeRef(env, op.loc1) &&
1541 !is_volatile_local(env.ctx.func, op.loc1)) {
1542 if (equivLoc != NoLocalId) {
1543 if (equivLoc == op.loc1 ||
1544 locsAreEquiv(env, equivLoc, op.loc1)) {
1545 return reduce(env, bc::Nop {});
1547 } else {
1548 equivLoc = op.loc1;
1551 auto val = popC(env);
1552 setLoc(env, op.loc1, val);
1553 if (equivLoc != op.loc1 && equivLoc != NoLocalId) {
1554 addLocEquiv(env, op.loc1, equivLoc);
1556 push(env, std::move(val), equivLoc);
1559 void in(ISS& env, const bc::SetN&) {
1560 // This isn't trivial to strength reduce, without a "flip two top
1561 // elements of stack" opcode.
1562 auto t1 = popC(env);
1563 auto const t2 = popC(env);
1564 auto const v2 = tv(t2);
1565 // TODO(#3653110): could nothrow if t2 can't be an Obj or Res
1567 auto const knownLoc = v2 && v2->m_type == KindOfPersistentString
1568 ? findLocal(env, v2->m_data.pstr)
1569 : NoLocalId;
1570 if (knownLoc != NoLocalId) {
1571 setLoc(env, knownLoc, t1);
1572 } else {
1573 // We could be changing the value of any local, but we won't
1574 // change whether or not they are boxed or initialized.
1575 loseNonRefLocalTypes(env);
1577 mayUseVV(env);
1578 push(env, std::move(t1));
1581 void in(ISS& env, const bc::SetG&) {
1582 auto t1 = popC(env);
1583 popC(env);
1584 push(env, std::move(t1));
1587 void in(ISS& env, const bc::SetS& op) {
1588 auto const t1 = popC(env);
1589 auto const tcls = takeClsRefSlot(env, op.slot);
1590 auto const tname = popC(env);
1591 auto const vname = tv(tname);
1592 auto const self = selfCls(env);
1594 if (!self || tcls.couldBe(*self)) {
1595 if (vname && vname->m_type == KindOfPersistentString) {
1596 nothrow(env);
1597 mergeSelfProp(env, vname->m_data.pstr, t1);
1598 } else {
1599 mergeEachSelfPropRaw(env, [&] (Type) { return t1; });
1603 if (auto c = env.collect.publicStatics) {
1604 c->merge(env.ctx, tcls, tname, t1);
1607 push(env, std::move(t1));
1610 void in(ISS& env, const bc::SetOpL& op) {
1611 auto const t1 = popC(env);
1612 auto const v1 = tv(t1);
1613 auto const loc = locAsCell(env, op.loc1);
1614 auto const locVal = tv(loc);
1615 if (v1 && locVal) {
1616 // Can't constprop at this eval_cell, because of the effects on
1617 // locals.
1618 auto resultTy = eval_cell([&] {
1619 Cell c = *locVal;
1620 Cell rhs = *v1;
1621 setopBody(&c, op.subop2, &rhs);
1622 return c;
1624 if (!resultTy) resultTy = TInitCell;
1626 // We may have inferred a TSStr or TSArr with a value here, but
1627 // at runtime it will not be static. For now just throw that
1628 // away. TODO(#3696042): should be able to loosen_statics here.
1629 if (resultTy->subtypeOf(TStr)) resultTy = TStr;
1630 else if (resultTy->subtypeOf(TArr)) resultTy = TArr;
1632 setLoc(env, op.loc1, *resultTy);
1633 push(env, *resultTy);
1634 return;
1637 auto resultTy = typeSetOp(op.subop2, loc, t1);
1638 setLoc(env, op.loc1, resultTy);
1639 push(env, std::move(resultTy));
1642 void in(ISS& env, const bc::SetOpN&) {
1643 popC(env);
1644 popC(env);
1645 loseNonRefLocalTypes(env);
1646 mayUseVV(env);
1647 push(env, TInitCell);
1650 void in(ISS& env, const bc::SetOpG&) {
1651 popC(env); popC(env);
1652 push(env, TInitCell);
1655 void in(ISS& env, const bc::SetOpS& op) {
1656 popC(env);
1657 auto const tcls = takeClsRefSlot(env, op.slot);
1658 auto const tname = popC(env);
1659 auto const vname = tv(tname);
1660 auto const self = selfCls(env);
1662 if (!self || tcls.couldBe(*self)) {
1663 if (vname && vname->m_type == KindOfPersistentString) {
1664 mergeSelfProp(env, vname->m_data.pstr, TInitCell);
1665 } else {
1666 loseNonRefSelfPropTypes(env);
1670 if (auto c = env.collect.publicStatics) {
1671 c->merge(env.ctx, tcls, tname, TInitCell);
1674 push(env, TInitCell);
1677 void in(ISS& env, const bc::IncDecL& op) {
1678 auto loc = locAsCell(env, op.loc1);
1679 auto newT = typeIncDec(op.subop2, loc);
1680 auto const pre = isPre(op.subop2);
1682 // If it's a non-numeric string, this may cause it to exceed the max length.
1683 if (!locCouldBeUninit(env, op.loc1) &&
1684 !loc.couldBe(TStr)) {
1685 nothrow(env);
1688 if (!pre) push(env, std::move(loc));
1689 setLoc(env, op.loc1, newT);
1690 if (pre) push(env, std::move(newT));
1693 void in(ISS& env, const bc::IncDecN& op) {
1694 auto const t1 = topC(env);
1695 auto const v1 = tv(t1);
1696 auto const knownLoc = v1 && v1->m_type == KindOfPersistentString
1697 ? findLocal(env, v1->m_data.pstr)
1698 : NoLocalId;
1699 if (knownLoc != NoLocalId) {
1700 return reduce(env, bc::PopC {},
1701 bc::IncDecL { knownLoc, op.subop1 });
1703 popC(env);
1704 loseNonRefLocalTypes(env);
1705 mayUseVV(env);
1706 push(env, TInitCell);
1709 void in(ISS& env, const bc::IncDecG&) { popC(env); push(env, TInitCell); }
1711 void in(ISS& env, const bc::IncDecS& op) {
1712 auto const tcls = takeClsRefSlot(env, op.slot);
1713 auto const tname = popC(env);
1714 auto const vname = tv(tname);
1715 auto const self = selfCls(env);
1717 if (!self || tcls.couldBe(*self)) {
1718 if (vname && vname->m_type == KindOfPersistentString) {
1719 mergeSelfProp(env, vname->m_data.pstr, TInitCell);
1720 } else {
1721 loseNonRefSelfPropTypes(env);
1725 if (auto c = env.collect.publicStatics) {
1726 c->merge(env.ctx, tcls, tname, TInitCell);
1729 push(env, TInitCell);
1732 void in(ISS& env, const bc::BindL& op) {
1733 // If the op.loc1 was bound to a local static, its going to be
1734 // unbound from it. If the thing its being bound /to/ is a local
1735 // static, we've already marked it as modified via the VGetL, so
1736 // there's nothing more to track.
1737 // Unbind it before any updates.
1738 modifyLocalStatic(env, op.loc1, TUninit);
1739 nothrow(env);
1740 auto t1 = popV(env);
1741 setLocRaw(env, op.loc1, t1);
1742 push(env, std::move(t1));
1745 void in(ISS& env, const bc::BindN&) {
1746 // TODO(#3653110): could nothrow if t2 can't be an Obj or Res
1747 auto t1 = popV(env);
1748 auto const t2 = popC(env);
1749 auto const v2 = tv(t2);
1750 auto const knownLoc = v2 && v2->m_type == KindOfPersistentString
1751 ? findLocal(env, v2->m_data.pstr)
1752 : NoLocalId;
1753 unbindLocalStatic(env, knownLoc);
1754 if (knownLoc != NoLocalId) {
1755 setLocRaw(env, knownLoc, t1);
1756 } else {
1757 boxUnknownLocal(env);
1759 mayUseVV(env);
1760 push(env, std::move(t1));
1763 void in(ISS& env, const bc::BindG&) {
1764 auto t1 = popV(env);
1765 popC(env);
1766 push(env, std::move(t1));
1769 void in(ISS& env, const bc::BindS& op) {
1770 popV(env);
1771 auto const tcls = takeClsRefSlot(env, op.slot);
1772 auto const tname = popC(env);
1773 auto const vname = tv(tname);
1774 auto const self = selfCls(env);
1776 if (!self || tcls.couldBe(*self)) {
1777 if (vname && vname->m_type == KindOfPersistentString) {
1778 boxSelfProp(env, vname->m_data.pstr);
1779 } else {
1780 killSelfProps(env);
1784 if (auto c = env.collect.publicStatics) {
1785 c->merge(env.ctx, tcls, tname, TRef);
1788 push(env, TRef);
1791 void in(ISS& env, const bc::UnsetL& op) {
1792 nothrow(env);
1793 setLocRaw(env, op.loc1, TUninit);
1796 void in(ISS& env, const bc::UnsetN& op) {
1797 auto const t1 = topC(env);
1798 auto const v1 = tv(t1);
1799 if (v1 && v1->m_type == KindOfPersistentString) {
1800 auto const loc = findLocal(env, v1->m_data.pstr);
1801 if (loc != NoLocalId) {
1802 return reduce(env, bc::PopC {},
1803 bc::UnsetL { loc });
1806 popC(env);
1807 if (!t1.couldBe(TObj) && !t1.couldBe(TRes)) nothrow(env);
1808 unsetUnknownLocal(env);
1809 mayUseVV(env);
1812 void in(ISS& env, const bc::UnsetG& op) {
1813 auto const t1 = popC(env);
1814 if (!t1.couldBe(TObj) && !t1.couldBe(TRes)) nothrow(env);
1817 void in(ISS& env, const bc::FPushFuncD& op) {
1818 auto const rfunc = env.index.resolve_func(env.ctx, op.str2);
1819 if (auto const func = rfunc.exactFunc()) {
1820 if (can_emit_builtin(func, op.arg1, op.has_unpack)) {
1821 fpiPush(env, ActRec { FPIKind::Builtin, folly::none, rfunc });
1822 return reduce(env, bc::Nop {});
1825 fpiPush(env, ActRec { FPIKind::Func, folly::none, rfunc });
1828 void in(ISS& env, const bc::FPushFunc& op) {
1829 auto const t1 = topC(env);
1830 auto const v1 = tv(t1);
1831 if (v1 && v1->m_type == KindOfPersistentString) {
1832 auto const name = normalizeNS(v1->m_data.pstr);
1833 // FPushFuncD doesn't support class-method pair strings yet.
1834 if (isNSNormalized(name) && notClassMethodPair(name)) {
1835 auto const rfunc = env.index.resolve_func(env.ctx, name);
1836 // Don't turn dynamic calls to caller frame affecting functions into
1837 // static calls, because they might fatal (whereas the static one won't).
1838 if (!rfunc.mightAccessCallerFrame()) {
1839 return reduce(env, bc::PopC {},
1840 bc::FPushFuncD { op.arg1, name, op.has_unpack });
1844 popC(env);
1845 if (t1.subtypeOf(TObj)) return fpiPush(env, ActRec { FPIKind::ObjInvoke });
1846 if (t1.subtypeOf(TArr)) return fpiPush(env, ActRec { FPIKind::CallableArr });
1847 if (t1.subtypeOf(TStr)) return fpiPush(env, ActRec { FPIKind::Func });
1848 fpiPush(env, ActRec { FPIKind::Unknown });
1851 void in(ISS& env, const bc::FPushFuncU& op) {
1852 auto const rfuncPair =
1853 env.index.resolve_func_fallback(env.ctx, op.str2, op.str3);
1854 if (options.ElideAutoloadInvokes && !rfuncPair.second) {
1855 return reduce(
1856 env,
1857 bc::FPushFuncD { op.arg1, rfuncPair.first.name(), op.has_unpack }
1860 fpiPush(
1861 env,
1862 ActRec { FPIKind::Func, folly::none, rfuncPair.first, rfuncPair.second }
1866 void in(ISS& env, const bc::FPushObjMethodD& op) {
1867 auto t1 = popC(env);
1868 if (is_opt(t1) && op.subop3 == ObjMethodOp::NullThrows) {
1869 t1 = unopt(t1);
1871 auto const clsTy = objcls(t1);
1872 auto const rcls = [&]() -> folly::Optional<res::Class> {
1873 if (is_specialized_cls(clsTy)) return dcls_of(clsTy).cls;
1874 return folly::none;
1875 }();
1877 fpiPush(env, ActRec {
1878 FPIKind::ObjMeth,
1879 rcls,
1880 env.index.resolve_method(env.ctx, clsTy, op.str2)
1884 void in(ISS& env, const bc::FPushObjMethod& op) {
1885 auto const t1 = topC(env);
1886 auto const v1 = tv(t1);
1887 if (v1 && v1->m_type == KindOfPersistentString) {
1888 return reduce(
1889 env,
1890 bc::PopC {},
1891 bc::FPushObjMethodD { op.arg1, v1->m_data.pstr, op.subop2, op.has_unpack }
1894 popC(env);
1895 popC(env);
1896 fpiPush(env, ActRec { FPIKind::ObjMeth });
1899 void in(ISS& env, const bc::FPushClsMethodD& op) {
1900 auto const rcls = env.index.resolve_class(env.ctx, op.str3);
1901 auto const rfun = env.index.resolve_method(
1902 env.ctx,
1903 rcls ? clsExact(*rcls) : TCls,
1904 op.str2
1906 fpiPush(env, ActRec { FPIKind::ClsMeth, rcls, rfun });
1909 void in(ISS& env, const bc::FPushClsMethod& op) {
1910 auto const t1 = takeClsRefSlot(env, op.slot);
1911 auto const t2 = popC(env);
1912 auto const v2 = tv(t2);
1914 folly::Optional<res::Func> rfunc;
1915 if (v2 && v2->m_type == KindOfPersistentString) {
1916 rfunc = env.index.resolve_method(env.ctx, t1, v2->m_data.pstr);
1918 folly::Optional<res::Class> rcls;
1919 if (is_specialized_cls(t1)) rcls = dcls_of(t1).cls;
1920 fpiPush(env, ActRec { FPIKind::ClsMeth, rcls, rfunc });
1923 void in(ISS& env, const bc::FPushClsMethodF& op) {
1924 // The difference with FPushClsMethod is what ends up on the
1925 // ActRec (late-bound class), which we currently aren't tracking.
1926 impl(env, bc::FPushClsMethod { op.arg1, op.slot, op.has_unpack });
1929 void ctorHelper(ISS& env, SString name) {
1930 auto const rcls = env.index.resolve_class(env.ctx, name);
1931 push(env, rcls ? objExact(*rcls) : TObj);
1932 auto const rfunc =
1933 rcls ? env.index.resolve_ctor(env.ctx, *rcls) : folly::none;
1934 fpiPush(env, ActRec { FPIKind::Ctor, rcls, rfunc });
1937 void in(ISS& env, const bc::FPushCtorD& op) {
1938 ctorHelper(env, op.str2);
1941 void in(ISS& env, const bc::FPushCtorI& op) {
1942 auto const name = env.ctx.unit->classes[op.arg2]->name;
1943 ctorHelper(env, name);
1946 void in(ISS& env, const bc::FPushCtor& op) {
1947 auto const& t1 = peekClsRefSlot(env, op.slot);
1948 if (is_specialized_cls(t1)) {
1949 auto const dcls = dcls_of(t1);
1950 if (dcls.type == DCls::Exact) {
1951 return reduce(env, bc::DiscardClsRef { op.slot },
1952 bc::FPushCtorD { op.arg1, dcls.cls.name(), op.has_unpack });
1955 takeClsRefSlot(env, op.slot);
1956 push(env, TObj);
1957 fpiPush(env, ActRec { FPIKind::Ctor });
1960 void in(ISS& env, const bc::FPushCufIter&) {
1961 nothrow(env);
1962 fpiPush(env, ActRec { FPIKind::Unknown });
1965 void in(ISS& env, const bc::FPushCuf&) {
1966 popC(env);
1967 fpiPush(env, ActRec { FPIKind::Unknown });
1969 void in(ISS& env, const bc::FPushCufF&) {
1970 popC(env);
1971 fpiPush(env, ActRec { FPIKind::Unknown });
1974 void in(ISS& env, const bc::FPushCufSafe&) {
1975 auto t1 = popC(env);
1976 popC(env);
1977 push(env, std::move(t1));
1978 fpiPush(env, ActRec { FPIKind::Unknown });
1979 push(env, TBool);
1982 void in(ISS& env, const bc::FPassL& op) {
1983 switch (prepKind(env, op.arg1)) {
1984 case PrepKind::Unknown:
1985 if (!locCouldBeUninit(env, op.loc2)) nothrow(env);
1986 // This might box the local, we can't tell. Note: if the local
1987 // is already TRef, we could try to leave it alone, but not for
1988 // now.
1989 setLocRaw(env, op.loc2, TGen);
1990 return push(env, TInitGen);
1991 case PrepKind::Val:
1992 return reduce_fpass_arg(env, bc::CGetL { op.loc2 }, op.arg1, false);
1993 case PrepKind::Ref:
1994 return reduce_fpass_arg(env, bc::VGetL { op.loc2 }, op.arg1, true);
1998 void in(ISS& env, const bc::FPassN& op) {
1999 switch (prepKind(env, op.arg1)) {
2000 case PrepKind::Unknown:
2001 // This could change the type of any local.
2002 popC(env);
2003 killLocals(env);
2004 mayUseVV(env);
2005 return push(env, TInitGen);
2006 case PrepKind::Val: return reduce_fpass_arg(env,
2007 bc::CGetN {},
2008 op.arg1,
2009 false);
2010 case PrepKind::Ref: return reduce_fpass_arg(env,
2011 bc::VGetN {},
2012 op.arg1,
2013 true);
2017 void in(ISS& env, const bc::FPassG& op) {
2018 switch (prepKind(env, op.arg1)) {
2019 case PrepKind::Unknown: popC(env); return push(env, TInitGen);
2020 case PrepKind::Val: return reduce_fpass_arg(env,
2021 bc::CGetG {},
2022 op.arg1,
2023 false);
2024 case PrepKind::Ref: return reduce_fpass_arg(env,
2025 bc::VGetG {},
2026 op.arg1,
2027 true);
2031 void in(ISS& env, const bc::FPassS& op) {
2032 switch (prepKind(env, op.arg1)) {
2033 case PrepKind::Unknown:
2035 auto tcls = takeClsRefSlot(env, op.slot);
2036 auto const self = selfCls(env);
2037 auto const tname = popC(env);
2038 auto const vname = tv(tname);
2039 if (!self || tcls.couldBe(*self)) {
2040 if (vname && vname->m_type == KindOfPersistentString) {
2041 // May or may not be boxing it, depending on the refiness.
2042 mergeSelfProp(env, vname->m_data.pstr, TInitGen);
2043 } else {
2044 killSelfProps(env);
2047 if (auto c = env.collect.publicStatics) {
2048 c->merge(env.ctx, std::move(tcls), tname, TInitGen);
2051 return push(env, TInitGen);
2052 case PrepKind::Val:
2053 return reduce_fpass_arg(env, bc::CGetS { op.slot }, op.arg1, false);
2054 case PrepKind::Ref:
2055 return reduce_fpass_arg(env, bc::VGetS { op.slot }, op.arg1, true);
2059 void in(ISS& env, const bc::FPassV& op) {
2060 nothrow(env);
2061 switch (prepKind(env, op.arg1)) {
2062 case PrepKind::Unknown:
2063 popV(env);
2064 return push(env, TInitGen);
2065 case PrepKind::Val:
2066 return reduce_fpass_arg(env, bc::Unbox {}, op.arg1, false);
2067 case PrepKind::Ref:
2068 return reduce_fpass_arg(env, bc::Nop {}, op.arg1, true);
2072 void in(ISS& env, const bc::FPassR& op) {
2073 nothrow(env);
2074 if (fpiTop(env).kind == FPIKind::Builtin) {
2075 switch (prepKind(env, op.arg1)) {
2076 case PrepKind::Unknown:
2077 not_reached();
2078 case PrepKind::Val:
2079 return reduce(env, bc::UnboxR {});
2080 case PrepKind::Ref:
2081 return reduce(env, bc::BoxR {});
2085 auto const t1 = topT(env);
2086 if (t1.subtypeOf(TCell)) {
2087 return reduce_fpass_arg(env, bc::UnboxRNop {}, op.arg1, false);
2090 // If it's known to be a ref, this behaves like FPassV, except we need to do
2091 // it slightly differently to keep stack flavors correct.
2092 if (t1.subtypeOf(TRef)) {
2093 switch (prepKind(env, op.arg1)) {
2094 case PrepKind::Unknown:
2095 popV(env);
2096 return push(env, TInitGen);
2097 case PrepKind::Val:
2098 return reduce_fpass_arg(env, bc::UnboxR {}, op.arg1, false);
2099 case PrepKind::Ref:
2100 return reduce_fpass_arg(env, bc::BoxRNop {}, op.arg1, true);
2102 not_reached();
2105 // Here we don't know if it is going to be a cell or a ref.
2106 switch (prepKind(env, op.arg1)) {
2107 case PrepKind::Unknown: popR(env); return push(env, TInitGen);
2108 case PrepKind::Val: popR(env); return push(env, TInitCell);
2109 case PrepKind::Ref: popR(env); return push(env, TRef);
2113 void in(ISS& env, const bc::FPassVNop&) {
2114 push(env, popV(env));
2115 if (fpiTop(env).kind == FPIKind::Builtin) {
2116 return reduce(env, bc::Nop {});
2118 nothrow(env);
2121 void in(ISS& env, const bc::FPassC& op) {
2122 if (fpiTop(env).kind == FPIKind::Builtin) {
2123 return reduce(env, bc::Nop {});
2125 nothrow(env);
2128 void fpassCXHelper(ISS& env, int param, bool error) {
2129 auto const& fpi = fpiTop(env);
2130 if (fpi.kind == FPIKind::Builtin) {
2131 switch (prepKind(env, param)) {
2132 case PrepKind::Unknown:
2133 not_reached();
2134 case PrepKind::Ref:
2136 auto const& params = fpi.func->exactFunc()->params;
2137 if (param >= params.size() || params[param].mustBeRef) {
2138 if (error) {
2139 return reduce(env,
2140 bc::String { s_byRefError.get() },
2141 bc::Fatal { FatalOp::Runtime });
2142 } else {
2143 return reduce(env,
2144 bc::String { s_byRefWarn.get() },
2145 bc::Int { (int)ErrorMode::STRICT },
2146 bc::FCallBuiltin { 2, 2, s_trigger_error.get() },
2147 bc::PopC {});
2150 // fall through
2152 case PrepKind::Val:
2153 return reduce(env, bc::Nop {});
2155 not_reached();
2157 switch (prepKind(env, param)) {
2158 case PrepKind::Unknown: return;
2159 case PrepKind::Val: return reduce(env, bc::FPassC { param });
2160 case PrepKind::Ref: /* will warn/fatal at runtime */ return;
2164 void in(ISS& env, const bc::FPassCW& op) {
2165 fpassCXHelper(env, op.arg1, false);
2168 void in(ISS& env, const bc::FPassCE& op) {
2169 fpassCXHelper(env, op.arg1, true);
2172 void pushCallReturnType(ISS& env, Type&& ty) {
2173 if (ty == TBottom) {
2174 // The callee function never returns. It might throw, or loop forever.
2175 unreachable(env);
2177 return push(env, std::move(ty));
2180 const StaticString s_defined { "defined" };
2181 const StaticString s_function_exists { "function_exists" };
2183 void fcallKnownImpl(ISS& env, uint32_t numArgs) {
2184 auto const ar = fpiPop(env);
2185 always_assert(ar.func.hasValue());
2186 specialFunctionEffects(env, ar);
2188 if (ar.func->name()->isame(s_function_exists.get())) {
2189 handle_function_exists(env, numArgs, false);
2192 std::vector<Type> args(numArgs);
2193 for (auto i = uint32_t{0}; i < numArgs; ++i) {
2194 args[numArgs - i - 1] = popF(env);
2197 if (options.HardConstProp &&
2198 numArgs == 1 &&
2199 ar.func->name()->isame(s_defined.get())) {
2200 // If someone calls defined('foo') they probably want foo to be
2201 // defined normally; ie not a persistent constant.
2202 if (auto const v = tv(args[0])) {
2203 if (isStringType(v->m_type) &&
2204 !env.index.lookup_constant(env.ctx, v->m_data.pstr)) {
2205 env.collect.cnsMap[v->m_data.pstr].m_type = kDynamicConstant;
2210 auto ty = env.index.lookup_return_type(
2211 CallContext { env.ctx, args },
2212 *ar.func
2214 if (!ar.fallbackFunc) {
2215 pushCallReturnType(env, std::move(ty));
2216 return;
2218 auto ty2 = env.index.lookup_return_type(
2219 CallContext { env.ctx, args },
2220 *ar.fallbackFunc
2222 pushCallReturnType(env, union_of(std::move(ty), std::move(ty2)));
2225 void in(ISS& env, const bc::FCall& op) {
2226 auto const ar = fpiTop(env);
2227 if (ar.func && !ar.fallbackFunc) {
2228 switch (ar.kind) {
2229 case FPIKind::Unknown:
2230 case FPIKind::CallableArr:
2231 case FPIKind::ObjInvoke:
2232 not_reached();
2233 case FPIKind::Func:
2234 // Don't turn dynamic calls into static calls with functions that can
2235 // potentially touch the caller's frame. Such functions will fatal if
2236 // called dynamically and we want to preserve that behavior.
2237 if (!ar.func->mightAccessCallerFrame()) {
2238 return reduce(
2239 env,
2240 bc::FCallD { op.arg1, s_empty.get(), ar.func->name() }
2243 break;
2244 case FPIKind::Builtin:
2245 return finish_builtin(env, ar.func->exactFunc(), op.arg1, false);
2246 case FPIKind::Ctor:
2248 * Need to be wary of old-style ctors. We could get into the situation
2249 * where we're constructing class D extends B, and B has an old-style
2250 * ctor but D::B also exists. (So in this case we'll skip the
2251 * fcallKnownImpl stuff.)
2253 if (!ar.func->name()->isame(s_construct.get()) &&
2254 !ar.func->name()->isame(s_86ctor.get())) {
2255 break;
2257 // fallthrough
2258 case FPIKind::ObjMeth:
2259 case FPIKind::ClsMeth:
2260 if (ar.cls.hasValue() && ar.func->cantBeMagicCall()) {
2261 return reduce(
2262 env,
2263 bc::FCallD { op.arg1, ar.cls->name(), ar.func->name() }
2267 // If we didn't return a reduce above, we still can compute a
2268 // partially-known FCall effect with our res::Func.
2269 return fcallKnownImpl(env, op.arg1);
2273 for (auto i = uint32_t{0}; i < op.arg1; ++i) popF(env);
2274 fpiPop(env);
2275 specialFunctionEffects(env, ar);
2276 push(env, TInitGen);
2279 void in(ISS& env, const bc::FCallD& op) {
2280 auto const ar = fpiTop(env);
2281 if (ar.kind == FPIKind::Builtin) {
2282 return finish_builtin(env, ar.func->exactFunc(), op.arg1, false);
2284 if (ar.func) return fcallKnownImpl(env, op.arg1);
2285 specialFunctionEffects(env, ar);
2286 for (auto i = uint32_t{0}; i < op.arg1; ++i) popF(env);
2287 push(env, TInitGen);
2290 void in(ISS& env, const bc::FCallAwait& op) {
2291 in(env, bc::FCallD { op.arg1, op.str2, op.str3 });
2292 in(env, bc::UnboxRNop { });
2293 in(env, bc::Await { });
2295 env.flags.wasPEI = true;
2296 env.flags.canConstProp = false;
2299 void fcallArrayImpl(ISS& env, int arg) {
2300 auto const ar = fpiTop(env);
2301 if (ar.kind == FPIKind::Builtin) {
2302 return finish_builtin(env, ar.func->exactFunc(), arg, true);
2305 for (auto i = uint32_t{0}; i < arg; ++i) { popF(env); }
2306 fpiPop(env);
2307 specialFunctionEffects(env, ar);
2308 if (ar.func) {
2309 auto ty = env.index.lookup_return_type(env.ctx, *ar.func);
2310 if (!ar.fallbackFunc) {
2311 pushCallReturnType(env, std::move(ty));
2312 return;
2314 auto ty2 = env.index.lookup_return_type(env.ctx, *ar.fallbackFunc);
2315 pushCallReturnType(env, union_of(std::move(ty), std::move(ty2)));
2316 return;
2318 return push(env, TInitGen);
2321 void in(ISS& env, const bc::FCallArray& op) {
2322 fcallArrayImpl(env, 1);
2325 void in(ISS& env, const bc::FCallUnpack& op) {
2326 fcallArrayImpl(env, op.arg1);
2329 void in(ISS& env, const bc::CufSafeArray&) {
2330 popR(env); popC(env); popC(env);
2331 push(env, TArr);
2334 void in(ISS& env, const bc::CufSafeReturn&) {
2335 popR(env); popC(env); popC(env);
2336 push(env, TInitCell);
2339 void in(ISS& env, const bc::DecodeCufIter& op) {
2340 popC(env); // func
2341 env.propagate(op.target, env.state); // before iter is modifed
2344 void in(ISS& env, const bc::IterInit& op) {
2345 auto const t1 = popC(env);
2346 // Take the branch before setting locals if the iter is already
2347 // empty, but after popping. Similar for the other IterInits
2348 // below.
2349 freeIter(env, op.iter1);
2350 env.propagate(op.target, env.state);
2351 if (t1.subtypeOf(TArrE)) {
2352 nothrow(env);
2353 jmp_nofallthrough(env);
2354 return;
2356 auto ity = iter_types(t1);
2357 setLoc(env, op.loc3, ity.second);
2358 setIter(env, op.iter1, TrackedIter { std::move(ity) });
2361 void in(ISS& env, const bc::MIterInit& op) {
2362 popV(env);
2363 env.propagate(op.target, env.state);
2364 unbindLocalStatic(env, op.loc3);
2365 setLocRaw(env, op.loc3, TRef);
2368 void in(ISS& env, const bc::IterInitK& op) {
2369 auto const t1 = popC(env);
2370 freeIter(env, op.iter1);
2371 env.propagate(op.target, env.state);
2372 if (t1.subtypeOf(TArrE)) {
2373 nothrow(env);
2374 jmp_nofallthrough(env);
2375 return;
2377 auto ity = iter_types(t1);
2378 setLoc(env, op.loc3, ity.second);
2379 setLoc(env, op.loc4, ity.first);
2380 setIter(env, op.iter1, TrackedIter { std::move(ity) });
2383 void in(ISS& env, const bc::MIterInitK& op) {
2384 popV(env);
2385 env.propagate(op.target, env.state);
2386 unbindLocalStatic(env, op.loc3);
2387 setLocRaw(env, op.loc3, TRef);
2388 setLoc(env, op.loc4, TInitCell);
2391 void in(ISS& env, const bc::WIterInit& op) {
2392 popC(env);
2393 env.propagate(op.target, env.state);
2394 // WIter* instructions may leave the value locals as either refs
2395 // or cells, depending whether the rhs of the assignment was a
2396 // ref.
2397 setLocRaw(env, op.loc3, TInitGen);
2400 void in(ISS& env, const bc::WIterInitK& op) {
2401 popC(env);
2402 env.propagate(op.target, env.state);
2403 setLocRaw(env, op.loc3, TInitGen);
2404 setLoc(env, op.loc4, TInitCell);
2407 void in(ISS& env, const bc::IterNext& op) {
2408 auto const curLoc3 = locRaw(env, op.loc3);
2410 match<void>(
2411 env.state.iters[op.iter1],
2412 [&] (UnknownIter) {
2413 setLoc(env, op.loc3, TInitCell);
2415 [&] (const TrackedIter& ti) {
2416 setLoc(env, op.loc3, ti.kv.second);
2419 env.propagate(op.target, env.state);
2421 freeIter(env, op.iter1);
2422 if (curLoc3.subtypeOf(TInitCell)) setLocRaw(env, op.loc3, curLoc3);
2425 void in(ISS& env, const bc::MIterNext& op) {
2426 env.propagate(op.target, env.state);
2427 unbindLocalStatic(env, op.loc3);
2428 setLocRaw(env, op.loc3, TRef);
2431 void in(ISS& env, const bc::IterNextK& op) {
2432 auto const curLoc3 = locRaw(env, op.loc3);
2433 auto const curLoc4 = locRaw(env, op.loc4);
2435 match<void>(
2436 env.state.iters[op.iter1],
2437 [&] (UnknownIter) {
2438 setLoc(env, op.loc3, TInitCell);
2439 setLoc(env, op.loc4, TInitCell);
2441 [&] (const TrackedIter& ti) {
2442 setLoc(env, op.loc3, ti.kv.second);
2443 setLoc(env, op.loc4, ti.kv.first);
2446 env.propagate(op.target, env.state);
2448 freeIter(env, op.iter1);
2449 if (curLoc3.subtypeOf(TInitCell)) setLocRaw(env, op.loc3, curLoc3);
2450 if (curLoc4.subtypeOf(TInitCell)) setLocRaw(env, op.loc4, curLoc4);
2453 void in(ISS& env, const bc::MIterNextK& op) {
2454 env.propagate(op.target, env.state);
2455 unbindLocalStatic(env, op.loc3);
2456 setLocRaw(env, op.loc3, TRef);
2457 setLoc(env, op.loc4, TInitCell);
2460 void in(ISS& env, const bc::WIterNext& op) {
2461 env.propagate(op.target, env.state);
2462 setLocRaw(env, op.loc3, TInitGen);
2465 void in(ISS& env, const bc::WIterNextK& op) {
2466 env.propagate(op.target, env.state);
2467 setLocRaw(env, op.loc3, TInitGen);
2468 setLoc(env, op.loc4, TInitCell);
2471 void in(ISS& env, const bc::IterFree& op) {
2472 nothrow(env);
2473 freeIter(env, op.iter1);
2475 void in(ISS& env, const bc::MIterFree& op) {
2476 nothrow(env);
2477 freeIter(env, op.iter1);
2479 void in(ISS& env, const bc::CIterFree& op) {
2480 nothrow(env);
2481 freeIter(env, op.iter1);
2484 void in(ISS& env, const bc::IterBreak& op) {
2485 for (auto& kv : op.iterTab) freeIter(env, kv.second);
2486 env.propagate(op.target, env.state);
2490 * Any include/require (or eval) op kills all locals, and private properties.
2492 * We don't need to do anything for collect.publicStatics because we'll analyze
2493 * the included pseudo-main separately and see any effects it may have on
2494 * public statics.
2496 void inclOpImpl(ISS& env) {
2497 popC(env);
2498 killLocals(env);
2499 killThisProps(env);
2500 killSelfProps(env);
2501 mayUseVV(env);
2502 push(env, TInitCell);
2505 void in(ISS& env, const bc::Incl&) { inclOpImpl(env); }
2506 void in(ISS& env, const bc::InclOnce&) { inclOpImpl(env); }
2507 void in(ISS& env, const bc::Req&) { inclOpImpl(env); }
2508 void in(ISS& env, const bc::ReqOnce&) { inclOpImpl(env); }
2509 void in(ISS& env, const bc::ReqDoc&) { inclOpImpl(env); }
2510 void in(ISS& env, const bc::Eval&) { inclOpImpl(env); }
2512 void in(ISS& env, const bc::DefFunc&) {}
2513 void in(ISS& env, const bc::DefCls&) {}
2514 void in(ISS& env, const bc::DefClsNop&) {}
2515 void in(ISS& env, const bc::AliasCls&) {
2516 popC(env);
2517 push(env, TBool);
2520 void in(ISS& env, const bc::DefCns& op) {
2521 auto const t = popC(env);
2522 if (options.HardConstProp) {
2523 auto const v = tv(t);
2524 auto const val = v && tvAsCVarRef(&*v).isAllowedAsConstantValue() ?
2525 *v : make_tv<KindOfUninit>();
2526 auto const res = env.collect.cnsMap.emplace(op.str1, val);
2527 if (!res.second) {
2528 if (res.first->second.m_type == kReadOnlyConstant) {
2529 // we only saw a read of this constant
2530 res.first->second = val;
2531 } else {
2532 // more than one definition in this function
2533 res.first->second.m_type = kDynamicConstant;
2537 push(env, TBool);
2540 void in(ISS& env, const bc::DefTypeAlias&) {}
2542 void in(ISS& env, const bc::This&) {
2543 if (thisAvailable(env)) {
2544 return reduce(env, bc::BareThis { BareThisOp::NeverNull });
2546 auto const ty = thisType(env);
2547 push(env, ty ? *ty : TObj);
2548 setThisAvailable(env);
2551 void in(ISS& env, const bc::LateBoundCls& op) {
2552 auto const ty = selfCls(env);
2553 putClsRefSlot(env, op.slot, ty ? *ty : TCls);
2556 void in(ISS& env, const bc::CheckThis&) {
2557 if (thisAvailable(env)) {
2558 reduce(env, bc::Nop {});
2560 setThisAvailable(env);
2563 void in(ISS& env, const bc::BareThis& op) {
2564 if (thisAvailable(env)) {
2565 if (op.subop1 != BareThisOp::NeverNull) {
2566 return reduce(env, bc::BareThis { BareThisOp::NeverNull });
2570 auto const ty = thisType(env);
2571 switch (op.subop1) {
2572 case BareThisOp::Notice:
2573 break;
2574 case BareThisOp::NoNotice:
2575 nothrow(env);
2576 break;
2577 case BareThisOp::NeverNull:
2578 nothrow(env);
2579 setThisAvailable(env);
2580 return push(env, ty ? *ty : TObj);
2583 push(env, ty ? opt(*ty) : TOptObj);
2586 void in(ISS& env, const bc::InitThisLoc& op) {
2587 setLocRaw(env, op.loc1, TCell);
2590 folly::Optional<Cell> staticLocHelper(ISS& env, LocalId l, Type init) {
2591 unbindLocalStatic(env, l);
2592 setLocRaw(env, l, TRef);
2593 bindLocalStatic(env, l, std::move(init));
2594 if (!env.ctx.func->isClosureBody &&
2595 env.collect.localStaticTypes.size() > l) {
2596 auto t = env.collect.localStaticTypes[l];
2597 if (auto v = tv(t)) {
2598 useLocalStatic(env, l);
2599 setLocRaw(env, l, t);
2600 return v;
2603 return folly::none;
2606 void in(ISS& env, const bc::StaticLoc& op) {
2607 if (auto const v = staticLocHelper(env, op.loc1, TBottom)) {
2608 return reduce(env,
2609 gen_constant(*v),
2610 bc::SetL { op.loc1 }, bc::PopC {},
2611 bc::True {});
2613 push(env, TBool);
2616 void in(ISS& env, const bc::StaticLocInit& op) {
2617 if (staticLocHelper(env, op.loc1, topC(env))) {
2618 return reduce(env, bc::SetL { op.loc1 }, bc::PopC {});
2620 popC(env);
2624 * Amongst other things, we use this to mark units non-persistent.
2626 void in(ISS& env, const bc::OODeclExists& op) {
2627 auto flag = popC(env);
2628 auto name = popC(env);
2629 push(env, [&] {
2630 if (!name.strictSubtypeOf(TStr)) return TBool;
2631 auto const v = tv(name);
2632 if (!v) return TBool;
2633 auto rcls = env.index.resolve_class(env.ctx, v->m_data.pstr);
2634 if (!rcls || !rcls->cls()) return TBool;
2635 auto const mayExist = [&] () -> bool {
2636 switch (op.subop1) {
2637 case OODeclExistsOp::Class:
2638 return !(rcls->cls()->attrs & (AttrInterface | AttrTrait));
2639 case OODeclExistsOp::Interface:
2640 return rcls->cls()->attrs & AttrInterface;
2641 case OODeclExistsOp::Trait:
2642 return rcls->cls()->attrs & AttrTrait;
2644 not_reached();
2645 }();
2646 auto unit = rcls->cls()->unit;
2647 auto canConstProp = [&] {
2648 // Its generally not safe to constprop this, because of
2649 // autoload. We're safe if its part of systemlib, or a
2650 // superclass of the current context.
2651 if (is_systemlib_part(*unit)) return true;
2652 if (!env.ctx.cls) return false;
2653 auto thisClass = env.index.resolve_class(env.ctx.cls);
2654 return thisClass.subtypeOf(*rcls);
2656 if (canConstProp()) {
2657 constprop(env);
2658 return mayExist ? TTrue : TFalse;
2660 unit->persistent.store(false, std::memory_order_relaxed);
2661 // At this point, if it mayExist, we still don't know that it
2662 // *does* exist, but if not we know that it either doesn't
2663 // exist, or it doesn't have the right type.
2664 return mayExist ? TBool : TFalse;
2665 } ());
2668 void in(ISS& env, const bc::VerifyParamType& op) {
2669 locAsCell(env, op.loc1);
2670 if (!options.HardTypeHints) return;
2673 * In HardTypeHints mode, we assume that if this opcode doesn't
2674 * throw, the parameter was of the specified type (although it may
2675 * have been a Ref if the parameter was by reference).
2677 * The env.setLoc here handles dealing with a parameter that was
2678 * already known to be a reference.
2680 * NB: VerifyParamType of a reference parameter can kill any
2681 * references if it re-enters, even if Option::HardTypeHints is
2682 * on.
2684 auto const constraint = env.ctx.func->params[op.loc1].typeConstraint;
2685 if (constraint.hasConstraint() && !constraint.isTypeVar() &&
2686 !constraint.isTypeConstant()) {
2687 FTRACE(2, " {}\n", constraint.fullName());
2688 auto t = env.index.lookup_constraint(env.ctx, constraint);
2689 if (t.subtypeOf(TBottom)) unreachable(env);
2690 setLoc(env, op.loc1, std::move(t));
2694 void in(ISS& env, const bc::VerifyRetTypeV& op) {}
2696 void in(ISS& env, const bc::VerifyRetTypeC& op) {
2697 auto const constraint = env.ctx.func->retTypeConstraint;
2698 auto const stackT = topC(env);
2700 // If there is no return type constraint, or if the return type
2701 // constraint is a typevar, or if the top of stack is the same
2702 // or a subtype of the type constraint, then this is a no-op.
2703 if (env.index.satisfies_constraint(env.ctx, stackT, constraint)) {
2704 reduce(env, bc::Nop {});
2705 return;
2708 // If HardReturnTypeHints is false OR if the constraint is soft,
2709 // then there are no optimizations we can safely do here, so
2710 // just leave the top of stack as is.
2711 if (!options.HardReturnTypeHints || constraint.isSoft()) {
2712 return;
2715 // If we reach here, then HardReturnTypeHints is true AND the constraint
2716 // is not soft. We can safely assume that either VerifyRetTypeC will
2717 // throw or it will produce a value whose type is compatible with the
2718 // return type constraint.
2719 auto tcT =
2720 remove_uninit(env.index.lookup_constraint(env.ctx, constraint));
2722 if (tcT.subtypeOf(TBottom)) {
2723 unreachable(env);
2724 return;
2727 // Below we compute retT, which is a rough conservative approximate of the
2728 // intersection of stackT and tcT.
2729 // TODO(4441939): We could do better if we had an intersect_of() function
2730 // that provided a formal way to compute the intersection of two Types.
2732 // If tcT could be an interface or trait, we upcast it to TObj/TOptObj.
2733 // Why? Because we want uphold the invariant that we only refine return
2734 // types and never widen them, and if we allow tcT to be an interface then
2735 // it's possible for violations of this invariant to arise. For an example,
2736 // see "hphp/test/slow/hhbbc/return-type-opt-bug.php".
2737 // Note: It's safe to use TObj/TOptObj because lookup_constraint() only
2738 // returns classes or interfaces or traits (it never returns something that
2739 // could be an enum or type alias) and it never returns anything that could
2740 // be a "magic" interface that supports non-objects. (For traits the return
2741 // typehint will always throw at run time, so it's safe to use TObj/TOptObj.)
2742 if (is_specialized_obj(tcT) && dobj_of(tcT).cls.couldBeInterfaceOrTrait()) {
2743 tcT = is_opt(tcT) ? TOptObj : TObj;
2745 // If stackT is a subtype of tcT, use stackT. Otherwise, if tc is an opt
2746 // type and stackT cannot be InitNull, then we can safely use unopt(tcT).
2747 // In all other cases, use tcT.
2748 auto retT = stackT.subtypeOf(tcT) ? stackT :
2749 is_opt(tcT) && !stackT.couldBe(TInitNull) ? unopt(tcT) :
2750 tcT;
2752 // Update the top of stack with the rough conservative approximate of the
2753 // intersection of stackT and tcT
2754 popC(env);
2755 push(env, std::move(retT));
2758 // These only occur in traits, so we don't need to do better than
2759 // this.
2760 void in(ISS& env, const bc::Self& op) {
2761 auto self = selfClsExact(env);
2762 putClsRefSlot(env, op.slot, self ? *self : TCls);
2765 void in(ISS& env, const bc::Parent& op) {
2766 auto parent = parentClsExact(env);
2767 putClsRefSlot(env, op.slot, parent ? *parent : TCls);
2770 void in(ISS& env, const bc::CreateCl& op) {
2771 auto const nargs = op.arg1;
2772 auto const clsPair = env.index.resolve_closure_class(env.ctx, op.arg2);
2775 * Every closure should have a unique allocation site, but we may see it
2776 * multiple times in a given round of analyzing this function. Each time we
2777 * may have more information about the used variables; the types should only
2778 * possibly grow. If it's already there we need to merge the used vars in
2779 * with what we saw last time.
2781 if (nargs) {
2782 std::vector<Type> usedVars(nargs);
2783 for (auto i = uint32_t{0}; i < nargs; ++i) {
2784 usedVars[nargs - i - 1] = popT(env);
2786 merge_closure_use_vars_into(
2787 env.collect.closureUseTypes,
2788 clsPair.second,
2789 usedVars
2793 // Closure classes can be cloned and rescoped at runtime, so it's not safe to
2794 // assert the exact type of closure objects. The best we can do is assert
2795 // that it's a subclass of Closure.
2796 auto const closure = env.index.resolve_class(env.ctx, s_Closure.get());
2797 always_assert(closure.hasValue());
2799 return push(env, subObj(*closure));
2802 void in(ISS& env, const bc::CreateCont& op) {
2803 // First resume is always next() which pushes null.
2804 push(env, TInitNull);
2807 void in(ISS& env, const bc::ContEnter&) { popC(env); push(env, TInitCell); }
2808 void in(ISS& env, const bc::ContRaise&) { popC(env); push(env, TInitCell); }
2810 void in(ISS& env, const bc::Yield&) {
2811 popC(env);
2812 push(env, TInitCell);
2815 void in(ISS& env, const bc::YieldK&) {
2816 popC(env);
2817 popC(env);
2818 push(env, TInitCell);
2821 void in(ISS& env, const bc::ContAssignDelegate&) {
2822 popC(env);
2825 void in(ISS& env, const bc::ContEnterDelegate&) {
2826 popC(env);
2829 void in(ISS& env, const bc::YieldFromDelegate&) {
2830 push(env, TInitCell);
2833 void in(ISS& env, const bc::ContUnsetDelegate&) {}
2835 void in(ISS& env, const bc::ContCheck&) {}
2836 void in(ISS& env, const bc::ContValid&) { push(env, TBool); }
2837 void in(ISS& env, const bc::ContStarted&) { push(env, TBool); }
2838 void in(ISS& env, const bc::ContKey&) { push(env, TInitCell); }
2839 void in(ISS& env, const bc::ContCurrent&) { push(env, TInitCell); }
2840 void in(ISS& env, const bc::ContGetReturn&) { push(env, TInitCell); }
2842 void pushTypeFromWH(ISS& env, const Type t) {
2843 if (!t.couldBe(TObj)) {
2844 // These opcodes require an object descending from WaitHandle.
2845 // Exceptions will be thrown for any non-object.
2846 push(env, TBottom);
2847 unreachable(env);
2848 return;
2850 // If we aren't even sure this is a wait handle, there's nothing we can
2851 // infer here. (This can happen if a user declares a class with a
2852 // getWaitHandle method that returns non-WaitHandle garbage.)
2853 if (!t.subtypeOf(TObj) || !is_specialized_wait_handle(t)) {
2854 return push(env, TInitCell);
2857 auto inner = wait_handle_inner(t);
2858 if (inner.subtypeOf(TBottom)) {
2859 // If it's a WaitH<Bottom>, we know it's going to throw an exception, and
2860 // the fallthrough code is not reachable.
2861 push(env, TBottom);
2862 unreachable(env);
2863 return;
2866 push(env, std::move(inner));
2869 void in(ISS& env, const bc::WHResult&) {
2870 pushTypeFromWH(env, popC(env));
2873 void in(ISS& env, const bc::Await&) {
2874 pushTypeFromWH(env, popC(env));
2877 void in(ISS& env, const bc::IncStat&) {}
2879 void in(ISS& env, const bc::Idx&) {
2880 popC(env); popC(env); popC(env);
2881 push(env, TInitCell);
2884 void in(ISS& env, const bc::ArrayIdx&) {
2885 popC(env); popC(env); popC(env);
2886 push(env, TInitCell);
2889 void in(ISS& env, const bc::CheckProp&) {
2890 if (env.ctx.cls->attrs & AttrNoOverride) {
2891 return reduce(env, bc::False {});
2893 nothrow(env);
2894 push(env, TBool);
2897 void in(ISS& env, const bc::InitProp& op) {
2898 auto const t = topC(env);
2899 switch (op.subop2) {
2900 case InitPropOp::Static:
2901 mergeSelfProp(env, op.str1, t);
2902 if (auto c = env.collect.publicStatics) {
2903 auto const cls = selfClsExact(env);
2904 always_assert(!!cls);
2905 c->merge(env.ctx, *cls, sval(op.str1), t);
2907 break;
2908 case InitPropOp::NonStatic:
2909 mergeThisProp(env, op.str1, t);
2910 break;
2912 if (auto const v = tv(t)) {
2913 for (auto& prop : env.ctx.func->cls->properties) {
2914 if (prop.name == op.str1) {
2915 ITRACE(1, "InitProp: {} = {}\n", op.str1, show(t));
2916 prop.val = *v;
2917 if (op.subop2 == InitPropOp::Static &&
2918 !env.collect.publicStatics &&
2919 !env.index.frozen()) {
2920 env.index.fixup_public_static(env.ctx.func->cls, prop.name, t);
2922 return reduce(env, bc::PopC {});
2926 popC(env);
2929 void in(ISS& env, const bc::Silence& op) {
2930 nothrow(env);
2931 switch (op.subop2) {
2932 case SilenceOp::Start:
2933 setLoc(env, op.loc1, TInt);
2934 break;
2935 case SilenceOp::End:
2936 break;
2940 void in(ISS& emv, const bc::VarEnvDynCall&) {}
2944 //////////////////////////////////////////////////////////////////////
2946 void dispatch(ISS& env, const Bytecode& op) {
2947 #define O(opcode, ...) case Op::opcode: interp_step::in(env, op.opcode); return;
2948 switch (op.op) { OPCODES }
2949 #undef O
2950 not_reached();
2953 //////////////////////////////////////////////////////////////////////
2955 template<class Iterator, class... Args>
2956 void group(ISS& env, Iterator& it, Args&&... args) {
2957 FTRACE(2, " {}\n", [&]() -> std::string {
2958 auto ret = std::string{};
2959 for (auto i = size_t{0}; i < sizeof...(Args); ++i) {
2960 ret += " " + show(env.ctx.func, it[i]);
2961 if (i != sizeof...(Args) - 1) ret += ';';
2963 return ret;
2964 }());
2965 it += sizeof...(Args);
2966 return interp_step::group(env, std::forward<Args>(args)...);
2969 template<class Iterator>
2970 void interpStep(ISS& env, Iterator& it, Iterator stop) {
2972 * During the analysis phase, we analyze some common bytecode
2973 * patterns involving conditional jumps as groups to be able to
2974 * add additional information to the type environment depending on
2975 * whether the branch is taken or not.
2977 auto const o1 = it->op;
2978 auto const o2 = it + 1 != stop ? it[1].op : Op::Nop;
2979 auto const o3 = it + 1 != stop &&
2980 it + 2 != stop ? it[2].op : Op::Nop;
2982 switch (o1) {
2983 case Op::CGetL:
2984 switch (o2) {
2985 case Op::JmpZ: return group(env, it, it[0].CGetL, it[1].JmpZ);
2986 case Op::JmpNZ: return group(env, it, it[0].CGetL, it[1].JmpNZ);
2987 case Op::InstanceOfD:
2988 switch (o3) {
2989 case Op::JmpZ:
2990 return group(env, it, it[0].CGetL, it[1].InstanceOfD, it[2].JmpZ);
2991 case Op::JmpNZ:
2992 return group(env, it, it[0].CGetL, it[1].InstanceOfD, it[2].JmpNZ);
2993 default: break;
2995 break;
2996 case Op::FPushObjMethodD:
2997 return group(env, it, it[0].CGetL, it[1].FPushObjMethodD);
2998 default: break;
3000 break;
3001 case Op::IsTypeL:
3002 switch (o2) {
3003 case Op::JmpZ: return group(env, it, it[0].IsTypeL, it[1].JmpZ);
3004 case Op::JmpNZ: return group(env, it, it[0].IsTypeL, it[1].JmpNZ);
3005 default: break;
3007 break;
3008 case Op::IsUninit:
3009 switch (o2) {
3010 case Op::JmpZ: return group(env, it, it[0].IsUninit, it[1].JmpZ);
3011 case Op::JmpNZ: return group(env, it, it[0].IsUninit, it[1].JmpNZ);
3012 default: break;
3014 break;
3015 case Op::Dup:
3016 switch (o2) {
3017 case Op::IsTypeC:
3018 switch (o3) {
3019 case Op::JmpZ:
3020 return group(env, it, it[0].Dup, it[1].IsTypeC, it[2].JmpZ);
3021 case Op::JmpNZ:
3022 return group(env, it, it[0].Dup, it[1].IsTypeC, it[2].JmpNZ);
3023 default: break;
3025 break;
3026 default: break;
3028 break;
3029 case Op::MemoGet:
3030 switch (o2) {
3031 case Op::IsUninit:
3032 switch (o3) {
3033 case Op::JmpZ:
3034 return group(env, it, it[0].MemoGet, it[1].IsUninit, it[2].JmpZ);
3035 case Op::JmpNZ:
3036 return group(env, it, it[0].MemoGet, it[1].IsUninit, it[2].JmpNZ);
3037 default: break;
3039 break;
3040 default: break;
3042 break;
3043 default: break;
3046 FTRACE(2, " {}\n", show(env.ctx.func, *it));
3047 dispatch(env, *it++);
3050 template<class Iterator>
3051 StepFlags interpOps(Interp& interp,
3052 Iterator& iter, Iterator stop,
3053 PropagateFn propagate) {
3054 auto flags = StepFlags{};
3055 ISS env { interp, flags, propagate };
3057 // If there are factored edges, make a copy of the state (except
3058 // stacks) in case we need to propagate across factored exits (if
3059 // it's a PEI).
3060 auto const stateBefore = interp.blk->factoredExits.empty()
3061 ? State{}
3062 : without_stacks(interp.state);
3064 auto const numPushed = iter->numPush();
3065 interpStep(env, iter, stop);
3067 auto outputs_constant = [&] {
3068 auto elems = &interp.state.stack.back();
3069 for (auto i = size_t{0}; i < numPushed; ++i, --elems) {
3070 if (!tv(elems->type)) return false;
3072 return true;
3075 if (options.ConstantProp && flags.canConstProp && outputs_constant()) {
3076 auto elems = &interp.state.stack.back();
3077 for (auto i = size_t{0}; i < numPushed; ++i, --elems) {
3078 auto& ty = elems->type;
3079 ty = from_cell(*tv(ty));
3081 if (flags.wasPEI) {
3082 FTRACE(2, " nothrow (due to constprop)\n");
3083 flags.wasPEI = false;
3087 if (flags.wasPEI) {
3088 FTRACE(2, " PEI.\n");
3089 for (auto factored : interp.blk->factoredExits) {
3090 propagate(factored, stateBefore);
3093 return flags;
3096 //////////////////////////////////////////////////////////////////////
3098 RunFlags run(Interp& interp, PropagateFn propagate) {
3099 SCOPE_EXIT {
3100 FTRACE(2, "out {}\n",
3101 state_string(*interp.ctx.func, interp.state, interp.collect));
3104 auto ret = RunFlags {};
3105 auto const stop = end(interp.blk->hhbcs);
3106 auto iter = begin(interp.blk->hhbcs);
3107 while (iter != stop) {
3108 auto const flags = interpOps(interp, iter, stop, propagate);
3109 if (flags.usedLocalStatics) {
3110 if (!ret.usedLocalStatics) {
3111 ret.usedLocalStatics = std::move(flags.usedLocalStatics);
3112 } else {
3113 for (auto& elm : *flags.usedLocalStatics) {
3114 ret.usedLocalStatics->insert(std::move(elm));
3119 if (interp.state.unreachable) {
3120 FTRACE(2, " <bytecode fallthrough is unreachable>\n");
3121 return ret;
3124 switch (flags.jmpFlag) {
3125 case StepFlags::JmpFlags::Taken:
3126 FTRACE(2, " <took branch; no fallthrough>\n");
3127 return ret;
3128 case StepFlags::JmpFlags::Fallthrough:
3129 case StepFlags::JmpFlags::Either:
3130 break;
3132 if (flags.returned) {
3133 FTRACE(2, " returned {}\n", show(*flags.returned));
3134 always_assert(iter == stop);
3135 always_assert(interp.blk->fallthrough == NoBlockId);
3136 ret.returned = flags.returned;
3137 return ret;
3141 FTRACE(2, " <end block>\n");
3142 if (interp.blk->fallthrough != NoBlockId) {
3143 propagate(interp.blk->fallthrough, interp.state);
3145 return ret;
3148 StepFlags step(Interp& interp, const Bytecode& op) {
3149 auto flags = StepFlags{};
3150 auto noop = [] (BlockId, const State&) {};
3151 ISS env { interp, flags, noop };
3152 dispatch(env, op);
3153 return flags;
3156 void default_dispatch(ISS& env, const Bytecode& op) {
3157 dispatch(env, op);
3160 //////////////////////////////////////////////////////////////////////