Store num args instead of offset in prologue and func entry SrcKeys
[hiphop-php.git] / hphp / runtime / vm / jit / irgen-sprop-global.cpp
blob5d6ad525bce6d94afe412b2e6cf534378b0ec33e
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/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 {
28 namespace {
30 //////////////////////////////////////////////////////////////////////
32 void bindMem(IRGS& env, SSATmp* ptr, SSATmp* src, Type prevTy) {
33 auto const prevValue = gen(env, LdMem, prevTy, ptr);
34 pushIncRef(env, src);
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,
52 const Class* cls,
53 const StringData* name,
54 bool ignoreLateInit,
55 bool writeMode,
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.
78 if (ignoreLateInit) {
79 knownType |= TUninit;
80 } else {
81 knownType -= TUninit;
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));
93 ifElse(
94 env,
95 [&] (Block* taken) {
96 gen(env, CheckTypeMem, TObj, taken, addr);
98 [&] {
99 gen(env, ThrowMustBeValueTypeException, data, cns(env, name));
100 return cns(env, TBottom);
103 return addr;
106 if (readonly && readonlyOp == ReadonlyOp::Mutable) {
107 if (writeMode) {
108 gen(env, ThrowMustBeMutableException, data, cns(env, name));
109 } else {
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) {
116 if (!readonly) {
117 gen(env, ThrowMustBeReadonlyException, data, cns(env, name));
118 return cns(env, TBottom);
120 return copyOnWriteCheck();
122 return addr;
125 auto const addr = [&]{
126 if (!(prop.attrs & AttrLateInit) || ignoreLateInit) {
127 return gen(
128 env,
129 LdRDSAddr,
130 RDSHandleAndType { handle, knownType },
131 TPtrToSProp
135 return cond(
136 env,
137 [&] (Block* taken) {
138 return gen(
139 env,
140 LdInitRDSAddr,
141 RDSHandleAndType { handle, knownType },
142 taken,
143 TPtrToSProp
146 [&] (SSATmp* addr) { return addr; },
147 [&] {
148 hint(env, Block::Hint::Unlikely);
149 gen(
150 env,
151 ThrowLateInitPropError,
152 cns(env, prop.cls.get()),
153 cns(env, name),
154 cns(env, true)
156 return cns(env, TBottom);
159 }();
161 auto const checkedAddr = checkReadonly(addr);
163 return {
164 checkedAddr,
165 knownType,
166 &prop.typeConstraint,
167 &prop.ubs,
168 slot,
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
183 * class itself.
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;
197 return true;
198 }();
200 if (sPropKnown) {
201 auto const lookup = ldClsPropAddrKnown(
202 env,
203 ssaCls->clsVal(),
204 ssaName->strVal(),
205 opts.ignoreLateInit,
206 opts.writeMode,
207 opts.readOnlyCheck
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(
217 env,
218 opts.raise ? LdClsPropAddrOrRaise : LdClsPropAddrOrNull,
219 data,
220 knownType,
221 ssaCls,
222 ssaName,
223 ctxTmp,
224 cns(env, opts.ignoreLateInit),
225 cns(env, opts.writeMode)
227 return {
228 propAddr,
229 knownType,
230 nullptr,
231 nullptr,
232 kInvalidSlot
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);
249 discard(env);
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);
265 if (lookup.tc) {
266 verifyPropType(
267 env,
268 ssaCls,
269 lookup.tc,
270 lookup.ubs,
271 lookup.slot,
272 value,
273 ssaPropName,
274 true,
275 &value
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);
285 return;
289 discard(env);
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) {
308 if (lookup.tc) {
309 verifyPropType(
310 env,
311 ssaCls,
312 lookup.tc,
313 lookup.ubs,
314 lookup.slot,
315 value,
316 ssaPropName,
317 true,
318 &value
320 } else if (RuntimeOption::EvalCheckPropTypeHints > 0) {
321 auto const slot = gen(env, LookupSPropSlot, ssaCls, ssaPropName);
322 value = gen(env, VerifyPropCoerceAll, ssaCls, slot, value,
323 cns(env, true));
326 if (lookup.slot != kInvalidSlot && ssaCls->hasConstVal()) {
327 if (!ssaCls->clsVal()->sPropLink(lookup.slot).isLocal()) {
328 gen(env, Unreachable, ASSERT_REASON);
329 return;
333 discard(env);
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)) {
342 finish(value);
343 } else {
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(
357 env,
358 [&] (Block* taken) {
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);
372 discard(env);
373 destroyName(env, ssaPropName);
374 push(env, ret);
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));
392 if (lookup.tc) {
393 verifyPropType(
394 env,
395 ssaCls,
396 lookup.tc,
397 lookup.ubs,
398 lookup.slot,
399 result,
400 ssaPropName,
401 true
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);
411 return;
415 discard(env);
416 destroyName(env, ssaPropName);
417 pushIncRef(env, isPre(subop) ? result : oldVal);
419 // Update marker to ensure newly-pushed value isn't clobbered by DecRef.
420 updateMarker(env);
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(
434 env,
435 name,
436 [&] (Block* taken) {
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);
443 return tmp;
445 [&] { return cns(env, TInitNull); },
446 true
449 destroyName(env, name);
450 push(env, ret);
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(
459 env,
460 name,
461 [&] (Block*) { return gen(env, LdGblAddrDef, name); },
462 [&] (SSATmp* ptr, Type) { return ptr; },
463 [&] { return gen(env, LdGblAddrDef, name); },
464 false
467 discard(env);
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(
477 env,
478 name,
479 [&] (Block* taken) {
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); },
487 false
490 destroyName(env, name);
491 push(env, ret);
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);
512 switch (op) {
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)) {
525 verifyPropType(
526 env,
527 cns(env, ctx),
528 &prop.typeConstraint,
529 &prop.ubs,
530 slot,
531 val,
532 cns(env, propName),
533 true,
534 &val
538 profileRDSAccess(env, handle);
539 auto const base = gen(
540 env,
541 LdRDSAddr,
542 RDSHandleAndType { handle, TCell },
543 TPtrToSProp
545 gen(env, StMem, base, val);
547 break;
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)) {
559 verifyPropType(
560 env,
561 cls,
562 &prop.typeConstraint,
563 &prop.ubs,
564 slot,
565 val,
566 cns(env, propName),
567 false,
568 &val
572 auto const base = gen(env, LdClsInitData, cls);
573 gen(env, StClsInitElem, IndexData{idx}, base, val);
575 break;
579 //////////////////////////////////////////////////////////////////////