js/src/jsanalyze.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

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

mercurial