Teach hhbbc to optimize away LockObj.
[hiphop-php.git] / hphp / hhbbc / interp-minstr.cpp
blob05348ca800a6f0dd48b67c44507fbce83fab9a60
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 res = [&] () -> folly::Optional<std::pair<Type,ThrowMode>> {
208 if (base.subtypeOf(BArr)) return array_set(std::move(base), key, value);
209 if (base.subtypeOf(BVec)) return vec_set(std::move(base), key, value);
210 if (base.subtypeOf(BDict)) return dict_set(std::move(base), key, value);
211 if (base.subtypeOf(BKeyset)) return keyset_set(std::move(base), key, value);
212 return folly::none;
213 }();
214 if (!res) return false;
216 switch (res->second) {
217 case ThrowMode::None:
218 nothrow(env);
219 break;
220 case ThrowMode::MaybeMissingElement:
221 case ThrowMode::MissingElement:
222 case ThrowMode::MaybeBadKey:
223 case ThrowMode::BadOperation:
224 break;
227 if (res->first == TBottom) {
228 unreachable(env);
231 base = std::move(res->first);
232 return true;
236 * If the current base is an array-like, return the best known type
237 * for base[key].
239 folly::Optional<Type> array_do_elem(ISS& env,
240 bool nullOnMissing,
241 const Type& key) {
242 auto const& base = env.collect.mInstrState.base.type;
243 auto res = [&] () -> folly::Optional<std::pair<Type,ThrowMode>> {
244 if (base.subtypeOf(BArr)) return array_elem(base, key);
245 if (base.subtypeOf(BVec)) return vec_elem(base, key);
246 if (base.subtypeOf(BDict)) return dict_elem(base, key);
247 if (base.subtypeOf(BKeyset)) return keyset_elem(base, key);
248 return folly::none;
249 }();
250 if (!res) return folly::none;
252 switch (res->second) {
253 case ThrowMode::None:
254 nothrow(env);
255 break;
256 case ThrowMode::MaybeMissingElement:
257 case ThrowMode::MissingElement:
258 if (nullOnMissing) {
259 nothrow(env);
260 res->first |= TInitNull;
262 break;
263 case ThrowMode::MaybeBadKey:
264 if (nullOnMissing) {
265 res->first |= TInitNull;
267 break;
268 case ThrowMode::BadOperation:
269 break;
272 if (res->first == TBottom) {
273 unreachable(env);
276 return std::move(res->first);
280 * If the current base is an array-like, update it via *_newelem, and
281 * return the best known type for the key added; otherwise return folly::none.
283 folly::Optional<Type> array_do_newelem(ISS& env, const Type& value) {
284 auto& base = env.collect.mInstrState.base.type;
285 auto res = [&] () -> folly::Optional<std::pair<Type,Type>> {
286 if (base.subtypeOf(BArr)) return array_newelem(std::move(base), value);
287 if (base.subtypeOf(BVec)) return vec_newelem(std::move(base), value);
288 if (base.subtypeOf(BDict)) return dict_newelem(std::move(base), value);
289 if (base.subtypeOf(BKeyset)) return keyset_newelem(std::move(base), value);
290 return folly::none;
291 }();
292 if (!res) return folly::none;
293 base = std::move(res->first);
294 return res->second;
297 //////////////////////////////////////////////////////////////////////
299 // MInstrs can throw in between each op, so the states of locals
300 // need to be propagated across throw exit edges.
301 void miThrow(ISS& env) {
302 if (env.blk.throwExit != NoBlockId) {
303 auto const stackLess = with_throwable_only(env.index, env.state);
304 env.propagate(env.blk.throwExit, &stackLess);
308 //////////////////////////////////////////////////////////////////////
310 void setLocalForBase(ISS& env, Type ty, LocalId firstKeyLoc) {
311 assert(mustBeInLocal(env.collect.mInstrState.base));
312 if (env.collect.mInstrState.base.locLocal == NoLocalId) {
313 return loseNonRefLocalTypes(env);
315 FTRACE(4, " ${} := {}\n",
316 env.collect.mInstrState.base.locName
317 ? env.collect.mInstrState.base.locName->data()
318 : "$<unnamed>",
319 show(ty)
321 setLoc(
322 env,
323 env.collect.mInstrState.base.locLocal,
324 std::move(ty),
325 firstKeyLoc
329 void setStackForBase(ISS& env, Type ty) {
330 assert(mustBeInStack(env.collect.mInstrState.base));
332 auto const locSlot = env.collect.mInstrState.base.locSlot;
333 FTRACE(4, " stk[{:02}] := {}\n", locSlot, show(ty));
334 assert(locSlot < env.state.stack.size());
336 auto const& oldTy = env.state.stack[locSlot].type;
337 if (oldTy.subtypeOf(BInitCell)) {
338 env.state.stack[locSlot] = StackElem { std::move(ty), NoLocalId };
342 void setPrivateStaticForBase(ISS& env, Type ty) {
343 assert(couldBeInPrivateStatic(env, env.collect.mInstrState.base));
345 if (auto const name = env.collect.mInstrState.base.locName) {
346 FTRACE(4, " self::${} |= {}\n", name->data(), show(ty));
347 mergeSelfProp(env, name, std::move(ty));
348 return;
350 FTRACE(4, " self::* |= {}\n", show(ty));
351 mergeEachSelfPropRaw(
352 env,
353 [&](const Type& old){ return old.subtypeOf(BInitCell) ? ty : TBottom; }
357 void setPublicStaticForBase(ISS& env, Type ty) {
358 auto const& base = env.collect.mInstrState.base;
359 assertx(couldBeInPublicStatic(base));
361 auto const nameTy = base.locName ? sval(base.locName) : TStr;
362 FTRACE(
363 4, " public ({})::$({}) |= {}\n",
364 show(base.locTy), show(nameTy), show(ty)
366 env.collect.publicSPropMutations.merge(
367 env.index, env.ctx, base.locTy, nameTy, ty
371 // Run backwards through an array chain doing array_set operations
372 // to produce the array type that incorporates the effects of any
373 // intermediate defining dims.
374 Type currentChainType(ISS& env, Type val) {
375 auto it = env.collect.mInstrState.arrayChain.end();
376 while (it != env.collect.mInstrState.arrayChain.begin()) {
377 --it;
378 if (it->base.subtypeOf(BArr)) {
379 val = array_set(it->base, it->key, val).first;
380 } else if (it->base.subtypeOf(BVec)) {
381 val = vec_set(it->base, it->key, val).first;
382 if (val == TBottom) val = TVec;
383 } else if (it->base.subtypeOf(BDict)) {
384 val = dict_set(it->base, it->key, val).first;
385 if (val == TBottom) val = TDict;
386 } else {
387 assert(it->base.subtypeOf(BKeyset));
388 val = keyset_set(it->base, it->key, val).first;
389 if (val == TBottom) val = TKeyset;
392 return val;
395 Type resolveArrayChain(ISS& env, Type val) {
396 static UNUSED const char prefix[] = " ";
397 FTRACE(5, "{}chain\n", prefix, show(val));
398 do {
399 auto arr = std::move(env.collect.mInstrState.arrayChain.back().base);
400 auto key = std::move(env.collect.mInstrState.arrayChain.back().key);
401 env.collect.mInstrState.arrayChain.pop_back();
402 FTRACE(5, "{} | {} := {} in {}\n", prefix,
403 show(key), show(val), show(arr));
404 if (arr.subtypeOf(BVec)) {
405 val = vec_set(std::move(arr), key, val).first;
406 if (val == TBottom) val = TVec;
407 } else if (arr.subtypeOf(BDict)) {
408 val = dict_set(std::move(arr), key, val).first;
409 if (val == TBottom) val = TDict;
410 } else if (arr.subtypeOf(BKeyset)) {
411 val = keyset_set(std::move(arr), key, val).first;
412 if (val == TBottom) val = TKeyset;
413 } else {
414 assert(arr.subtypeOf(BArr));
415 val = array_set(std::move(arr), key, val).first;
417 } while (!env.collect.mInstrState.arrayChain.empty());
418 FTRACE(5, "{} = {}\n", prefix, show(val));
419 return val;
422 void updateBaseWithType(ISS& env,
423 const Type& ty,
424 LocalId firstKeyLoc = NoLocalId) {
425 FTRACE(6, " updateBaseWithType: {}\n", show(ty));
427 auto const& base = env.collect.mInstrState.base;
429 if (mustBeInLocal(base)) {
430 setLocalForBase(env, ty, firstKeyLoc);
431 return miThrow(env);
433 if (mustBeInStack(base)) {
434 return setStackForBase(env, ty);
437 if (couldBeInPrivateStatic(env, base)) setPrivateStaticForBase(env, ty);
438 if (couldBeInPublicStatic(base)) setPublicStaticForBase(env, ty);
441 void startBase(ISS& env, Base base) {
442 auto& oldState = env.collect.mInstrState;
443 assert(oldState.base.loc == BaseLoc::None);
444 assert(oldState.arrayChain.empty());
445 assert(isInitialBaseLoc(base.loc));
446 assert(!base.type.subtypeOf(TBottom));
448 oldState.noThrow = !env.flags.wasPEI;
449 oldState.extraPop = false;
450 oldState.base = std::move(base);
451 FTRACE(5, " startBase: {}\n", show(*env.ctx.func, oldState.base));
454 void endBase(ISS& env, bool update = true, LocalId keyLoc = NoLocalId) {
455 auto& state = env.collect.mInstrState;
456 assert(state.base.loc != BaseLoc::None);
458 FTRACE(5, " endBase: {}\n", show(*env.ctx.func, state.base));
460 auto const firstKeyLoc = state.arrayChain.empty()
461 ? keyLoc
462 : state.arrayChain.data()->keyLoc;
463 auto const& ty = state.arrayChain.empty()
464 ? state.base.type
465 : resolveArrayChain(env, state.base.type);
467 if (update) updateBaseWithType(env, ty, firstKeyLoc);
468 state.base.loc = BaseLoc::None;
471 void moveBase(ISS& env,
472 Base newBase,
473 bool update = true,
474 LocalId keyLoc = NoLocalId) {
475 auto& state = env.collect.mInstrState;
476 assert(state.base.loc != BaseLoc::None);
477 assert(isDimBaseLoc(newBase.loc));
478 assert(!state.base.type.subtypeOf(BBottom));
480 FTRACE(5, " moveBase: {} -> {}\n",
481 show(*env.ctx.func, state.base),
482 show(*env.ctx.func, newBase));
484 if (newBase.loc == BaseLoc::Elem) {
485 state.base.type =
486 add_nonemptiness(
487 loosen_staticness(loosen_values(state.base.type))
491 auto const firstKeyLoc = state.arrayChain.empty()
492 ? keyLoc
493 : state.arrayChain.data()->keyLoc;
494 auto const& ty = state.arrayChain.empty()
495 ? state.base.type
496 : resolveArrayChain(env, state.base.type);
498 if (update) updateBaseWithType(env, ty, firstKeyLoc);
499 state.base = std::move(newBase);
502 void extendArrChain(ISS& env, Type key, Type arr,
503 Type val, bool update = true,
504 LocalId keyLoc = NoLocalId) {
505 auto& state = env.collect.mInstrState;
506 assert(state.base.loc != BaseLoc::None);
507 assert(mustBeArrLike(arr));
508 assert(!state.base.type.subtypeOf(BBottom));
509 assert(!val.subtypeOf(BBottom));
511 state.arrayChain.emplace_back(
512 CollectedInfo::MInstrState::ArrayChainEnt{std::move(arr), std::move(key), keyLoc}
514 state.base.type = std::move(val);
516 auto const firstKeyLoc = state.arrayChain.data()->keyLoc;
518 FTRACE(5, " extendArrChain: {}\n", show(*env.ctx.func, state));
519 if (update) {
520 updateBaseWithType(
521 env,
522 currentChainType(env, state.base.type),
523 firstKeyLoc
528 //////////////////////////////////////////////////////////////////////
531 * The following promoteBase{Elem,Prop}* functions are used to implement the
532 * 'normal' portion of the effects on base types, which are mostly what are done
533 * by intermediate dims.
535 * The contract with these functions is that they should handle all the effects
536 * on the base type /except/ for the case of the base being an array
537 * subtype---the caller is responsible for that. The reason for this is that for
538 * tracking effects on specialized array types, the final ops generally need to
539 * do completely different things to the array, so this allows reuse of this
540 * shared part of the type transitions. The intermediate routines must handle
541 * array subtypes outside of calls to this as well.
544 void promoteBaseElemU(ISS& env) {
545 // We're conservative with unsets on array types for now.
546 env.collect.mInstrState.base.type = loosen_all(env.collect.mInstrState.base.type);
549 void promoteBasePropD(ISS& env, bool isNullsafe) {
550 auto& ty = env.collect.mInstrState.base.type;
552 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
553 if (isNullsafe || ty.subtypeOf(BObj)) return;
555 if (propMustPromoteToObj(ty)) {
556 ty = objExact(env.index.builtin_class(s_stdClass.get()));
557 return;
559 if (propCouldPromoteToObj(ty)) {
560 ty = promote_emptyish(ty, TObj);
561 return;
565 void promoteBaseElemD(ISS& env) {
566 auto& ty = env.collect.mInstrState.base.type;
568 // When the base is actually a subtype of array, we handle it in the callers
569 // of these functions.
570 if (mustBeArrLike(ty)) return;
572 if (elemMustPromoteToArr(ty)) {
573 ty = some_aempty();
574 return;
577 // Intermediate ElemD operations on strings fatal, unless the string is empty,
578 // which promotes to array. So for any string here we can assume it promoted
579 // to an empty array.
580 if (ty.subtypeOf(BStr)) {
581 ty = some_aempty();
582 return;
585 if (elemCouldPromoteToArr(ty)) {
586 ty = promote_emptyish(ty, some_aempty());
587 return;
591 * If the base still could be some kind of array (but isn't an array sub-type
592 * which would be handled outside this routine), we need to give up on any
593 * better information here (or track the effects, but we're not doing that
594 * yet).
596 ty = loosen_arrays(ty);
599 void promoteBaseNewElem(ISS& env) {
600 promoteBaseElemD(env);
601 // Technically we don't need to do TStr case.
604 //////////////////////////////////////////////////////////////////////
606 void handleInPublicStaticElemD(ISS& env) {
607 auto const& base = env.collect.mInstrState.base;
608 if (!couldBeInPublicStatic(base)) return;
610 auto const name = baseLocNameType(base);
611 auto const ty = env.index.lookup_public_static(env.ctx, base.locTy, name);
612 if (elemCouldPromoteToArr(ty)) {
613 // Might be possible to only merge a TArrE, but for now this is ok.
614 env.collect.publicSPropMutations.merge(
615 env.index, env.ctx, base.locTy, name, TArr
620 void handleInThisElemD(ISS& env) {
621 if (!couldBeInProp(env, env.collect.mInstrState.base)) return;
623 if (auto const name = env.collect.mInstrState.base.locName) {
624 auto const ty = thisPropAsCell(env, name);
625 if (ty && elemCouldPromoteToArr(*ty)) {
626 mergeThisProp(env, name, TArr);
628 return;
631 mergeEachThisPropRaw(env, [&] (const Type& t) {
632 return elemCouldPromoteToArr(t) ? TArr : TBottom;
636 void handleInPublicStaticPropD(ISS& env, bool isNullsafe) {
637 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
638 if (isNullsafe) return;
640 auto const& base = env.collect.mInstrState.base;
641 if (!couldBeInPublicStatic(base)) return;
643 auto const name = baseLocNameType(base);
644 auto const ty = env.index.lookup_public_static(env.ctx, base.locTy, name);
645 if (propCouldPromoteToObj(ty)) {
646 env.collect.publicSPropMutations.merge(
647 env.index, env.ctx, base.locTy, name,
648 objExact(env.index.builtin_class(s_stdClass.get()))
653 void handleInThisPropD(ISS& env, bool isNullsafe) {
654 // NullSafe (Q) props do not promote an emptyish base to stdClass instance.
655 if (isNullsafe) return;
657 if (!couldBeInProp(env, env.collect.mInstrState.base)) return;
659 if (auto const name = env.collect.mInstrState.base.locName) {
660 auto const ty = thisPropAsCell(env, name);
661 if (ty && propCouldPromoteToObj(*ty)) {
662 mergeThisProp(env, name,
663 objExact(env.index.builtin_class(s_stdClass.get())));
665 return;
668 if (RuntimeOption::EvalPromoteEmptyObject) {
669 mergeEachThisPropRaw(env, [&] (const Type& t) {
670 return propCouldPromoteToObj(t) ? TObj : TBottom;
675 // Currently NewElem and Elem InFoo effects don't need to do
676 // anything different from each other.
677 void handleInPublicStaticNewElem(ISS& env) { handleInPublicStaticElemD(env); }
678 void handleInThisNewElem(ISS& env) { handleInThisElemD(env); }
680 void handleInPublicStaticElemU(ISS& env) {
681 auto const& base = env.collect.mInstrState.base;
682 if (!couldBeInPublicStatic(base)) return;
685 * Merging InitCell is correct, but very conservative, for now.
687 auto const name = baseLocNameType(base);
688 env.collect.publicSPropMutations.merge(
689 env.index, env.ctx, base.locTy, name, TInitCell
693 //////////////////////////////////////////////////////////////////////
695 // Returns nullptr if it's an unknown key or not a string.
696 SString mStringKey(const Type& key) {
697 auto const v = tv(key);
698 return v && v->m_type == KindOfPersistentString ? v->m_data.pstr : nullptr;
701 template<typename Op>
702 auto update_mkey(const Op& op) { return false; }
704 template<typename Op>
705 auto update_mkey(Op& op) -> decltype(op.mkey, true) {
706 switch (op.mkey.mcode) {
707 case MEC: case MPC: {
708 op.mkey.idx++;
709 return true;
711 default:
712 return false;
716 template<typename Op>
717 auto update_discard(const Op& op) { return false; }
719 template<typename Op>
720 auto update_discard(Op& op) -> decltype(op.arg1, true) {
721 op.arg1++;
722 return true;
726 * Return the type of the key, or reduce op, and return folly::none.
727 * Note that when folly::none is returned, there is nothing further to
728 * do.
730 template<typename Op>
731 folly::Optional<Type> key_type_or_fixup(ISS& env, Op op) {
732 if (env.collect.mInstrState.extraPop) {
733 auto const mkey = update_mkey(op);
734 if (update_discard(op) || mkey) {
735 env.collect.mInstrState.extraPop = false;
736 reduce(env, op);
737 env.collect.mInstrState.extraPop = true;
738 return folly::none;
741 auto fixup = [&] (Type ty, bool isProp) -> folly::Optional<Type> {
742 if (auto const val = tv(ty)) {
743 if (isStringType(val->m_type)) {
744 op.mkey.mcode = isProp ? MPT : MET;
745 op.mkey.litstr = val->m_data.pstr;
746 reduce(env, op);
747 return folly::none;
749 if (!isProp && val->m_type == KindOfInt64) {
750 op.mkey.mcode = MEI;
751 op.mkey.int64 = val->m_data.num;
752 reduce(env, op);
753 return folly::none;
756 return std::move(ty);
758 switch (op.mkey.mcode) {
759 case MEC: case MPC:
760 return fixup(topC(env, op.mkey.idx), op.mkey.mcode == MPC);
761 case MEL: case MPL:
762 return fixup(locAsCell(env, op.mkey.local), op.mkey.mcode == MPL);
763 case MW:
764 return TBottom;
765 case MEI:
766 return ival(op.mkey.int64);
767 case MET: case MPT: case MQT:
768 return sval(op.mkey.litstr);
770 not_reached();
773 template<typename Op>
774 LocalId key_local(ISS& env, Op op) {
775 switch (op.mkey.mcode) {
776 case MEC: case MPC:
777 return topStkLocal(env, op.mkey.idx);
778 case MEL: case MPL:
779 return op.mkey.local;
780 case MW:
781 case MEI:
782 case MET: case MPT: case MQT:
783 return NoLocalId;
785 not_reached();
788 //////////////////////////////////////////////////////////////////////
789 // base ops
791 Base miBaseLocal(ISS& env, LocalId locBase, MOpMode mode) {
792 auto const locName = env.ctx.func->locals[locBase].name;
793 auto const isDefine = mode == MOpMode::Define;
794 if (mode == MOpMode::None ||
795 (mode == MOpMode::Warn && !locCouldBeUninit(env, locBase))) {
796 nothrow(env);
798 // If we're changing the local to define it, we don't need to do an miThrow
799 // yet---the promotions (to array or stdClass) on previously uninitialized
800 // locals happen before raising warnings that could throw, so we can wait
801 // until the first moveBase.
802 return Base { isDefine ? locAsCell(env, locBase) : derefLoc(env, locBase),
803 BaseLoc::Local,
804 TBottom,
805 locName,
806 locBase };
809 Base miBaseSProp(ISS& env, Type cls, const Type& tprop) {
810 auto const self = selfCls(env);
811 auto const prop = tv(tprop);
812 auto const name = prop && prop->m_type == KindOfPersistentString
813 ? prop->m_data.pstr : nullptr;
814 if (self && cls.subtypeOf(*self) && name) {
815 if (auto ty = selfPropAsCell(env, name)) {
816 return
817 Base { std::move(*ty), BaseLoc::StaticProp, std::move(cls), name };
820 auto indexTy = env.index.lookup_public_static(env.ctx, cls, tprop);
821 if (!indexTy.subtypeOf(BInitCell)) indexTy = TInitCell;
822 return Base { std::move(indexTy),
823 BaseLoc::StaticProp,
824 std::move(cls),
825 name };
828 //////////////////////////////////////////////////////////////////////
829 // intermediate ops
831 void miProp(ISS& env, bool isNullsafe, MOpMode mode, Type key) {
832 auto const name = mStringKey(key);
833 auto const isDefine = mode == MOpMode::Define;
834 auto const isUnset = mode == MOpMode::Unset;
835 // PHP5 doesn't modify an unset local if you unset a property or
836 // array elem on it, but hhvm does (it promotes it to init-null).
837 auto const update = isDefine || isUnset;
839 * MOpMode::Unset Props doesn't promote "emptyish" things to stdClass, or
840 * affect arrays, however it can define a property on an object base. This
841 * means we don't need any couldBeInFoo logic, but if the base could actually
842 * be $this, and a declared property could be Uninit, we need to merge
843 * InitNull.
845 * We're trying to handle this case correctly as far as the type inference
846 * here is concerned, but the runtime doesn't actually behave this way right
847 * now for declared properties. Note that it never hurts to merge more types
848 * than a thisProp could actually be, so this is fine.
850 * See TODO(#3602740): unset with intermediate dims on previously declared
851 * properties doesn't define them to null.
853 if (isUnset && couldBeThisObj(env, env.collect.mInstrState.base)) {
854 if (name) {
855 auto const elem = thisPropRaw(env, name);
856 if (elem && elem->ty.couldBe(BUninit)) {
857 mergeThisProp(env, name, TInitNull);
859 } else {
860 mergeEachThisPropRaw(env, [&] (const Type& ty) {
861 return ty.couldBe(BUninit) ? TInitNull : TBottom;
866 if (isDefine) {
867 handleInThisPropD(env, isNullsafe);
868 handleInPublicStaticPropD(env, isNullsafe);
869 promoteBasePropD(env, isNullsafe);
872 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
873 auto const optThisTy = thisTypeFromContext(env.index, env.ctx);
874 auto const thisTy = optThisTy ? *optThisTy : TObj;
875 if (name) {
876 auto const ty = [&] {
877 if (auto const propTy = thisPropAsCell(env, name)) return *propTy;
878 return to_cell(
879 env.index.lookup_public_prop(objcls(thisTy), sval(name))
881 }();
883 if (ty.subtypeOf(BBottom)) return unreachable(env);
884 moveBase(
885 env,
886 Base { ty, BaseLoc::Prop, thisTy, name },
887 update
889 } else {
890 moveBase(env,
891 Base { TInitCell, BaseLoc::Prop, thisTy },
892 update);
894 return;
897 // We know for sure we're going to be in an object property.
898 if (env.collect.mInstrState.base.type.subtypeOf(BObj)) {
899 auto const ty = to_cell(
900 env.index.lookup_public_prop(
901 objcls(env.collect.mInstrState.base.type),
902 name ? sval(name) : TStr
905 if (ty.subtypeOf(BBottom)) return unreachable(env);
906 moveBase(env,
907 Base { ty,
908 BaseLoc::Prop,
909 env.collect.mInstrState.base.type,
910 name },
911 update);
912 return;
916 * Otherwise, intermediate props with define can promote a null, false, or ""
917 * to stdClass. Those cases, and others, if it's MOpMode::Define, will set
918 * the base to a null value in tvScratch. The base may also legitimately be
919 * an object and our next base is in an object property. Conservatively treat
920 * all these cases as "possibly" being inside of an object property with
921 * "Prop" with locType TTop.
923 moveBase(env,
924 Base { TInitCell, BaseLoc::Prop, TTop, name },
925 update);
928 void miElem(ISS& env, MOpMode mode, Type key, LocalId keyLoc) {
929 auto const isDefine = mode == MOpMode::Define;
930 auto const isUnset = mode == MOpMode::Unset;
931 auto const update = isDefine || isUnset;
933 if (isUnset) {
935 * Elem dims with MOpMode::Unset can change a base from a static array into
936 * a reference counted array. It never promotes emptyish types, however.
938 handleInPublicStaticElemU(env);
939 promoteBaseElemU(env);
941 if (auto ty = array_do_elem(env, false, key)) {
942 if (ty->subtypeOf(BBottom)) return unreachable(env);
943 moveBase(
944 env,
945 Base {
946 std::move(*ty), BaseLoc::Elem,
947 env.collect.mInstrState.base.type
949 update,
950 keyLoc
952 return;
956 if (isDefine) {
957 handleInThisElemD(env);
958 handleInPublicStaticElemD(env);
959 promoteBaseElemD(env);
962 if (auto ty = array_do_elem(env, mode == MOpMode::None, key)) {
963 if (ty->subtypeOf(BBottom)) return unreachable(env);
964 extendArrChain(
965 env, std::move(key), env.collect.mInstrState.base.type,
966 std::move(*ty),
967 update,
968 keyLoc
970 return;
973 if (env.collect.mInstrState.base.type.subtypeOf(BStr)) {
974 moveBase(env, Base { TStr, BaseLoc::Elem }, update);
975 return;
979 * Other cases could leave the base as anything (if nothing else, via
980 * ArrayAccess on an object).
982 * The resulting BaseLoc is either inside an array, is the global
983 * init_null_variant, or inside tvScratch. We represent this with the
984 * Elem base location with locType TTop.
986 moveBase(env, Base { TInitCell, BaseLoc::Elem, TTop }, update, keyLoc);
989 void miNewElem(ISS& env) {
990 handleInThisNewElem(env);
991 handleInPublicStaticNewElem(env);
992 promoteBaseNewElem(env);
994 if (auto kty = array_do_newelem(env, TInitNull)) {
995 extendArrChain(env,
996 std::move(*kty),
997 std::move(env.collect.mInstrState.base.type),
998 TInitNull);
999 } else {
1000 moveBase(env, Base { TInitCell, BaseLoc::Elem, TTop });
1004 //////////////////////////////////////////////////////////////////////
1005 // final prop ops
1007 void miFinalIssetProp(ISS& env, int32_t nDiscard, const Type& key) {
1008 auto const name = mStringKey(key);
1009 discard(env, nDiscard);
1010 if (name && mustBeThisObj(env, env.collect.mInstrState.base)) {
1011 if (auto const pt = thisPropAsCell(env, name)) {
1012 if (isMaybeLateInitThisProp(env, name)) {
1013 // LateInit props can always be maybe unset, except if its never set at
1014 // all.
1015 return push(env, pt->subtypeOf(BBottom) ? TFalse : TBool);
1017 if (pt->subtypeOf(BNull)) return push(env, TFalse);
1018 if (!pt->couldBe(BNull)) return push(env, TTrue);
1021 push(env, TBool);
1024 void miFinalCGetProp(ISS& env, int32_t nDiscard, const Type& key) {
1025 auto const name = mStringKey(key);
1026 discard(env, nDiscard);
1028 auto const ty = [&] {
1029 if (name) {
1030 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1031 if (auto const t = thisPropAsCell(env, name)) return *t;
1033 return to_cell(
1034 env.index.lookup_public_prop(
1035 objcls(env.collect.mInstrState.base.type), sval(name)
1039 return TInitCell;
1040 }();
1041 if (ty.subtypeOf(BBottom)) unreachable(env);
1042 push(env, ty);
1045 void miFinalSetProp(ISS& env, int32_t nDiscard, const Type& key) {
1046 auto const name = mStringKey(key);
1047 auto const t1 = unctx(popC(env));
1049 auto const finish = [&](Type ty) {
1050 endBase(env);
1051 discard(env, nDiscard);
1052 push(env, std::move(ty));
1055 handleInThisPropD(env, false);
1056 handleInPublicStaticPropD(env, false);
1057 promoteBasePropD(env, false);
1059 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1060 if (!name) {
1061 mergeEachThisPropRaw(
1062 env,
1063 [&] (Type propTy) {
1064 return propTy.couldBe(BInitCell) ? t1 : TBottom;
1067 } else {
1068 mergeThisProp(env, name, t1);
1072 if (env.collect.mInstrState.base.type.subtypeOf(BObj)) {
1073 if (t1.subtypeOf(BBottom)) return unreachable(env);
1074 moveBase(
1075 env,
1076 Base { t1, BaseLoc::Prop, env.collect.mInstrState.base.type, name }
1078 return finish(t1);
1081 moveBase(env, Base { TInitCell, BaseLoc::Prop, TTop, name });
1082 return finish(TInitCell);
1085 void miFinalSetOpProp(ISS& env, int32_t nDiscard,
1086 SetOpOp subop, const Type& key) {
1087 auto const name = mStringKey(key);
1088 auto const rhsTy = popC(env);
1090 handleInThisPropD(env, false);
1091 handleInPublicStaticPropD(env, false);
1092 promoteBasePropD(env, false);
1094 auto const lhsTy = [&] {
1095 if (name) {
1096 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1097 if (auto const t = thisPropAsCell(env, name)) return *t;
1099 return to_cell(
1100 env.index.lookup_public_prop(
1101 objcls(env.collect.mInstrState.base.type), sval(name)
1105 return TInitCell;
1106 }();
1108 auto const resultTy = env.collect.mInstrState.base.type.subtypeOf(TObj)
1109 ? typeSetOp(subop, lhsTy, rhsTy)
1110 : TInitCell;
1111 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1112 if (name) {
1113 mergeThisProp(env, name, resultTy);
1114 } else {
1115 loseNonRefThisPropTypes(env);
1119 endBase(env);
1120 discard(env, nDiscard);
1121 push(env, resultTy);
1124 void miFinalIncDecProp(ISS& env, int32_t nDiscard,
1125 IncDecOp subop, const Type& key) {
1126 auto const name = mStringKey(key);
1128 handleInThisPropD(env, false);
1129 handleInPublicStaticPropD(env, false);
1130 promoteBasePropD(env, false);
1132 auto const postPropTy = [&] {
1133 if (name) {
1134 if (mustBeThisObj(env, env.collect.mInstrState.base)) {
1135 if (auto const t = thisPropAsCell(env, name)) return *t;
1137 return to_cell(
1138 env.index.lookup_public_prop(
1139 objcls(env.collect.mInstrState.base.type), sval(name)
1143 return TInitCell;
1144 }();
1145 auto const prePropTy = env.collect.mInstrState.base.type.subtypeOf(TObj)
1146 ? typeIncDec(subop, postPropTy)
1147 : TInitCell;
1149 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1150 if (name) {
1151 mergeThisProp(env, name, prePropTy);
1152 } else {
1153 loseNonRefThisPropTypes(env);
1157 endBase(env);
1158 discard(env, nDiscard);
1159 push(env, isPre(subop) ? prePropTy : postPropTy);
1162 void miFinalUnsetProp(ISS& env, int32_t nDiscard, const Type& key) {
1163 auto const name = mStringKey(key);
1166 * Unset does define intermediate dims but with slightly different
1167 * rules than sets. It only applies to object properties.
1169 * Note that this can't affect self props, because static
1170 * properties can never be unset. It also can't change anything
1171 * about an inner array type.
1173 handleInThisPropD(env, false);
1175 if (couldBeThisObj(env, env.collect.mInstrState.base)) {
1176 if (name) {
1177 unsetThisProp(env, name);
1178 } else {
1179 unsetUnknownThisProp(env);
1183 endBase(env);
1184 discard(env, nDiscard);
1187 //////////////////////////////////////////////////////////////////////
1188 // Final elem ops
1190 // This is a helper for final defining Elem operations that need to
1191 // handle array chains and frame effects, but don't yet do anything
1192 // better than supplying a single type.
1193 void pessimisticFinalElemD(ISS& env, const Type& key, const Type& ty) {
1194 array_do_set(env, key, ty);
1197 template<typename F>
1198 void miFinalCGetElem(ISS& env, int32_t nDiscard,
1199 const Type& key, bool nullOnMissing,
1200 F transform) {
1201 auto ty = [&] {
1202 if (auto type = array_do_elem(env, nullOnMissing, key)) {
1203 return std::move(*type);
1205 return TInitCell;
1206 }();
1207 discard(env, nDiscard);
1208 push(env, transform(std::move(ty)));
1211 void miFinalSetElem(ISS& env,
1212 int32_t nDiscard,
1213 const Type& key,
1214 LocalId keyLoc) {
1215 auto const t1 = popC(env);
1217 handleInThisElemD(env);
1218 handleInPublicStaticElemD(env);
1220 auto const finish = [&](Type ty) {
1221 endBase(env, true, keyLoc);
1222 discard(env, nDiscard);
1223 push(env, std::move(ty));
1226 // Note: we must handle the string-related cases before doing the
1227 // general handleBaseElemD, since operates on strings as if this
1228 // was an intermediate ElemD.
1229 if (env.collect.mInstrState.base.type.subtypeOf(sempty())) {
1230 env.collect.mInstrState.base.type = some_aempty();
1231 } else {
1232 auto& ty = env.collect.mInstrState.base.type;
1233 if (ty.couldBe(BStr)) {
1234 // Note here that a string type stays a string (with a changed character,
1235 // and loss of staticness), unless it was the empty string, where it
1236 // becomes an array. Do it conservatively for now:
1237 ty = union_of(
1238 loosen_staticness(loosen_values(std::move(ty))),
1239 some_aempty()
1242 if (!ty.subtypeOf(BStr)) promoteBaseElemD(env);
1246 * In some unusual cases with illegal keys, SetM pushes null
1247 * instead of the right hand side.
1249 * There are also some special cases for SetM for different base types:
1250 * 1. If the base is a string, SetM pushes a new string with the
1251 * value of the first character of the right hand side converted
1252 * to a string (or something like that).
1253 * 2. If the base is a primitive type, SetM pushes null.
1254 * 3. If the base is an object, and it does not implement ArrayAccess,
1255 * it is still ok to push the right hand side, because it is a
1256 * fatal.
1258 * We push the right hand side on the stack only if the base is an
1259 * array, object or emptyish.
1261 if (array_do_set(env, key, t1)) {
1262 if (env.state.unreachable) return finish(TBottom);
1263 auto const maybeWeird =
1264 env.collect.mInstrState.base.type.subtypeOf(BArr) && keyCouldBeWeird(key);
1265 return finish(maybeWeird ? union_of(t1, TInitNull) : t1);
1268 // ArrayAccess on $this will always push the rhs, even if things
1269 // were weird.
1270 if (mustBeThisObj(env, env.collect.mInstrState.base)) return finish(t1);
1272 auto const isWeird =
1273 keyCouldBeWeird(key) ||
1274 (!mustBeEmptyish(env.collect.mInstrState.base.type) &&
1275 !env.collect.mInstrState.base.type.subtypeOf(BObj));
1276 finish(isWeird ? TInitCell : t1);
1279 void miFinalSetOpElem(ISS& env, int32_t nDiscard,
1280 SetOpOp subop, const Type& key,
1281 LocalId keyLoc) {
1282 auto const rhsTy = popC(env);
1283 handleInThisElemD(env);
1284 handleInPublicStaticElemD(env);
1285 promoteBaseElemD(env);
1286 auto const lhsTy = [&] {
1287 if (auto ty = array_do_elem(env, false, key)) {
1288 if (env.state.unreachable) return TBottom;
1289 assertx(!ty->subtypeOf(BBottom));
1290 return std::move(*ty);
1292 return TInitCell;
1293 }();
1294 auto const resultTy = typeSetOp(subop, lhsTy, rhsTy);
1295 pessimisticFinalElemD(env, key, resultTy);
1296 endBase(env, true, keyLoc);
1297 discard(env, nDiscard);
1298 push(env, resultTy);
1301 void miFinalIncDecElem(ISS& env, int32_t nDiscard,
1302 IncDecOp subop, const Type& key,
1303 LocalId keyLoc) {
1304 handleInThisElemD(env);
1305 handleInPublicStaticElemD(env);
1306 promoteBaseElemD(env);
1307 auto const postTy = [&] {
1308 if (auto ty = array_do_elem(env, false, key)) {
1309 if (env.state.unreachable) return TBottom;
1310 assertx(!ty->subtypeOf(BBottom));
1311 return std::move(*ty);
1313 return TInitCell;
1314 }();
1315 auto const preTy = typeIncDec(subop, postTy);
1316 pessimisticFinalElemD(env, key, preTy);
1317 endBase(env, true, keyLoc);
1318 discard(env, nDiscard);
1319 push(env, isPre(subop) ? preTy : postTy);
1322 void miFinalUnsetElem(ISS& env, int32_t nDiscard, const Type&) {
1323 handleInPublicStaticElemU(env);
1324 promoteBaseElemU(env);
1325 // We don't handle inner-array types with unset yet.
1326 always_assert(env.collect.mInstrState.arrayChain.empty());
1327 auto const& ty = env.collect.mInstrState.base.type;
1328 always_assert(!ty.strictSubtypeOf(TArr) && !ty.strictSubtypeOf(TVec) &&
1329 !ty.strictSubtypeOf(TDict) && !ty.strictSubtypeOf(TKeyset));
1330 endBase(env);
1331 discard(env, nDiscard);
1334 //////////////////////////////////////////////////////////////////////
1335 // Final new elem ops
1337 // This is a helper for final defining Elem operations that need to handle
1338 // array chains and frame effects, but don't yet do anything better than
1339 // supplying a single type.
1340 void pessimisticFinalNewElem(ISS& env, const Type& type) {
1341 array_do_newelem(env, type);
1344 void miFinalSetNewElem(ISS& env, int32_t nDiscard) {
1345 auto const t1 = popC(env);
1347 handleInThisNewElem(env);
1348 handleInPublicStaticNewElem(env);
1349 promoteBaseNewElem(env);
1351 auto const finish = [&](Type ty) {
1352 endBase(env);
1353 discard(env, nDiscard);
1354 push(env, std::move(ty));
1357 if (array_do_newelem(env, t1)) {
1358 return finish(t1);
1361 // ArrayAccess on $this will always push the rhs.
1362 if (mustBeThisObj(env, env.collect.mInstrState.base)) return finish(t1);
1364 // TODO(#3343813): we should push the type of the rhs when we can;
1365 // SetM for a new elem still has some weird cases where it pushes
1366 // null instead to handle. (E.g. if the base is a number.)
1367 finish(TInitCell);
1370 void miFinalSetOpNewElem(ISS& env, int32_t nDiscard) {
1371 popC(env);
1372 handleInThisNewElem(env);
1373 handleInPublicStaticNewElem(env);
1374 promoteBaseNewElem(env);
1375 pessimisticFinalNewElem(env, TInitCell);
1376 endBase(env);
1377 discard(env, nDiscard);
1378 push(env, TInitCell);
1381 void miFinalIncDecNewElem(ISS& env, int32_t nDiscard) {
1382 handleInThisNewElem(env);
1383 handleInPublicStaticNewElem(env);
1384 promoteBaseNewElem(env);
1385 pessimisticFinalNewElem(env, TInitCell);
1386 endBase(env);
1387 discard(env, nDiscard);
1388 push(env, TInitCell);
1393 namespace interp_step {
1395 //////////////////////////////////////////////////////////////////////
1396 // Base operations
1398 void in(ISS& env, const bc::BaseGC& op) {
1399 topC(env, op.arg1);
1400 startBase(env, Base{TInitCell, BaseLoc::Global});
1403 void in(ISS& env, const bc::BaseGL& op) {
1404 locAsCell(env, op.loc1);
1405 startBase(env, Base{TInitCell, BaseLoc::Global});
1408 void in(ISS& env, const bc::BaseSC& op) {
1409 auto prop = topC(env, op.arg1);
1410 auto cls = takeClsRefSlot(env, op.slot);
1411 auto newBase = miBaseSProp(env, std::move(cls), prop);
1412 if (newBase.type.subtypeOf(BBottom)) return unreachable(env);
1413 startBase(env, std::move(newBase));
1416 void in(ISS& env, const bc::BaseL& op) {
1417 auto newBase = miBaseLocal(env, op.loc1, op.subop2);
1418 if (newBase.type.subtypeOf(BBottom)) return unreachable(env);
1419 startBase(env, std::move(newBase));
1422 void in(ISS& env, const bc::BaseC& op) {
1423 assert(op.arg1 < env.state.stack.size());
1424 auto ty = topC(env, op.arg1);
1425 if (ty.subtypeOf(BBottom)) return unreachable(env);
1426 nothrow(env);
1427 startBase(
1428 env,
1429 Base {
1430 std::move(ty),
1431 BaseLoc::Stack,
1432 TBottom,
1433 SString{},
1434 NoLocalId,
1435 (uint32_t)env.state.stack.size() - op.arg1 - 1
1440 void in(ISS& env, const bc::BaseH&) {
1441 auto const ty = thisTypeNonNull(env);
1442 if (ty.subtypeOf(BBottom)) return unreachable(env);
1443 nothrow(env);
1444 startBase(env, Base{ty, BaseLoc::This});
1447 //////////////////////////////////////////////////////////////////////
1448 // Intermediate operations
1450 void in(ISS& env, const bc::Dim& op) {
1451 auto const key = key_type_or_fixup(env, op);
1452 if (!key) return;
1453 auto const keyLoc = key_local(env, op);
1454 if (mcodeIsProp(op.mkey.mcode)) {
1455 miProp(env, op.mkey.mcode == MQT, op.subop1, *key);
1456 } else if (mcodeIsElem(op.mkey.mcode)) {
1457 miElem(env, op.subop1, *key, keyLoc);
1458 } else {
1459 miNewElem(env);
1461 if (env.flags.wasPEI) env.collect.mInstrState.noThrow = false;
1462 if (env.collect.mInstrState.noThrow &&
1463 (op.subop1 == MOpMode::None || op.subop1 == MOpMode::Warn) &&
1464 will_reduce(env) &&
1465 is_scalar(env.collect.mInstrState.base.type)) {
1466 for (int i = 0; ; i++) {
1467 auto const last = last_op(env, i);
1468 if (!last) break;
1469 if (isMemberBaseOp(last->op)) {
1470 auto const base = *last;
1471 rewind(env, i + 1);
1472 auto const reuseStack =
1473 [&] {
1474 switch (base.op) {
1475 case Op::BaseGC: return base.BaseGC.arg1 == 0;
1476 case Op::BaseSC: return base.BaseSC.arg1 == 0;
1477 case Op::BaseC: return base.BaseC.arg1 == 0;
1478 default: return false;
1480 }();
1481 assertx(!env.collect.mInstrState.extraPop || reuseStack);
1482 auto const extraPop = !reuseStack || env.collect.mInstrState.extraPop;
1483 env.collect.mInstrState.clear();
1484 if (reuseStack) reduce(env, bc::PopC {});
1485 auto const v = tv(env.collect.mInstrState.base.type);
1486 assertx(v);
1487 reduce(env, gen_constant(*v), bc::BaseC { 0, op.subop1 });
1488 env.collect.mInstrState.extraPop = extraPop;
1489 return;
1491 if (!isMemberDimOp(last->op)) break;
1496 //////////////////////////////////////////////////////////////////////
1497 // Final operations
1499 const StaticString s_classname("classname");
1500 const StaticString s_type_structure("HH\\type_structure");
1501 const StaticString s_type_structure_classname("HH\\type_structure_classname");
1503 void in(ISS& env, const bc::QueryM& op) {
1504 auto const key = key_type_or_fixup(env, op);
1505 if (!key) return;
1506 auto const nDiscard = op.arg1;
1508 if (mcodeIsProp(op.mkey.mcode)) {
1509 // We don't currently do anything different for nullsafe query ops.
1510 switch (op.subop2) {
1511 case QueryMOp::CGet:
1512 case QueryMOp::CGetQuiet:
1513 miFinalCGetProp(env, nDiscard, *key);
1514 break;
1515 case QueryMOp::Isset:
1516 miFinalIssetProp(env, nDiscard, *key);
1517 break;
1518 case QueryMOp::Empty:
1519 discard(env, nDiscard);
1520 push(env, TBool);
1521 break;
1522 case QueryMOp::InOut:
1523 always_assert(false);
1525 } else if (mcodeIsElem(op.mkey.mcode)) {
1526 switch (op.subop2) {
1527 case QueryMOp::InOut:
1528 case QueryMOp::CGet:
1529 miFinalCGetElem(env, nDiscard, *key, false,
1530 [](Type t) { return t; });
1531 break;
1532 case QueryMOp::CGetQuiet:
1533 miFinalCGetElem(env, nDiscard, *key, true,
1534 [](Type t) { return t; });
1535 break;
1536 case QueryMOp::Isset:
1537 miFinalCGetElem(env, nDiscard, *key, true,
1538 [](Type t) {
1539 return t.subtypeOf(BInitNull) ? TFalse :
1540 !t.couldBe(BInitNull) ? TTrue : TBool;
1542 break;
1543 case QueryMOp::Empty:
1544 miFinalCGetElem(env, nDiscard, *key, true,
1545 [](Type t) {
1546 auto const e = emptiness(t);
1547 return
1548 e == Emptiness::Empty ? TTrue :
1549 e == Emptiness::NonEmpty ? TFalse : TBool;
1551 break;
1553 if (!env.flags.wasPEI &&
1554 env.collect.mInstrState.noThrow &&
1555 is_scalar(topC(env))) {
1556 for (int i = 0; ; i++) {
1557 auto const last = last_op(env, i);
1558 if (!last) break;
1559 if (isMemberBaseOp(last->op)) {
1560 auto const v = tv(topC(env));
1561 rewind(env, op);
1562 rewind(env, i + 1);
1563 env.collect.mInstrState.clear();
1564 BytecodeVec bcs{nDiscard, bc::PopC{}};
1565 bcs.push_back(gen_constant(*v));
1566 return reduce(env, std::move(bcs));
1568 if (!isMemberDimOp(last->op)) break;
1572 // Try to detect type_structure(cls_name, cns_name)['classname'] and
1573 // reduce this to type_structure_classname(cls_name, cns_name)
1574 if (op.subop2 == QueryMOp::CGet &&
1575 nDiscard == 1 &&
1576 op.mkey.mcode == MemberCode::MET &&
1577 op.mkey.litstr->isame(s_classname.get())) {
1578 if (auto const last = last_op(env, 0)) {
1579 if (last->op == Op::BaseC) {
1580 if (auto const prev = last_op(env, 1)) {
1581 if (prev->op == Op::FCallBuiltin &&
1582 prev->FCallBuiltin.str3->isame(s_type_structure.get()) &&
1583 prev->FCallBuiltin.arg1 == 2) {
1584 auto const params = prev->FCallBuiltin.arg1;
1585 auto const passed_params = prev->FCallBuiltin.arg2;
1586 rewind(env, op); // querym
1587 rewind(env, 2); // basec + fcallbuiltin
1588 env.collect.mInstrState.clear();
1589 return reduce(
1590 env,
1591 bc::FCallBuiltin {
1592 params,
1593 passed_params,
1594 s_type_structure_classname.get()
1602 } else {
1603 // QueryMNewElem will always throw without doing any work.
1604 discard(env, nDiscard);
1605 push(env, TInitCell);
1608 endBase(env, false);
1611 void in(ISS& env, const bc::SetM& op) {
1612 auto const key = key_type_or_fixup(env, op);
1613 if (!key) return;
1614 auto const keyLoc = key_local(env, op);
1615 if (mcodeIsProp(op.mkey.mcode)) {
1616 miFinalSetProp(env, op.arg1, *key);
1617 } else if (mcodeIsElem(op.mkey.mcode)) {
1618 miFinalSetElem(env, op.arg1, *key, keyLoc);
1619 } else {
1620 miFinalSetNewElem(env, op.arg1);
1624 void in(ISS& env, const bc::SetRangeM& op) {
1625 auto const count = popC(env);
1626 auto const value = popC(env);
1627 auto const offset = popC(env);
1629 auto& base = env.collect.mInstrState.base.type;
1630 if (base.couldBe(BStr)) {
1631 base = loosen_staticness(loosen_values(std::move(base)));
1634 endBase(env, true, NoLocalId);
1635 discard(env, op.arg1);
1638 void in(ISS& env, const bc::IncDecM& op) {
1639 auto const key = key_type_or_fixup(env, op);
1640 if (!key) return;
1641 auto const keyLoc = key_local(env, op);
1642 if (mcodeIsProp(op.mkey.mcode)) {
1643 miFinalIncDecProp(env, op.arg1, op.subop2, *key);
1644 } else if (mcodeIsElem(op.mkey.mcode)) {
1645 miFinalIncDecElem(env, op.arg1, op.subop2, *key, keyLoc);
1646 } else {
1647 miFinalIncDecNewElem(env, op.arg1);
1651 void in(ISS& env, const bc::SetOpM& op) {
1652 auto const key = key_type_or_fixup(env, op);
1653 if (!key) return;
1654 auto const keyLoc = key_local(env, op);
1655 if (mcodeIsProp(op.mkey.mcode)) {
1656 miFinalSetOpProp(env, op.arg1, op.subop2, *key);
1657 } else if (mcodeIsElem(op.mkey.mcode)) {
1658 miFinalSetOpElem(env, op.arg1, op.subop2, *key, keyLoc);
1659 } else {
1660 miFinalSetOpNewElem(env, op.arg1);
1664 void in(ISS& env, const bc::UnsetM& op) {
1665 auto const key = key_type_or_fixup(env, op);
1666 if (!key) return;
1667 if (mcodeIsProp(op.mkey.mcode)) {
1668 miFinalUnsetProp(env, op.arg1, *key);
1669 } else {
1670 assert(mcodeIsElem(op.mkey.mcode));
1671 miFinalUnsetElem(env, op.arg1, *key);
1677 //////////////////////////////////////////////////////////////////////