Use shape values at runtime for position data
[hiphop-php.git] / hphp / hhbbc / interp-minstr.cpp
blobe0f2d40eaf56efce00f39546957e8c3a6c48b301
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 <vector>
19 #include <algorithm>
20 #include <string>
21 #include <utility>
23 #include <folly/Optional.h>
24 #include <folly/Format.h>
26 #include "hphp/util/trace.h"
28 #include "hphp/hhbbc/interp-internal.h"
29 #include "hphp/hhbbc/optimize.h"
30 #include "hphp/hhbbc/type-ops.h"
32 namespace HPHP { namespace HHBBC {
34 namespace {
36 //////////////////////////////////////////////////////////////////////
38 // Represents the "effectful"-ness of a particular member instruction
39 // operation. This includes operations which are visible outside of
40 // the function (writing to a static property), but also potential
41 // throwing. Many different aspects of a member instruction operation
42 // can have effects, so we combine the effects from the various
43 // portions and use the final result to determine how to mark the
44 // bytecode.
45 enum class Effects {
46 None, // Effect-free
47 SideEffect, // Cannot throw, but has some side-effect
48 Throws, // Might throw an exception
49 AlwaysThrows // Always throws an exception
52 // Combine two effects, in the sense that either of them might happen
53 // (therefore AlwaysThrows becomes just Throws if mixed with something
54 // else).
55 Effects unionEffects(Effects e1, Effects e2) {
56 if (e1 == e2) return e1;
57 if (e1 == Effects::Throws || e2 == Effects::Throws ||
58 e1 == Effects::AlwaysThrows || e2 == Effects::AlwaysThrows) {
59 return Effects::Throws;
61 return Effects::SideEffect;
64 // There's no good default for Effects when you want to union them
65 // together. Instead you want to start with the first Effect you
66 // see. This keeps the Effect from taking a value until you union one
67 // into it.
68 using OptEffects = folly::Optional<Effects>;
70 OptEffects unionEffects(OptEffects e1, Effects e2) {
71 if (!e1) return e2;
72 return unionEffects(*e1, e2);
75 //////////////////////////////////////////////////////////////////////
78 * A note about bases.
80 * Generally type inference needs to know two kinds of things about the base to
81 * handle effects on tracked locations:
83 * - Could the base be a location we're tracking deeper structure on, so the
84 * next operation actually affects something inside of it. For example,
85 * could the base be an object with the same type as $this, or an array in a
86 * local variable.
88 * - Could the base be something (regardless of type) that is inside one of
89 * the things we're tracking. I.e., the base might be whatever (an array or
90 * a bool or something), but living inside a property inside an object with
91 * the same type as $this, or living inside of an array in the local frame.
93 * The first cases apply because final operations are going to directly affect
94 * the type of these elements. The second case is because member operations may
95 * change the base at each step if it is a defining instruction.
97 * Note that both of these cases can apply to the same base in some cases: you
98 * might have an object property on $this that could be an object of the type of
99 * $this.
102 //////////////////////////////////////////////////////////////////////
104 bool couldBeThisObj(ISS& env, const Base& b) {
105 if (b.loc == BaseLoc::This) return true;
106 auto const thisTy = thisTypeFromContext(env.index, env.ctx);
107 return b.type.couldBe(thisTy ? *thisTy : TObj);
110 bool mustBeThisObj(ISS& env, const Base& b) {
111 if (b.loc == BaseLoc::This) return true;
112 if (auto const ty = thisTypeFromContext(env.index, env.ctx)) {
113 return b.type.subtypeOf(*ty);
115 return false;
118 bool mustBeInLocal(const Base& b) {
119 return b.loc == BaseLoc::Local;
122 bool mustBeInStack(const Base& b) {
123 return b.loc == BaseLoc::Stack;
126 bool mustBeInStatic(const Base& b) {
127 return b.loc == BaseLoc::StaticProp;
130 //////////////////////////////////////////////////////////////////////
132 // Base locations that only occur at the start of a minstr sequence.
133 bool isInitialBaseLoc(BaseLoc loc) {
134 return
135 loc == BaseLoc::Local ||
136 loc == BaseLoc::Stack ||
137 loc == BaseLoc::StaticProp ||
138 loc == BaseLoc::This ||
139 loc == BaseLoc::Global;
142 // Base locations that only occur after the start of a minstr sequence.
143 bool isDimBaseLoc(BaseLoc loc) {
144 return loc == BaseLoc::Elem || loc == BaseLoc::Prop;
147 //////////////////////////////////////////////////////////////////////
150 * Update the current base via array_like_set, and return whether the
151 * operation can possibly throw.
153 TriBool array_do_set(ISS& env,
154 const Type& key,
155 const Type& value) {
156 auto& base = env.collect.mInstrState.base.type;
157 assertx(base.couldBe(BArrLike));
159 // array_like_set requires the key to be a TArrKey already. If it's
160 // not guaranteed to be, we assume we could throw.
161 if (!key.couldBe(BArrKey)) return TriBool::Yes;
162 auto const validKey = key.subtypeOf(BArrKey);
164 auto set = array_like_set(
165 base,
166 validKey ? key : intersection_of(key, TArrKey),
167 value
169 // Check for the presence of TArrLike rather than Bottom, since the
170 // base may have contained more than just TArrLike to begin with.
171 if (!set.first.couldBe(BArrLike)) {
172 assertx(set.second);
173 return TriBool::Yes;
176 base = std::move(set.first);
177 return maybeOrNo(set.second || !validKey);
181 * Look up the specified key via array_like_elem and return the
182 * associated value, along with whether the lookup can throw and if
183 * the value is definitely present.
185 * If excludeKeyset is true, we remove any TKeyset types from the
186 * array before doing the lookup. This is useful for the cases where a
187 * keyset would fatal, but other types wouldn't.
189 struct ElemResult {
190 Type elem;
191 TriBool throws;
192 bool present;
194 ElemResult array_do_elem(ISS& env, const Type& key,
195 bool excludeKeyset = false) {
196 auto const& base = env.collect.mInstrState.base.type;
197 assertx(base.couldBe(BArrLike));
199 // If the key can't possibly be good, or the array is entirely a
200 // keyset (and we exclude keysets), we'll always throw.
201 if (!key.couldBe(BArrKey)) return ElemResult { TBottom, TriBool::Yes, false };
202 if (excludeKeyset && base.subtypeAmong(BKeyset, BArrLike)) {
203 return ElemResult { TBottom, TriBool::Yes, false };
206 // Otherwise remove the problematic parts of the key or value. If we
207 // have to remove anything, assume we could possibly throw.
208 auto const validKey = key.subtypeOf(BArrKey);
209 auto const validArr = !excludeKeyset || base.subtypeAmong(BKVish, BArrLike);
210 auto r = array_like_elem(
211 validArr ? base : intersection_of(base, TKVish),
212 validKey ? key : intersection_of(key, TArrKey)
215 return ElemResult {
216 std::move(r.first),
217 maybeOrNo(!validKey || !validArr),
218 r.second
223 * Update the current base via array_like_newelem, and return whether
224 * the newelem can throw.
226 TriBool array_do_newelem(ISS& env, const Type& value) {
227 auto& base = env.collect.mInstrState.base.type;
228 assertx(base.couldBe(BArrLike));
230 auto update = array_like_newelem(base, value);
231 // Check for the presence of TArrLike rather than Bottom, since the
232 // base may have contained more than just TArrLike to begin with.
233 if (!update.first.couldBe(BArrLike)) {
234 assertx(update.second);
235 return TriBool::Yes;
238 base = std::move(update.first);
239 return maybeOrNo(update.second);
242 //////////////////////////////////////////////////////////////////////
244 void setLocalForBase(ISS& env, Type ty, LocalId firstKeyLoc) {
245 assertx(mustBeInLocal(env.collect.mInstrState.base));
246 if (env.collect.mInstrState.base.locLocal == NoLocalId) {
247 return killLocals(env);
249 FTRACE(4, " ${} := {}\n",
250 env.collect.mInstrState.base.locName
251 ? env.collect.mInstrState.base.locName->data()
252 : "$<unnamed>",
253 show(ty)
255 setLoc(
256 env,
257 env.collect.mInstrState.base.locLocal,
258 std::move(ty),
259 firstKeyLoc
263 void setStackForBase(ISS& env, Type ty) {
264 assertx(mustBeInStack(env.collect.mInstrState.base));
266 auto const locSlot = env.collect.mInstrState.base.locSlot;
267 FTRACE(4, " stk[{:02}] := {}\n", locSlot, show(ty));
268 assertx(locSlot < env.state.stack.size());
270 env.state.stack[locSlot] = StackElem { std::move(ty), NoLocalId };
273 void setStaticForBase(ISS& env, Type ty) {
274 auto const& base = env.collect.mInstrState.base;
275 assertx(mustBeInStatic(base));
277 auto const nameTy = base.locName ? sval(base.locName) : TStr;
278 FTRACE(
279 4, " ({})::$({}) |= {}\n",
280 show(base.locTy), show(nameTy), show(ty)
283 env.index.merge_static_type(
284 env.ctx,
285 env.collect.publicSPropMutations,
286 env.collect.props,
287 base.locTy,
288 nameTy,
289 remove_uninit(std::move(ty))
293 // Run backwards through an array chain doing array_set operations
294 // to produce the array type that incorporates the effects of any
295 // intermediate defining dims.
296 Type currentChainType(ISS& env, Type val) {
297 auto it = env.collect.mInstrState.arrayChain.end();
298 while (it != env.collect.mInstrState.arrayChain.begin()) {
299 if (val.is(BBottom)) break;
300 --it;
301 assertx(it->base.subtypeOf(BArrLike));
302 assertx(it->key.subtypeOf(BArrKey));
303 val = array_like_set(it->base, it->key, val).first;
305 return val;
308 Type resolveArrayChain(ISS& env, Type val) {
309 static UNUSED const char prefix[] = " ";
310 FTRACE(5, "{}chain {}\n", prefix, show(val));
311 do {
312 if (val.is(BBottom)) {
313 // If val is Bottom, the update isn't actually going to happen,
314 // so we don't need to unwind the chain.
315 env.collect.mInstrState.arrayChain.clear();
316 break;
318 auto arr = std::move(env.collect.mInstrState.arrayChain.back().base);
319 auto key = std::move(env.collect.mInstrState.arrayChain.back().key);
320 env.collect.mInstrState.arrayChain.pop_back();
321 FTRACE(5, "{} | {} := {} in {}\n", prefix,
322 show(key), show(val), show(arr));
323 assertx(arr.subtypeOf(BArrLike));
324 assertx(key.subtypeOf(BArrKey));
325 val = array_like_set(std::move(arr), key, val).first;
326 } while (!env.collect.mInstrState.arrayChain.empty());
327 FTRACE(5, "{} = {}\n", prefix, show(val));
328 return val;
331 // Returns true if the base update can be considered "effect-free"
332 // (IE, updating a static prop base is a side-effect because its
333 // visible elsewhere. Updating a local or stack slot is not).
334 bool updateBaseWithType(ISS& env,
335 const Type& ty,
336 LocalId firstKeyLoc = NoLocalId) {
337 FTRACE(6, " updateBaseWithType: {}\n", show(ty));
339 if (ty.subtypeOf(BBottom)) return true;
341 auto const& base = env.collect.mInstrState.base;
343 if (mustBeInLocal(base)) {
344 setLocalForBase(env, ty, firstKeyLoc);
345 // If we're speculating, a local update is considered a
346 // side-effect.
347 return !any(env.collect.opts & CollectionOpts::Speculating);
349 if (mustBeInStack(base)) {
350 setStackForBase(env, ty);
351 return true;
353 if (mustBeInStatic(base)) {
354 setStaticForBase(env, ty);
355 return false;
358 return true;
361 // Whether its worthwhile to refine the base's type based on knowing
362 // that certain types would have fatalled (and therefore the base can
363 // no longer be those types after the op). This is only worthwhile if
364 // the base is flow sensitive (IE, a local or stack slot). This is
365 // just an optimization so its always legal to say no.
366 bool shouldRefineBase(ISS& env) {
367 auto const& base = env.collect.mInstrState.base;
368 return mustBeInLocal(base) || mustBeInStack(base);
371 void startBase(ISS& env, Base base) {
372 auto& oldState = env.collect.mInstrState;
373 assertx(oldState.base.loc == BaseLoc::None);
374 assertx(oldState.arrayChain.empty());
375 assertx(isInitialBaseLoc(base.loc));
376 assertx(!base.type.subtypeOf(TBottom));
378 oldState.effectFree = env.flags.effectFree;
379 oldState.extraPop = false;
380 oldState.base = std::move(base);
381 FTRACE(5, " startBase: {}\n", show(*env.ctx.func, oldState.base));
384 // Return true if the base is updated and that update is considered
385 // "effect-free" (see updateBaseWithType).
386 bool endBase(ISS& env, bool update = true, LocalId keyLoc = NoLocalId) {
387 auto& state = env.collect.mInstrState;
388 assertx(state.base.loc != BaseLoc::None);
390 FTRACE(5, " endBase: {}\n", show(*env.ctx.func, state.base));
392 auto const firstKeyLoc = state.arrayChain.empty()
393 ? keyLoc
394 : state.arrayChain.data()->keyLoc;
395 auto const& ty = state.arrayChain.empty()
396 ? state.base.type
397 : resolveArrayChain(env, state.base.type);
399 auto const effectFree = update
400 ? updateBaseWithType(env, ty, firstKeyLoc)
401 : true;
402 state.base.loc = BaseLoc::None;
403 return effectFree;
406 // Return true if the base is updated and that update is considered
407 // "effect-free" (see updateBaseWithType).
408 bool moveBase(ISS& env,
409 Base newBase,
410 bool update = true,
411 LocalId keyLoc = NoLocalId) {
412 auto& state = env.collect.mInstrState;
413 assertx(state.base.loc != BaseLoc::None);
414 assertx(isDimBaseLoc(newBase.loc));
415 assertx(!state.base.type.subtypeOf(BBottom));
417 FTRACE(5, " moveBase: {} -> {}\n",
418 show(*env.ctx.func, state.base),
419 show(*env.ctx.func, newBase));
421 auto const firstKeyLoc = state.arrayChain.empty()
422 ? keyLoc
423 : state.arrayChain.data()->keyLoc;
424 auto const& ty = state.arrayChain.empty()
425 ? state.base.type
426 : resolveArrayChain(env, state.base.type);
428 auto const effectFree = update
429 ? updateBaseWithType(env, ty, firstKeyLoc)
430 : true;
431 state.base = std::move(newBase);
432 return effectFree;
435 // Return true if the base is updated and that update is considered
436 // "effect-free" (see updateBaseWithType).
437 bool extendArrChain(ISS& env, Type key, Type arr,
438 Type val, LocalId keyLoc = NoLocalId) {
439 auto& state = env.collect.mInstrState;
440 assertx(state.base.loc != BaseLoc::None);
441 // NB: The various array operation functions can accept arbitrary
442 // types as long as they contain TArrLike. However we still do not
443 // allow putting anything but TArrLike in the chain, since (in
444 // general) its not clear how to deal with the other types when
445 // resolving the chain. Currently the only case where we set up
446 // array chains is ElemD or ElemU. For ElemD, most of the common
447 // other types just fatal, so we can simply remove them (including
448 // TInitNull). For ElemU we do the same thing, but less types
449 // qualify.
450 assertx(arr.subtypeOf(BArrLike));
451 assertx(key.subtypeOf(BArrKey));
452 assertx(!state.base.type.subtypeOf(BBottom));
453 assertx(!val.subtypeOf(BBottom));
454 assertx(!key.subtypeOf(BBottom));
456 state.arrayChain.emplace_back(
457 CollectedInfo::MInstrState::ArrayChainEnt{
458 std::move(arr),
459 std::move(key),
460 keyLoc
463 state.base.type = std::move(val);
465 auto const firstKeyLoc = state.arrayChain.data()->keyLoc;
467 FTRACE(5, " extendArrChain: {}\n", show(*env.ctx.func, state));
468 return updateBaseWithType(
469 env,
470 currentChainType(env, state.base.type),
471 firstKeyLoc
475 //////////////////////////////////////////////////////////////////////
477 // Returns nullptr if it's an unknown key or not a string.
478 SString mStringKey(const Type& key) {
479 auto const v = tv(key);
480 return v && v->m_type == KindOfPersistentString ? v->m_data.pstr : nullptr;
483 template<typename Op>
484 auto update_mkey(const Op& op) { return false; }
486 template<typename Op>
487 auto update_mkey(Op& op) -> decltype(op.mkey, true) {
488 switch (op.mkey.mcode) {
489 case MEC: case MPC: {
490 op.mkey.idx++;
491 return true;
493 default:
494 return false;
498 template<typename Op>
499 auto update_discard(const Op& op) { return false; }
501 template<typename Op>
502 auto update_discard(Op& op) -> decltype(op.arg1, true) {
503 op.arg1++;
504 return true;
508 * Return the type of the key and whether any promotions happened, or
509 * reduce op and return folly::none. Note that when folly::none is
510 * returned, there is nothing further to do.
512 template<typename Op>
513 folly::Optional<std::pair<Type,Promotion>> key_type_or_fixup(ISS& env, Op op) {
514 if (env.collect.mInstrState.extraPop) {
515 auto const mkey = update_mkey(op);
516 if (update_discard(op) || mkey) {
517 env.collect.mInstrState.extraPop = false;
518 reduce(env, op);
519 env.collect.mInstrState.extraPop = true;
520 return folly::none;
523 auto const fixup = [&] (Type ty, bool isProp, bool couldBeUninit)
524 -> folly::Optional<std::pair<Type,Promotion>> {
526 // Handle any classlike key promotions
527 auto promoted = promote_classlike_to_key(std::move(ty));
528 // We could also promote and potentially throw if we had an uninit
529 // local.
530 if (couldBeUninit) promoted.second = Promotion::YesMightThrow;
531 // If we might throw, we don't want to const prop the key
532 if (promoted.second == Promotion::YesMightThrow) return promoted;
534 if (auto const val = tv(promoted.first)) {
535 if (isStringType(val->m_type)) {
536 op.mkey.mcode = isProp ? MPT : MET;
537 op.mkey.litstr = val->m_data.pstr;
538 reduce(env, op);
539 return folly::none;
541 if (!isProp && val->m_type == KindOfInt64) {
542 op.mkey.mcode = MEI;
543 op.mkey.int64 = val->m_data.num;
544 reduce(env, op);
545 return folly::none;
548 return promoted;
550 switch (op.mkey.mcode) {
551 case MEC: case MPC:
552 return fixup(topC(env, op.mkey.idx), op.mkey.mcode == MPC, false);
553 case MEL: case MPL: {
554 auto couldBeUninit = true;
555 if (!peekLocCouldBeUninit(env, op.mkey.local.id)) {
556 couldBeUninit = false;
557 auto const minLocEquiv = findMinLocEquiv(env, op.mkey.local.id, false);
558 if (minLocEquiv != NoLocalId) {
559 op.mkey.local = NamedLocal { kInvalidLocalName, minLocEquiv };
560 reduce(env, op);
561 return folly::none;
564 return fixup(
565 locAsCell(env, op.mkey.local.id),
566 op.mkey.mcode == MPL,
567 couldBeUninit
570 case MW:
571 return std::make_pair(TBottom, Promotion::No);
572 case MEI:
573 return std::make_pair(ival(op.mkey.int64), Promotion::No);
574 case MET: case MPT: case MQT:
575 return std::make_pair(sval(op.mkey.litstr), Promotion::No);
577 not_reached();
580 template<typename Op>
581 LocalId key_local(ISS& env, Op op) {
582 switch (op.mkey.mcode) {
583 case MEC: case MPC:
584 return topStkLocal(env, op.mkey.idx);
585 case MEL: case MPL:
586 return op.mkey.local.id;
587 case MW:
588 case MEI:
589 case MET: case MPT: case MQT:
590 return NoLocalId;
592 not_reached();
595 //////////////////////////////////////////////////////////////////////
597 // Handle the promotions that can happen to the base for ElemU or
598 // ElemD operations (including mutating final operations). Return
599 // whether any promotion happened.
600 Promotion handleElemUDBasePromos(ISS& env) {
601 auto& base = env.collect.mInstrState.base.type;
602 Promotion promotion;
603 std::tie(base, promotion) = promote_clsmeth_to_vecish(std::move(base));
604 return promotion;
607 //////////////////////////////////////////////////////////////////////
609 // Helper function for ending the base when we know this member
610 // instruction will always throw.
611 Effects endUnreachableBase(ISS& env, Promotion basePromo,
612 LocalId keyLoc = NoLocalId) {
613 // If we promoted the base, we still need to reflect that
614 if (basePromo != Promotion::No) endBase(env, true, keyLoc);
615 return Effects::AlwaysThrows;
618 // Helper function for ending the base. Takes the current aggregated
619 // effects up until this point, and whether the base underwent any
620 // promotions. Returns an updated aggregated effects, taking into
621 // account any effects from the base write-back. The given lambda will
622 // be called after the base is updated (this is needed because you
623 // don't want to modify the stack until after the base write back has
624 // occurred).
625 template <typename F>
626 Effects endBaseWithEffects(ISS& env, Effects effects, bool update,
627 Promotion basePromo,
628 const F& finish,
629 LocalId keyLoc = NoLocalId) {
630 if (effects == Effects::AlwaysThrows) {
631 auto const e = endUnreachableBase(env, basePromo, keyLoc);
632 finish();
633 return e;
635 // End the base. We request a base update if the caller requested,
636 // or if a base promotion happened (if the base promoted, we need to
637 // record the new type into it).
638 auto const effectFree =
639 endBase(env, update || basePromo != Promotion::No, keyLoc);
640 finish();
641 // If we might throw because of base promotion, it doesn't matter
642 // what other effects are.
643 if (basePromo == Promotion::YesMightThrow) return Effects::Throws;
644 // Special case: if we don't have any other effects, but writing to
645 // the base is side-effectful, then we can be nothrow (but not
646 // effect_free).
647 if (!effectFree && effects == Effects::None) return Effects::SideEffect;
648 return effects;
651 //////////////////////////////////////////////////////////////////////
653 // Helper function for Elem (not ElemD or ElemU) operations. Luckily
654 // the logic for the Dim portion, and the final operation are
655 // identical, so we can treat them as the same. Return the elem type
656 // and what effects the access has.
657 std::pair<Type, Effects> elemHelper(ISS& env, MOpMode mode, Type key) {
658 assertx(mode == MOpMode::None ||
659 mode == MOpMode::Warn ||
660 mode == MOpMode::InOut);
662 auto& base = env.collect.mInstrState.base.type;
663 assertx(!base.is(BBottom));
665 // If we're using MOpMode::InOut, then the base has to be a ArrLike
666 // or we'll throw.
667 auto inOutFail = false;
668 if (mode == MOpMode::InOut) {
669 if (!base.couldBe(BArrLike)) return { TBottom, Effects::AlwaysThrows };
670 if (!base.subtypeOf(BArrLike)) inOutFail = true;
673 auto const warnsWithNull =
674 BTrue | BNum | BRes | BFunc | BRFunc | BRClsMeth |
675 (!RO::EvalIsCompatibleClsMethType ? BClsMeth : BBottom);
676 auto const justNull = BNull | BFalse;
677 auto const DEBUG_ONLY handled =
678 warnsWithNull | justNull | BClsMeth |
679 BStr | BCls | BLazyCls | BObj | BRecord | BArrLike;
681 // Can't static assert because warnsWithNull depends on
682 // RuntimeOptions.
683 assertx(handled == BCell);
685 OptEffects effects;
686 auto ty = TBottom;
688 // These emit a warning and push InitNull
689 if (base.couldBe(warnsWithNull)) {
690 effects = unionEffects(effects, Effects::Throws);
691 ty |= TInitNull;
693 // These silently push InitNull
694 if (base.couldBe(justNull)) {
695 effects = unionEffects(
696 effects,
697 inOutFail ? Effects::Throws : Effects::None
699 ty |= TInitNull;
701 // Strings will return a static string (a character from itself or
702 // an empty string). Class-likes will convert to its equivalent string
703 // first.
704 if (base.couldBe(BStr | BCls | BLazyCls)) {
705 auto const isNoThrow =
706 !inOutFail &&
707 mode != MOpMode::Warn &&
708 key.subtypeOf(BArrKey) &&
709 (!base.couldBe(BCls | BLazyCls) ||
710 !RuntimeOption::EvalRaiseClassConversionWarning);
711 effects = unionEffects(
712 effects,
713 isNoThrow ? Effects::None : Effects::Throws
715 ty |= TSStr;
717 // With the right setting, ClsMeth will behave like an equivalent
718 // varray/vec (but will not actually promote). This is the same as a
719 // 2 element vec/varray containing static strings.
720 if (RO::EvalIsCompatibleClsMethType && base.couldBe(BClsMeth)) {
721 auto const isNoThrow =
722 !inOutFail &&
723 mode == MOpMode::None &&
724 !RuntimeOption::EvalRaiseClsMethConversionWarning;
725 effects = unionEffects(
726 effects,
727 isNoThrow ? Effects::None : Effects::Throws
729 ty |= TOptSStr;
731 // These can throw and push anything.
732 if (base.couldBe(BObj | BRecord)) {
733 effects = unionEffects(effects, Effects::Throws);
734 ty |= TInitCell;
736 // If it's an array, we can determine the exact element.
737 if (base.couldBe(BArrLike)) {
738 auto elem = array_do_elem(env, key);
739 if (elem.throws == TriBool::Yes) {
740 // Key is bad
741 effects = unionEffects(effects, Effects::AlwaysThrows);
742 } else {
743 auto mightThrow = elem.throws != TriBool::No;
744 // If the mode is MOpMode::None, we'll use TInitNull if the key
745 // is missing. Otherwise, we'll throw.
746 if (!elem.present) {
747 if (mode == MOpMode::None) {
748 elem.elem |= TInitNull;
749 } else {
750 mightThrow = true;
754 if (elem.elem.is(BBottom)) {
755 // Key definitely doesn't exist. We'll always throw. Note that
756 // this can't happen if mode is MOpMode::None because we added
757 // TInitNull to the type above.
758 effects = unionEffects(effects, Effects::AlwaysThrows);
759 } else {
760 ty |= std::move(elem.elem);
761 effects = unionEffects(
762 effects,
763 !mightThrow && !inOutFail ? Effects::None : Effects::Throws
769 assertx(effects.has_value());
770 return { std::move(ty), *effects };
773 // Helper function for SetOpElem/IncDecElem final operations. Reads
774 // the element in the base given by the key, performs the operation
775 // using `op', then writes the new value back to the base. Returns the
776 // aggregates effects of the entire operation.
777 template <typename F>
778 Effects setOpElemHelper(ISS& env, int32_t nDiscard, const Type& key,
779 LocalId keyLoc, F op) {
780 // Before anything else, the base might promote to a different type
781 auto const promo = handleElemUDBasePromos(env);
783 auto& base = env.collect.mInstrState.base.type;
784 assertx(!base.is(BBottom));
786 auto const throws = BNull | BFalse | BStr;
787 auto const null =
788 BTrue | BNum | BRes | BFunc | BRFunc |
789 BCls | BLazyCls | BClsMeth | BRClsMeth;
790 auto const handled = throws | null | BArrLike | BObj | BRecord;
792 static_assert(handled == BCell);
794 OptEffects effects;
795 auto pushed = TBottom;
796 auto refine = BBottom;
797 auto update = false;
799 // Always throws
800 if (base.couldBe(throws)) {
801 effects = unionEffects(effects, Effects::AlwaysThrows);
802 refine |= throws;
804 // Raises a warning and pushes null
805 if (base.couldBe(null)) {
806 effects = unionEffects(effects, Effects::Throws);
807 pushed |= TInitNull;
809 // Objects can throw and push anything
810 if (base.couldBe(BObj | BRecord)) {
811 effects = unionEffects(effects, Effects::Throws);
812 pushed |= TInitCell;
814 // Records can throw and push anything. Since we might be mutating
815 // the record, record an update.
816 if (base.couldBe(BRecord)) {
817 effects = unionEffects(effects, Effects::Throws);
818 pushed |= TInitCell;
819 update = true;
821 if (base.couldBe(BArrLike)) {
822 auto const unreachable = [&] {
823 effects = unionEffects(effects, Effects::AlwaysThrows);
824 refine |= BArrLike;
827 [&] {
828 // For the array portion, we can analyze the effects precisely
829 auto elem = array_do_elem(env, key, true);
830 if (elem.elem.is(BBottom) || elem.throws == TriBool::Yes) {
831 // Element doesn't exist or key is bad. Always throws.
832 unreachable();
833 return;
836 // Keysets will throw, so we can assume the base does not
837 // contain them afterwards.
838 if (base.couldBe(BKeyset)) base = remove_keyset(std::move(base));
840 // We'll have already have COWed the array if the setop throws,
841 // so we need to manually remove staticness and force a base
842 // update.
843 base = loosen_array_staticness(std::move(base));
844 update = true;
846 // Perform the op
847 auto [toSet, toPush, opEffects] = op(std::move(elem.elem));
848 if (opEffects == Effects::AlwaysThrows) {
849 // The op will always throw. We've already COWed the base, so we
850 // still need to update the base type.
851 unreachable();
852 return;
855 // Write the element back into the array
856 auto const set = array_do_set(env, key, toSet);
857 if (set == TriBool::Yes) {
858 // The set will always throw. In theory this can happen if we do
859 // something like read a string out of a keyset, turn it into
860 // something that's not an array key, then try to write it back.
861 unreachable();
862 return;
865 auto const maybeThrows =
866 !elem.present ||
867 elem.throws == TriBool::Maybe ||
868 set == TriBool::Maybe;
869 effects = unionEffects(
870 effects,
871 maybeThrows ? Effects::Throws : opEffects
873 pushed |= std::move(toPush);
874 }();
877 // Refine the base and remove bits that will always throw
878 if (refine && shouldRefineBase(env)) {
879 base = remove_bits(std::move(base), refine);
880 update = true;
883 assertx(effects.has_value());
885 // We have to update the base even if we'll always throw to account
886 // for potential COWing of the array (it could be in a static for
887 // example).
888 if (*effects == Effects::AlwaysThrows && update) {
889 assertx(pushed.is(BBottom));
890 endBase(env, true, keyLoc);
891 discard(env, nDiscard);
892 push(env, TBottom);
893 return Effects::AlwaysThrows;
896 return endBaseWithEffects(
897 env, *effects, update, promo,
898 [&] {
899 discard(env, nDiscard);
900 push(env, std::move(pushed));
902 keyLoc
906 // Helper function for SetOpNewElem/IncDecNewElemm final
907 // operations. These all either throw or push null.
908 Effects setOpNewElemHelper(ISS& env, int32_t nDiscard) {
909 auto& base = env.collect.mInstrState.base.type;
910 assertx(!base.is(BBottom));
912 auto const alwaysThrow =
913 BNull | BFalse | BStr | BArrLike | BObj | BClsMeth | BRecord;
914 auto const null =
915 BTrue | BNum | BRes | BRFunc | BFunc | BRClsMeth | BCls | BLazyCls;
916 auto const handled = alwaysThrow | null;
918 static_assert(handled == BCell);
920 OptEffects effects;
921 auto pushed = TBottom;
922 auto refine = BBottom;
923 auto update = false;
925 // These always throw
926 if (base.couldBe(alwaysThrow)) {
927 effects = unionEffects(effects, Effects::AlwaysThrows);
928 refine |= alwaysThrow;
930 // These raise a warning and push InitNull
931 if (base.couldBe(null)) {
932 effects = unionEffects(effects, Effects::Throws);
933 pushed |= TInitNull;
936 // Refine the base and remove bits that will always throw
937 if (refine && shouldRefineBase(env)) {
938 base = remove_bits(std::move(base), refine);
939 update = true;
942 assertx(effects.has_value());
944 return endBaseWithEffects(
945 env, *effects, update, Promotion::No,
946 [&] {
947 discard(env, nDiscard);
948 push(env, std::move(pushed));
953 //////////////////////////////////////////////////////////////////////
954 // intermediate ops
956 Effects miProp(ISS& env, bool, MOpMode mode, Type key, ReadOnlyOp op) {
957 auto const name = mStringKey(key);
958 auto const isDefine = mode == MOpMode::Define;
959 auto const isUnset = mode == MOpMode::Unset;
960 // PHP5 doesn't modify an unset local if you unset a property or
961 // array elem on it, but hhvm does (it promotes it to init-null).
962 auto const update = isDefine || isUnset;
964 * MOpMode::Unset Props doesn't promote "emptyish" things to stdClass, or
965 * affect arrays, however it can define a property on an object base. This
966 * means we don't need any couldBeInFoo logic, but if the base could actually
967 * be $this, and a declared property could be Uninit, we need to merge
968 * InitNull.
970 * We're trying to handle this case correctly as far as the type inference
971 * here is concerned, but the runtime doesn't actually behave this way right
972 * now for declared properties. Note that it never hurts to merge more types
973 * than a thisProp could actually be, so this is fine.
975 * See TODO(#3602740): unset with intermediate dims on previously declared
976 * properties doesn't define them to null.
978 if (isUnset && couldBeThisObj(env, env.collect.mInstrState.base)) {
979 if (name) {
980 auto const elem = thisPropRaw(env, name);
981 if (elem && elem->ty.couldBe(BUninit)) {
982 mergeThisProp(env, name, TInitNull);
984 } else {
985 mergeEachThisPropRaw(env, [&] (const Type& ty) {
986 return ty.couldBe(BUninit) ? TInitNull : TBottom;
991 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
992 auto const optThisTy = thisTypeFromContext(env.index, env.ctx);
993 auto const thisTy = optThisTy ? *optThisTy : TObj;
994 if (name) {
995 auto const elem = thisPropRaw(env, name);
996 if (elem && elem->attrs & AttrIsReadOnly && op == ReadOnlyOp::Mutable) {
997 return Effects::AlwaysThrows;
1000 auto const ty = [&] {
1001 if (update) {
1002 if (elem) return elem->ty;
1003 } else {
1004 if (auto const propTy = thisPropAsCell(env, name)) return *propTy;
1006 auto const raw =
1007 env.index.lookup_public_prop(objcls(thisTy), sval(name));
1008 return update ? raw : to_cell(raw);
1009 }();
1011 if (ty.subtypeOf(BBottom)) return Effects::AlwaysThrows;
1012 moveBase(
1013 env,
1014 Base { ty, BaseLoc::Prop, thisTy, name },
1015 update
1017 } else {
1018 moveBase(env,
1019 Base { TInitCell, BaseLoc::Prop, thisTy },
1020 update);
1022 return Effects::Throws;
1025 // We know for sure we're going to be in an object property.
1026 if (env.collect.mInstrState.base.type.subtypeOf(BObj)) {
1027 auto const raw =
1028 env.index.lookup_public_prop(
1029 objcls(env.collect.mInstrState.base.type),
1030 name ? sval(name) : TStr
1032 auto const ty = update ? raw : to_cell(raw);
1033 if (ty.subtypeOf(BBottom)) return Effects::AlwaysThrows;
1034 moveBase(env,
1035 Base { ty,
1036 BaseLoc::Prop,
1037 env.collect.mInstrState.base.type,
1038 name },
1039 update);
1040 return Effects::Throws;
1044 * Otherwise, intermediate props with define can promote a null, false, or ""
1045 * to stdClass. Those cases, and others, if it's MOpMode::Define, will set
1046 * the base to a null value in tvScratch. The base may also legitimately be
1047 * an object and our next base is in an object property. Conservatively treat
1048 * all these cases as "possibly" being inside of an object property with
1049 * "Prop" with locType TCell.
1051 moveBase(env,
1052 Base { TInitCell, BaseLoc::Prop, TCell, name },
1053 update);
1054 return Effects::Throws;
1057 Effects miElem(ISS& env, MOpMode mode, Type key, LocalId keyLoc) {
1058 auto const isDefine = mode == MOpMode::Define;
1059 auto const isUnset = mode == MOpMode::Unset;
1061 if (!isDefine && !isUnset) {
1062 // An Elem operation which doesn't mutate the base at all
1063 auto [elem, effects] = elemHelper(env, mode, std::move(key));
1064 if (effects != Effects::AlwaysThrows) {
1065 // Since this Dim is not mutating the base, we don't need to use
1066 // an array chain here.
1067 moveBase(
1068 env, Base { std::move(elem), BaseLoc::Elem }, false, keyLoc
1071 return effects;
1074 // ElemD or ElemU. The base might mutate here. First handle any base
1075 // promotions.
1076 auto const promo = handleElemUDBasePromos(env);
1078 auto& base = env.collect.mInstrState.base.type;
1079 assertx(!base.is(BBottom));
1081 // These are similar to endBaseWithEffects, but moves the base or
1082 // extends the array chain instead.
1083 auto const move = [&] (Type ty, bool update, Effects effects) {
1084 if (effects == Effects::AlwaysThrows) {
1085 return endUnreachableBase(env, promo, keyLoc);
1087 auto const effectFree = moveBase(
1088 env,
1089 Base { std::move(ty), BaseLoc::Elem },
1090 update || promo != Promotion::No,
1091 keyLoc
1093 if (promo == Promotion::YesMightThrow) return Effects::Throws;
1094 if (!effectFree && effects == Effects::None) return Effects::SideEffect;
1095 return effects;
1097 auto const extend = [&] (Type ty, Effects effects) {
1098 assertx(effects != Effects::AlwaysThrows);
1099 if (!key.subtypeOf(BArrKey)) {
1100 assertx(effects == Effects::Throws);
1101 key = intersection_of(std::move(key), TArrKey);
1102 assertx(!key.is(BBottom));
1104 auto const effectFree =
1105 extendArrChain(env, std::move(key), base, std::move(ty), keyLoc);
1106 if (promo == Promotion::YesMightThrow) return Effects::Throws;
1107 if (!effectFree && effects == Effects::None) return Effects::SideEffect;
1108 return effects;
1111 if (isUnset) {
1112 // ElemU. These types either always throw, or set the base to
1113 // Uninit.
1114 auto const alwaysThrows =
1115 BCls | BLazyCls | BFunc | BRFunc | BStr | BClsMeth |
1116 BRClsMeth | BRecord;
1117 auto const movesToUninit = BPrim | BRes;
1118 auto const handled =
1119 alwaysThrows | movesToUninit | BObj | BArrLike;
1121 static_assert(handled == BCell);
1123 // It is quite unfortunate that InitNull doesn't always throw, as
1124 // this means we cannot use an array chain with a nullable array
1125 // (which is common).
1126 if (base.couldBe(BArrLike) && base.subtypeOf(alwaysThrows | BArrLike)) {
1127 auto elem = array_do_elem(env, key, true);
1128 if (elem.throws == TriBool::Yes) {
1129 // We'll always throw
1130 return endUnreachableBase(env, promo, keyLoc);
1132 if (!elem.present) elem.elem |= TInitNull;
1133 auto const maybeAlwaysThrows = base.couldBe(alwaysThrows);
1134 // Keysets will throw, so we can assume the base does not
1135 // contain them afterwards.
1136 if (base.couldBe(BKeyset)) base = remove_keyset(std::move(base));
1137 base = loosen_array_staticness(std::move(base)); // Handle COW
1138 // We can safely remove the always throws bits, since if we get
1139 // to the next op, it means they weren't present.
1140 if (maybeAlwaysThrows) base = remove_bits(std::move(base), alwaysThrows);
1141 return extend(
1142 std::move(elem.elem),
1143 (maybeAlwaysThrows || elem.throws == TriBool::Maybe)
1144 ? Effects::Throws : Effects::None
1148 OptEffects effects;
1149 auto ty = TBottom;
1150 auto update = false;
1151 auto refine = BBottom;
1153 // These always raise an error
1154 if (base.couldBe(alwaysThrows)) {
1155 effects = unionEffects(effects, Effects::AlwaysThrows);
1156 refine |= alwaysThrows;
1158 // These silently set the base to uninit
1159 if (base.couldBe(movesToUninit)) {
1160 effects = unionEffects(effects, Effects::None);
1161 ty |= TUninit;
1163 // Objects can throw and retrieve anything
1164 if (base.couldBe(BObj)) {
1165 effects = unionEffects(effects, Effects::Throws);
1166 ty |= TInitCell;
1168 // For arrays we can do a precise lookup
1169 if (base.couldBe(BArrLike)) {
1170 auto elem = array_do_elem(env, key, true);
1171 if (elem.throws == TriBool::Yes) {
1172 // Bad key or the element never exists. We'll always throw.
1173 effects = unionEffects(effects, Effects::AlwaysThrows);
1174 refine |= BArrLike;
1175 } else {
1176 if (!elem.present) elem.elem |= TInitNull;
1177 // Keysets will throw, so we can assume the base does not
1178 // contain them afterwards.
1179 if (base.couldBe(BKeyset)) base = remove_keyset(std::move(base));
1180 // Since we're not using an array chain here, we need to
1181 // pessimize the array (since we won't be able to track
1182 // further changes to its inner structure).
1183 base = loosen_array_staticness(loosen_array_values(std::move(base)));
1184 update = true;
1185 effects = unionEffects(
1186 effects,
1187 elem.throws == TriBool::Maybe
1188 ? Effects::Throws
1189 : Effects::None
1191 ty |= std::move(elem.elem);
1195 // Refine the base and remove bits that will always throw
1196 if (refine && shouldRefineBase(env)) {
1197 base = remove_bits(std::move(base), refine);
1198 update = true;
1201 assertx(effects.has_value());
1203 return move(std::move(ty), update, *effects);
1204 } else {
1205 assertx(isDefine);
1207 // ElemD. These types either always throw, or emit a warning and
1208 // push InitNull.
1209 auto const alwaysThrows = BNull | BFalse | BStr;
1210 auto const warnsAndNull =
1211 BTrue | BNum | BRes | BFunc | BRFunc | BCls | BLazyCls |
1212 BClsMeth | BRClsMeth;
1213 auto const handled =
1214 alwaysThrows | warnsAndNull | BObj | BRecord | BArrLike;
1216 static_assert(handled == BCell);
1218 // As a special case, if we just have an array, we can extend an
1219 // array chain and track the modifications to the inner array
1220 // structure. We also allow the types which always throw since
1221 // they don't affect the structure (if we get one at runtime,
1222 // we'll just throw without modifying anything).
1223 if (base.couldBe(BArrLike) && base.subtypeOf(alwaysThrows | BArrLike)) {
1224 auto elem = array_do_elem(env, key, true);
1225 if (elem.elem.is(BBottom) || elem.throws == TriBool::Yes) {
1226 // We'll always throw
1227 return endUnreachableBase(env, promo, keyLoc);
1229 auto const maybeAlwaysThrows = base.couldBe(alwaysThrows);
1230 // Keysets will throw, so we can assume the base does not
1231 // contain them afterwards.
1232 if (base.couldBe(BKeyset)) base = remove_keyset(std::move(base));
1233 base = loosen_array_staticness(std::move(base)); // Handle COW
1234 // We can safely remove the always throws bits, since if we get
1235 // to the next op, it means they weren't present.
1236 if (maybeAlwaysThrows) base = remove_bits(std::move(base), alwaysThrows);
1237 auto const mightThrow =
1238 maybeAlwaysThrows ||
1239 !elem.present ||
1240 elem.throws == TriBool::Maybe;
1241 return extend(
1242 std::move(elem.elem),
1243 mightThrow ? Effects::Throws : Effects::None
1247 OptEffects effects;
1248 auto ty = TBottom;
1249 auto update = false;
1250 auto refine = BBottom;
1252 // These always raise an error
1253 if (base.couldBe(alwaysThrows)) {
1254 effects = unionEffects(effects, Effects::AlwaysThrows);
1255 refine |= alwaysThrows;
1257 // These emit a warning and push InitNull
1258 if (base.couldBe(warnsAndNull)) {
1259 effects = unionEffects(effects, Effects::Throws);
1260 ty |= TInitNull;
1262 // Objects can throw and push anything
1263 if (base.couldBe(BObj)) {
1264 effects = unionEffects(effects, Effects::Throws);
1265 ty |= TInitCell;
1267 // Records can throw and push anything as well. Since we might be
1268 // mutating the record, we need to perform an update.
1269 if (base.couldBe(BRecord)) {
1270 effects = unionEffects(effects, Effects::Throws);
1271 ty |= TInitCell;
1272 update = true;
1274 // For arrays we can lookup the specific element
1275 if (base.couldBe(BArrLike)) {
1276 auto elem = array_do_elem(env, key, true);
1277 if (elem.elem.is(BBottom) || elem.throws == TriBool::Yes) {
1278 // Bad key or the element never exists. We'll always throw.
1279 effects = unionEffects(effects, Effects::AlwaysThrows);
1280 refine |= BArrLike;
1281 } else {
1282 // Keysets will throw, so we can assume the base does not
1283 // contain them afterwards.
1284 if (base.couldBe(BKeyset)) base = remove_keyset(std::move(base));
1285 // Since we're not using an array chain here, we need to
1286 // pessimize the array (since we won't be able to track
1287 // further changes to its inner structure).
1288 base = loosen_array_staticness(loosen_array_values(std::move(base)));
1289 update = true;
1290 effects = unionEffects(
1291 effects,
1292 (!elem.present || elem.throws == TriBool::Maybe)
1293 ? Effects::Throws
1294 : Effects::None
1296 ty |= std::move(elem.elem);
1300 // Refine the base and remove bits that will always throw
1301 if (refine && shouldRefineBase(env)) {
1302 base = remove_bits(std::move(base), refine);
1303 update = true;
1306 assertx(effects.has_value());
1308 return move(std::move(ty), update, *effects);
1312 Effects miNewElem(ISS& env) {
1313 // NewElem. These all either throw or raise a warning and set the
1314 // base to Uninit.
1315 auto& base = env.collect.mInstrState.base.type;
1316 assertx(!base.is(BBottom));
1318 auto const alwaysThrows =
1319 BNull | BFalse | BArrLike | BObj | BClsMeth | BRecord;
1320 auto const uninit =
1321 BTrue | BNum | BRes | BRFunc | BFunc |
1322 BRClsMeth | BCls | BLazyCls;
1323 auto const handled = alwaysThrows | uninit | BStr;
1325 static_assert(handled == BCell);
1327 OptEffects effects;
1328 auto newBase = TBottom;
1329 auto update = false;
1330 auto refine = BBottom;
1332 if (base.couldBe(alwaysThrows)) {
1333 effects = unionEffects(effects, Effects::AlwaysThrows);
1334 refine |= alwaysThrows;
1336 if (base.couldBe(uninit)) {
1337 effects = unionEffects(effects, Effects::Throws);
1338 newBase |= TUninit;
1340 if (base.couldBe(BStr)) {
1341 if (is_specialized_string(base) && sval_of(base)->empty()) {
1342 effects = unionEffects(effects, Effects::AlwaysThrows);
1343 refine |= BStr;
1344 } else {
1345 effects = unionEffects(effects, Effects::Throws);
1346 newBase |= TUninit;
1350 // Refine the base and remove bits that will always throw
1351 if (refine && shouldRefineBase(env)) {
1352 base = remove_bits(std::move(base), refine);
1353 update = true;
1356 assertx(effects.has_value());
1357 assertx(*effects == Effects::Throws || *effects == Effects::AlwaysThrows);
1358 if (*effects != Effects::AlwaysThrows) {
1359 moveBase(env, Base { std::move(newBase), BaseLoc::Elem }, update);
1361 return *effects;
1364 //////////////////////////////////////////////////////////////////////
1365 // final prop ops
1367 Effects miFinalIssetProp(ISS& env, int32_t nDiscard, const Type& key) {
1368 auto const name = mStringKey(key);
1369 discard(env, nDiscard);
1371 if (name && mustBeThisObj(env, env.collect.mInstrState.base)) {
1372 if (auto const pt = thisPropAsCell(env, name)) {
1373 if (isMaybeThisPropAttr(env, name, AttrLateInit)) {
1374 // LateInit props can always be maybe unset, except if its never set at
1375 // all.
1376 push(env, pt->subtypeOf(BBottom) ? TFalse : TBool);
1377 } else if (pt->subtypeOf(BNull)) {
1378 push(env, TFalse);
1379 } else if (!pt->couldBe(BNull)) {
1380 push(env, TTrue);
1381 } else {
1382 push(env, TBool);
1384 return Effects::None;
1388 push(env, TBool);
1389 return Effects::Throws;
1392 Effects miFinalCGetProp(ISS& env, int32_t nDiscard, const Type& key,
1393 bool quiet, bool mustBeMutable) {
1394 auto const name = mStringKey(key);
1395 discard(env, nDiscard);
1397 auto const& base = env.collect.mInstrState.base.type;
1399 if (name) {
1400 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1401 if (auto const t = thisPropAsCell(env, name)) {
1402 push(env, *t);
1403 if (mustBeMutable && isMaybeThisPropAttr(env, name, AttrIsReadOnly)) {
1404 return Effects::Throws;
1406 if (t->subtypeOf(BBottom)) return Effects::AlwaysThrows;
1407 if (isMaybeThisPropAttr(env, name, AttrLateInit)) return Effects::Throws;
1408 if (quiet) return Effects::None;
1409 auto const elem = thisPropRaw(env, name);
1410 assertx(elem);
1411 return elem->ty.couldBe(BUninit) ? Effects::Throws : Effects::None;
1414 auto ty = [&] {
1415 if (!base.couldBe(BObj)) return TInitNull;
1416 auto t = to_cell(
1417 env.index.lookup_public_prop(
1418 objcls(base.subtypeOf(BObj) ? base : intersection_of(base, TObj)),
1419 sval(name)
1422 if (!base.subtypeOf(BObj)) t = opt(std::move(t));
1423 return t;
1424 }();
1425 auto const e =
1426 ty.subtypeOf(BBottom) ? Effects::AlwaysThrows : Effects::Throws;
1427 push(env, std::move(ty));
1428 return e;
1431 push(env, TInitCell);
1432 return Effects::Throws;
1435 Effects miFinalSetProp(ISS& env, int32_t nDiscard, const Type& key, ReadOnlyOp op) {
1436 auto const name = mStringKey(key);
1437 auto const t1 = unctx(popC(env));
1439 auto const finish = [&](Type ty) {
1440 endBase(env);
1441 discard(env, nDiscard);
1442 push(env, std::move(ty));
1443 return Effects::Throws;
1446 auto const alwaysThrows = [&] {
1447 discard(env, nDiscard);
1448 push(env, TBottom);
1449 return Effects::AlwaysThrows;
1452 if (op == ReadOnlyOp::ReadOnly && !isMaybeThisPropAttr(env, name, AttrIsReadOnly)) {
1453 return alwaysThrows();
1456 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1457 if (!name) {
1458 mergeEachThisPropRaw(
1459 env,
1460 [&] (Type propTy) {
1461 return propTy.couldBe(BInitCell) ? t1 : TBottom;
1464 } else {
1465 mergeThisProp(env, name, t1);
1469 if (env.collect.mInstrState.base.type.subtypeOf(BObj)) {
1470 if (t1.subtypeOf(BBottom)) return alwaysThrows();
1471 moveBase(
1472 env,
1473 Base { t1, BaseLoc::Prop, env.collect.mInstrState.base.type, name }
1475 return finish(t1);
1478 moveBase(env, Base { TInitCell, BaseLoc::Prop, TCell, name });
1479 return finish(TInitCell);
1482 Effects miFinalSetOpProp(ISS& env, int32_t nDiscard,
1483 SetOpOp subop, const Type& key) {
1484 auto const name = mStringKey(key);
1485 auto const rhsTy = popC(env);
1487 auto const& base = env.collect.mInstrState.base.type;
1489 if (!base.couldBe(BObj)) {
1490 endBase(env);
1491 discard(env, nDiscard);
1492 push(env, TInitNull);
1493 return Effects::Throws;
1496 auto lhsTy = [&] {
1497 if (name) {
1498 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1499 if (auto const t = thisPropAsCell(env, name)) return *t;
1501 return to_cell(
1502 env.index.lookup_public_prop(
1503 objcls(base.subtypeOf(BObj) ? base : intersection_of(base, TObj)),
1504 sval(name)
1508 return TInitCell;
1509 }();
1510 if (lhsTy.subtypeOf(BBottom)) {
1511 discard(env, nDiscard);
1512 push(env, TBottom);
1513 return Effects::AlwaysThrows;
1515 if (!base.subtypeOf(BObj)) lhsTy = opt(std::move(lhsTy));
1517 auto const resultTy = base.subtypeOf(BObj)
1518 ? typeSetOp(subop, lhsTy, rhsTy)
1519 : TInitCell;
1521 if (resultTy.subtypeOf(BBottom)) {
1522 discard(env, nDiscard);
1523 push(env, TBottom);
1524 return Effects::AlwaysThrows;
1527 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1528 if (name) {
1529 mergeThisProp(env, name, resultTy);
1530 } else {
1531 killThisProps(env);
1535 endBase(env);
1536 discard(env, nDiscard);
1537 push(env, resultTy);
1538 return Effects::Throws;
1541 Effects miFinalIncDecProp(ISS& env, int32_t nDiscard,
1542 IncDecOp subop, const Type& key) {
1543 auto const name = mStringKey(key);
1545 auto const& base = env.collect.mInstrState.base.type;
1547 if (!base.couldBe(BObj)) {
1548 endBase(env);
1549 discard(env, nDiscard);
1550 push(env, TInitNull);
1551 return Effects::Throws;
1554 auto postPropTy = [&] {
1555 if (name) {
1556 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1557 if (auto const t = thisPropAsCell(env, name)) return *t;
1559 return to_cell(
1560 env.index.lookup_public_prop(
1561 objcls(base.subtypeOf(BObj) ? base : intersection_of(base, TObj)),
1562 sval(name)
1566 return TInitCell;
1567 }();
1568 if (postPropTy.subtypeOf(BBottom)) {
1569 discard(env, nDiscard);
1570 push(env, TBottom);
1571 return Effects::AlwaysThrows;
1573 if (!base.subtypeOf(BObj)) postPropTy = opt(std::move(postPropTy));
1575 auto const prePropTy = base.subtypeOf(TObj)
1576 ? typeIncDec(subop, postPropTy)
1577 : TInitCell;
1578 if (prePropTy.subtypeOf(BBottom)) {
1579 discard(env, nDiscard);
1580 push(env, TBottom);
1581 return Effects::AlwaysThrows;
1584 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1585 if (name) {
1586 mergeThisProp(env, name, prePropTy);
1587 } else {
1588 killThisProps(env);
1592 endBase(env);
1593 discard(env, nDiscard);
1594 push(env, isPre(subop) ? prePropTy : postPropTy);
1595 return Effects::Throws;
1598 Effects miFinalUnsetProp(ISS& env, int32_t nDiscard, const Type& key) {
1599 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1600 if (auto const name = mStringKey(key)) {
1601 unsetThisProp(env, name);
1602 } else {
1603 unsetUnknownThisProp(env);
1607 endBase(env);
1608 discard(env, nDiscard);
1609 return Effects::Throws;
1612 //////////////////////////////////////////////////////////////////////
1613 // Final elem ops
1615 Effects miFinalCGetElem(ISS& env, int32_t nDiscard,
1616 const Type& key, MOpMode mode) {
1617 auto [elem, effects] = elemHelper(env, mode, key);
1618 discard(env, nDiscard);
1619 push(env, std::move(elem));
1620 return effects;
1623 Effects miFinalIssetElem(ISS& env,
1624 int32_t nDiscard,
1625 const Type& key) {
1626 auto& base = env.collect.mInstrState.base.type;
1627 assertx(!base.is(BBottom));
1629 auto const pushesFalse =
1630 BNull | BBool | BNum | BRes | BFunc | BRFunc | BRClsMeth |
1631 (!RO::EvalIsCompatibleClsMethType ? BClsMeth : BBottom);
1632 auto const handled = pushesFalse | BArrLike;
1634 OptEffects effects;
1635 auto pushed = TBottom;
1637 if (base.couldBe(pushesFalse)) {
1638 effects = unionEffects(effects, Effects::None);
1639 pushed |= TFalse;
1641 if (base.couldBe(BArrLike)) {
1642 auto const elem = array_do_elem(env, key);
1643 if (elem.throws == TriBool::Yes) {
1644 effects = unionEffects(effects, Effects::AlwaysThrows);
1645 } else {
1646 effects = unionEffects(
1647 effects,
1648 elem.throws == TriBool::Maybe
1649 ? Effects::Throws
1650 : Effects::None
1653 if (elem.elem.subtypeOf(BNull)) {
1654 pushed |= TFalse;
1655 } else if (elem.present && !elem.elem.couldBe(BNull)) {
1656 pushed |= TTrue;
1657 } else {
1658 pushed |= TBool;
1663 if (!base.subtypeOf(handled)) {
1664 effects = unionEffects(effects, Effects::Throws);
1665 pushed |= TBool;
1668 assertx(effects.has_value());
1669 discard(env, nDiscard);
1670 push(env, std::move(pushed));
1671 return *effects;
1674 Effects miFinalSetElem(ISS& env,
1675 int32_t nDiscard,
1676 const Type& key,
1677 LocalId keyLoc) {
1678 auto const rhs = popC(env);
1680 // First handle base promotions
1681 auto const promo = handleElemUDBasePromos(env);
1683 auto& base = env.collect.mInstrState.base.type;
1684 assertx(!base.is(BBottom));
1686 auto const alwaysThrows = BNull | BFalse;
1687 auto const pushesNull =
1688 BTrue | BNum | BRes | BFunc | BRFunc |
1689 BCls | BLazyCls | BClsMeth | BRClsMeth;
1690 auto const handled =
1691 alwaysThrows | pushesNull | BObj | BRecord | BStr | BArrLike;
1693 static_assert(handled == BCell);
1695 OptEffects effects;
1696 auto pushed = TBottom;
1697 auto update = false;
1698 auto refine = BBottom;
1700 if (base.couldBe(pushesNull)) {
1701 // These emit a warning and push null
1702 effects = unionEffects(effects, Effects::Throws);
1703 pushed |= TInitNull;
1705 if (base.couldBe(alwaysThrows)) {
1706 // These always raise a fatal
1707 effects = unionEffects(effects, Effects::AlwaysThrows);
1708 refine |= alwaysThrows;
1710 if (base.couldBe(BStr)) {
1711 // String is a special case here. It will throw on empty strings,
1712 // and otherwise can return null or a static string (and possibly
1713 // warn). We don't bother predicting the effects of the set on the
1714 // string here, so be pessimistic and forget what we know about
1715 // the base in regards to staticness and values.
1716 if (is_specialized_string(base) && sval_of(base)->empty()) {
1717 effects = unionEffects(effects, Effects::AlwaysThrows);
1718 refine |= BStr;
1719 } else {
1720 effects = unionEffects(effects, Effects::Throws);
1721 base = loosen_string_staticness(loosen_string_values(std::move(base)));
1722 update = true;
1723 pushed |= TOptSStr;
1726 if (base.couldBe(BObj)) {
1727 // Objects can throw but otherwise don't affect the base and push
1728 // the rhs.
1729 effects = unionEffects(effects, Effects::Throws);
1730 pushed |= rhs;
1732 if (base.couldBe(BRecord)) {
1733 // Records can throw but otherwise don't affect the base and push
1734 // the rhs. Record an update since we're potentially mutating it.
1735 effects = unionEffects(effects, Effects::Throws);
1736 pushed |= rhs;
1737 update = true;
1739 if (base.couldBe(BArrLike)) {
1740 // Arrays will set the value and push the right hande side of the
1741 // assignment (keysets will always fatal because you can't do a
1742 // set on them).
1743 auto const doesThrow = array_do_set(env, key, rhs);
1744 if (doesThrow == TriBool::Yes) {
1745 effects = unionEffects(effects, Effects::AlwaysThrows);
1746 refine |= BArrLike;
1747 } else {
1748 effects = unionEffects(
1749 effects,
1750 doesThrow == TriBool::No ? Effects::None : Effects::Throws
1752 pushed |= rhs;
1753 update = true;
1757 // Refine the base and remove bits that will always throw
1758 if (refine && shouldRefineBase(env)) {
1759 base = remove_bits(std::move(base), refine);
1760 update = true;
1763 assertx(effects.has_value());
1765 return endBaseWithEffects(
1766 env, *effects, update, promo,
1767 [&] {
1768 discard(env, nDiscard);
1769 push(env, std::move(pushed));
1771 keyLoc
1775 Effects miFinalSetOpElem(ISS& env, int32_t nDiscard,
1776 SetOpOp subop, const Type& key,
1777 LocalId keyLoc) {
1778 auto const rhsTy = popC(env);
1779 return setOpElemHelper(
1780 env, nDiscard, key, keyLoc,
1781 [&] (const Type& lhsTy) {
1782 auto const result = typeSetOp(subop, lhsTy, rhsTy);
1783 return std::make_tuple(result, result, Effects::Throws);
1788 Effects miFinalIncDecElem(ISS& env, int32_t nDiscard,
1789 IncDecOp subop, const Type& key,
1790 LocalId keyLoc) {
1791 return setOpElemHelper(
1792 env, nDiscard, key, keyLoc,
1793 [&] (const Type& before) {
1794 auto const after = typeIncDec(subop, before);
1795 return std::make_tuple(
1796 after,
1797 isPre(subop) ? after : before,
1798 before.subtypeOf(BNum) ? Effects::None : Effects::Throws
1804 Effects miFinalUnsetElem(ISS& env, int32_t nDiscard, const Type& key) {
1805 // First handle base promotions
1806 auto const promo = handleElemUDBasePromos(env);
1808 auto& base = env.collect.mInstrState.base.type;
1809 assertx(!base.is(BBottom));
1811 auto const doesNothing = BNull | BBool | BNum | BRes;
1812 auto const alwaysThrows =
1813 BFunc | BRFunc | BCls | BLazyCls | BStr | BRecord | BClsMeth | BRClsMeth;
1814 auto const handled = doesNothing | alwaysThrows | BArrLike | BObj;
1816 static_assert(handled == BCell);
1818 OptEffects effects;
1819 auto update = false;
1820 auto refine = BBottom;
1822 // These silently does nothing
1823 if (base.couldBe(doesNothing)) {
1824 effects = unionEffects(effects, Effects::None);
1826 // These always raise an error
1827 if (base.couldBe(alwaysThrows)) {
1828 effects = unionEffects(effects, Effects::AlwaysThrows);
1829 refine |= alwaysThrows;
1831 // Objects can throw but otherwise do not affect the base
1832 if (base.couldBe(BObj)) {
1833 effects = unionEffects(effects, Effects::Throws);
1835 if (base.couldBe(BArrLike)) {
1836 // Unset doesn't throw for a missing element on dicts, keysets, or
1837 // darrays, only if the key is invalid. Vecs and varrays silently do
1838 // nothing for string keys, but can throw with int keys.
1839 if (!key.couldBe(BArrKey)) {
1840 effects = unionEffects(effects, Effects::AlwaysThrows);
1841 refine |= BArrLike;
1842 } else {
1843 auto e = key.subtypeOf(BArrKey) ? Effects::None : Effects::Throws;
1844 if (base.couldBe(BVec) && key.couldBe(BInt)) e = Effects::Throws;
1845 effects = unionEffects(effects, e);
1847 // We purposefully do not model the effects of unset on array
1848 // structure. This lets us assume that if we have array structure,
1849 // we also have no tombstones. Pessimize the base which drops array
1850 // structure and also remove emptiness information.
1851 if (!base.subtypeAmong(BVec, BArrLike) || key.couldBe(BInt)) {
1852 base = loosen_array_staticness(loosen_array_values(std::move(base)));
1853 base = loosen_emptiness(std::move(base));
1854 update = true;
1859 // Refine the base and remove bits that will always throw
1860 if (refine && shouldRefineBase(env)) {
1861 base = remove_bits(std::move(base), refine);
1862 update = true;
1865 assertx(effects.has_value());
1867 return endBaseWithEffects(
1868 env, *effects, update, promo,
1869 [&] { discard(env, nDiscard); }
1873 //////////////////////////////////////////////////////////////////////
1874 // Final new elem ops
1876 Effects miFinalSetNewElem(ISS& env, int32_t nDiscard) {
1877 auto const rhs = popC(env);
1879 // First handle base promotions
1880 auto const promo = handleElemUDBasePromos(env);
1882 auto& base = env.collect.mInstrState.base.type;
1883 assertx(!base.is(BBottom));
1885 auto const pushesNull =
1886 BTrue | BNum | BRes | BFunc | BRFunc | BCls |
1887 BLazyCls | BClsMeth | BRClsMeth;
1888 auto const alwaysThrows = BNull | BStr | BRecord | BFalse;
1889 auto const handled = pushesNull | alwaysThrows | BObj | BArrLike;
1891 static_assert(handled == BCell);
1893 OptEffects effects;
1894 auto pushed = TBottom;
1895 auto update = false;
1896 auto refine = BBottom;
1898 if (base.couldBe(pushesNull)) {
1899 // These emit a warning and push null
1900 effects = unionEffects(effects, Effects::Throws);
1901 pushed |= TInitNull;
1903 if (base.couldBe(alwaysThrows)) {
1904 // These always raise a fatal
1905 effects = unionEffects(effects, Effects::AlwaysThrows);
1906 refine |= alwaysThrows;
1908 if (base.couldBe(BObj)) {
1909 // Objects can throw but otherwise don't affect the base and push
1910 // the rhs.
1911 effects = unionEffects(effects, Effects::Throws);
1912 pushed |= rhs;
1914 if (base.couldBe(BArrLike)) {
1915 // Arrays will add a new element and push the right hand side of the
1916 // assignment.
1917 auto const doesThrow = array_do_newelem(env, rhs);
1918 if (doesThrow == TriBool::Yes) {
1919 effects = unionEffects(effects, Effects::AlwaysThrows);
1920 refine |= BArrLike;
1921 } else {
1922 effects = unionEffects(
1923 effects,
1924 doesThrow == TriBool::No ? Effects::None : Effects::Throws
1926 pushed |= rhs;
1927 update = true;
1931 // Refine the base and remove bits that will always throw
1932 if (refine && shouldRefineBase(env)) {
1933 base = remove_bits(std::move(base), refine);
1934 update = true;
1937 assertx(effects.has_value());
1939 return endBaseWithEffects(
1940 env, *effects, update, promo,
1941 [&] {
1942 discard(env, nDiscard);
1943 push(env, std::move(pushed));
1948 Effects miFinalSetOpNewElem(ISS& env, int32_t nDiscard) {
1949 popC(env);
1950 return setOpNewElemHelper(env, nDiscard);
1953 Effects miFinalIncDecNewElem(ISS& env, int32_t nDiscard) {
1954 return setOpNewElemHelper(env, nDiscard);
1957 //////////////////////////////////////////////////////////////////////
1959 // Translate the aggregated effects of the instruction into the
1960 // appropriate interp-state actions. Returns true if the instruction
1961 // is totally effect-free.
1962 bool handleEffects(ISS& env, Effects effects, Promotion keyPromotion) {
1963 auto const effectFree = [&]{
1964 switch (effects) {
1965 case Effects::None:
1966 if (keyPromotion == Promotion::YesMightThrow) return false;
1967 effect_free(env);
1968 return true;
1969 case Effects::SideEffect:
1970 if (keyPromotion != Promotion::YesMightThrow) nothrow(env);
1971 return false;
1972 case Effects::Throws:
1973 return false;
1974 case Effects::AlwaysThrows:
1975 unreachable(env);
1976 return false;
1978 always_assert(false);
1979 }();
1981 if (env.flags.wasPEI && env.blk.throwExit != NoBlockId) {
1982 // Minstr opcodes may throw after side-effects.
1983 assertx(!effectFree);
1984 auto const state = with_throwable_only(env.index, env.state);
1985 env.propagate(env.blk.throwExit, &state);
1988 return effectFree;
1993 namespace interp_step {
1995 //////////////////////////////////////////////////////////////////////
1996 // Base operations
1998 void in(ISS& env, const bc::BaseGC& op) {
1999 startBase(env, Base{TInitCell, BaseLoc::Global});
2002 void in(ISS& env, const bc::BaseGL& op) {
2003 mayReadLocal(env, op.loc1);
2004 startBase(env, Base{TInitCell, BaseLoc::Global});
2007 void in(ISS& env, const bc::BaseSC& op) {
2008 auto tcls = topC(env, op.arg2);
2009 auto const tname = topC(env, op.arg1);
2011 // We'll raise an error if its not a class
2012 if (!tcls.couldBe(BCls)) return unreachable(env);
2014 // Lookup what we know about the property
2015 auto lookup = env.index.lookup_static(
2016 env.ctx,
2017 env.collect.props,
2018 tcls,
2019 tname
2022 // If we definitely didn't find anything, we'll definitely throw
2023 if (lookup.found == TriBool::No || lookup.ty.subtypeOf(BBottom)) {
2024 return unreachable(env);
2027 // Whether we might potentially throw because of AttrConst
2028 auto mightConstThrow = false;
2029 switch (op.subop3) {
2030 case MOpMode::Define:
2031 case MOpMode::Unset:
2032 case MOpMode::InOut:
2033 // If its definitely const, we'll always throw. Otherwise we'll
2034 // potentially throw if there's a chance its AttrConst.
2035 if (lookup.isConst == TriBool::Yes) return unreachable(env);
2036 mightConstThrow = lookup.isConst == TriBool::Maybe;
2037 break;
2038 case MOpMode::None:
2039 case MOpMode::Warn:
2040 // These don't mutate the base, so AttrConst does not apply
2041 break;
2044 // Whether we might potentially throw because of AttrIsReadOnly
2045 if (op.subop4 == ReadOnlyOp::Mutable && lookup.readOnly == TriBool::Yes) {
2046 return unreachable(env);
2048 auto const mightReadOnlyThrow =
2049 (op.subop4 == ReadOnlyOp::Mutable && lookup.readOnly == TriBool::Maybe);
2051 // Loading the base from a static property can be considered
2052 // effect_free if there's no possibility of throwing. This requires
2053 // a definitely found, non-AttrLateInit property with normal class
2054 // initialization, and both the class and name have to be the normal
2055 // types.
2056 if (lookup.found == TriBool::Yes &&
2057 lookup.lateInit == TriBool::No &&
2058 !lookup.classInitMightRaise &&
2059 !mightConstThrow &&
2060 !mightReadOnlyThrow &&
2061 tcls.subtypeOf(BCls) &&
2062 tname.subtypeOf(BStr)) {
2064 // If we're not mutating the base, and the base is a constant,
2065 // turn it into a BaseC with the appropriate constant on the
2066 // stack.
2067 if (op.subop3 == MOpMode::Warn || op.subop3 == MOpMode::None) {
2068 if (auto const v = tv(lookup.ty)) {
2069 reduce(env, gen_constant(*v), bc::BaseC { 0, op.subop3 });
2070 env.collect.mInstrState.extraPop = true;
2071 return;
2075 effect_free(env);
2078 return startBase(
2079 env,
2080 Base {
2081 std::move(lookup.ty),
2082 BaseLoc::StaticProp,
2083 std::move(tcls),
2084 lookup.name
2089 void in(ISS& env, const bc::BaseL& op) {
2090 auto ty = peekLocRaw(env, op.nloc1.id);
2092 // An Uninit local base can raise a notice.
2093 if (!ty.couldBe(BUninit)) {
2094 // If we're not mutating the base, and the base is a constant,
2095 // turn it into a BaseC with the appropriate constant on the
2096 // stack.
2097 if (op.subop2 == MOpMode::Warn || op.subop2 == MOpMode::None) {
2098 if (auto const v = tv(ty)) {
2099 reduce(env, gen_constant(*v), bc::BaseC { 0, op.subop2 });
2100 env.collect.mInstrState.extraPop = true;
2101 return;
2104 // Try to find an equivalent local to use instead
2105 auto const minLocEquiv = findMinLocEquiv(env, op.nloc1.id, false);
2106 if (minLocEquiv != NoLocalId) {
2107 return reduce(
2108 env,
2109 bc::BaseL {
2110 NamedLocal { kInvalidLocalName, minLocEquiv },
2111 op.subop2
2117 effect_free(env);
2118 } else if (op.subop2 != MOpMode::Warn) {
2119 // The local could be Uninit, but we won't warn about it anyways.
2120 effect_free(env);
2123 mayReadLocal(env, op.nloc1.id);
2124 if (ty.subtypeOf(BBottom)) return unreachable(env);
2126 startBase(
2127 env,
2128 Base {
2129 std::move(ty),
2130 BaseLoc::Local,
2131 TBottom,
2132 op.nloc1.name != kInvalidLocalName
2133 ? env.ctx.func->locals[op.nloc1.name].name
2134 : nullptr,
2135 op.nloc1.id
2140 void in(ISS& env, const bc::BaseC& op) {
2141 assertx(op.arg1 < env.state.stack.size());
2142 auto ty = topC(env, op.arg1);
2143 if (ty.subtypeOf(BBottom)) return unreachable(env);
2144 effect_free(env);
2145 startBase(
2146 env,
2147 Base {
2148 std::move(ty),
2149 BaseLoc::Stack,
2150 TBottom,
2151 SString{},
2152 NoLocalId,
2153 (uint32_t)env.state.stack.size() - op.arg1 - 1
2158 void in(ISS& env, const bc::BaseH&) {
2159 auto const ty = thisTypeNonNull(env);
2160 if (ty.subtypeOf(BBottom)) return unreachable(env);
2161 effect_free(env);
2162 startBase(env, Base{ty, BaseLoc::This});
2165 //////////////////////////////////////////////////////////////////////
2166 // Intermediate operations
2168 void in(ISS& env, const bc::Dim& op) {
2169 auto key = key_type_or_fixup(env, op);
2170 if (!key) return;
2172 auto const effects = [&] {
2173 if (mcodeIsProp(op.mkey.mcode)) {
2174 return miProp(
2175 env, op.mkey.mcode == MQT, op.subop1, std::move(key->first), op.mkey.rop
2177 } else if (mcodeIsElem(op.mkey.mcode)) {
2178 return miElem(env, op.subop1, std::move(key->first), key_local(env, op));
2179 } else {
2180 return miNewElem(env);
2182 }();
2184 if (effects != Effects::None) {
2185 env.collect.mInstrState.effectFree = false;
2187 if (!handleEffects(env, effects, key->second)) return;
2189 // This instruction must be effect free
2190 assertx(env.flags.effectFree);
2191 assertx(!env.state.unreachable);
2193 // If the base is a constant, and we're not mutating the base, and
2194 // if the entire minstr sequence up until now has been effect-free,
2195 // we can remove the entire sequence up until now. We replace it
2196 // with just a BaseC on the constant pushed onto the stack.
2197 if ((op.subop1 == MOpMode::None || op.subop1 == MOpMode::Warn) &&
2198 env.collect.mInstrState.effectFree &&
2199 will_reduce(env) &&
2200 is_scalar(env.collect.mInstrState.base.type)) {
2201 // Find the base instruction which started the sequence.
2202 for (int i = 0; ; i++) {
2203 auto const last = last_op(env, i);
2204 if (!last) break;
2205 if (isMemberBaseOp(last->op)) {
2206 auto const base = *last;
2207 rewind(env, i + 1);
2208 // We'll need to push the constant onto the stack. If the
2209 // sequence originally started with a BaseC (or BaseGC)
2210 // instruction, we can just pop off the original value and
2211 // replace it with the constant. This leaves all offsets the
2212 // same. If not, we push the constant and set extraPop, which
2213 // makes us increment all of the offsets when we reprocess
2214 // them.
2215 auto const reuseStack =
2216 [&] {
2217 switch (base.op) {
2218 case Op::BaseGC: return base.BaseGC.arg1 == 0;
2219 case Op::BaseC: return base.BaseC.arg1 == 0;
2220 default: return false;
2222 }();
2223 assertx(!env.collect.mInstrState.extraPop || reuseStack);
2224 auto const extraPop = !reuseStack || env.collect.mInstrState.extraPop;
2225 env.collect.mInstrState.clear();
2226 if (reuseStack) reduce(env, bc::PopC {});
2227 auto const v = tv(env.collect.mInstrState.base.type);
2228 assertx(v);
2229 reduce(env, gen_constant(*v), bc::BaseC { 0, op.subop1 });
2230 env.collect.mInstrState.extraPop = extraPop;
2231 return;
2233 if (!isMemberDimOp(last->op)) break;
2238 //////////////////////////////////////////////////////////////////////
2239 // Final operations
2241 const StaticString s_classname("classname");
2242 const StaticString s_type_structure("HH\\type_structure");
2243 const StaticString s_type_structure_classname("HH\\type_structure_classname");
2245 void in(ISS& env, const bc::QueryM& op) {
2246 auto const key = key_type_or_fixup(env, op);
2247 if (!key) return;
2248 auto const nDiscard = op.arg1;
2250 auto const effects = [&] {
2251 if (mcodeIsProp(op.mkey.mcode)) {
2252 // We don't currently do anything different for nullsafe query ops.
2253 switch (op.subop2) {
2254 case QueryMOp::CGet:
2255 case QueryMOp::CGetQuiet:
2256 return miFinalCGetProp(env, nDiscard, key->first,
2257 op.subop2 == QueryMOp::CGetQuiet,
2258 op.mkey.rop == ReadOnlyOp::Mutable);
2259 case QueryMOp::Isset:
2260 return miFinalIssetProp(env, nDiscard, key->first);
2261 case QueryMOp::InOut:
2262 always_assert(false);
2264 always_assert(false);
2265 } else if (mcodeIsElem(op.mkey.mcode)) {
2266 switch (op.subop2) {
2267 case QueryMOp::InOut:
2268 case QueryMOp::CGet:
2269 case QueryMOp::CGetQuiet:
2270 return miFinalCGetElem(
2271 env, nDiscard, key->first, getQueryMOpMode(op.subop2)
2273 case QueryMOp::Isset:
2274 return miFinalIssetElem(env, nDiscard, key->first);
2276 always_assert(false);
2277 } else {
2278 // QueryMNewElem will always throw without doing any work.
2279 discard(env, nDiscard);
2280 push(env, TBottom);
2281 return Effects::AlwaysThrows;
2283 }();
2285 // Try to detect type_structure(cls_name, cns_name)['classname'] and
2286 // reduce this to type_structure_classname(cls_name, cns_name)
2287 if (mcodeIsElem(op.mkey.mcode) &&
2288 op.subop2 == QueryMOp::CGet &&
2289 nDiscard == 1 &&
2290 op.mkey.mcode == MemberCode::MET &&
2291 op.mkey.litstr->isame(s_classname.get())) {
2292 if (auto const last = last_op(env, 0)) {
2293 if (last->op == Op::BaseC) {
2294 if (auto const prev = last_op(env, 1)) {
2295 if (prev->op == Op::FCallFuncD &&
2296 prev->FCallFuncD.str2->isame(s_type_structure.get()) &&
2297 prev->FCallFuncD.fca.numArgs() == 2) {
2298 auto const params = prev->FCallFuncD.fca.numArgs();
2299 rewind(env, op); // querym
2300 rewind(env, 2); // basec + fcallfuncd
2301 env.collect.mInstrState.clear();
2302 return reduce(
2303 env,
2304 bc::FCallFuncD {
2305 FCallArgs(params),
2306 s_type_structure_classname.get()
2315 if (effects != Effects::None) env.collect.mInstrState.effectFree = false;
2317 // For the QueryM ops, its our responsibility to call endBase()
2318 // (unless we'll always throw).
2320 if (!handleEffects(env, effects, key->second)) {
2321 if (effects != Effects::AlwaysThrows) endBase(env, false);
2322 return;
2325 assertx(env.flags.effectFree);
2326 assertx(!env.state.unreachable);
2328 // If the QueryM produced a constant without any possible
2329 // side-ffects, we can replace the entire thing with the constant.
2330 if (env.collect.mInstrState.effectFree && is_scalar(topC(env))) {
2331 for (int i = 0; ; i++) {
2332 auto const last = last_op(env, i);
2333 if (!last) break;
2334 if (isMemberBaseOp(last->op)) {
2335 auto const v = tv(topC(env));
2336 rewind(env, op);
2337 rewind(env, i + 1);
2338 env.collect.mInstrState.clear();
2339 BytecodeVec bcs{nDiscard, bc::PopC{}};
2340 bcs.push_back(gen_constant(*v));
2341 return reduce(env, std::move(bcs));
2343 if (!isMemberDimOp(last->op)) break;
2346 endBase(env, false);
2349 void in(ISS& env, const bc::SetM& op) {
2350 auto const key = key_type_or_fixup(env, op);
2351 if (!key) return;
2353 auto const effects = [&] {
2354 if (mcodeIsProp(op.mkey.mcode)) {
2355 return miFinalSetProp(env, op.arg1, key->first, op.mkey.rop);
2356 } else if (mcodeIsElem(op.mkey.mcode)) {
2357 return miFinalSetElem(env, op.arg1, key->first, key_local(env, op));
2358 } else {
2359 return miFinalSetNewElem(env, op.arg1);
2361 }();
2362 handleEffects(env, effects, key->second);
2365 void in(ISS& env, const bc::SetRangeM& op) {
2366 popC(env);
2367 popC(env);
2368 popC(env);
2369 discard(env, op.arg1);
2370 auto& base = env.collect.mInstrState.base.type;
2371 if (!base.couldBe(BStr)) return unreachable(env);
2372 base = loosen_staticness(loosen_values(std::move(base)));
2373 endBase(env);
2376 void in(ISS& env, const bc::IncDecM& op) {
2377 auto const key = key_type_or_fixup(env, op);
2378 if (!key) return;
2380 auto const effects = [&] {
2381 if (mcodeIsProp(op.mkey.mcode)) {
2382 return miFinalIncDecProp(env, op.arg1, op.subop2, key->first);
2383 } else if (mcodeIsElem(op.mkey.mcode)) {
2384 return miFinalIncDecElem(
2385 env, op.arg1, op.subop2, key->first, key_local(env, op)
2387 } else {
2388 return miFinalIncDecNewElem(env, op.arg1);
2390 }();
2391 handleEffects(env, effects, key->second);
2394 void in(ISS& env, const bc::SetOpM& op) {
2395 auto const key = key_type_or_fixup(env, op);
2396 if (!key) return;
2398 auto const effects = [&] {
2399 if (mcodeIsProp(op.mkey.mcode)) {
2400 return miFinalSetOpProp(env, op.arg1, op.subop2, key->first);
2401 } else if (mcodeIsElem(op.mkey.mcode)) {
2402 return miFinalSetOpElem(
2403 env, op.arg1, op.subop2, key->first, key_local(env, op)
2405 } else {
2406 return miFinalSetOpNewElem(env, op.arg1);
2408 }();
2409 handleEffects(env, effects, key->second);
2412 void in(ISS& env, const bc::UnsetM& op) {
2413 auto const key = key_type_or_fixup(env, op);
2414 if (!key) return;
2416 auto const effects = [&] {
2417 if (mcodeIsProp(op.mkey.mcode)) {
2418 return miFinalUnsetProp(env, op.arg1, key->first);
2419 } else {
2420 assertx(mcodeIsElem(op.mkey.mcode));
2421 return miFinalUnsetElem(env, op.arg1, key->first);
2423 }();
2424 handleEffects(env, effects, key->second);
2429 //////////////////////////////////////////////////////////////////////