2 +----------------------------------------------------------------------+
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/runtime/vm/jit/irgen-sprop-global.h"
18 #include "hphp/runtime/base/datatype.h"
19 #include "hphp/runtime/vm/jit/irgen-create.h"
20 #include "hphp/runtime/vm/jit/irgen-exit.h"
21 #include "hphp/runtime/vm/jit/irgen-incdec.h"
22 #include "hphp/runtime/vm/jit/irgen-internal.h"
23 #include "hphp/runtime/vm/jit/irgen-minstr.h"
24 #include "hphp/runtime/vm/jit/irgen-types.h"
26 namespace HPHP::jit::irgen
{
30 //////////////////////////////////////////////////////////////////////
32 void bindMem(IRGS
& env
, SSATmp
* ptr
, SSATmp
* src
, Type prevTy
) {
33 auto const prevValue
= gen(env
, LdMem
, prevTy
, ptr
);
35 gen(env
, StMem
, ptr
, src
);
36 decRef(env
, prevValue
, DecRefProfileId::Default
);
39 void destroyName(IRGS
& env
, SSATmp
* name
) {
40 if (env
.irb
->inUnreachableState()) return;
41 assertx(name
== topC(env
));
42 popDecRef(env
, DecRefProfileId::Default
);
45 //////////////////////////////////////////////////////////////////////
49 //////////////////////////////////////////////////////////////////////
51 ClsPropLookup
ldClsPropAddrKnown(IRGS
& env
,
53 const StringData
* name
,
56 ReadonlyOp readonlyOp
) {
57 initSProps(env
, cls
); // calls init; must be above sPropHandle()
58 auto const slot
= cls
->lookupSProp(name
);
59 auto const handle
= cls
->sPropHandle(slot
);
60 assertx(!rds::isNormalHandle(handle
));
62 auto const ctx
= curClass(env
);
63 auto const& prop
= cls
->staticProperties()[slot
];
65 auto knownType
= TCell
;
66 if (RuntimeOption::EvalCheckPropTypeHints
>= 3 &&
67 (!prop
.typeConstraint
.isUpperBound() ||
68 RuntimeOption::EvalEnforceGenericsUB
>= 2)) {
69 knownType
= typeFromPropTC(prop
.typeConstraint
, cls
, ctx
, true);
70 if (!(prop
.attrs
& AttrNoImplicitNullable
)) knownType
|= TInitNull
;
72 knownType
&= typeFromRAT(prop
.repoAuthType
, ctx
);
73 // Repo-auth-type doesn't include uninit for AttrLateInit props, so we need
74 // to add it after intersecting with it.
75 if (prop
.attrs
& AttrLateInit
) {
76 // If we're ignoring AttrLateInit, the prop might be uninit, but if we're
77 // validating it, we'll never see uninit, so remove it.
85 profileRDSAccess(env
, handle
);
87 auto data
= ClassData
{ cls
};
88 auto const readonly
= prop
.attrs
& AttrIsReadonly
;
90 auto const checkReadonly
= [&](SSATmp
* addr
) {
91 auto const copyOnWriteCheck
= [&]() {
92 gen(env
, StMROProp
, cns(env
, true));
96 gen(env
, CheckTypeMem
, TObj
, taken
, addr
);
99 gen(env
, ThrowMustBeValueTypeException
, data
, cns(env
, name
));
100 return cns(env
, TBottom
);
106 if (readonly
&& readonlyOp
== ReadonlyOp::Mutable
) {
108 gen(env
, ThrowMustBeMutableException
, data
, cns(env
, name
));
110 gen(env
, ThrowMustBeEnclosedInReadonly
, data
, cns(env
, name
));
112 return cns(env
, TBottom
);
113 } else if (readonly
&& readonlyOp
== ReadonlyOp::CheckMutROCOW
) {
114 return copyOnWriteCheck();
115 } else if (readonlyOp
== ReadonlyOp::CheckROCOW
) {
117 gen(env
, ThrowMustBeReadonlyException
, data
, cns(env
, name
));
118 return cns(env
, TBottom
);
120 return copyOnWriteCheck();
125 auto const addr
= [&]{
126 if (!(prop
.attrs
& AttrLateInit
) || ignoreLateInit
) {
130 RDSHandleAndType
{ handle
, knownType
},
141 RDSHandleAndType
{ handle
, knownType
},
146 [&] (SSATmp
* addr
) { return addr
; },
148 hint(env
, Block::Hint::Unlikely
);
151 ThrowLateInitPropError
,
152 cns(env
, prop
.cls
.get()),
156 return cns(env
, TBottom
);
161 auto const checkedAddr
= checkReadonly(addr
);
166 &prop
.typeConstraint
,
171 static_assert(sizeof(StaticPropData
) == sizeof(TypedValue
),
172 "StaticPropData expected to only wrap TypedValue");
175 ClsPropLookup
ldClsPropAddr(IRGS
& env
, SSATmp
* ssaCls
, SSATmp
* ssaName
,
176 const LdClsPropOptions
& opts
) {
177 assertx(ssaCls
->isA(TCls
));
178 assertx(ssaName
->isA(TStr
));
181 * We can use ldClsPropAddrKnown if either we know which property it is and
182 * that it is visible && accessible, or we know it is a property on this
185 auto const sPropKnown
= [&] {
186 if (!ssaName
->hasConstVal()) return false;
187 auto const propName
= ssaName
->strVal();
189 if (!ssaCls
->hasConstVal()) return false;
190 auto const cls
= ssaCls
->clsVal();
192 auto const lookup
= cls
->findSProp(MemberLookupContext(curClass(env
), curUnit(env
)->moduleName()), propName
);
194 if (lookup
.slot
== kInvalidSlot
) return false;
195 if (!lookup
.accessible
) return false;
196 if (opts
.writeMode
&& lookup
.constant
) return false;
201 auto const lookup
= ldClsPropAddrKnown(
209 if (lookup
.propPtr
) return lookup
;
212 auto const ctxClass
= curClass(env
);
213 auto const ctxTmp
= ctxClass
? cns(env
, ctxClass
) : cns(env
, nullptr);
214 auto const data
= ReadonlyData
{ opts
.readOnlyCheck
};
215 auto const knownType
= opts
.ignoreLateInit
? TCell
: TInitCell
;
216 auto const propAddr
= gen(
218 opts
.raise
? LdClsPropAddrOrRaise
: LdClsPropAddrOrNull
,
224 cns(env
, opts
.ignoreLateInit
),
225 cns(env
, opts
.writeMode
)
236 //////////////////////////////////////////////////////////////////////
238 void emitCGetS(IRGS
& env
, ReadonlyOp op
) {
239 auto const ssaCls
= topC(env
);
240 auto const ssaPropName
= topC(env
, BCSPRelOffset
{1});
242 if (!ssaPropName
->isA(TStr
)) PUNT(CGetS
-PropNameNotString
);
243 if (!ssaCls
->isA(TCls
)) PUNT(CGetS
-NotClass
);
245 const LdClsPropOptions opts
{ op
, true, false, false };
246 auto const lookup
= ldClsPropAddr(env
, ssaCls
, ssaPropName
, opts
);
247 auto const ldMem
= gen(env
, LdMem
, lookup
.knownType
, lookup
.propPtr
);
250 destroyName(env
, ssaPropName
);
251 pushIncRef(env
, ldMem
);
254 void emitSetS(IRGS
& env
, ReadonlyOp op
) {
255 auto const ssaCls
= topC(env
, BCSPRelOffset
{1});
256 auto const ssaPropName
= topC(env
, BCSPRelOffset
{2});
258 if (!ssaPropName
->isA(TStr
)) PUNT(SetS
-PropNameNotString
);
259 if (!ssaCls
->isA(TCls
)) PUNT(SetS
-NotClass
);
261 auto value
= popC(env
, DataTypeGeneric
);
262 const LdClsPropOptions opts
{ op
, true, true, true };
263 auto const lookup
= ldClsPropAddr(env
, ssaCls
, ssaPropName
, opts
);
277 } else if (RuntimeOption::EvalCheckPropTypeHints
> 0) {
278 auto const slot
= gen(env
, LookupSPropSlot
, ssaCls
, ssaPropName
);
279 value
= gen(env
, VerifyPropCoerceAll
, ssaCls
, slot
, value
, cns(env
, true));
282 if (lookup
.slot
!= kInvalidSlot
&& ssaCls
->hasConstVal()) {
283 if (!ssaCls
->clsVal()->sPropLink(lookup
.slot
).isLocal()) {
284 gen(env
, Unreachable
, ASSERT_REASON
);
290 destroyName(env
, ssaPropName
);
291 bindMem(env
, lookup
.propPtr
, value
, lookup
.knownType
);
294 void emitSetOpS(IRGS
& env
, SetOpOp op
) {
295 auto const ssaCls
= topC(env
, BCSPRelOffset
{1});
296 auto const ssaPropName
= topC(env
, BCSPRelOffset
{2});
298 if (!ssaPropName
->isA(TStr
)) PUNT(SetOpS
-PropNameNotString
);
299 if (!ssaCls
->isA(TCls
)) PUNT(SetOpS
-NotClass
);
301 auto const rhs
= popC(env
);
302 const LdClsPropOptions opts
{ ReadonlyOp::Any
, true, false, true };
303 auto const lookup
= ldClsPropAddr(env
, ssaCls
, ssaPropName
, opts
);
305 auto const lhs
= gen(env
, LdMem
, lookup
.knownType
, lookup
.propPtr
);
307 auto const finish
= [&] (SSATmp
* value
) {
320 } else if (RuntimeOption::EvalCheckPropTypeHints
> 0) {
321 auto const slot
= gen(env
, LookupSPropSlot
, ssaCls
, ssaPropName
);
322 value
= gen(env
, VerifyPropCoerceAll
, ssaCls
, slot
, value
,
326 if (lookup
.slot
!= kInvalidSlot
&& ssaCls
->hasConstVal()) {
327 if (!ssaCls
->clsVal()->sPropLink(lookup
.slot
).isLocal()) {
328 gen(env
, Unreachable
, ASSERT_REASON
);
334 destroyName(env
, ssaPropName
);
335 pushIncRef(env
, value
);
336 gen(env
, StMem
, lookup
.propPtr
, value
);
337 decRef(env
, lhs
, DecRefProfileId::SetOpSLhs
);
338 decRef(env
, rhs
, DecRefProfileId::SetOpSRhs
);
341 if (auto value
= inlineSetOp(env
, op
, lhs
, rhs
)) {
344 // Handle cases not performed inline.
345 finish(gen(env
, OutlineSetOp
, SetOpData
{op
}, lhs
, rhs
));
349 void emitIssetS(IRGS
& env
) {
350 auto const ssaCls
= topC(env
);
351 auto const ssaPropName
= topC(env
, BCSPRelOffset
{1});
353 if (!ssaPropName
->isA(TStr
)) PUNT(IssetS
-PropNameNotString
);
354 if (!ssaCls
->isA(TCls
)) PUNT(IssetS
-NotClass
);
356 auto const ret
= cond(
359 const LdClsPropOptions opts
{ ReadonlyOp::Any
, false, true, false };
360 auto const propAddr
=
361 ldClsPropAddr(env
, ssaCls
, ssaPropName
, opts
).propPtr
;
362 return gen(env
, CheckNonNull
, taken
, propAddr
);
364 [&] (SSATmp
* ptr
) { // Next: property or global exists
365 return gen(env
, IsNTypeMem
, TNull
, ptr
);
367 [&] { // Taken: LdClsPropAddr* returned Nullptr because it isn't defined
368 return cns(env
, false);
373 destroyName(env
, ssaPropName
);
377 void emitIncDecS(IRGS
& env
, IncDecOp subop
) {
378 auto const ssaCls
= topC(env
);
379 auto const ssaPropName
= topC(env
, BCSPRelOffset
{1});
381 if (!ssaPropName
->isA(TStr
)) PUNT(IncDecS
-PropNameNotString
);
382 if (!ssaCls
->isA(TCls
)) PUNT(IncDecS
-NotClass
);
383 const LdClsPropOptions opts
{ ReadonlyOp::Any
, true, false, true };
384 auto const lookup
= ldClsPropAddr(env
, ssaCls
, ssaPropName
, opts
);
385 auto const oldVal
= gen(env
, LdMem
, lookup
.knownType
, lookup
.propPtr
);
387 auto const result
= incDec(env
, subop
, oldVal
);
388 if (!result
) PUNT(IncDecS
);
389 assertx(result
->isA(TUncounted
));
390 assertx(!result
->type().maybe(TClsMeth
));
403 } else if (RuntimeOption::EvalCheckPropTypeHints
> 0) {
404 auto const slot
= gen(env
, LookupSPropSlot
, ssaCls
, ssaPropName
);
405 gen(env
, VerifyPropAll
, ssaCls
, slot
, result
, cns(env
, true));
408 if (lookup
.slot
!= kInvalidSlot
&& ssaCls
->hasConstVal()) {
409 if (!ssaCls
->clsVal()->sPropLink(lookup
.slot
).isLocal()) {
410 gen(env
, Unreachable
, ASSERT_REASON
);
416 destroyName(env
, ssaPropName
);
417 pushIncRef(env
, isPre(subop
) ? result
: oldVal
);
419 // Update marker to ensure newly-pushed value isn't clobbered by DecRef.
422 gen(env
, StMem
, lookup
.propPtr
, result
);
423 gen(env
, IncRef
, result
);
424 decRef(env
, oldVal
, DecRefProfileId::Default
);
427 //////////////////////////////////////////////////////////////////////
429 void emitCGetG(IRGS
& env
) {
430 auto const name
= topC(env
);
431 if (!name
->isA(TStr
)) PUNT(CGetG
-NonStrName
);
433 auto const ret
= profiledGlobalAccess(
437 auto const addr
= gen(env
, LdGblAddr
, name
);
438 return gen(env
, CheckNonNull
, taken
, addr
);
440 [&] (SSATmp
* ptr
, Type type
) {
441 auto const tmp
= gen(env
, LdMem
, type
, ptr
);
442 gen(env
, IncRef
, tmp
);
445 [&] { return cns(env
, TInitNull
); },
449 destroyName(env
, name
);
453 void emitSetG(IRGS
& env
) {
454 auto const name
= topC(env
, BCSPRelOffset
{1});
455 if (!name
->isA(TStr
)) PUNT(SetG
-NameNotStr
);
456 auto const value
= topC(env
, BCSPRelOffset
{0}, DataTypeGeneric
);
458 auto const ptr
= profiledGlobalAccess(
461 [&] (Block
*) { return gen(env
, LdGblAddrDef
, name
); },
462 [&] (SSATmp
* ptr
, Type
) { return ptr
; },
463 [&] { return gen(env
, LdGblAddrDef
, name
); },
468 destroyName(env
, name
);
469 bindMem(env
, ptr
, value
, TCell
);
472 void emitIssetG(IRGS
& env
) {
473 auto const name
= topC(env
, BCSPRelOffset
{0});
474 if (!name
->isA(TStr
)) PUNT(IssetG
-NameNotStr
);
476 auto const ret
= profiledGlobalAccess(
480 auto const addr
= gen(env
, LdGblAddr
, name
);
481 return gen(env
, CheckNonNull
, taken
, addr
);
483 [&] (SSATmp
* ptr
, Type
) {
484 return gen(env
, IsNTypeMem
, TNull
, ptr
);
486 [&] { return cns(env
, false); },
490 destroyName(env
, name
);
494 //////////////////////////////////////////////////////////////////////
496 void emitCheckProp(IRGS
& env
, const StringData
* propName
) {
497 auto const cls
= ldCtxCls(env
);
498 auto const propInitVec
= gen(env
, LdClsInitData
, cls
);
500 auto const ctx
= curClass(env
);
501 auto const slot
= ctx
->lookupDeclProp(propName
);
502 auto const idx
= ctx
->propSlotToIndex(slot
);
504 auto const curVal
= gen(env
, LdClsInitElem
, IndexData
{idx
}, propInitVec
);
505 push(env
, gen(env
, IsNType
, TUninit
, curVal
));
508 void emitInitProp(IRGS
& env
, const StringData
* propName
, InitPropOp op
) {
509 auto val
= popC(env
);
510 auto const ctx
= curClass(env
);
513 case InitPropOp::Static
:
515 // For sinit, the context class is always the same as the late-bound
516 // class, so we can just use curClass().
517 auto const slot
= ctx
->lookupSProp(propName
);
518 assertx(slot
!= kInvalidSlot
);
519 auto const handle
= ctx
->sPropHandle(slot
);
520 assertx(!rds::isNormalHandle(handle
));
522 auto const& prop
= ctx
->staticProperties()[slot
];
523 assertx(!(prop
.attrs
& AttrSystemInitialValue
));
524 if (!(prop
.attrs
& AttrInitialSatisfiesTC
)) {
528 &prop
.typeConstraint
,
538 profileRDSAccess(env
, handle
);
539 auto const base
= gen(
542 RDSHandleAndType
{ handle
, TCell
},
545 gen(env
, StMem
, base
, val
);
549 case InitPropOp::NonStatic
:
551 // The above is not the case for pinit, so we need to load.
552 auto const cls
= ldCtxCls(env
);
554 const auto slot
= ctx
->lookupDeclProp(propName
);
555 auto const idx
= ctx
->propSlotToIndex(slot
);
556 auto const& prop
= ctx
->declProperties()[slot
];
557 assertx(!(prop
.attrs
& AttrSystemInitialValue
));
558 if (!(prop
.attrs
& AttrInitialSatisfiesTC
)) {
562 &prop
.typeConstraint
,
572 auto const base
= gen(env
, LdClsInitData
, cls
);
573 gen(env
, StClsInitElem
, IndexData
{idx
}, base
, val
);
579 //////////////////////////////////////////////////////////////////////