|
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * vim: set ts=8 sts=4 et sw=4 tw=99: |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "mozilla/DebugOnly.h" |
|
8 #include "mozilla/MathAlgorithms.h" |
|
9 #include "mozilla/PodOperations.h" |
|
10 |
|
11 #include "jscntxt.h" |
|
12 #include "jscompartment.h" |
|
13 |
|
14 #include "vm/Opcodes.h" |
|
15 |
|
16 #include "jsinferinlines.h" |
|
17 #include "jsobjinlines.h" |
|
18 #include "jsopcodeinlines.h" |
|
19 |
|
20 using mozilla::DebugOnly; |
|
21 using mozilla::PodCopy; |
|
22 using mozilla::PodZero; |
|
23 using mozilla::FloorLog2; |
|
24 |
|
25 namespace js { |
|
26 namespace analyze { |
|
27 class LoopAnalysis; |
|
28 } // namespace analyze |
|
29 } // namespace js |
|
30 |
|
31 namespace mozilla { |
|
32 |
|
33 template <> struct IsPod<js::analyze::LifetimeVariable> : TrueType {}; |
|
34 template <> struct IsPod<js::analyze::LoopAnalysis> : TrueType {}; |
|
35 template <> struct IsPod<js::analyze::SlotValue> : TrueType {}; |
|
36 template <> struct IsPod<js::analyze::SSAValue> : TrueType {}; |
|
37 template <> struct IsPod<js::analyze::SSAUseChain> : TrueType {}; |
|
38 |
|
39 } /* namespace mozilla */ |
|
40 |
|
41 namespace js { |
|
42 namespace analyze { |
|
43 |
|
44 /* |
|
45 * There are three analyses we can perform on a JSScript, outlined below. |
|
46 * The results of all three are stored in ScriptAnalysis, but the analyses |
|
47 * themselves can be performed separately. Along with type inference results, |
|
48 * per-script analysis results are tied to the per-compartment analysis pool |
|
49 * and are freed on every garbage collection. |
|
50 * |
|
51 * - Basic bytecode analysis. For each bytecode, determine the stack depth at |
|
52 * that point and control flow information needed for compilation. Also does |
|
53 * a defined-variables analysis to look for local variables which have uses |
|
54 * before definitions. |
|
55 * |
|
56 * - Lifetime analysis. Makes a backwards pass over the script to approximate |
|
57 * the regions where each variable is live, avoiding a full fixpointing |
|
58 * live-variables pass. This is based on the algorithm described in: |
|
59 * |
|
60 * "Quality and Speed in Linear-scan Register Allocation" |
|
61 * Traub et. al. |
|
62 * PLDI, 1998 |
|
63 * |
|
64 * - SSA analysis of the script's variables and stack values. For each stack |
|
65 * value popped and non-escaping local variable or argument read, determines |
|
66 * which push(es) or write(s) produced that value. |
|
67 * |
|
68 * Intermediate type inference results are additionally stored here. The above |
|
69 * analyses are independent from type inference. |
|
70 */ |
|
71 |
|
72 /* Information about a bytecode instruction. */ |
|
73 class Bytecode |
|
74 { |
|
75 friend class ScriptAnalysis; |
|
76 |
|
77 public: |
|
78 Bytecode() { mozilla::PodZero(this); } |
|
79 |
|
80 /* --------- Bytecode analysis --------- */ |
|
81 |
|
82 /* Whether there are any incoming jumps to this instruction. */ |
|
83 bool jumpTarget : 1; |
|
84 |
|
85 /* Whether there is fallthrough to this instruction from a non-branching instruction. */ |
|
86 bool fallthrough : 1; |
|
87 |
|
88 /* Whether this instruction is the fall through point of a conditional jump. */ |
|
89 bool jumpFallthrough : 1; |
|
90 |
|
91 /* |
|
92 * Whether this instruction must always execute, unless the script throws |
|
93 * an exception which it does not later catch. |
|
94 */ |
|
95 bool unconditional : 1; |
|
96 |
|
97 /* Whether this instruction has been analyzed to get its output defines and stack. */ |
|
98 bool analyzed : 1; |
|
99 |
|
100 /* Whether this is a catch/finally entry point. */ |
|
101 bool exceptionEntry : 1; |
|
102 |
|
103 /* Stack depth before this opcode. */ |
|
104 uint32_t stackDepth; |
|
105 |
|
106 private: |
|
107 |
|
108 /* If this is a JSOP_LOOPHEAD or JSOP_LOOPENTRY, information about the loop. */ |
|
109 LoopAnalysis *loop; |
|
110 |
|
111 /* --------- SSA analysis --------- */ |
|
112 |
|
113 /* Generated location of each value popped by this bytecode. */ |
|
114 SSAValue *poppedValues; |
|
115 |
|
116 /* Points where values pushed or written by this bytecode are popped. */ |
|
117 SSAUseChain **pushedUses; |
|
118 |
|
119 union { |
|
120 /* |
|
121 * If this is a join point (implies jumpTarget), any slots at this |
|
122 * point which can have a different values than at the immediate |
|
123 * predecessor in the bytecode. Array is terminated by an entry with |
|
124 * a zero slot. |
|
125 */ |
|
126 SlotValue *newValues; |
|
127 |
|
128 /* |
|
129 * Vector used during SSA analysis to store values in need of merging |
|
130 * at this point. If this has incoming forward jumps and we have not |
|
131 * yet reached this point, stores values for entries on the stack and |
|
132 * for variables which have changed since the branch. If this is a loop |
|
133 * head and we haven't reached the back edge yet, stores loop phi nodes |
|
134 * for variables and entries live at the head of the loop. |
|
135 */ |
|
136 Vector<SlotValue> *pendingValues; |
|
137 }; |
|
138 }; |
|
139 |
|
140 /* |
|
141 * Information about the lifetime of a local or argument. These form a linked |
|
142 * list describing successive intervals in the program where the variable's |
|
143 * value may be live. At points in the script not in one of these segments |
|
144 * (points in a 'lifetime hole'), the variable is dead and registers containing |
|
145 * its type/payload can be discarded without needing to be synced. |
|
146 */ |
|
147 struct Lifetime |
|
148 { |
|
149 /* |
|
150 * Start and end offsets of this lifetime. The variable is live at the |
|
151 * beginning of every bytecode in this (inclusive) range. |
|
152 */ |
|
153 uint32_t start; |
|
154 uint32_t end; |
|
155 |
|
156 /* |
|
157 * In a loop body, endpoint to extend this lifetime with if the variable is |
|
158 * live in the next iteration. |
|
159 */ |
|
160 uint32_t savedEnd; |
|
161 |
|
162 /* |
|
163 * The start of this lifetime is a bytecode writing the variable. Each |
|
164 * write to a variable is associated with a lifetime. |
|
165 */ |
|
166 bool write; |
|
167 |
|
168 /* Next lifetime. The variable is dead from this->end to next->start. */ |
|
169 Lifetime *next; |
|
170 |
|
171 Lifetime(uint32_t offset, uint32_t savedEnd, Lifetime *next) |
|
172 : start(offset), end(offset), savedEnd(savedEnd), |
|
173 write(false), next(next) |
|
174 {} |
|
175 }; |
|
176 |
|
177 /* Basic information for a loop. */ |
|
178 class LoopAnalysis |
|
179 { |
|
180 public: |
|
181 /* Any loop this one is nested in. */ |
|
182 LoopAnalysis *parent; |
|
183 |
|
184 /* Offset of the head of the loop. */ |
|
185 uint32_t head; |
|
186 |
|
187 /* |
|
188 * Offset of the unique jump going to the head of the loop. The code |
|
189 * between the head and the backedge forms the loop body. |
|
190 */ |
|
191 uint32_t backedge; |
|
192 }; |
|
193 |
|
194 /* Current lifetime information for a variable. */ |
|
195 struct LifetimeVariable |
|
196 { |
|
197 /* If the variable is currently live, the lifetime segment. */ |
|
198 Lifetime *lifetime; |
|
199 |
|
200 /* If the variable is currently dead, the next live segment. */ |
|
201 Lifetime *saved; |
|
202 |
|
203 /* Jump preceding the basic block which killed this variable. */ |
|
204 uint32_t savedEnd : 31; |
|
205 |
|
206 /* If the variable needs to be kept alive until lifetime->start. */ |
|
207 bool ensured : 1; |
|
208 |
|
209 /* Whether this variable is live at offset. */ |
|
210 Lifetime * live(uint32_t offset) const { |
|
211 if (lifetime && lifetime->end >= offset) |
|
212 return lifetime; |
|
213 Lifetime *segment = lifetime ? lifetime : saved; |
|
214 while (segment && segment->start <= offset) { |
|
215 if (segment->end >= offset) |
|
216 return segment; |
|
217 segment = segment->next; |
|
218 } |
|
219 return nullptr; |
|
220 } |
|
221 |
|
222 /* |
|
223 * Get the offset of the first write to the variable in an inclusive range, |
|
224 * UINT32_MAX if the variable is not written in the range. |
|
225 */ |
|
226 uint32_t firstWrite(uint32_t start, uint32_t end) const { |
|
227 Lifetime *segment = lifetime ? lifetime : saved; |
|
228 while (segment && segment->start <= end) { |
|
229 if (segment->start >= start && segment->write) |
|
230 return segment->start; |
|
231 segment = segment->next; |
|
232 } |
|
233 return UINT32_MAX; |
|
234 } |
|
235 uint32_t firstWrite(LoopAnalysis *loop) const { |
|
236 return firstWrite(loop->head, loop->backedge); |
|
237 } |
|
238 |
|
239 /* |
|
240 * If the variable is only written once in the body of a loop, offset of |
|
241 * that write. UINT32_MAX otherwise. |
|
242 */ |
|
243 uint32_t onlyWrite(LoopAnalysis *loop) const { |
|
244 uint32_t offset = UINT32_MAX; |
|
245 Lifetime *segment = lifetime ? lifetime : saved; |
|
246 while (segment && segment->start <= loop->backedge) { |
|
247 if (segment->start >= loop->head && segment->write) { |
|
248 if (offset != UINT32_MAX) |
|
249 return UINT32_MAX; |
|
250 offset = segment->start; |
|
251 } |
|
252 segment = segment->next; |
|
253 } |
|
254 return offset; |
|
255 } |
|
256 |
|
257 #ifdef DEBUG |
|
258 void print() const; |
|
259 #endif |
|
260 }; |
|
261 |
|
262 struct SSAPhiNode; |
|
263 |
|
264 /* |
|
265 * Representation of values on stack or in slots at each point in the script. |
|
266 * Values are independent from the bytecode position, and mean the same thing |
|
267 * everywhere in the script. SSA values are immutable, except for contents of |
|
268 * the values and types in an SSAPhiNode. |
|
269 */ |
|
270 class SSAValue |
|
271 { |
|
272 friend class ScriptAnalysis; |
|
273 |
|
274 public: |
|
275 enum Kind { |
|
276 EMPTY = 0, /* Invalid entry. */ |
|
277 PUSHED = 1, /* Value pushed by some bytecode. */ |
|
278 VAR = 2, /* Initial or written value to some argument or local. */ |
|
279 PHI = 3 /* Selector for one of several values. */ |
|
280 }; |
|
281 |
|
282 Kind kind() const { |
|
283 JS_ASSERT(u.pushed.kind == u.var.kind && u.pushed.kind == u.phi.kind); |
|
284 |
|
285 /* Use a bitmask because MSVC wants to use -1 for PHI nodes. */ |
|
286 return (Kind) (u.pushed.kind & 0x3); |
|
287 } |
|
288 |
|
289 bool operator==(const SSAValue &o) const { |
|
290 return !memcmp(this, &o, sizeof(SSAValue)); |
|
291 } |
|
292 |
|
293 /* Accessors for values pushed by a bytecode within this script. */ |
|
294 |
|
295 uint32_t pushedOffset() const { |
|
296 JS_ASSERT(kind() == PUSHED); |
|
297 return u.pushed.offset; |
|
298 } |
|
299 |
|
300 uint32_t pushedIndex() const { |
|
301 JS_ASSERT(kind() == PUSHED); |
|
302 return u.pushed.index; |
|
303 } |
|
304 |
|
305 /* Accessors for initial and written values of arguments and (undefined) locals. */ |
|
306 |
|
307 bool varInitial() const { |
|
308 JS_ASSERT(kind() == VAR); |
|
309 return u.var.initial; |
|
310 } |
|
311 |
|
312 uint32_t varSlot() const { |
|
313 JS_ASSERT(kind() == VAR); |
|
314 return u.var.slot; |
|
315 } |
|
316 |
|
317 uint32_t varOffset() const { |
|
318 JS_ASSERT(!varInitial()); |
|
319 return u.var.offset; |
|
320 } |
|
321 |
|
322 /* Accessors for phi nodes. */ |
|
323 |
|
324 uint32_t phiSlot() const; |
|
325 uint32_t phiLength() const; |
|
326 const SSAValue &phiValue(uint32_t i) const; |
|
327 |
|
328 /* Offset at which this phi node was created. */ |
|
329 uint32_t phiOffset() const { |
|
330 JS_ASSERT(kind() == PHI); |
|
331 return u.phi.offset; |
|
332 } |
|
333 |
|
334 SSAPhiNode *phiNode() const { |
|
335 JS_ASSERT(kind() == PHI); |
|
336 return u.phi.node; |
|
337 } |
|
338 |
|
339 /* Other accessors. */ |
|
340 |
|
341 #ifdef DEBUG |
|
342 void print() const; |
|
343 #endif |
|
344 |
|
345 void clear() { |
|
346 mozilla::PodZero(this); |
|
347 JS_ASSERT(kind() == EMPTY); |
|
348 } |
|
349 |
|
350 void initPushed(uint32_t offset, uint32_t index) { |
|
351 clear(); |
|
352 u.pushed.kind = PUSHED; |
|
353 u.pushed.offset = offset; |
|
354 u.pushed.index = index; |
|
355 } |
|
356 |
|
357 static SSAValue PushedValue(uint32_t offset, uint32_t index) { |
|
358 SSAValue v; |
|
359 v.initPushed(offset, index); |
|
360 return v; |
|
361 } |
|
362 |
|
363 void initInitial(uint32_t slot) { |
|
364 clear(); |
|
365 u.var.kind = VAR; |
|
366 u.var.initial = true; |
|
367 u.var.slot = slot; |
|
368 } |
|
369 |
|
370 void initWritten(uint32_t slot, uint32_t offset) { |
|
371 clear(); |
|
372 u.var.kind = VAR; |
|
373 u.var.initial = false; |
|
374 u.var.slot = slot; |
|
375 u.var.offset = offset; |
|
376 } |
|
377 |
|
378 static SSAValue WrittenVar(uint32_t slot, uint32_t offset) { |
|
379 SSAValue v; |
|
380 v.initWritten(slot, offset); |
|
381 return v; |
|
382 } |
|
383 |
|
384 void initPhi(uint32_t offset, SSAPhiNode *node) { |
|
385 clear(); |
|
386 u.phi.kind = PHI; |
|
387 u.phi.offset = offset; |
|
388 u.phi.node = node; |
|
389 } |
|
390 |
|
391 static SSAValue PhiValue(uint32_t offset, SSAPhiNode *node) { |
|
392 SSAValue v; |
|
393 v.initPhi(offset, node); |
|
394 return v; |
|
395 } |
|
396 |
|
397 private: |
|
398 union { |
|
399 struct { |
|
400 Kind kind : 2; |
|
401 uint32_t offset : 30; |
|
402 uint32_t index; |
|
403 } pushed; |
|
404 struct { |
|
405 Kind kind : 2; |
|
406 bool initial : 1; |
|
407 uint32_t slot : 29; |
|
408 uint32_t offset; |
|
409 } var; |
|
410 struct { |
|
411 Kind kind : 2; |
|
412 uint32_t offset : 30; |
|
413 SSAPhiNode *node; |
|
414 } phi; |
|
415 } u; |
|
416 }; |
|
417 |
|
418 /* |
|
419 * Mutable component of a phi node, with the possible values of the phi |
|
420 * and the possible types of the node as determined by type inference. |
|
421 * When phi nodes are copied around, any updates to the original will |
|
422 * be seen by all copies made. |
|
423 */ |
|
424 struct SSAPhiNode |
|
425 { |
|
426 uint32_t slot; |
|
427 uint32_t length; |
|
428 SSAValue *options; |
|
429 SSAUseChain *uses; |
|
430 SSAPhiNode() { mozilla::PodZero(this); } |
|
431 }; |
|
432 |
|
433 inline uint32_t |
|
434 SSAValue::phiSlot() const |
|
435 { |
|
436 return u.phi.node->slot; |
|
437 } |
|
438 |
|
439 inline uint32_t |
|
440 SSAValue::phiLength() const |
|
441 { |
|
442 JS_ASSERT(kind() == PHI); |
|
443 return u.phi.node->length; |
|
444 } |
|
445 |
|
446 inline const SSAValue & |
|
447 SSAValue::phiValue(uint32_t i) const |
|
448 { |
|
449 JS_ASSERT(kind() == PHI && i < phiLength()); |
|
450 return u.phi.node->options[i]; |
|
451 } |
|
452 |
|
453 class SSAUseChain |
|
454 { |
|
455 public: |
|
456 bool popped : 1; |
|
457 uint32_t offset : 31; |
|
458 /* FIXME: Assert that only the proper arm of this union is accessed. */ |
|
459 union { |
|
460 uint32_t which; |
|
461 SSAPhiNode *phi; |
|
462 } u; |
|
463 SSAUseChain *next; |
|
464 |
|
465 SSAUseChain() { mozilla::PodZero(this); } |
|
466 }; |
|
467 |
|
468 class SlotValue |
|
469 { |
|
470 public: |
|
471 uint32_t slot; |
|
472 SSAValue value; |
|
473 SlotValue(uint32_t slot, const SSAValue &value) : slot(slot), value(value) {} |
|
474 }; |
|
475 |
|
476 ///////////////////////////////////////////////////////////////////// |
|
477 // Bytecode |
|
478 ///////////////////////////////////////////////////////////////////// |
|
479 |
|
480 #ifdef DEBUG |
|
481 void |
|
482 PrintBytecode(JSContext *cx, HandleScript script, jsbytecode *pc) |
|
483 { |
|
484 fprintf(stderr, "#%u:", script->id()); |
|
485 Sprinter sprinter(cx); |
|
486 if (!sprinter.init()) |
|
487 return; |
|
488 js_Disassemble1(cx, script, pc, script->pcToOffset(pc), true, &sprinter); |
|
489 fprintf(stderr, "%s", sprinter.string()); |
|
490 } |
|
491 #endif |
|
492 |
|
493 /* |
|
494 * For opcodes which assign to a local variable or argument, track an extra def |
|
495 * during SSA analysis for the value's use chain and assigned type. |
|
496 */ |
|
497 static inline bool |
|
498 ExtendedDef(jsbytecode *pc) |
|
499 { |
|
500 switch ((JSOp)*pc) { |
|
501 case JSOP_SETARG: |
|
502 case JSOP_SETLOCAL: |
|
503 return true; |
|
504 default: |
|
505 return false; |
|
506 } |
|
507 } |
|
508 |
|
509 /* |
|
510 * For opcodes which access local variables or arguments, we track an extra |
|
511 * use during SSA analysis for the value of the variable before/after the op. |
|
512 */ |
|
513 static inline bool |
|
514 ExtendedUse(jsbytecode *pc) |
|
515 { |
|
516 if (ExtendedDef(pc)) |
|
517 return true; |
|
518 switch ((JSOp)*pc) { |
|
519 case JSOP_GETARG: |
|
520 case JSOP_GETLOCAL: |
|
521 return true; |
|
522 default: |
|
523 return false; |
|
524 } |
|
525 } |
|
526 |
|
527 static inline unsigned |
|
528 FollowBranch(JSContext *cx, JSScript *script, unsigned offset) |
|
529 { |
|
530 /* |
|
531 * Get the target offset of a branch. For GOTO opcodes implementing |
|
532 * 'continue' statements, short circuit any artificial backwards jump |
|
533 * inserted by the emitter. |
|
534 */ |
|
535 jsbytecode *pc = script->offsetToPC(offset); |
|
536 unsigned targetOffset = offset + GET_JUMP_OFFSET(pc); |
|
537 if (targetOffset < offset) { |
|
538 jsbytecode *target = script->offsetToPC(targetOffset); |
|
539 JSOp nop = JSOp(*target); |
|
540 if (nop == JSOP_GOTO) |
|
541 return targetOffset + GET_JUMP_OFFSET(target); |
|
542 } |
|
543 return targetOffset; |
|
544 } |
|
545 |
|
546 static inline uint32_t StackSlot(JSScript *script, uint32_t index) { |
|
547 return TotalSlots(script) + index; |
|
548 } |
|
549 |
|
550 static inline uint32_t GetBytecodeSlot(JSScript *script, jsbytecode *pc) |
|
551 { |
|
552 switch (JSOp(*pc)) { |
|
553 |
|
554 case JSOP_GETARG: |
|
555 case JSOP_SETARG: |
|
556 return ArgSlot(GET_ARGNO(pc)); |
|
557 |
|
558 case JSOP_GETLOCAL: |
|
559 case JSOP_SETLOCAL: |
|
560 return LocalSlot(script, GET_LOCALNO(pc)); |
|
561 |
|
562 case JSOP_THIS: |
|
563 return ThisSlot(); |
|
564 |
|
565 default: |
|
566 MOZ_ASSUME_UNREACHABLE("Bad slot opcode"); |
|
567 } |
|
568 } |
|
569 |
|
570 ///////////////////////////////////////////////////////////////////// |
|
571 // Bytecode Analysis |
|
572 ///////////////////////////////////////////////////////////////////// |
|
573 |
|
574 inline bool |
|
575 ScriptAnalysis::addJump(JSContext *cx, unsigned offset, |
|
576 unsigned *currentOffset, unsigned *forwardJump, unsigned *forwardLoop, |
|
577 unsigned stackDepth) |
|
578 { |
|
579 JS_ASSERT(offset < script_->length()); |
|
580 |
|
581 Bytecode *&code = codeArray[offset]; |
|
582 if (!code) { |
|
583 code = cx->typeLifoAlloc().new_<Bytecode>(); |
|
584 if (!code) { |
|
585 js_ReportOutOfMemory(cx); |
|
586 return false; |
|
587 } |
|
588 code->stackDepth = stackDepth; |
|
589 } |
|
590 JS_ASSERT(code->stackDepth == stackDepth); |
|
591 |
|
592 code->jumpTarget = true; |
|
593 |
|
594 if (offset < *currentOffset) { |
|
595 if (!code->analyzed) { |
|
596 /* |
|
597 * Backedge in a while/for loop, whose body has not been analyzed |
|
598 * due to a lack of fallthrough at the loop head. Roll back the |
|
599 * offset to analyze the body. |
|
600 */ |
|
601 if (*forwardJump == 0) |
|
602 *forwardJump = *currentOffset; |
|
603 if (*forwardLoop == 0) |
|
604 *forwardLoop = *currentOffset; |
|
605 *currentOffset = offset; |
|
606 } |
|
607 } else if (offset > *forwardJump) { |
|
608 *forwardJump = offset; |
|
609 } |
|
610 |
|
611 return true; |
|
612 } |
|
613 |
|
614 inline bool |
|
615 ScriptAnalysis::jumpTarget(uint32_t offset) |
|
616 { |
|
617 JS_ASSERT(offset < script_->length()); |
|
618 return codeArray[offset] && codeArray[offset]->jumpTarget; |
|
619 } |
|
620 |
|
621 inline bool |
|
622 ScriptAnalysis::jumpTarget(const jsbytecode *pc) |
|
623 { |
|
624 return jumpTarget(script_->pcToOffset(pc)); |
|
625 } |
|
626 |
|
627 inline const SSAValue & |
|
628 ScriptAnalysis::poppedValue(uint32_t offset, uint32_t which) |
|
629 { |
|
630 JS_ASSERT(which < GetUseCount(script_, offset) + |
|
631 (ExtendedUse(script_->offsetToPC(offset)) ? 1 : 0)); |
|
632 return getCode(offset).poppedValues[which]; |
|
633 } |
|
634 |
|
635 inline const SSAValue & |
|
636 ScriptAnalysis::poppedValue(const jsbytecode *pc, uint32_t which) |
|
637 { |
|
638 return poppedValue(script_->pcToOffset(pc), which); |
|
639 } |
|
640 |
|
641 inline const SlotValue * |
|
642 ScriptAnalysis::newValues(uint32_t offset) |
|
643 { |
|
644 JS_ASSERT(offset < script_->length()); |
|
645 return getCode(offset).newValues; |
|
646 } |
|
647 |
|
648 inline const SlotValue * |
|
649 ScriptAnalysis::newValues(const jsbytecode *pc) |
|
650 { |
|
651 return newValues(script_->pcToOffset(pc)); |
|
652 } |
|
653 |
|
654 inline bool |
|
655 ScriptAnalysis::trackUseChain(const SSAValue &v) |
|
656 { |
|
657 JS_ASSERT_IF(v.kind() == SSAValue::VAR, trackSlot(v.varSlot())); |
|
658 return v.kind() != SSAValue::EMPTY && |
|
659 (v.kind() != SSAValue::VAR || !v.varInitial()); |
|
660 } |
|
661 |
|
662 /* |
|
663 * Get the use chain for an SSA value. |
|
664 */ |
|
665 inline SSAUseChain *& |
|
666 ScriptAnalysis::useChain(const SSAValue &v) |
|
667 { |
|
668 JS_ASSERT(trackUseChain(v)); |
|
669 if (v.kind() == SSAValue::PUSHED) |
|
670 return getCode(v.pushedOffset()).pushedUses[v.pushedIndex()]; |
|
671 if (v.kind() == SSAValue::VAR) |
|
672 return getCode(v.varOffset()).pushedUses[GetDefCount(script_, v.varOffset())]; |
|
673 return v.phiNode()->uses; |
|
674 } |
|
675 |
|
676 /* For a JSOP_CALL* op, get the pc of the corresponding JSOP_CALL/NEW/etc. */ |
|
677 inline jsbytecode * |
|
678 ScriptAnalysis::getCallPC(jsbytecode *pc) |
|
679 { |
|
680 SSAUseChain *uses = useChain(SSAValue::PushedValue(script_->pcToOffset(pc), 0)); |
|
681 JS_ASSERT(uses && uses->popped); |
|
682 JS_ASSERT(js_CodeSpec[script_->code()[uses->offset]].format & JOF_INVOKE); |
|
683 return script_->offsetToPC(uses->offset); |
|
684 } |
|
685 |
|
686 /* Accessors for local variable information. */ |
|
687 |
|
688 /* |
|
689 * Escaping slots include all slots that can be accessed in ways other than |
|
690 * through the corresponding LOCAL/ARG opcode. This includes all closed |
|
691 * slots in the script, all slots in scripts which use eval or are in debug |
|
692 * mode, and slots which are aliased by NAME or similar opcodes in the |
|
693 * containing script (which does not imply the variable is closed). |
|
694 */ |
|
695 inline bool |
|
696 ScriptAnalysis::slotEscapes(uint32_t slot) |
|
697 { |
|
698 JS_ASSERT(script_->compartment()->activeAnalysis); |
|
699 if (slot >= numSlots) |
|
700 return true; |
|
701 return escapedSlots[slot]; |
|
702 } |
|
703 |
|
704 /* |
|
705 * Whether we distinguish different writes of this variable while doing |
|
706 * SSA analysis. Escaping locals can be written in other scripts, and the |
|
707 * presence of NAME opcodes which could alias local variables or arguments |
|
708 * keeps us from tracking variable values at each point. |
|
709 */ |
|
710 inline bool |
|
711 ScriptAnalysis::trackSlot(uint32_t slot) |
|
712 { |
|
713 return !slotEscapes(slot) && canTrackVars && slot < 1000; |
|
714 } |
|
715 |
|
716 inline const LifetimeVariable & |
|
717 ScriptAnalysis::liveness(uint32_t slot) |
|
718 { |
|
719 JS_ASSERT(script_->compartment()->activeAnalysis); |
|
720 JS_ASSERT(!slotEscapes(slot)); |
|
721 return lifetimes[slot]; |
|
722 } |
|
723 |
|
724 bool |
|
725 ScriptAnalysis::analyzeBytecode(JSContext *cx) |
|
726 { |
|
727 JS_ASSERT(cx->compartment()->activeAnalysis); |
|
728 JS_ASSERT(!codeArray); |
|
729 |
|
730 LifoAlloc &alloc = cx->typeLifoAlloc(); |
|
731 |
|
732 numSlots = TotalSlots(script_); |
|
733 |
|
734 unsigned length = script_->length(); |
|
735 codeArray = alloc.newArray<Bytecode*>(length); |
|
736 escapedSlots = alloc.newArray<bool>(numSlots); |
|
737 |
|
738 if (!codeArray || !escapedSlots) { |
|
739 js_ReportOutOfMemory(cx); |
|
740 return false; |
|
741 } |
|
742 |
|
743 PodZero(codeArray, length); |
|
744 |
|
745 /* |
|
746 * Populate arg and local slots which can escape and be accessed in ways |
|
747 * other than through ARG* and LOCAL* opcodes (though arguments can still |
|
748 * be indirectly read but not written through 'arguments' properties). |
|
749 * All escaping locals are treated as having possible use-before-defs. |
|
750 * Conservatively use 'argumentsHasVarBinding' instead of 'needsArgsObj' |
|
751 * (needsArgsObj requires SSA which requires escapedSlots). Lastly, the |
|
752 * debugger can access any local at any time. Even though debugger |
|
753 * reads/writes are monitored by the DebugScopeProxy, this monitoring |
|
754 * updates the flow-insensitive type sets, so we cannot use SSA. |
|
755 */ |
|
756 |
|
757 PodZero(escapedSlots, numSlots); |
|
758 |
|
759 bool allVarsAliased = script_->compartment()->debugMode(); |
|
760 bool allArgsAliased = allVarsAliased || script_->argumentsHasVarBinding(); |
|
761 |
|
762 RootedScript script(cx, script_); |
|
763 for (BindingIter bi(script); bi; bi++) { |
|
764 if (bi->kind() == Binding::ARGUMENT) |
|
765 escapedSlots[ArgSlot(bi.frameIndex())] = allArgsAliased || bi->aliased(); |
|
766 else |
|
767 escapedSlots[LocalSlot(script_, bi.frameIndex())] = allVarsAliased || bi->aliased(); |
|
768 } |
|
769 |
|
770 canTrackVars = true; |
|
771 |
|
772 /* |
|
773 * If we are in the middle of one or more jumps, the offset of the highest |
|
774 * target jumping over this bytecode. Includes implicit jumps from |
|
775 * try/catch/finally blocks. |
|
776 */ |
|
777 unsigned forwardJump = 0; |
|
778 |
|
779 /* If we are in the middle of a loop, the offset of the highest backedge. */ |
|
780 unsigned forwardLoop = 0; |
|
781 |
|
782 /* |
|
783 * If we are in the middle of a try block, the offset of the highest |
|
784 * catch/finally/enditer. |
|
785 */ |
|
786 unsigned forwardCatch = 0; |
|
787 |
|
788 /* Fill in stack depth and definitions at initial bytecode. */ |
|
789 Bytecode *startcode = alloc.new_<Bytecode>(); |
|
790 if (!startcode) { |
|
791 js_ReportOutOfMemory(cx); |
|
792 return false; |
|
793 } |
|
794 |
|
795 startcode->stackDepth = 0; |
|
796 codeArray[0] = startcode; |
|
797 |
|
798 unsigned offset, nextOffset = 0; |
|
799 while (nextOffset < length) { |
|
800 offset = nextOffset; |
|
801 |
|
802 JS_ASSERT(forwardCatch <= forwardJump); |
|
803 |
|
804 /* Check if the current forward jump/try-block has finished. */ |
|
805 if (forwardJump && forwardJump == offset) |
|
806 forwardJump = 0; |
|
807 if (forwardCatch && forwardCatch == offset) |
|
808 forwardCatch = 0; |
|
809 |
|
810 Bytecode *code = maybeCode(offset); |
|
811 jsbytecode *pc = script_->offsetToPC(offset); |
|
812 |
|
813 JSOp op = (JSOp)*pc; |
|
814 JS_ASSERT(op < JSOP_LIMIT); |
|
815 |
|
816 /* Immediate successor of this bytecode. */ |
|
817 unsigned successorOffset = offset + GetBytecodeLength(pc); |
|
818 |
|
819 /* |
|
820 * Next bytecode to analyze. This is either the successor, or is an |
|
821 * earlier bytecode if this bytecode has a loop backedge. |
|
822 */ |
|
823 nextOffset = successorOffset; |
|
824 |
|
825 if (!code) { |
|
826 /* Haven't found a path by which this bytecode is reachable. */ |
|
827 continue; |
|
828 } |
|
829 |
|
830 /* |
|
831 * Update info about bytecodes inside loops, which may have been |
|
832 * analyzed before the backedge was seen. |
|
833 */ |
|
834 if (forwardLoop) { |
|
835 if (forwardLoop <= offset) |
|
836 forwardLoop = 0; |
|
837 } |
|
838 |
|
839 if (code->analyzed) { |
|
840 /* No need to reanalyze, see Bytecode::mergeDefines. */ |
|
841 continue; |
|
842 } |
|
843 |
|
844 code->analyzed = true; |
|
845 |
|
846 if (script_->hasBreakpointsAt(pc)) |
|
847 canTrackVars = false; |
|
848 |
|
849 unsigned stackDepth = code->stackDepth; |
|
850 |
|
851 if (!forwardJump) |
|
852 code->unconditional = true; |
|
853 |
|
854 unsigned nuses = GetUseCount(script_, offset); |
|
855 unsigned ndefs = GetDefCount(script_, offset); |
|
856 |
|
857 JS_ASSERT(stackDepth >= nuses); |
|
858 stackDepth -= nuses; |
|
859 stackDepth += ndefs; |
|
860 |
|
861 switch (op) { |
|
862 |
|
863 case JSOP_DEFFUN: |
|
864 case JSOP_DEFVAR: |
|
865 case JSOP_DEFCONST: |
|
866 case JSOP_SETCONST: |
|
867 canTrackVars = false; |
|
868 break; |
|
869 |
|
870 case JSOP_EVAL: |
|
871 case JSOP_SPREADEVAL: |
|
872 case JSOP_ENTERWITH: |
|
873 canTrackVars = false; |
|
874 break; |
|
875 |
|
876 case JSOP_TABLESWITCH: { |
|
877 unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc); |
|
878 jsbytecode *pc2 = pc + JUMP_OFFSET_LEN; |
|
879 int32_t low = GET_JUMP_OFFSET(pc2); |
|
880 pc2 += JUMP_OFFSET_LEN; |
|
881 int32_t high = GET_JUMP_OFFSET(pc2); |
|
882 pc2 += JUMP_OFFSET_LEN; |
|
883 |
|
884 if (!addJump(cx, defaultOffset, &nextOffset, &forwardJump, &forwardLoop, stackDepth)) |
|
885 return false; |
|
886 |
|
887 for (int32_t i = low; i <= high; i++) { |
|
888 unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2); |
|
889 if (targetOffset != offset) { |
|
890 if (!addJump(cx, targetOffset, &nextOffset, &forwardJump, &forwardLoop, stackDepth)) |
|
891 return false; |
|
892 } |
|
893 pc2 += JUMP_OFFSET_LEN; |
|
894 } |
|
895 break; |
|
896 } |
|
897 |
|
898 case JSOP_TRY: { |
|
899 /* |
|
900 * Everything between a try and corresponding catch or finally is conditional. |
|
901 * Note that there is no problem with code which is skipped by a thrown |
|
902 * exception but is not caught by a later handler in the same function: |
|
903 * no more code will execute, and it does not matter what is defined. |
|
904 */ |
|
905 JSTryNote *tn = script_->trynotes()->vector; |
|
906 JSTryNote *tnlimit = tn + script_->trynotes()->length; |
|
907 for (; tn < tnlimit; tn++) { |
|
908 unsigned startOffset = script_->mainOffset() + tn->start; |
|
909 if (startOffset == offset + 1) { |
|
910 unsigned catchOffset = startOffset + tn->length; |
|
911 |
|
912 /* This will overestimate try block code, for multiple catch/finally. */ |
|
913 if (catchOffset > forwardCatch) |
|
914 forwardCatch = catchOffset; |
|
915 |
|
916 if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) { |
|
917 if (!addJump(cx, catchOffset, &nextOffset, &forwardJump, &forwardLoop, stackDepth)) |
|
918 return false; |
|
919 getCode(catchOffset).exceptionEntry = true; |
|
920 } |
|
921 } |
|
922 } |
|
923 break; |
|
924 } |
|
925 |
|
926 case JSOP_GETLOCAL: |
|
927 case JSOP_SETLOCAL: |
|
928 JS_ASSERT(GET_LOCALNO(pc) < script_->nfixed()); |
|
929 break; |
|
930 |
|
931 default: |
|
932 break; |
|
933 } |
|
934 |
|
935 bool jump = IsJumpOpcode(op); |
|
936 |
|
937 /* Check basic jump opcodes, which may or may not have a fallthrough. */ |
|
938 if (jump) { |
|
939 /* Case instructions do not push the lvalue back when branching. */ |
|
940 unsigned newStackDepth = stackDepth; |
|
941 if (op == JSOP_CASE) |
|
942 newStackDepth--; |
|
943 |
|
944 unsigned targetOffset = offset + GET_JUMP_OFFSET(pc); |
|
945 if (!addJump(cx, targetOffset, &nextOffset, &forwardJump, &forwardLoop, newStackDepth)) |
|
946 return false; |
|
947 } |
|
948 |
|
949 /* Handle any fallthrough from this opcode. */ |
|
950 if (BytecodeFallsThrough(op)) { |
|
951 JS_ASSERT(successorOffset < script_->length()); |
|
952 |
|
953 Bytecode *&nextcode = codeArray[successorOffset]; |
|
954 |
|
955 if (!nextcode) { |
|
956 nextcode = alloc.new_<Bytecode>(); |
|
957 if (!nextcode) { |
|
958 js_ReportOutOfMemory(cx); |
|
959 return false; |
|
960 } |
|
961 nextcode->stackDepth = stackDepth; |
|
962 } |
|
963 JS_ASSERT(nextcode->stackDepth == stackDepth); |
|
964 |
|
965 if (jump) |
|
966 nextcode->jumpFallthrough = true; |
|
967 |
|
968 /* Treat the fallthrough of a branch instruction as a jump target. */ |
|
969 if (jump) |
|
970 nextcode->jumpTarget = true; |
|
971 else |
|
972 nextcode->fallthrough = true; |
|
973 } |
|
974 } |
|
975 |
|
976 JS_ASSERT(forwardJump == 0 && forwardLoop == 0 && forwardCatch == 0); |
|
977 |
|
978 /* |
|
979 * Always ensure that a script's arguments usage has been analyzed before |
|
980 * entering the script. This allows the functionPrologue to ensure that |
|
981 * arguments are always created eagerly which simplifies interp logic. |
|
982 */ |
|
983 if (!script_->analyzedArgsUsage()) { |
|
984 if (!analyzeLifetimes(cx)) |
|
985 return false; |
|
986 if (!analyzeSSA(cx)) |
|
987 return false; |
|
988 |
|
989 /* |
|
990 * Now that we have full SSA information for the script, analyze whether |
|
991 * we can avoid creating the arguments object. |
|
992 */ |
|
993 script_->setNeedsArgsObj(needsArgsObj(cx)); |
|
994 } |
|
995 |
|
996 return true; |
|
997 } |
|
998 |
|
999 ///////////////////////////////////////////////////////////////////// |
|
1000 // Lifetime Analysis |
|
1001 ///////////////////////////////////////////////////////////////////// |
|
1002 |
|
1003 bool |
|
1004 ScriptAnalysis::analyzeLifetimes(JSContext *cx) |
|
1005 { |
|
1006 JS_ASSERT(cx->compartment()->activeAnalysis); |
|
1007 JS_ASSERT(codeArray); |
|
1008 JS_ASSERT(!lifetimes); |
|
1009 |
|
1010 LifoAlloc &alloc = cx->typeLifoAlloc(); |
|
1011 |
|
1012 lifetimes = alloc.newArray<LifetimeVariable>(numSlots); |
|
1013 if (!lifetimes) { |
|
1014 js_ReportOutOfMemory(cx); |
|
1015 return false; |
|
1016 } |
|
1017 PodZero(lifetimes, numSlots); |
|
1018 |
|
1019 /* |
|
1020 * Variables which are currently dead. On forward branches to locations |
|
1021 * where these are live, they need to be marked as live. |
|
1022 */ |
|
1023 LifetimeVariable **saved = cx->pod_calloc<LifetimeVariable*>(numSlots); |
|
1024 if (!saved) { |
|
1025 js_ReportOutOfMemory(cx); |
|
1026 return false; |
|
1027 } |
|
1028 unsigned savedCount = 0; |
|
1029 |
|
1030 LoopAnalysis *loop = nullptr; |
|
1031 |
|
1032 uint32_t offset = script_->length() - 1; |
|
1033 while (offset < script_->length()) { |
|
1034 Bytecode *code = maybeCode(offset); |
|
1035 if (!code) { |
|
1036 offset--; |
|
1037 continue; |
|
1038 } |
|
1039 |
|
1040 jsbytecode *pc = script_->offsetToPC(offset); |
|
1041 |
|
1042 JSOp op = (JSOp) *pc; |
|
1043 |
|
1044 if (op == JSOP_LOOPHEAD && code->loop) { |
|
1045 /* |
|
1046 * This is the head of a loop, we need to go and make sure that any |
|
1047 * variables live at the head are live at the backedge and points prior. |
|
1048 * For each such variable, look for the last lifetime segment in the body |
|
1049 * and extend it to the end of the loop. |
|
1050 */ |
|
1051 JS_ASSERT(loop == code->loop); |
|
1052 unsigned backedge = code->loop->backedge; |
|
1053 for (unsigned i = 0; i < numSlots; i++) { |
|
1054 if (lifetimes[i].lifetime) { |
|
1055 if (!extendVariable(cx, lifetimes[i], offset, backedge)) |
|
1056 return false; |
|
1057 } |
|
1058 } |
|
1059 |
|
1060 loop = loop->parent; |
|
1061 JS_ASSERT_IF(loop, loop->head < offset); |
|
1062 } |
|
1063 |
|
1064 if (code->exceptionEntry) { |
|
1065 DebugOnly<bool> found = false; |
|
1066 JSTryNote *tn = script_->trynotes()->vector; |
|
1067 JSTryNote *tnlimit = tn + script_->trynotes()->length; |
|
1068 for (; tn < tnlimit; tn++) { |
|
1069 unsigned startOffset = script_->mainOffset() + tn->start; |
|
1070 if (startOffset + tn->length == offset) { |
|
1071 /* |
|
1072 * Extend all live variables at exception entry to the start of |
|
1073 * the try block. |
|
1074 */ |
|
1075 for (unsigned i = 0; i < numSlots; i++) { |
|
1076 if (lifetimes[i].lifetime) |
|
1077 ensureVariable(lifetimes[i], startOffset - 1); |
|
1078 } |
|
1079 |
|
1080 found = true; |
|
1081 break; |
|
1082 } |
|
1083 } |
|
1084 JS_ASSERT(found); |
|
1085 } |
|
1086 |
|
1087 switch (op) { |
|
1088 case JSOP_GETARG: |
|
1089 case JSOP_GETLOCAL: |
|
1090 case JSOP_THIS: { |
|
1091 uint32_t slot = GetBytecodeSlot(script_, pc); |
|
1092 if (!slotEscapes(slot)) { |
|
1093 if (!addVariable(cx, lifetimes[slot], offset, saved, savedCount)) |
|
1094 return false; |
|
1095 } |
|
1096 break; |
|
1097 } |
|
1098 |
|
1099 case JSOP_SETARG: |
|
1100 case JSOP_SETLOCAL: { |
|
1101 uint32_t slot = GetBytecodeSlot(script_, pc); |
|
1102 if (!slotEscapes(slot)) { |
|
1103 if (!killVariable(cx, lifetimes[slot], offset, saved, savedCount)) |
|
1104 return false; |
|
1105 } |
|
1106 break; |
|
1107 } |
|
1108 |
|
1109 case JSOP_TABLESWITCH: |
|
1110 /* Restore all saved variables. :FIXME: maybe do this precisely. */ |
|
1111 for (unsigned i = 0; i < savedCount; i++) { |
|
1112 LifetimeVariable &var = *saved[i]; |
|
1113 uint32_t savedEnd = var.savedEnd; |
|
1114 var.lifetime = alloc.new_<Lifetime>(offset, savedEnd, var.saved); |
|
1115 if (!var.lifetime) { |
|
1116 js_free(saved); |
|
1117 js_ReportOutOfMemory(cx); |
|
1118 return false; |
|
1119 } |
|
1120 var.saved = nullptr; |
|
1121 saved[i--] = saved[--savedCount]; |
|
1122 } |
|
1123 savedCount = 0; |
|
1124 break; |
|
1125 |
|
1126 case JSOP_TRY: |
|
1127 for (unsigned i = 0; i < numSlots; i++) { |
|
1128 LifetimeVariable &var = lifetimes[i]; |
|
1129 if (var.ensured) { |
|
1130 JS_ASSERT(var.lifetime); |
|
1131 if (var.lifetime->start == offset) |
|
1132 var.ensured = false; |
|
1133 } |
|
1134 } |
|
1135 break; |
|
1136 |
|
1137 case JSOP_LOOPENTRY: |
|
1138 getCode(offset).loop = loop; |
|
1139 break; |
|
1140 |
|
1141 default:; |
|
1142 } |
|
1143 |
|
1144 if (IsJumpOpcode(op)) { |
|
1145 /* |
|
1146 * Forward jumps need to pull in all variables which are live at |
|
1147 * their target offset --- the variables live before the jump are |
|
1148 * the union of those live at the fallthrough and at the target. |
|
1149 */ |
|
1150 uint32_t targetOffset = FollowBranch(cx, script_, offset); |
|
1151 |
|
1152 if (targetOffset < offset) { |
|
1153 /* This is a loop back edge, no lifetime to pull in yet. */ |
|
1154 |
|
1155 #ifdef DEBUG |
|
1156 JSOp nop = JSOp(script_->code()[targetOffset]); |
|
1157 JS_ASSERT(nop == JSOP_LOOPHEAD); |
|
1158 #endif |
|
1159 |
|
1160 LoopAnalysis *nloop = alloc.new_<LoopAnalysis>(); |
|
1161 if (!nloop) { |
|
1162 js_free(saved); |
|
1163 js_ReportOutOfMemory(cx); |
|
1164 return false; |
|
1165 } |
|
1166 PodZero(nloop); |
|
1167 |
|
1168 nloop->parent = loop; |
|
1169 loop = nloop; |
|
1170 |
|
1171 getCode(targetOffset).loop = loop; |
|
1172 loop->head = targetOffset; |
|
1173 loop->backedge = offset; |
|
1174 |
|
1175 /* |
|
1176 * Find the entry jump, which will be a GOTO for 'for' or |
|
1177 * 'while' loops or a fallthrough for 'do while' loops. |
|
1178 */ |
|
1179 uint32_t entry = targetOffset; |
|
1180 if (entry) { |
|
1181 do { |
|
1182 entry--; |
|
1183 } while (!maybeCode(entry)); |
|
1184 |
|
1185 jsbytecode *entrypc = script_->offsetToPC(entry); |
|
1186 |
|
1187 if (JSOp(*entrypc) == JSOP_GOTO) |
|
1188 entry += GET_JUMP_OFFSET(entrypc); |
|
1189 else |
|
1190 entry = targetOffset; |
|
1191 } else { |
|
1192 /* Do-while loop at the start of the script. */ |
|
1193 entry = targetOffset; |
|
1194 } |
|
1195 JS_ASSERT(script_->code()[entry] == JSOP_LOOPHEAD || |
|
1196 script_->code()[entry] == JSOP_LOOPENTRY); |
|
1197 } else { |
|
1198 for (unsigned i = 0; i < savedCount; i++) { |
|
1199 LifetimeVariable &var = *saved[i]; |
|
1200 JS_ASSERT(!var.lifetime && var.saved); |
|
1201 if (var.live(targetOffset)) { |
|
1202 /* |
|
1203 * Jumping to a place where this variable is live. Make a new |
|
1204 * lifetime segment for the variable. |
|
1205 */ |
|
1206 uint32_t savedEnd = var.savedEnd; |
|
1207 var.lifetime = alloc.new_<Lifetime>(offset, savedEnd, var.saved); |
|
1208 if (!var.lifetime) { |
|
1209 js_free(saved); |
|
1210 js_ReportOutOfMemory(cx); |
|
1211 return false; |
|
1212 } |
|
1213 var.saved = nullptr; |
|
1214 saved[i--] = saved[--savedCount]; |
|
1215 } else if (loop && !var.savedEnd) { |
|
1216 /* |
|
1217 * This jump precedes the basic block which killed the variable, |
|
1218 * remember it and use it for the end of the next lifetime |
|
1219 * segment should the variable become live again. This is needed |
|
1220 * for loops, as if we wrap liveness around the loop the isLive |
|
1221 * test below may have given the wrong answer. |
|
1222 */ |
|
1223 var.savedEnd = offset; |
|
1224 } |
|
1225 } |
|
1226 } |
|
1227 } |
|
1228 |
|
1229 offset--; |
|
1230 } |
|
1231 |
|
1232 js_free(saved); |
|
1233 |
|
1234 return true; |
|
1235 } |
|
1236 |
|
1237 #ifdef DEBUG |
|
1238 void |
|
1239 LifetimeVariable::print() const |
|
1240 { |
|
1241 Lifetime *segment = lifetime ? lifetime : saved; |
|
1242 while (segment) { |
|
1243 printf(" (%u,%u)", segment->start, segment->end); |
|
1244 segment = segment->next; |
|
1245 } |
|
1246 printf("\n"); |
|
1247 } |
|
1248 #endif /* DEBUG */ |
|
1249 |
|
1250 inline bool |
|
1251 ScriptAnalysis::addVariable(JSContext *cx, LifetimeVariable &var, unsigned offset, |
|
1252 LifetimeVariable **&saved, unsigned &savedCount) |
|
1253 { |
|
1254 if (var.lifetime) { |
|
1255 if (var.ensured) |
|
1256 return true; |
|
1257 |
|
1258 JS_ASSERT(offset < var.lifetime->start); |
|
1259 var.lifetime->start = offset; |
|
1260 } else { |
|
1261 if (var.saved) { |
|
1262 /* Remove from the list of saved entries. */ |
|
1263 for (unsigned i = 0; i < savedCount; i++) { |
|
1264 if (saved[i] == &var) { |
|
1265 JS_ASSERT(savedCount); |
|
1266 saved[i--] = saved[--savedCount]; |
|
1267 break; |
|
1268 } |
|
1269 } |
|
1270 } |
|
1271 uint32_t savedEnd = var.savedEnd; |
|
1272 var.lifetime = cx->typeLifoAlloc().new_<Lifetime>(offset, savedEnd, var.saved); |
|
1273 if (!var.lifetime) { |
|
1274 js_ReportOutOfMemory(cx); |
|
1275 return false; |
|
1276 } |
|
1277 var.saved = nullptr; |
|
1278 } |
|
1279 |
|
1280 return true; |
|
1281 } |
|
1282 |
|
1283 inline bool |
|
1284 ScriptAnalysis::killVariable(JSContext *cx, LifetimeVariable &var, unsigned offset, |
|
1285 LifetimeVariable **&saved, unsigned &savedCount) |
|
1286 { |
|
1287 if (!var.lifetime) { |
|
1288 /* Make a point lifetime indicating the write. */ |
|
1289 uint32_t savedEnd = var.savedEnd; |
|
1290 Lifetime *lifetime = cx->typeLifoAlloc().new_<Lifetime>(offset, savedEnd, var.saved); |
|
1291 if (!lifetime) { |
|
1292 js_ReportOutOfMemory(cx); |
|
1293 return false; |
|
1294 } |
|
1295 if (!var.saved) |
|
1296 saved[savedCount++] = &var; |
|
1297 var.saved = lifetime; |
|
1298 var.saved->write = true; |
|
1299 var.savedEnd = 0; |
|
1300 return true; |
|
1301 } |
|
1302 |
|
1303 JS_ASSERT_IF(!var.ensured, offset < var.lifetime->start); |
|
1304 unsigned start = var.lifetime->start; |
|
1305 |
|
1306 /* |
|
1307 * The variable is considered to be live at the bytecode which kills it |
|
1308 * (just not at earlier bytecodes). This behavior is needed by downstream |
|
1309 * register allocation (see FrameState::bestEvictReg). |
|
1310 */ |
|
1311 var.lifetime->start = offset; |
|
1312 var.lifetime->write = true; |
|
1313 |
|
1314 if (var.ensured) { |
|
1315 /* |
|
1316 * The variable is live even before the write, due to an enclosing try |
|
1317 * block. We need to split the lifetime to indicate there was a write. |
|
1318 * We set the new interval's savedEnd to 0, since it will always be |
|
1319 * adjacent to the old interval, so it never needs to be extended. |
|
1320 */ |
|
1321 var.lifetime = cx->typeLifoAlloc().new_<Lifetime>(start, 0, var.lifetime); |
|
1322 if (!var.lifetime) { |
|
1323 js_ReportOutOfMemory(cx); |
|
1324 return false; |
|
1325 } |
|
1326 var.lifetime->end = offset; |
|
1327 } else { |
|
1328 var.saved = var.lifetime; |
|
1329 var.savedEnd = 0; |
|
1330 var.lifetime = nullptr; |
|
1331 |
|
1332 saved[savedCount++] = &var; |
|
1333 } |
|
1334 |
|
1335 return true; |
|
1336 } |
|
1337 |
|
1338 inline bool |
|
1339 ScriptAnalysis::extendVariable(JSContext *cx, LifetimeVariable &var, |
|
1340 unsigned start, unsigned end) |
|
1341 { |
|
1342 JS_ASSERT(var.lifetime); |
|
1343 if (var.ensured) { |
|
1344 /* |
|
1345 * If we are still ensured to be live, the try block must scope over |
|
1346 * the loop, in which case the variable is already guaranteed to be |
|
1347 * live for the entire loop. |
|
1348 */ |
|
1349 JS_ASSERT(var.lifetime->start < start); |
|
1350 return true; |
|
1351 } |
|
1352 |
|
1353 var.lifetime->start = start; |
|
1354 |
|
1355 /* |
|
1356 * Consider this code: |
|
1357 * |
|
1358 * while (...) { (#1) |
|
1359 * use x; (#2) |
|
1360 * ... |
|
1361 * x = ...; (#3) |
|
1362 * ... |
|
1363 * } (#4) |
|
1364 * |
|
1365 * Just before analyzing the while statement, there would be a live range |
|
1366 * from #1..#2 and a "point range" at #3. The job of extendVariable is to |
|
1367 * create a new live range from #3..#4. |
|
1368 * |
|
1369 * However, more extensions may be required if the definition of x is |
|
1370 * conditional. Consider the following. |
|
1371 * |
|
1372 * while (...) { (#1) |
|
1373 * use x; (#2) |
|
1374 * ... |
|
1375 * if (...) (#5) |
|
1376 * x = ...; (#3) |
|
1377 * ... |
|
1378 * } (#4) |
|
1379 * |
|
1380 * Assume that x is not used after the loop. Then, before extendVariable is |
|
1381 * run, the live ranges would be the same as before (#1..#2 and #3..#3). We |
|
1382 * still need to create a range from #3..#4. But, since the assignment at #3 |
|
1383 * may never run, we also need to create a range from #2..#3. This is done |
|
1384 * as follows. |
|
1385 * |
|
1386 * Each time we create a Lifetime, we store the start of the most recently |
|
1387 * seen sequence of conditional code in the Lifetime's savedEnd field. So, |
|
1388 * when creating the Lifetime at #2, we set the Lifetime's savedEnd to |
|
1389 * #5. (The start of the most recent conditional is cached in each |
|
1390 * variable's savedEnd field.) Consequently, extendVariable is able to |
|
1391 * create a new interval from #2..#5 using the savedEnd field of the |
|
1392 * existing #1..#2 interval. |
|
1393 */ |
|
1394 |
|
1395 Lifetime *segment = var.lifetime; |
|
1396 while (segment && segment->start < end) { |
|
1397 uint32_t savedEnd = segment->savedEnd; |
|
1398 if (!segment->next || segment->next->start >= end) { |
|
1399 /* |
|
1400 * savedEnd is only set for variables killed in the middle of the |
|
1401 * loop. Make a tail segment connecting the last use with the |
|
1402 * back edge. |
|
1403 */ |
|
1404 if (segment->end >= end) { |
|
1405 /* Variable known to be live after the loop finishes. */ |
|
1406 break; |
|
1407 } |
|
1408 savedEnd = end; |
|
1409 } |
|
1410 JS_ASSERT(savedEnd <= end); |
|
1411 if (savedEnd > segment->end) { |
|
1412 Lifetime *tail = cx->typeLifoAlloc().new_<Lifetime>(savedEnd, 0, segment->next); |
|
1413 if (!tail) { |
|
1414 js_ReportOutOfMemory(cx); |
|
1415 return false; |
|
1416 } |
|
1417 tail->start = segment->end; |
|
1418 |
|
1419 /* |
|
1420 * Clear the segment's saved end, but preserve in the tail if this |
|
1421 * is the last segment in the loop and the variable is killed in an |
|
1422 * outer loop before the backedge. |
|
1423 */ |
|
1424 if (segment->savedEnd > end) { |
|
1425 JS_ASSERT(savedEnd == end); |
|
1426 tail->savedEnd = segment->savedEnd; |
|
1427 } |
|
1428 segment->savedEnd = 0; |
|
1429 |
|
1430 segment->next = tail; |
|
1431 segment = tail->next; |
|
1432 } else { |
|
1433 JS_ASSERT(segment->savedEnd == 0); |
|
1434 segment = segment->next; |
|
1435 } |
|
1436 } |
|
1437 return true; |
|
1438 } |
|
1439 |
|
1440 inline void |
|
1441 ScriptAnalysis::ensureVariable(LifetimeVariable &var, unsigned until) |
|
1442 { |
|
1443 JS_ASSERT(var.lifetime); |
|
1444 |
|
1445 /* |
|
1446 * If we are already ensured, the current range we are trying to ensure |
|
1447 * should already be included. |
|
1448 */ |
|
1449 if (var.ensured) { |
|
1450 JS_ASSERT(var.lifetime->start <= until); |
|
1451 return; |
|
1452 } |
|
1453 |
|
1454 JS_ASSERT(until < var.lifetime->start); |
|
1455 var.lifetime->start = until; |
|
1456 var.ensured = true; |
|
1457 } |
|
1458 |
|
1459 ///////////////////////////////////////////////////////////////////// |
|
1460 // SSA Analysis |
|
1461 ///////////////////////////////////////////////////////////////////// |
|
1462 |
|
1463 /* Current value for a variable or stack value, as tracked during SSA. */ |
|
1464 struct SSAValueInfo |
|
1465 { |
|
1466 SSAValue v; |
|
1467 |
|
1468 /* |
|
1469 * Sizes of branchTargets the last time this slot was written. Branches less |
|
1470 * than this threshold do not need to be inspected if the slot is written |
|
1471 * again, as they will already reflect the slot's value at the branch. |
|
1472 */ |
|
1473 int32_t branchSize; |
|
1474 }; |
|
1475 |
|
1476 bool |
|
1477 ScriptAnalysis::analyzeSSA(JSContext *cx) |
|
1478 { |
|
1479 JS_ASSERT(cx->compartment()->activeAnalysis); |
|
1480 JS_ASSERT(codeArray); |
|
1481 JS_ASSERT(lifetimes); |
|
1482 |
|
1483 LifoAlloc &alloc = cx->typeLifoAlloc(); |
|
1484 unsigned maxDepth = script_->nslots() - script_->nfixed(); |
|
1485 |
|
1486 /* |
|
1487 * Current value of each variable and stack value. Empty for missing or |
|
1488 * untracked entries, i.e. escaping locals and arguments. |
|
1489 */ |
|
1490 SSAValueInfo *values = cx->pod_calloc<SSAValueInfo>(numSlots + maxDepth); |
|
1491 if (!values) { |
|
1492 js_ReportOutOfMemory(cx); |
|
1493 return false; |
|
1494 } |
|
1495 struct FreeSSAValues { |
|
1496 SSAValueInfo *values; |
|
1497 FreeSSAValues(SSAValueInfo *values) : values(values) {} |
|
1498 ~FreeSSAValues() { js_free(values); } |
|
1499 } free(values); |
|
1500 |
|
1501 SSAValueInfo *stack = values + numSlots; |
|
1502 uint32_t stackDepth = 0; |
|
1503 |
|
1504 for (uint32_t slot = ArgSlot(0); slot < numSlots; slot++) { |
|
1505 if (trackSlot(slot)) |
|
1506 values[slot].v.initInitial(slot); |
|
1507 } |
|
1508 |
|
1509 /* |
|
1510 * All target offsets for forward jumps we have seen (including ones whose |
|
1511 * target we have advanced past). We lazily add pending entries at these |
|
1512 * targets for the original value of variables modified before the branch |
|
1513 * rejoins. |
|
1514 */ |
|
1515 Vector<uint32_t> branchTargets(cx); |
|
1516 |
|
1517 /* |
|
1518 * Subset of branchTargets which are exception handlers at future offsets. |
|
1519 * Any new value of a variable modified before the target is reached is a |
|
1520 * potential value at that target, along with the lazy original value. |
|
1521 */ |
|
1522 Vector<uint32_t> exceptionTargets(cx); |
|
1523 |
|
1524 uint32_t offset = 0; |
|
1525 while (offset < script_->length()) { |
|
1526 jsbytecode *pc = script_->offsetToPC(offset); |
|
1527 JSOp op = (JSOp)*pc; |
|
1528 |
|
1529 uint32_t successorOffset = offset + GetBytecodeLength(pc); |
|
1530 |
|
1531 Bytecode *code = maybeCode(pc); |
|
1532 if (!code) { |
|
1533 offset = successorOffset; |
|
1534 continue; |
|
1535 } |
|
1536 |
|
1537 if (code->exceptionEntry) { |
|
1538 /* Remove from exception targets list, which reflects only future targets. */ |
|
1539 for (size_t i = 0; i < exceptionTargets.length(); i++) { |
|
1540 if (exceptionTargets[i] == offset) { |
|
1541 exceptionTargets[i] = exceptionTargets.back(); |
|
1542 exceptionTargets.popBack(); |
|
1543 break; |
|
1544 } |
|
1545 } |
|
1546 } |
|
1547 |
|
1548 if (code->stackDepth > stackDepth) |
|
1549 PodZero(stack + stackDepth, code->stackDepth - stackDepth); |
|
1550 stackDepth = code->stackDepth; |
|
1551 |
|
1552 if (op == JSOP_LOOPHEAD && code->loop) { |
|
1553 /* |
|
1554 * Make sure there is a pending value array for phi nodes at the |
|
1555 * loop head. We won't be able to clear these until we reach the |
|
1556 * loop's back edge. |
|
1557 * |
|
1558 * We need phi nodes for all variables which might be modified |
|
1559 * during the loop. This ensures that in the loop body we have |
|
1560 * already updated state to reflect possible changes that happen |
|
1561 * before the back edge, and don't need to go back and fix things |
|
1562 * up when we *do* get to the back edge. This could be made lazier. |
|
1563 * |
|
1564 * We don't make phi nodes for values on the stack at the head of |
|
1565 * the loop. These may be popped during the loop (i.e. for ITER |
|
1566 * loops), but in such cases the original value is pushed back. |
|
1567 */ |
|
1568 Vector<SlotValue> *&pending = code->pendingValues; |
|
1569 if (!pending) { |
|
1570 pending = cx->new_< Vector<SlotValue> >(cx); |
|
1571 if (!pending) { |
|
1572 js_ReportOutOfMemory(cx); |
|
1573 return false; |
|
1574 } |
|
1575 } |
|
1576 |
|
1577 /* |
|
1578 * Make phi nodes and update state for slots which are already in |
|
1579 * pending from previous branches to the loop head, and which are |
|
1580 * modified in the body of the loop. |
|
1581 */ |
|
1582 for (unsigned i = 0; i < pending->length(); i++) { |
|
1583 SlotValue &v = (*pending)[i]; |
|
1584 if (v.slot < numSlots && liveness(v.slot).firstWrite(code->loop) != UINT32_MAX) { |
|
1585 if (v.value.kind() != SSAValue::PHI || v.value.phiOffset() != offset) { |
|
1586 JS_ASSERT(v.value.phiOffset() < offset); |
|
1587 SSAValue ov = v.value; |
|
1588 if (!makePhi(cx, v.slot, offset, &ov)) |
|
1589 return false; |
|
1590 if (!insertPhi(cx, ov, v.value)) |
|
1591 return false; |
|
1592 v.value = ov; |
|
1593 } |
|
1594 } |
|
1595 if (code->fallthrough || code->jumpFallthrough) { |
|
1596 if (!mergeValue(cx, offset, values[v.slot].v, &v)) |
|
1597 return false; |
|
1598 } |
|
1599 if (!mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets, offset - 1)) |
|
1600 return false; |
|
1601 values[v.slot].v = v.value; |
|
1602 } |
|
1603 |
|
1604 /* |
|
1605 * Make phi nodes for all other slots which might be modified |
|
1606 * during the loop. This ensures that in the loop body we have |
|
1607 * already updated state to reflect possible changes that happen |
|
1608 * before the back edge, and don't need to go back and fix things |
|
1609 * up when we *do* get to the back edge. This could be made lazier. |
|
1610 */ |
|
1611 for (uint32_t slot = ArgSlot(0); slot < numSlots + stackDepth; slot++) { |
|
1612 if (slot >= numSlots || !trackSlot(slot)) |
|
1613 continue; |
|
1614 if (liveness(slot).firstWrite(code->loop) == UINT32_MAX) |
|
1615 continue; |
|
1616 if (values[slot].v.kind() == SSAValue::PHI && values[slot].v.phiOffset() == offset) { |
|
1617 /* There is already a pending entry for this slot. */ |
|
1618 continue; |
|
1619 } |
|
1620 SSAValue ov; |
|
1621 if (!makePhi(cx, slot, offset, &ov)) |
|
1622 return false; |
|
1623 if (code->fallthrough || code->jumpFallthrough) { |
|
1624 if (!insertPhi(cx, ov, values[slot].v)) |
|
1625 return false; |
|
1626 } |
|
1627 if (!mergeBranchTarget(cx, values[slot], slot, branchTargets, offset - 1)) |
|
1628 return false; |
|
1629 values[slot].v = ov; |
|
1630 if (!pending->append(SlotValue(slot, ov))) { |
|
1631 js_ReportOutOfMemory(cx); |
|
1632 return false; |
|
1633 } |
|
1634 } |
|
1635 } else if (code->pendingValues) { |
|
1636 /* |
|
1637 * New values at this point from a previous jump to this bytecode. |
|
1638 * If there is fallthrough from the previous instruction, merge |
|
1639 * with the current state and create phi nodes where necessary, |
|
1640 * otherwise replace current values with the new values. |
|
1641 * |
|
1642 * Catch blocks are artifically treated as having fallthrough, so |
|
1643 * that values written inside the block but not subsequently |
|
1644 * overwritten are picked up. |
|
1645 */ |
|
1646 bool exception = getCode(offset).exceptionEntry; |
|
1647 Vector<SlotValue> *pending = code->pendingValues; |
|
1648 for (unsigned i = 0; i < pending->length(); i++) { |
|
1649 SlotValue &v = (*pending)[i]; |
|
1650 if (code->fallthrough || code->jumpFallthrough || |
|
1651 (exception && values[v.slot].v.kind() != SSAValue::EMPTY)) { |
|
1652 if (!mergeValue(cx, offset, values[v.slot].v, &v)) |
|
1653 return false; |
|
1654 } |
|
1655 if (!mergeBranchTarget(cx, values[v.slot], v.slot, branchTargets, offset)) |
|
1656 return false; |
|
1657 values[v.slot].v = v.value; |
|
1658 } |
|
1659 if (!freezeNewValues(cx, offset)) |
|
1660 return false; |
|
1661 } |
|
1662 |
|
1663 unsigned nuses = GetUseCount(script_, offset); |
|
1664 unsigned ndefs = GetDefCount(script_, offset); |
|
1665 JS_ASSERT(stackDepth >= nuses); |
|
1666 |
|
1667 unsigned xuses = ExtendedUse(pc) ? nuses + 1 : nuses; |
|
1668 |
|
1669 if (xuses) { |
|
1670 code->poppedValues = alloc.newArray<SSAValue>(xuses); |
|
1671 if (!code->poppedValues) { |
|
1672 js_ReportOutOfMemory(cx); |
|
1673 return false; |
|
1674 } |
|
1675 for (unsigned i = 0; i < nuses; i++) { |
|
1676 SSAValue &v = stack[stackDepth - 1 - i].v; |
|
1677 code->poppedValues[i] = v; |
|
1678 v.clear(); |
|
1679 } |
|
1680 if (xuses > nuses) { |
|
1681 /* |
|
1682 * For SETLOCAL, etc. opcodes, add an extra popped value |
|
1683 * holding the value of the local before the op. |
|
1684 */ |
|
1685 uint32_t slot = GetBytecodeSlot(script_, pc); |
|
1686 if (trackSlot(slot)) |
|
1687 code->poppedValues[nuses] = values[slot].v; |
|
1688 else |
|
1689 code->poppedValues[nuses].clear(); |
|
1690 } |
|
1691 |
|
1692 if (xuses) { |
|
1693 SSAUseChain *useChains = alloc.newArray<SSAUseChain>(xuses); |
|
1694 if (!useChains) { |
|
1695 js_ReportOutOfMemory(cx); |
|
1696 return false; |
|
1697 } |
|
1698 PodZero(useChains, xuses); |
|
1699 for (unsigned i = 0; i < xuses; i++) { |
|
1700 const SSAValue &v = code->poppedValues[i]; |
|
1701 if (trackUseChain(v)) { |
|
1702 SSAUseChain *&uses = useChain(v); |
|
1703 useChains[i].popped = true; |
|
1704 useChains[i].offset = offset; |
|
1705 useChains[i].u.which = i; |
|
1706 useChains[i].next = uses; |
|
1707 uses = &useChains[i]; |
|
1708 } |
|
1709 } |
|
1710 } |
|
1711 } |
|
1712 |
|
1713 stackDepth -= nuses; |
|
1714 |
|
1715 for (unsigned i = 0; i < ndefs; i++) |
|
1716 stack[stackDepth + i].v.initPushed(offset, i); |
|
1717 |
|
1718 unsigned xdefs = ExtendedDef(pc) ? ndefs + 1 : ndefs; |
|
1719 if (xdefs) { |
|
1720 code->pushedUses = alloc.newArray<SSAUseChain *>(xdefs); |
|
1721 if (!code->pushedUses) { |
|
1722 js_ReportOutOfMemory(cx); |
|
1723 return false; |
|
1724 } |
|
1725 PodZero(code->pushedUses, xdefs); |
|
1726 } |
|
1727 |
|
1728 stackDepth += ndefs; |
|
1729 |
|
1730 if (op == JSOP_SETARG || op == JSOP_SETLOCAL) { |
|
1731 uint32_t slot = GetBytecodeSlot(script_, pc); |
|
1732 if (trackSlot(slot)) { |
|
1733 if (!mergeBranchTarget(cx, values[slot], slot, branchTargets, offset)) |
|
1734 return false; |
|
1735 if (!mergeExceptionTarget(cx, values[slot].v, slot, exceptionTargets)) |
|
1736 return false; |
|
1737 values[slot].v.initWritten(slot, offset); |
|
1738 } |
|
1739 } |
|
1740 |
|
1741 switch (op) { |
|
1742 case JSOP_GETARG: |
|
1743 case JSOP_GETLOCAL: { |
|
1744 uint32_t slot = GetBytecodeSlot(script_, pc); |
|
1745 if (trackSlot(slot)) { |
|
1746 /* |
|
1747 * Propagate the current value of the local to the pushed value, |
|
1748 * and remember it with an extended use on the opcode. |
|
1749 */ |
|
1750 stack[stackDepth - 1].v = code->poppedValues[0] = values[slot].v; |
|
1751 } |
|
1752 break; |
|
1753 } |
|
1754 |
|
1755 /* Short circuit ops which push back one of their operands. */ |
|
1756 |
|
1757 case JSOP_MOREITER: |
|
1758 stack[stackDepth - 2].v = code->poppedValues[0]; |
|
1759 break; |
|
1760 |
|
1761 case JSOP_MUTATEPROTO: |
|
1762 case JSOP_INITPROP: |
|
1763 case JSOP_INITPROP_GETTER: |
|
1764 case JSOP_INITPROP_SETTER: |
|
1765 stack[stackDepth - 1].v = code->poppedValues[1]; |
|
1766 break; |
|
1767 |
|
1768 case JSOP_SPREAD: |
|
1769 case JSOP_INITELEM_INC: |
|
1770 stack[stackDepth - 2].v = code->poppedValues[2]; |
|
1771 break; |
|
1772 |
|
1773 case JSOP_INITELEM_ARRAY: |
|
1774 stack[stackDepth - 1].v = code->poppedValues[1]; |
|
1775 break; |
|
1776 |
|
1777 case JSOP_INITELEM: |
|
1778 case JSOP_INITELEM_GETTER: |
|
1779 case JSOP_INITELEM_SETTER: |
|
1780 stack[stackDepth - 1].v = code->poppedValues[2]; |
|
1781 break; |
|
1782 |
|
1783 case JSOP_DUP: |
|
1784 stack[stackDepth - 1].v = stack[stackDepth - 2].v = code->poppedValues[0]; |
|
1785 break; |
|
1786 |
|
1787 case JSOP_DUP2: |
|
1788 stack[stackDepth - 1].v = stack[stackDepth - 3].v = code->poppedValues[0]; |
|
1789 stack[stackDepth - 2].v = stack[stackDepth - 4].v = code->poppedValues[1]; |
|
1790 break; |
|
1791 |
|
1792 case JSOP_DUPAT: { |
|
1793 unsigned pickedDepth = GET_UINT24 (pc); |
|
1794 JS_ASSERT(pickedDepth < stackDepth - 1); |
|
1795 stack[stackDepth - 1].v = stack[stackDepth - 2 - pickedDepth].v; |
|
1796 break; |
|
1797 } |
|
1798 |
|
1799 case JSOP_SWAP: |
|
1800 /* Swap is like pick 1. */ |
|
1801 case JSOP_PICK: { |
|
1802 unsigned pickedDepth = (op == JSOP_SWAP ? 1 : pc[1]); |
|
1803 stack[stackDepth - 1].v = code->poppedValues[pickedDepth]; |
|
1804 for (unsigned i = 0; i < pickedDepth; i++) |
|
1805 stack[stackDepth - 2 - i].v = code->poppedValues[i]; |
|
1806 break; |
|
1807 } |
|
1808 |
|
1809 /* |
|
1810 * Switch and try blocks preserve the stack between the original op |
|
1811 * and all case statements or exception/finally handlers. |
|
1812 */ |
|
1813 |
|
1814 case JSOP_TABLESWITCH: { |
|
1815 unsigned defaultOffset = offset + GET_JUMP_OFFSET(pc); |
|
1816 jsbytecode *pc2 = pc + JUMP_OFFSET_LEN; |
|
1817 int32_t low = GET_JUMP_OFFSET(pc2); |
|
1818 pc2 += JUMP_OFFSET_LEN; |
|
1819 int32_t high = GET_JUMP_OFFSET(pc2); |
|
1820 pc2 += JUMP_OFFSET_LEN; |
|
1821 |
|
1822 for (int32_t i = low; i <= high; i++) { |
|
1823 unsigned targetOffset = offset + GET_JUMP_OFFSET(pc2); |
|
1824 if (targetOffset != offset) { |
|
1825 if (!checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth)) |
|
1826 return false; |
|
1827 } |
|
1828 pc2 += JUMP_OFFSET_LEN; |
|
1829 } |
|
1830 |
|
1831 if (!checkBranchTarget(cx, defaultOffset, branchTargets, values, stackDepth)) |
|
1832 return false; |
|
1833 break; |
|
1834 } |
|
1835 |
|
1836 case JSOP_TRY: { |
|
1837 JSTryNote *tn = script_->trynotes()->vector; |
|
1838 JSTryNote *tnlimit = tn + script_->trynotes()->length; |
|
1839 for (; tn < tnlimit; tn++) { |
|
1840 unsigned startOffset = script_->mainOffset() + tn->start; |
|
1841 if (startOffset == offset + 1) { |
|
1842 unsigned catchOffset = startOffset + tn->length; |
|
1843 |
|
1844 if (tn->kind != JSTRY_ITER && tn->kind != JSTRY_LOOP) { |
|
1845 if (!checkBranchTarget(cx, catchOffset, branchTargets, values, stackDepth)) |
|
1846 return false; |
|
1847 if (!checkExceptionTarget(cx, catchOffset, exceptionTargets)) |
|
1848 return false; |
|
1849 } |
|
1850 } |
|
1851 } |
|
1852 break; |
|
1853 } |
|
1854 |
|
1855 case JSOP_THROW: |
|
1856 case JSOP_RETURN: |
|
1857 case JSOP_RETRVAL: |
|
1858 if (!mergeAllExceptionTargets(cx, values, exceptionTargets)) |
|
1859 return false; |
|
1860 break; |
|
1861 |
|
1862 default:; |
|
1863 } |
|
1864 |
|
1865 if (IsJumpOpcode(op)) { |
|
1866 unsigned targetOffset = FollowBranch(cx, script_, offset); |
|
1867 if (!checkBranchTarget(cx, targetOffset, branchTargets, values, stackDepth)) |
|
1868 return false; |
|
1869 |
|
1870 /* |
|
1871 * If this is a back edge, we're done with the loop and can freeze |
|
1872 * the phi values at the head now. |
|
1873 */ |
|
1874 if (targetOffset < offset) { |
|
1875 if (!freezeNewValues(cx, targetOffset)) |
|
1876 return false; |
|
1877 } |
|
1878 } |
|
1879 |
|
1880 offset = successorOffset; |
|
1881 } |
|
1882 |
|
1883 return true; |
|
1884 } |
|
1885 |
|
1886 /* Get a phi node's capacity for a given length. */ |
|
1887 static inline unsigned |
|
1888 PhiNodeCapacity(unsigned length) |
|
1889 { |
|
1890 if (length <= 4) |
|
1891 return 4; |
|
1892 |
|
1893 return 1 << (FloorLog2(length - 1) + 1); |
|
1894 } |
|
1895 |
|
1896 bool |
|
1897 ScriptAnalysis::makePhi(JSContext *cx, uint32_t slot, uint32_t offset, SSAValue *pv) |
|
1898 { |
|
1899 SSAPhiNode *node = cx->typeLifoAlloc().new_<SSAPhiNode>(); |
|
1900 SSAValue *options = cx->typeLifoAlloc().newArray<SSAValue>(PhiNodeCapacity(0)); |
|
1901 if (!node || !options) { |
|
1902 js_ReportOutOfMemory(cx); |
|
1903 return false; |
|
1904 } |
|
1905 node->slot = slot; |
|
1906 node->options = options; |
|
1907 pv->initPhi(offset, node); |
|
1908 return true; |
|
1909 } |
|
1910 |
|
1911 bool |
|
1912 ScriptAnalysis::insertPhi(JSContext *cx, SSAValue &phi, const SSAValue &v) |
|
1913 { |
|
1914 JS_ASSERT(phi.kind() == SSAValue::PHI); |
|
1915 SSAPhiNode *node = phi.phiNode(); |
|
1916 |
|
1917 /* |
|
1918 * Filter dupes inserted into small nodes to keep things clean and avoid |
|
1919 * extra type constraints, but don't bother on large phi nodes to avoid |
|
1920 * quadratic behavior. |
|
1921 */ |
|
1922 if (node->length <= 8) { |
|
1923 for (unsigned i = 0; i < node->length; i++) { |
|
1924 if (v == node->options[i]) |
|
1925 return true; |
|
1926 } |
|
1927 } |
|
1928 |
|
1929 if (trackUseChain(v)) { |
|
1930 SSAUseChain *&uses = useChain(v); |
|
1931 |
|
1932 SSAUseChain *use = cx->typeLifoAlloc().new_<SSAUseChain>(); |
|
1933 if (!use) { |
|
1934 js_ReportOutOfMemory(cx); |
|
1935 return false; |
|
1936 } |
|
1937 |
|
1938 use->popped = false; |
|
1939 use->offset = phi.phiOffset(); |
|
1940 use->u.phi = node; |
|
1941 use->next = uses; |
|
1942 uses = use; |
|
1943 } |
|
1944 |
|
1945 if (node->length < PhiNodeCapacity(node->length)) { |
|
1946 node->options[node->length++] = v; |
|
1947 return true; |
|
1948 } |
|
1949 |
|
1950 SSAValue *newOptions = |
|
1951 cx->typeLifoAlloc().newArray<SSAValue>(PhiNodeCapacity(node->length + 1)); |
|
1952 if (!newOptions) { |
|
1953 js_ReportOutOfMemory(cx); |
|
1954 return false; |
|
1955 } |
|
1956 |
|
1957 PodCopy(newOptions, node->options, node->length); |
|
1958 node->options = newOptions; |
|
1959 node->options[node->length++] = v; |
|
1960 |
|
1961 return true; |
|
1962 } |
|
1963 |
|
1964 inline bool |
|
1965 ScriptAnalysis::mergeValue(JSContext *cx, uint32_t offset, const SSAValue &v, SlotValue *pv) |
|
1966 { |
|
1967 /* Make sure that v is accounted for in the pending value or phi value at pv. */ |
|
1968 JS_ASSERT(v.kind() != SSAValue::EMPTY && pv->value.kind() != SSAValue::EMPTY); |
|
1969 |
|
1970 if (v == pv->value) |
|
1971 return true; |
|
1972 |
|
1973 if (pv->value.kind() != SSAValue::PHI || pv->value.phiOffset() < offset) { |
|
1974 SSAValue ov = pv->value; |
|
1975 return makePhi(cx, pv->slot, offset, &pv->value) |
|
1976 && insertPhi(cx, pv->value, v) |
|
1977 && insertPhi(cx, pv->value, ov); |
|
1978 } |
|
1979 |
|
1980 JS_ASSERT(pv->value.phiOffset() == offset); |
|
1981 return insertPhi(cx, pv->value, v); |
|
1982 } |
|
1983 |
|
1984 bool |
|
1985 ScriptAnalysis::checkPendingValue(JSContext *cx, const SSAValue &v, uint32_t slot, |
|
1986 Vector<SlotValue> *pending) |
|
1987 { |
|
1988 JS_ASSERT(v.kind() != SSAValue::EMPTY); |
|
1989 |
|
1990 for (unsigned i = 0; i < pending->length(); i++) { |
|
1991 if ((*pending)[i].slot == slot) |
|
1992 return true; |
|
1993 } |
|
1994 |
|
1995 if (!pending->append(SlotValue(slot, v))) { |
|
1996 js_ReportOutOfMemory(cx); |
|
1997 return false; |
|
1998 } |
|
1999 |
|
2000 return true; |
|
2001 } |
|
2002 |
|
2003 bool |
|
2004 ScriptAnalysis::checkBranchTarget(JSContext *cx, uint32_t targetOffset, |
|
2005 Vector<uint32_t> &branchTargets, |
|
2006 SSAValueInfo *values, uint32_t stackDepth) |
|
2007 { |
|
2008 unsigned targetDepth = getCode(targetOffset).stackDepth; |
|
2009 JS_ASSERT(targetDepth <= stackDepth); |
|
2010 |
|
2011 /* |
|
2012 * If there is already an active branch to target, make sure its pending |
|
2013 * values reflect any changes made since the first branch. Otherwise, add a |
|
2014 * new pending branch and determine its pending values lazily. |
|
2015 */ |
|
2016 Vector<SlotValue> *&pending = getCode(targetOffset).pendingValues; |
|
2017 if (pending) { |
|
2018 for (unsigned i = 0; i < pending->length(); i++) { |
|
2019 SlotValue &v = (*pending)[i]; |
|
2020 if (!mergeValue(cx, targetOffset, values[v.slot].v, &v)) |
|
2021 return false; |
|
2022 } |
|
2023 } else { |
|
2024 pending = cx->new_< Vector<SlotValue> >(cx); |
|
2025 if (!pending || !branchTargets.append(targetOffset)) { |
|
2026 js_ReportOutOfMemory(cx); |
|
2027 return false; |
|
2028 } |
|
2029 } |
|
2030 |
|
2031 /* |
|
2032 * Make sure there is a pending entry for each value on the stack. |
|
2033 * The number of stack entries at join points is usually zero, and |
|
2034 * we don't want to look at the active branches while popping and |
|
2035 * pushing values in each opcode. |
|
2036 */ |
|
2037 for (unsigned i = 0; i < targetDepth; i++) { |
|
2038 uint32_t slot = StackSlot(script_, i); |
|
2039 if (!checkPendingValue(cx, values[slot].v, slot, pending)) |
|
2040 return false; |
|
2041 } |
|
2042 |
|
2043 return true; |
|
2044 } |
|
2045 |
|
2046 bool |
|
2047 ScriptAnalysis::checkExceptionTarget(JSContext *cx, uint32_t catchOffset, |
|
2048 Vector<uint32_t> &exceptionTargets) |
|
2049 { |
|
2050 JS_ASSERT(getCode(catchOffset).exceptionEntry); |
|
2051 |
|
2052 /* |
|
2053 * The catch offset will already be in the branch targets, just check |
|
2054 * whether this is already a known exception target. |
|
2055 */ |
|
2056 for (unsigned i = 0; i < exceptionTargets.length(); i++) { |
|
2057 if (exceptionTargets[i] == catchOffset) |
|
2058 return true; |
|
2059 } |
|
2060 if (!exceptionTargets.append(catchOffset)) { |
|
2061 js_ReportOutOfMemory(cx); |
|
2062 return false; |
|
2063 } |
|
2064 |
|
2065 return true; |
|
2066 } |
|
2067 |
|
2068 bool |
|
2069 ScriptAnalysis::mergeBranchTarget(JSContext *cx, SSAValueInfo &value, uint32_t slot, |
|
2070 const Vector<uint32_t> &branchTargets, uint32_t currentOffset) |
|
2071 { |
|
2072 if (slot >= numSlots) { |
|
2073 /* |
|
2074 * There is no need to lazily check that there are pending values at |
|
2075 * branch targets for slots on the stack, these are added to pending |
|
2076 * eagerly. |
|
2077 */ |
|
2078 return true; |
|
2079 } |
|
2080 |
|
2081 JS_ASSERT(trackSlot(slot)); |
|
2082 |
|
2083 /* |
|
2084 * Before changing the value of a variable, make sure the old value is |
|
2085 * marked at the target of any branches jumping over the current opcode. |
|
2086 * Only look at new branch targets which have appeared since the last time |
|
2087 * the variable was written. |
|
2088 */ |
|
2089 for (int i = branchTargets.length() - 1; i >= value.branchSize; i--) { |
|
2090 if (branchTargets[i] <= currentOffset) |
|
2091 continue; |
|
2092 |
|
2093 const Bytecode &code = getCode(branchTargets[i]); |
|
2094 |
|
2095 Vector<SlotValue> *pending = code.pendingValues; |
|
2096 if (!checkPendingValue(cx, value.v, slot, pending)) |
|
2097 return false; |
|
2098 } |
|
2099 |
|
2100 value.branchSize = branchTargets.length(); |
|
2101 return true; |
|
2102 } |
|
2103 |
|
2104 bool |
|
2105 ScriptAnalysis::mergeExceptionTarget(JSContext *cx, const SSAValue &value, uint32_t slot, |
|
2106 const Vector<uint32_t> &exceptionTargets) |
|
2107 { |
|
2108 JS_ASSERT(trackSlot(slot)); |
|
2109 |
|
2110 /* |
|
2111 * Update the value at exception targets with the value of a variable |
|
2112 * before it is overwritten. Unlike mergeBranchTarget, this is done whether |
|
2113 * or not the overwritten value is the value of the variable at the |
|
2114 * original branch. Values for a variable which are written after the |
|
2115 * try block starts and overwritten before it is finished can still be |
|
2116 * seen at exception handlers via exception paths. |
|
2117 */ |
|
2118 for (unsigned i = 0; i < exceptionTargets.length(); i++) { |
|
2119 unsigned offset = exceptionTargets[i]; |
|
2120 Vector<SlotValue> *pending = getCode(offset).pendingValues; |
|
2121 |
|
2122 bool duplicate = false; |
|
2123 for (unsigned i = 0; i < pending->length(); i++) { |
|
2124 if ((*pending)[i].slot == slot) { |
|
2125 duplicate = true; |
|
2126 SlotValue &v = (*pending)[i]; |
|
2127 if (!mergeValue(cx, offset, value, &v)) |
|
2128 return false; |
|
2129 break; |
|
2130 } |
|
2131 } |
|
2132 |
|
2133 if (!duplicate && !pending->append(SlotValue(slot, value))) { |
|
2134 js_ReportOutOfMemory(cx); |
|
2135 return false; |
|
2136 } |
|
2137 } |
|
2138 |
|
2139 return true; |
|
2140 } |
|
2141 |
|
2142 bool |
|
2143 ScriptAnalysis::mergeAllExceptionTargets(JSContext *cx, SSAValueInfo *values, |
|
2144 const Vector<uint32_t> &exceptionTargets) |
|
2145 { |
|
2146 for (unsigned i = 0; i < exceptionTargets.length(); i++) { |
|
2147 Vector<SlotValue> *pending = getCode(exceptionTargets[i]).pendingValues; |
|
2148 for (unsigned i = 0; i < pending->length(); i++) { |
|
2149 const SlotValue &v = (*pending)[i]; |
|
2150 if (trackSlot(v.slot)) { |
|
2151 if (!mergeExceptionTarget(cx, values[v.slot].v, v.slot, exceptionTargets)) |
|
2152 return false; |
|
2153 } |
|
2154 } |
|
2155 } |
|
2156 return true; |
|
2157 } |
|
2158 |
|
2159 bool |
|
2160 ScriptAnalysis::freezeNewValues(JSContext *cx, uint32_t offset) |
|
2161 { |
|
2162 Bytecode &code = getCode(offset); |
|
2163 |
|
2164 Vector<SlotValue> *pending = code.pendingValues; |
|
2165 code.pendingValues = nullptr; |
|
2166 |
|
2167 unsigned count = pending->length(); |
|
2168 if (count == 0) { |
|
2169 js_delete(pending); |
|
2170 return true; |
|
2171 } |
|
2172 |
|
2173 code.newValues = cx->typeLifoAlloc().newArray<SlotValue>(count + 1); |
|
2174 if (!code.newValues) { |
|
2175 js_ReportOutOfMemory(cx); |
|
2176 return false; |
|
2177 } |
|
2178 |
|
2179 for (unsigned i = 0; i < count; i++) |
|
2180 code.newValues[i] = (*pending)[i]; |
|
2181 code.newValues[count].slot = 0; |
|
2182 code.newValues[count].value.clear(); |
|
2183 |
|
2184 js_delete(pending); |
|
2185 return true; |
|
2186 } |
|
2187 |
|
2188 bool |
|
2189 ScriptAnalysis::needsArgsObj(JSContext *cx, SeenVector &seen, const SSAValue &v) |
|
2190 { |
|
2191 /* |
|
2192 * trackUseChain is false for initial values of variables, which |
|
2193 * cannot hold the script's arguments object. |
|
2194 */ |
|
2195 if (!trackUseChain(v)) |
|
2196 return false; |
|
2197 |
|
2198 for (unsigned i = 0; i < seen.length(); i++) { |
|
2199 if (v == seen[i]) |
|
2200 return false; |
|
2201 } |
|
2202 if (!seen.append(v)) |
|
2203 return true; |
|
2204 |
|
2205 SSAUseChain *use = useChain(v); |
|
2206 while (use) { |
|
2207 if (needsArgsObj(cx, seen, use)) |
|
2208 return true; |
|
2209 use = use->next; |
|
2210 } |
|
2211 |
|
2212 return false; |
|
2213 } |
|
2214 |
|
2215 bool |
|
2216 ScriptAnalysis::needsArgsObj(JSContext *cx, SeenVector &seen, SSAUseChain *use) |
|
2217 { |
|
2218 if (!use->popped) |
|
2219 return needsArgsObj(cx, seen, SSAValue::PhiValue(use->offset, use->u.phi)); |
|
2220 |
|
2221 jsbytecode *pc = script_->offsetToPC(use->offset); |
|
2222 JSOp op = JSOp(*pc); |
|
2223 |
|
2224 if (op == JSOP_POP || op == JSOP_POPN) |
|
2225 return false; |
|
2226 |
|
2227 /* We can read the frame's arguments directly for f.apply(x, arguments). */ |
|
2228 if (op == JSOP_FUNAPPLY && GET_ARGC(pc) == 2 && use->u.which == 0) { |
|
2229 argumentsContentsObserved_ = true; |
|
2230 return false; |
|
2231 } |
|
2232 |
|
2233 /* arguments[i] can read fp->canonicalActualArg(i) directly. */ |
|
2234 if (op == JSOP_GETELEM && use->u.which == 1) { |
|
2235 argumentsContentsObserved_ = true; |
|
2236 return false; |
|
2237 } |
|
2238 |
|
2239 /* arguments.length length can read fp->numActualArgs() directly. */ |
|
2240 if (op == JSOP_LENGTH) |
|
2241 return false; |
|
2242 |
|
2243 /* Allow assignments to non-closed locals (but not arguments). */ |
|
2244 |
|
2245 if (op == JSOP_SETLOCAL) { |
|
2246 uint32_t slot = GetBytecodeSlot(script_, pc); |
|
2247 if (!trackSlot(slot)) |
|
2248 return true; |
|
2249 return needsArgsObj(cx, seen, SSAValue::PushedValue(use->offset, 0)) || |
|
2250 needsArgsObj(cx, seen, SSAValue::WrittenVar(slot, use->offset)); |
|
2251 } |
|
2252 |
|
2253 if (op == JSOP_GETLOCAL) |
|
2254 return needsArgsObj(cx, seen, SSAValue::PushedValue(use->offset, 0)); |
|
2255 |
|
2256 return true; |
|
2257 } |
|
2258 |
|
2259 bool |
|
2260 ScriptAnalysis::needsArgsObj(JSContext *cx) |
|
2261 { |
|
2262 JS_ASSERT(script_->argumentsHasVarBinding()); |
|
2263 |
|
2264 /* |
|
2265 * Always construct arguments objects when in debug mode and for generator |
|
2266 * scripts (generators can be suspended when speculation fails). |
|
2267 * |
|
2268 * FIXME: Don't build arguments for ES6 generator expressions. |
|
2269 */ |
|
2270 if (cx->compartment()->debugMode() || script_->isGenerator()) |
|
2271 return true; |
|
2272 |
|
2273 /* |
|
2274 * If the script has dynamic name accesses which could reach 'arguments', |
|
2275 * the parser will already have checked to ensure there are no explicit |
|
2276 * uses of 'arguments' in the function. If there are such uses, the script |
|
2277 * will be marked as definitely needing an arguments object. |
|
2278 * |
|
2279 * New accesses on 'arguments' can occur through 'eval' or the debugger |
|
2280 * statement. In the former case, we will dynamically detect the use and |
|
2281 * mark the arguments optimization as having failed. |
|
2282 */ |
|
2283 if (script_->bindingsAccessedDynamically()) |
|
2284 return false; |
|
2285 |
|
2286 unsigned pcOff = script_->pcToOffset(script_->argumentsBytecode()); |
|
2287 |
|
2288 SeenVector seen(cx); |
|
2289 if (needsArgsObj(cx, seen, SSAValue::PushedValue(pcOff, 0))) |
|
2290 return true; |
|
2291 |
|
2292 /* |
|
2293 * If a script explicitly accesses the contents of 'arguments', and has |
|
2294 * formals which may be stored as part of a call object, don't use lazy |
|
2295 * arguments. The compiler can then assume that accesses through |
|
2296 * arguments[i] will be on unaliased variables. |
|
2297 */ |
|
2298 if (script_->funHasAnyAliasedFormal() && argumentsContentsObserved_) |
|
2299 return true; |
|
2300 |
|
2301 return false; |
|
2302 } |
|
2303 |
|
2304 #ifdef DEBUG |
|
2305 |
|
2306 void |
|
2307 ScriptAnalysis::printSSA(JSContext *cx) |
|
2308 { |
|
2309 types::AutoEnterAnalysis enter(cx); |
|
2310 |
|
2311 printf("\n"); |
|
2312 |
|
2313 RootedScript script(cx, script_); |
|
2314 for (unsigned offset = 0; offset < script_->length(); offset++) { |
|
2315 Bytecode *code = maybeCode(offset); |
|
2316 if (!code) |
|
2317 continue; |
|
2318 |
|
2319 jsbytecode *pc = script_->offsetToPC(offset); |
|
2320 |
|
2321 PrintBytecode(cx, script, pc); |
|
2322 |
|
2323 SlotValue *newv = code->newValues; |
|
2324 if (newv) { |
|
2325 while (newv->slot) { |
|
2326 if (newv->value.kind() != SSAValue::PHI || newv->value.phiOffset() != offset) { |
|
2327 newv++; |
|
2328 continue; |
|
2329 } |
|
2330 printf(" phi "); |
|
2331 newv->value.print(); |
|
2332 printf(" ["); |
|
2333 for (unsigned i = 0; i < newv->value.phiLength(); i++) { |
|
2334 if (i) |
|
2335 printf(","); |
|
2336 newv->value.phiValue(i).print(); |
|
2337 } |
|
2338 printf("]\n"); |
|
2339 newv++; |
|
2340 } |
|
2341 } |
|
2342 |
|
2343 unsigned nuses = GetUseCount(script_, offset); |
|
2344 unsigned xuses = ExtendedUse(pc) ? nuses + 1 : nuses; |
|
2345 |
|
2346 for (unsigned i = 0; i < xuses; i++) { |
|
2347 printf(" popped%d: ", i); |
|
2348 code->poppedValues[i].print(); |
|
2349 printf("\n"); |
|
2350 } |
|
2351 } |
|
2352 |
|
2353 printf("\n"); |
|
2354 } |
|
2355 |
|
2356 void |
|
2357 SSAValue::print() const |
|
2358 { |
|
2359 switch (kind()) { |
|
2360 |
|
2361 case EMPTY: |
|
2362 printf("empty"); |
|
2363 break; |
|
2364 |
|
2365 case PUSHED: |
|
2366 printf("pushed:%05u#%u", pushedOffset(), pushedIndex()); |
|
2367 break; |
|
2368 |
|
2369 case VAR: |
|
2370 if (varInitial()) |
|
2371 printf("initial:%u", varSlot()); |
|
2372 else |
|
2373 printf("write:%05u", varOffset()); |
|
2374 break; |
|
2375 |
|
2376 case PHI: |
|
2377 printf("phi:%05u#%u", phiOffset(), phiSlot()); |
|
2378 break; |
|
2379 |
|
2380 default: |
|
2381 MOZ_ASSUME_UNREACHABLE("Bad kind"); |
|
2382 } |
|
2383 } |
|
2384 |
|
2385 void |
|
2386 ScriptAnalysis::assertMatchingDebugMode() |
|
2387 { |
|
2388 JS_ASSERT(!!script_->compartment()->debugMode() == !!originalDebugMode_); |
|
2389 } |
|
2390 |
|
2391 void |
|
2392 ScriptAnalysis::assertMatchingStackDepthAtOffset(uint32_t offset, uint32_t stackDepth) { |
|
2393 JS_ASSERT_IF(maybeCode(offset), getCode(offset).stackDepth == stackDepth); |
|
2394 } |
|
2395 |
|
2396 #endif /* DEBUG */ |
|
2397 |
|
2398 } // namespace analyze |
|
2399 } // namespace js |