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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/jit/check.h"
19 #include "hphp/runtime/vm/jit/analysis.h"
20 #include "hphp/runtime/vm/jit/block.h"
21 #include "hphp/runtime/vm/jit/cfg.h"
22 #include "hphp/runtime/vm/jit/id-set.h"
23 #include "hphp/runtime/vm/jit/ir-instruction.h"
24 #include "hphp/runtime/vm/jit/ir-opcode.h"
25 #include "hphp/runtime/vm/jit/ir-unit.h"
26 #include "hphp/runtime/vm/jit/state-vector.h"
27 #include "hphp/runtime/vm/jit/phys-reg.h"
28 #include "hphp/runtime/vm/jit/reg-alloc.h"
29 #include "hphp/runtime/vm/jit/type.h"
31 #include "hphp/runtime/base/perf-warning.h"
33 #include <folly/Format.h>
38 #include <unordered_set>
40 #include <boost/dynamic_bitset.hpp>
42 namespace HPHP
{ namespace jit
{
44 //////////////////////////////////////////////////////////////////////
48 //////////////////////////////////////////////////////////////////////
53 * Return the number of parameters required for this block.
55 DEBUG_ONLY
static int numBlockParams(Block
* b
) {
56 return b
->empty() || b
->front().op() != DefLabel
? 0 :
61 * Check one block for being well formed. Invariants verified:
62 * 1. The block begins with an optional DefLabel, followed by an optional
64 * 2. DefLabel and BeginCatch may not appear anywhere in a block other than
65 * where specified in #1.
66 * 3. If this block is a catch block, it must have at most one predecessor.
67 * 4. The last instruction must be isBlockEnd() and the middle instructions
68 * must not be isBlockEnd(). Therefore, blocks cannot be empty.
69 * 5. block->next() must be null iff the last instruction isTerminal().
70 * 6. Every instruction must have a catch block attached to it if and only if it
71 * has the MayRaiseError flag.
72 * 7. Any path from this block to a Block that expects values must be
73 * from a Jmp instruciton.
74 * 8. Every instruction's BCMarker must point to a valid bytecode instruction.
76 bool checkBlock(Block
* b
) {
77 SCOPE_ASSERT_DETAIL("checkBlock") { return folly::sformat("B{}", b
->id()); };
80 always_assert(!b
->empty());
83 if (it
->op() == DefLabel
) {
88 if (it
!= end
&& it
->op() == BeginCatch
) {
93 always_assert(it
!= end
&& b
->back().isBlockEnd());
95 for (IRInstruction
& inst
: folly::range(it
, end
)) {
96 always_assert(inst
.op() != DefLabel
);
97 always_assert(inst
.op() != BeginCatch
);
98 always_assert(!inst
.isBlockEnd());
100 for (IRInstruction
& inst
: *b
) {
102 always_assert(inst
.marker().valid());
103 always_assert(inst
.block() == b
);
106 inst
.mayRaiseError() == (inst
.taken() && inst
.taken()->isCatch()),
112 always_assert(b
->back().isTerminal() == (b
->next() == nullptr));
116 // only Jmp can branch to a join block expecting values.
117 IRInstruction
* branch
= &b
->back();
118 auto numArgs
= branch
->op() == Jmp
? branch
->numSrcs() : 0;
119 always_assert(numBlockParams(b
->taken()) == numArgs
);
124 // keyed off a tca, so there needs to be exactly one
125 always_assert(b
->preds().size() <= 1);
131 ///////////////////////////////////////////////////////////////////////////////
134 * Check some invariants around InitCtx(fp):
135 * 1. For each fp, at most one should exist in a given unit.
136 * 2. If present, InitCtx must dominate all occurrences of LdCtx and LdCctx
139 bool DEBUG_ONLY
checkInitCtxInvariants(const IRUnit
& unit
) {
140 auto const blocks
= rpoSortCfg(unit
);
142 jit::hash_map
<SSATmp
*, Block
*> init_ctx_blocks
;
144 for (auto& blk
: blocks
) {
145 for (auto& inst
: blk
->instrs()) {
146 if (!inst
.is(InitCtx
)) continue;
147 auto& init_ctx_block
= init_ctx_blocks
[inst
.src(0)];
148 if (init_ctx_block
) return false;
149 init_ctx_block
= blk
;
153 if (init_ctx_blocks
.empty()) return true;
155 auto const rpoIDs
= numberBlocks(unit
, blocks
);
156 auto const idoms
= findDominators(unit
, blocks
, rpoIDs
);
158 for (auto& blk
: blocks
) {
159 SSATmp
* init_ctx_src
= nullptr;
161 for (auto& inst
: blk
->instrs()) {
162 if (inst
.is(InitCtx
)) {
163 init_ctx_src
= inst
.src(0);
166 if (!inst
.is(LdCtx
, LdCctx
)) continue;
168 auto const init_ctx_block
= init_ctx_blocks
[inst
.src(0)];
169 if (!init_ctx_block
) continue;
170 if (init_ctx_block
== blk
&& init_ctx_src
!= inst
.src(0)) return false;
171 if (!dominates(init_ctx_block
, blk
, idoms
)) return false;
178 ///////////////////////////////////////////////////////////////////////////////
182 ///////////////////////////////////////////////////////////////////////////////
185 * Build the CFG, then the dominator tree, then use it to validate SSA.
186 * 1. Each src must be defined by some other instruction, and each dst must
187 * be defined by the current instruction.
188 * 2. Each src must be defined earlier in the same block or in a dominator.
189 * 3. Each dst must not be previously defined.
190 * 4. Treat tmps defined by DefConst as always defined.
191 * 5. Each predecessor of a reachable block must be reachable (deleted
192 * blocks must not have out-edges to reachable blocks).
193 * 6. The entry block must not have any predecessors.
194 * 7. The entry block starts with a DefFP instruction.
196 bool checkCfg(const IRUnit
& unit
) {
197 auto const blocks
= rpoSortCfg(unit
);
198 auto const rpoIDs
= numberBlocks(unit
, blocks
);
199 auto reachable
= boost::dynamic_bitset
<>(unit
.numBlocks());
201 // Entry block can't have predecessors.
202 always_assert(unit
.entry()->numPreds() == 0);
204 // Entry block starts with DefFP.
205 always_assert(!unit
.entry()->empty() &&
206 unit
.entry()->begin()->op() == DefFP
);
208 // Check valid successor/predecessor edges, and identify reachable blocks.
209 for (Block
* b
: blocks
) {
210 reachable
.set(b
->id());
211 auto checkEdge
= [&] (const Edge
* e
) {
212 always_assert(e
->from() == b
);
213 for (auto& p
: e
->to()->preds()) if (&p
== e
) return;
214 always_assert(false); // did not find edge.
217 if (auto e
= b
->nextEdge()) checkEdge(e
);
218 if (auto e
= b
->takenEdge()) checkEdge(e
);
220 for (Block
* b
: blocks
) {
221 for (auto const& e
: b
->preds()) {
222 always_assert(&e
== e
.inst()->takenEdge() || &e
== e
.inst()->nextEdge());
223 always_assert(e
.to() == b
);
226 always_assert_flog(reachable
.test(e
.from()->id()),
227 "unreachable: B{}\n", e
.from()->id());
231 auto defined_set
= jit::sparse_idptr_set
<SSATmp
>{unit
.numTmps()};
234 * Visit every instruction and make sure their sources are either defined in
235 * a block that strictly dominates the block containing the instruction, or
236 * defined earlier in the same block as the instruction.
238 auto const idoms
= findDominators(unit
, blocks
, rpoIDs
);
239 for (auto& blk
: blocks
) {
240 for (auto& inst
: blk
->instrs()) {
241 for (auto src
: inst
.srcs()) {
242 if (src
->inst()->is(DefConst
)) continue;
243 auto const dom
= findDefiningBlock(src
, idoms
);
244 auto const locally_defined
=
245 src
->inst()->block() == inst
.block() && defined_set
.contains(src
);
246 auto const strictly_dominates
=
247 src
->inst()->block() != inst
.block() &&
248 dom
&& dominates(dom
, inst
.block(), idoms
);
250 locally_defined
|| strictly_dominates
,
251 "src '{}' in '{}' came from '{}', which is not a "
252 "DefConst and is not defined at this use site",
253 src
->toString(), inst
.toString(),
254 src
->inst()->toString()
257 for (auto dst
: inst
.dsts()) defined_set
.insert(dst
);
263 * Check that each dst is defined only once.
266 for (auto& blk
: blocks
) {
267 for (auto& inst
: blk
->instrs()) {
268 for (auto dst
: inst
.dsts()) {
270 !defined_set
.contains(dst
),
271 "SSATmp ({}) was defined multiple times",
274 defined_set
.insert(dst
);
282 bool checkTmpsSpanningCalls(const IRUnit
& unit
) {
283 auto ignoreSrc
= [&](IRInstruction
& inst
, SSATmp
* src
) {
285 * FramePtr/StkPtr-typed tmps may live across calls.
287 * Tmps defined by DefConst are always available and may be assigned to
288 * registers if needed by the instructions using the const.
290 return src
->isA(TStkPtr
) ||
291 src
->isA(TFramePtr
) ||
292 src
->inst()->is(DefConst
);
295 StateVector
<Block
,IdSet
<SSATmp
>> livein(unit
, IdSet
<SSATmp
>());
297 std::string failures
;
298 postorderWalk(unit
, [&](Block
* block
) {
299 auto& live
= livein
[block
];
300 if (auto taken
= block
->taken()) live
= livein
[taken
];
301 if (auto next
= block
->next()) live
|= livein
[next
];
302 for (auto it
= block
->end(); it
!= block
->begin();) {
304 for (auto dst
: inst
.dsts()) {
307 if (isCallOp(inst
.op())) {
308 live
.forEach([&](uint32_t tmp
) {
309 folly::format(&failures
, "t{} is live across `{}`\n", tmp
, inst
);
313 for (auto* src
: inst
.srcs()) {
314 if (!ignoreSrc(inst
, src
)) live
.add(src
);
320 logLowPriPerfWarning(
321 "checkTmpsSpanningCalls",
322 [&](StructuredLogEntry
& cols
) {
323 cols
.setStr("live_tmps", failures
);
324 cols
.setStr("hhir_unit", show(unit
));
331 ///////////////////////////////////////////////////////////////////////////////
332 // checkOperandTypes().
337 * Return a union type containing all the types in the argument list.
343 template<class... Args
>
344 Type
buildUnion(Type t
, Args
... ts
) {
345 return t
| buildUnion(ts
...);
348 template <uint32_t...> struct IdxSeq
{};
350 template <typename F
>
351 inline void forEachSrcIdx(F f
, IdxSeq
<>) {}
353 template <typename F
, uint32_t Idx
, uint32_t... Rest
>
354 inline void forEachSrcIdx(F f
, IdxSeq
<Idx
, Rest
...>) {
355 f(Idx
); forEachSrcIdx(f
, IdxSeq
<Rest
...>{});
361 * Runtime typechecking for IRInstruction operands.
363 * This is generated using the table in ir-opcode.h. We instantiate
364 * IR_OPCODES after defining all the various source forms to do type
365 * assertions according to their form (see ir-opcode.h for documentation on
366 * the notation). The checkers appear in argument order, so each one
367 * increments curSrc, and at the end we can check that the argument
368 * count was also correct.
370 bool checkOperandTypes(const IRInstruction
* inst
, const IRUnit
* unit
) {
373 auto bail
= [&] (const std::string
& msg
) {
374 FTRACE(1, "{}", msg
);
375 fprintf(stderr
, "%s\n", msg
.c_str());
377 always_assert_log(false, [&] { return msg
; });
380 if (opHasExtraData(inst
->op()) != (bool)inst
->rawExtra()) {
381 bail(folly::format("opcode {} should{} have an ExtraData struct "
382 "but instruction {} does{}",
384 opHasExtraData(inst
->op()) ? "" : "n't",
386 inst
->rawExtra() ? "" : "n't").str());
389 auto src
= [&]() -> SSATmp
* {
390 if (curSrc
< inst
->numSrcs()) {
391 return inst
->src(curSrc
);
395 "Error: instruction had too few operands\n"
396 " instruction: {}\n",
403 // If expected is not nullptr, it will be used. Otherwise, t.toString() will
404 // be used as the expected string.
405 auto check
= [&] (bool cond
, const Type t
, const char* expected
) {
406 if (cond
) return true;
408 std::string expectStr
= expected
? expected
: t
.toString();
411 "Error: failed type check on operand {}\n"
413 " was expecting: {}\n"
418 inst
->src(curSrc
)->type().toString()
424 auto checkNoArgs
= [&]{
425 if (inst
->numSrcs() == 0) return true;
427 "Error: instruction expected no operands\n"
428 " instruction: {}\n",
435 auto countCheck
= [&]{
436 if (inst
->numSrcs() == curSrc
) return true;
438 "Error: instruction had too many operands\n"
440 " expected {} arguments\n",
448 auto checkDst
= [&] (bool cond
, const std::string
& errorMessage
) {
449 if (cond
) return true;
451 bail(folly::format("Error: failed type check on dest operand\n"
455 errorMessage
).str());
459 auto requireTypeParam
= [&] {
460 checkDst(inst
->hasTypeParam() || inst
->is(DefConst
),
461 "Missing paramType for DParam instruction");
464 auto requireTypeParamPtr
= [&] (Ptr kind
) {
465 checkDst(inst
->hasTypeParam(),
466 "Missing paramType for DParamPtr instruction");
467 if (inst
->hasTypeParam()) {
468 checkDst(inst
->typeParam() <= TGen
.ptr(kind
),
469 "Invalid paramType for DParamPtr instruction");
473 auto checkVariadic
= [&] (Type super
) {
474 for (; curSrc
< inst
->numSrcs(); ++curSrc
) {
475 auto const valid
= (inst
->src(curSrc
)->type() <= super
);
476 check(valid
, Type(), nullptr);
480 #define IRT(name, ...) UNUSED static constexpr Type name = T##name;
481 #define IRTP(name, ...) IRT(name)
486 #define NA return checkNoArgs();
488 Type t = buildUnion(__VA_ARGS__); \
489 check(src()->isA(t), t, nullptr); \
493 Type t = Type::Array(ArrayData::k##kind##Kind); \
494 check(src()->isA(t), t, nullptr); \
497 #define C(T) check(src()->hasConstVal(T) || \
498 src()->isA(TBottom), \
502 #define CStr C(StaticStr)
503 #define SVar(...) checkVariadic(buildUnion(__VA_ARGS__));
510 #define DSubtract(src, t)checkDst(src < inst->numSrcs(), \
512 #define DofS(src) checkDst(src < inst->numSrcs(), \
514 #define DRefineS(src) checkDst(src < inst->numSrcs(), \
515 "invalid src num"); \
517 #define DParamMayRelax requireTypeParam();
518 #define DParam requireTypeParam();
519 #define DParamPtr(k) requireTypeParamPtr(Ptr::k);
520 #define DUnion(...) forEachSrcIdx( \
521 [&](uint32_t idx) { \
522 checkDst(idx < inst->numSrcs(), "invalid src num"); \
524 IdxSeq<__VA_ARGS__>{} \
540 #define O(opcode, dstinfo, srcinfo, flags) \
541 case opcode: dstinfo srcinfo countCheck(); return true;
543 switch (inst
->op()) {
545 default: always_assert(false);
566 #undef DParamMayRelax
587 bool checkEverything(const IRUnit
& unit
) {
588 assertx(checkCfg(unit
));
589 assertx(checkInitCtxInvariants(unit
));
591 checkTmpsSpanningCalls(unit
);
592 forEachInst(rpoSortCfg(unit
), [&](IRInstruction
* inst
) {
593 assertx(checkOperandTypes(inst
, &unit
));
599 //////////////////////////////////////////////////////////////////////