forked from facebook/hhvm
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathir-builder.h
560 lines (504 loc) · 19.2 KB
/
ir-builder.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010-2014 Facebook, Inc. (http://www.facebook.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| [email protected] so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_VM_IRBUILDER_H_
#define incl_HPHP_VM_IRBUILDER_H_
#include <functional>
#include <folly/ScopeGuard.h>
#include <folly/Optional.h>
#include "hphp/runtime/vm/jit/containers.h"
#include "hphp/runtime/vm/jit/block.h"
#include "hphp/runtime/vm/jit/cfg.h"
#include "hphp/runtime/vm/jit/cse.h"
#include "hphp/runtime/vm/jit/frame-state.h"
#include "hphp/runtime/vm/jit/guard-constraints.h"
#include "hphp/runtime/vm/jit/ir-unit.h"
#include "hphp/runtime/vm/jit/ir-opcode.h"
#include "hphp/runtime/vm/jit/region-selection.h"
#include "hphp/runtime/vm/jit/simplify.h"
#include "hphp/runtime/vm/jit/state-vector.h"
namespace HPHP { namespace jit {
//////////////////////////////////////////////////////////////////////
struct ExnStackState {
uint32_t stackDeficit;
EvalStack evalStack;
SSATmp* sp;
};
/*
* This module provides the basic utilities for generating the IR instructions
* in a trace and emitting control flow. It also performs some optimizations
* while generating IR, and may be reinvoked for a second optimization pass.
*
* This module is also responsible for organizing a few types of
* gen-time optimizations:
*
* - preOptimize pass
*
* Before an instruction is linked into the trace, IRBuilder
* internally runs preOptimize() on it, which can do some
* tracelet-state related modifications to the instruction. For
* example, it can eliminate redundant guards.
*
* - value numbering
*
* After preOptimize, instructions that support it are hashed and
* looked up in the CSEHash for this trace. If we find an
* available expression for the same value, instead of linking a
* new instruction into the trace we will just add a use to the
* previous SSATmp.
*
* - simplification pass
*
* After the preOptimize pass, IRBuilder calls out to
* Simplifier to perform state-independent optimizations, like
* copy propagation and strength reduction. (See simplify.h.)
*
*
* After all the instructions are linked into the trace, this module can also
* be used to perform a second round of the above two optimizations via the
* reoptimize() entry point.
*/
struct IRBuilder {
IRBuilder(IRUnit&, BCMarker);
/*
* Updates the marker used for instructions generated without one
* supplied.
*/
void setCurMarker(BCMarker);
/*
* Called before we start lowering each bytecode instruction. Right now all
* this does is cause an implicit exceptionStackBoundary. See below.
*/
void prepareForNextHHBC();
/*
* Exception handling and IRBuilder.
*
* Normally HHBC opcodes that throw don't have any effects before they throw.
* By default, when you gen() instructions that could throw, IRBuilder
* automatically creates catch blocks that take the current frame-state
* information, except spill the stack as if the instruction has not yet
* started.
*
* There are some exceptions, and so there are two ways to modify this
* behavior. If an HHBC opcode should have some effects on the stack prior
* to throwing, the lowering function can call exceptionStackBoundary after
* doing this to inform IRBuilder that it's not a bug---in this case the
* automatically created catch blocks will spill the stack as of the last
* boundary.
*
* The other way is to set a custom catch creator function. This is
* basically for the minstr instructions, which has various temporary stack
* state to clean up during unwinding.
*/
void exceptionStackBoundary();
const ExnStackState& exceptionStackState() const { return m_exnStack; }
/*
* The following functions are an abstraction layer we probably don't need.
* You can keep using them until we find time to remove them.
*/
IRUnit& unit() const { return m_unit; }
BCMarker curMarker() const { return m_curMarker; }
const Func* curFunc() const { return m_state.func(); }
int32_t spOffset() { return m_state.spOffset(); }
SSATmp* sp() const { return m_state.sp(); }
SSATmp* fp() const { return m_state.fp(); }
uint32_t stackDeficit() const { return m_state.stackDeficit(); }
void incStackDeficit() { m_state.incStackDeficit(); }
void clearStackDeficit() { m_state.clearStackDeficit(); }
void setStackDeficit(uint32_t d) { m_state.setStackDeficit(d); }
EvalStack& evalStack() { return m_state.evalStack(); }
bool thisAvailable() const { return m_state.thisAvailable(); }
void setThisAvailable() { m_state.setThisAvailable(); }
Type localType(uint32_t id, TypeConstraint tc);
Type predictedInnerType(uint32_t id);
Type predictedLocalType(uint32_t id);
SSATmp* localValue(uint32_t id, TypeConstraint tc);
TypeSourceSet localTypeSources(uint32_t id) const {
return m_state.localTypeSources(id);
}
bool frameMaySpanCall() const { return m_state.frameMaySpanCall(); }
/*
* Support for guard relaxation.
*
* Whenever the semantics of an hhir operation depends on the type of one of
* its input values, that value's type must be constrained using one of these
* methods. This happens automatically for most values, when obtained through
* irgen-internal functions like popC (and friends).
*/
void setConstrainGuards(bool constrain) { m_constrainGuards = constrain; }
bool shouldConstrainGuards() const { return m_constrainGuards; }
bool constrainGuard(const IRInstruction* inst, TypeConstraint tc);
bool constrainValue(SSATmp* const val, TypeConstraint tc);
bool constrainLocal(uint32_t id, TypeConstraint tc, const std::string& why);
bool constrainStack(int32_t offset, TypeConstraint tc);
bool constrainStack(SSATmp* sp, int32_t offset, TypeConstraint tc);
bool typeMightRelax(SSATmp* val = nullptr) const;
const GuardConstraints* guards() const { return &m_constraints; }
public:
/*
* API for managing state when building IR with bytecode-level control flow.
*/
/*
* Start the given block. Returns whether or not it succeeded. A failure
* may occur in case the block turned out to be unreachable.
*/
bool startBlock(Block* block, const BCMarker& marker, bool isLoopHeader);
/*
* Create a new block corresponding to bytecode control flow.
*/
Block* makeBlock(Offset offset);
/*
* Clear the map from bytecode offsets to Blocks.
*/
void resetOffsetMapping();
/*
* Checks whether or not there's a block associated with the given
* bytecode offset.
*/
bool hasBlock(Offset offset) const;
/*
* Set the block associated with the given offset in the offset->block map.
*/
void setBlock(Offset offset, Block* block);
public:
/*
* To emit code to a block other than the current block, call pushBlock(),
* emit instructions as usual with gen(...), then call popBlock(). This is
* best done using the BlockPusher struct:
*
* gen(CodeForMainBlock, ...);
* {
* BlockPusher<PauseExit> bp(m_irb, marker, exitBlock);
* gen(CodeForExitBlock, ...);
* }
* gen(CodeForMainBlock, ...);
*/
void pushBlock(BCMarker marker, Block* b);
void popBlock();
/*
* Run another pass of IRBuilder-managed optimizations on this
* unit.
*/
void reoptimize();
/*
* Conditionally-append a new instruction to the current Block, depending on
* what some optimizations have to say about it.
*/
enum class CloneFlag { Yes, No };
SSATmp* optimizeInst(IRInstruction* inst,
CloneFlag doClone,
Block* srcBlock,
const folly::Optional<IdomVector>&);
//////////////////////////////////////////////////////////////////////
// constants
SSATmp* genPtrToInitNull();
SSATmp* genPtrToUninit();
template<class... Args>
SSATmp* cns(Args&&... args) {
return m_unit.cns(std::forward<Args>(args)...);
}
//////////////////////////////////////////////////////////////////////
// control flow
// hint the execution frequency of the current block
void hint(Block::Hint h) const {
m_curBlock->setHint(h);
}
private:
template<typename T> struct BranchImpl;
public:
/*
* cond() generates if-then-else blocks within a trace. The caller supplies
* lambdas to create the branch, next-body, and taken-body. The next and
* taken lambdas must return one SSATmp* value; cond() returns the SSATmp for
* the merged value.
*
* If branch returns void, next must take zero arguments. If branch returns
* SSATmp*, next must take one SSATmp* argument. This allows branch to return
* an SSATmp* that is only defined in the next branch, without letting it
* escape into the caller's scope (most commonly used with things like
* LdMem).
*
* The producedRefs argument is needed for the refcount optimizations in
* refcount-opts.cpp. It should be the number of unconsumed references
* forwarded from each Jmp src to the DefLabel's dst (for a description of
* reference producers and consumers, read the "Refcount Optimizations"
* section in hphp/doc/hackers-guide/jit-optimizations.md). As an example,
* code that looks like the following should pass 1 for producedRefs, since
* LdCns and LookupCns each produce a reference that should then be forwarded
* to t2, the dest of the DefLabel:
*
* B0:
* t0:FramePtr = DefFP
* t1:Cell = LdCns "foo" // produce reference to t1
* CheckInit t1:Cell -> B3<Unlikely>
* -> B1
*
* B1 (preds B0):
* Jmp t1:Cell -> B2 // forward t1's unconsumed ref to t2
*
* B2 (preds B1, B3):
* t2:Cell = DefLabel // produce reference to t2, from t1 and t4
* StLoc<1> t0:FramePtr t2:Cell // consume reference to t2
* Halt
*
* B3<Unlikely> (preds B0):
* t3:Uninit = AssertType<Uninit> t1:Cell // consume reference to t1
* t4:Cell = LookupCns "foo" // produce reference to t4
* Jmp t4:Cell -> B2 // forward t4's unconsumed ref to t2
*
* A sufficiently advanced analysis pass could deduce this value from the
* structure of the IR, but it would require traversing all possible control
* flow paths, causing an explosion of required CPU time and/or memory.
*/
template <class Branch, class Next, class Taken>
SSATmp* cond(unsigned producedRefs, Branch branch, Next next, Taken taken) {
Block* taken_block = m_unit.defBlock();
Block* done_block = m_unit.defBlock();
DisableCseGuard guard(*this);
typedef decltype(branch(taken_block)) T;
SSATmp* v1 = BranchImpl<T>::go(branch, taken_block, next);
gen(Jmp, done_block, v1);
appendBlock(taken_block);
SSATmp* v2 = taken();
gen(Jmp, done_block, v2);
appendBlock(done_block);
IRInstruction* label = m_unit.defLabel(1, m_curMarker, {producedRefs});
done_block->push_back(label);
SSATmp* result = label->dst(0);
result->setType(Type::unionOf(v1->type(), v2->type()));
return result;
}
/*
* ifThenElse() generates if-then-else blocks within a trace that do not
* produce values. Code emitted in the {next,taken} lambda will be executed
* iff the branch emitted in the branch lambda is {not,} taken.
*/
template <class Branch, class Next, class Taken>
void ifThenElse(Branch branch, Next next, Taken taken) {
Block* taken_block = m_unit.defBlock();
Block* done_block = m_unit.defBlock();
DisableCseGuard guard(*this);
branch(taken_block);
next();
// patch the last block added by the Next lambda to jump to
// the done block. Note that last might not be taken_block.
Block* last = m_curBlock;
if (last->empty() || !last->back().isBlockEnd()) {
gen(Jmp, done_block);
} else if (!last->back().isTerminal()) {
last->back().setNext(done_block);
}
appendBlock(taken_block);
taken();
// patch the last block added by the Taken lambda to jump to
// the done block. Note that last might not be taken_block.
last = m_curBlock;
if (last->empty() || !last->back().isBlockEnd()) {
gen(Jmp, done_block);
} else if (!last->back().isTerminal()) {
last->back().setNext(done_block);
}
appendBlock(done_block);
}
/*
* ifThen generates if-then blocks within a trace that do not
* produce values. Code emitted in the taken lambda will be executed
* iff the branch emitted in the branch lambda is taken.
*/
template <class Branch, class Taken>
void ifThen(Branch branch, Taken taken) {
Block* taken_block = m_unit.defBlock();
Block* done_block = m_unit.defBlock();
DisableCseGuard guard(*this);
branch(taken_block);
Block* last = m_curBlock;
if (last->empty() || !last->back().isBlockEnd()) {
gen(Jmp, done_block);
} else if (!last->back().isTerminal()) {
last->back().setNext(done_block);
}
appendBlock(taken_block);
taken();
// patch the last block added by the Taken lambda to jump to
// the done block. Note that last might not be taken_block.
last = m_curBlock;
if (last->empty() || !last->back().isBlockEnd()) {
gen(Jmp, done_block);
} else if (!last->back().isTerminal()) {
last->back().setNext(done_block);
}
appendBlock(done_block);
}
/*
* ifElse generates if-then-else blocks with an empty 'then' block
* that do not produce values. Code emitted in the next lambda will
* be executed iff the branch emitted in the branch lambda is not
* taken.
*/
template <class Branch, class Next>
void ifElse(Branch branch, Next next) {
Block* done_block = m_unit.defBlock();
DisableCseGuard guard(*this);
branch(done_block);
next();
// patch the last block added by the Next lambda to jump to
// the done block.
auto last = m_curBlock;
if (last->empty() || !last->back().isBlockEnd()) {
gen(Jmp, done_block);
} else if (!last->back().isTerminal()) {
last->back().setNext(done_block);
}
appendBlock(done_block);
}
private:
struct BlockState {
Block* block;
BCMarker marker;
ExnStackState exnStack;
std::function<Block* ()> catchCreator;
};
private:
// RAII disable of CSE; only restores if it used to be on. Used for
// control flow, where we currently don't allow CSE.
struct DisableCseGuard {
explicit DisableCseGuard(IRBuilder& irb)
: m_irb(irb)
, m_oldEnable(irb.m_enableCse)
{
m_irb.m_enableCse = false;
}
~DisableCseGuard() {
m_irb.m_enableCse = m_oldEnable;
}
DisableCseGuard(const DisableCseGuard&) = delete;
DisableCseGuard& operator=(const DisableCseGuard&) = delete;
private:
IRBuilder& m_irb;
bool m_oldEnable;
};
private:
// Helper for cond() and such. We should move them out of IRBuilder so they
// can just use irgen::gen.
template<class... Args>
SSATmp* gen(Opcode op, Args&&... args) {
return makeInstruction(
[this] (IRInstruction* inst) {
return optimizeInst(inst, CloneFlag::Yes, nullptr, folly::none);
},
op,
m_curMarker,
std::forward<Args>(args)...
);
}
private:
using ConstrainBoxedFunc = std::function<SSATmp*(Type)>;
SSATmp* preOptimizeCheckTypeOp(IRInstruction* inst,
Type oldType,
ConstrainBoxedFunc func);
SSATmp* preOptimizeCheckType(IRInstruction*);
SSATmp* preOptimizeCheckStk(IRInstruction*);
SSATmp* preOptimizeCheckLoc(IRInstruction*);
SSATmp* preOptimizeHintLocInner(IRInstruction*);
SSATmp* preOptimizeAssertTypeOp(IRInstruction* inst,
Type oldType,
SSATmp* oldVal,
const IRInstruction* typeSrc);
SSATmp* preOptimizeAssertType(IRInstruction*);
SSATmp* preOptimizeAssertStk(IRInstruction*);
SSATmp* preOptimizeAssertLoc(IRInstruction*);
SSATmp* preOptimizeCheckCtxThis(IRInstruction*);
SSATmp* preOptimizeLdCtx(IRInstruction*);
SSATmp* preOptimizeDecRefThis(IRInstruction*);
SSATmp* preOptimizeDecRefLoc(IRInstruction*);
SSATmp* preOptimizeLdLocPseudoMain(IRInstruction*);
SSATmp* preOptimizeLdLoc(IRInstruction*);
SSATmp* preOptimizeStLoc(IRInstruction*);
SSATmp* preOptimize(IRInstruction* inst);
bool constrainLocal(uint32_t id,
TypeSource typeSrc,
TypeConstraint tc,
const std::string& why);
private:
void appendInstruction(IRInstruction* inst);
void appendBlock(Block* block);
SSATmp* cseLookup(const IRInstruction&,
const Block*,
const folly::Optional<IdomVector>&) const;
void clearCse();
const CSEHash& cseHashTable(const IRInstruction& inst) const;
CSEHash& cseHashTable(const IRInstruction& inst);
void cseUpdate(const IRInstruction& inst);
private:
IRUnit& m_unit;
BCMarker m_initialMarker;
BCMarker m_curMarker;
FrameStateMgr m_state;
CSEHash m_cseHash;
bool m_enableCse{false};
/*
* m_savedBlocks will be nonempty iff we're emitting code to a block other
* than the main block. m_curMarker, and m_curBlock are all set from the
* most recent call to pushBlock() or popBlock().
*/
jit::vector<BlockState> m_savedBlocks;
Block* m_curBlock;
ExnStackState m_exnStack{0, EvalStack{}, nullptr};
bool m_enableSimplification;
bool m_constrainGuards;
GuardConstraints m_constraints;
// Keep track of blocks created to support bytecode control flow.
//
// TODO(t3730559): Offset is used here since it's passed from
// emitJmp*, but SrcKey might be better in case of inlining.
jit::flat_map<Offset,Block*> m_offsetToBlockMap;
};
/*
* BranchImpl is used by IRBuilder::cond to support branch and next lambdas
* with different signatures. See cond for details.
*/
template<> struct IRBuilder::BranchImpl<void> {
template<typename Branch, typename Next>
static SSATmp* go(Branch branch, Block* taken, Next next) {
branch(taken);
return next();
}
};
template<> struct IRBuilder::BranchImpl<SSATmp*> {
template<typename Branch, typename Next>
static SSATmp* go(Branch branch, Block* taken, Next next) {
return next(branch(taken));
}
};
//////////////////////////////////////////////////////////////////////
/*
* RAII helper for emitting code to exit traces. See IRBuilder::pushBlock
* for usage.
*/
struct BlockPusher {
BlockPusher(IRBuilder& irb, BCMarker marker, Block* block)
: m_irb(irb)
{
irb.pushBlock(marker, block);
}
~BlockPusher() {
m_irb.popBlock();
}
private:
IRBuilder& m_irb;
};
//////////////////////////////////////////////////////////////////////
}}
#endif