use new hugify method only in fb builds
[hiphop-php.git] / hphp / hhbbc / interp-minstr.cpp
blob4babaf405a50adc769a232dc7f46d95e5e74289e
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 const StaticString s_stdClass("stdClass");
40 //////////////////////////////////////////////////////////////////////
43 * Note: the couldBe comparisons here with sempty() are asking "can this string
44 * be a non-reference counted empty string". What actually matters is whether
45 * it can be an empty string at all. Currently, all reference counted strings
46 * are TStr, which has no values and may also be non-reference
47 * counted---emptiness isn't separately tracked like it is for arrays, so if
48 * anything happened that could make it reference counted this check will
49 * return true.
51 * This means this code is fine for now, but if we implement #3837503
52 * (non-static strings with values in the type system) it will need to change.
55 bool couldBeEmptyish(const Type& ty) {
56 return ty.couldBe(BNull | BFalse) || ty.couldBe(sempty());
59 bool mustBeEmptyish(const Type& ty) {
60 return ty.subtypeOf(BNull | BFalse) || ty.subtypeOf(sempty());
63 bool elemCouldPromoteToArr(const Type& ty) { return couldBeEmptyish(ty); }
64 bool elemMustPromoteToArr(const Type& ty) { return mustBeEmptyish(ty); }
66 bool propCouldPromoteToObj(const Type& ty) {
67 return RuntimeOption::EvalPromoteEmptyObject && couldBeEmptyish(ty);
70 bool propMustPromoteToObj(const Type& ty) {
71 return RuntimeOption::EvalPromoteEmptyObject && mustBeEmptyish(ty);
74 bool keyCouldBeWeird(const Type& key) {
75 return key.couldBe(BObj | BArr | BVec | BDict | BKeyset);
78 bool mustBeArrLike(const Type& ty) {
79 return ty.subtypeOf(BArr | BVec | BDict | BKeyset);
82 //////////////////////////////////////////////////////////////////////
84 Type baseLocNameType(const Base& b) {
85 return b.locName ? sval(b.locName) : TInitGen;
88 //////////////////////////////////////////////////////////////////////
91 * A note about bases.
93 * Generally type inference needs to know two kinds of things about the base to
94 * handle effects on tracked locations:
96 * - Could the base be a location we're tracking deeper structure on, so the
97 * next operation actually affects something inside of it. For example,
98 * could the base be an object with the same type as $this, or an array in a
99 * local variable.
101 * - Could the base be something (regardless of type) that is inside one of
102 * the things we're tracking. I.e., the base might be whatever (an array or
103 * a bool or something), but living inside a property inside an object with
104 * the same type as $this, or living inside of an array in the local frame.
106 * The first cases apply because final operations are going to directly affect
107 * the type of these elements. The second case is because vector operations may
108 * change the base at each step if it is a defining instruction.
110 * Note that both of these cases can apply to the same base in some cases: you
111 * might have an object property on $this that could be an object of the type of
112 * $this.
114 * The functions below with names "couldBeIn*" detect the second case. The
115 * effects on the tracked location in the second case are handled in the
116 * functions with names "promoteIn*{Prop,Elem,..}". The effects for the first
117 * case are generally handled in the miFinal op functions.
119 * Control flow insensitive vs. control flow sensitive types:
121 * Things are also slightly complicated by the fact that we are analyzing some
122 * control flow insensitve types along side precisely tracked types. For
123 * effects on locals, we perform the type effects of each operation on
124 * base.type, and then allow updateBaseWithType() to make the updates to the
125 * local when we know what its final type will be.
127 * This approach doesn't do as well for possible properties in $this or self::,
128 * because we may see situations where the base could be one of these properties
129 * but we're not sure---perhaps because it came off a property with the same
130 * name on an object with an unknown type (i.e. base.type is InitCell but
131 * couldBeInProp is true). In these situations, we can get away with just
132 * merging Obj=stdClass into the thisProp (because it 'could' promote) instead
133 * of merging the whole InitCell, which possibly lets us leave the type at ?Obj
134 * in some cases.
136 * This is why there's two fairly different mechanisms for handling the effects
137 * of defining ops on base types.
140 //////////////////////////////////////////////////////////////////////
142 bool couldBeThisObj(ISS& env, const Base& b) {
143 auto const thisTy = thisTypeFromContext(env.index, env.ctx);
144 return b.type.couldBe(thisTy ? *thisTy : TObj);
147 bool mustBeThisObj(ISS& env, const Base& b) {
148 if (b.loc == BaseLoc::This) return true;
149 if (auto const ty = thisTypeFromContext(env.index, env.ctx)) {
150 return b.type.subtypeOf(*ty);
152 return false;
155 bool mustBeInLocal(const Base& b) {
156 return b.loc == BaseLoc::Local;
159 bool mustBeInStack(const Base& b) {
160 return b.loc == BaseLoc::Stack;
163 bool couldBeInProp(ISS& env, const Base& b) {
164 if (b.loc != BaseLoc::Prop) return false;
165 auto const thisTy = thisTypeFromContext(env.index, env.ctx);
166 if (!thisTy) return true;
167 if (!b.locTy.couldBe(*thisTy)) return false;
168 if (b.locName) return isTrackedThisProp(env, b.locName);
169 return true;
172 bool couldBeInPrivateStatic(ISS& env, const Base& b) {
173 if (b.loc != BaseLoc::StaticProp) return false;
174 auto const selfTy = selfCls(env);
175 return !selfTy || b.locTy.couldBe(*selfTy);
178 bool couldBeInPublicStatic(const Base& b) {
179 return b.loc == BaseLoc::StaticProp;
182 //////////////////////////////////////////////////////////////////////
184 // Base locations that only occur at the start of a minstr sequence.
185 bool isInitialBaseLoc(BaseLoc loc) {
186 return
187 loc == BaseLoc::Local ||
188 loc == BaseLoc::Stack ||
189 loc == BaseLoc::StaticProp ||
190 loc == BaseLoc::This ||
191 loc == BaseLoc::Global;
194 // Base locations that only occur after the start of a minstr sequence.
195 bool isDimBaseLoc(BaseLoc loc) {
196 return loc == BaseLoc::Elem || loc == BaseLoc::Prop;
199 //////////////////////////////////////////////////////////////////////
202 * If the current base is an array-like, update it via *_set, and return true;
203 * otherwise, return false.
205 bool array_do_set(ISS& env, const Type& key, const Type& value) {
206 auto& base = env.collect.mInstrState.base.type;
207 auto const tag = provTagHere(env);
208 auto res = [&] () -> folly::Optional<std::pair<Type,ThrowMode>> {
209 if (base.subtypeOf(BArr)) {
210 return array_set(std::move(base), key, value, tag);
211 } else if (base.subtypeOf(BVec)) {
212 return vec_set(std::move(base), key, value, tag);
213 } else if (base.subtypeOf(BDict)) {
214 return dict_set(std::move(base), key, value, tag);
215 } else if (base.subtypeOf(BKeyset)) {
216 return keyset_set(std::move(base), key, value);
218 return folly::none;
219 }();
220 if (!res) return false;
222 switch (res->second) {
223 case ThrowMode::None:
224 nothrow(env);
225 break;
226 case ThrowMode::MaybeMissingElement:
227 case ThrowMode::MissingElement:
228 case ThrowMode::MaybeBadKey:
229 case ThrowMode::BadOperation:
230 break;
233 if (res->first == TBottom) {
234 unreachable(env);
237 base = std::move(res->first);
238 return true;
242 * If the current base is an array-like, return the best known type
243 * for base[key].
245 folly::Optional<Type> array_do_elem(ISS& env,
246 bool nullOnMissing,
247 const Type& key) {
248 auto const& base = env.collect.mInstrState.base.type;
249 auto res = [&] () -> folly::Optional<std::pair<Type,ThrowMode>> {
250 if (base.subtypeOf(BArr)) return array_elem(base, key);
251 if (base.subtypeOf(BVec)) return vec_elem(base, key);
252 if (base.subtypeOf(BDict)) return dict_elem(base, key);
253 if (base.subtypeOf(BKeyset)) return keyset_elem(base, key);
254 return folly::none;
255 }();
256 if (!res) return folly::none;
258 switch (res->second) {
259 case ThrowMode::None:
260 nothrow(env);
261 break;
262 case ThrowMode::MaybeMissingElement:
263 case ThrowMode::MissingElement:
264 if (nullOnMissing) {
265 nothrow(env);
266 res->first |= TInitNull;
268 break;
269 case ThrowMode::MaybeBadKey:
270 if (nullOnMissing) {
271 res->first |= TInitNull;
273 break;
274 case ThrowMode::BadOperation:
275 break;
278 if (res->first == TBottom) {
279 unreachable(env);
282 return std::move(res->first);
286 * If the current base is an array-like, update it via *_newelem, and
287 * return the best known type for the key added; otherwise return folly::none.
289 folly::Optional<Type> array_do_newelem(ISS& env, const Type& value) {
290 auto& base = env.collect.mInstrState.base.type;
291 auto const tag = provTagHere(env);
292 auto res = [&] () -> folly::Optional<std::pair<Type,Type>> {
293 if (base.subtypeOf(BArr)) {
294 return array_newelem(std::move(base), value, tag);
295 } else if (base.subtypeOf(BVec)) {
296 return vec_newelem(std::move(base), value, tag);
297 } else if (base.subtypeOf(BDict)) {
298 return dict_newelem(std::move(base), value, tag);
299 } else if (base.subtypeOf(BKeyset)) {
300 return keyset_newelem(std::move(base), value);
302 return folly::none;
303 }();
304 if (!res) return folly::none;
305 base = std::move(res->first);
306 return res->second;
309 //////////////////////////////////////////////////////////////////////
311 // MInstrs can throw in between each op, so the states of locals
312 // need to be propagated across throw exit edges.
313 void miThrow(ISS& env) {
314 if (env.blk.throwExit != NoBlockId) {
315 auto const stackLess = with_throwable_only(env.index, env.state);
316 env.propagate(env.blk.throwExit, &stackLess);
320 //////////////////////////////////////////////////////////////////////
322 void setLocalForBase(ISS& env, Type ty, LocalId firstKeyLoc) {
323 assert(mustBeInLocal(env.collect.mInstrState.base));
324 if (env.collect.mInstrState.base.locLocal == NoLocalId) {
325 return loseNonRefLocalTypes(env);
327 FTRACE(4, " ${} := {}\n",
328 env.collect.mInstrState.base.locName
329 ? env.collect.mInstrState.base.locName->data()
330 : "$<unnamed>",
331 show(ty)
333 setLoc(
334 env,
335 env.collect.mInstrState.base.locLocal,
336 std::move(ty),
337 firstKeyLoc
341 void setStackForBase(ISS& env, Type ty) {
342 assert(mustBeInStack(env.collect.mInstrState.base));
344 auto const locSlot = env.collect.mInstrState.base.locSlot;
345 FTRACE(4, " stk[{:02}] := {}\n", locSlot, show(ty));
346 assert(locSlot < env.state.stack.size());
348 auto const& oldTy = env.state.stack[locSlot].type;
349 if (oldTy.subtypeOf(BInitCell)) {
350 env.state.stack[locSlot] = StackElem { std::move(ty), NoLocalId };
354 void setPrivateStaticForBase(ISS& env, Type ty) {
355 assert(couldBeInPrivateStatic(env, env.collect.mInstrState.base));
357 if (auto const name = env.collect.mInstrState.base.locName) {
358 FTRACE(4, " self::${} |= {}\n", name->data(), show(ty));
359 mergeSelfProp(env, name, std::move(ty));
360 return;
362 FTRACE(4, " self::* |= {}\n", show(ty));
363 mergeEachSelfPropRaw(
364 env,
365 [&](const Type& old){ return old.subtypeOf(BInitCell) ? ty : TBottom; }
369 void setPublicStaticForBase(ISS& env, Type ty) {
370 auto const& base = env.collect.mInstrState.base;
371 assertx(couldBeInPublicStatic(base));
373 auto const nameTy = base.locName ? sval(base.locName) : TStr;
374 FTRACE(
375 4, " public ({})::$({}) |= {}\n",
376 show(base.locTy), show(nameTy), show(ty)
378 env.collect.publicSPropMutations.merge(
379 env.index, env.ctx, base.locTy, nameTy, ty
383 // Run backwards through an array chain doing array_set operations
384 // to produce the array type that incorporates the effects of any
385 // intermediate defining dims.
386 Type currentChainType(ISS& env, Type val) {
387 auto it = env.collect.mInstrState.arrayChain.end();
388 auto const tag = provTagHere(env);
389 while (it != env.collect.mInstrState.arrayChain.begin()) {
390 --it;
391 if (it->base.subtypeOf(BArr)) {
392 val = array_set(it->base, it->key, val, tag).first;
393 } else if (it->base.subtypeOf(BVec)) {
394 val = vec_set(it->base, it->key, val, tag).first;
395 if (val == TBottom) val = TVec;
396 } else if (it->base.subtypeOf(BDict)) {
397 val = dict_set(it->base, it->key, val, tag).first;
398 if (val == TBottom) val = TDict;
399 } else {
400 assert(it->base.subtypeOf(BKeyset));
401 val = keyset_set(it->base, it->key, val).first;
402 if (val == TBottom) val = TKeyset;
405 return val;
408 Type resolveArrayChain(ISS& env, Type val) {
409 static UNUSED const char prefix[] = " ";
410 FTRACE(5, "{}chain\n", prefix, show(val));
411 auto const tag = provTagHere(env);
412 do {
413 auto arr = std::move(env.collect.mInstrState.arrayChain.back().base);
414 auto key = std::move(env.collect.mInstrState.arrayChain.back().key);
415 env.collect.mInstrState.arrayChain.pop_back();
416 FTRACE(5, "{} | {} := {} in {}\n", prefix,
417 show(key), show(val), show(arr));
418 if (arr.subtypeOf(BVec)) {
419 val = vec_set(std::move(arr), key, val, tag).first;
420 if (val == TBottom) val = TVec;
421 } else if (arr.subtypeOf(BDict)) {
422 val = dict_set(std::move(arr), key, val, tag).first;
423 if (val == TBottom) val = TDict;
424 } else if (arr.subtypeOf(BKeyset)) {
425 val = keyset_set(std::move(arr), key, val).first;
426 if (val == TBottom) val = TKeyset;
427 } else {
428 assert(arr.subtypeOf(BArr));
429 val = array_set(std::move(arr), key, val, tag).first;
431 } while (!env.collect.mInstrState.arrayChain.empty());
432 FTRACE(5, "{} = {}\n", prefix, show(val));
433 return val;
436 void updateBaseWithType(ISS& env,
437 const Type& ty,
438 LocalId firstKeyLoc = NoLocalId) {
439 FTRACE(6, " updateBaseWithType: {}\n", show(ty));
441 auto const& base = env.collect.mInstrState.base;
443 if (mustBeInLocal(base)) {
444 setLocalForBase(env, ty, firstKeyLoc);
445 return miThrow(env);
447 if (mustBeInStack(base)) {
448 return setStackForBase(env, ty);
451 if (couldBeInPrivateStatic(env, base)) setPrivateStaticForBase(env, ty);
452 if (couldBeInPublicStatic(base)) setPublicStaticForBase(env, ty);
455 void startBase(ISS& env, Base base) {
456 auto& oldState = env.collect.mInstrState;
457 assert(oldState.base.loc == BaseLoc::None);
458 assert(oldState.arrayChain.empty());
459 assert(isInitialBaseLoc(base.loc));
460 assert(!base.type.subtypeOf(TBottom));
462 oldState.noThrow = !env.flags.wasPEI;
463 oldState.extraPop = false;
464 oldState.base = std::move(base);
465 FTRACE(5, " startBase: {}\n", show(*env.ctx.func, oldState.base));
468 void endBase(ISS& env, bool update = true, LocalId keyLoc = NoLocalId) {
469 auto& state = env.collect.mInstrState;
470 assert(state.base.loc != BaseLoc::None);
472 FTRACE(5, " endBase: {}\n", show(*env.ctx.func, state.base));
474 auto const firstKeyLoc = state.arrayChain.empty()
475 ? keyLoc
476 : state.arrayChain.data()->keyLoc;
477 auto const& ty = state.arrayChain.empty()
478 ? state.base.type
479 : resolveArrayChain(env, state.base.type);
481 if (update) updateBaseWithType(env, ty, firstKeyLoc);
482 state.base.loc = BaseLoc::None;
485 void moveBase(ISS& env,
486 Base newBase,
487 bool update = true,
488 LocalId keyLoc = NoLocalId) {
489 auto& state = env.collect.mInstrState;
490 assert(state.base.loc != BaseLoc::None);
491 assert(isDimBaseLoc(newBase.loc));
492 assert(!state.base.type.subtypeOf(BBottom));
494 FTRACE(5, " moveBase: {} -> {}\n",
495 show(*env.ctx.func, state.base),
496 show(*env.ctx.func, newBase));
498 if (newBase.loc == BaseLoc::Elem) {
499 state.base.type =
500 add_nonemptiness(
501 loosen_staticness(loosen_values(state.base.type))
505 auto const firstKeyLoc = state.arrayChain.empty()
506 ? keyLoc
507 : state.arrayChain.data()->keyLoc;
508 auto const& ty = state.arrayChain.empty()
509 ? state.base.type
510 : resolveArrayChain(env, state.base.type);
512 if (update) updateBaseWithType(env, ty, firstKeyLoc);
513 state.base = std::move(newBase);
516 void extendArrChain(ISS& env, Type key, Type arr,
517 Type val, bool update = true,
518 LocalId keyLoc = NoLocalId) {
519 auto& state = env.collect.mInstrState;
520 assert(state.base.loc != BaseLoc::None);
521 assert(mustBeArrLike(arr));
522 assert(!state.base.type.subtypeOf(BBottom));
523 assert(!val.subtypeOf(BBottom));
525 state.arrayChain.emplace_back(
526 CollectedInfo::MInstrState::ArrayChainEnt{std::move(arr), std::move(key), keyLoc}
528 state.base.type = std::move(val);
530 auto const firstKeyLoc = state.arrayChain.data()->keyLoc;
532 FTRACE(5, " extendArrChain: {}\n", show(*env.ctx.func, state));
533 if (update) {
534 updateBaseWithType(
535 env,
536 currentChainType(env, state.base.type),
537 firstKeyLoc
542 //////////////////////////////////////////////////////////////////////
545 * The following promoteBase{Elem,Prop}* functions are used to implement the
546 * 'normal' portion of the effects on base types, which are mostly what are done
547 * by intermediate dims.
549 * The contract with these functions is that they should handle all the effects
550 * on the base type /except/ for the case of the base being an array
551 * subtype---the caller is responsible for that. The reason for this is that for
552 * tracking effects on specialized array types, the final ops generally need to
553 * do completely different things to the array, so this allows reuse of this
554 * shared part of the type transitions. The intermediate routines must handle
555 * array subtypes outside of calls to this as well.
558 void promoteBaseElemU(ISS& env) {
559 // We're conservative with unsets on array types for now.
560 env.collect.mInstrState.base.type = loosen_all(env.collect.mInstrState.base.type);
563 void promoteBasePropD(ISS& env, bool isNullsafe) {
564 auto& ty = env.collect.mInstrState.base.type;
566 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
567 if (isNullsafe || ty.subtypeOf(BObj)) return;
569 if (propMustPromoteToObj(ty)) {
570 ty = objExact(env.index.builtin_class(s_stdClass.get()));
571 return;
573 if (propCouldPromoteToObj(ty)) {
574 ty = promote_emptyish(ty, TObj);
575 return;
579 void promoteBaseElemD(ISS& env) {
580 auto& ty = env.collect.mInstrState.base.type;
582 // When the base is actually a subtype of array, we handle it in the callers
583 // of these functions.
584 if (mustBeArrLike(ty)) return;
586 if (elemMustPromoteToArr(ty)) {
587 ty = some_aempty();
588 return;
591 // Intermediate ElemD operations on strings fatal, unless the string is empty,
592 // which promotes to array. So for any string here we can assume it promoted
593 // to an empty array.
594 if (ty.subtypeOf(BStr)) {
595 ty = some_aempty();
596 return;
599 if (elemCouldPromoteToArr(ty)) {
600 ty = promote_emptyish(ty, some_aempty());
601 return;
605 * If the base still could be some kind of array (but isn't an array sub-type
606 * which would be handled outside this routine), we need to give up on any
607 * better information here (or track the effects, but we're not doing that
608 * yet).
610 ty = loosen_arrays(ty);
613 void promoteBaseNewElem(ISS& env) {
614 promoteBaseElemD(env);
615 // Technically we don't need to do TStr case.
618 //////////////////////////////////////////////////////////////////////
620 void handleInPublicStaticElemD(ISS& env) {
621 auto const& base = env.collect.mInstrState.base;
622 if (!couldBeInPublicStatic(base)) return;
624 auto const name = baseLocNameType(base);
625 auto const ty = env.index.lookup_public_static(env.ctx, base.locTy, name);
626 if (elemCouldPromoteToArr(ty)) {
627 // Might be possible to only merge a TArrE, but for now this is ok.
628 env.collect.publicSPropMutations.merge(
629 env.index, env.ctx, base.locTy, name, TArr
634 void handleInThisElemD(ISS& env) {
635 if (!couldBeInProp(env, env.collect.mInstrState.base)) return;
637 if (auto const name = env.collect.mInstrState.base.locName) {
638 auto const ty = thisPropAsCell(env, name);
639 if (ty && elemCouldPromoteToArr(*ty)) {
640 mergeThisProp(env, name, TArr);
642 return;
645 mergeEachThisPropRaw(env, [&] (const Type& t) {
646 return elemCouldPromoteToArr(t) ? TArr : TBottom;
650 void handleInPublicStaticPropD(ISS& env, bool isNullsafe) {
651 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
652 if (isNullsafe) return;
654 auto const& base = env.collect.mInstrState.base;
655 if (!couldBeInPublicStatic(base)) return;
657 auto const name = baseLocNameType(base);
658 auto const ty = env.index.lookup_public_static(env.ctx, base.locTy, name);
659 if (propCouldPromoteToObj(ty)) {
660 env.collect.publicSPropMutations.merge(
661 env.index, env.ctx, base.locTy, name,
662 objExact(env.index.builtin_class(s_stdClass.get()))
667 void handleInThisPropD(ISS& env, bool isNullsafe) {
668 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
669 if (isNullsafe) return;
671 if (!couldBeInProp(env, env.collect.mInstrState.base)) return;
673 if (auto const name = env.collect.mInstrState.base.locName) {
674 auto const ty = thisPropAsCell(env, name);
675 if (ty && propCouldPromoteToObj(*ty)) {
676 mergeThisProp(env, name,
677 objExact(env.index.builtin_class(s_stdClass.get())));
679 return;
682 if (RuntimeOption::EvalPromoteEmptyObject) {
683 mergeEachThisPropRaw(env, [&] (const Type& t) {
684 return propCouldPromoteToObj(t) ? TObj : TBottom;
689 // Currently NewElem and Elem InFoo effects don't need to do
690 // anything different from each other.
691 void handleInPublicStaticNewElem(ISS& env) { handleInPublicStaticElemD(env); }
692 void handleInThisNewElem(ISS& env) { handleInThisElemD(env); }
694 void handleInPublicStaticElemU(ISS& env) {
695 auto const& base = env.collect.mInstrState.base;
696 if (!couldBeInPublicStatic(base)) return;
699 * Merging InitCell is correct, but very conservative, for now.
701 auto const name = baseLocNameType(base);
702 env.collect.publicSPropMutations.merge(
703 env.index, env.ctx, base.locTy, name, TInitCell
707 //////////////////////////////////////////////////////////////////////
709 // Returns nullptr if it's an unknown key or not a string.
710 SString mStringKey(const Type& key) {
711 auto const v = tv(key);
712 return v && v->m_type == KindOfPersistentString ? v->m_data.pstr : nullptr;
715 template<typename Op>
716 auto update_mkey(const Op& op) { return false; }
718 template<typename Op>
719 auto update_mkey(Op& op) -> decltype(op.mkey, true) {
720 switch (op.mkey.mcode) {
721 case MEC: case MPC: {
722 op.mkey.idx++;
723 return true;
725 default:
726 return false;
730 template<typename Op>
731 auto update_discard(const Op& op) { return false; }
733 template<typename Op>
734 auto update_discard(Op& op) -> decltype(op.arg1, true) {
735 op.arg1++;
736 return true;
740 * Return the type of the key, or reduce op, and return folly::none.
741 * Note that when folly::none is returned, there is nothing further to
742 * do.
744 template<typename Op>
745 folly::Optional<Type> key_type_or_fixup(ISS& env, Op op) {
746 if (env.collect.mInstrState.extraPop) {
747 auto const mkey = update_mkey(op);
748 if (update_discard(op) || mkey) {
749 env.collect.mInstrState.extraPop = false;
750 reduce(env, op);
751 env.collect.mInstrState.extraPop = true;
752 return folly::none;
755 auto fixup = [&] (Type ty, bool isProp) -> folly::Optional<Type> {
756 if (auto const val = tv(ty)) {
757 if (isStringType(val->m_type)) {
758 op.mkey.mcode = isProp ? MPT : MET;
759 op.mkey.litstr = val->m_data.pstr;
760 reduce(env, op);
761 return folly::none;
763 if (!isProp && val->m_type == KindOfInt64) {
764 op.mkey.mcode = MEI;
765 op.mkey.int64 = val->m_data.num;
766 reduce(env, op);
767 return folly::none;
770 return std::move(ty);
772 switch (op.mkey.mcode) {
773 case MEC: case MPC:
774 return fixup(topC(env, op.mkey.idx), op.mkey.mcode == MPC);
775 case MEL: case MPL:
776 return fixup(locAsCell(env, op.mkey.local), op.mkey.mcode == MPL);
777 case MW:
778 return TBottom;
779 case MEI:
780 return ival(op.mkey.int64);
781 case MET: case MPT: case MQT:
782 return sval(op.mkey.litstr);
784 not_reached();
787 template<typename Op>
788 LocalId key_local(ISS& env, Op op) {
789 switch (op.mkey.mcode) {
790 case MEC: case MPC:
791 return topStkLocal(env, op.mkey.idx);
792 case MEL: case MPL:
793 return op.mkey.local;
794 case MW:
795 case MEI:
796 case MET: case MPT: case MQT:
797 return NoLocalId;
799 not_reached();
802 //////////////////////////////////////////////////////////////////////
803 // base ops
805 Base miBaseLocal(ISS& env, LocalId locBase, MOpMode mode) {
806 auto const locName = env.ctx.func->locals[locBase].name;
807 auto const isDefine = mode == MOpMode::Define;
808 if (mode == MOpMode::None ||
809 (mode == MOpMode::Warn && !locCouldBeUninit(env, locBase))) {
810 nothrow(env);
812 // If we're changing the local to define it, we don't need to do an miThrow
813 // yet---the promotions (to array or stdClass) on previously uninitialized
814 // locals happen before raising warnings that could throw, so we can wait
815 // until the first moveBase.
816 return Base { isDefine ? locAsCell(env, locBase) : derefLoc(env, locBase),
817 BaseLoc::Local,
818 TBottom,
819 locName,
820 locBase };
823 Base miBaseSProp(ISS& env, Type cls, const Type& tprop) {
824 auto const self = selfCls(env);
825 auto const prop = tv(tprop);
826 auto const name = prop && prop->m_type == KindOfPersistentString
827 ? prop->m_data.pstr : nullptr;
828 if (self && cls.subtypeOf(*self) && name) {
829 if (auto ty = selfPropAsCell(env, name)) {
830 return
831 Base { std::move(*ty), BaseLoc::StaticProp, std::move(cls), name };
834 auto indexTy = env.index.lookup_public_static(env.ctx, cls, tprop);
835 if (!indexTy.subtypeOf(BInitCell)) indexTy = TInitCell;
836 return Base { std::move(indexTy),
837 BaseLoc::StaticProp,
838 std::move(cls),
839 name };
842 //////////////////////////////////////////////////////////////////////
843 // intermediate ops
845 void miProp(ISS& env, bool isNullsafe, MOpMode mode, Type key) {
846 auto const name = mStringKey(key);
847 auto const isDefine = mode == MOpMode::Define;
848 auto const isUnset = mode == MOpMode::Unset;
849 // PHP5 doesn't modify an unset local if you unset a property or
850 // array elem on it, but hhvm does (it promotes it to init-null).
851 auto const update = isDefine || isUnset;
853 * MOpMode::Unset Props doesn't promote "emptyish" things to stdClass, or
854 * affect arrays, however it can define a property on an object base. This
855 * means we don't need any couldBeInFoo logic, but if the base could actually
856 * be $this, and a declared property could be Uninit, we need to merge
857 * InitNull.
859 * We're trying to handle this case correctly as far as the type inference
860 * here is concerned, but the runtime doesn't actually behave this way right
861 * now for declared properties. Note that it never hurts to merge more types
862 * than a thisProp could actually be, so this is fine.
864 * See TODO(#3602740): unset with intermediate dims on previously declared
865 * properties doesn't define them to null.
867 if (isUnset && couldBeThisObj(env, env.collect.mInstrState.base)) {
868 if (name) {
869 auto const elem = thisPropRaw(env, name);
870 if (elem && elem->ty.couldBe(BUninit)) {
871 mergeThisProp(env, name, TInitNull);
873 } else {
874 mergeEachThisPropRaw(env, [&] (const Type& ty) {
875 return ty.couldBe(BUninit) ? TInitNull : TBottom;
880 if (isDefine) {
881 handleInThisPropD(env, isNullsafe);
882 handleInPublicStaticPropD(env, isNullsafe);
883 promoteBasePropD(env, isNullsafe);
886 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
887 auto const optThisTy = thisTypeFromContext(env.index, env.ctx);
888 auto const thisTy = optThisTy ? *optThisTy : TObj;
889 if (name) {
890 auto const ty = [&] {
891 if (auto const propTy = thisPropAsCell(env, name)) return *propTy;
892 return to_cell(
893 env.index.lookup_public_prop(objcls(thisTy), sval(name))
895 }();
897 if (ty.subtypeOf(BBottom)) return unreachable(env);
898 moveBase(
899 env,
900 Base { ty, BaseLoc::Prop, thisTy, name },
901 update
903 } else {
904 moveBase(env,
905 Base { TInitCell, BaseLoc::Prop, thisTy },
906 update);
908 return;
911 // We know for sure we're going to be in an object property.
912 if (env.collect.mInstrState.base.type.subtypeOf(BObj)) {
913 auto const ty = to_cell(
914 env.index.lookup_public_prop(
915 objcls(env.collect.mInstrState.base.type),
916 name ? sval(name) : TStr
919 if (ty.subtypeOf(BBottom)) return unreachable(env);
920 moveBase(env,
921 Base { ty,
922 BaseLoc::Prop,
923 env.collect.mInstrState.base.type,
924 name },
925 update);
926 return;
930 * Otherwise, intermediate props with define can promote a null, false, or ""
931 * to stdClass. Those cases, and others, if it's MOpMode::Define, will set
932 * the base to a null value in tvScratch. The base may also legitimately be
933 * an object and our next base is in an object property. Conservatively treat
934 * all these cases as "possibly" being inside of an object property with
935 * "Prop" with locType TTop.
937 moveBase(env,
938 Base { TInitCell, BaseLoc::Prop, TTop, name },
939 update);
942 void miElem(ISS& env, MOpMode mode, Type key, LocalId keyLoc) {
943 auto const isDefine = mode == MOpMode::Define;
944 auto const isUnset = mode == MOpMode::Unset;
945 auto const update = isDefine || isUnset;
947 if (isUnset) {
949 * Elem dims with MOpMode::Unset can change a base from a static array into
950 * a reference counted array. It never promotes emptyish types, however.
952 handleInPublicStaticElemU(env);
953 promoteBaseElemU(env);
955 if (auto ty = array_do_elem(env, false, key)) {
956 if (ty->subtypeOf(BBottom)) return unreachable(env);
957 moveBase(
958 env,
959 Base {
960 std::move(*ty), BaseLoc::Elem,
961 env.collect.mInstrState.base.type
963 update,
964 keyLoc
966 return;
970 if (isDefine) {
971 handleInThisElemD(env);
972 handleInPublicStaticElemD(env);
973 promoteBaseElemD(env);
976 if (auto ty = array_do_elem(env, mode == MOpMode::None, key)) {
977 if (ty->subtypeOf(BBottom)) return unreachable(env);
978 extendArrChain(
979 env, std::move(key), env.collect.mInstrState.base.type,
980 std::move(*ty),
981 update,
982 keyLoc
984 return;
987 if (env.collect.mInstrState.base.type.subtypeOf(BStr)) {
988 moveBase(env, Base { TStr, BaseLoc::Elem }, update);
989 return;
993 * Other cases could leave the base as anything (if nothing else, via
994 * ArrayAccess on an object).
996 * The resulting BaseLoc is either inside an array, is the global
997 * init_null_variant, or inside tvScratch. We represent this with the
998 * Elem base location with locType TTop.
1000 moveBase(env, Base { TInitCell, BaseLoc::Elem, TTop }, update, keyLoc);
1003 void miNewElem(ISS& env) {
1004 handleInThisNewElem(env);
1005 handleInPublicStaticNewElem(env);
1006 promoteBaseNewElem(env);
1008 if (auto kty = array_do_newelem(env, TInitNull)) {
1009 extendArrChain(env,
1010 std::move(*kty),
1011 std::move(env.collect.mInstrState.base.type),
1012 TInitNull);
1013 } else {
1014 moveBase(env, Base { TInitCell, BaseLoc::Elem, TTop });
1018 //////////////////////////////////////////////////////////////////////
1019 // final prop ops
1021 void miFinalIssetProp(ISS& env, int32_t nDiscard, const Type& key) {
1022 auto const name = mStringKey(key);
1023 discard(env, nDiscard);
1024 if (name && mustBeThisObj(env, env.collect.mInstrState.base)) {
1025 if (auto const pt = thisPropAsCell(env, name)) {
1026 if (isMaybeLateInitThisProp(env, name)) {
1027 // LateInit props can always be maybe unset, except if its never set at
1028 // all.
1029 return push(env, pt->subtypeOf(BBottom) ? TFalse : TBool);
1031 if (pt->subtypeOf(BNull)) return push(env, TFalse);
1032 if (!pt->couldBe(BNull)) return push(env, TTrue);
1035 push(env, TBool);
1038 void miFinalCGetProp(ISS& env, int32_t nDiscard, const Type& key) {
1039 auto const name = mStringKey(key);
1040 discard(env, nDiscard);
1042 auto const ty = [&] {
1043 if (name) {
1044 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1045 if (auto const t = thisPropAsCell(env, name)) return *t;
1047 return to_cell(
1048 env.index.lookup_public_prop(
1049 objcls(env.collect.mInstrState.base.type), sval(name)
1053 return TInitCell;
1054 }();
1055 if (ty.subtypeOf(BBottom)) unreachable(env);
1056 push(env, ty);
1059 void miFinalSetProp(ISS& env, int32_t nDiscard, const Type& key) {
1060 auto const name = mStringKey(key);
1061 auto const t1 = unctx(popC(env));
1063 auto const finish = [&](Type ty) {
1064 endBase(env);
1065 discard(env, nDiscard);
1066 push(env, std::move(ty));
1069 handleInThisPropD(env, false);
1070 handleInPublicStaticPropD(env, false);
1071 promoteBasePropD(env, false);
1073 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1074 if (!name) {
1075 mergeEachThisPropRaw(
1076 env,
1077 [&] (Type propTy) {
1078 return propTy.couldBe(BInitCell) ? t1 : TBottom;
1081 } else {
1082 mergeThisProp(env, name, t1);
1086 if (env.collect.mInstrState.base.type.subtypeOf(BObj)) {
1087 if (t1.subtypeOf(BBottom)) return unreachable(env);
1088 moveBase(
1089 env,
1090 Base { t1, BaseLoc::Prop, env.collect.mInstrState.base.type, name }
1092 return finish(t1);
1095 moveBase(env, Base { TInitCell, BaseLoc::Prop, TTop, name });
1096 return finish(TInitCell);
1099 void miFinalSetOpProp(ISS& env, int32_t nDiscard,
1100 SetOpOp subop, const Type& key) {
1101 auto const name = mStringKey(key);
1102 auto const rhsTy = popC(env);
1104 handleInThisPropD(env, false);
1105 handleInPublicStaticPropD(env, false);
1106 promoteBasePropD(env, false);
1108 auto const lhsTy = [&] {
1109 if (name) {
1110 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1111 if (auto const t = thisPropAsCell(env, name)) return *t;
1113 return to_cell(
1114 env.index.lookup_public_prop(
1115 objcls(env.collect.mInstrState.base.type), sval(name)
1119 return TInitCell;
1120 }();
1122 auto const resultTy = env.collect.mInstrState.base.type.subtypeOf(TObj)
1123 ? typeSetOp(subop, lhsTy, rhsTy)
1124 : TInitCell;
1125 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1126 if (name) {
1127 mergeThisProp(env, name, resultTy);
1128 } else {
1129 loseNonRefThisPropTypes(env);
1133 endBase(env);
1134 discard(env, nDiscard);
1135 push(env, resultTy);
1138 void miFinalIncDecProp(ISS& env, int32_t nDiscard,
1139 IncDecOp subop, const Type& key) {
1140 auto const name = mStringKey(key);
1142 handleInThisPropD(env, false);
1143 handleInPublicStaticPropD(env, false);
1144 promoteBasePropD(env, false);
1146 auto const postPropTy = [&] {
1147 if (name) {
1148 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1149 if (auto const t = thisPropAsCell(env, name)) return *t;
1151 return to_cell(
1152 env.index.lookup_public_prop(
1153 objcls(env.collect.mInstrState.base.type), sval(name)
1157 return TInitCell;
1158 }();
1159 auto const prePropTy = env.collect.mInstrState.base.type.subtypeOf(TObj)
1160 ? typeIncDec(subop, postPropTy)
1161 : TInitCell;
1163 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1164 if (name) {
1165 mergeThisProp(env, name, prePropTy);
1166 } else {
1167 loseNonRefThisPropTypes(env);
1171 endBase(env);
1172 discard(env, nDiscard);
1173 push(env, isPre(subop) ? prePropTy : postPropTy);
1176 void miFinalUnsetProp(ISS& env, int32_t nDiscard, const Type& key) {
1177 auto const name = mStringKey(key);
1180 * Unset does define intermediate dims but with slightly different
1181 * rules than sets. It only applies to object properties.
1183 * Note that this can't affect self props, because static
1184 * properties can never be unset. It also can't change anything
1185 * about an inner array type.
1187 handleInThisPropD(env, false);
1189 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1190 if (name) {
1191 unsetThisProp(env, name);
1192 } else {
1193 unsetUnknownThisProp(env);
1197 endBase(env);
1198 discard(env, nDiscard);
1201 //////////////////////////////////////////////////////////////////////
1202 // Final elem ops
1204 // This is a helper for final defining Elem operations that need to
1205 // handle array chains and frame effects, but don't yet do anything
1206 // better than supplying a single type.
1207 void pessimisticFinalElemD(ISS& env, const Type& key, const Type& ty) {
1208 array_do_set(env, key, ty);
1211 template<typename F>
1212 void miFinalCGetElem(ISS& env, int32_t nDiscard,
1213 const Type& key, bool nullOnMissing,
1214 F transform) {
1215 auto ty = [&] {
1216 if (auto type = array_do_elem(env, nullOnMissing, key)) {
1217 return std::move(*type);
1219 return TInitCell;
1220 }();
1221 discard(env, nDiscard);
1222 push(env, transform(std::move(ty)));
1225 void miFinalSetElem(ISS& env,
1226 int32_t nDiscard,
1227 const Type& key,
1228 LocalId keyLoc) {
1229 auto const t1 = popC(env);
1231 handleInThisElemD(env);
1232 handleInPublicStaticElemD(env);
1234 auto const finish = [&](Type ty) {
1235 endBase(env, true, keyLoc);
1236 discard(env, nDiscard);
1237 push(env, std::move(ty));
1240 // Note: we must handle the string-related cases before doing the
1241 // general handleBaseElemD, since operates on strings as if this
1242 // was an intermediate ElemD.
1243 if (env.collect.mInstrState.base.type.subtypeOf(sempty())) {
1244 env.collect.mInstrState.base.type = some_aempty();
1245 } else {
1246 auto& ty = env.collect.mInstrState.base.type;
1247 if (ty.couldBe(BStr)) {
1248 // Note here that a string type stays a string (with a changed character,
1249 // and loss of staticness), unless it was the empty string, where it
1250 // becomes an array. Do it conservatively for now:
1251 ty = union_of(
1252 loosen_staticness(loosen_values(std::move(ty))),
1253 some_aempty()
1256 if (!ty.subtypeOf(BStr)) promoteBaseElemD(env);
1260 * In some unusual cases with illegal keys, SetM pushes null
1261 * instead of the right hand side.
1263 * There are also some special cases for SetM for different base types:
1264 * 1. If the base is a string, SetM pushes a new string with the
1265 * value of the first character of the right hand side converted
1266 * to a string (or something like that).
1267 * 2. If the base is a primitive type, SetM pushes null.
1268 * 3. If the base is an object, and it does not implement ArrayAccess,
1269 * it is still ok to push the right hand side, because it is a
1270 * fatal.
1272 * We push the right hand side on the stack only if the base is an
1273 * array, object or emptyish.
1275 if (array_do_set(env, key, t1)) {
1276 if (env.state.unreachable) return finish(TBottom);
1277 auto const maybeWeird =
1278 env.collect.mInstrState.base.type.subtypeOf(BArr) && keyCouldBeWeird(key);
1279 return finish(maybeWeird ? union_of(t1, TInitNull) : t1);
1282 // ArrayAccess on $this will always push the rhs, even if things
1283 // were weird.
1284 if (mustBeThisObj(env, env.collect.mInstrState.base)) return finish(t1);
1286 auto const isWeird =
1287 keyCouldBeWeird(key) ||
1288 (!mustBeEmptyish(env.collect.mInstrState.base.type) &&
1289 !env.collect.mInstrState.base.type.subtypeOf(BObj));
1290 finish(isWeird ? TInitCell : t1);
1293 void miFinalSetOpElem(ISS& env, int32_t nDiscard,
1294 SetOpOp subop, const Type& key,
1295 LocalId keyLoc) {
1296 auto const rhsTy = popC(env);
1297 handleInThisElemD(env);
1298 handleInPublicStaticElemD(env);
1299 promoteBaseElemD(env);
1300 auto const lhsTy = [&] {
1301 if (auto ty = array_do_elem(env, false, key)) {
1302 if (env.state.unreachable) return TBottom;
1303 assertx(!ty->subtypeOf(BBottom));
1304 return std::move(*ty);
1306 return TInitCell;
1307 }();
1308 auto const resultTy = typeSetOp(subop, lhsTy, rhsTy);
1309 pessimisticFinalElemD(env, key, resultTy);
1310 endBase(env, true, keyLoc);
1311 discard(env, nDiscard);
1312 push(env, resultTy);
1315 void miFinalIncDecElem(ISS& env, int32_t nDiscard,
1316 IncDecOp subop, const Type& key,
1317 LocalId keyLoc) {
1318 handleInThisElemD(env);
1319 handleInPublicStaticElemD(env);
1320 promoteBaseElemD(env);
1321 auto const postTy = [&] {
1322 if (auto ty = array_do_elem(env, false, key)) {
1323 if (env.state.unreachable) return TBottom;
1324 assertx(!ty->subtypeOf(BBottom));
1325 return std::move(*ty);
1327 return TInitCell;
1328 }();
1329 auto const preTy = typeIncDec(subop, postTy);
1330 pessimisticFinalElemD(env, key, preTy);
1331 endBase(env, true, keyLoc);
1332 discard(env, nDiscard);
1333 push(env, isPre(subop) ? preTy : postTy);
1336 void miFinalUnsetElem(ISS& env, int32_t nDiscard, const Type&) {
1337 handleInPublicStaticElemU(env);
1338 promoteBaseElemU(env);
1339 // We don't handle inner-array types with unset yet.
1340 always_assert(env.collect.mInstrState.arrayChain.empty());
1341 auto const& ty = env.collect.mInstrState.base.type;
1342 always_assert(!ty.strictSubtypeOf(TArr) && !ty.strictSubtypeOf(TVec) &&
1343 !ty.strictSubtypeOf(TDict) && !ty.strictSubtypeOf(TKeyset));
1344 endBase(env);
1345 discard(env, nDiscard);
1348 //////////////////////////////////////////////////////////////////////
1349 // Final new elem ops
1351 // This is a helper for final defining Elem operations that need to handle
1352 // array chains and frame effects, but don't yet do anything better than
1353 // supplying a single type.
1354 void pessimisticFinalNewElem(ISS& env, const Type& type) {
1355 array_do_newelem(env, type);
1358 void miFinalSetNewElem(ISS& env, int32_t nDiscard) {
1359 auto const t1 = popC(env);
1361 handleInThisNewElem(env);
1362 handleInPublicStaticNewElem(env);
1363 promoteBaseNewElem(env);
1365 auto const finish = [&](Type ty) {
1366 endBase(env);
1367 discard(env, nDiscard);
1368 push(env, std::move(ty));
1371 if (array_do_newelem(env, t1)) {
1372 return finish(t1);
1375 // ArrayAccess on $this will always push the rhs.
1376 if (mustBeThisObj(env, env.collect.mInstrState.base)) return finish(t1);
1378 // TODO(#3343813): we should push the type of the rhs when we can;
1379 // SetM for a new elem still has some weird cases where it pushes
1380 // null instead to handle. (E.g. if the base is a number.)
1381 finish(TInitCell);
1384 void miFinalSetOpNewElem(ISS& env, int32_t nDiscard) {
1385 popC(env);
1386 handleInThisNewElem(env);
1387 handleInPublicStaticNewElem(env);
1388 promoteBaseNewElem(env);
1389 pessimisticFinalNewElem(env, TInitCell);
1390 endBase(env);
1391 discard(env, nDiscard);
1392 push(env, TInitCell);
1395 void miFinalIncDecNewElem(ISS& env, int32_t nDiscard) {
1396 handleInThisNewElem(env);
1397 handleInPublicStaticNewElem(env);
1398 promoteBaseNewElem(env);
1399 pessimisticFinalNewElem(env, TInitCell);
1400 endBase(env);
1401 discard(env, nDiscard);
1402 push(env, TInitCell);
1407 namespace interp_step {
1409 //////////////////////////////////////////////////////////////////////
1410 // Base operations
1412 void in(ISS& env, const bc::BaseGC& op) {
1413 topC(env, op.arg1);
1414 startBase(env, Base{TInitCell, BaseLoc::Global});
1417 void in(ISS& env, const bc::BaseGL& op) {
1418 locAsCell(env, op.loc1);
1419 startBase(env, Base{TInitCell, BaseLoc::Global});
1422 void in(ISS& env, const bc::BaseSC& op) {
1423 auto cls = topC(env, op.arg2);
1424 auto prop = topC(env, op.arg1);
1426 auto const vname = tv(prop);
1427 if (op.subop3 == MOpMode::Define || op.subop3 == MOpMode::Unset ||
1428 op.subop3 == MOpMode::InOut ) {
1429 if (vname && vname->m_type == KindOfPersistentString &&
1430 canSkipMergeOnConstProp(env, cls, vname->m_data.pstr)) {
1431 unreachable(env);
1432 return;
1435 auto newBase = miBaseSProp(env, std::move(cls), prop);
1436 if (newBase.type.subtypeOf(BBottom)) return unreachable(env);
1437 startBase(env, std::move(newBase));
1440 void in(ISS& env, const bc::BaseL& op) {
1441 auto newBase = miBaseLocal(env, op.loc1, op.subop2);
1442 if (newBase.type.subtypeOf(BBottom)) return unreachable(env);
1443 startBase(env, std::move(newBase));
1446 void in(ISS& env, const bc::BaseC& op) {
1447 assert(op.arg1 < env.state.stack.size());
1448 auto ty = topC(env, op.arg1);
1449 if (ty.subtypeOf(BBottom)) return unreachable(env);
1450 nothrow(env);
1451 startBase(
1452 env,
1453 Base {
1454 std::move(ty),
1455 BaseLoc::Stack,
1456 TBottom,
1457 SString{},
1458 NoLocalId,
1459 (uint32_t)env.state.stack.size() - op.arg1 - 1
1464 void in(ISS& env, const bc::BaseH&) {
1465 auto const ty = thisTypeNonNull(env);
1466 if (ty.subtypeOf(BBottom)) return unreachable(env);
1467 nothrow(env);
1468 startBase(env, Base{ty, BaseLoc::This});
1471 //////////////////////////////////////////////////////////////////////
1472 // Intermediate operations
1474 void in(ISS& env, const bc::Dim& op) {
1475 auto const key = key_type_or_fixup(env, op);
1476 if (!key) return;
1477 auto const keyLoc = key_local(env, op);
1478 if (mcodeIsProp(op.mkey.mcode)) {
1479 miProp(env, op.mkey.mcode == MQT, op.subop1, *key);
1480 } else if (mcodeIsElem(op.mkey.mcode)) {
1481 miElem(env, op.subop1, *key, keyLoc);
1482 } else {
1483 miNewElem(env);
1485 if (env.flags.wasPEI) env.collect.mInstrState.noThrow = false;
1486 if (env.collect.mInstrState.noThrow &&
1487 (op.subop1 == MOpMode::None || op.subop1 == MOpMode::Warn) &&
1488 will_reduce(env) &&
1489 is_scalar(env.collect.mInstrState.base.type)) {
1490 for (int i = 0; ; i++) {
1491 auto const last = last_op(env, i);
1492 if (!last) break;
1493 if (isMemberBaseOp(last->op)) {
1494 auto const base = *last;
1495 rewind(env, i + 1);
1496 auto const reuseStack =
1497 [&] {
1498 switch (base.op) {
1499 case Op::BaseGC: return base.BaseGC.arg1 == 0;
1500 case Op::BaseC: return base.BaseC.arg1 == 0;
1501 default: return false;
1503 }();
1504 assertx(!env.collect.mInstrState.extraPop || reuseStack);
1505 auto const extraPop = !reuseStack || env.collect.mInstrState.extraPop;
1506 env.collect.mInstrState.clear();
1507 if (reuseStack) reduce(env, bc::PopC {});
1508 auto const v = tv(env.collect.mInstrState.base.type);
1509 assertx(v);
1510 reduce(env, gen_constant(*v), bc::BaseC { 0, op.subop1 });
1511 env.collect.mInstrState.extraPop = extraPop;
1512 return;
1514 if (!isMemberDimOp(last->op)) break;
1519 //////////////////////////////////////////////////////////////////////
1520 // Final operations
1522 const StaticString s_classname("classname");
1523 const StaticString s_type_structure("HH\\type_structure");
1524 const StaticString s_type_structure_classname("HH\\type_structure_classname");
1526 void in(ISS& env, const bc::QueryM& op) {
1527 auto const key = key_type_or_fixup(env, op);
1528 if (!key) return;
1529 auto const nDiscard = op.arg1;
1531 if (mcodeIsProp(op.mkey.mcode)) {
1532 // We don't currently do anything different for nullsafe query ops.
1533 switch (op.subop2) {
1534 case QueryMOp::CGet:
1535 case QueryMOp::CGetQuiet:
1536 miFinalCGetProp(env, nDiscard, *key);
1537 break;
1538 case QueryMOp::Isset:
1539 miFinalIssetProp(env, nDiscard, *key);
1540 break;
1541 case QueryMOp::Empty:
1542 discard(env, nDiscard);
1543 push(env, TBool);
1544 break;
1545 case QueryMOp::InOut:
1546 always_assert(false);
1548 } else if (mcodeIsElem(op.mkey.mcode)) {
1549 switch (op.subop2) {
1550 case QueryMOp::InOut:
1551 case QueryMOp::CGet:
1552 miFinalCGetElem(env, nDiscard, *key, false,
1553 [](Type t) { return t; });
1554 break;
1555 case QueryMOp::CGetQuiet:
1556 miFinalCGetElem(env, nDiscard, *key, true,
1557 [](Type t) { return t; });
1558 break;
1559 case QueryMOp::Isset:
1560 miFinalCGetElem(env, nDiscard, *key, true,
1561 [](Type t) {
1562 return t.subtypeOf(BInitNull) ? TFalse :
1563 !t.couldBe(BInitNull) ? TTrue : TBool;
1565 break;
1566 case QueryMOp::Empty:
1567 miFinalCGetElem(env, nDiscard, *key, true,
1568 [](Type t) {
1569 auto const e = emptiness(t);
1570 return
1571 e == Emptiness::Empty ? TTrue :
1572 e == Emptiness::NonEmpty ? TFalse : TBool;
1574 break;
1576 if (!env.flags.wasPEI &&
1577 env.collect.mInstrState.noThrow &&
1578 is_scalar(topC(env))) {
1579 for (int i = 0; ; i++) {
1580 auto const last = last_op(env, i);
1581 if (!last) break;
1582 if (isMemberBaseOp(last->op)) {
1583 auto const v = tv(topC(env));
1584 rewind(env, op);
1585 rewind(env, i + 1);
1586 env.collect.mInstrState.clear();
1587 BytecodeVec bcs{nDiscard, bc::PopC{}};
1588 bcs.push_back(gen_constant(*v));
1589 return reduce(env, std::move(bcs));
1591 if (!isMemberDimOp(last->op)) break;
1595 // Try to detect type_structure(cls_name, cns_name)['classname'] and
1596 // reduce this to type_structure_classname(cls_name, cns_name)
1597 if (op.subop2 == QueryMOp::CGet &&
1598 nDiscard == 1 &&
1599 op.mkey.mcode == MemberCode::MET &&
1600 op.mkey.litstr->isame(s_classname.get())) {
1601 if (auto const last = last_op(env, 0)) {
1602 if (last->op == Op::BaseC) {
1603 if (auto const prev = last_op(env, 1)) {
1604 if (prev->op == Op::FCallBuiltin &&
1605 prev->FCallBuiltin.str4->isame(s_type_structure.get()) &&
1606 prev->FCallBuiltin.arg1 == 2) {
1607 auto const params = prev->FCallBuiltin.arg1;
1608 auto const passed_params = prev->FCallBuiltin.arg2;
1609 rewind(env, op); // querym
1610 rewind(env, 2); // basec + fcallbuiltin
1611 env.collect.mInstrState.clear();
1612 return reduce(
1613 env,
1614 bc::FCallBuiltin {
1615 params,
1616 passed_params,
1618 s_type_structure_classname.get()
1626 } else {
1627 // QueryMNewElem will always throw without doing any work.
1628 discard(env, nDiscard);
1629 push(env, TInitCell);
1632 endBase(env, false);
1635 void in(ISS& env, const bc::SetM& op) {
1636 auto const key = key_type_or_fixup(env, op);
1637 if (!key) return;
1638 auto const keyLoc = key_local(env, op);
1639 if (mcodeIsProp(op.mkey.mcode)) {
1640 miFinalSetProp(env, op.arg1, *key);
1641 } else if (mcodeIsElem(op.mkey.mcode)) {
1642 miFinalSetElem(env, op.arg1, *key, keyLoc);
1643 } else {
1644 miFinalSetNewElem(env, op.arg1);
1648 void in(ISS& env, const bc::SetRangeM& op) {
1649 auto const count = popC(env);
1650 auto const value = popC(env);
1651 auto const offset = popC(env);
1653 auto& base = env.collect.mInstrState.base.type;
1654 if (base.couldBe(BStr)) {
1655 base = loosen_staticness(loosen_values(std::move(base)));
1658 endBase(env, true, NoLocalId);
1659 discard(env, op.arg1);
1662 void in(ISS& env, const bc::IncDecM& op) {
1663 auto const key = key_type_or_fixup(env, op);
1664 if (!key) return;
1665 auto const keyLoc = key_local(env, op);
1666 if (mcodeIsProp(op.mkey.mcode)) {
1667 miFinalIncDecProp(env, op.arg1, op.subop2, *key);
1668 } else if (mcodeIsElem(op.mkey.mcode)) {
1669 miFinalIncDecElem(env, op.arg1, op.subop2, *key, keyLoc);
1670 } else {
1671 miFinalIncDecNewElem(env, op.arg1);
1675 void in(ISS& env, const bc::SetOpM& op) {
1676 auto const key = key_type_or_fixup(env, op);
1677 if (!key) return;
1678 auto const keyLoc = key_local(env, op);
1679 if (mcodeIsProp(op.mkey.mcode)) {
1680 miFinalSetOpProp(env, op.arg1, op.subop2, *key);
1681 } else if (mcodeIsElem(op.mkey.mcode)) {
1682 miFinalSetOpElem(env, op.arg1, op.subop2, *key, keyLoc);
1683 } else {
1684 miFinalSetOpNewElem(env, op.arg1);
1688 void in(ISS& env, const bc::UnsetM& op) {
1689 auto const key = key_type_or_fixup(env, op);
1690 if (!key) return;
1691 if (mcodeIsProp(op.mkey.mcode)) {
1692 miFinalUnsetProp(env, op.arg1, *key);
1693 } else {
1694 assert(mcodeIsElem(op.mkey.mcode));
1695 miFinalUnsetElem(env, op.arg1, *key);
1701 //////////////////////////////////////////////////////////////////////