Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | /* |
michael@0 | 8 | * JS bytecode generation. |
michael@0 | 9 | */ |
michael@0 | 10 | |
michael@0 | 11 | #include "frontend/BytecodeEmitter.h" |
michael@0 | 12 | |
michael@0 | 13 | #include "mozilla/DebugOnly.h" |
michael@0 | 14 | #include "mozilla/FloatingPoint.h" |
michael@0 | 15 | #include "mozilla/PodOperations.h" |
michael@0 | 16 | |
michael@0 | 17 | #include <string.h> |
michael@0 | 18 | |
michael@0 | 19 | #include "jsapi.h" |
michael@0 | 20 | #include "jsatom.h" |
michael@0 | 21 | #include "jscntxt.h" |
michael@0 | 22 | #include "jsfun.h" |
michael@0 | 23 | #include "jsnum.h" |
michael@0 | 24 | #include "jsopcode.h" |
michael@0 | 25 | #include "jsscript.h" |
michael@0 | 26 | #include "jstypes.h" |
michael@0 | 27 | #include "jsutil.h" |
michael@0 | 28 | |
michael@0 | 29 | #include "frontend/Parser.h" |
michael@0 | 30 | #include "frontend/TokenStream.h" |
michael@0 | 31 | #include "jit/AsmJSLink.h" |
michael@0 | 32 | #include "vm/Debugger.h" |
michael@0 | 33 | |
michael@0 | 34 | #include "jsatominlines.h" |
michael@0 | 35 | #include "jsobjinlines.h" |
michael@0 | 36 | #include "jsscriptinlines.h" |
michael@0 | 37 | |
michael@0 | 38 | #include "frontend/ParseMaps-inl.h" |
michael@0 | 39 | #include "frontend/ParseNode-inl.h" |
michael@0 | 40 | #include "vm/ScopeObject-inl.h" |
michael@0 | 41 | |
michael@0 | 42 | using namespace js; |
michael@0 | 43 | using namespace js::gc; |
michael@0 | 44 | using namespace js::frontend; |
michael@0 | 45 | |
michael@0 | 46 | using mozilla::DebugOnly; |
michael@0 | 47 | using mozilla::NumberIsInt32; |
michael@0 | 48 | using mozilla::PodCopy; |
michael@0 | 49 | |
michael@0 | 50 | static bool |
michael@0 | 51 | SetSrcNoteOffset(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned index, unsigned which, ptrdiff_t offset); |
michael@0 | 52 | |
michael@0 | 53 | struct frontend::StmtInfoBCE : public StmtInfoBase |
michael@0 | 54 | { |
michael@0 | 55 | StmtInfoBCE *down; /* info for enclosing statement */ |
michael@0 | 56 | StmtInfoBCE *downScope; /* next enclosing lexical scope */ |
michael@0 | 57 | |
michael@0 | 58 | ptrdiff_t update; /* loop update offset (top if none) */ |
michael@0 | 59 | ptrdiff_t breaks; /* offset of last break in loop */ |
michael@0 | 60 | ptrdiff_t continues; /* offset of last continue in loop */ |
michael@0 | 61 | uint32_t blockScopeIndex; /* index of scope in BlockScopeArray */ |
michael@0 | 62 | |
michael@0 | 63 | StmtInfoBCE(ExclusiveContext *cx) : StmtInfoBase(cx) {} |
michael@0 | 64 | |
michael@0 | 65 | /* |
michael@0 | 66 | * To reuse space, alias two of the ptrdiff_t fields for use during |
michael@0 | 67 | * try/catch/finally code generation and backpatching. |
michael@0 | 68 | * |
michael@0 | 69 | * Only a loop, switch, or label statement info record can have breaks and |
michael@0 | 70 | * continues, and only a for loop has an update backpatch chain, so it's |
michael@0 | 71 | * safe to overlay these for the "trying" StmtTypes. |
michael@0 | 72 | */ |
michael@0 | 73 | |
michael@0 | 74 | ptrdiff_t &gosubs() { |
michael@0 | 75 | JS_ASSERT(type == STMT_FINALLY); |
michael@0 | 76 | return breaks; |
michael@0 | 77 | } |
michael@0 | 78 | |
michael@0 | 79 | ptrdiff_t &guardJump() { |
michael@0 | 80 | JS_ASSERT(type == STMT_TRY || type == STMT_FINALLY); |
michael@0 | 81 | return continues; |
michael@0 | 82 | } |
michael@0 | 83 | }; |
michael@0 | 84 | |
michael@0 | 85 | |
michael@0 | 86 | namespace { |
michael@0 | 87 | |
michael@0 | 88 | struct LoopStmtInfo : public StmtInfoBCE |
michael@0 | 89 | { |
michael@0 | 90 | int32_t stackDepth; // Stack depth when this loop was pushed. |
michael@0 | 91 | uint32_t loopDepth; // Loop depth. |
michael@0 | 92 | |
michael@0 | 93 | // Can we OSR into Ion from here? True unless there is non-loop state on the stack. |
michael@0 | 94 | bool canIonOsr; |
michael@0 | 95 | |
michael@0 | 96 | LoopStmtInfo(ExclusiveContext *cx) : StmtInfoBCE(cx) {} |
michael@0 | 97 | |
michael@0 | 98 | static LoopStmtInfo* fromStmtInfo(StmtInfoBCE *stmt) { |
michael@0 | 99 | JS_ASSERT(stmt->isLoop()); |
michael@0 | 100 | return static_cast<LoopStmtInfo*>(stmt); |
michael@0 | 101 | } |
michael@0 | 102 | }; |
michael@0 | 103 | |
michael@0 | 104 | } // anonymous namespace |
michael@0 | 105 | |
michael@0 | 106 | BytecodeEmitter::BytecodeEmitter(BytecodeEmitter *parent, |
michael@0 | 107 | Parser<FullParseHandler> *parser, SharedContext *sc, |
michael@0 | 108 | HandleScript script, bool insideEval, HandleScript evalCaller, |
michael@0 | 109 | bool hasGlobalScope, uint32_t lineNum, EmitterMode emitterMode) |
michael@0 | 110 | : sc(sc), |
michael@0 | 111 | parent(parent), |
michael@0 | 112 | script(sc->context, script), |
michael@0 | 113 | prolog(sc->context, lineNum), |
michael@0 | 114 | main(sc->context, lineNum), |
michael@0 | 115 | current(&main), |
michael@0 | 116 | parser(parser), |
michael@0 | 117 | evalCaller(evalCaller), |
michael@0 | 118 | topStmt(nullptr), |
michael@0 | 119 | topScopeStmt(nullptr), |
michael@0 | 120 | staticScope(sc->context), |
michael@0 | 121 | atomIndices(sc->context), |
michael@0 | 122 | firstLine(lineNum), |
michael@0 | 123 | stackDepth(0), maxStackDepth(0), |
michael@0 | 124 | arrayCompDepth(0), |
michael@0 | 125 | emitLevel(0), |
michael@0 | 126 | constList(sc->context), |
michael@0 | 127 | tryNoteList(sc->context), |
michael@0 | 128 | blockScopeList(sc->context), |
michael@0 | 129 | typesetCount(0), |
michael@0 | 130 | hasSingletons(false), |
michael@0 | 131 | emittingForInit(false), |
michael@0 | 132 | emittingRunOnceLambda(false), |
michael@0 | 133 | lazyRunOnceLambda(false), |
michael@0 | 134 | insideEval(insideEval), |
michael@0 | 135 | hasGlobalScope(hasGlobalScope), |
michael@0 | 136 | emitterMode(emitterMode) |
michael@0 | 137 | { |
michael@0 | 138 | JS_ASSERT_IF(evalCaller, insideEval); |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | bool |
michael@0 | 142 | BytecodeEmitter::init() |
michael@0 | 143 | { |
michael@0 | 144 | return atomIndices.ensureMap(sc->context); |
michael@0 | 145 | } |
michael@0 | 146 | |
michael@0 | 147 | static ptrdiff_t |
michael@0 | 148 | EmitCheck(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t delta) |
michael@0 | 149 | { |
michael@0 | 150 | ptrdiff_t offset = bce->code().length(); |
michael@0 | 151 | |
michael@0 | 152 | // Start it off moderately large to avoid repeated resizings early on. |
michael@0 | 153 | if (bce->code().capacity() == 0 && !bce->code().reserve(1024)) |
michael@0 | 154 | return -1; |
michael@0 | 155 | |
michael@0 | 156 | jsbytecode dummy = 0; |
michael@0 | 157 | if (!bce->code().appendN(dummy, delta)) { |
michael@0 | 158 | js_ReportOutOfMemory(cx); |
michael@0 | 159 | return -1; |
michael@0 | 160 | } |
michael@0 | 161 | return offset; |
michael@0 | 162 | } |
michael@0 | 163 | |
michael@0 | 164 | static void |
michael@0 | 165 | UpdateDepth(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t target) |
michael@0 | 166 | { |
michael@0 | 167 | jsbytecode *pc = bce->code(target); |
michael@0 | 168 | JSOp op = (JSOp) *pc; |
michael@0 | 169 | const JSCodeSpec *cs = &js_CodeSpec[op]; |
michael@0 | 170 | |
michael@0 | 171 | if (cs->format & JOF_TMPSLOT_MASK) { |
michael@0 | 172 | /* |
michael@0 | 173 | * An opcode may temporarily consume stack space during execution. |
michael@0 | 174 | * Account for this in maxStackDepth separately from uses/defs here. |
michael@0 | 175 | */ |
michael@0 | 176 | uint32_t depth = (uint32_t) bce->stackDepth + |
michael@0 | 177 | ((cs->format & JOF_TMPSLOT_MASK) >> JOF_TMPSLOT_SHIFT); |
michael@0 | 178 | if (depth > bce->maxStackDepth) |
michael@0 | 179 | bce->maxStackDepth = depth; |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | int nuses = StackUses(nullptr, pc); |
michael@0 | 183 | int ndefs = StackDefs(nullptr, pc); |
michael@0 | 184 | |
michael@0 | 185 | bce->stackDepth -= nuses; |
michael@0 | 186 | JS_ASSERT(bce->stackDepth >= 0); |
michael@0 | 187 | bce->stackDepth += ndefs; |
michael@0 | 188 | if ((uint32_t)bce->stackDepth > bce->maxStackDepth) |
michael@0 | 189 | bce->maxStackDepth = bce->stackDepth; |
michael@0 | 190 | } |
michael@0 | 191 | |
michael@0 | 192 | ptrdiff_t |
michael@0 | 193 | frontend::Emit1(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op) |
michael@0 | 194 | { |
michael@0 | 195 | ptrdiff_t offset = EmitCheck(cx, bce, 1); |
michael@0 | 196 | if (offset < 0) |
michael@0 | 197 | return -1; |
michael@0 | 198 | |
michael@0 | 199 | jsbytecode *code = bce->code(offset); |
michael@0 | 200 | code[0] = jsbytecode(op); |
michael@0 | 201 | UpdateDepth(cx, bce, offset); |
michael@0 | 202 | return offset; |
michael@0 | 203 | } |
michael@0 | 204 | |
michael@0 | 205 | ptrdiff_t |
michael@0 | 206 | frontend::Emit2(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, jsbytecode op1) |
michael@0 | 207 | { |
michael@0 | 208 | ptrdiff_t offset = EmitCheck(cx, bce, 2); |
michael@0 | 209 | if (offset < 0) |
michael@0 | 210 | return -1; |
michael@0 | 211 | |
michael@0 | 212 | jsbytecode *code = bce->code(offset); |
michael@0 | 213 | code[0] = jsbytecode(op); |
michael@0 | 214 | code[1] = op1; |
michael@0 | 215 | UpdateDepth(cx, bce, offset); |
michael@0 | 216 | return offset; |
michael@0 | 217 | } |
michael@0 | 218 | |
michael@0 | 219 | ptrdiff_t |
michael@0 | 220 | frontend::Emit3(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, jsbytecode op1, |
michael@0 | 221 | jsbytecode op2) |
michael@0 | 222 | { |
michael@0 | 223 | /* These should filter through EmitVarOp. */ |
michael@0 | 224 | JS_ASSERT(!IsArgOp(op)); |
michael@0 | 225 | JS_ASSERT(!IsLocalOp(op)); |
michael@0 | 226 | |
michael@0 | 227 | ptrdiff_t offset = EmitCheck(cx, bce, 3); |
michael@0 | 228 | if (offset < 0) |
michael@0 | 229 | return -1; |
michael@0 | 230 | |
michael@0 | 231 | jsbytecode *code = bce->code(offset); |
michael@0 | 232 | code[0] = jsbytecode(op); |
michael@0 | 233 | code[1] = op1; |
michael@0 | 234 | code[2] = op2; |
michael@0 | 235 | UpdateDepth(cx, bce, offset); |
michael@0 | 236 | return offset; |
michael@0 | 237 | } |
michael@0 | 238 | |
michael@0 | 239 | ptrdiff_t |
michael@0 | 240 | frontend::EmitN(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, size_t extra) |
michael@0 | 241 | { |
michael@0 | 242 | ptrdiff_t length = 1 + (ptrdiff_t)extra; |
michael@0 | 243 | ptrdiff_t offset = EmitCheck(cx, bce, length); |
michael@0 | 244 | if (offset < 0) |
michael@0 | 245 | return -1; |
michael@0 | 246 | |
michael@0 | 247 | jsbytecode *code = bce->code(offset); |
michael@0 | 248 | code[0] = jsbytecode(op); |
michael@0 | 249 | /* The remaining |extra| bytes are set by the caller */ |
michael@0 | 250 | |
michael@0 | 251 | /* |
michael@0 | 252 | * Don't UpdateDepth if op's use-count comes from the immediate |
michael@0 | 253 | * operand yet to be stored in the extra bytes after op. |
michael@0 | 254 | */ |
michael@0 | 255 | if (js_CodeSpec[op].nuses >= 0) |
michael@0 | 256 | UpdateDepth(cx, bce, offset); |
michael@0 | 257 | |
michael@0 | 258 | return offset; |
michael@0 | 259 | } |
michael@0 | 260 | |
michael@0 | 261 | static ptrdiff_t |
michael@0 | 262 | EmitJump(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, ptrdiff_t off) |
michael@0 | 263 | { |
michael@0 | 264 | ptrdiff_t offset = EmitCheck(cx, bce, 5); |
michael@0 | 265 | if (offset < 0) |
michael@0 | 266 | return -1; |
michael@0 | 267 | |
michael@0 | 268 | jsbytecode *code = bce->code(offset); |
michael@0 | 269 | code[0] = jsbytecode(op); |
michael@0 | 270 | SET_JUMP_OFFSET(code, off); |
michael@0 | 271 | UpdateDepth(cx, bce, offset); |
michael@0 | 272 | return offset; |
michael@0 | 273 | } |
michael@0 | 274 | |
michael@0 | 275 | static ptrdiff_t |
michael@0 | 276 | EmitCall(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op, uint16_t argc) |
michael@0 | 277 | { |
michael@0 | 278 | return Emit3(cx, bce, op, ARGC_HI(argc), ARGC_LO(argc)); |
michael@0 | 279 | } |
michael@0 | 280 | |
michael@0 | 281 | // Dup the var in operand stack slot "slot". The first item on the operand |
michael@0 | 282 | // stack is one slot past the last fixed slot. The last (most recent) item is |
michael@0 | 283 | // slot bce->stackDepth - 1. |
michael@0 | 284 | // |
michael@0 | 285 | // The instruction that is written (JSOP_DUPAT) switches the depth around so |
michael@0 | 286 | // that it is addressed from the sp instead of from the fp. This is useful when |
michael@0 | 287 | // you don't know the size of the fixed stack segment (nfixed), as is the case |
michael@0 | 288 | // when compiling scripts (because each statement is parsed and compiled |
michael@0 | 289 | // separately, but they all together form one script with one fixed stack |
michael@0 | 290 | // frame). |
michael@0 | 291 | static bool |
michael@0 | 292 | EmitDupAt(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned slot) |
michael@0 | 293 | { |
michael@0 | 294 | JS_ASSERT(slot < unsigned(bce->stackDepth)); |
michael@0 | 295 | // The slot's position on the operand stack, measured from the top. |
michael@0 | 296 | unsigned slotFromTop = bce->stackDepth - 1 - slot; |
michael@0 | 297 | if (slotFromTop >= JS_BIT(24)) { |
michael@0 | 298 | bce->reportError(nullptr, JSMSG_TOO_MANY_LOCALS); |
michael@0 | 299 | return false; |
michael@0 | 300 | } |
michael@0 | 301 | ptrdiff_t off = EmitN(cx, bce, JSOP_DUPAT, 3); |
michael@0 | 302 | if (off < 0) |
michael@0 | 303 | return false; |
michael@0 | 304 | jsbytecode *pc = bce->code(off); |
michael@0 | 305 | SET_UINT24(pc, slotFromTop); |
michael@0 | 306 | return true; |
michael@0 | 307 | } |
michael@0 | 308 | |
michael@0 | 309 | /* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */ |
michael@0 | 310 | const char js_with_statement_str[] = "with statement"; |
michael@0 | 311 | const char js_finally_block_str[] = "finally block"; |
michael@0 | 312 | const char js_script_str[] = "script"; |
michael@0 | 313 | |
michael@0 | 314 | static const char * const statementName[] = { |
michael@0 | 315 | "label statement", /* LABEL */ |
michael@0 | 316 | "if statement", /* IF */ |
michael@0 | 317 | "else statement", /* ELSE */ |
michael@0 | 318 | "destructuring body", /* BODY */ |
michael@0 | 319 | "switch statement", /* SWITCH */ |
michael@0 | 320 | "block", /* BLOCK */ |
michael@0 | 321 | js_with_statement_str, /* WITH */ |
michael@0 | 322 | "catch block", /* CATCH */ |
michael@0 | 323 | "try block", /* TRY */ |
michael@0 | 324 | js_finally_block_str, /* FINALLY */ |
michael@0 | 325 | js_finally_block_str, /* SUBROUTINE */ |
michael@0 | 326 | "do loop", /* DO_LOOP */ |
michael@0 | 327 | "for loop", /* FOR_LOOP */ |
michael@0 | 328 | "for/in loop", /* FOR_IN_LOOP */ |
michael@0 | 329 | "for/of loop", /* FOR_OF_LOOP */ |
michael@0 | 330 | "while loop", /* WHILE_LOOP */ |
michael@0 | 331 | }; |
michael@0 | 332 | |
michael@0 | 333 | JS_STATIC_ASSERT(JS_ARRAY_LENGTH(statementName) == STMT_LIMIT); |
michael@0 | 334 | |
michael@0 | 335 | static const char * |
michael@0 | 336 | StatementName(StmtInfoBCE *topStmt) |
michael@0 | 337 | { |
michael@0 | 338 | if (!topStmt) |
michael@0 | 339 | return js_script_str; |
michael@0 | 340 | return statementName[topStmt->type]; |
michael@0 | 341 | } |
michael@0 | 342 | |
michael@0 | 343 | static void |
michael@0 | 344 | ReportStatementTooLarge(TokenStream &ts, StmtInfoBCE *topStmt) |
michael@0 | 345 | { |
michael@0 | 346 | ts.reportError(JSMSG_NEED_DIET, StatementName(topStmt)); |
michael@0 | 347 | } |
michael@0 | 348 | |
michael@0 | 349 | /* |
michael@0 | 350 | * Emit a backpatch op with offset pointing to the previous jump of this type, |
michael@0 | 351 | * so that we can walk back up the chain fixing up the op and jump offset. |
michael@0 | 352 | */ |
michael@0 | 353 | static ptrdiff_t |
michael@0 | 354 | EmitBackPatchOp(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t *lastp) |
michael@0 | 355 | { |
michael@0 | 356 | ptrdiff_t offset, delta; |
michael@0 | 357 | |
michael@0 | 358 | offset = bce->offset(); |
michael@0 | 359 | delta = offset - *lastp; |
michael@0 | 360 | *lastp = offset; |
michael@0 | 361 | JS_ASSERT(delta > 0); |
michael@0 | 362 | return EmitJump(cx, bce, JSOP_BACKPATCH, delta); |
michael@0 | 363 | } |
michael@0 | 364 | |
michael@0 | 365 | static inline unsigned |
michael@0 | 366 | LengthOfSetLine(unsigned line) |
michael@0 | 367 | { |
michael@0 | 368 | return 1 /* SN_SETLINE */ + (line > SN_4BYTE_OFFSET_MASK ? 4 : 1); |
michael@0 | 369 | } |
michael@0 | 370 | |
michael@0 | 371 | /* Updates line number notes, not column notes. */ |
michael@0 | 372 | static inline bool |
michael@0 | 373 | UpdateLineNumberNotes(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t offset) |
michael@0 | 374 | { |
michael@0 | 375 | TokenStream *ts = &bce->parser->tokenStream; |
michael@0 | 376 | if (!ts->srcCoords.isOnThisLine(offset, bce->currentLine())) { |
michael@0 | 377 | unsigned line = ts->srcCoords.lineNum(offset); |
michael@0 | 378 | unsigned delta = line - bce->currentLine(); |
michael@0 | 379 | |
michael@0 | 380 | /* |
michael@0 | 381 | * Encode any change in the current source line number by using |
michael@0 | 382 | * either several SRC_NEWLINE notes or just one SRC_SETLINE note, |
michael@0 | 383 | * whichever consumes less space. |
michael@0 | 384 | * |
michael@0 | 385 | * NB: We handle backward line number deltas (possible with for |
michael@0 | 386 | * loops where the update part is emitted after the body, but its |
michael@0 | 387 | * line number is <= any line number in the body) here by letting |
michael@0 | 388 | * unsigned delta_ wrap to a very large number, which triggers a |
michael@0 | 389 | * SRC_SETLINE. |
michael@0 | 390 | */ |
michael@0 | 391 | bce->current->currentLine = line; |
michael@0 | 392 | bce->current->lastColumn = 0; |
michael@0 | 393 | if (delta >= LengthOfSetLine(line)) { |
michael@0 | 394 | if (NewSrcNote2(cx, bce, SRC_SETLINE, (ptrdiff_t)line) < 0) |
michael@0 | 395 | return false; |
michael@0 | 396 | } else { |
michael@0 | 397 | do { |
michael@0 | 398 | if (NewSrcNote(cx, bce, SRC_NEWLINE) < 0) |
michael@0 | 399 | return false; |
michael@0 | 400 | } while (--delta != 0); |
michael@0 | 401 | } |
michael@0 | 402 | } |
michael@0 | 403 | return true; |
michael@0 | 404 | } |
michael@0 | 405 | |
michael@0 | 406 | /* A function, so that we avoid macro-bloating all the other callsites. */ |
michael@0 | 407 | static bool |
michael@0 | 408 | UpdateSourceCoordNotes(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t offset) |
michael@0 | 409 | { |
michael@0 | 410 | if (!UpdateLineNumberNotes(cx, bce, offset)) |
michael@0 | 411 | return false; |
michael@0 | 412 | |
michael@0 | 413 | uint32_t columnIndex = bce->parser->tokenStream.srcCoords.columnIndex(offset); |
michael@0 | 414 | ptrdiff_t colspan = ptrdiff_t(columnIndex) - ptrdiff_t(bce->current->lastColumn); |
michael@0 | 415 | if (colspan != 0) { |
michael@0 | 416 | if (colspan < 0) { |
michael@0 | 417 | colspan += SN_COLSPAN_DOMAIN; |
michael@0 | 418 | } else if (colspan >= SN_COLSPAN_DOMAIN / 2) { |
michael@0 | 419 | // If the column span is so large that we can't store it, then just |
michael@0 | 420 | // discard this information because column information would most |
michael@0 | 421 | // likely be useless anyway once the column numbers are ~4000000. |
michael@0 | 422 | // This has been known to happen with scripts that have been |
michael@0 | 423 | // minimized and put into all one line. |
michael@0 | 424 | return true; |
michael@0 | 425 | } |
michael@0 | 426 | if (NewSrcNote2(cx, bce, SRC_COLSPAN, colspan) < 0) |
michael@0 | 427 | return false; |
michael@0 | 428 | bce->current->lastColumn = columnIndex; |
michael@0 | 429 | } |
michael@0 | 430 | return true; |
michael@0 | 431 | } |
michael@0 | 432 | |
michael@0 | 433 | static ptrdiff_t |
michael@0 | 434 | EmitLoopHead(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *nextpn) |
michael@0 | 435 | { |
michael@0 | 436 | if (nextpn) { |
michael@0 | 437 | /* |
michael@0 | 438 | * Try to give the JSOP_LOOPHEAD the same line number as the next |
michael@0 | 439 | * instruction. nextpn is often a block, in which case the next |
michael@0 | 440 | * instruction typically comes from the first statement inside. |
michael@0 | 441 | */ |
michael@0 | 442 | JS_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); |
michael@0 | 443 | if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) |
michael@0 | 444 | nextpn = nextpn->pn_head; |
michael@0 | 445 | if (!UpdateSourceCoordNotes(cx, bce, nextpn->pn_pos.begin)) |
michael@0 | 446 | return -1; |
michael@0 | 447 | } |
michael@0 | 448 | |
michael@0 | 449 | return Emit1(cx, bce, JSOP_LOOPHEAD); |
michael@0 | 450 | } |
michael@0 | 451 | |
michael@0 | 452 | static bool |
michael@0 | 453 | EmitLoopEntry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *nextpn) |
michael@0 | 454 | { |
michael@0 | 455 | if (nextpn) { |
michael@0 | 456 | /* Update the line number, as for LOOPHEAD. */ |
michael@0 | 457 | JS_ASSERT_IF(nextpn->isKind(PNK_STATEMENTLIST), nextpn->isArity(PN_LIST)); |
michael@0 | 458 | if (nextpn->isKind(PNK_STATEMENTLIST) && nextpn->pn_head) |
michael@0 | 459 | nextpn = nextpn->pn_head; |
michael@0 | 460 | if (!UpdateSourceCoordNotes(cx, bce, nextpn->pn_pos.begin)) |
michael@0 | 461 | return false; |
michael@0 | 462 | } |
michael@0 | 463 | |
michael@0 | 464 | LoopStmtInfo *loop = LoopStmtInfo::fromStmtInfo(bce->topStmt); |
michael@0 | 465 | JS_ASSERT(loop->loopDepth > 0); |
michael@0 | 466 | |
michael@0 | 467 | uint8_t loopDepthAndFlags = PackLoopEntryDepthHintAndFlags(loop->loopDepth, loop->canIonOsr); |
michael@0 | 468 | return Emit2(cx, bce, JSOP_LOOPENTRY, loopDepthAndFlags) >= 0; |
michael@0 | 469 | } |
michael@0 | 470 | |
michael@0 | 471 | /* |
michael@0 | 472 | * If op is JOF_TYPESET (see the type barriers comment in jsinfer.h), reserve |
michael@0 | 473 | * a type set to store its result. |
michael@0 | 474 | */ |
michael@0 | 475 | static inline void |
michael@0 | 476 | CheckTypeSet(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op) |
michael@0 | 477 | { |
michael@0 | 478 | if (js_CodeSpec[op].format & JOF_TYPESET) { |
michael@0 | 479 | if (bce->typesetCount < UINT16_MAX) |
michael@0 | 480 | bce->typesetCount++; |
michael@0 | 481 | } |
michael@0 | 482 | } |
michael@0 | 483 | |
michael@0 | 484 | /* |
michael@0 | 485 | * Macro to emit a bytecode followed by a uint16_t immediate operand stored in |
michael@0 | 486 | * big-endian order. |
michael@0 | 487 | * |
michael@0 | 488 | * NB: We use cx and bce from our caller's lexical environment, and return |
michael@0 | 489 | * false on error. |
michael@0 | 490 | */ |
michael@0 | 491 | #define EMIT_UINT16_IMM_OP(op, i) \ |
michael@0 | 492 | JS_BEGIN_MACRO \ |
michael@0 | 493 | if (Emit3(cx, bce, op, UINT16_HI(i), UINT16_LO(i)) < 0) \ |
michael@0 | 494 | return false; \ |
michael@0 | 495 | CheckTypeSet(cx, bce, op); \ |
michael@0 | 496 | JS_END_MACRO |
michael@0 | 497 | |
michael@0 | 498 | static bool |
michael@0 | 499 | FlushPops(ExclusiveContext *cx, BytecodeEmitter *bce, int *npops) |
michael@0 | 500 | { |
michael@0 | 501 | JS_ASSERT(*npops != 0); |
michael@0 | 502 | EMIT_UINT16_IMM_OP(JSOP_POPN, *npops); |
michael@0 | 503 | *npops = 0; |
michael@0 | 504 | return true; |
michael@0 | 505 | } |
michael@0 | 506 | |
michael@0 | 507 | static bool |
michael@0 | 508 | PopIterator(ExclusiveContext *cx, BytecodeEmitter *bce) |
michael@0 | 509 | { |
michael@0 | 510 | if (Emit1(cx, bce, JSOP_ENDITER) < 0) |
michael@0 | 511 | return false; |
michael@0 | 512 | return true; |
michael@0 | 513 | } |
michael@0 | 514 | |
michael@0 | 515 | namespace { |
michael@0 | 516 | |
michael@0 | 517 | class NonLocalExitScope { |
michael@0 | 518 | ExclusiveContext *cx; |
michael@0 | 519 | BytecodeEmitter *bce; |
michael@0 | 520 | const uint32_t savedScopeIndex; |
michael@0 | 521 | const int savedDepth; |
michael@0 | 522 | uint32_t openScopeIndex; |
michael@0 | 523 | |
michael@0 | 524 | NonLocalExitScope(const NonLocalExitScope &) MOZ_DELETE; |
michael@0 | 525 | |
michael@0 | 526 | public: |
michael@0 | 527 | explicit NonLocalExitScope(ExclusiveContext *cx_, BytecodeEmitter *bce_) |
michael@0 | 528 | : cx(cx_), |
michael@0 | 529 | bce(bce_), |
michael@0 | 530 | savedScopeIndex(bce->blockScopeList.length()), |
michael@0 | 531 | savedDepth(bce->stackDepth), |
michael@0 | 532 | openScopeIndex(UINT32_MAX) { |
michael@0 | 533 | if (bce->staticScope) { |
michael@0 | 534 | StmtInfoBCE *stmt = bce->topStmt; |
michael@0 | 535 | while (1) { |
michael@0 | 536 | JS_ASSERT(stmt); |
michael@0 | 537 | if (stmt->isNestedScope) { |
michael@0 | 538 | openScopeIndex = stmt->blockScopeIndex; |
michael@0 | 539 | break; |
michael@0 | 540 | } |
michael@0 | 541 | stmt = stmt->down; |
michael@0 | 542 | } |
michael@0 | 543 | } |
michael@0 | 544 | } |
michael@0 | 545 | |
michael@0 | 546 | ~NonLocalExitScope() { |
michael@0 | 547 | for (uint32_t n = savedScopeIndex; n < bce->blockScopeList.length(); n++) |
michael@0 | 548 | bce->blockScopeList.recordEnd(n, bce->offset()); |
michael@0 | 549 | bce->stackDepth = savedDepth; |
michael@0 | 550 | } |
michael@0 | 551 | |
michael@0 | 552 | bool popScopeForNonLocalExit(uint32_t blockScopeIndex) { |
michael@0 | 553 | uint32_t scopeObjectIndex = bce->blockScopeList.findEnclosingScope(blockScopeIndex); |
michael@0 | 554 | uint32_t parent = openScopeIndex; |
michael@0 | 555 | |
michael@0 | 556 | if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent)) |
michael@0 | 557 | return false; |
michael@0 | 558 | openScopeIndex = bce->blockScopeList.length() - 1; |
michael@0 | 559 | return true; |
michael@0 | 560 | } |
michael@0 | 561 | |
michael@0 | 562 | bool prepareForNonLocalJump(StmtInfoBCE *toStmt); |
michael@0 | 563 | }; |
michael@0 | 564 | |
michael@0 | 565 | /* |
michael@0 | 566 | * Emit additional bytecode(s) for non-local jumps. |
michael@0 | 567 | */ |
michael@0 | 568 | bool |
michael@0 | 569 | NonLocalExitScope::prepareForNonLocalJump(StmtInfoBCE *toStmt) |
michael@0 | 570 | { |
michael@0 | 571 | int npops = 0; |
michael@0 | 572 | |
michael@0 | 573 | #define FLUSH_POPS() if (npops && !FlushPops(cx, bce, &npops)) return false |
michael@0 | 574 | |
michael@0 | 575 | for (StmtInfoBCE *stmt = bce->topStmt; stmt != toStmt; stmt = stmt->down) { |
michael@0 | 576 | switch (stmt->type) { |
michael@0 | 577 | case STMT_FINALLY: |
michael@0 | 578 | FLUSH_POPS(); |
michael@0 | 579 | if (EmitBackPatchOp(cx, bce, &stmt->gosubs()) < 0) |
michael@0 | 580 | return false; |
michael@0 | 581 | break; |
michael@0 | 582 | |
michael@0 | 583 | case STMT_WITH: |
michael@0 | 584 | if (Emit1(cx, bce, JSOP_LEAVEWITH) < 0) |
michael@0 | 585 | return false; |
michael@0 | 586 | JS_ASSERT(stmt->isNestedScope); |
michael@0 | 587 | if (!popScopeForNonLocalExit(stmt->blockScopeIndex)) |
michael@0 | 588 | return false; |
michael@0 | 589 | break; |
michael@0 | 590 | |
michael@0 | 591 | case STMT_FOR_OF_LOOP: |
michael@0 | 592 | npops += 2; |
michael@0 | 593 | break; |
michael@0 | 594 | |
michael@0 | 595 | case STMT_FOR_IN_LOOP: |
michael@0 | 596 | FLUSH_POPS(); |
michael@0 | 597 | if (!PopIterator(cx, bce)) |
michael@0 | 598 | return false; |
michael@0 | 599 | break; |
michael@0 | 600 | |
michael@0 | 601 | case STMT_SUBROUTINE: |
michael@0 | 602 | /* |
michael@0 | 603 | * There's a [exception or hole, retsub pc-index] pair on the |
michael@0 | 604 | * stack that we need to pop. |
michael@0 | 605 | */ |
michael@0 | 606 | npops += 2; |
michael@0 | 607 | break; |
michael@0 | 608 | |
michael@0 | 609 | default:; |
michael@0 | 610 | } |
michael@0 | 611 | |
michael@0 | 612 | if (stmt->isBlockScope) { |
michael@0 | 613 | JS_ASSERT(stmt->isNestedScope); |
michael@0 | 614 | StaticBlockObject &blockObj = stmt->staticBlock(); |
michael@0 | 615 | if (Emit1(cx, bce, JSOP_DEBUGLEAVEBLOCK) < 0) |
michael@0 | 616 | return false; |
michael@0 | 617 | if (!popScopeForNonLocalExit(stmt->blockScopeIndex)) |
michael@0 | 618 | return false; |
michael@0 | 619 | if (blockObj.needsClone()) { |
michael@0 | 620 | if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) |
michael@0 | 621 | return false; |
michael@0 | 622 | } |
michael@0 | 623 | } |
michael@0 | 624 | } |
michael@0 | 625 | |
michael@0 | 626 | FLUSH_POPS(); |
michael@0 | 627 | return true; |
michael@0 | 628 | |
michael@0 | 629 | #undef FLUSH_POPS |
michael@0 | 630 | } |
michael@0 | 631 | |
michael@0 | 632 | } // anonymous namespace |
michael@0 | 633 | |
michael@0 | 634 | static ptrdiff_t |
michael@0 | 635 | EmitGoto(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *toStmt, ptrdiff_t *lastp, |
michael@0 | 636 | SrcNoteType noteType = SRC_NULL) |
michael@0 | 637 | { |
michael@0 | 638 | NonLocalExitScope nle(cx, bce); |
michael@0 | 639 | |
michael@0 | 640 | if (!nle.prepareForNonLocalJump(toStmt)) |
michael@0 | 641 | return -1; |
michael@0 | 642 | |
michael@0 | 643 | if (noteType != SRC_NULL) { |
michael@0 | 644 | if (NewSrcNote(cx, bce, noteType) < 0) |
michael@0 | 645 | return -1; |
michael@0 | 646 | } |
michael@0 | 647 | |
michael@0 | 648 | return EmitBackPatchOp(cx, bce, lastp); |
michael@0 | 649 | } |
michael@0 | 650 | |
michael@0 | 651 | static bool |
michael@0 | 652 | BackPatch(ExclusiveContext *cx, BytecodeEmitter *bce, ptrdiff_t last, jsbytecode *target, jsbytecode op) |
michael@0 | 653 | { |
michael@0 | 654 | jsbytecode *pc, *stop; |
michael@0 | 655 | ptrdiff_t delta, span; |
michael@0 | 656 | |
michael@0 | 657 | pc = bce->code(last); |
michael@0 | 658 | stop = bce->code(-1); |
michael@0 | 659 | while (pc != stop) { |
michael@0 | 660 | delta = GET_JUMP_OFFSET(pc); |
michael@0 | 661 | span = target - pc; |
michael@0 | 662 | SET_JUMP_OFFSET(pc, span); |
michael@0 | 663 | *pc = op; |
michael@0 | 664 | pc -= delta; |
michael@0 | 665 | } |
michael@0 | 666 | return true; |
michael@0 | 667 | } |
michael@0 | 668 | |
michael@0 | 669 | #define SET_STATEMENT_TOP(stmt, top) \ |
michael@0 | 670 | ((stmt)->update = (top), (stmt)->breaks = (stmt)->continues = (-1)) |
michael@0 | 671 | |
michael@0 | 672 | static void |
michael@0 | 673 | PushStatementInner(BytecodeEmitter *bce, StmtInfoBCE *stmt, StmtType type, ptrdiff_t top) |
michael@0 | 674 | { |
michael@0 | 675 | SET_STATEMENT_TOP(stmt, top); |
michael@0 | 676 | PushStatement(bce, stmt, type); |
michael@0 | 677 | } |
michael@0 | 678 | |
michael@0 | 679 | static void |
michael@0 | 680 | PushStatementBCE(BytecodeEmitter *bce, StmtInfoBCE *stmt, StmtType type, ptrdiff_t top) |
michael@0 | 681 | { |
michael@0 | 682 | PushStatementInner(bce, stmt, type, top); |
michael@0 | 683 | JS_ASSERT(!stmt->isLoop()); |
michael@0 | 684 | } |
michael@0 | 685 | |
michael@0 | 686 | static void |
michael@0 | 687 | PushLoopStatement(BytecodeEmitter *bce, LoopStmtInfo *stmt, StmtType type, ptrdiff_t top) |
michael@0 | 688 | { |
michael@0 | 689 | PushStatementInner(bce, stmt, type, top); |
michael@0 | 690 | JS_ASSERT(stmt->isLoop()); |
michael@0 | 691 | |
michael@0 | 692 | LoopStmtInfo *downLoop = nullptr; |
michael@0 | 693 | for (StmtInfoBCE *outer = stmt->down; outer; outer = outer->down) { |
michael@0 | 694 | if (outer->isLoop()) { |
michael@0 | 695 | downLoop = LoopStmtInfo::fromStmtInfo(outer); |
michael@0 | 696 | break; |
michael@0 | 697 | } |
michael@0 | 698 | } |
michael@0 | 699 | |
michael@0 | 700 | stmt->stackDepth = bce->stackDepth; |
michael@0 | 701 | stmt->loopDepth = downLoop ? downLoop->loopDepth + 1 : 1; |
michael@0 | 702 | |
michael@0 | 703 | int loopSlots; |
michael@0 | 704 | if (type == STMT_FOR_OF_LOOP) |
michael@0 | 705 | loopSlots = 2; |
michael@0 | 706 | else if (type == STMT_FOR_IN_LOOP) |
michael@0 | 707 | loopSlots = 1; |
michael@0 | 708 | else |
michael@0 | 709 | loopSlots = 0; |
michael@0 | 710 | |
michael@0 | 711 | if (downLoop) |
michael@0 | 712 | stmt->canIonOsr = (downLoop->canIonOsr && |
michael@0 | 713 | stmt->stackDepth == downLoop->stackDepth + loopSlots); |
michael@0 | 714 | else |
michael@0 | 715 | stmt->canIonOsr = stmt->stackDepth == loopSlots; |
michael@0 | 716 | } |
michael@0 | 717 | |
michael@0 | 718 | /* |
michael@0 | 719 | * Return the enclosing lexical scope, which is the innermost enclosing static |
michael@0 | 720 | * block object or compiler created function. |
michael@0 | 721 | */ |
michael@0 | 722 | static JSObject * |
michael@0 | 723 | EnclosingStaticScope(BytecodeEmitter *bce) |
michael@0 | 724 | { |
michael@0 | 725 | if (bce->staticScope) |
michael@0 | 726 | return bce->staticScope; |
michael@0 | 727 | |
michael@0 | 728 | if (!bce->sc->isFunctionBox()) { |
michael@0 | 729 | JS_ASSERT(!bce->parent); |
michael@0 | 730 | return nullptr; |
michael@0 | 731 | } |
michael@0 | 732 | |
michael@0 | 733 | return bce->sc->asFunctionBox()->function(); |
michael@0 | 734 | } |
michael@0 | 735 | |
michael@0 | 736 | #ifdef DEBUG |
michael@0 | 737 | static bool |
michael@0 | 738 | AllLocalsAliased(StaticBlockObject &obj) |
michael@0 | 739 | { |
michael@0 | 740 | for (unsigned i = 0; i < obj.numVariables(); i++) |
michael@0 | 741 | if (!obj.isAliased(i)) |
michael@0 | 742 | return false; |
michael@0 | 743 | return true; |
michael@0 | 744 | } |
michael@0 | 745 | #endif |
michael@0 | 746 | |
michael@0 | 747 | static bool |
michael@0 | 748 | ComputeAliasedSlots(ExclusiveContext *cx, BytecodeEmitter *bce, Handle<StaticBlockObject *> blockObj) |
michael@0 | 749 | { |
michael@0 | 750 | for (unsigned i = 0; i < blockObj->numVariables(); i++) { |
michael@0 | 751 | Definition *dn = blockObj->definitionParseNode(i); |
michael@0 | 752 | |
michael@0 | 753 | JS_ASSERT(dn->isDefn()); |
michael@0 | 754 | if (!dn->pn_cookie.set(bce->parser->tokenStream, dn->pn_cookie.level(), |
michael@0 | 755 | blockObj->blockIndexToLocalIndex(dn->frameSlot()))) |
michael@0 | 756 | { |
michael@0 | 757 | return false; |
michael@0 | 758 | } |
michael@0 | 759 | |
michael@0 | 760 | #ifdef DEBUG |
michael@0 | 761 | for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) { |
michael@0 | 762 | JS_ASSERT(pnu->pn_lexdef == dn); |
michael@0 | 763 | JS_ASSERT(!(pnu->pn_dflags & PND_BOUND)); |
michael@0 | 764 | JS_ASSERT(pnu->pn_cookie.isFree()); |
michael@0 | 765 | } |
michael@0 | 766 | #endif |
michael@0 | 767 | |
michael@0 | 768 | blockObj->setAliased(i, bce->isAliasedName(dn)); |
michael@0 | 769 | } |
michael@0 | 770 | |
michael@0 | 771 | JS_ASSERT_IF(bce->sc->allLocalsAliased(), AllLocalsAliased(*blockObj)); |
michael@0 | 772 | |
michael@0 | 773 | return true; |
michael@0 | 774 | } |
michael@0 | 775 | |
michael@0 | 776 | static bool |
michael@0 | 777 | EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmitter *bce); |
michael@0 | 778 | |
michael@0 | 779 | // In a function, block-scoped locals go after the vars, and form part of the |
michael@0 | 780 | // fixed part of a stack frame. Outside a function, there are no fixed vars, |
michael@0 | 781 | // but block-scoped locals still form part of the fixed part of a stack frame |
michael@0 | 782 | // and are thus addressable via GETLOCAL and friends. |
michael@0 | 783 | static void |
michael@0 | 784 | ComputeLocalOffset(ExclusiveContext *cx, BytecodeEmitter *bce, Handle<StaticBlockObject *> blockObj) |
michael@0 | 785 | { |
michael@0 | 786 | unsigned nfixedvars = bce->sc->isFunctionBox() ? bce->script->bindings.numVars() : 0; |
michael@0 | 787 | unsigned localOffset = nfixedvars; |
michael@0 | 788 | |
michael@0 | 789 | if (bce->staticScope) { |
michael@0 | 790 | Rooted<NestedScopeObject *> outer(cx, bce->staticScope); |
michael@0 | 791 | for (; outer; outer = outer->enclosingNestedScope()) { |
michael@0 | 792 | if (outer->is<StaticBlockObject>()) { |
michael@0 | 793 | StaticBlockObject &outerBlock = outer->as<StaticBlockObject>(); |
michael@0 | 794 | localOffset = outerBlock.localOffset() + outerBlock.numVariables(); |
michael@0 | 795 | break; |
michael@0 | 796 | } |
michael@0 | 797 | } |
michael@0 | 798 | } |
michael@0 | 799 | |
michael@0 | 800 | JS_ASSERT(localOffset + blockObj->numVariables() |
michael@0 | 801 | <= nfixedvars + bce->script->bindings.numBlockScoped()); |
michael@0 | 802 | |
michael@0 | 803 | blockObj->setLocalOffset(localOffset); |
michael@0 | 804 | } |
michael@0 | 805 | |
michael@0 | 806 | // ~ Nested Scopes ~ |
michael@0 | 807 | // |
michael@0 | 808 | // A nested scope is a region of a compilation unit (function, script, or eval |
michael@0 | 809 | // code) with an additional node on the scope chain. This node may either be a |
michael@0 | 810 | // "with" object or a "block" object. "With" objects represent "with" scopes. |
michael@0 | 811 | // Block objects represent lexical scopes, and contain named block-scoped |
michael@0 | 812 | // bindings, for example "let" bindings or the exception in a catch block. |
michael@0 | 813 | // Those variables may be local and thus accessible directly from the stack, or |
michael@0 | 814 | // "aliased" (accessed by name from nested functions, or dynamically via nested |
michael@0 | 815 | // "eval" or "with") and only accessible through the scope chain. |
michael@0 | 816 | // |
michael@0 | 817 | // All nested scopes are present on the "static scope chain". A nested scope |
michael@0 | 818 | // that is a "with" scope will be present on the scope chain at run-time as |
michael@0 | 819 | // well. A block scope may or may not have a corresponding link on the run-time |
michael@0 | 820 | // scope chain; if no variable declared in the block scope is "aliased", then no |
michael@0 | 821 | // scope chain node is allocated. |
michael@0 | 822 | // |
michael@0 | 823 | // To help debuggers, the bytecode emitter arranges to record the PC ranges |
michael@0 | 824 | // comprehended by a nested scope, and ultimately attach them to the JSScript. |
michael@0 | 825 | // An element in the "block scope array" specifies the PC range, and links to a |
michael@0 | 826 | // NestedScopeObject in the object list of the script. That scope object is |
michael@0 | 827 | // linked to the previous link in the static scope chain, if any. The static |
michael@0 | 828 | // scope chain at any pre-retire PC can be retrieved using |
michael@0 | 829 | // JSScript::getStaticScope(jsbytecode *pc). |
michael@0 | 830 | // |
michael@0 | 831 | // Block scopes store their locals in the fixed part of a stack frame, after the |
michael@0 | 832 | // "fixed var" bindings. A fixed var binding is a "var" or legacy "const" |
michael@0 | 833 | // binding that occurs in a function (as opposed to a script or in eval code). |
michael@0 | 834 | // Only functions have fixed var bindings. |
michael@0 | 835 | // |
michael@0 | 836 | // To assist the debugger, we emit a DEBUGLEAVEBLOCK opcode before leaving a |
michael@0 | 837 | // block scope, even if the block has no aliased locals. This allows |
michael@0 | 838 | // DebugScopes to invalidate any association between a debugger scope object, |
michael@0 | 839 | // which can proxy access to unaliased stack locals, and the actual live frame. |
michael@0 | 840 | // In normal, non-debug mode, this opcode does not cause any baseline code to be |
michael@0 | 841 | // emitted. |
michael@0 | 842 | // |
michael@0 | 843 | // Enter a nested scope with EnterNestedScope. It will emit |
michael@0 | 844 | // PUSHBLOCKSCOPE/ENTERWITH if needed, and arrange to record the PC bounds of |
michael@0 | 845 | // the scope. Leave a nested scope with LeaveNestedScope, which, for blocks, |
michael@0 | 846 | // will emit DEBUGLEAVEBLOCK and may emit POPBLOCKSCOPE. (For "with" scopes it |
michael@0 | 847 | // emits LEAVEWITH, of course.) Pass EnterNestedScope a fresh StmtInfoBCE |
michael@0 | 848 | // object, and pass that same object to the corresponding LeaveNestedScope. If |
michael@0 | 849 | // the statement is a block scope, pass STMT_BLOCK as stmtType; otherwise for |
michael@0 | 850 | // with scopes pass STMT_WITH. |
michael@0 | 851 | // |
michael@0 | 852 | static bool |
michael@0 | 853 | EnterNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt, ObjectBox *objbox, |
michael@0 | 854 | StmtType stmtType) |
michael@0 | 855 | { |
michael@0 | 856 | Rooted<NestedScopeObject *> scopeObj(cx, &objbox->object->as<NestedScopeObject>()); |
michael@0 | 857 | uint32_t scopeObjectIndex = bce->objectList.add(objbox); |
michael@0 | 858 | |
michael@0 | 859 | switch (stmtType) { |
michael@0 | 860 | case STMT_BLOCK: { |
michael@0 | 861 | Rooted<StaticBlockObject *> blockObj(cx, &scopeObj->as<StaticBlockObject>()); |
michael@0 | 862 | |
michael@0 | 863 | ComputeLocalOffset(cx, bce, blockObj); |
michael@0 | 864 | |
michael@0 | 865 | if (!ComputeAliasedSlots(cx, bce, blockObj)) |
michael@0 | 866 | return false; |
michael@0 | 867 | |
michael@0 | 868 | if (blockObj->needsClone()) { |
michael@0 | 869 | if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_PUSHBLOCKSCOPE, bce)) |
michael@0 | 870 | return false; |
michael@0 | 871 | } |
michael@0 | 872 | break; |
michael@0 | 873 | } |
michael@0 | 874 | case STMT_WITH: |
michael@0 | 875 | JS_ASSERT(scopeObj->is<StaticWithObject>()); |
michael@0 | 876 | if (!EmitInternedObjectOp(cx, scopeObjectIndex, JSOP_ENTERWITH, bce)) |
michael@0 | 877 | return false; |
michael@0 | 878 | break; |
michael@0 | 879 | default: |
michael@0 | 880 | MOZ_ASSUME_UNREACHABLE(); |
michael@0 | 881 | } |
michael@0 | 882 | |
michael@0 | 883 | uint32_t parent = BlockScopeNote::NoBlockScopeIndex; |
michael@0 | 884 | if (StmtInfoBCE *stmt = bce->topScopeStmt) { |
michael@0 | 885 | for (; stmt->staticScope != bce->staticScope; stmt = stmt->down) {} |
michael@0 | 886 | parent = stmt->blockScopeIndex; |
michael@0 | 887 | } |
michael@0 | 888 | |
michael@0 | 889 | stmt->blockScopeIndex = bce->blockScopeList.length(); |
michael@0 | 890 | if (!bce->blockScopeList.append(scopeObjectIndex, bce->offset(), parent)) |
michael@0 | 891 | return false; |
michael@0 | 892 | |
michael@0 | 893 | PushStatementBCE(bce, stmt, stmtType, bce->offset()); |
michael@0 | 894 | scopeObj->initEnclosingNestedScope(EnclosingStaticScope(bce)); |
michael@0 | 895 | FinishPushNestedScope(bce, stmt, *scopeObj); |
michael@0 | 896 | JS_ASSERT(stmt->isNestedScope); |
michael@0 | 897 | stmt->isBlockScope = (stmtType == STMT_BLOCK); |
michael@0 | 898 | |
michael@0 | 899 | return true; |
michael@0 | 900 | } |
michael@0 | 901 | |
michael@0 | 902 | // Patches |breaks| and |continues| unless the top statement info record |
michael@0 | 903 | // represents a try-catch-finally suite. May fail if a jump offset overflows. |
michael@0 | 904 | static bool |
michael@0 | 905 | PopStatementBCE(ExclusiveContext *cx, BytecodeEmitter *bce) |
michael@0 | 906 | { |
michael@0 | 907 | StmtInfoBCE *stmt = bce->topStmt; |
michael@0 | 908 | if (!stmt->isTrying() && |
michael@0 | 909 | (!BackPatch(cx, bce, stmt->breaks, bce->code().end(), JSOP_GOTO) || |
michael@0 | 910 | !BackPatch(cx, bce, stmt->continues, bce->code(stmt->update), JSOP_GOTO))) |
michael@0 | 911 | { |
michael@0 | 912 | return false; |
michael@0 | 913 | } |
michael@0 | 914 | |
michael@0 | 915 | FinishPopStatement(bce); |
michael@0 | 916 | return true; |
michael@0 | 917 | } |
michael@0 | 918 | |
michael@0 | 919 | static bool |
michael@0 | 920 | LeaveNestedScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmt) |
michael@0 | 921 | { |
michael@0 | 922 | JS_ASSERT(stmt == bce->topStmt); |
michael@0 | 923 | JS_ASSERT(stmt->isNestedScope); |
michael@0 | 924 | JS_ASSERT(stmt->isBlockScope == !(stmt->type == STMT_WITH)); |
michael@0 | 925 | uint32_t blockScopeIndex = stmt->blockScopeIndex; |
michael@0 | 926 | |
michael@0 | 927 | #ifdef DEBUG |
michael@0 | 928 | JS_ASSERT(bce->blockScopeList.list[blockScopeIndex].length == 0); |
michael@0 | 929 | uint32_t blockObjIndex = bce->blockScopeList.list[blockScopeIndex].index; |
michael@0 | 930 | ObjectBox *blockObjBox = bce->objectList.find(blockObjIndex); |
michael@0 | 931 | NestedScopeObject *staticScope = &blockObjBox->object->as<NestedScopeObject>(); |
michael@0 | 932 | JS_ASSERT(stmt->staticScope == staticScope); |
michael@0 | 933 | JS_ASSERT(staticScope == bce->staticScope); |
michael@0 | 934 | JS_ASSERT_IF(!stmt->isBlockScope, staticScope->is<StaticWithObject>()); |
michael@0 | 935 | #endif |
michael@0 | 936 | |
michael@0 | 937 | if (!PopStatementBCE(cx, bce)) |
michael@0 | 938 | return false; |
michael@0 | 939 | |
michael@0 | 940 | if (Emit1(cx, bce, stmt->isBlockScope ? JSOP_DEBUGLEAVEBLOCK : JSOP_LEAVEWITH) < 0) |
michael@0 | 941 | return false; |
michael@0 | 942 | |
michael@0 | 943 | bce->blockScopeList.recordEnd(blockScopeIndex, bce->offset()); |
michael@0 | 944 | |
michael@0 | 945 | if (stmt->isBlockScope && stmt->staticScope->as<StaticBlockObject>().needsClone()) { |
michael@0 | 946 | if (Emit1(cx, bce, JSOP_POPBLOCKSCOPE) < 0) |
michael@0 | 947 | return false; |
michael@0 | 948 | } |
michael@0 | 949 | |
michael@0 | 950 | return true; |
michael@0 | 951 | } |
michael@0 | 952 | |
michael@0 | 953 | static bool |
michael@0 | 954 | EmitIndex32(ExclusiveContext *cx, JSOp op, uint32_t index, BytecodeEmitter *bce) |
michael@0 | 955 | { |
michael@0 | 956 | const size_t len = 1 + UINT32_INDEX_LEN; |
michael@0 | 957 | JS_ASSERT(len == size_t(js_CodeSpec[op].length)); |
michael@0 | 958 | ptrdiff_t offset = EmitCheck(cx, bce, len); |
michael@0 | 959 | if (offset < 0) |
michael@0 | 960 | return false; |
michael@0 | 961 | |
michael@0 | 962 | jsbytecode *code = bce->code(offset); |
michael@0 | 963 | code[0] = jsbytecode(op); |
michael@0 | 964 | SET_UINT32_INDEX(code, index); |
michael@0 | 965 | UpdateDepth(cx, bce, offset); |
michael@0 | 966 | CheckTypeSet(cx, bce, op); |
michael@0 | 967 | return true; |
michael@0 | 968 | } |
michael@0 | 969 | |
michael@0 | 970 | static bool |
michael@0 | 971 | EmitIndexOp(ExclusiveContext *cx, JSOp op, uint32_t index, BytecodeEmitter *bce) |
michael@0 | 972 | { |
michael@0 | 973 | const size_t len = js_CodeSpec[op].length; |
michael@0 | 974 | JS_ASSERT(len >= 1 + UINT32_INDEX_LEN); |
michael@0 | 975 | ptrdiff_t offset = EmitCheck(cx, bce, len); |
michael@0 | 976 | if (offset < 0) |
michael@0 | 977 | return false; |
michael@0 | 978 | |
michael@0 | 979 | jsbytecode *code = bce->code(offset); |
michael@0 | 980 | code[0] = jsbytecode(op); |
michael@0 | 981 | SET_UINT32_INDEX(code, index); |
michael@0 | 982 | UpdateDepth(cx, bce, offset); |
michael@0 | 983 | CheckTypeSet(cx, bce, op); |
michael@0 | 984 | return true; |
michael@0 | 985 | } |
michael@0 | 986 | |
michael@0 | 987 | static bool |
michael@0 | 988 | EmitAtomOp(ExclusiveContext *cx, JSAtom *atom, JSOp op, BytecodeEmitter *bce) |
michael@0 | 989 | { |
michael@0 | 990 | JS_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); |
michael@0 | 991 | |
michael@0 | 992 | if (op == JSOP_GETPROP && atom == cx->names().length) { |
michael@0 | 993 | /* Specialize length accesses for the interpreter. */ |
michael@0 | 994 | op = JSOP_LENGTH; |
michael@0 | 995 | } |
michael@0 | 996 | |
michael@0 | 997 | jsatomid index; |
michael@0 | 998 | if (!bce->makeAtomIndex(atom, &index)) |
michael@0 | 999 | return false; |
michael@0 | 1000 | |
michael@0 | 1001 | return EmitIndexOp(cx, op, index, bce); |
michael@0 | 1002 | } |
michael@0 | 1003 | |
michael@0 | 1004 | static bool |
michael@0 | 1005 | EmitAtomOp(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) |
michael@0 | 1006 | { |
michael@0 | 1007 | JS_ASSERT(pn->pn_atom != nullptr); |
michael@0 | 1008 | return EmitAtomOp(cx, pn->pn_atom, op, bce); |
michael@0 | 1009 | } |
michael@0 | 1010 | |
michael@0 | 1011 | static bool |
michael@0 | 1012 | EmitInternedObjectOp(ExclusiveContext *cx, uint32_t index, JSOp op, BytecodeEmitter *bce) |
michael@0 | 1013 | { |
michael@0 | 1014 | JS_ASSERT(JOF_OPTYPE(op) == JOF_OBJECT); |
michael@0 | 1015 | JS_ASSERT(index < bce->objectList.length); |
michael@0 | 1016 | return EmitIndex32(cx, op, index, bce); |
michael@0 | 1017 | } |
michael@0 | 1018 | |
michael@0 | 1019 | static bool |
michael@0 | 1020 | EmitObjectOp(ExclusiveContext *cx, ObjectBox *objbox, JSOp op, BytecodeEmitter *bce) |
michael@0 | 1021 | { |
michael@0 | 1022 | return EmitInternedObjectOp(cx, bce->objectList.add(objbox), op, bce); |
michael@0 | 1023 | } |
michael@0 | 1024 | |
michael@0 | 1025 | static bool |
michael@0 | 1026 | EmitRegExp(ExclusiveContext *cx, uint32_t index, BytecodeEmitter *bce) |
michael@0 | 1027 | { |
michael@0 | 1028 | return EmitIndex32(cx, JSOP_REGEXP, index, bce); |
michael@0 | 1029 | } |
michael@0 | 1030 | |
michael@0 | 1031 | /* |
michael@0 | 1032 | * To catch accidental misuse, EMIT_UINT16_IMM_OP/Emit3 assert that they are |
michael@0 | 1033 | * not used to unconditionally emit JSOP_GETLOCAL. Variable access should |
michael@0 | 1034 | * instead be emitted using EmitVarOp. In special cases, when the caller |
michael@0 | 1035 | * definitely knows that a given local slot is unaliased, this function may be |
michael@0 | 1036 | * used as a non-asserting version of EMIT_UINT16_IMM_OP. |
michael@0 | 1037 | */ |
michael@0 | 1038 | static bool |
michael@0 | 1039 | EmitUnaliasedVarOp(ExclusiveContext *cx, JSOp op, uint32_t slot, BytecodeEmitter *bce) |
michael@0 | 1040 | { |
michael@0 | 1041 | JS_ASSERT(JOF_OPTYPE(op) != JOF_SCOPECOORD); |
michael@0 | 1042 | |
michael@0 | 1043 | if (IsLocalOp(op)) { |
michael@0 | 1044 | ptrdiff_t off = EmitN(cx, bce, op, LOCALNO_LEN); |
michael@0 | 1045 | if (off < 0) |
michael@0 | 1046 | return false; |
michael@0 | 1047 | |
michael@0 | 1048 | SET_LOCALNO(bce->code(off), slot); |
michael@0 | 1049 | return true; |
michael@0 | 1050 | } |
michael@0 | 1051 | |
michael@0 | 1052 | JS_ASSERT(IsArgOp(op)); |
michael@0 | 1053 | ptrdiff_t off = EmitN(cx, bce, op, ARGNO_LEN); |
michael@0 | 1054 | if (off < 0) |
michael@0 | 1055 | return false; |
michael@0 | 1056 | |
michael@0 | 1057 | SET_ARGNO(bce->code(off), slot); |
michael@0 | 1058 | return true; |
michael@0 | 1059 | } |
michael@0 | 1060 | |
michael@0 | 1061 | static bool |
michael@0 | 1062 | EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ScopeCoordinate sc, BytecodeEmitter *bce) |
michael@0 | 1063 | { |
michael@0 | 1064 | JS_ASSERT(JOF_OPTYPE(op) == JOF_SCOPECOORD); |
michael@0 | 1065 | |
michael@0 | 1066 | unsigned n = SCOPECOORD_HOPS_LEN + SCOPECOORD_SLOT_LEN; |
michael@0 | 1067 | JS_ASSERT(int(n) + 1 /* op */ == js_CodeSpec[op].length); |
michael@0 | 1068 | |
michael@0 | 1069 | ptrdiff_t off = EmitN(cx, bce, op, n); |
michael@0 | 1070 | if (off < 0) |
michael@0 | 1071 | return false; |
michael@0 | 1072 | |
michael@0 | 1073 | jsbytecode *pc = bce->code(off); |
michael@0 | 1074 | SET_SCOPECOORD_HOPS(pc, sc.hops()); |
michael@0 | 1075 | pc += SCOPECOORD_HOPS_LEN; |
michael@0 | 1076 | SET_SCOPECOORD_SLOT(pc, sc.slot()); |
michael@0 | 1077 | pc += SCOPECOORD_SLOT_LEN; |
michael@0 | 1078 | CheckTypeSet(cx, bce, op); |
michael@0 | 1079 | return true; |
michael@0 | 1080 | } |
michael@0 | 1081 | |
michael@0 | 1082 | // Compute the number of nested scope objects that will actually be on the scope |
michael@0 | 1083 | // chain at runtime, given the BCE's current staticScope. |
michael@0 | 1084 | static unsigned |
michael@0 | 1085 | DynamicNestedScopeDepth(BytecodeEmitter *bce) |
michael@0 | 1086 | { |
michael@0 | 1087 | unsigned depth = 0; |
michael@0 | 1088 | for (NestedScopeObject *b = bce->staticScope; b; b = b->enclosingNestedScope()) { |
michael@0 | 1089 | if (!b->is<StaticBlockObject>() || b->as<StaticBlockObject>().needsClone()) |
michael@0 | 1090 | ++depth; |
michael@0 | 1091 | } |
michael@0 | 1092 | |
michael@0 | 1093 | return depth; |
michael@0 | 1094 | } |
michael@0 | 1095 | |
michael@0 | 1096 | static bool |
michael@0 | 1097 | LookupAliasedName(HandleScript script, PropertyName *name, uint32_t *pslot) |
michael@0 | 1098 | { |
michael@0 | 1099 | /* |
michael@0 | 1100 | * Beware: BindingIter may contain more than one Binding for a given name |
michael@0 | 1101 | * (in the case of |function f(x,x) {}|) but only one will be aliased. |
michael@0 | 1102 | */ |
michael@0 | 1103 | uint32_t slot = CallObject::RESERVED_SLOTS; |
michael@0 | 1104 | for (BindingIter bi(script); !bi.done(); bi++) { |
michael@0 | 1105 | if (bi->aliased()) { |
michael@0 | 1106 | if (bi->name() == name) { |
michael@0 | 1107 | *pslot = slot; |
michael@0 | 1108 | return true; |
michael@0 | 1109 | } |
michael@0 | 1110 | slot++; |
michael@0 | 1111 | } |
michael@0 | 1112 | } |
michael@0 | 1113 | return false; |
michael@0 | 1114 | } |
michael@0 | 1115 | |
michael@0 | 1116 | static bool |
michael@0 | 1117 | LookupAliasedNameSlot(HandleScript script, PropertyName *name, ScopeCoordinate *sc) |
michael@0 | 1118 | { |
michael@0 | 1119 | uint32_t slot; |
michael@0 | 1120 | if (!LookupAliasedName(script, name, &slot)) |
michael@0 | 1121 | return false; |
michael@0 | 1122 | |
michael@0 | 1123 | sc->setSlot(slot); |
michael@0 | 1124 | return true; |
michael@0 | 1125 | } |
michael@0 | 1126 | |
michael@0 | 1127 | /* |
michael@0 | 1128 | * Use this function instead of assigning directly to 'hops' to guard for |
michael@0 | 1129 | * uint8_t overflows. |
michael@0 | 1130 | */ |
michael@0 | 1131 | static bool |
michael@0 | 1132 | AssignHops(BytecodeEmitter *bce, ParseNode *pn, unsigned src, ScopeCoordinate *dst) |
michael@0 | 1133 | { |
michael@0 | 1134 | if (src > UINT8_MAX) { |
michael@0 | 1135 | bce->reportError(pn, JSMSG_TOO_DEEP, js_function_str); |
michael@0 | 1136 | return false; |
michael@0 | 1137 | } |
michael@0 | 1138 | |
michael@0 | 1139 | dst->setHops(src); |
michael@0 | 1140 | return true; |
michael@0 | 1141 | } |
michael@0 | 1142 | |
michael@0 | 1143 | static bool |
michael@0 | 1144 | EmitAliasedVarOp(ExclusiveContext *cx, JSOp op, ParseNode *pn, BytecodeEmitter *bce) |
michael@0 | 1145 | { |
michael@0 | 1146 | /* |
michael@0 | 1147 | * While pn->pn_cookie tells us how many function scopes are between the use and the def this |
michael@0 | 1148 | * is not the same as how many hops up the dynamic scope chain are needed. In particular: |
michael@0 | 1149 | * - a lexical function scope only contributes a hop if it is "heavyweight" (has a dynamic |
michael@0 | 1150 | * scope object). |
michael@0 | 1151 | * - a heavyweight named function scope contributes an extra scope to the scope chain (a |
michael@0 | 1152 | * DeclEnvObject that holds just the name). |
michael@0 | 1153 | * - all the intervening let/catch blocks must be counted. |
michael@0 | 1154 | */ |
michael@0 | 1155 | unsigned skippedScopes = 0; |
michael@0 | 1156 | BytecodeEmitter *bceOfDef = bce; |
michael@0 | 1157 | if (pn->isUsed()) { |
michael@0 | 1158 | /* |
michael@0 | 1159 | * As explained in BindNameToSlot, the 'level' of a use indicates how |
michael@0 | 1160 | * many function scopes (i.e., BytecodeEmitters) to skip to find the |
michael@0 | 1161 | * enclosing function scope of the definition being accessed. |
michael@0 | 1162 | */ |
michael@0 | 1163 | for (unsigned i = pn->pn_cookie.level(); i; i--) { |
michael@0 | 1164 | skippedScopes += DynamicNestedScopeDepth(bceOfDef); |
michael@0 | 1165 | FunctionBox *funbox = bceOfDef->sc->asFunctionBox(); |
michael@0 | 1166 | if (funbox->isHeavyweight()) { |
michael@0 | 1167 | skippedScopes++; |
michael@0 | 1168 | if (funbox->function()->isNamedLambda()) |
michael@0 | 1169 | skippedScopes++; |
michael@0 | 1170 | } |
michael@0 | 1171 | bceOfDef = bceOfDef->parent; |
michael@0 | 1172 | } |
michael@0 | 1173 | } else { |
michael@0 | 1174 | JS_ASSERT(pn->isDefn()); |
michael@0 | 1175 | JS_ASSERT(pn->pn_cookie.level() == bce->script->staticLevel()); |
michael@0 | 1176 | } |
michael@0 | 1177 | |
michael@0 | 1178 | /* |
michael@0 | 1179 | * The final part of the skippedScopes computation depends on the type of |
michael@0 | 1180 | * variable. An arg or local variable is at the outer scope of a function |
michael@0 | 1181 | * and so includes the full DynamicNestedScopeDepth. A let/catch-binding |
michael@0 | 1182 | * requires a search of the block chain to see how many (dynamic) block |
michael@0 | 1183 | * objects to skip. |
michael@0 | 1184 | */ |
michael@0 | 1185 | ScopeCoordinate sc; |
michael@0 | 1186 | if (IsArgOp(pn->getOp())) { |
michael@0 | 1187 | if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc)) |
michael@0 | 1188 | return false; |
michael@0 | 1189 | JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc)); |
michael@0 | 1190 | } else { |
michael@0 | 1191 | JS_ASSERT(IsLocalOp(pn->getOp()) || pn->isKind(PNK_FUNCTION)); |
michael@0 | 1192 | uint32_t local = pn->pn_cookie.slot(); |
michael@0 | 1193 | if (local < bceOfDef->script->bindings.numVars()) { |
michael@0 | 1194 | if (!AssignHops(bce, pn, skippedScopes + DynamicNestedScopeDepth(bceOfDef), &sc)) |
michael@0 | 1195 | return false; |
michael@0 | 1196 | JS_ALWAYS_TRUE(LookupAliasedNameSlot(bceOfDef->script, pn->name(), &sc)); |
michael@0 | 1197 | } else { |
michael@0 | 1198 | JS_ASSERT_IF(bce->sc->isFunctionBox(), local <= bceOfDef->script->bindings.numLocals()); |
michael@0 | 1199 | JS_ASSERT(bceOfDef->staticScope->is<StaticBlockObject>()); |
michael@0 | 1200 | Rooted<StaticBlockObject*> b(cx, &bceOfDef->staticScope->as<StaticBlockObject>()); |
michael@0 | 1201 | while (local < b->localOffset()) { |
michael@0 | 1202 | if (b->needsClone()) |
michael@0 | 1203 | skippedScopes++; |
michael@0 | 1204 | b = &b->enclosingNestedScope()->as<StaticBlockObject>(); |
michael@0 | 1205 | } |
michael@0 | 1206 | if (!AssignHops(bce, pn, skippedScopes, &sc)) |
michael@0 | 1207 | return false; |
michael@0 | 1208 | sc.setSlot(b->localIndexToSlot(local)); |
michael@0 | 1209 | } |
michael@0 | 1210 | } |
michael@0 | 1211 | |
michael@0 | 1212 | return EmitAliasedVarOp(cx, op, sc, bce); |
michael@0 | 1213 | } |
michael@0 | 1214 | |
michael@0 | 1215 | static bool |
michael@0 | 1216 | EmitVarOp(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) |
michael@0 | 1217 | { |
michael@0 | 1218 | JS_ASSERT(pn->isKind(PNK_FUNCTION) || pn->isKind(PNK_NAME)); |
michael@0 | 1219 | JS_ASSERT(!pn->pn_cookie.isFree()); |
michael@0 | 1220 | |
michael@0 | 1221 | if (IsAliasedVarOp(op)) { |
michael@0 | 1222 | ScopeCoordinate sc; |
michael@0 | 1223 | sc.setHops(pn->pn_cookie.level()); |
michael@0 | 1224 | sc.setSlot(pn->pn_cookie.slot()); |
michael@0 | 1225 | return EmitAliasedVarOp(cx, op, sc, bce); |
michael@0 | 1226 | } |
michael@0 | 1227 | |
michael@0 | 1228 | JS_ASSERT_IF(pn->isKind(PNK_NAME), IsArgOp(op) || IsLocalOp(op)); |
michael@0 | 1229 | |
michael@0 | 1230 | if (!bce->isAliasedName(pn)) { |
michael@0 | 1231 | JS_ASSERT(pn->isUsed() || pn->isDefn()); |
michael@0 | 1232 | JS_ASSERT_IF(pn->isUsed(), pn->pn_cookie.level() == 0); |
michael@0 | 1233 | JS_ASSERT_IF(pn->isDefn(), pn->pn_cookie.level() == bce->script->staticLevel()); |
michael@0 | 1234 | return EmitUnaliasedVarOp(cx, op, pn->pn_cookie.slot(), bce); |
michael@0 | 1235 | } |
michael@0 | 1236 | |
michael@0 | 1237 | switch (op) { |
michael@0 | 1238 | case JSOP_GETARG: case JSOP_GETLOCAL: op = JSOP_GETALIASEDVAR; break; |
michael@0 | 1239 | case JSOP_SETARG: case JSOP_SETLOCAL: op = JSOP_SETALIASEDVAR; break; |
michael@0 | 1240 | default: MOZ_ASSUME_UNREACHABLE("unexpected var op"); |
michael@0 | 1241 | } |
michael@0 | 1242 | |
michael@0 | 1243 | return EmitAliasedVarOp(cx, op, pn, bce); |
michael@0 | 1244 | } |
michael@0 | 1245 | |
michael@0 | 1246 | static JSOp |
michael@0 | 1247 | GetIncDecInfo(ParseNodeKind kind, bool *post) |
michael@0 | 1248 | { |
michael@0 | 1249 | JS_ASSERT(kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT || |
michael@0 | 1250 | kind == PNK_POSTDECREMENT || kind == PNK_PREDECREMENT); |
michael@0 | 1251 | *post = kind == PNK_POSTINCREMENT || kind == PNK_POSTDECREMENT; |
michael@0 | 1252 | return (kind == PNK_POSTINCREMENT || kind == PNK_PREINCREMENT) ? JSOP_ADD : JSOP_SUB; |
michael@0 | 1253 | } |
michael@0 | 1254 | |
michael@0 | 1255 | static bool |
michael@0 | 1256 | EmitVarIncDec(ExclusiveContext *cx, ParseNode *pn, BytecodeEmitter *bce) |
michael@0 | 1257 | { |
michael@0 | 1258 | JSOp op = pn->pn_kid->getOp(); |
michael@0 | 1259 | JS_ASSERT(IsArgOp(op) || IsLocalOp(op) || IsAliasedVarOp(op)); |
michael@0 | 1260 | JS_ASSERT(pn->pn_kid->isKind(PNK_NAME)); |
michael@0 | 1261 | JS_ASSERT(!pn->pn_kid->pn_cookie.isFree()); |
michael@0 | 1262 | |
michael@0 | 1263 | bool post; |
michael@0 | 1264 | JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
michael@0 | 1265 | |
michael@0 | 1266 | JSOp getOp, setOp; |
michael@0 | 1267 | if (IsLocalOp(op)) { |
michael@0 | 1268 | getOp = JSOP_GETLOCAL; |
michael@0 | 1269 | setOp = JSOP_SETLOCAL; |
michael@0 | 1270 | } else if (IsArgOp(op)) { |
michael@0 | 1271 | getOp = JSOP_GETARG; |
michael@0 | 1272 | setOp = JSOP_SETARG; |
michael@0 | 1273 | } else { |
michael@0 | 1274 | getOp = JSOP_GETALIASEDVAR; |
michael@0 | 1275 | setOp = JSOP_SETALIASEDVAR; |
michael@0 | 1276 | } |
michael@0 | 1277 | |
michael@0 | 1278 | if (!EmitVarOp(cx, pn->pn_kid, getOp, bce)) // V |
michael@0 | 1279 | return false; |
michael@0 | 1280 | if (Emit1(cx, bce, JSOP_POS) < 0) // N |
michael@0 | 1281 | return false; |
michael@0 | 1282 | if (post && Emit1(cx, bce, JSOP_DUP) < 0) // N? N |
michael@0 | 1283 | return false; |
michael@0 | 1284 | if (Emit1(cx, bce, JSOP_ONE) < 0) // N? N 1 |
michael@0 | 1285 | return false; |
michael@0 | 1286 | if (Emit1(cx, bce, binop) < 0) // N? N+1 |
michael@0 | 1287 | return false; |
michael@0 | 1288 | if (!EmitVarOp(cx, pn->pn_kid, setOp, bce)) // N? N+1 |
michael@0 | 1289 | return false; |
michael@0 | 1290 | if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT |
michael@0 | 1291 | return false; |
michael@0 | 1292 | |
michael@0 | 1293 | return true; |
michael@0 | 1294 | } |
michael@0 | 1295 | |
michael@0 | 1296 | bool |
michael@0 | 1297 | BytecodeEmitter::isAliasedName(ParseNode *pn) |
michael@0 | 1298 | { |
michael@0 | 1299 | Definition *dn = pn->resolve(); |
michael@0 | 1300 | JS_ASSERT(dn->isDefn()); |
michael@0 | 1301 | JS_ASSERT(!dn->isPlaceholder()); |
michael@0 | 1302 | JS_ASSERT(dn->isBound()); |
michael@0 | 1303 | |
michael@0 | 1304 | /* If dn is in an enclosing function, it is definitely aliased. */ |
michael@0 | 1305 | if (dn->pn_cookie.level() != script->staticLevel()) |
michael@0 | 1306 | return true; |
michael@0 | 1307 | |
michael@0 | 1308 | switch (dn->kind()) { |
michael@0 | 1309 | case Definition::LET: |
michael@0 | 1310 | /* |
michael@0 | 1311 | * There are two ways to alias a let variable: nested functions and |
michael@0 | 1312 | * dynamic scope operations. (This is overly conservative since the |
michael@0 | 1313 | * bindingsAccessedDynamically flag, checked by allLocalsAliased, is |
michael@0 | 1314 | * function-wide.) |
michael@0 | 1315 | * |
michael@0 | 1316 | * In addition all locals in generators are marked as aliased, to ensure |
michael@0 | 1317 | * that they are allocated on scope chains instead of on the stack. See |
michael@0 | 1318 | * the definition of SharedContext::allLocalsAliased. |
michael@0 | 1319 | */ |
michael@0 | 1320 | return dn->isClosed() || sc->allLocalsAliased(); |
michael@0 | 1321 | case Definition::ARG: |
michael@0 | 1322 | /* |
michael@0 | 1323 | * Consult the bindings, since they already record aliasing. We might |
michael@0 | 1324 | * be tempted to use the same definition as VAR/CONST/LET, but there is |
michael@0 | 1325 | * a problem caused by duplicate arguments: only the last argument with |
michael@0 | 1326 | * a given name is aliased. This is necessary to avoid generating a |
michael@0 | 1327 | * shape for the call object with with more than one name for a given |
michael@0 | 1328 | * slot (which violates internal engine invariants). All this means that |
michael@0 | 1329 | * the '|| sc->allLocalsAliased()' disjunct is incorrect since it will |
michael@0 | 1330 | * mark both parameters in function(x,x) as aliased. |
michael@0 | 1331 | */ |
michael@0 | 1332 | return script->formalIsAliased(pn->pn_cookie.slot()); |
michael@0 | 1333 | case Definition::VAR: |
michael@0 | 1334 | case Definition::CONST: |
michael@0 | 1335 | JS_ASSERT_IF(sc->allLocalsAliased(), script->varIsAliased(pn->pn_cookie.slot())); |
michael@0 | 1336 | return script->varIsAliased(pn->pn_cookie.slot()); |
michael@0 | 1337 | case Definition::PLACEHOLDER: |
michael@0 | 1338 | case Definition::NAMED_LAMBDA: |
michael@0 | 1339 | case Definition::MISSING: |
michael@0 | 1340 | MOZ_ASSUME_UNREACHABLE("unexpected dn->kind"); |
michael@0 | 1341 | } |
michael@0 | 1342 | return false; |
michael@0 | 1343 | } |
michael@0 | 1344 | |
michael@0 | 1345 | /* |
michael@0 | 1346 | * Try to convert a *NAME op with a free name to a more specialized GNAME, |
michael@0 | 1347 | * INTRINSIC or ALIASEDVAR op, which optimize accesses on that name. |
michael@0 | 1348 | * Return true if a conversion was made. |
michael@0 | 1349 | */ |
michael@0 | 1350 | static bool |
michael@0 | 1351 | TryConvertFreeName(BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 1352 | { |
michael@0 | 1353 | /* |
michael@0 | 1354 | * In self-hosting mode, JSOP_*NAME is unconditionally converted to |
michael@0 | 1355 | * JSOP_*INTRINSIC. This causes lookups to be redirected to the special |
michael@0 | 1356 | * intrinsics holder in the global object, into which any missing values are |
michael@0 | 1357 | * cloned lazily upon first access. |
michael@0 | 1358 | */ |
michael@0 | 1359 | if (bce->emitterMode == BytecodeEmitter::SelfHosting) { |
michael@0 | 1360 | JSOp op; |
michael@0 | 1361 | switch (pn->getOp()) { |
michael@0 | 1362 | case JSOP_NAME: op = JSOP_GETINTRINSIC; break; |
michael@0 | 1363 | case JSOP_SETNAME: op = JSOP_SETINTRINSIC; break; |
michael@0 | 1364 | /* Other *NAME ops aren't (yet) supported in self-hosted code. */ |
michael@0 | 1365 | default: MOZ_ASSUME_UNREACHABLE("intrinsic"); |
michael@0 | 1366 | } |
michael@0 | 1367 | pn->setOp(op); |
michael@0 | 1368 | return true; |
michael@0 | 1369 | } |
michael@0 | 1370 | |
michael@0 | 1371 | /* |
michael@0 | 1372 | * When parsing inner functions lazily, parse nodes for outer functions no |
michael@0 | 1373 | * longer exist and only the function's scope chain is available for |
michael@0 | 1374 | * resolving upvar accesses within the inner function. |
michael@0 | 1375 | */ |
michael@0 | 1376 | if (bce->emitterMode == BytecodeEmitter::LazyFunction) { |
michael@0 | 1377 | // The only statements within a lazy function which can push lexical |
michael@0 | 1378 | // scopes are try/catch blocks. Use generic ops in this case. |
michael@0 | 1379 | for (StmtInfoBCE *stmt = bce->topStmt; stmt; stmt = stmt->down) { |
michael@0 | 1380 | if (stmt->type == STMT_CATCH) |
michael@0 | 1381 | return true; |
michael@0 | 1382 | } |
michael@0 | 1383 | |
michael@0 | 1384 | size_t hops = 0; |
michael@0 | 1385 | FunctionBox *funbox = bce->sc->asFunctionBox(); |
michael@0 | 1386 | if (funbox->hasExtensibleScope()) |
michael@0 | 1387 | return false; |
michael@0 | 1388 | if (funbox->function()->isNamedLambda() && funbox->function()->atom() == pn->pn_atom) |
michael@0 | 1389 | return false; |
michael@0 | 1390 | if (funbox->isHeavyweight()) { |
michael@0 | 1391 | hops++; |
michael@0 | 1392 | if (funbox->function()->isNamedLambda()) |
michael@0 | 1393 | hops++; |
michael@0 | 1394 | } |
michael@0 | 1395 | if (bce->script->directlyInsideEval()) |
michael@0 | 1396 | return false; |
michael@0 | 1397 | RootedObject outerScope(bce->sc->context, bce->script->enclosingStaticScope()); |
michael@0 | 1398 | for (StaticScopeIter<CanGC> ssi(bce->sc->context, outerScope); !ssi.done(); ssi++) { |
michael@0 | 1399 | if (ssi.type() != StaticScopeIter<CanGC>::FUNCTION) { |
michael@0 | 1400 | if (ssi.type() == StaticScopeIter<CanGC>::BLOCK) { |
michael@0 | 1401 | // Use generic ops if a catch block is encountered. |
michael@0 | 1402 | return false; |
michael@0 | 1403 | } |
michael@0 | 1404 | if (ssi.hasDynamicScopeObject()) |
michael@0 | 1405 | hops++; |
michael@0 | 1406 | continue; |
michael@0 | 1407 | } |
michael@0 | 1408 | RootedScript script(bce->sc->context, ssi.funScript()); |
michael@0 | 1409 | if (script->functionNonDelazifying()->atom() == pn->pn_atom) |
michael@0 | 1410 | return false; |
michael@0 | 1411 | if (ssi.hasDynamicScopeObject()) { |
michael@0 | 1412 | uint32_t slot; |
michael@0 | 1413 | if (LookupAliasedName(script, pn->pn_atom->asPropertyName(), &slot)) { |
michael@0 | 1414 | JSOp op; |
michael@0 | 1415 | switch (pn->getOp()) { |
michael@0 | 1416 | case JSOP_NAME: op = JSOP_GETALIASEDVAR; break; |
michael@0 | 1417 | case JSOP_SETNAME: op = JSOP_SETALIASEDVAR; break; |
michael@0 | 1418 | default: return false; |
michael@0 | 1419 | } |
michael@0 | 1420 | pn->setOp(op); |
michael@0 | 1421 | JS_ALWAYS_TRUE(pn->pn_cookie.set(bce->parser->tokenStream, hops, slot)); |
michael@0 | 1422 | return true; |
michael@0 | 1423 | } |
michael@0 | 1424 | hops++; |
michael@0 | 1425 | } |
michael@0 | 1426 | |
michael@0 | 1427 | if (script->funHasExtensibleScope() || script->directlyInsideEval()) |
michael@0 | 1428 | return false; |
michael@0 | 1429 | } |
michael@0 | 1430 | } |
michael@0 | 1431 | |
michael@0 | 1432 | // Unbound names aren't recognizable global-property references if the |
michael@0 | 1433 | // script isn't running against its global object. |
michael@0 | 1434 | if (!bce->script->compileAndGo() || !bce->hasGlobalScope) |
michael@0 | 1435 | return false; |
michael@0 | 1436 | |
michael@0 | 1437 | // Deoptimized names also aren't necessarily globals. |
michael@0 | 1438 | if (pn->isDeoptimized()) |
michael@0 | 1439 | return false; |
michael@0 | 1440 | |
michael@0 | 1441 | if (bce->sc->isFunctionBox()) { |
michael@0 | 1442 | // Unbound names in function code may not be globals if new locals can |
michael@0 | 1443 | // be added to this function (or an enclosing one) to alias a global |
michael@0 | 1444 | // reference. |
michael@0 | 1445 | FunctionBox *funbox = bce->sc->asFunctionBox(); |
michael@0 | 1446 | if (funbox->mightAliasLocals()) |
michael@0 | 1447 | return false; |
michael@0 | 1448 | } |
michael@0 | 1449 | |
michael@0 | 1450 | // If this is eval code, being evaluated inside strict mode eval code, |
michael@0 | 1451 | // an "unbound" name might be a binding local to that outer eval: |
michael@0 | 1452 | // |
michael@0 | 1453 | // var x = "GLOBAL"; |
michael@0 | 1454 | // eval('"use strict"; ' + |
michael@0 | 1455 | // 'var x; ' + |
michael@0 | 1456 | // 'eval("print(x)");'); // "undefined", not "GLOBAL" |
michael@0 | 1457 | // |
michael@0 | 1458 | // Given the enclosing eval code's strictness and its bindings (neither is |
michael@0 | 1459 | // readily available now), we could exactly check global-ness, but it's not |
michael@0 | 1460 | // worth the trouble for doubly-nested eval code. So we conservatively |
michael@0 | 1461 | // approximate. If the outer eval code is strict, then this eval code will |
michael@0 | 1462 | // be: thus, don't optimize if we're compiling strict code inside an eval. |
michael@0 | 1463 | if (bce->insideEval && bce->sc->strict) |
michael@0 | 1464 | return false; |
michael@0 | 1465 | |
michael@0 | 1466 | // Beware: if you change anything here, you might also need to change |
michael@0 | 1467 | // js::ReportIfUndeclaredVarAssignment. |
michael@0 | 1468 | JSOp op; |
michael@0 | 1469 | switch (pn->getOp()) { |
michael@0 | 1470 | case JSOP_NAME: op = JSOP_GETGNAME; break; |
michael@0 | 1471 | case JSOP_SETNAME: op = JSOP_SETGNAME; break; |
michael@0 | 1472 | case JSOP_SETCONST: |
michael@0 | 1473 | // Not supported. |
michael@0 | 1474 | return false; |
michael@0 | 1475 | default: MOZ_ASSUME_UNREACHABLE("gname"); |
michael@0 | 1476 | } |
michael@0 | 1477 | pn->setOp(op); |
michael@0 | 1478 | return true; |
michael@0 | 1479 | } |
michael@0 | 1480 | |
michael@0 | 1481 | /* |
michael@0 | 1482 | * BindNameToSlotHelper attempts to optimize name gets and sets to stack slot |
michael@0 | 1483 | * loads and stores, given the compile-time information in bce and a PNK_NAME |
michael@0 | 1484 | * node pn. It returns false on error, true on success. |
michael@0 | 1485 | * |
michael@0 | 1486 | * The caller can test pn->pn_cookie.isFree() to tell whether optimization |
michael@0 | 1487 | * occurred, in which case BindNameToSlotHelper also updated pn->pn_op. If |
michael@0 | 1488 | * pn->pn_cookie.isFree() is still true on return, pn->pn_op still may have |
michael@0 | 1489 | * been optimized, e.g., from JSOP_NAME to JSOP_CALLEE. Whether or not |
michael@0 | 1490 | * pn->pn_op was modified, if this function finds an argument or local variable |
michael@0 | 1491 | * name, PND_CONST will be set in pn_dflags for read-only properties after a |
michael@0 | 1492 | * successful return. |
michael@0 | 1493 | * |
michael@0 | 1494 | * NB: if you add more opcodes specialized from JSOP_NAME, etc., don't forget |
michael@0 | 1495 | * to update the special cases in EmitFor (for-in) and EmitAssignment (= and |
michael@0 | 1496 | * op=, e.g. +=). |
michael@0 | 1497 | */ |
michael@0 | 1498 | static bool |
michael@0 | 1499 | BindNameToSlotHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 1500 | { |
michael@0 | 1501 | JS_ASSERT(pn->isKind(PNK_NAME)); |
michael@0 | 1502 | |
michael@0 | 1503 | JS_ASSERT_IF(pn->isKind(PNK_FUNCTION), pn->isBound()); |
michael@0 | 1504 | |
michael@0 | 1505 | /* Don't attempt if 'pn' is already bound or deoptimized or a function. */ |
michael@0 | 1506 | if (pn->isBound() || pn->isDeoptimized()) |
michael@0 | 1507 | return true; |
michael@0 | 1508 | |
michael@0 | 1509 | /* JSOP_CALLEE is pre-bound by definition. */ |
michael@0 | 1510 | JSOp op = pn->getOp(); |
michael@0 | 1511 | JS_ASSERT(op != JSOP_CALLEE); |
michael@0 | 1512 | JS_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); |
michael@0 | 1513 | |
michael@0 | 1514 | /* |
michael@0 | 1515 | * The parser already linked name uses to definitions when (where not |
michael@0 | 1516 | * prevented by non-lexical constructs like 'with' and 'eval'). |
michael@0 | 1517 | */ |
michael@0 | 1518 | Definition *dn; |
michael@0 | 1519 | if (pn->isUsed()) { |
michael@0 | 1520 | JS_ASSERT(pn->pn_cookie.isFree()); |
michael@0 | 1521 | dn = pn->pn_lexdef; |
michael@0 | 1522 | JS_ASSERT(dn->isDefn()); |
michael@0 | 1523 | pn->pn_dflags |= (dn->pn_dflags & PND_CONST); |
michael@0 | 1524 | } else if (pn->isDefn()) { |
michael@0 | 1525 | dn = (Definition *) pn; |
michael@0 | 1526 | } else { |
michael@0 | 1527 | return true; |
michael@0 | 1528 | } |
michael@0 | 1529 | |
michael@0 | 1530 | /* |
michael@0 | 1531 | * Turn attempts to mutate const-declared bindings into get ops (for |
michael@0 | 1532 | * pre-increment and pre-decrement ops, our caller will have to emit |
michael@0 | 1533 | * JSOP_POS, JSOP_ONE, and JSOP_ADD as well). |
michael@0 | 1534 | * |
michael@0 | 1535 | * Turn JSOP_DELNAME into JSOP_FALSE if dn is known, as all declared |
michael@0 | 1536 | * bindings visible to the compiler are permanent in JS unless the |
michael@0 | 1537 | * declaration originates at top level in eval code. |
michael@0 | 1538 | */ |
michael@0 | 1539 | switch (op) { |
michael@0 | 1540 | case JSOP_NAME: |
michael@0 | 1541 | case JSOP_SETCONST: |
michael@0 | 1542 | break; |
michael@0 | 1543 | default: |
michael@0 | 1544 | if (pn->isConst()) { |
michael@0 | 1545 | if (bce->sc->needStrictChecks()) { |
michael@0 | 1546 | JSAutoByteString name; |
michael@0 | 1547 | if (!AtomToPrintableString(cx, pn->pn_atom, &name) || |
michael@0 | 1548 | !bce->reportStrictModeError(pn, JSMSG_READ_ONLY, name.ptr())) |
michael@0 | 1549 | { |
michael@0 | 1550 | return false; |
michael@0 | 1551 | } |
michael@0 | 1552 | } |
michael@0 | 1553 | pn->setOp(op = JSOP_NAME); |
michael@0 | 1554 | } |
michael@0 | 1555 | } |
michael@0 | 1556 | |
michael@0 | 1557 | if (dn->pn_cookie.isFree()) { |
michael@0 | 1558 | if (HandleScript caller = bce->evalCaller) { |
michael@0 | 1559 | JS_ASSERT(bce->script->compileAndGo()); |
michael@0 | 1560 | |
michael@0 | 1561 | /* |
michael@0 | 1562 | * Don't generate upvars on the left side of a for loop. See |
michael@0 | 1563 | * bug 470758. |
michael@0 | 1564 | */ |
michael@0 | 1565 | if (bce->emittingForInit) |
michael@0 | 1566 | return true; |
michael@0 | 1567 | |
michael@0 | 1568 | /* |
michael@0 | 1569 | * If this is an eval in the global scope, then unbound variables |
michael@0 | 1570 | * must be globals, so try to use GNAME ops. |
michael@0 | 1571 | */ |
michael@0 | 1572 | if (!caller->functionOrCallerFunction() && TryConvertFreeName(bce, pn)) { |
michael@0 | 1573 | pn->pn_dflags |= PND_BOUND; |
michael@0 | 1574 | return true; |
michael@0 | 1575 | } |
michael@0 | 1576 | |
michael@0 | 1577 | /* |
michael@0 | 1578 | * Out of tricks, so we must rely on PICs to optimize named |
michael@0 | 1579 | * accesses from direct eval called from function code. |
michael@0 | 1580 | */ |
michael@0 | 1581 | return true; |
michael@0 | 1582 | } |
michael@0 | 1583 | |
michael@0 | 1584 | /* Optimize accesses to undeclared globals. */ |
michael@0 | 1585 | if (!TryConvertFreeName(bce, pn)) |
michael@0 | 1586 | return true; |
michael@0 | 1587 | |
michael@0 | 1588 | pn->pn_dflags |= PND_BOUND; |
michael@0 | 1589 | return true; |
michael@0 | 1590 | } |
michael@0 | 1591 | |
michael@0 | 1592 | /* |
michael@0 | 1593 | * At this point, we are only dealing with uses that have already been |
michael@0 | 1594 | * bound to definitions via pn_lexdef. The rest of this routine converts |
michael@0 | 1595 | * the parse node of the use from its initial JSOP_*NAME* op to a LOCAL/ARG |
michael@0 | 1596 | * op. This requires setting the node's pn_cookie with a pair (level, slot) |
michael@0 | 1597 | * where 'level' is the number of function scopes between the use and the |
michael@0 | 1598 | * def and 'slot' is the index to emit as the immediate of the ARG/LOCAL |
michael@0 | 1599 | * op. For example, in this code: |
michael@0 | 1600 | * |
michael@0 | 1601 | * function(a,b,x) { return x } |
michael@0 | 1602 | * function(y) { function() { return y } } |
michael@0 | 1603 | * |
michael@0 | 1604 | * x will get (level = 0, slot = 2) and y will get (level = 1, slot = 0). |
michael@0 | 1605 | */ |
michael@0 | 1606 | JS_ASSERT(!pn->isDefn()); |
michael@0 | 1607 | JS_ASSERT(pn->isUsed()); |
michael@0 | 1608 | JS_ASSERT(pn->pn_lexdef); |
michael@0 | 1609 | JS_ASSERT(pn->pn_cookie.isFree()); |
michael@0 | 1610 | |
michael@0 | 1611 | /* |
michael@0 | 1612 | * We are compiling a function body and may be able to optimize name |
michael@0 | 1613 | * to stack slot. Look for an argument or variable in the function and |
michael@0 | 1614 | * rewrite pn_op and update pn accordingly. |
michael@0 | 1615 | */ |
michael@0 | 1616 | switch (dn->kind()) { |
michael@0 | 1617 | case Definition::ARG: |
michael@0 | 1618 | switch (op) { |
michael@0 | 1619 | case JSOP_NAME: op = JSOP_GETARG; break; |
michael@0 | 1620 | case JSOP_SETNAME: op = JSOP_SETARG; break; |
michael@0 | 1621 | default: MOZ_ASSUME_UNREACHABLE("arg"); |
michael@0 | 1622 | } |
michael@0 | 1623 | JS_ASSERT(!pn->isConst()); |
michael@0 | 1624 | break; |
michael@0 | 1625 | |
michael@0 | 1626 | case Definition::VAR: |
michael@0 | 1627 | case Definition::CONST: |
michael@0 | 1628 | case Definition::LET: |
michael@0 | 1629 | switch (op) { |
michael@0 | 1630 | case JSOP_NAME: op = JSOP_GETLOCAL; break; |
michael@0 | 1631 | case JSOP_SETNAME: op = JSOP_SETLOCAL; break; |
michael@0 | 1632 | case JSOP_SETCONST: op = JSOP_SETLOCAL; break; |
michael@0 | 1633 | default: MOZ_ASSUME_UNREACHABLE("local"); |
michael@0 | 1634 | } |
michael@0 | 1635 | break; |
michael@0 | 1636 | |
michael@0 | 1637 | case Definition::NAMED_LAMBDA: { |
michael@0 | 1638 | JS_ASSERT(dn->isOp(JSOP_CALLEE)); |
michael@0 | 1639 | JS_ASSERT(op != JSOP_CALLEE); |
michael@0 | 1640 | |
michael@0 | 1641 | /* |
michael@0 | 1642 | * Currently, the ALIASEDVAR ops do not support accessing the |
michael@0 | 1643 | * callee of a DeclEnvObject, so use NAME. |
michael@0 | 1644 | */ |
michael@0 | 1645 | if (dn->pn_cookie.level() != bce->script->staticLevel()) |
michael@0 | 1646 | return true; |
michael@0 | 1647 | |
michael@0 | 1648 | DebugOnly<JSFunction *> fun = bce->sc->asFunctionBox()->function(); |
michael@0 | 1649 | JS_ASSERT(fun->isLambda()); |
michael@0 | 1650 | JS_ASSERT(pn->pn_atom == fun->atom()); |
michael@0 | 1651 | |
michael@0 | 1652 | /* |
michael@0 | 1653 | * Leave pn->isOp(JSOP_NAME) if bce->fun is heavyweight to |
michael@0 | 1654 | * address two cases: a new binding introduced by eval, and |
michael@0 | 1655 | * assignment to the name in strict mode. |
michael@0 | 1656 | * |
michael@0 | 1657 | * var fun = (function f(s) { eval(s); return f; }); |
michael@0 | 1658 | * assertEq(fun("var f = 42"), 42); |
michael@0 | 1659 | * |
michael@0 | 1660 | * ECMAScript specifies that a function expression's name is bound |
michael@0 | 1661 | * in a lexical environment distinct from that used to bind its |
michael@0 | 1662 | * named parameters, the arguments object, and its variables. The |
michael@0 | 1663 | * new binding for "var f = 42" shadows the binding for the |
michael@0 | 1664 | * function itself, so the name of the function will not refer to |
michael@0 | 1665 | * the function. |
michael@0 | 1666 | * |
michael@0 | 1667 | * (function f() { "use strict"; f = 12; })(); |
michael@0 | 1668 | * |
michael@0 | 1669 | * Outside strict mode, assignment to a function expression's name |
michael@0 | 1670 | * has no effect. But in strict mode, this attempt to mutate an |
michael@0 | 1671 | * immutable binding must throw a TypeError. We implement this by |
michael@0 | 1672 | * not optimizing such assignments and by marking such functions as |
michael@0 | 1673 | * heavyweight, ensuring that the function name is represented in |
michael@0 | 1674 | * the scope chain so that assignment will throw a TypeError. |
michael@0 | 1675 | */ |
michael@0 | 1676 | if (!bce->sc->asFunctionBox()->isHeavyweight()) { |
michael@0 | 1677 | op = JSOP_CALLEE; |
michael@0 | 1678 | pn->pn_dflags |= PND_CONST; |
michael@0 | 1679 | } |
michael@0 | 1680 | |
michael@0 | 1681 | pn->setOp(op); |
michael@0 | 1682 | pn->pn_dflags |= PND_BOUND; |
michael@0 | 1683 | return true; |
michael@0 | 1684 | } |
michael@0 | 1685 | |
michael@0 | 1686 | case Definition::PLACEHOLDER: |
michael@0 | 1687 | return true; |
michael@0 | 1688 | |
michael@0 | 1689 | case Definition::MISSING: |
michael@0 | 1690 | MOZ_ASSUME_UNREACHABLE("missing"); |
michael@0 | 1691 | } |
michael@0 | 1692 | |
michael@0 | 1693 | /* |
michael@0 | 1694 | * The difference between the current static level and the static level of |
michael@0 | 1695 | * the definition is the number of function scopes between the current |
michael@0 | 1696 | * scope and dn's scope. |
michael@0 | 1697 | */ |
michael@0 | 1698 | unsigned skip = bce->script->staticLevel() - dn->pn_cookie.level(); |
michael@0 | 1699 | JS_ASSERT_IF(skip, dn->isClosed()); |
michael@0 | 1700 | |
michael@0 | 1701 | /* |
michael@0 | 1702 | * Explicitly disallow accessing var/let bindings in global scope from |
michael@0 | 1703 | * nested functions. The reason for this limitation is that, since the |
michael@0 | 1704 | * global script is not included in the static scope chain (1. because it |
michael@0 | 1705 | * has no object to stand in the static scope chain, 2. to minimize memory |
michael@0 | 1706 | * bloat where a single live function keeps its whole global script |
michael@0 | 1707 | * alive.), ScopeCoordinateToTypeSet is not able to find the var/let's |
michael@0 | 1708 | * associated types::TypeSet. |
michael@0 | 1709 | */ |
michael@0 | 1710 | if (skip) { |
michael@0 | 1711 | BytecodeEmitter *bceSkipped = bce; |
michael@0 | 1712 | for (unsigned i = 0; i < skip; i++) |
michael@0 | 1713 | bceSkipped = bceSkipped->parent; |
michael@0 | 1714 | if (!bceSkipped->sc->isFunctionBox()) |
michael@0 | 1715 | return true; |
michael@0 | 1716 | } |
michael@0 | 1717 | |
michael@0 | 1718 | JS_ASSERT(!pn->isOp(op)); |
michael@0 | 1719 | pn->setOp(op); |
michael@0 | 1720 | if (!pn->pn_cookie.set(bce->parser->tokenStream, skip, dn->pn_cookie.slot())) |
michael@0 | 1721 | return false; |
michael@0 | 1722 | |
michael@0 | 1723 | pn->pn_dflags |= PND_BOUND; |
michael@0 | 1724 | return true; |
michael@0 | 1725 | } |
michael@0 | 1726 | |
michael@0 | 1727 | /* |
michael@0 | 1728 | * Attempts to bind the name, then checks that no dynamic scope lookup ops are |
michael@0 | 1729 | * emitted in self-hosting mode. NAME ops do lookups off current scope chain, |
michael@0 | 1730 | * and we do not want to allow self-hosted code to use the dynamic scope. |
michael@0 | 1731 | */ |
michael@0 | 1732 | static bool |
michael@0 | 1733 | BindNameToSlot(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 1734 | { |
michael@0 | 1735 | if (!BindNameToSlotHelper(cx, bce, pn)) |
michael@0 | 1736 | return false; |
michael@0 | 1737 | |
michael@0 | 1738 | if (bce->emitterMode == BytecodeEmitter::SelfHosting && !pn->isBound()) { |
michael@0 | 1739 | bce->reportError(pn, JSMSG_SELFHOSTED_UNBOUND_NAME); |
michael@0 | 1740 | return false; |
michael@0 | 1741 | } |
michael@0 | 1742 | |
michael@0 | 1743 | return true; |
michael@0 | 1744 | } |
michael@0 | 1745 | |
michael@0 | 1746 | /* |
michael@0 | 1747 | * If pn contains a useful expression, return true with *answer set to true. |
michael@0 | 1748 | * If pn contains a useless expression, return true with *answer set to false. |
michael@0 | 1749 | * Return false on error. |
michael@0 | 1750 | * |
michael@0 | 1751 | * The caller should initialize *answer to false and invoke this function on |
michael@0 | 1752 | * an expression statement or similar subtree to decide whether the tree could |
michael@0 | 1753 | * produce code that has any side effects. For an expression statement, we |
michael@0 | 1754 | * define useless code as code with no side effects, because the main effect, |
michael@0 | 1755 | * the value left on the stack after the code executes, will be discarded by a |
michael@0 | 1756 | * pop bytecode. |
michael@0 | 1757 | */ |
michael@0 | 1758 | static bool |
michael@0 | 1759 | CheckSideEffects(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool *answer) |
michael@0 | 1760 | { |
michael@0 | 1761 | if (!pn || *answer) |
michael@0 | 1762 | return true; |
michael@0 | 1763 | |
michael@0 | 1764 | switch (pn->getArity()) { |
michael@0 | 1765 | case PN_CODE: |
michael@0 | 1766 | /* |
michael@0 | 1767 | * A named function, contrary to ES3, is no longer useful, because we |
michael@0 | 1768 | * bind its name lexically (using JSOP_CALLEE) instead of creating an |
michael@0 | 1769 | * Object instance and binding a readonly, permanent property in it |
michael@0 | 1770 | * (the object and binding can be detected and hijacked or captured). |
michael@0 | 1771 | * This is a bug fix to ES3; it is fixed in ES3.1 drafts. |
michael@0 | 1772 | */ |
michael@0 | 1773 | MOZ_ASSERT(*answer == false); |
michael@0 | 1774 | return true; |
michael@0 | 1775 | |
michael@0 | 1776 | case PN_LIST: |
michael@0 | 1777 | if (pn->isOp(JSOP_NOP) || pn->isOp(JSOP_OR) || pn->isOp(JSOP_AND) || |
michael@0 | 1778 | pn->isOp(JSOP_STRICTEQ) || pn->isOp(JSOP_STRICTNE)) { |
michael@0 | 1779 | /* |
michael@0 | 1780 | * Non-operators along with ||, &&, ===, and !== never invoke |
michael@0 | 1781 | * toString or valueOf. |
michael@0 | 1782 | */ |
michael@0 | 1783 | bool ok = true; |
michael@0 | 1784 | for (ParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) |
michael@0 | 1785 | ok &= CheckSideEffects(cx, bce, pn2, answer); |
michael@0 | 1786 | return ok; |
michael@0 | 1787 | } |
michael@0 | 1788 | |
michael@0 | 1789 | if (pn->isKind(PNK_GENEXP)) { |
michael@0 | 1790 | /* Generator-expressions are harmless if the result is ignored. */ |
michael@0 | 1791 | MOZ_ASSERT(*answer == false); |
michael@0 | 1792 | return true; |
michael@0 | 1793 | } |
michael@0 | 1794 | |
michael@0 | 1795 | /* |
michael@0 | 1796 | * All invocation operations (construct: PNK_NEW, call: PNK_CALL) |
michael@0 | 1797 | * are presumed to be useful, because they may have side effects |
michael@0 | 1798 | * even if their main effect (their return value) is discarded. |
michael@0 | 1799 | * |
michael@0 | 1800 | * PNK_ELEM binary trees of 3+ nodes are flattened into lists to |
michael@0 | 1801 | * avoid too much recursion. All such lists must be presumed to be |
michael@0 | 1802 | * useful because each index operation could invoke a getter. |
michael@0 | 1803 | * |
michael@0 | 1804 | * Likewise, array and object initialisers may call prototype |
michael@0 | 1805 | * setters (the __defineSetter__ built-in, and writable __proto__ |
michael@0 | 1806 | * on Array.prototype create this hazard). Initialiser list nodes |
michael@0 | 1807 | * have JSOP_NEWINIT in their pn_op. |
michael@0 | 1808 | */ |
michael@0 | 1809 | *answer = true; |
michael@0 | 1810 | return true; |
michael@0 | 1811 | |
michael@0 | 1812 | case PN_TERNARY: |
michael@0 | 1813 | return CheckSideEffects(cx, bce, pn->pn_kid1, answer) && |
michael@0 | 1814 | CheckSideEffects(cx, bce, pn->pn_kid2, answer) && |
michael@0 | 1815 | CheckSideEffects(cx, bce, pn->pn_kid3, answer); |
michael@0 | 1816 | |
michael@0 | 1817 | case PN_BINARY: |
michael@0 | 1818 | case PN_BINARY_OBJ: |
michael@0 | 1819 | if (pn->isAssignment()) { |
michael@0 | 1820 | /* |
michael@0 | 1821 | * Assignment is presumed to be useful, even if the next operation |
michael@0 | 1822 | * is another assignment overwriting this one's ostensible effect, |
michael@0 | 1823 | * because the left operand may be a property with a setter that |
michael@0 | 1824 | * has side effects. |
michael@0 | 1825 | * |
michael@0 | 1826 | * The only exception is assignment of a useless value to a const |
michael@0 | 1827 | * declared in the function currently being compiled. |
michael@0 | 1828 | */ |
michael@0 | 1829 | ParseNode *pn2 = pn->pn_left; |
michael@0 | 1830 | if (!pn2->isKind(PNK_NAME)) { |
michael@0 | 1831 | *answer = true; |
michael@0 | 1832 | } else { |
michael@0 | 1833 | if (!BindNameToSlot(cx, bce, pn2)) |
michael@0 | 1834 | return false; |
michael@0 | 1835 | if (!CheckSideEffects(cx, bce, pn->pn_right, answer)) |
michael@0 | 1836 | return false; |
michael@0 | 1837 | if (!*answer && (!pn->isOp(JSOP_NOP) || !pn2->isConst())) |
michael@0 | 1838 | *answer = true; |
michael@0 | 1839 | } |
michael@0 | 1840 | return true; |
michael@0 | 1841 | } |
michael@0 | 1842 | |
michael@0 | 1843 | if (pn->isOp(JSOP_OR) || pn->isOp(JSOP_AND) || pn->isOp(JSOP_STRICTEQ) || |
michael@0 | 1844 | pn->isOp(JSOP_STRICTNE)) { |
michael@0 | 1845 | /* |
michael@0 | 1846 | * ||, &&, ===, and !== do not convert their operands via |
michael@0 | 1847 | * toString or valueOf method calls. |
michael@0 | 1848 | */ |
michael@0 | 1849 | return CheckSideEffects(cx, bce, pn->pn_left, answer) && |
michael@0 | 1850 | CheckSideEffects(cx, bce, pn->pn_right, answer); |
michael@0 | 1851 | } |
michael@0 | 1852 | |
michael@0 | 1853 | /* |
michael@0 | 1854 | * We can't easily prove that neither operand ever denotes an |
michael@0 | 1855 | * object with a toString or valueOf method. |
michael@0 | 1856 | */ |
michael@0 | 1857 | *answer = true; |
michael@0 | 1858 | return true; |
michael@0 | 1859 | |
michael@0 | 1860 | case PN_UNARY: |
michael@0 | 1861 | switch (pn->getKind()) { |
michael@0 | 1862 | case PNK_DELETE: |
michael@0 | 1863 | { |
michael@0 | 1864 | ParseNode *pn2 = pn->pn_kid; |
michael@0 | 1865 | switch (pn2->getKind()) { |
michael@0 | 1866 | case PNK_NAME: |
michael@0 | 1867 | if (!BindNameToSlot(cx, bce, pn2)) |
michael@0 | 1868 | return false; |
michael@0 | 1869 | if (pn2->isConst()) { |
michael@0 | 1870 | MOZ_ASSERT(*answer == false); |
michael@0 | 1871 | return true; |
michael@0 | 1872 | } |
michael@0 | 1873 | /* FALL THROUGH */ |
michael@0 | 1874 | case PNK_DOT: |
michael@0 | 1875 | case PNK_CALL: |
michael@0 | 1876 | case PNK_ELEM: |
michael@0 | 1877 | /* All these delete addressing modes have effects too. */ |
michael@0 | 1878 | *answer = true; |
michael@0 | 1879 | return true; |
michael@0 | 1880 | default: |
michael@0 | 1881 | return CheckSideEffects(cx, bce, pn2, answer); |
michael@0 | 1882 | } |
michael@0 | 1883 | MOZ_ASSUME_UNREACHABLE("We have a returning default case"); |
michael@0 | 1884 | } |
michael@0 | 1885 | |
michael@0 | 1886 | case PNK_TYPEOF: |
michael@0 | 1887 | case PNK_VOID: |
michael@0 | 1888 | case PNK_NOT: |
michael@0 | 1889 | case PNK_BITNOT: |
michael@0 | 1890 | if (pn->isOp(JSOP_NOT)) { |
michael@0 | 1891 | /* ! does not convert its operand via toString or valueOf. */ |
michael@0 | 1892 | return CheckSideEffects(cx, bce, pn->pn_kid, answer); |
michael@0 | 1893 | } |
michael@0 | 1894 | /* FALL THROUGH */ |
michael@0 | 1895 | |
michael@0 | 1896 | default: |
michael@0 | 1897 | /* |
michael@0 | 1898 | * All of PNK_INC, PNK_DEC, PNK_THROW, PNK_YIELD, and PNK_YIELD_STAR |
michael@0 | 1899 | * have direct effects. Of the remaining unary-arity node types, we |
michael@0 | 1900 | * can't easily prove that the operand never denotes an object with |
michael@0 | 1901 | * a toString or valueOf method. |
michael@0 | 1902 | */ |
michael@0 | 1903 | *answer = true; |
michael@0 | 1904 | return true; |
michael@0 | 1905 | } |
michael@0 | 1906 | MOZ_ASSUME_UNREACHABLE("We have a returning default case"); |
michael@0 | 1907 | |
michael@0 | 1908 | case PN_NAME: |
michael@0 | 1909 | /* |
michael@0 | 1910 | * Take care to avoid trying to bind a label name (labels, both for |
michael@0 | 1911 | * statements and property values in object initialisers, have pn_op |
michael@0 | 1912 | * defaulted to JSOP_NOP). |
michael@0 | 1913 | */ |
michael@0 | 1914 | if (pn->isKind(PNK_NAME) && !pn->isOp(JSOP_NOP)) { |
michael@0 | 1915 | if (!BindNameToSlot(cx, bce, pn)) |
michael@0 | 1916 | return false; |
michael@0 | 1917 | if (!pn->isOp(JSOP_CALLEE) && pn->pn_cookie.isFree()) { |
michael@0 | 1918 | /* |
michael@0 | 1919 | * Not a use of an unshadowed named function expression's given |
michael@0 | 1920 | * name, so this expression could invoke a getter that has side |
michael@0 | 1921 | * effects. |
michael@0 | 1922 | */ |
michael@0 | 1923 | *answer = true; |
michael@0 | 1924 | } |
michael@0 | 1925 | } |
michael@0 | 1926 | if (pn->isKind(PNK_DOT)) { |
michael@0 | 1927 | /* Dotted property references in general can call getters. */ |
michael@0 | 1928 | *answer = true; |
michael@0 | 1929 | } |
michael@0 | 1930 | return CheckSideEffects(cx, bce, pn->maybeExpr(), answer); |
michael@0 | 1931 | |
michael@0 | 1932 | case PN_NULLARY: |
michael@0 | 1933 | if (pn->isKind(PNK_DEBUGGER)) |
michael@0 | 1934 | *answer = true; |
michael@0 | 1935 | return true; |
michael@0 | 1936 | } |
michael@0 | 1937 | return true; |
michael@0 | 1938 | } |
michael@0 | 1939 | |
michael@0 | 1940 | bool |
michael@0 | 1941 | BytecodeEmitter::isInLoop() |
michael@0 | 1942 | { |
michael@0 | 1943 | for (StmtInfoBCE *stmt = topStmt; stmt; stmt = stmt->down) { |
michael@0 | 1944 | if (stmt->isLoop()) |
michael@0 | 1945 | return true; |
michael@0 | 1946 | } |
michael@0 | 1947 | return false; |
michael@0 | 1948 | } |
michael@0 | 1949 | |
michael@0 | 1950 | bool |
michael@0 | 1951 | BytecodeEmitter::checkSingletonContext() |
michael@0 | 1952 | { |
michael@0 | 1953 | if (!script->compileAndGo() || sc->isFunctionBox() || isInLoop()) |
michael@0 | 1954 | return false; |
michael@0 | 1955 | hasSingletons = true; |
michael@0 | 1956 | return true; |
michael@0 | 1957 | } |
michael@0 | 1958 | |
michael@0 | 1959 | bool |
michael@0 | 1960 | BytecodeEmitter::needsImplicitThis() |
michael@0 | 1961 | { |
michael@0 | 1962 | if (!script->compileAndGo()) |
michael@0 | 1963 | return true; |
michael@0 | 1964 | |
michael@0 | 1965 | if (sc->isFunctionBox()) { |
michael@0 | 1966 | if (sc->asFunctionBox()->inWith) |
michael@0 | 1967 | return true; |
michael@0 | 1968 | } else { |
michael@0 | 1969 | JSObject *scope = sc->asGlobalSharedContext()->scopeChain(); |
michael@0 | 1970 | while (scope) { |
michael@0 | 1971 | if (scope->is<DynamicWithObject>()) |
michael@0 | 1972 | return true; |
michael@0 | 1973 | scope = scope->enclosingScope(); |
michael@0 | 1974 | } |
michael@0 | 1975 | } |
michael@0 | 1976 | |
michael@0 | 1977 | for (StmtInfoBCE *stmt = topStmt; stmt; stmt = stmt->down) { |
michael@0 | 1978 | if (stmt->type == STMT_WITH) |
michael@0 | 1979 | return true; |
michael@0 | 1980 | } |
michael@0 | 1981 | return false; |
michael@0 | 1982 | } |
michael@0 | 1983 | |
michael@0 | 1984 | void |
michael@0 | 1985 | BytecodeEmitter::tellDebuggerAboutCompiledScript(ExclusiveContext *cx) |
michael@0 | 1986 | { |
michael@0 | 1987 | // Note: when parsing off thread the resulting scripts need to be handed to |
michael@0 | 1988 | // the debugger after rejoining to the main thread. |
michael@0 | 1989 | if (!cx->isJSContext()) |
michael@0 | 1990 | return; |
michael@0 | 1991 | |
michael@0 | 1992 | RootedFunction function(cx, script->functionNonDelazifying()); |
michael@0 | 1993 | CallNewScriptHook(cx->asJSContext(), script, function); |
michael@0 | 1994 | // Lazy scripts are never top level (despite always being invoked with a |
michael@0 | 1995 | // nullptr parent), and so the hook should never be fired. |
michael@0 | 1996 | if (emitterMode != LazyFunction && !parent) { |
michael@0 | 1997 | GlobalObject *compileAndGoGlobal = nullptr; |
michael@0 | 1998 | if (script->compileAndGo()) |
michael@0 | 1999 | compileAndGoGlobal = &script->global(); |
michael@0 | 2000 | Debugger::onNewScript(cx->asJSContext(), script, compileAndGoGlobal); |
michael@0 | 2001 | } |
michael@0 | 2002 | } |
michael@0 | 2003 | |
michael@0 | 2004 | inline TokenStream * |
michael@0 | 2005 | BytecodeEmitter::tokenStream() |
michael@0 | 2006 | { |
michael@0 | 2007 | return &parser->tokenStream; |
michael@0 | 2008 | } |
michael@0 | 2009 | |
michael@0 | 2010 | bool |
michael@0 | 2011 | BytecodeEmitter::reportError(ParseNode *pn, unsigned errorNumber, ...) |
michael@0 | 2012 | { |
michael@0 | 2013 | TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; |
michael@0 | 2014 | |
michael@0 | 2015 | va_list args; |
michael@0 | 2016 | va_start(args, errorNumber); |
michael@0 | 2017 | bool result = tokenStream()->reportCompileErrorNumberVA(pos.begin, JSREPORT_ERROR, |
michael@0 | 2018 | errorNumber, args); |
michael@0 | 2019 | va_end(args); |
michael@0 | 2020 | return result; |
michael@0 | 2021 | } |
michael@0 | 2022 | |
michael@0 | 2023 | bool |
michael@0 | 2024 | BytecodeEmitter::reportStrictWarning(ParseNode *pn, unsigned errorNumber, ...) |
michael@0 | 2025 | { |
michael@0 | 2026 | TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; |
michael@0 | 2027 | |
michael@0 | 2028 | va_list args; |
michael@0 | 2029 | va_start(args, errorNumber); |
michael@0 | 2030 | bool result = tokenStream()->reportStrictWarningErrorNumberVA(pos.begin, errorNumber, args); |
michael@0 | 2031 | va_end(args); |
michael@0 | 2032 | return result; |
michael@0 | 2033 | } |
michael@0 | 2034 | |
michael@0 | 2035 | bool |
michael@0 | 2036 | BytecodeEmitter::reportStrictModeError(ParseNode *pn, unsigned errorNumber, ...) |
michael@0 | 2037 | { |
michael@0 | 2038 | TokenPos pos = pn ? pn->pn_pos : tokenStream()->currentToken().pos; |
michael@0 | 2039 | |
michael@0 | 2040 | va_list args; |
michael@0 | 2041 | va_start(args, errorNumber); |
michael@0 | 2042 | bool result = tokenStream()->reportStrictModeErrorNumberVA(pos.begin, sc->strict, |
michael@0 | 2043 | errorNumber, args); |
michael@0 | 2044 | va_end(args); |
michael@0 | 2045 | return result; |
michael@0 | 2046 | } |
michael@0 | 2047 | |
michael@0 | 2048 | static bool |
michael@0 | 2049 | EmitNewInit(ExclusiveContext *cx, BytecodeEmitter *bce, JSProtoKey key) |
michael@0 | 2050 | { |
michael@0 | 2051 | const size_t len = 1 + UINT32_INDEX_LEN; |
michael@0 | 2052 | ptrdiff_t offset = EmitCheck(cx, bce, len); |
michael@0 | 2053 | if (offset < 0) |
michael@0 | 2054 | return false; |
michael@0 | 2055 | |
michael@0 | 2056 | jsbytecode *code = bce->code(offset); |
michael@0 | 2057 | code[0] = JSOP_NEWINIT; |
michael@0 | 2058 | code[1] = jsbytecode(key); |
michael@0 | 2059 | code[2] = 0; |
michael@0 | 2060 | code[3] = 0; |
michael@0 | 2061 | code[4] = 0; |
michael@0 | 2062 | UpdateDepth(cx, bce, offset); |
michael@0 | 2063 | CheckTypeSet(cx, bce, JSOP_NEWINIT); |
michael@0 | 2064 | return true; |
michael@0 | 2065 | } |
michael@0 | 2066 | |
michael@0 | 2067 | static bool |
michael@0 | 2068 | IteratorResultShape(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned *shape) |
michael@0 | 2069 | { |
michael@0 | 2070 | JS_ASSERT(bce->script->compileAndGo()); |
michael@0 | 2071 | |
michael@0 | 2072 | RootedObject obj(cx); |
michael@0 | 2073 | gc::AllocKind kind = GuessObjectGCKind(2); |
michael@0 | 2074 | obj = NewBuiltinClassInstance(cx, &JSObject::class_, kind); |
michael@0 | 2075 | if (!obj) |
michael@0 | 2076 | return false; |
michael@0 | 2077 | |
michael@0 | 2078 | Rooted<jsid> value_id(cx, AtomToId(cx->names().value)); |
michael@0 | 2079 | Rooted<jsid> done_id(cx, AtomToId(cx->names().done)); |
michael@0 | 2080 | if (!DefineNativeProperty(cx, obj, value_id, UndefinedHandleValue, nullptr, nullptr, |
michael@0 | 2081 | JSPROP_ENUMERATE)) |
michael@0 | 2082 | return false; |
michael@0 | 2083 | if (!DefineNativeProperty(cx, obj, done_id, UndefinedHandleValue, nullptr, nullptr, |
michael@0 | 2084 | JSPROP_ENUMERATE)) |
michael@0 | 2085 | return false; |
michael@0 | 2086 | |
michael@0 | 2087 | ObjectBox *objbox = bce->parser->newObjectBox(obj); |
michael@0 | 2088 | if (!objbox) |
michael@0 | 2089 | return false; |
michael@0 | 2090 | |
michael@0 | 2091 | *shape = bce->objectList.add(objbox); |
michael@0 | 2092 | |
michael@0 | 2093 | return true; |
michael@0 | 2094 | } |
michael@0 | 2095 | |
michael@0 | 2096 | static bool |
michael@0 | 2097 | EmitPrepareIteratorResult(ExclusiveContext *cx, BytecodeEmitter *bce) |
michael@0 | 2098 | { |
michael@0 | 2099 | if (bce->script->compileAndGo()) { |
michael@0 | 2100 | unsigned shape; |
michael@0 | 2101 | if (!IteratorResultShape(cx, bce, &shape)) |
michael@0 | 2102 | return false; |
michael@0 | 2103 | return EmitIndex32(cx, JSOP_NEWOBJECT, shape, bce); |
michael@0 | 2104 | } |
michael@0 | 2105 | |
michael@0 | 2106 | return EmitNewInit(cx, bce, JSProto_Object); |
michael@0 | 2107 | } |
michael@0 | 2108 | |
michael@0 | 2109 | static bool |
michael@0 | 2110 | EmitFinishIteratorResult(ExclusiveContext *cx, BytecodeEmitter *bce, bool done) |
michael@0 | 2111 | { |
michael@0 | 2112 | jsatomid value_id; |
michael@0 | 2113 | if (!bce->makeAtomIndex(cx->names().value, &value_id)) |
michael@0 | 2114 | return UINT_MAX; |
michael@0 | 2115 | jsatomid done_id; |
michael@0 | 2116 | if (!bce->makeAtomIndex(cx->names().done, &done_id)) |
michael@0 | 2117 | return UINT_MAX; |
michael@0 | 2118 | |
michael@0 | 2119 | if (!EmitIndex32(cx, JSOP_INITPROP, value_id, bce)) |
michael@0 | 2120 | return false; |
michael@0 | 2121 | if (Emit1(cx, bce, done ? JSOP_TRUE : JSOP_FALSE) < 0) |
michael@0 | 2122 | return false; |
michael@0 | 2123 | if (!EmitIndex32(cx, JSOP_INITPROP, done_id, bce)) |
michael@0 | 2124 | return false; |
michael@0 | 2125 | if (Emit1(cx, bce, JSOP_ENDINIT) < 0) |
michael@0 | 2126 | return false; |
michael@0 | 2127 | return true; |
michael@0 | 2128 | } |
michael@0 | 2129 | |
michael@0 | 2130 | static bool |
michael@0 | 2131 | EmitNameOp(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool callContext) |
michael@0 | 2132 | { |
michael@0 | 2133 | if (!BindNameToSlot(cx, bce, pn)) |
michael@0 | 2134 | return false; |
michael@0 | 2135 | |
michael@0 | 2136 | JSOp op = pn->getOp(); |
michael@0 | 2137 | |
michael@0 | 2138 | if (op == JSOP_CALLEE) { |
michael@0 | 2139 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 2140 | return false; |
michael@0 | 2141 | } else { |
michael@0 | 2142 | if (!pn->pn_cookie.isFree()) { |
michael@0 | 2143 | JS_ASSERT(JOF_OPTYPE(op) != JOF_ATOM); |
michael@0 | 2144 | if (!EmitVarOp(cx, pn, op, bce)) |
michael@0 | 2145 | return false; |
michael@0 | 2146 | } else { |
michael@0 | 2147 | if (!EmitAtomOp(cx, pn, op, bce)) |
michael@0 | 2148 | return false; |
michael@0 | 2149 | } |
michael@0 | 2150 | } |
michael@0 | 2151 | |
michael@0 | 2152 | /* Need to provide |this| value for call */ |
michael@0 | 2153 | if (callContext) { |
michael@0 | 2154 | if (op == JSOP_NAME && bce->needsImplicitThis()) { |
michael@0 | 2155 | if (!EmitAtomOp(cx, pn, JSOP_IMPLICITTHIS, bce)) |
michael@0 | 2156 | return false; |
michael@0 | 2157 | } else { |
michael@0 | 2158 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) |
michael@0 | 2159 | return false; |
michael@0 | 2160 | } |
michael@0 | 2161 | } |
michael@0 | 2162 | |
michael@0 | 2163 | return true; |
michael@0 | 2164 | } |
michael@0 | 2165 | |
michael@0 | 2166 | static bool |
michael@0 | 2167 | EmitPropLHS(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) |
michael@0 | 2168 | { |
michael@0 | 2169 | JS_ASSERT(pn->isKind(PNK_DOT)); |
michael@0 | 2170 | ParseNode *pn2 = pn->maybeExpr(); |
michael@0 | 2171 | |
michael@0 | 2172 | /* |
michael@0 | 2173 | * If the object operand is also a dotted property reference, reverse the |
michael@0 | 2174 | * list linked via pn_expr temporarily so we can iterate over it from the |
michael@0 | 2175 | * bottom up (reversing again as we go), to avoid excessive recursion. |
michael@0 | 2176 | */ |
michael@0 | 2177 | if (pn2->isKind(PNK_DOT)) { |
michael@0 | 2178 | ParseNode *pndot = pn2; |
michael@0 | 2179 | ParseNode *pnup = nullptr, *pndown; |
michael@0 | 2180 | ptrdiff_t top = bce->offset(); |
michael@0 | 2181 | for (;;) { |
michael@0 | 2182 | /* Reverse pndot->pn_expr to point up, not down. */ |
michael@0 | 2183 | pndot->pn_offset = top; |
michael@0 | 2184 | JS_ASSERT(!pndot->isUsed()); |
michael@0 | 2185 | pndown = pndot->pn_expr; |
michael@0 | 2186 | pndot->pn_expr = pnup; |
michael@0 | 2187 | if (!pndown->isKind(PNK_DOT)) |
michael@0 | 2188 | break; |
michael@0 | 2189 | pnup = pndot; |
michael@0 | 2190 | pndot = pndown; |
michael@0 | 2191 | } |
michael@0 | 2192 | |
michael@0 | 2193 | /* pndown is a primary expression, not a dotted property reference. */ |
michael@0 | 2194 | if (!EmitTree(cx, bce, pndown)) |
michael@0 | 2195 | return false; |
michael@0 | 2196 | |
michael@0 | 2197 | do { |
michael@0 | 2198 | /* Walk back up the list, emitting annotated name ops. */ |
michael@0 | 2199 | if (!EmitAtomOp(cx, pndot, JSOP_GETPROP, bce)) |
michael@0 | 2200 | return false; |
michael@0 | 2201 | |
michael@0 | 2202 | /* Reverse the pn_expr link again. */ |
michael@0 | 2203 | pnup = pndot->pn_expr; |
michael@0 | 2204 | pndot->pn_expr = pndown; |
michael@0 | 2205 | pndown = pndot; |
michael@0 | 2206 | } while ((pndot = pnup) != nullptr); |
michael@0 | 2207 | return true; |
michael@0 | 2208 | } |
michael@0 | 2209 | |
michael@0 | 2210 | // The non-optimized case. |
michael@0 | 2211 | return EmitTree(cx, bce, pn2); |
michael@0 | 2212 | } |
michael@0 | 2213 | |
michael@0 | 2214 | static bool |
michael@0 | 2215 | EmitPropOp(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) |
michael@0 | 2216 | { |
michael@0 | 2217 | JS_ASSERT(pn->isArity(PN_NAME)); |
michael@0 | 2218 | |
michael@0 | 2219 | if (!EmitPropLHS(cx, pn, op, bce)) |
michael@0 | 2220 | return false; |
michael@0 | 2221 | |
michael@0 | 2222 | if (op == JSOP_CALLPROP && Emit1(cx, bce, JSOP_DUP) < 0) |
michael@0 | 2223 | return false; |
michael@0 | 2224 | |
michael@0 | 2225 | if (!EmitAtomOp(cx, pn, op, bce)) |
michael@0 | 2226 | return false; |
michael@0 | 2227 | |
michael@0 | 2228 | if (op == JSOP_CALLPROP && Emit1(cx, bce, JSOP_SWAP) < 0) |
michael@0 | 2229 | return false; |
michael@0 | 2230 | |
michael@0 | 2231 | return true; |
michael@0 | 2232 | } |
michael@0 | 2233 | |
michael@0 | 2234 | static bool |
michael@0 | 2235 | EmitPropIncDec(ExclusiveContext *cx, ParseNode *pn, BytecodeEmitter *bce) |
michael@0 | 2236 | { |
michael@0 | 2237 | JS_ASSERT(pn->pn_kid->getKind() == PNK_DOT); |
michael@0 | 2238 | |
michael@0 | 2239 | bool post; |
michael@0 | 2240 | JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
michael@0 | 2241 | |
michael@0 | 2242 | JSOp get = JSOP_GETPROP; |
michael@0 | 2243 | if (!EmitPropLHS(cx, pn->pn_kid, get, bce)) // OBJ |
michael@0 | 2244 | return false; |
michael@0 | 2245 | if (Emit1(cx, bce, JSOP_DUP) < 0) // OBJ OBJ |
michael@0 | 2246 | return false; |
michael@0 | 2247 | if (!EmitAtomOp(cx, pn->pn_kid, JSOP_GETPROP, bce)) // OBJ V |
michael@0 | 2248 | return false; |
michael@0 | 2249 | if (Emit1(cx, bce, JSOP_POS) < 0) // OBJ N |
michael@0 | 2250 | return false; |
michael@0 | 2251 | if (post && Emit1(cx, bce, JSOP_DUP) < 0) // OBJ N? N |
michael@0 | 2252 | return false; |
michael@0 | 2253 | if (Emit1(cx, bce, JSOP_ONE) < 0) // OBJ N? N 1 |
michael@0 | 2254 | return false; |
michael@0 | 2255 | if (Emit1(cx, bce, binop) < 0) // OBJ N? N+1 |
michael@0 | 2256 | return false; |
michael@0 | 2257 | |
michael@0 | 2258 | if (post) { |
michael@0 | 2259 | if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)2) < 0) // N? N+1 OBJ |
michael@0 | 2260 | return false; |
michael@0 | 2261 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // N? OBJ N+1 |
michael@0 | 2262 | return false; |
michael@0 | 2263 | } |
michael@0 | 2264 | |
michael@0 | 2265 | if (!EmitAtomOp(cx, pn->pn_kid, JSOP_SETPROP, bce)) // N? N+1 |
michael@0 | 2266 | return false; |
michael@0 | 2267 | if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT |
michael@0 | 2268 | return false; |
michael@0 | 2269 | |
michael@0 | 2270 | return true; |
michael@0 | 2271 | } |
michael@0 | 2272 | |
michael@0 | 2273 | static bool |
michael@0 | 2274 | EmitNameIncDec(ExclusiveContext *cx, ParseNode *pn, BytecodeEmitter *bce) |
michael@0 | 2275 | { |
michael@0 | 2276 | const JSCodeSpec *cs = &js_CodeSpec[pn->pn_kid->getOp()]; |
michael@0 | 2277 | |
michael@0 | 2278 | bool global = (cs->format & JOF_GNAME); |
michael@0 | 2279 | bool post; |
michael@0 | 2280 | JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
michael@0 | 2281 | |
michael@0 | 2282 | if (!EmitAtomOp(cx, pn->pn_kid, global ? JSOP_BINDGNAME : JSOP_BINDNAME, bce)) // OBJ |
michael@0 | 2283 | return false; |
michael@0 | 2284 | if (!EmitAtomOp(cx, pn->pn_kid, global ? JSOP_GETGNAME : JSOP_NAME, bce)) // OBJ V |
michael@0 | 2285 | return false; |
michael@0 | 2286 | if (Emit1(cx, bce, JSOP_POS) < 0) // OBJ N |
michael@0 | 2287 | return false; |
michael@0 | 2288 | if (post && Emit1(cx, bce, JSOP_DUP) < 0) // OBJ N? N |
michael@0 | 2289 | return false; |
michael@0 | 2290 | if (Emit1(cx, bce, JSOP_ONE) < 0) // OBJ N? N 1 |
michael@0 | 2291 | return false; |
michael@0 | 2292 | if (Emit1(cx, bce, binop) < 0) // OBJ N? N+1 |
michael@0 | 2293 | return false; |
michael@0 | 2294 | |
michael@0 | 2295 | if (post) { |
michael@0 | 2296 | if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)2) < 0) // N? N+1 OBJ |
michael@0 | 2297 | return false; |
michael@0 | 2298 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // N? OBJ N+1 |
michael@0 | 2299 | return false; |
michael@0 | 2300 | } |
michael@0 | 2301 | |
michael@0 | 2302 | if (!EmitAtomOp(cx, pn->pn_kid, global ? JSOP_SETGNAME : JSOP_SETNAME, bce)) // N? N+1 |
michael@0 | 2303 | return false; |
michael@0 | 2304 | if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT |
michael@0 | 2305 | return false; |
michael@0 | 2306 | |
michael@0 | 2307 | return true; |
michael@0 | 2308 | } |
michael@0 | 2309 | |
michael@0 | 2310 | /* |
michael@0 | 2311 | * Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM |
michael@0 | 2312 | * opcode onto the stack in the right order. In the case of SETELEM, the |
michael@0 | 2313 | * value to be assigned must already be pushed. |
michael@0 | 2314 | */ |
michael@0 | 2315 | static bool |
michael@0 | 2316 | EmitElemOperands(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) |
michael@0 | 2317 | { |
michael@0 | 2318 | JS_ASSERT(pn->isArity(PN_BINARY)); |
michael@0 | 2319 | if (!EmitTree(cx, bce, pn->pn_left)) |
michael@0 | 2320 | return false; |
michael@0 | 2321 | if (op == JSOP_CALLELEM && Emit1(cx, bce, JSOP_DUP) < 0) |
michael@0 | 2322 | return false; |
michael@0 | 2323 | if (!EmitTree(cx, bce, pn->pn_right)) |
michael@0 | 2324 | return false; |
michael@0 | 2325 | if (op == JSOP_SETELEM && Emit2(cx, bce, JSOP_PICK, (jsbytecode)2) < 0) |
michael@0 | 2326 | return false; |
michael@0 | 2327 | return true; |
michael@0 | 2328 | } |
michael@0 | 2329 | |
michael@0 | 2330 | static inline bool |
michael@0 | 2331 | EmitElemOpBase(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp op) |
michael@0 | 2332 | { |
michael@0 | 2333 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 2334 | return false; |
michael@0 | 2335 | CheckTypeSet(cx, bce, op); |
michael@0 | 2336 | |
michael@0 | 2337 | if (op == JSOP_CALLELEM) { |
michael@0 | 2338 | if (Emit1(cx, bce, JSOP_SWAP) < 0) |
michael@0 | 2339 | return false; |
michael@0 | 2340 | } |
michael@0 | 2341 | return true; |
michael@0 | 2342 | } |
michael@0 | 2343 | |
michael@0 | 2344 | static bool |
michael@0 | 2345 | EmitElemOp(ExclusiveContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce) |
michael@0 | 2346 | { |
michael@0 | 2347 | return EmitElemOperands(cx, pn, op, bce) && EmitElemOpBase(cx, bce, op); |
michael@0 | 2348 | } |
michael@0 | 2349 | |
michael@0 | 2350 | static bool |
michael@0 | 2351 | EmitElemIncDec(ExclusiveContext *cx, ParseNode *pn, BytecodeEmitter *bce) |
michael@0 | 2352 | { |
michael@0 | 2353 | JS_ASSERT(pn->pn_kid->getKind() == PNK_ELEM); |
michael@0 | 2354 | |
michael@0 | 2355 | if (!EmitElemOperands(cx, pn->pn_kid, JSOP_GETELEM, bce)) |
michael@0 | 2356 | return false; |
michael@0 | 2357 | |
michael@0 | 2358 | bool post; |
michael@0 | 2359 | JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
michael@0 | 2360 | |
michael@0 | 2361 | /* |
michael@0 | 2362 | * We need to convert the key to an object id first, so that we do not do |
michael@0 | 2363 | * it inside both the GETELEM and the SETELEM. |
michael@0 | 2364 | */ |
michael@0 | 2365 | // OBJ KEY* |
michael@0 | 2366 | if (Emit1(cx, bce, JSOP_TOID) < 0) // OBJ KEY |
michael@0 | 2367 | return false; |
michael@0 | 2368 | if (Emit1(cx, bce, JSOP_DUP2) < 0) // OBJ KEY OBJ KEY |
michael@0 | 2369 | return false; |
michael@0 | 2370 | if (!EmitElemOpBase(cx, bce, JSOP_GETELEM)) // OBJ KEY V |
michael@0 | 2371 | return false; |
michael@0 | 2372 | if (Emit1(cx, bce, JSOP_POS) < 0) // OBJ KEY N |
michael@0 | 2373 | return false; |
michael@0 | 2374 | if (post && Emit1(cx, bce, JSOP_DUP) < 0) // OBJ KEY N? N |
michael@0 | 2375 | return false; |
michael@0 | 2376 | if (Emit1(cx, bce, JSOP_ONE) < 0) // OBJ KEY N? N 1 |
michael@0 | 2377 | return false; |
michael@0 | 2378 | if (Emit1(cx, bce, binop) < 0) // OBJ KEY N? N+1 |
michael@0 | 2379 | return false; |
michael@0 | 2380 | |
michael@0 | 2381 | if (post) { |
michael@0 | 2382 | if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0) // KEY N N+1 OBJ |
michael@0 | 2383 | return false; |
michael@0 | 2384 | if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0) // N N+1 OBJ KEY |
michael@0 | 2385 | return false; |
michael@0 | 2386 | if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)2) < 0) // N OBJ KEY N+1 |
michael@0 | 2387 | return false; |
michael@0 | 2388 | } |
michael@0 | 2389 | |
michael@0 | 2390 | if (!EmitElemOpBase(cx, bce, JSOP_SETELEM)) // N? N+1 |
michael@0 | 2391 | return false; |
michael@0 | 2392 | if (post && Emit1(cx, bce, JSOP_POP) < 0) // RESULT |
michael@0 | 2393 | return false; |
michael@0 | 2394 | |
michael@0 | 2395 | return true; |
michael@0 | 2396 | } |
michael@0 | 2397 | |
michael@0 | 2398 | static bool |
michael@0 | 2399 | EmitNumberOp(ExclusiveContext *cx, double dval, BytecodeEmitter *bce) |
michael@0 | 2400 | { |
michael@0 | 2401 | int32_t ival; |
michael@0 | 2402 | uint32_t u; |
michael@0 | 2403 | ptrdiff_t off; |
michael@0 | 2404 | jsbytecode *pc; |
michael@0 | 2405 | |
michael@0 | 2406 | if (NumberIsInt32(dval, &ival)) { |
michael@0 | 2407 | if (ival == 0) |
michael@0 | 2408 | return Emit1(cx, bce, JSOP_ZERO) >= 0; |
michael@0 | 2409 | if (ival == 1) |
michael@0 | 2410 | return Emit1(cx, bce, JSOP_ONE) >= 0; |
michael@0 | 2411 | if ((int)(int8_t)ival == ival) |
michael@0 | 2412 | return Emit2(cx, bce, JSOP_INT8, (jsbytecode)(int8_t)ival) >= 0; |
michael@0 | 2413 | |
michael@0 | 2414 | u = (uint32_t)ival; |
michael@0 | 2415 | if (u < JS_BIT(16)) { |
michael@0 | 2416 | EMIT_UINT16_IMM_OP(JSOP_UINT16, u); |
michael@0 | 2417 | } else if (u < JS_BIT(24)) { |
michael@0 | 2418 | off = EmitN(cx, bce, JSOP_UINT24, 3); |
michael@0 | 2419 | if (off < 0) |
michael@0 | 2420 | return false; |
michael@0 | 2421 | pc = bce->code(off); |
michael@0 | 2422 | SET_UINT24(pc, u); |
michael@0 | 2423 | } else { |
michael@0 | 2424 | off = EmitN(cx, bce, JSOP_INT32, 4); |
michael@0 | 2425 | if (off < 0) |
michael@0 | 2426 | return false; |
michael@0 | 2427 | pc = bce->code(off); |
michael@0 | 2428 | SET_INT32(pc, ival); |
michael@0 | 2429 | } |
michael@0 | 2430 | return true; |
michael@0 | 2431 | } |
michael@0 | 2432 | |
michael@0 | 2433 | if (!bce->constList.append(DoubleValue(dval))) |
michael@0 | 2434 | return false; |
michael@0 | 2435 | |
michael@0 | 2436 | return EmitIndex32(cx, JSOP_DOUBLE, bce->constList.length() - 1, bce); |
michael@0 | 2437 | } |
michael@0 | 2438 | |
michael@0 | 2439 | static inline void |
michael@0 | 2440 | SetJumpOffsetAt(BytecodeEmitter *bce, ptrdiff_t off) |
michael@0 | 2441 | { |
michael@0 | 2442 | SET_JUMP_OFFSET(bce->code(off), bce->offset() - off); |
michael@0 | 2443 | } |
michael@0 | 2444 | |
michael@0 | 2445 | static bool |
michael@0 | 2446 | PushUndefinedValues(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned n) |
michael@0 | 2447 | { |
michael@0 | 2448 | for (unsigned i = 0; i < n; ++i) { |
michael@0 | 2449 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) |
michael@0 | 2450 | return false; |
michael@0 | 2451 | } |
michael@0 | 2452 | return true; |
michael@0 | 2453 | } |
michael@0 | 2454 | |
michael@0 | 2455 | static bool |
michael@0 | 2456 | InitializeBlockScopedLocalsFromStack(ExclusiveContext *cx, BytecodeEmitter *bce, |
michael@0 | 2457 | Handle<StaticBlockObject *> blockObj) |
michael@0 | 2458 | { |
michael@0 | 2459 | for (unsigned i = blockObj->numVariables(); i > 0; --i) { |
michael@0 | 2460 | if (blockObj->isAliased(i - 1)) { |
michael@0 | 2461 | ScopeCoordinate sc; |
michael@0 | 2462 | sc.setHops(0); |
michael@0 | 2463 | sc.setSlot(BlockObject::RESERVED_SLOTS + i - 1); |
michael@0 | 2464 | if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, bce)) |
michael@0 | 2465 | return false; |
michael@0 | 2466 | } else { |
michael@0 | 2467 | unsigned local = blockObj->blockIndexToLocalIndex(i - 1); |
michael@0 | 2468 | if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, local, bce)) |
michael@0 | 2469 | return false; |
michael@0 | 2470 | } |
michael@0 | 2471 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 2472 | return false; |
michael@0 | 2473 | } |
michael@0 | 2474 | return true; |
michael@0 | 2475 | } |
michael@0 | 2476 | |
michael@0 | 2477 | static bool |
michael@0 | 2478 | EnterBlockScope(ExclusiveContext *cx, BytecodeEmitter *bce, StmtInfoBCE *stmtInfo, |
michael@0 | 2479 | ObjectBox *objbox, unsigned alreadyPushed = 0) |
michael@0 | 2480 | { |
michael@0 | 2481 | // Initial values for block-scoped locals. |
michael@0 | 2482 | Rooted<StaticBlockObject *> blockObj(cx, &objbox->object->as<StaticBlockObject>()); |
michael@0 | 2483 | if (!PushUndefinedValues(cx, bce, blockObj->numVariables() - alreadyPushed)) |
michael@0 | 2484 | return false; |
michael@0 | 2485 | |
michael@0 | 2486 | if (!EnterNestedScope(cx, bce, stmtInfo, objbox, STMT_BLOCK)) |
michael@0 | 2487 | return false; |
michael@0 | 2488 | |
michael@0 | 2489 | if (!InitializeBlockScopedLocalsFromStack(cx, bce, blockObj)) |
michael@0 | 2490 | return false; |
michael@0 | 2491 | |
michael@0 | 2492 | return true; |
michael@0 | 2493 | } |
michael@0 | 2494 | |
michael@0 | 2495 | /* |
michael@0 | 2496 | * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. |
michael@0 | 2497 | * LLVM is deciding to inline this function which uses a lot of stack space |
michael@0 | 2498 | * into EmitTree which is recursive and uses relatively little stack space. |
michael@0 | 2499 | */ |
michael@0 | 2500 | MOZ_NEVER_INLINE static bool |
michael@0 | 2501 | EmitSwitch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 2502 | { |
michael@0 | 2503 | JSOp switchOp; |
michael@0 | 2504 | bool hasDefault; |
michael@0 | 2505 | ptrdiff_t top, off, defaultOffset; |
michael@0 | 2506 | ParseNode *pn2, *pn3, *pn4; |
michael@0 | 2507 | int32_t low, high; |
michael@0 | 2508 | int noteIndex; |
michael@0 | 2509 | size_t switchSize; |
michael@0 | 2510 | jsbytecode *pc; |
michael@0 | 2511 | |
michael@0 | 2512 | /* Try for most optimal, fall back if not dense ints. */ |
michael@0 | 2513 | switchOp = JSOP_TABLESWITCH; |
michael@0 | 2514 | hasDefault = false; |
michael@0 | 2515 | defaultOffset = -1; |
michael@0 | 2516 | |
michael@0 | 2517 | pn2 = pn->pn_right; |
michael@0 | 2518 | JS_ASSERT(pn2->isKind(PNK_LEXICALSCOPE) || pn2->isKind(PNK_STATEMENTLIST)); |
michael@0 | 2519 | |
michael@0 | 2520 | /* Push the discriminant. */ |
michael@0 | 2521 | if (!EmitTree(cx, bce, pn->pn_left)) |
michael@0 | 2522 | return false; |
michael@0 | 2523 | |
michael@0 | 2524 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 2525 | if (pn2->isKind(PNK_LEXICALSCOPE)) { |
michael@0 | 2526 | if (!EnterBlockScope(cx, bce, &stmtInfo, pn2->pn_objbox, 0)) |
michael@0 | 2527 | return false; |
michael@0 | 2528 | |
michael@0 | 2529 | stmtInfo.type = STMT_SWITCH; |
michael@0 | 2530 | stmtInfo.update = top = bce->offset(); |
michael@0 | 2531 | /* Advance pn2 to refer to the switch case list. */ |
michael@0 | 2532 | pn2 = pn2->expr(); |
michael@0 | 2533 | } else { |
michael@0 | 2534 | JS_ASSERT(pn2->isKind(PNK_STATEMENTLIST)); |
michael@0 | 2535 | top = bce->offset(); |
michael@0 | 2536 | PushStatementBCE(bce, &stmtInfo, STMT_SWITCH, top); |
michael@0 | 2537 | } |
michael@0 | 2538 | |
michael@0 | 2539 | /* Switch bytecodes run from here till end of final case. */ |
michael@0 | 2540 | uint32_t caseCount = pn2->pn_count; |
michael@0 | 2541 | uint32_t tableLength = 0; |
michael@0 | 2542 | ScopedJSFreePtr<ParseNode*> table(nullptr); |
michael@0 | 2543 | |
michael@0 | 2544 | if (caseCount > JS_BIT(16)) { |
michael@0 | 2545 | bce->parser->tokenStream.reportError(JSMSG_TOO_MANY_CASES); |
michael@0 | 2546 | return false; |
michael@0 | 2547 | } |
michael@0 | 2548 | |
michael@0 | 2549 | if (caseCount == 0 || |
michael@0 | 2550 | (caseCount == 1 && |
michael@0 | 2551 | (hasDefault = (pn2->pn_head->isKind(PNK_DEFAULT))))) { |
michael@0 | 2552 | caseCount = 0; |
michael@0 | 2553 | low = 0; |
michael@0 | 2554 | high = -1; |
michael@0 | 2555 | } else { |
michael@0 | 2556 | bool ok = true; |
michael@0 | 2557 | #define INTMAP_LENGTH 256 |
michael@0 | 2558 | jsbitmap intmap_space[INTMAP_LENGTH]; |
michael@0 | 2559 | jsbitmap *intmap = nullptr; |
michael@0 | 2560 | int32_t intmap_bitlen = 0; |
michael@0 | 2561 | |
michael@0 | 2562 | low = JSVAL_INT_MAX; |
michael@0 | 2563 | high = JSVAL_INT_MIN; |
michael@0 | 2564 | |
michael@0 | 2565 | for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { |
michael@0 | 2566 | if (pn3->isKind(PNK_DEFAULT)) { |
michael@0 | 2567 | hasDefault = true; |
michael@0 | 2568 | caseCount--; /* one of the "cases" was the default */ |
michael@0 | 2569 | continue; |
michael@0 | 2570 | } |
michael@0 | 2571 | |
michael@0 | 2572 | JS_ASSERT(pn3->isKind(PNK_CASE)); |
michael@0 | 2573 | if (switchOp == JSOP_CONDSWITCH) |
michael@0 | 2574 | continue; |
michael@0 | 2575 | |
michael@0 | 2576 | JS_ASSERT(switchOp == JSOP_TABLESWITCH); |
michael@0 | 2577 | |
michael@0 | 2578 | pn4 = pn3->pn_left; |
michael@0 | 2579 | |
michael@0 | 2580 | if (pn4->getKind() != PNK_NUMBER) { |
michael@0 | 2581 | switchOp = JSOP_CONDSWITCH; |
michael@0 | 2582 | continue; |
michael@0 | 2583 | } |
michael@0 | 2584 | |
michael@0 | 2585 | int32_t i; |
michael@0 | 2586 | if (!NumberIsInt32(pn4->pn_dval, &i)) { |
michael@0 | 2587 | switchOp = JSOP_CONDSWITCH; |
michael@0 | 2588 | continue; |
michael@0 | 2589 | } |
michael@0 | 2590 | |
michael@0 | 2591 | if ((unsigned)(i + (int)JS_BIT(15)) >= (unsigned)JS_BIT(16)) { |
michael@0 | 2592 | switchOp = JSOP_CONDSWITCH; |
michael@0 | 2593 | continue; |
michael@0 | 2594 | } |
michael@0 | 2595 | if (i < low) |
michael@0 | 2596 | low = i; |
michael@0 | 2597 | if (high < i) |
michael@0 | 2598 | high = i; |
michael@0 | 2599 | |
michael@0 | 2600 | /* |
michael@0 | 2601 | * Check for duplicates, which require a JSOP_CONDSWITCH. |
michael@0 | 2602 | * We bias i by 65536 if it's negative, and hope that's a rare |
michael@0 | 2603 | * case (because it requires a malloc'd bitmap). |
michael@0 | 2604 | */ |
michael@0 | 2605 | if (i < 0) |
michael@0 | 2606 | i += JS_BIT(16); |
michael@0 | 2607 | if (i >= intmap_bitlen) { |
michael@0 | 2608 | if (!intmap && |
michael@0 | 2609 | size_t(i) < (INTMAP_LENGTH * JS_BITMAP_NBITS)) { |
michael@0 | 2610 | intmap = intmap_space; |
michael@0 | 2611 | intmap_bitlen = INTMAP_LENGTH * JS_BITMAP_NBITS; |
michael@0 | 2612 | } else { |
michael@0 | 2613 | /* Just grab 8K for the worst-case bitmap. */ |
michael@0 | 2614 | intmap_bitlen = JS_BIT(16); |
michael@0 | 2615 | intmap = cx->pod_malloc<jsbitmap>(JS_BIT(16) / JS_BITMAP_NBITS); |
michael@0 | 2616 | if (!intmap) { |
michael@0 | 2617 | js_ReportOutOfMemory(cx); |
michael@0 | 2618 | return false; |
michael@0 | 2619 | } |
michael@0 | 2620 | } |
michael@0 | 2621 | memset(intmap, 0, size_t(intmap_bitlen) / CHAR_BIT); |
michael@0 | 2622 | } |
michael@0 | 2623 | if (JS_TEST_BIT(intmap, i)) { |
michael@0 | 2624 | switchOp = JSOP_CONDSWITCH; |
michael@0 | 2625 | continue; |
michael@0 | 2626 | } |
michael@0 | 2627 | JS_SET_BIT(intmap, i); |
michael@0 | 2628 | } |
michael@0 | 2629 | |
michael@0 | 2630 | if (intmap && intmap != intmap_space) |
michael@0 | 2631 | js_free(intmap); |
michael@0 | 2632 | if (!ok) |
michael@0 | 2633 | return false; |
michael@0 | 2634 | |
michael@0 | 2635 | /* |
michael@0 | 2636 | * Compute table length and select condswitch instead if overlarge or |
michael@0 | 2637 | * more than half-sparse. |
michael@0 | 2638 | */ |
michael@0 | 2639 | if (switchOp == JSOP_TABLESWITCH) { |
michael@0 | 2640 | tableLength = (uint32_t)(high - low + 1); |
michael@0 | 2641 | if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount) |
michael@0 | 2642 | switchOp = JSOP_CONDSWITCH; |
michael@0 | 2643 | } |
michael@0 | 2644 | } |
michael@0 | 2645 | |
michael@0 | 2646 | /* |
michael@0 | 2647 | * The note has one or two offsets: first tells total switch code length; |
michael@0 | 2648 | * second (if condswitch) tells offset to first JSOP_CASE. |
michael@0 | 2649 | */ |
michael@0 | 2650 | if (switchOp == JSOP_CONDSWITCH) { |
michael@0 | 2651 | /* 0 bytes of immediate for unoptimized switch. */ |
michael@0 | 2652 | switchSize = 0; |
michael@0 | 2653 | noteIndex = NewSrcNote3(cx, bce, SRC_CONDSWITCH, 0, 0); |
michael@0 | 2654 | } else { |
michael@0 | 2655 | JS_ASSERT(switchOp == JSOP_TABLESWITCH); |
michael@0 | 2656 | |
michael@0 | 2657 | /* 3 offsets (len, low, high) before the table, 1 per entry. */ |
michael@0 | 2658 | switchSize = (size_t)(JUMP_OFFSET_LEN * (3 + tableLength)); |
michael@0 | 2659 | noteIndex = NewSrcNote2(cx, bce, SRC_TABLESWITCH, 0); |
michael@0 | 2660 | } |
michael@0 | 2661 | if (noteIndex < 0) |
michael@0 | 2662 | return false; |
michael@0 | 2663 | |
michael@0 | 2664 | /* Emit switchOp followed by switchSize bytes of jump or lookup table. */ |
michael@0 | 2665 | if (EmitN(cx, bce, switchOp, switchSize) < 0) |
michael@0 | 2666 | return false; |
michael@0 | 2667 | |
michael@0 | 2668 | off = -1; |
michael@0 | 2669 | if (switchOp == JSOP_CONDSWITCH) { |
michael@0 | 2670 | int caseNoteIndex = -1; |
michael@0 | 2671 | bool beforeCases = true; |
michael@0 | 2672 | |
michael@0 | 2673 | /* Emit code for evaluating cases and jumping to case statements. */ |
michael@0 | 2674 | for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { |
michael@0 | 2675 | pn4 = pn3->pn_left; |
michael@0 | 2676 | if (pn4 && !EmitTree(cx, bce, pn4)) |
michael@0 | 2677 | return false; |
michael@0 | 2678 | if (caseNoteIndex >= 0) { |
michael@0 | 2679 | /* off is the previous JSOP_CASE's bytecode offset. */ |
michael@0 | 2680 | if (!SetSrcNoteOffset(cx, bce, (unsigned)caseNoteIndex, 0, bce->offset() - off)) |
michael@0 | 2681 | return false; |
michael@0 | 2682 | } |
michael@0 | 2683 | if (!pn4) { |
michael@0 | 2684 | JS_ASSERT(pn3->isKind(PNK_DEFAULT)); |
michael@0 | 2685 | continue; |
michael@0 | 2686 | } |
michael@0 | 2687 | caseNoteIndex = NewSrcNote2(cx, bce, SRC_NEXTCASE, 0); |
michael@0 | 2688 | if (caseNoteIndex < 0) |
michael@0 | 2689 | return false; |
michael@0 | 2690 | off = EmitJump(cx, bce, JSOP_CASE, 0); |
michael@0 | 2691 | if (off < 0) |
michael@0 | 2692 | return false; |
michael@0 | 2693 | pn3->pn_offset = off; |
michael@0 | 2694 | if (beforeCases) { |
michael@0 | 2695 | unsigned noteCount, noteCountDelta; |
michael@0 | 2696 | |
michael@0 | 2697 | /* Switch note's second offset is to first JSOP_CASE. */ |
michael@0 | 2698 | noteCount = bce->notes().length(); |
michael@0 | 2699 | if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 1, off - top)) |
michael@0 | 2700 | return false; |
michael@0 | 2701 | noteCountDelta = bce->notes().length() - noteCount; |
michael@0 | 2702 | if (noteCountDelta != 0) |
michael@0 | 2703 | caseNoteIndex += noteCountDelta; |
michael@0 | 2704 | beforeCases = false; |
michael@0 | 2705 | } |
michael@0 | 2706 | } |
michael@0 | 2707 | |
michael@0 | 2708 | /* |
michael@0 | 2709 | * If we didn't have an explicit default (which could fall in between |
michael@0 | 2710 | * cases, preventing us from fusing this SetSrcNoteOffset with the call |
michael@0 | 2711 | * in the loop above), link the last case to the implicit default for |
michael@0 | 2712 | * the benefit of IonBuilder. |
michael@0 | 2713 | */ |
michael@0 | 2714 | if (!hasDefault && |
michael@0 | 2715 | caseNoteIndex >= 0 && |
michael@0 | 2716 | !SetSrcNoteOffset(cx, bce, (unsigned)caseNoteIndex, 0, bce->offset() - off)) |
michael@0 | 2717 | { |
michael@0 | 2718 | return false; |
michael@0 | 2719 | } |
michael@0 | 2720 | |
michael@0 | 2721 | /* Emit default even if no explicit default statement. */ |
michael@0 | 2722 | defaultOffset = EmitJump(cx, bce, JSOP_DEFAULT, 0); |
michael@0 | 2723 | if (defaultOffset < 0) |
michael@0 | 2724 | return false; |
michael@0 | 2725 | } else { |
michael@0 | 2726 | JS_ASSERT(switchOp == JSOP_TABLESWITCH); |
michael@0 | 2727 | pc = bce->code(top + JUMP_OFFSET_LEN); |
michael@0 | 2728 | |
michael@0 | 2729 | /* Fill in switch bounds, which we know fit in 16-bit offsets. */ |
michael@0 | 2730 | SET_JUMP_OFFSET(pc, low); |
michael@0 | 2731 | pc += JUMP_OFFSET_LEN; |
michael@0 | 2732 | SET_JUMP_OFFSET(pc, high); |
michael@0 | 2733 | pc += JUMP_OFFSET_LEN; |
michael@0 | 2734 | |
michael@0 | 2735 | /* |
michael@0 | 2736 | * Use malloc to avoid arena bloat for programs with many switches. |
michael@0 | 2737 | * ScopedJSFreePtr takes care of freeing it on exit. |
michael@0 | 2738 | */ |
michael@0 | 2739 | if (tableLength != 0) { |
michael@0 | 2740 | table = cx->pod_calloc<ParseNode*>(tableLength); |
michael@0 | 2741 | if (!table) |
michael@0 | 2742 | return false; |
michael@0 | 2743 | for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { |
michael@0 | 2744 | if (pn3->isKind(PNK_DEFAULT)) |
michael@0 | 2745 | continue; |
michael@0 | 2746 | |
michael@0 | 2747 | JS_ASSERT(pn3->isKind(PNK_CASE)); |
michael@0 | 2748 | |
michael@0 | 2749 | pn4 = pn3->pn_left; |
michael@0 | 2750 | JS_ASSERT(pn4->getKind() == PNK_NUMBER); |
michael@0 | 2751 | |
michael@0 | 2752 | int32_t i = int32_t(pn4->pn_dval); |
michael@0 | 2753 | JS_ASSERT(double(i) == pn4->pn_dval); |
michael@0 | 2754 | |
michael@0 | 2755 | i -= low; |
michael@0 | 2756 | JS_ASSERT(uint32_t(i) < tableLength); |
michael@0 | 2757 | table[i] = pn3; |
michael@0 | 2758 | } |
michael@0 | 2759 | } |
michael@0 | 2760 | } |
michael@0 | 2761 | |
michael@0 | 2762 | /* Emit code for each case's statements, copying pn_offset up to pn3. */ |
michael@0 | 2763 | for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { |
michael@0 | 2764 | if (switchOp == JSOP_CONDSWITCH && !pn3->isKind(PNK_DEFAULT)) |
michael@0 | 2765 | SetJumpOffsetAt(bce, pn3->pn_offset); |
michael@0 | 2766 | pn4 = pn3->pn_right; |
michael@0 | 2767 | if (!EmitTree(cx, bce, pn4)) |
michael@0 | 2768 | return false; |
michael@0 | 2769 | pn3->pn_offset = pn4->pn_offset; |
michael@0 | 2770 | if (pn3->isKind(PNK_DEFAULT)) |
michael@0 | 2771 | off = pn3->pn_offset - top; |
michael@0 | 2772 | } |
michael@0 | 2773 | |
michael@0 | 2774 | if (!hasDefault) { |
michael@0 | 2775 | /* If no default case, offset for default is to end of switch. */ |
michael@0 | 2776 | off = bce->offset() - top; |
michael@0 | 2777 | } |
michael@0 | 2778 | |
michael@0 | 2779 | /* We better have set "off" by now. */ |
michael@0 | 2780 | JS_ASSERT(off != -1); |
michael@0 | 2781 | |
michael@0 | 2782 | /* Set the default offset (to end of switch if no default). */ |
michael@0 | 2783 | if (switchOp == JSOP_CONDSWITCH) { |
michael@0 | 2784 | pc = nullptr; |
michael@0 | 2785 | JS_ASSERT(defaultOffset != -1); |
michael@0 | 2786 | SET_JUMP_OFFSET(bce->code(defaultOffset), off - (defaultOffset - top)); |
michael@0 | 2787 | } else { |
michael@0 | 2788 | pc = bce->code(top); |
michael@0 | 2789 | SET_JUMP_OFFSET(pc, off); |
michael@0 | 2790 | pc += JUMP_OFFSET_LEN; |
michael@0 | 2791 | } |
michael@0 | 2792 | |
michael@0 | 2793 | /* Set the SRC_SWITCH note's offset operand to tell end of switch. */ |
michael@0 | 2794 | off = bce->offset() - top; |
michael@0 | 2795 | if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, off)) |
michael@0 | 2796 | return false; |
michael@0 | 2797 | |
michael@0 | 2798 | if (switchOp == JSOP_TABLESWITCH) { |
michael@0 | 2799 | /* Skip over the already-initialized switch bounds. */ |
michael@0 | 2800 | pc += 2 * JUMP_OFFSET_LEN; |
michael@0 | 2801 | |
michael@0 | 2802 | /* Fill in the jump table, if there is one. */ |
michael@0 | 2803 | for (uint32_t i = 0; i < tableLength; i++) { |
michael@0 | 2804 | pn3 = table[i]; |
michael@0 | 2805 | off = pn3 ? pn3->pn_offset - top : 0; |
michael@0 | 2806 | SET_JUMP_OFFSET(pc, off); |
michael@0 | 2807 | pc += JUMP_OFFSET_LEN; |
michael@0 | 2808 | } |
michael@0 | 2809 | } |
michael@0 | 2810 | |
michael@0 | 2811 | if (pn->pn_right->isKind(PNK_LEXICALSCOPE)) { |
michael@0 | 2812 | if (!LeaveNestedScope(cx, bce, &stmtInfo)) |
michael@0 | 2813 | return false; |
michael@0 | 2814 | } else { |
michael@0 | 2815 | if (!PopStatementBCE(cx, bce)) |
michael@0 | 2816 | return false; |
michael@0 | 2817 | } |
michael@0 | 2818 | |
michael@0 | 2819 | return true; |
michael@0 | 2820 | } |
michael@0 | 2821 | |
michael@0 | 2822 | bool |
michael@0 | 2823 | BytecodeEmitter::isRunOnceLambda() |
michael@0 | 2824 | { |
michael@0 | 2825 | // The run once lambda flags set by the parser are approximate, and we look |
michael@0 | 2826 | // at properties of the function itself before deciding to emit a function |
michael@0 | 2827 | // as a run once lambda. |
michael@0 | 2828 | |
michael@0 | 2829 | if (!(parent && parent->emittingRunOnceLambda) && !lazyRunOnceLambda) |
michael@0 | 2830 | return false; |
michael@0 | 2831 | |
michael@0 | 2832 | FunctionBox *funbox = sc->asFunctionBox(); |
michael@0 | 2833 | return !funbox->argumentsHasLocalBinding() && |
michael@0 | 2834 | !funbox->isGenerator() && |
michael@0 | 2835 | !funbox->function()->name(); |
michael@0 | 2836 | } |
michael@0 | 2837 | |
michael@0 | 2838 | bool |
michael@0 | 2839 | frontend::EmitFunctionScript(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *body) |
michael@0 | 2840 | { |
michael@0 | 2841 | /* |
michael@0 | 2842 | * IonBuilder has assumptions about what may occur immediately after |
michael@0 | 2843 | * script->main (e.g., in the case of destructuring params). Thus, put the |
michael@0 | 2844 | * following ops into the range [script->code, script->main). Note: |
michael@0 | 2845 | * execution starts from script->code, so this has no semantic effect. |
michael@0 | 2846 | */ |
michael@0 | 2847 | |
michael@0 | 2848 | FunctionBox *funbox = bce->sc->asFunctionBox(); |
michael@0 | 2849 | if (funbox->argumentsHasLocalBinding()) { |
michael@0 | 2850 | JS_ASSERT(bce->offset() == 0); /* See JSScript::argumentsBytecode. */ |
michael@0 | 2851 | bce->switchToProlog(); |
michael@0 | 2852 | if (Emit1(cx, bce, JSOP_ARGUMENTS) < 0) |
michael@0 | 2853 | return false; |
michael@0 | 2854 | InternalBindingsHandle bindings(bce->script, &bce->script->bindings); |
michael@0 | 2855 | uint32_t varIndex = Bindings::argumentsVarIndex(cx, bindings); |
michael@0 | 2856 | if (bce->script->varIsAliased(varIndex)) { |
michael@0 | 2857 | ScopeCoordinate sc; |
michael@0 | 2858 | sc.setHops(0); |
michael@0 | 2859 | JS_ALWAYS_TRUE(LookupAliasedNameSlot(bce->script, cx->names().arguments, &sc)); |
michael@0 | 2860 | if (!EmitAliasedVarOp(cx, JSOP_SETALIASEDVAR, sc, bce)) |
michael@0 | 2861 | return false; |
michael@0 | 2862 | } else { |
michael@0 | 2863 | if (!EmitUnaliasedVarOp(cx, JSOP_SETLOCAL, varIndex, bce)) |
michael@0 | 2864 | return false; |
michael@0 | 2865 | } |
michael@0 | 2866 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 2867 | return false; |
michael@0 | 2868 | bce->switchToMain(); |
michael@0 | 2869 | } |
michael@0 | 2870 | |
michael@0 | 2871 | if (funbox->isGenerator()) { |
michael@0 | 2872 | bce->switchToProlog(); |
michael@0 | 2873 | if (Emit1(cx, bce, JSOP_GENERATOR) < 0) |
michael@0 | 2874 | return false; |
michael@0 | 2875 | bce->switchToMain(); |
michael@0 | 2876 | } |
michael@0 | 2877 | |
michael@0 | 2878 | /* |
michael@0 | 2879 | * Emit a prologue for run-once scripts which will deoptimize JIT code if |
michael@0 | 2880 | * the script ends up running multiple times via foo.caller related |
michael@0 | 2881 | * shenanigans. |
michael@0 | 2882 | */ |
michael@0 | 2883 | bool runOnce = bce->isRunOnceLambda(); |
michael@0 | 2884 | if (runOnce) { |
michael@0 | 2885 | bce->switchToProlog(); |
michael@0 | 2886 | if (Emit1(cx, bce, JSOP_RUNONCE) < 0) |
michael@0 | 2887 | return false; |
michael@0 | 2888 | bce->switchToMain(); |
michael@0 | 2889 | } |
michael@0 | 2890 | |
michael@0 | 2891 | if (!EmitTree(cx, bce, body)) |
michael@0 | 2892 | return false; |
michael@0 | 2893 | |
michael@0 | 2894 | // If we fall off the end of an ES6 generator, return a boxed iterator |
michael@0 | 2895 | // result object of the form { value: undefined, done: true }. |
michael@0 | 2896 | if (bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isStarGenerator()) { |
michael@0 | 2897 | if (!EmitPrepareIteratorResult(cx, bce)) |
michael@0 | 2898 | return false; |
michael@0 | 2899 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) |
michael@0 | 2900 | return false; |
michael@0 | 2901 | if (!EmitFinishIteratorResult(cx, bce, true)) |
michael@0 | 2902 | return false; |
michael@0 | 2903 | |
michael@0 | 2904 | // No need to check for finally blocks, etc as in EmitReturn. |
michael@0 | 2905 | if (Emit1(cx, bce, JSOP_RETURN) < 0) |
michael@0 | 2906 | return false; |
michael@0 | 2907 | } |
michael@0 | 2908 | |
michael@0 | 2909 | /* |
michael@0 | 2910 | * Always end the script with a JSOP_RETRVAL. Some other parts of the codebase |
michael@0 | 2911 | * depend on this opcode, e.g. js_InternalInterpret. |
michael@0 | 2912 | */ |
michael@0 | 2913 | if (Emit1(cx, bce, JSOP_RETRVAL) < 0) |
michael@0 | 2914 | return false; |
michael@0 | 2915 | |
michael@0 | 2916 | if (!JSScript::fullyInitFromEmitter(cx, bce->script, bce)) |
michael@0 | 2917 | return false; |
michael@0 | 2918 | |
michael@0 | 2919 | /* |
michael@0 | 2920 | * If this function is only expected to run once, mark the script so that |
michael@0 | 2921 | * initializers created within it may be given more precise types. |
michael@0 | 2922 | */ |
michael@0 | 2923 | if (runOnce) { |
michael@0 | 2924 | bce->script->setTreatAsRunOnce(); |
michael@0 | 2925 | JS_ASSERT(!bce->script->hasRunOnce()); |
michael@0 | 2926 | } |
michael@0 | 2927 | |
michael@0 | 2928 | /* Initialize fun->script() so that the debugger has a valid fun->script(). */ |
michael@0 | 2929 | RootedFunction fun(cx, bce->script->functionNonDelazifying()); |
michael@0 | 2930 | JS_ASSERT(fun->isInterpreted()); |
michael@0 | 2931 | |
michael@0 | 2932 | if (fun->isInterpretedLazy()) |
michael@0 | 2933 | fun->setUnlazifiedScript(bce->script); |
michael@0 | 2934 | else |
michael@0 | 2935 | fun->setScript(bce->script); |
michael@0 | 2936 | |
michael@0 | 2937 | bce->tellDebuggerAboutCompiledScript(cx); |
michael@0 | 2938 | |
michael@0 | 2939 | return true; |
michael@0 | 2940 | } |
michael@0 | 2941 | |
michael@0 | 2942 | static bool |
michael@0 | 2943 | MaybeEmitVarDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn, |
michael@0 | 2944 | jsatomid *result) |
michael@0 | 2945 | { |
michael@0 | 2946 | jsatomid atomIndex; |
michael@0 | 2947 | |
michael@0 | 2948 | if (!pn->pn_cookie.isFree()) { |
michael@0 | 2949 | atomIndex = pn->pn_cookie.slot(); |
michael@0 | 2950 | } else { |
michael@0 | 2951 | if (!bce->makeAtomIndex(pn->pn_atom, &atomIndex)) |
michael@0 | 2952 | return false; |
michael@0 | 2953 | } |
michael@0 | 2954 | |
michael@0 | 2955 | if (JOF_OPTYPE(pn->getOp()) == JOF_ATOM && |
michael@0 | 2956 | (!bce->sc->isFunctionBox() || bce->sc->asFunctionBox()->isHeavyweight())) |
michael@0 | 2957 | { |
michael@0 | 2958 | bce->switchToProlog(); |
michael@0 | 2959 | if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin)) |
michael@0 | 2960 | return false; |
michael@0 | 2961 | if (!EmitIndexOp(cx, prologOp, atomIndex, bce)) |
michael@0 | 2962 | return false; |
michael@0 | 2963 | bce->switchToMain(); |
michael@0 | 2964 | } |
michael@0 | 2965 | |
michael@0 | 2966 | if (result) |
michael@0 | 2967 | *result = atomIndex; |
michael@0 | 2968 | return true; |
michael@0 | 2969 | } |
michael@0 | 2970 | |
michael@0 | 2971 | /* |
michael@0 | 2972 | * This enum tells EmitVariables and the destructuring functions how emit the |
michael@0 | 2973 | * given Parser::variables parse tree. In the base case, DefineVars, the caller |
michael@0 | 2974 | * only wants variables to be defined in the prologue (if necessary). For |
michael@0 | 2975 | * PushInitialValues, variable initializer expressions are evaluated and left |
michael@0 | 2976 | * on the stack. For InitializeVars, the initializer expressions values are |
michael@0 | 2977 | * assigned (to local variables) and popped. |
michael@0 | 2978 | */ |
michael@0 | 2979 | enum VarEmitOption |
michael@0 | 2980 | { |
michael@0 | 2981 | DefineVars = 0, |
michael@0 | 2982 | PushInitialValues = 1, |
michael@0 | 2983 | InitializeVars = 2 |
michael@0 | 2984 | }; |
michael@0 | 2985 | |
michael@0 | 2986 | typedef bool |
michael@0 | 2987 | (*DestructuringDeclEmitter)(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn); |
michael@0 | 2988 | |
michael@0 | 2989 | static bool |
michael@0 | 2990 | EmitDestructuringDecl(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn) |
michael@0 | 2991 | { |
michael@0 | 2992 | JS_ASSERT(pn->isKind(PNK_NAME)); |
michael@0 | 2993 | if (!BindNameToSlot(cx, bce, pn)) |
michael@0 | 2994 | return false; |
michael@0 | 2995 | |
michael@0 | 2996 | JS_ASSERT(!pn->isOp(JSOP_CALLEE)); |
michael@0 | 2997 | return MaybeEmitVarDecl(cx, bce, prologOp, pn, nullptr); |
michael@0 | 2998 | } |
michael@0 | 2999 | |
michael@0 | 3000 | static bool |
michael@0 | 3001 | EmitDestructuringDecls(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, |
michael@0 | 3002 | ParseNode *pattern) |
michael@0 | 3003 | { |
michael@0 | 3004 | if (pattern->isKind(PNK_ARRAY)) { |
michael@0 | 3005 | for (ParseNode *element = pattern->pn_head; element; element = element->pn_next) { |
michael@0 | 3006 | if (element->isKind(PNK_ELISION)) |
michael@0 | 3007 | continue; |
michael@0 | 3008 | DestructuringDeclEmitter emitter = |
michael@0 | 3009 | element->isKind(PNK_NAME) ? EmitDestructuringDecl : EmitDestructuringDecls; |
michael@0 | 3010 | if (!emitter(cx, bce, prologOp, element)) |
michael@0 | 3011 | return false; |
michael@0 | 3012 | } |
michael@0 | 3013 | return true; |
michael@0 | 3014 | } |
michael@0 | 3015 | |
michael@0 | 3016 | MOZ_ASSERT(pattern->isKind(PNK_OBJECT)); |
michael@0 | 3017 | for (ParseNode *member = pattern->pn_head; member; member = member->pn_next) { |
michael@0 | 3018 | ParseNode *target = member->pn_right; |
michael@0 | 3019 | DestructuringDeclEmitter emitter = |
michael@0 | 3020 | target->isKind(PNK_NAME) ? EmitDestructuringDecl : EmitDestructuringDecls; |
michael@0 | 3021 | if (!emitter(cx, bce, prologOp, target)) |
michael@0 | 3022 | return false; |
michael@0 | 3023 | } |
michael@0 | 3024 | return true; |
michael@0 | 3025 | } |
michael@0 | 3026 | |
michael@0 | 3027 | static bool |
michael@0 | 3028 | EmitDestructuringOpsHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, |
michael@0 | 3029 | VarEmitOption emitOption); |
michael@0 | 3030 | |
michael@0 | 3031 | /* |
michael@0 | 3032 | * EmitDestructuringLHS assumes the to-be-destructured value has been pushed on |
michael@0 | 3033 | * the stack and emits code to destructure a single lhs expression (either a |
michael@0 | 3034 | * name or a compound []/{} expression). |
michael@0 | 3035 | * |
michael@0 | 3036 | * If emitOption is InitializeVars, the to-be-destructured value is assigned to |
michael@0 | 3037 | * locals and ultimately the initial slot is popped (-1 total depth change). |
michael@0 | 3038 | * |
michael@0 | 3039 | * If emitOption is PushInitialValues, the to-be-destructured value is replaced |
michael@0 | 3040 | * with the initial values of the N (where 0 <= N) variables assigned in the |
michael@0 | 3041 | * lhs expression. (Same post-condition as EmitDestructuringOpsHelper) |
michael@0 | 3042 | */ |
michael@0 | 3043 | static bool |
michael@0 | 3044 | EmitDestructuringLHS(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption) |
michael@0 | 3045 | { |
michael@0 | 3046 | JS_ASSERT(emitOption != DefineVars); |
michael@0 | 3047 | |
michael@0 | 3048 | // Now emit the lvalue opcode sequence. If the lvalue is a nested |
michael@0 | 3049 | // destructuring initialiser-form, call ourselves to handle it, then pop |
michael@0 | 3050 | // the matched value. Otherwise emit an lvalue bytecode sequence followed |
michael@0 | 3051 | // by an assignment op. |
michael@0 | 3052 | if (pn->isKind(PNK_ARRAY) || pn->isKind(PNK_OBJECT)) { |
michael@0 | 3053 | if (!EmitDestructuringOpsHelper(cx, bce, pn, emitOption)) |
michael@0 | 3054 | return false; |
michael@0 | 3055 | if (emitOption == InitializeVars) { |
michael@0 | 3056 | // Per its post-condition, EmitDestructuringOpsHelper has left the |
michael@0 | 3057 | // to-be-destructured value on top of the stack. |
michael@0 | 3058 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3059 | return false; |
michael@0 | 3060 | } |
michael@0 | 3061 | } else if (emitOption == PushInitialValues) { |
michael@0 | 3062 | // The lhs is a simple name so the to-be-destructured value is |
michael@0 | 3063 | // its initial value and there is nothing to do. |
michael@0 | 3064 | JS_ASSERT(pn->getOp() == JSOP_GETLOCAL); |
michael@0 | 3065 | JS_ASSERT(pn->pn_dflags & PND_BOUND); |
michael@0 | 3066 | } else { |
michael@0 | 3067 | switch (pn->getKind()) { |
michael@0 | 3068 | case PNK_NAME: |
michael@0 | 3069 | if (!BindNameToSlot(cx, bce, pn)) |
michael@0 | 3070 | return false; |
michael@0 | 3071 | |
michael@0 | 3072 | // Allow 'const [x,y] = o', make 'const x,y; [x,y] = o' a nop. |
michael@0 | 3073 | if (pn->isConst() && !pn->isDefn()) |
michael@0 | 3074 | return Emit1(cx, bce, JSOP_POP) >= 0; |
michael@0 | 3075 | |
michael@0 | 3076 | switch (pn->getOp()) { |
michael@0 | 3077 | case JSOP_SETNAME: |
michael@0 | 3078 | case JSOP_SETGNAME: |
michael@0 | 3079 | case JSOP_SETCONST: { |
michael@0 | 3080 | // This is like ordinary assignment, but with one difference. |
michael@0 | 3081 | // |
michael@0 | 3082 | // In `a = b`, we first determine a binding for `a` (using |
michael@0 | 3083 | // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, then |
michael@0 | 3084 | // a JSOP_SETNAME instruction. |
michael@0 | 3085 | // |
michael@0 | 3086 | // In `[a] = [b]`, per spec, `b` is evaluated first, then we |
michael@0 | 3087 | // determine a binding for `a`. Then we need to do assignment-- |
michael@0 | 3088 | // but the operands are on the stack in the wrong order for |
michael@0 | 3089 | // JSOP_SETPROP, so we have to add a JSOP_SWAP. |
michael@0 | 3090 | jsatomid atomIndex; |
michael@0 | 3091 | if (!bce->makeAtomIndex(pn->pn_atom, &atomIndex)) |
michael@0 | 3092 | return false; |
michael@0 | 3093 | |
michael@0 | 3094 | if (!pn->isOp(JSOP_SETCONST)) { |
michael@0 | 3095 | JSOp bindOp = pn->isOp(JSOP_SETNAME) ? JSOP_BINDNAME : JSOP_BINDGNAME; |
michael@0 | 3096 | if (!EmitIndex32(cx, bindOp, atomIndex, bce)) |
michael@0 | 3097 | return false; |
michael@0 | 3098 | if (Emit1(cx, bce, JSOP_SWAP) < 0) |
michael@0 | 3099 | return false; |
michael@0 | 3100 | } |
michael@0 | 3101 | |
michael@0 | 3102 | if (!EmitIndexOp(cx, pn->getOp(), atomIndex, bce)) |
michael@0 | 3103 | return false; |
michael@0 | 3104 | break; |
michael@0 | 3105 | } |
michael@0 | 3106 | |
michael@0 | 3107 | case JSOP_SETLOCAL: |
michael@0 | 3108 | case JSOP_SETARG: |
michael@0 | 3109 | if (!EmitVarOp(cx, pn, pn->getOp(), bce)) |
michael@0 | 3110 | return false; |
michael@0 | 3111 | break; |
michael@0 | 3112 | |
michael@0 | 3113 | default: |
michael@0 | 3114 | MOZ_ASSUME_UNREACHABLE("EmitDestructuringLHS: bad name op"); |
michael@0 | 3115 | } |
michael@0 | 3116 | break; |
michael@0 | 3117 | |
michael@0 | 3118 | case PNK_DOT: |
michael@0 | 3119 | // See the (PNK_NAME, JSOP_SETNAME) case above. |
michael@0 | 3120 | // |
michael@0 | 3121 | // In `a.x = b`, `a` is evaluated first, then `b`, then a |
michael@0 | 3122 | // JSOP_SETPROP instruction. |
michael@0 | 3123 | // |
michael@0 | 3124 | // In `[a.x] = [b]`, per spec, `b` is evaluated before `a`. Then we |
michael@0 | 3125 | // need a property set -- but the operands are on the stack in the |
michael@0 | 3126 | // wrong order for JSOP_SETPROP, so we have to add a JSOP_SWAP. |
michael@0 | 3127 | if (!EmitTree(cx, bce, pn->pn_expr)) |
michael@0 | 3128 | return false; |
michael@0 | 3129 | if (Emit1(cx, bce, JSOP_SWAP) < 0) |
michael@0 | 3130 | return false; |
michael@0 | 3131 | if (!EmitAtomOp(cx, pn, JSOP_SETPROP, bce)) |
michael@0 | 3132 | return false; |
michael@0 | 3133 | break; |
michael@0 | 3134 | |
michael@0 | 3135 | case PNK_ELEM: |
michael@0 | 3136 | // See the comment at `case PNK_DOT:` above. This case, |
michael@0 | 3137 | // `[a[x]] = [b]`, is handled much the same way. The JSOP_SWAP |
michael@0 | 3138 | // is emitted by EmitElemOperands. |
michael@0 | 3139 | if (!EmitElemOp(cx, pn, JSOP_SETELEM, bce)) |
michael@0 | 3140 | return false; |
michael@0 | 3141 | break; |
michael@0 | 3142 | |
michael@0 | 3143 | case PNK_CALL: |
michael@0 | 3144 | JS_ASSERT(pn->pn_xflags & PNX_SETCALL); |
michael@0 | 3145 | if (!EmitTree(cx, bce, pn)) |
michael@0 | 3146 | return false; |
michael@0 | 3147 | |
michael@0 | 3148 | // Pop the call return value. Below, we pop the RHS too, balancing |
michael@0 | 3149 | // the stack --- presumably for the benefit of bytecode |
michael@0 | 3150 | // analysis. (The interpreter will never reach these instructions |
michael@0 | 3151 | // since we just emitted JSOP_SETCALL, which always throws. It's |
michael@0 | 3152 | // possible no analyses actually depend on this either.) |
michael@0 | 3153 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3154 | return false; |
michael@0 | 3155 | break; |
michael@0 | 3156 | |
michael@0 | 3157 | default: |
michael@0 | 3158 | MOZ_ASSUME_UNREACHABLE("EmitDestructuringLHS: bad lhs kind"); |
michael@0 | 3159 | } |
michael@0 | 3160 | |
michael@0 | 3161 | // Pop the assigned value. |
michael@0 | 3162 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3163 | return false; |
michael@0 | 3164 | } |
michael@0 | 3165 | |
michael@0 | 3166 | return true; |
michael@0 | 3167 | } |
michael@0 | 3168 | |
michael@0 | 3169 | /* |
michael@0 | 3170 | * Recursive helper for EmitDestructuringOps. |
michael@0 | 3171 | * EmitDestructuringOpsHelper assumes the to-be-destructured value has been |
michael@0 | 3172 | * pushed on the stack and emits code to destructure each part of a [] or {} |
michael@0 | 3173 | * lhs expression. |
michael@0 | 3174 | * |
michael@0 | 3175 | * If emitOption is InitializeVars, the initial to-be-destructured value is |
michael@0 | 3176 | * left untouched on the stack and the overall depth is not changed. |
michael@0 | 3177 | * |
michael@0 | 3178 | * If emitOption is PushInitialValues, the to-be-destructured value is replaced |
michael@0 | 3179 | * with the initial values of the N (where 0 <= N) variables assigned in the |
michael@0 | 3180 | * lhs expression. (Same post-condition as EmitDestructuringLHS) |
michael@0 | 3181 | */ |
michael@0 | 3182 | static bool |
michael@0 | 3183 | EmitDestructuringOpsHelper(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, |
michael@0 | 3184 | VarEmitOption emitOption) |
michael@0 | 3185 | { |
michael@0 | 3186 | JS_ASSERT(emitOption != DefineVars); |
michael@0 | 3187 | |
michael@0 | 3188 | unsigned index; |
michael@0 | 3189 | ParseNode *pn2, *pn3; |
michael@0 | 3190 | bool doElemOp; |
michael@0 | 3191 | |
michael@0 | 3192 | #ifdef DEBUG |
michael@0 | 3193 | int stackDepth = bce->stackDepth; |
michael@0 | 3194 | JS_ASSERT(stackDepth != 0); |
michael@0 | 3195 | JS_ASSERT(pn->isArity(PN_LIST)); |
michael@0 | 3196 | JS_ASSERT(pn->isKind(PNK_ARRAY) || pn->isKind(PNK_OBJECT)); |
michael@0 | 3197 | #endif |
michael@0 | 3198 | |
michael@0 | 3199 | index = 0; |
michael@0 | 3200 | for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { |
michael@0 | 3201 | /* Duplicate the value being destructured to use as a reference base. */ |
michael@0 | 3202 | if (Emit1(cx, bce, JSOP_DUP) < 0) |
michael@0 | 3203 | return false; |
michael@0 | 3204 | |
michael@0 | 3205 | /* |
michael@0 | 3206 | * Now push the property name currently being matched, which is either |
michael@0 | 3207 | * the array initialiser's current index, or the current property name |
michael@0 | 3208 | * "label" on the left of a colon in the object initialiser. Set pn3 |
michael@0 | 3209 | * to the lvalue node, which is in the value-initializing position. |
michael@0 | 3210 | */ |
michael@0 | 3211 | doElemOp = true; |
michael@0 | 3212 | if (pn->isKind(PNK_ARRAY)) { |
michael@0 | 3213 | if (!EmitNumberOp(cx, index, bce)) |
michael@0 | 3214 | return false; |
michael@0 | 3215 | pn3 = pn2; |
michael@0 | 3216 | } else { |
michael@0 | 3217 | JS_ASSERT(pn->isKind(PNK_OBJECT)); |
michael@0 | 3218 | JS_ASSERT(pn2->isKind(PNK_COLON)); |
michael@0 | 3219 | |
michael@0 | 3220 | ParseNode *key = pn2->pn_left; |
michael@0 | 3221 | if (key->isKind(PNK_NUMBER)) { |
michael@0 | 3222 | if (!EmitNumberOp(cx, key->pn_dval, bce)) |
michael@0 | 3223 | return false; |
michael@0 | 3224 | } else { |
michael@0 | 3225 | MOZ_ASSERT(key->isKind(PNK_STRING) || key->isKind(PNK_NAME)); |
michael@0 | 3226 | PropertyName *name = key->pn_atom->asPropertyName(); |
michael@0 | 3227 | |
michael@0 | 3228 | // The parser already checked for atoms representing indexes and |
michael@0 | 3229 | // used PNK_NUMBER instead, but also watch for ids which TI treats |
michael@0 | 3230 | // as indexes for simplification of downstream analysis. |
michael@0 | 3231 | jsid id = NameToId(name); |
michael@0 | 3232 | if (id != types::IdToTypeId(id)) { |
michael@0 | 3233 | if (!EmitTree(cx, bce, key)) |
michael@0 | 3234 | return false; |
michael@0 | 3235 | } else { |
michael@0 | 3236 | if (!EmitAtomOp(cx, name, JSOP_GETPROP, bce)) |
michael@0 | 3237 | return false; |
michael@0 | 3238 | doElemOp = false; |
michael@0 | 3239 | } |
michael@0 | 3240 | } |
michael@0 | 3241 | |
michael@0 | 3242 | pn3 = pn2->pn_right; |
michael@0 | 3243 | } |
michael@0 | 3244 | |
michael@0 | 3245 | if (doElemOp) { |
michael@0 | 3246 | /* |
michael@0 | 3247 | * Ok, get the value of the matching property name. This leaves |
michael@0 | 3248 | * that value on top of the value being destructured, so the stack |
michael@0 | 3249 | * is one deeper than when we started. |
michael@0 | 3250 | */ |
michael@0 | 3251 | if (!EmitElemOpBase(cx, bce, JSOP_GETELEM)) |
michael@0 | 3252 | return false; |
michael@0 | 3253 | JS_ASSERT(bce->stackDepth >= stackDepth + 1); |
michael@0 | 3254 | } |
michael@0 | 3255 | |
michael@0 | 3256 | /* Elision node makes a hole in the array destructurer. */ |
michael@0 | 3257 | if (pn3->isKind(PNK_ELISION)) { |
michael@0 | 3258 | JS_ASSERT(pn->isKind(PNK_ARRAY)); |
michael@0 | 3259 | JS_ASSERT(pn2 == pn3); |
michael@0 | 3260 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3261 | return false; |
michael@0 | 3262 | } else { |
michael@0 | 3263 | int32_t depthBefore = bce->stackDepth; |
michael@0 | 3264 | if (!EmitDestructuringLHS(cx, bce, pn3, emitOption)) |
michael@0 | 3265 | return false; |
michael@0 | 3266 | |
michael@0 | 3267 | if (emitOption == PushInitialValues) { |
michael@0 | 3268 | /* |
michael@0 | 3269 | * After '[x,y]' in 'let ([[x,y], z] = o)', the stack is |
michael@0 | 3270 | * | to-be-destructured-value | x | y | |
michael@0 | 3271 | * The goal is: |
michael@0 | 3272 | * | x | y | z | |
michael@0 | 3273 | * so emit a pick to produce the intermediate state |
michael@0 | 3274 | * | x | y | to-be-destructured-value | |
michael@0 | 3275 | * before destructuring z. This gives the loop invariant that |
michael@0 | 3276 | * the to-be-destructured-value is always on top of the stack. |
michael@0 | 3277 | */ |
michael@0 | 3278 | JS_ASSERT((bce->stackDepth - bce->stackDepth) >= -1); |
michael@0 | 3279 | uint32_t pickDistance = (uint32_t)((bce->stackDepth + 1) - depthBefore); |
michael@0 | 3280 | if (pickDistance > 0) { |
michael@0 | 3281 | if (pickDistance > UINT8_MAX) { |
michael@0 | 3282 | bce->reportError(pn3, JSMSG_TOO_MANY_LOCALS); |
michael@0 | 3283 | return false; |
michael@0 | 3284 | } |
michael@0 | 3285 | if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)pickDistance) < 0) |
michael@0 | 3286 | return false; |
michael@0 | 3287 | } |
michael@0 | 3288 | } |
michael@0 | 3289 | } |
michael@0 | 3290 | |
michael@0 | 3291 | ++index; |
michael@0 | 3292 | } |
michael@0 | 3293 | |
michael@0 | 3294 | if (emitOption == PushInitialValues) { |
michael@0 | 3295 | /* |
michael@0 | 3296 | * Per the above loop invariant, to-be-destructured-value is at the top |
michael@0 | 3297 | * of the stack. To achieve the post-condition, pop it. |
michael@0 | 3298 | */ |
michael@0 | 3299 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3300 | return false; |
michael@0 | 3301 | } |
michael@0 | 3302 | |
michael@0 | 3303 | return true; |
michael@0 | 3304 | } |
michael@0 | 3305 | |
michael@0 | 3306 | static bool |
michael@0 | 3307 | EmitDestructuringOps(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool isLet = false) |
michael@0 | 3308 | { |
michael@0 | 3309 | /* |
michael@0 | 3310 | * Call our recursive helper to emit the destructuring assignments and |
michael@0 | 3311 | * related stack manipulations. |
michael@0 | 3312 | */ |
michael@0 | 3313 | VarEmitOption emitOption = isLet ? PushInitialValues : InitializeVars; |
michael@0 | 3314 | return EmitDestructuringOpsHelper(cx, bce, pn, emitOption); |
michael@0 | 3315 | } |
michael@0 | 3316 | |
michael@0 | 3317 | static bool |
michael@0 | 3318 | EmitGroupAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, |
michael@0 | 3319 | ParseNode *lhs, ParseNode *rhs) |
michael@0 | 3320 | { |
michael@0 | 3321 | uint32_t depth, limit, i, nslots; |
michael@0 | 3322 | ParseNode *pn; |
michael@0 | 3323 | |
michael@0 | 3324 | depth = limit = (uint32_t) bce->stackDepth; |
michael@0 | 3325 | for (pn = rhs->pn_head; pn; pn = pn->pn_next) { |
michael@0 | 3326 | if (limit == JS_BIT(16)) { |
michael@0 | 3327 | bce->reportError(rhs, JSMSG_ARRAY_INIT_TOO_BIG); |
michael@0 | 3328 | return false; |
michael@0 | 3329 | } |
michael@0 | 3330 | |
michael@0 | 3331 | /* MaybeEmitGroupAssignment won't call us if rhs is holey. */ |
michael@0 | 3332 | JS_ASSERT(!pn->isKind(PNK_ELISION)); |
michael@0 | 3333 | if (!EmitTree(cx, bce, pn)) |
michael@0 | 3334 | return false; |
michael@0 | 3335 | ++limit; |
michael@0 | 3336 | } |
michael@0 | 3337 | |
michael@0 | 3338 | i = depth; |
michael@0 | 3339 | for (pn = lhs->pn_head; pn; pn = pn->pn_next, ++i) { |
michael@0 | 3340 | /* MaybeEmitGroupAssignment requires lhs->pn_count <= rhs->pn_count. */ |
michael@0 | 3341 | JS_ASSERT(i < limit); |
michael@0 | 3342 | |
michael@0 | 3343 | if (!EmitDupAt(cx, bce, i)) |
michael@0 | 3344 | return false; |
michael@0 | 3345 | |
michael@0 | 3346 | if (pn->isKind(PNK_ELISION)) { |
michael@0 | 3347 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3348 | return false; |
michael@0 | 3349 | } else { |
michael@0 | 3350 | if (!EmitDestructuringLHS(cx, bce, pn, InitializeVars)) |
michael@0 | 3351 | return false; |
michael@0 | 3352 | } |
michael@0 | 3353 | } |
michael@0 | 3354 | |
michael@0 | 3355 | nslots = limit - depth; |
michael@0 | 3356 | EMIT_UINT16_IMM_OP(JSOP_POPN, nslots); |
michael@0 | 3357 | bce->stackDepth = (uint32_t) depth; |
michael@0 | 3358 | return true; |
michael@0 | 3359 | } |
michael@0 | 3360 | |
michael@0 | 3361 | enum GroupOption { GroupIsDecl, GroupIsNotDecl }; |
michael@0 | 3362 | |
michael@0 | 3363 | /* |
michael@0 | 3364 | * Helper called with pop out param initialized to a JSOP_POP* opcode. If we |
michael@0 | 3365 | * can emit a group assignment sequence, which results in 0 stack depth delta, |
michael@0 | 3366 | * we set *pop to JSOP_NOP so callers can veto emitting pn followed by a pop. |
michael@0 | 3367 | */ |
michael@0 | 3368 | static bool |
michael@0 | 3369 | MaybeEmitGroupAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, JSOp prologOp, ParseNode *pn, |
michael@0 | 3370 | GroupOption groupOption, JSOp *pop) |
michael@0 | 3371 | { |
michael@0 | 3372 | JS_ASSERT(pn->isKind(PNK_ASSIGN)); |
michael@0 | 3373 | JS_ASSERT(pn->isOp(JSOP_NOP)); |
michael@0 | 3374 | JS_ASSERT(*pop == JSOP_POP || *pop == JSOP_SETRVAL); |
michael@0 | 3375 | |
michael@0 | 3376 | ParseNode *lhs = pn->pn_left; |
michael@0 | 3377 | ParseNode *rhs = pn->pn_right; |
michael@0 | 3378 | if (lhs->isKind(PNK_ARRAY) && rhs->isKind(PNK_ARRAY) && |
michael@0 | 3379 | !(rhs->pn_xflags & PNX_SPECIALARRAYINIT) && |
michael@0 | 3380 | lhs->pn_count <= rhs->pn_count) |
michael@0 | 3381 | { |
michael@0 | 3382 | if (groupOption == GroupIsDecl && !EmitDestructuringDecls(cx, bce, prologOp, lhs)) |
michael@0 | 3383 | return false; |
michael@0 | 3384 | if (!EmitGroupAssignment(cx, bce, prologOp, lhs, rhs)) |
michael@0 | 3385 | return false; |
michael@0 | 3386 | *pop = JSOP_NOP; |
michael@0 | 3387 | } |
michael@0 | 3388 | return true; |
michael@0 | 3389 | } |
michael@0 | 3390 | |
michael@0 | 3391 | /* |
michael@0 | 3392 | * Like MaybeEmitGroupAssignment, but for 'let ([x,y] = [a,b]) ...'. |
michael@0 | 3393 | * |
michael@0 | 3394 | * Instead of issuing a sequence |dup|eval-rhs|set-lhs|pop| (which doesn't work |
michael@0 | 3395 | * since the bound vars don't yet have slots), just eval/push each rhs element |
michael@0 | 3396 | * just like what EmitLet would do for 'let (x = a, y = b) ...'. While shorter, |
michael@0 | 3397 | * simpler and more efficient than MaybeEmitGroupAssignment, it is harder to |
michael@0 | 3398 | * decompile so we restrict the ourselves to cases where the lhs and rhs are in |
michael@0 | 3399 | * 1:1 correspondence and lhs elements are simple names. |
michael@0 | 3400 | */ |
michael@0 | 3401 | static bool |
michael@0 | 3402 | MaybeEmitLetGroupDecl(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, JSOp *pop) |
michael@0 | 3403 | { |
michael@0 | 3404 | JS_ASSERT(pn->isKind(PNK_ASSIGN)); |
michael@0 | 3405 | JS_ASSERT(pn->isOp(JSOP_NOP)); |
michael@0 | 3406 | JS_ASSERT(*pop == JSOP_POP || *pop == JSOP_SETRVAL); |
michael@0 | 3407 | |
michael@0 | 3408 | ParseNode *lhs = pn->pn_left; |
michael@0 | 3409 | ParseNode *rhs = pn->pn_right; |
michael@0 | 3410 | if (lhs->isKind(PNK_ARRAY) && rhs->isKind(PNK_ARRAY) && |
michael@0 | 3411 | !(rhs->pn_xflags & PNX_SPECIALARRAYINIT) && |
michael@0 | 3412 | !(lhs->pn_xflags & PNX_SPECIALARRAYINIT) && |
michael@0 | 3413 | lhs->pn_count == rhs->pn_count) |
michael@0 | 3414 | { |
michael@0 | 3415 | for (ParseNode *l = lhs->pn_head; l; l = l->pn_next) { |
michael@0 | 3416 | if (l->getOp() != JSOP_SETLOCAL) |
michael@0 | 3417 | return true; |
michael@0 | 3418 | } |
michael@0 | 3419 | |
michael@0 | 3420 | for (ParseNode *r = rhs->pn_head; r; r = r->pn_next) { |
michael@0 | 3421 | if (!EmitTree(cx, bce, r)) |
michael@0 | 3422 | return false; |
michael@0 | 3423 | } |
michael@0 | 3424 | |
michael@0 | 3425 | *pop = JSOP_NOP; |
michael@0 | 3426 | } |
michael@0 | 3427 | return true; |
michael@0 | 3428 | } |
michael@0 | 3429 | |
michael@0 | 3430 | static bool |
michael@0 | 3431 | EmitVariables(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, VarEmitOption emitOption, |
michael@0 | 3432 | bool isLet = false) |
michael@0 | 3433 | { |
michael@0 | 3434 | JS_ASSERT(pn->isArity(PN_LIST)); |
michael@0 | 3435 | JS_ASSERT(isLet == (emitOption == PushInitialValues)); |
michael@0 | 3436 | |
michael@0 | 3437 | ParseNode *next; |
michael@0 | 3438 | for (ParseNode *pn2 = pn->pn_head; ; pn2 = next) { |
michael@0 | 3439 | if (!UpdateSourceCoordNotes(cx, bce, pn2->pn_pos.begin)) |
michael@0 | 3440 | return false; |
michael@0 | 3441 | next = pn2->pn_next; |
michael@0 | 3442 | |
michael@0 | 3443 | ParseNode *pn3; |
michael@0 | 3444 | if (!pn2->isKind(PNK_NAME)) { |
michael@0 | 3445 | if (pn2->isKind(PNK_ARRAY) || pn2->isKind(PNK_OBJECT)) { |
michael@0 | 3446 | /* |
michael@0 | 3447 | * Emit variable binding ops, but not destructuring ops. The |
michael@0 | 3448 | * parser (see Parser::variables) has ensured that our caller |
michael@0 | 3449 | * will be the PNK_FOR/PNK_FORIN/PNK_FOROF case in EmitTree, and |
michael@0 | 3450 | * that case will emit the destructuring code only after |
michael@0 | 3451 | * emitting an enumerating opcode and a branch that tests |
michael@0 | 3452 | * whether the enumeration ended. |
michael@0 | 3453 | */ |
michael@0 | 3454 | JS_ASSERT(emitOption == DefineVars); |
michael@0 | 3455 | JS_ASSERT(pn->pn_count == 1); |
michael@0 | 3456 | if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn2)) |
michael@0 | 3457 | return false; |
michael@0 | 3458 | break; |
michael@0 | 3459 | } |
michael@0 | 3460 | |
michael@0 | 3461 | /* |
michael@0 | 3462 | * A destructuring initialiser assignment preceded by var will |
michael@0 | 3463 | * never occur to the left of 'in' in a for-in loop. As with 'for |
michael@0 | 3464 | * (var x = i in o)...', this will cause the entire 'var [a, b] = |
michael@0 | 3465 | * i' to be hoisted out of the loop. |
michael@0 | 3466 | */ |
michael@0 | 3467 | JS_ASSERT(pn2->isKind(PNK_ASSIGN)); |
michael@0 | 3468 | JS_ASSERT(pn2->isOp(JSOP_NOP)); |
michael@0 | 3469 | JS_ASSERT(emitOption != DefineVars); |
michael@0 | 3470 | |
michael@0 | 3471 | /* |
michael@0 | 3472 | * To allow the front end to rewrite var f = x; as f = x; when a |
michael@0 | 3473 | * function f(){} precedes the var, detect simple name assignment |
michael@0 | 3474 | * here and initialize the name. |
michael@0 | 3475 | */ |
michael@0 | 3476 | if (pn2->pn_left->isKind(PNK_NAME)) { |
michael@0 | 3477 | pn3 = pn2->pn_right; |
michael@0 | 3478 | pn2 = pn2->pn_left; |
michael@0 | 3479 | goto do_name; |
michael@0 | 3480 | } |
michael@0 | 3481 | |
michael@0 | 3482 | JSOp op = JSOP_POP; |
michael@0 | 3483 | if (pn->pn_count == 1) { |
michael@0 | 3484 | /* |
michael@0 | 3485 | * If this is the only destructuring assignment in the list, |
michael@0 | 3486 | * try to optimize to a group assignment. If we're in a let |
michael@0 | 3487 | * head, pass JSOP_POP rather than the pseudo-prolog JSOP_NOP |
michael@0 | 3488 | * in pn->pn_op, to suppress a second (and misplaced) 'let'. |
michael@0 | 3489 | */ |
michael@0 | 3490 | JS_ASSERT(!pn2->pn_next); |
michael@0 | 3491 | if (isLet) { |
michael@0 | 3492 | if (!MaybeEmitLetGroupDecl(cx, bce, pn2, &op)) |
michael@0 | 3493 | return false; |
michael@0 | 3494 | } else { |
michael@0 | 3495 | if (!MaybeEmitGroupAssignment(cx, bce, pn->getOp(), pn2, GroupIsDecl, &op)) |
michael@0 | 3496 | return false; |
michael@0 | 3497 | } |
michael@0 | 3498 | } |
michael@0 | 3499 | if (op == JSOP_NOP) { |
michael@0 | 3500 | pn->pn_xflags = (pn->pn_xflags & ~PNX_POPVAR) | PNX_GROUPINIT; |
michael@0 | 3501 | } else { |
michael@0 | 3502 | pn3 = pn2->pn_left; |
michael@0 | 3503 | if (!EmitDestructuringDecls(cx, bce, pn->getOp(), pn3)) |
michael@0 | 3504 | return false; |
michael@0 | 3505 | |
michael@0 | 3506 | if (!EmitTree(cx, bce, pn2->pn_right)) |
michael@0 | 3507 | return false; |
michael@0 | 3508 | |
michael@0 | 3509 | if (!EmitDestructuringOps(cx, bce, pn3, isLet)) |
michael@0 | 3510 | return false; |
michael@0 | 3511 | } |
michael@0 | 3512 | |
michael@0 | 3513 | /* If we are not initializing, nothing to pop. */ |
michael@0 | 3514 | if (emitOption != InitializeVars) { |
michael@0 | 3515 | if (next) |
michael@0 | 3516 | continue; |
michael@0 | 3517 | break; |
michael@0 | 3518 | } |
michael@0 | 3519 | goto emit_note_pop; |
michael@0 | 3520 | } |
michael@0 | 3521 | |
michael@0 | 3522 | /* |
michael@0 | 3523 | * Load initializer early to share code above that jumps to do_name. |
michael@0 | 3524 | * NB: if this var redeclares an existing binding, then pn2 is linked |
michael@0 | 3525 | * on its definition's use-chain and pn_expr has been overlayed with |
michael@0 | 3526 | * pn_lexdef. |
michael@0 | 3527 | */ |
michael@0 | 3528 | pn3 = pn2->maybeExpr(); |
michael@0 | 3529 | |
michael@0 | 3530 | do_name: |
michael@0 | 3531 | if (!BindNameToSlot(cx, bce, pn2)) |
michael@0 | 3532 | return false; |
michael@0 | 3533 | |
michael@0 | 3534 | |
michael@0 | 3535 | JSOp op; |
michael@0 | 3536 | op = pn2->getOp(); |
michael@0 | 3537 | JS_ASSERT(op != JSOP_CALLEE); |
michael@0 | 3538 | JS_ASSERT(!pn2->pn_cookie.isFree() || !pn->isOp(JSOP_NOP)); |
michael@0 | 3539 | |
michael@0 | 3540 | jsatomid atomIndex; |
michael@0 | 3541 | if (!MaybeEmitVarDecl(cx, bce, pn->getOp(), pn2, &atomIndex)) |
michael@0 | 3542 | return false; |
michael@0 | 3543 | |
michael@0 | 3544 | if (pn3) { |
michael@0 | 3545 | JS_ASSERT(emitOption != DefineVars); |
michael@0 | 3546 | if (op == JSOP_SETNAME || op == JSOP_SETGNAME || op == JSOP_SETINTRINSIC) { |
michael@0 | 3547 | JS_ASSERT(emitOption != PushInitialValues); |
michael@0 | 3548 | JSOp bindOp; |
michael@0 | 3549 | if (op == JSOP_SETNAME) |
michael@0 | 3550 | bindOp = JSOP_BINDNAME; |
michael@0 | 3551 | else if (op == JSOP_SETGNAME) |
michael@0 | 3552 | bindOp = JSOP_BINDGNAME; |
michael@0 | 3553 | else |
michael@0 | 3554 | bindOp = JSOP_BINDINTRINSIC; |
michael@0 | 3555 | if (!EmitIndex32(cx, bindOp, atomIndex, bce)) |
michael@0 | 3556 | return false; |
michael@0 | 3557 | } |
michael@0 | 3558 | |
michael@0 | 3559 | bool oldEmittingForInit = bce->emittingForInit; |
michael@0 | 3560 | bce->emittingForInit = false; |
michael@0 | 3561 | if (!EmitTree(cx, bce, pn3)) |
michael@0 | 3562 | return false; |
michael@0 | 3563 | bce->emittingForInit = oldEmittingForInit; |
michael@0 | 3564 | } else if (isLet) { |
michael@0 | 3565 | /* JSOP_ENTERLETx expects at least 1 slot to have been pushed. */ |
michael@0 | 3566 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) |
michael@0 | 3567 | return false; |
michael@0 | 3568 | } |
michael@0 | 3569 | |
michael@0 | 3570 | /* If we are not initializing, nothing to pop. */ |
michael@0 | 3571 | if (emitOption != InitializeVars) { |
michael@0 | 3572 | if (next) |
michael@0 | 3573 | continue; |
michael@0 | 3574 | break; |
michael@0 | 3575 | } |
michael@0 | 3576 | |
michael@0 | 3577 | JS_ASSERT_IF(pn2->isDefn(), pn3 == pn2->pn_expr); |
michael@0 | 3578 | if (!pn2->pn_cookie.isFree()) { |
michael@0 | 3579 | if (!EmitVarOp(cx, pn2, op, bce)) |
michael@0 | 3580 | return false; |
michael@0 | 3581 | } else { |
michael@0 | 3582 | if (!EmitIndexOp(cx, op, atomIndex, bce)) |
michael@0 | 3583 | return false; |
michael@0 | 3584 | } |
michael@0 | 3585 | |
michael@0 | 3586 | emit_note_pop: |
michael@0 | 3587 | if (!next) |
michael@0 | 3588 | break; |
michael@0 | 3589 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3590 | return false; |
michael@0 | 3591 | } |
michael@0 | 3592 | |
michael@0 | 3593 | if (pn->pn_xflags & PNX_POPVAR) { |
michael@0 | 3594 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3595 | return false; |
michael@0 | 3596 | } |
michael@0 | 3597 | |
michael@0 | 3598 | return true; |
michael@0 | 3599 | } |
michael@0 | 3600 | |
michael@0 | 3601 | static bool |
michael@0 | 3602 | EmitAssignment(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *lhs, JSOp op, ParseNode *rhs) |
michael@0 | 3603 | { |
michael@0 | 3604 | /* |
michael@0 | 3605 | * Check left operand type and generate specialized code for it. |
michael@0 | 3606 | * Specialize to avoid ECMA "reference type" values on the operand |
michael@0 | 3607 | * stack, which impose pervasive runtime "GetValue" costs. |
michael@0 | 3608 | */ |
michael@0 | 3609 | jsatomid atomIndex = (jsatomid) -1; |
michael@0 | 3610 | jsbytecode offset = 1; |
michael@0 | 3611 | |
michael@0 | 3612 | switch (lhs->getKind()) { |
michael@0 | 3613 | case PNK_NAME: |
michael@0 | 3614 | if (!BindNameToSlot(cx, bce, lhs)) |
michael@0 | 3615 | return false; |
michael@0 | 3616 | if (lhs->pn_cookie.isFree()) { |
michael@0 | 3617 | if (!bce->makeAtomIndex(lhs->pn_atom, &atomIndex)) |
michael@0 | 3618 | return false; |
michael@0 | 3619 | if (!lhs->isConst()) { |
michael@0 | 3620 | JSOp bindOp; |
michael@0 | 3621 | if (lhs->isOp(JSOP_SETNAME)) |
michael@0 | 3622 | bindOp = JSOP_BINDNAME; |
michael@0 | 3623 | else if (lhs->isOp(JSOP_SETGNAME)) |
michael@0 | 3624 | bindOp = JSOP_BINDGNAME; |
michael@0 | 3625 | else |
michael@0 | 3626 | bindOp = JSOP_BINDINTRINSIC; |
michael@0 | 3627 | if (!EmitIndex32(cx, bindOp, atomIndex, bce)) |
michael@0 | 3628 | return false; |
michael@0 | 3629 | offset++; |
michael@0 | 3630 | } |
michael@0 | 3631 | } |
michael@0 | 3632 | break; |
michael@0 | 3633 | case PNK_DOT: |
michael@0 | 3634 | if (!EmitTree(cx, bce, lhs->expr())) |
michael@0 | 3635 | return false; |
michael@0 | 3636 | offset++; |
michael@0 | 3637 | if (!bce->makeAtomIndex(lhs->pn_atom, &atomIndex)) |
michael@0 | 3638 | return false; |
michael@0 | 3639 | break; |
michael@0 | 3640 | case PNK_ELEM: |
michael@0 | 3641 | JS_ASSERT(lhs->isArity(PN_BINARY)); |
michael@0 | 3642 | if (!EmitTree(cx, bce, lhs->pn_left)) |
michael@0 | 3643 | return false; |
michael@0 | 3644 | if (!EmitTree(cx, bce, lhs->pn_right)) |
michael@0 | 3645 | return false; |
michael@0 | 3646 | offset += 2; |
michael@0 | 3647 | break; |
michael@0 | 3648 | case PNK_ARRAY: |
michael@0 | 3649 | case PNK_OBJECT: |
michael@0 | 3650 | break; |
michael@0 | 3651 | case PNK_CALL: |
michael@0 | 3652 | JS_ASSERT(lhs->pn_xflags & PNX_SETCALL); |
michael@0 | 3653 | if (!EmitTree(cx, bce, lhs)) |
michael@0 | 3654 | return false; |
michael@0 | 3655 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3656 | return false; |
michael@0 | 3657 | break; |
michael@0 | 3658 | default: |
michael@0 | 3659 | JS_ASSERT(0); |
michael@0 | 3660 | } |
michael@0 | 3661 | |
michael@0 | 3662 | if (op != JSOP_NOP) { |
michael@0 | 3663 | JS_ASSERT(rhs); |
michael@0 | 3664 | switch (lhs->getKind()) { |
michael@0 | 3665 | case PNK_NAME: |
michael@0 | 3666 | if (lhs->isConst()) { |
michael@0 | 3667 | if (lhs->isOp(JSOP_CALLEE)) { |
michael@0 | 3668 | if (Emit1(cx, bce, JSOP_CALLEE) < 0) |
michael@0 | 3669 | return false; |
michael@0 | 3670 | } else if (lhs->isOp(JSOP_NAME) || lhs->isOp(JSOP_GETGNAME)) { |
michael@0 | 3671 | if (!EmitIndex32(cx, lhs->getOp(), atomIndex, bce)) |
michael@0 | 3672 | return false; |
michael@0 | 3673 | } else { |
michael@0 | 3674 | JS_ASSERT(JOF_OPTYPE(lhs->getOp()) != JOF_ATOM); |
michael@0 | 3675 | if (!EmitVarOp(cx, lhs, lhs->getOp(), bce)) |
michael@0 | 3676 | return false; |
michael@0 | 3677 | } |
michael@0 | 3678 | } else if (lhs->isOp(JSOP_SETNAME)) { |
michael@0 | 3679 | if (Emit1(cx, bce, JSOP_DUP) < 0) |
michael@0 | 3680 | return false; |
michael@0 | 3681 | if (!EmitIndex32(cx, JSOP_GETXPROP, atomIndex, bce)) |
michael@0 | 3682 | return false; |
michael@0 | 3683 | } else if (lhs->isOp(JSOP_SETGNAME)) { |
michael@0 | 3684 | JS_ASSERT(lhs->pn_cookie.isFree()); |
michael@0 | 3685 | if (!EmitAtomOp(cx, lhs, JSOP_GETGNAME, bce)) |
michael@0 | 3686 | return false; |
michael@0 | 3687 | } else if (lhs->isOp(JSOP_SETINTRINSIC)) { |
michael@0 | 3688 | JS_ASSERT(lhs->pn_cookie.isFree()); |
michael@0 | 3689 | if (!EmitAtomOp(cx, lhs, JSOP_GETINTRINSIC, bce)) |
michael@0 | 3690 | return false; |
michael@0 | 3691 | } else { |
michael@0 | 3692 | JSOp op; |
michael@0 | 3693 | switch (lhs->getOp()) { |
michael@0 | 3694 | case JSOP_SETARG: op = JSOP_GETARG; break; |
michael@0 | 3695 | case JSOP_SETLOCAL: op = JSOP_GETLOCAL; break; |
michael@0 | 3696 | case JSOP_SETALIASEDVAR: op = JSOP_GETALIASEDVAR; break; |
michael@0 | 3697 | default: MOZ_ASSUME_UNREACHABLE("Bad op"); |
michael@0 | 3698 | } |
michael@0 | 3699 | if (!EmitVarOp(cx, lhs, op, bce)) |
michael@0 | 3700 | return false; |
michael@0 | 3701 | } |
michael@0 | 3702 | break; |
michael@0 | 3703 | case PNK_DOT: { |
michael@0 | 3704 | if (Emit1(cx, bce, JSOP_DUP) < 0) |
michael@0 | 3705 | return false; |
michael@0 | 3706 | bool isLength = (lhs->pn_atom == cx->names().length); |
michael@0 | 3707 | if (!EmitIndex32(cx, isLength ? JSOP_LENGTH : JSOP_GETPROP, atomIndex, bce)) |
michael@0 | 3708 | return false; |
michael@0 | 3709 | break; |
michael@0 | 3710 | } |
michael@0 | 3711 | case PNK_ELEM: |
michael@0 | 3712 | if (Emit1(cx, bce, JSOP_DUP2) < 0) |
michael@0 | 3713 | return false; |
michael@0 | 3714 | if (!EmitElemOpBase(cx, bce, JSOP_GETELEM)) |
michael@0 | 3715 | return false; |
michael@0 | 3716 | break; |
michael@0 | 3717 | case PNK_CALL: |
michael@0 | 3718 | /* |
michael@0 | 3719 | * We just emitted a JSOP_SETCALL (which will always throw) and |
michael@0 | 3720 | * popped the call's return value. Push a random value to make sure |
michael@0 | 3721 | * the stack depth is correct. |
michael@0 | 3722 | */ |
michael@0 | 3723 | JS_ASSERT(lhs->pn_xflags & PNX_SETCALL); |
michael@0 | 3724 | if (Emit1(cx, bce, JSOP_NULL) < 0) |
michael@0 | 3725 | return false; |
michael@0 | 3726 | break; |
michael@0 | 3727 | default:; |
michael@0 | 3728 | } |
michael@0 | 3729 | } |
michael@0 | 3730 | |
michael@0 | 3731 | /* Now emit the right operand (it may affect the namespace). */ |
michael@0 | 3732 | if (rhs) { |
michael@0 | 3733 | if (!EmitTree(cx, bce, rhs)) |
michael@0 | 3734 | return false; |
michael@0 | 3735 | } else { |
michael@0 | 3736 | /* |
michael@0 | 3737 | * The value to assign is the next enumeration value in a for-in or |
michael@0 | 3738 | * for-of loop. That value has already been emitted: by JSOP_ITERNEXT |
michael@0 | 3739 | * in the for-in case, or via a GETPROP "value" on the result object in |
michael@0 | 3740 | * the for-of case. If offset == 1, that slot is already at the top of |
michael@0 | 3741 | * the stack. Otherwise, rearrange the stack to put that value on top. |
michael@0 | 3742 | */ |
michael@0 | 3743 | if (offset != 1 && Emit2(cx, bce, JSOP_PICK, offset - 1) < 0) |
michael@0 | 3744 | return false; |
michael@0 | 3745 | } |
michael@0 | 3746 | |
michael@0 | 3747 | /* If += etc., emit the binary operator with a source note. */ |
michael@0 | 3748 | if (op != JSOP_NOP) { |
michael@0 | 3749 | /* |
michael@0 | 3750 | * Take care to avoid SRC_ASSIGNOP if the left-hand side is a const |
michael@0 | 3751 | * declared in the current compilation unit, as in this case (just |
michael@0 | 3752 | * a bit further below) we will avoid emitting the assignment op. |
michael@0 | 3753 | */ |
michael@0 | 3754 | if (!lhs->isKind(PNK_NAME) || !lhs->isConst()) { |
michael@0 | 3755 | if (NewSrcNote(cx, bce, SRC_ASSIGNOP) < 0) |
michael@0 | 3756 | return false; |
michael@0 | 3757 | } |
michael@0 | 3758 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 3759 | return false; |
michael@0 | 3760 | } |
michael@0 | 3761 | |
michael@0 | 3762 | /* Finally, emit the specialized assignment bytecode. */ |
michael@0 | 3763 | switch (lhs->getKind()) { |
michael@0 | 3764 | case PNK_NAME: |
michael@0 | 3765 | if (lhs->isConst()) { |
michael@0 | 3766 | if (!rhs) { |
michael@0 | 3767 | bce->reportError(lhs, JSMSG_BAD_FOR_LEFTSIDE); |
michael@0 | 3768 | return false; |
michael@0 | 3769 | } |
michael@0 | 3770 | break; |
michael@0 | 3771 | } |
michael@0 | 3772 | if (lhs->isOp(JSOP_SETARG) || lhs->isOp(JSOP_SETLOCAL) || lhs->isOp(JSOP_SETALIASEDVAR)) { |
michael@0 | 3773 | if (!EmitVarOp(cx, lhs, lhs->getOp(), bce)) |
michael@0 | 3774 | return false; |
michael@0 | 3775 | } else { |
michael@0 | 3776 | if (!EmitIndexOp(cx, lhs->getOp(), atomIndex, bce)) |
michael@0 | 3777 | return false; |
michael@0 | 3778 | } |
michael@0 | 3779 | break; |
michael@0 | 3780 | case PNK_DOT: |
michael@0 | 3781 | if (!EmitIndexOp(cx, JSOP_SETPROP, atomIndex, bce)) |
michael@0 | 3782 | return false; |
michael@0 | 3783 | break; |
michael@0 | 3784 | case PNK_CALL: |
michael@0 | 3785 | /* Do nothing. The JSOP_SETCALL we emitted will always throw. */ |
michael@0 | 3786 | JS_ASSERT(lhs->pn_xflags & PNX_SETCALL); |
michael@0 | 3787 | break; |
michael@0 | 3788 | case PNK_ELEM: |
michael@0 | 3789 | if (Emit1(cx, bce, JSOP_SETELEM) < 0) |
michael@0 | 3790 | return false; |
michael@0 | 3791 | break; |
michael@0 | 3792 | case PNK_ARRAY: |
michael@0 | 3793 | case PNK_OBJECT: |
michael@0 | 3794 | if (!EmitDestructuringOps(cx, bce, lhs)) |
michael@0 | 3795 | return false; |
michael@0 | 3796 | break; |
michael@0 | 3797 | default: |
michael@0 | 3798 | JS_ASSERT(0); |
michael@0 | 3799 | } |
michael@0 | 3800 | return true; |
michael@0 | 3801 | } |
michael@0 | 3802 | |
michael@0 | 3803 | bool |
michael@0 | 3804 | ParseNode::getConstantValue(ExclusiveContext *cx, bool strictChecks, MutableHandleValue vp) |
michael@0 | 3805 | { |
michael@0 | 3806 | switch (getKind()) { |
michael@0 | 3807 | case PNK_NUMBER: |
michael@0 | 3808 | vp.setNumber(pn_dval); |
michael@0 | 3809 | return true; |
michael@0 | 3810 | case PNK_STRING: |
michael@0 | 3811 | vp.setString(pn_atom); |
michael@0 | 3812 | return true; |
michael@0 | 3813 | case PNK_TRUE: |
michael@0 | 3814 | vp.setBoolean(true); |
michael@0 | 3815 | return true; |
michael@0 | 3816 | case PNK_FALSE: |
michael@0 | 3817 | vp.setBoolean(false); |
michael@0 | 3818 | return true; |
michael@0 | 3819 | case PNK_NULL: |
michael@0 | 3820 | vp.setNull(); |
michael@0 | 3821 | return true; |
michael@0 | 3822 | case PNK_SPREAD: |
michael@0 | 3823 | return false; |
michael@0 | 3824 | case PNK_ARRAY: { |
michael@0 | 3825 | JS_ASSERT(isOp(JSOP_NEWINIT) && !(pn_xflags & PNX_NONCONST)); |
michael@0 | 3826 | |
michael@0 | 3827 | RootedObject obj(cx, |
michael@0 | 3828 | NewDenseAllocatedArray(cx, pn_count, nullptr, MaybeSingletonObject)); |
michael@0 | 3829 | if (!obj) |
michael@0 | 3830 | return false; |
michael@0 | 3831 | |
michael@0 | 3832 | unsigned idx = 0; |
michael@0 | 3833 | RootedId id(cx); |
michael@0 | 3834 | RootedValue value(cx); |
michael@0 | 3835 | for (ParseNode *pn = pn_head; pn; idx++, pn = pn->pn_next) { |
michael@0 | 3836 | if (!pn->getConstantValue(cx, strictChecks, &value)) |
michael@0 | 3837 | return false; |
michael@0 | 3838 | id = INT_TO_JSID(idx); |
michael@0 | 3839 | if (!JSObject::defineGeneric(cx, obj, id, value, nullptr, nullptr, JSPROP_ENUMERATE)) |
michael@0 | 3840 | return false; |
michael@0 | 3841 | } |
michael@0 | 3842 | JS_ASSERT(idx == pn_count); |
michael@0 | 3843 | |
michael@0 | 3844 | types::FixArrayType(cx, obj); |
michael@0 | 3845 | vp.setObject(*obj); |
michael@0 | 3846 | return true; |
michael@0 | 3847 | } |
michael@0 | 3848 | case PNK_OBJECT: { |
michael@0 | 3849 | JS_ASSERT(isOp(JSOP_NEWINIT)); |
michael@0 | 3850 | JS_ASSERT(!(pn_xflags & PNX_NONCONST)); |
michael@0 | 3851 | |
michael@0 | 3852 | gc::AllocKind kind = GuessObjectGCKind(pn_count); |
michael@0 | 3853 | RootedObject obj(cx, NewBuiltinClassInstance(cx, &JSObject::class_, kind, MaybeSingletonObject)); |
michael@0 | 3854 | if (!obj) |
michael@0 | 3855 | return false; |
michael@0 | 3856 | |
michael@0 | 3857 | RootedValue value(cx), idvalue(cx); |
michael@0 | 3858 | for (ParseNode *pn = pn_head; pn; pn = pn->pn_next) { |
michael@0 | 3859 | if (!pn->pn_right->getConstantValue(cx, strictChecks, &value)) |
michael@0 | 3860 | return false; |
michael@0 | 3861 | |
michael@0 | 3862 | ParseNode *pnid = pn->pn_left; |
michael@0 | 3863 | if (pnid->isKind(PNK_NUMBER)) { |
michael@0 | 3864 | idvalue = NumberValue(pnid->pn_dval); |
michael@0 | 3865 | } else { |
michael@0 | 3866 | JS_ASSERT(pnid->isKind(PNK_NAME) || pnid->isKind(PNK_STRING)); |
michael@0 | 3867 | JS_ASSERT(pnid->pn_atom != cx->names().proto); |
michael@0 | 3868 | idvalue = StringValue(pnid->pn_atom); |
michael@0 | 3869 | } |
michael@0 | 3870 | |
michael@0 | 3871 | uint32_t index; |
michael@0 | 3872 | if (IsDefinitelyIndex(idvalue, &index)) { |
michael@0 | 3873 | if (!JSObject::defineElement(cx, obj, index, value, nullptr, nullptr, |
michael@0 | 3874 | JSPROP_ENUMERATE)) |
michael@0 | 3875 | { |
michael@0 | 3876 | return false; |
michael@0 | 3877 | } |
michael@0 | 3878 | |
michael@0 | 3879 | continue; |
michael@0 | 3880 | } |
michael@0 | 3881 | |
michael@0 | 3882 | JSAtom *name = ToAtom<CanGC>(cx, idvalue); |
michael@0 | 3883 | if (!name) |
michael@0 | 3884 | return false; |
michael@0 | 3885 | |
michael@0 | 3886 | if (name->isIndex(&index)) { |
michael@0 | 3887 | if (!JSObject::defineElement(cx, obj, index, value, |
michael@0 | 3888 | nullptr, nullptr, JSPROP_ENUMERATE)) |
michael@0 | 3889 | return false; |
michael@0 | 3890 | } else { |
michael@0 | 3891 | if (!JSObject::defineProperty(cx, obj, name->asPropertyName(), value, |
michael@0 | 3892 | nullptr, nullptr, JSPROP_ENUMERATE)) |
michael@0 | 3893 | { |
michael@0 | 3894 | return false; |
michael@0 | 3895 | } |
michael@0 | 3896 | } |
michael@0 | 3897 | } |
michael@0 | 3898 | |
michael@0 | 3899 | types::FixObjectType(cx, obj); |
michael@0 | 3900 | vp.setObject(*obj); |
michael@0 | 3901 | return true; |
michael@0 | 3902 | } |
michael@0 | 3903 | default: |
michael@0 | 3904 | MOZ_ASSUME_UNREACHABLE("Unexpected node"); |
michael@0 | 3905 | } |
michael@0 | 3906 | return false; |
michael@0 | 3907 | } |
michael@0 | 3908 | |
michael@0 | 3909 | static bool |
michael@0 | 3910 | EmitSingletonInitialiser(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 3911 | { |
michael@0 | 3912 | RootedValue value(cx); |
michael@0 | 3913 | if (!pn->getConstantValue(cx, bce->sc->needStrictChecks(), &value)) |
michael@0 | 3914 | return false; |
michael@0 | 3915 | |
michael@0 | 3916 | JS_ASSERT(value.isObject()); |
michael@0 | 3917 | ObjectBox *objbox = bce->parser->newObjectBox(&value.toObject()); |
michael@0 | 3918 | if (!objbox) |
michael@0 | 3919 | return false; |
michael@0 | 3920 | |
michael@0 | 3921 | return EmitObjectOp(cx, objbox, JSOP_OBJECT, bce); |
michael@0 | 3922 | } |
michael@0 | 3923 | |
michael@0 | 3924 | /* See the SRC_FOR source note offsetBias comments later in this file. */ |
michael@0 | 3925 | JS_STATIC_ASSERT(JSOP_NOP_LENGTH == 1); |
michael@0 | 3926 | JS_STATIC_ASSERT(JSOP_POP_LENGTH == 1); |
michael@0 | 3927 | |
michael@0 | 3928 | namespace { |
michael@0 | 3929 | |
michael@0 | 3930 | class EmitLevelManager |
michael@0 | 3931 | { |
michael@0 | 3932 | BytecodeEmitter *bce; |
michael@0 | 3933 | public: |
michael@0 | 3934 | EmitLevelManager(BytecodeEmitter *bce) : bce(bce) { bce->emitLevel++; } |
michael@0 | 3935 | ~EmitLevelManager() { bce->emitLevel--; } |
michael@0 | 3936 | }; |
michael@0 | 3937 | |
michael@0 | 3938 | } /* anonymous namespace */ |
michael@0 | 3939 | |
michael@0 | 3940 | static bool |
michael@0 | 3941 | EmitCatch(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 3942 | { |
michael@0 | 3943 | /* |
michael@0 | 3944 | * Morph STMT_BLOCK to STMT_CATCH, note the block entry code offset, |
michael@0 | 3945 | * and save the block object atom. |
michael@0 | 3946 | */ |
michael@0 | 3947 | StmtInfoBCE *stmt = bce->topStmt; |
michael@0 | 3948 | JS_ASSERT(stmt->type == STMT_BLOCK && stmt->isBlockScope); |
michael@0 | 3949 | stmt->type = STMT_CATCH; |
michael@0 | 3950 | |
michael@0 | 3951 | /* Go up one statement info record to the TRY or FINALLY record. */ |
michael@0 | 3952 | stmt = stmt->down; |
michael@0 | 3953 | JS_ASSERT(stmt->type == STMT_TRY || stmt->type == STMT_FINALLY); |
michael@0 | 3954 | |
michael@0 | 3955 | /* Pick up the pending exception and bind it to the catch variable. */ |
michael@0 | 3956 | if (Emit1(cx, bce, JSOP_EXCEPTION) < 0) |
michael@0 | 3957 | return false; |
michael@0 | 3958 | |
michael@0 | 3959 | /* |
michael@0 | 3960 | * Dup the exception object if there is a guard for rethrowing to use |
michael@0 | 3961 | * it later when rethrowing or in other catches. |
michael@0 | 3962 | */ |
michael@0 | 3963 | if (pn->pn_kid2 && Emit1(cx, bce, JSOP_DUP) < 0) |
michael@0 | 3964 | return false; |
michael@0 | 3965 | |
michael@0 | 3966 | ParseNode *pn2 = pn->pn_kid1; |
michael@0 | 3967 | switch (pn2->getKind()) { |
michael@0 | 3968 | case PNK_ARRAY: |
michael@0 | 3969 | case PNK_OBJECT: |
michael@0 | 3970 | if (!EmitDestructuringOps(cx, bce, pn2)) |
michael@0 | 3971 | return false; |
michael@0 | 3972 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3973 | return false; |
michael@0 | 3974 | break; |
michael@0 | 3975 | |
michael@0 | 3976 | case PNK_NAME: |
michael@0 | 3977 | /* Inline and specialize BindNameToSlot for pn2. */ |
michael@0 | 3978 | JS_ASSERT(!pn2->pn_cookie.isFree()); |
michael@0 | 3979 | if (!EmitVarOp(cx, pn2, JSOP_SETLOCAL, bce)) |
michael@0 | 3980 | return false; |
michael@0 | 3981 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 3982 | return false; |
michael@0 | 3983 | break; |
michael@0 | 3984 | |
michael@0 | 3985 | default: |
michael@0 | 3986 | JS_ASSERT(0); |
michael@0 | 3987 | } |
michael@0 | 3988 | |
michael@0 | 3989 | // If there is a guard expression, emit it and arrange to jump to the next |
michael@0 | 3990 | // catch block if the guard expression is false. |
michael@0 | 3991 | if (pn->pn_kid2) { |
michael@0 | 3992 | if (!EmitTree(cx, bce, pn->pn_kid2)) |
michael@0 | 3993 | return false; |
michael@0 | 3994 | |
michael@0 | 3995 | // If the guard expression is false, fall through, pop the block scope, |
michael@0 | 3996 | // and jump to the next catch block. Otherwise jump over that code and |
michael@0 | 3997 | // pop the dupped exception. |
michael@0 | 3998 | ptrdiff_t guardCheck = EmitJump(cx, bce, JSOP_IFNE, 0); |
michael@0 | 3999 | if (guardCheck < 0) |
michael@0 | 4000 | return false; |
michael@0 | 4001 | |
michael@0 | 4002 | { |
michael@0 | 4003 | NonLocalExitScope nle(cx, bce); |
michael@0 | 4004 | |
michael@0 | 4005 | // Move exception back to cx->exception to prepare for |
michael@0 | 4006 | // the next catch. |
michael@0 | 4007 | if (Emit1(cx, bce, JSOP_THROWING) < 0) |
michael@0 | 4008 | return false; |
michael@0 | 4009 | |
michael@0 | 4010 | // Leave the scope for this catch block. |
michael@0 | 4011 | if (!nle.prepareForNonLocalJump(stmt)) |
michael@0 | 4012 | return false; |
michael@0 | 4013 | |
michael@0 | 4014 | // Jump to the next handler. The jump target is backpatched by EmitTry. |
michael@0 | 4015 | ptrdiff_t guardJump = EmitJump(cx, bce, JSOP_GOTO, 0); |
michael@0 | 4016 | if (guardJump < 0) |
michael@0 | 4017 | return false; |
michael@0 | 4018 | stmt->guardJump() = guardJump; |
michael@0 | 4019 | } |
michael@0 | 4020 | |
michael@0 | 4021 | // Back to normal control flow. |
michael@0 | 4022 | SetJumpOffsetAt(bce, guardCheck); |
michael@0 | 4023 | |
michael@0 | 4024 | // Pop duplicated exception object as we no longer need it. |
michael@0 | 4025 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 4026 | return false; |
michael@0 | 4027 | } |
michael@0 | 4028 | |
michael@0 | 4029 | /* Emit the catch body. */ |
michael@0 | 4030 | return EmitTree(cx, bce, pn->pn_kid3); |
michael@0 | 4031 | } |
michael@0 | 4032 | |
michael@0 | 4033 | // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the |
michael@0 | 4034 | // comment on EmitSwitch. |
michael@0 | 4035 | // |
michael@0 | 4036 | MOZ_NEVER_INLINE static bool |
michael@0 | 4037 | EmitTry(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 4038 | { |
michael@0 | 4039 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 4040 | |
michael@0 | 4041 | // Push stmtInfo to track jumps-over-catches and gosubs-to-finally |
michael@0 | 4042 | // for later fixup. |
michael@0 | 4043 | // |
michael@0 | 4044 | // When a finally block is active (STMT_FINALLY in our parse context), |
michael@0 | 4045 | // non-local jumps (including jumps-over-catches) result in a GOSUB |
michael@0 | 4046 | // being written into the bytecode stream and fixed-up later (c.f. |
michael@0 | 4047 | // EmitBackPatchOp and BackPatch). |
michael@0 | 4048 | // |
michael@0 | 4049 | PushStatementBCE(bce, &stmtInfo, pn->pn_kid3 ? STMT_FINALLY : STMT_TRY, bce->offset()); |
michael@0 | 4050 | |
michael@0 | 4051 | // Since an exception can be thrown at any place inside the try block, |
michael@0 | 4052 | // we need to restore the stack and the scope chain before we transfer |
michael@0 | 4053 | // the control to the exception handler. |
michael@0 | 4054 | // |
michael@0 | 4055 | // For that we store in a try note associated with the catch or |
michael@0 | 4056 | // finally block the stack depth upon the try entry. The interpreter |
michael@0 | 4057 | // uses this depth to properly unwind the stack and the scope chain. |
michael@0 | 4058 | // |
michael@0 | 4059 | int depth = bce->stackDepth; |
michael@0 | 4060 | |
michael@0 | 4061 | // Record the try location, then emit the try block. |
michael@0 | 4062 | ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_TRY); |
michael@0 | 4063 | if (noteIndex < 0 || Emit1(cx, bce, JSOP_TRY) < 0) |
michael@0 | 4064 | return false; |
michael@0 | 4065 | ptrdiff_t tryStart = bce->offset(); |
michael@0 | 4066 | if (!EmitTree(cx, bce, pn->pn_kid1)) |
michael@0 | 4067 | return false; |
michael@0 | 4068 | JS_ASSERT(depth == bce->stackDepth); |
michael@0 | 4069 | |
michael@0 | 4070 | // GOSUB to finally, if present. |
michael@0 | 4071 | if (pn->pn_kid3) { |
michael@0 | 4072 | if (EmitBackPatchOp(cx, bce, &stmtInfo.gosubs()) < 0) |
michael@0 | 4073 | return false; |
michael@0 | 4074 | } |
michael@0 | 4075 | |
michael@0 | 4076 | // Source note points to the jump at the end of the try block. |
michael@0 | 4077 | if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, bce->offset() - tryStart + JSOP_TRY_LENGTH)) |
michael@0 | 4078 | return false; |
michael@0 | 4079 | |
michael@0 | 4080 | // Emit jump over catch and/or finally. |
michael@0 | 4081 | ptrdiff_t catchJump = -1; |
michael@0 | 4082 | if (EmitBackPatchOp(cx, bce, &catchJump) < 0) |
michael@0 | 4083 | return false; |
michael@0 | 4084 | |
michael@0 | 4085 | ptrdiff_t tryEnd = bce->offset(); |
michael@0 | 4086 | |
michael@0 | 4087 | // If this try has a catch block, emit it. |
michael@0 | 4088 | if (ParseNode *pn2 = pn->pn_kid2) { |
michael@0 | 4089 | // The emitted code for a catch block looks like: |
michael@0 | 4090 | // |
michael@0 | 4091 | // [pushblockscope] only if any local aliased |
michael@0 | 4092 | // exception |
michael@0 | 4093 | // if there is a catchguard: |
michael@0 | 4094 | // dup |
michael@0 | 4095 | // setlocal 0; pop assign or possibly destructure exception |
michael@0 | 4096 | // if there is a catchguard: |
michael@0 | 4097 | // < catchguard code > |
michael@0 | 4098 | // ifne POST |
michael@0 | 4099 | // debugleaveblock |
michael@0 | 4100 | // [popblockscope] only if any local aliased |
michael@0 | 4101 | // throwing pop exception to cx->exception |
michael@0 | 4102 | // goto <next catch block> |
michael@0 | 4103 | // POST: pop |
michael@0 | 4104 | // < catch block contents > |
michael@0 | 4105 | // debugleaveblock |
michael@0 | 4106 | // [popblockscope] only if any local aliased |
michael@0 | 4107 | // goto <end of catch blocks> non-local; finally applies |
michael@0 | 4108 | // |
michael@0 | 4109 | // If there's no catch block without a catchguard, the last <next catch |
michael@0 | 4110 | // block> points to rethrow code. This code will [gosub] to the finally |
michael@0 | 4111 | // code if appropriate, and is also used for the catch-all trynote for |
michael@0 | 4112 | // capturing exceptions thrown from catch{} blocks. |
michael@0 | 4113 | // |
michael@0 | 4114 | for (ParseNode *pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) { |
michael@0 | 4115 | JS_ASSERT(bce->stackDepth == depth); |
michael@0 | 4116 | |
michael@0 | 4117 | // Emit the lexical scope and catch body. |
michael@0 | 4118 | JS_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); |
michael@0 | 4119 | if (!EmitTree(cx, bce, pn3)) |
michael@0 | 4120 | return false; |
michael@0 | 4121 | |
michael@0 | 4122 | // gosub <finally>, if required. |
michael@0 | 4123 | if (pn->pn_kid3) { |
michael@0 | 4124 | if (EmitBackPatchOp(cx, bce, &stmtInfo.gosubs()) < 0) |
michael@0 | 4125 | return false; |
michael@0 | 4126 | JS_ASSERT(bce->stackDepth == depth); |
michael@0 | 4127 | } |
michael@0 | 4128 | |
michael@0 | 4129 | // Jump over the remaining catch blocks. This will get fixed |
michael@0 | 4130 | // up to jump to after catch/finally. |
michael@0 | 4131 | if (EmitBackPatchOp(cx, bce, &catchJump) < 0) |
michael@0 | 4132 | return false; |
michael@0 | 4133 | |
michael@0 | 4134 | // If this catch block had a guard clause, patch the guard jump to |
michael@0 | 4135 | // come here. |
michael@0 | 4136 | if (stmtInfo.guardJump() != -1) { |
michael@0 | 4137 | SetJumpOffsetAt(bce, stmtInfo.guardJump()); |
michael@0 | 4138 | stmtInfo.guardJump() = -1; |
michael@0 | 4139 | |
michael@0 | 4140 | // If this catch block is the last one, rethrow, delegating |
michael@0 | 4141 | // execution of any finally block to the exception handler. |
michael@0 | 4142 | if (!pn3->pn_next) { |
michael@0 | 4143 | if (Emit1(cx, bce, JSOP_EXCEPTION) < 0) |
michael@0 | 4144 | return false; |
michael@0 | 4145 | if (Emit1(cx, bce, JSOP_THROW) < 0) |
michael@0 | 4146 | return false; |
michael@0 | 4147 | } |
michael@0 | 4148 | } |
michael@0 | 4149 | } |
michael@0 | 4150 | } |
michael@0 | 4151 | |
michael@0 | 4152 | JS_ASSERT(bce->stackDepth == depth); |
michael@0 | 4153 | |
michael@0 | 4154 | // Emit the finally handler, if there is one. |
michael@0 | 4155 | ptrdiff_t finallyStart = 0; |
michael@0 | 4156 | if (pn->pn_kid3) { |
michael@0 | 4157 | // Fix up the gosubs that might have been emitted before non-local |
michael@0 | 4158 | // jumps to the finally code. |
michael@0 | 4159 | if (!BackPatch(cx, bce, stmtInfo.gosubs(), bce->code().end(), JSOP_GOSUB)) |
michael@0 | 4160 | return false; |
michael@0 | 4161 | |
michael@0 | 4162 | finallyStart = bce->offset(); |
michael@0 | 4163 | |
michael@0 | 4164 | // Indicate that we're emitting a subroutine body. |
michael@0 | 4165 | stmtInfo.type = STMT_SUBROUTINE; |
michael@0 | 4166 | if (!UpdateSourceCoordNotes(cx, bce, pn->pn_kid3->pn_pos.begin)) |
michael@0 | 4167 | return false; |
michael@0 | 4168 | if (Emit1(cx, bce, JSOP_FINALLY) < 0 || |
michael@0 | 4169 | !EmitTree(cx, bce, pn->pn_kid3) || |
michael@0 | 4170 | Emit1(cx, bce, JSOP_RETSUB) < 0) |
michael@0 | 4171 | { |
michael@0 | 4172 | return false; |
michael@0 | 4173 | } |
michael@0 | 4174 | JS_ASSERT(bce->stackDepth == depth); |
michael@0 | 4175 | } |
michael@0 | 4176 | if (!PopStatementBCE(cx, bce)) |
michael@0 | 4177 | return false; |
michael@0 | 4178 | |
michael@0 | 4179 | // ReconstructPCStack needs a NOP here to mark the end of the last catch block. |
michael@0 | 4180 | if (Emit1(cx, bce, JSOP_NOP) < 0) |
michael@0 | 4181 | return false; |
michael@0 | 4182 | |
michael@0 | 4183 | // Fix up the end-of-try/catch jumps to come here. |
michael@0 | 4184 | if (!BackPatch(cx, bce, catchJump, bce->code().end(), JSOP_GOTO)) |
michael@0 | 4185 | return false; |
michael@0 | 4186 | |
michael@0 | 4187 | // Add the try note last, to let post-order give us the right ordering |
michael@0 | 4188 | // (first to last for a given nesting level, inner to outer by level). |
michael@0 | 4189 | if (pn->pn_kid2 && !bce->tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd)) |
michael@0 | 4190 | return false; |
michael@0 | 4191 | |
michael@0 | 4192 | // If we've got a finally, mark try+catch region with additional |
michael@0 | 4193 | // trynote to catch exceptions (re)thrown from a catch block or |
michael@0 | 4194 | // for the try{}finally{} case. |
michael@0 | 4195 | if (pn->pn_kid3 && !bce->tryNoteList.append(JSTRY_FINALLY, depth, tryStart, finallyStart)) |
michael@0 | 4196 | return false; |
michael@0 | 4197 | |
michael@0 | 4198 | return true; |
michael@0 | 4199 | } |
michael@0 | 4200 | |
michael@0 | 4201 | static bool |
michael@0 | 4202 | EmitIf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 4203 | { |
michael@0 | 4204 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 4205 | |
michael@0 | 4206 | /* Initialize so we can detect else-if chains and avoid recursion. */ |
michael@0 | 4207 | stmtInfo.type = STMT_IF; |
michael@0 | 4208 | ptrdiff_t beq = -1; |
michael@0 | 4209 | ptrdiff_t jmp = -1; |
michael@0 | 4210 | ptrdiff_t noteIndex = -1; |
michael@0 | 4211 | |
michael@0 | 4212 | if_again: |
michael@0 | 4213 | /* Emit code for the condition before pushing stmtInfo. */ |
michael@0 | 4214 | if (!EmitTree(cx, bce, pn->pn_kid1)) |
michael@0 | 4215 | return false; |
michael@0 | 4216 | ptrdiff_t top = bce->offset(); |
michael@0 | 4217 | if (stmtInfo.type == STMT_IF) { |
michael@0 | 4218 | PushStatementBCE(bce, &stmtInfo, STMT_IF, top); |
michael@0 | 4219 | } else { |
michael@0 | 4220 | /* |
michael@0 | 4221 | * We came here from the goto further below that detects else-if |
michael@0 | 4222 | * chains, so we must mutate stmtInfo back into a STMT_IF record. |
michael@0 | 4223 | * Also we need a note offset for SRC_IF_ELSE to help IonMonkey. |
michael@0 | 4224 | */ |
michael@0 | 4225 | JS_ASSERT(stmtInfo.type == STMT_ELSE); |
michael@0 | 4226 | stmtInfo.type = STMT_IF; |
michael@0 | 4227 | stmtInfo.update = top; |
michael@0 | 4228 | if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, jmp - beq)) |
michael@0 | 4229 | return false; |
michael@0 | 4230 | } |
michael@0 | 4231 | |
michael@0 | 4232 | /* Emit an annotated branch-if-false around the then part. */ |
michael@0 | 4233 | ParseNode *pn3 = pn->pn_kid3; |
michael@0 | 4234 | noteIndex = NewSrcNote(cx, bce, pn3 ? SRC_IF_ELSE : SRC_IF); |
michael@0 | 4235 | if (noteIndex < 0) |
michael@0 | 4236 | return false; |
michael@0 | 4237 | beq = EmitJump(cx, bce, JSOP_IFEQ, 0); |
michael@0 | 4238 | if (beq < 0) |
michael@0 | 4239 | return false; |
michael@0 | 4240 | |
michael@0 | 4241 | /* Emit code for the then and optional else parts. */ |
michael@0 | 4242 | if (!EmitTree(cx, bce, pn->pn_kid2)) |
michael@0 | 4243 | return false; |
michael@0 | 4244 | if (pn3) { |
michael@0 | 4245 | /* Modify stmtInfo so we know we're in the else part. */ |
michael@0 | 4246 | stmtInfo.type = STMT_ELSE; |
michael@0 | 4247 | |
michael@0 | 4248 | /* |
michael@0 | 4249 | * Emit a JSOP_BACKPATCH op to jump from the end of our then part |
michael@0 | 4250 | * around the else part. The PopStatementBCE call at the bottom of |
michael@0 | 4251 | * this function will fix up the backpatch chain linked from |
michael@0 | 4252 | * stmtInfo.breaks. |
michael@0 | 4253 | */ |
michael@0 | 4254 | jmp = EmitGoto(cx, bce, &stmtInfo, &stmtInfo.breaks); |
michael@0 | 4255 | if (jmp < 0) |
michael@0 | 4256 | return false; |
michael@0 | 4257 | |
michael@0 | 4258 | /* Ensure the branch-if-false comes here, then emit the else. */ |
michael@0 | 4259 | SetJumpOffsetAt(bce, beq); |
michael@0 | 4260 | if (pn3->isKind(PNK_IF)) { |
michael@0 | 4261 | pn = pn3; |
michael@0 | 4262 | goto if_again; |
michael@0 | 4263 | } |
michael@0 | 4264 | |
michael@0 | 4265 | if (!EmitTree(cx, bce, pn3)) |
michael@0 | 4266 | return false; |
michael@0 | 4267 | |
michael@0 | 4268 | /* |
michael@0 | 4269 | * Annotate SRC_IF_ELSE with the offset from branch to jump, for |
michael@0 | 4270 | * IonMonkey's benefit. We can't just "back up" from the pc |
michael@0 | 4271 | * of the else clause, because we don't know whether an extended |
michael@0 | 4272 | * jump was required to leap from the end of the then clause over |
michael@0 | 4273 | * the else clause. |
michael@0 | 4274 | */ |
michael@0 | 4275 | if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, jmp - beq)) |
michael@0 | 4276 | return false; |
michael@0 | 4277 | } else { |
michael@0 | 4278 | /* No else part, fixup the branch-if-false to come here. */ |
michael@0 | 4279 | SetJumpOffsetAt(bce, beq); |
michael@0 | 4280 | } |
michael@0 | 4281 | return PopStatementBCE(cx, bce); |
michael@0 | 4282 | } |
michael@0 | 4283 | |
michael@0 | 4284 | /* |
michael@0 | 4285 | * pnLet represents one of: |
michael@0 | 4286 | * |
michael@0 | 4287 | * let-expression: (let (x = y) EXPR) |
michael@0 | 4288 | * let-statement: let (x = y) { ... } |
michael@0 | 4289 | * |
michael@0 | 4290 | * For a let-expression 'let (x = a, [y,z] = b) e', EmitLet produces: |
michael@0 | 4291 | * |
michael@0 | 4292 | * bytecode stackDepth srcnotes |
michael@0 | 4293 | * evaluate a +1 |
michael@0 | 4294 | * evaluate b +1 |
michael@0 | 4295 | * dup +1 |
michael@0 | 4296 | * destructure y |
michael@0 | 4297 | * pick 1 |
michael@0 | 4298 | * dup +1 |
michael@0 | 4299 | * destructure z |
michael@0 | 4300 | * pick 1 |
michael@0 | 4301 | * pop -1 |
michael@0 | 4302 | * setlocal 2 -1 |
michael@0 | 4303 | * setlocal 1 -1 |
michael@0 | 4304 | * setlocal 0 -1 |
michael@0 | 4305 | * pushblockscope (if needed) |
michael@0 | 4306 | * evaluate e +1 |
michael@0 | 4307 | * debugleaveblock |
michael@0 | 4308 | * popblockscope (if needed) |
michael@0 | 4309 | * |
michael@0 | 4310 | * Note that, since pushblockscope simply changes fp->scopeChain and does not |
michael@0 | 4311 | * otherwise touch the stack, evaluation of the let-var initializers must leave |
michael@0 | 4312 | * the initial value in the let-var's future slot. |
michael@0 | 4313 | */ |
michael@0 | 4314 | /* |
michael@0 | 4315 | * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
michael@0 | 4316 | * the comment on EmitSwitch. |
michael@0 | 4317 | */ |
michael@0 | 4318 | MOZ_NEVER_INLINE static bool |
michael@0 | 4319 | EmitLet(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pnLet) |
michael@0 | 4320 | { |
michael@0 | 4321 | JS_ASSERT(pnLet->isArity(PN_BINARY)); |
michael@0 | 4322 | ParseNode *varList = pnLet->pn_left; |
michael@0 | 4323 | JS_ASSERT(varList->isArity(PN_LIST)); |
michael@0 | 4324 | ParseNode *letBody = pnLet->pn_right; |
michael@0 | 4325 | JS_ASSERT(letBody->isLet() && letBody->isKind(PNK_LEXICALSCOPE)); |
michael@0 | 4326 | |
michael@0 | 4327 | int letHeadDepth = bce->stackDepth; |
michael@0 | 4328 | |
michael@0 | 4329 | if (!EmitVariables(cx, bce, varList, PushInitialValues, true)) |
michael@0 | 4330 | return false; |
michael@0 | 4331 | |
michael@0 | 4332 | /* Push storage for hoisted let decls (e.g. 'let (x) { let y }'). */ |
michael@0 | 4333 | uint32_t alreadyPushed = bce->stackDepth - letHeadDepth; |
michael@0 | 4334 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 4335 | if (!EnterBlockScope(cx, bce, &stmtInfo, letBody->pn_objbox, alreadyPushed)) |
michael@0 | 4336 | return false; |
michael@0 | 4337 | |
michael@0 | 4338 | if (!EmitTree(cx, bce, letBody->pn_expr)) |
michael@0 | 4339 | return false; |
michael@0 | 4340 | |
michael@0 | 4341 | if (!LeaveNestedScope(cx, bce, &stmtInfo)) |
michael@0 | 4342 | return false; |
michael@0 | 4343 | |
michael@0 | 4344 | return true; |
michael@0 | 4345 | } |
michael@0 | 4346 | |
michael@0 | 4347 | /* |
michael@0 | 4348 | * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
michael@0 | 4349 | * the comment on EmitSwitch. |
michael@0 | 4350 | */ |
michael@0 | 4351 | MOZ_NEVER_INLINE static bool |
michael@0 | 4352 | EmitLexicalScope(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 4353 | { |
michael@0 | 4354 | JS_ASSERT(pn->isKind(PNK_LEXICALSCOPE)); |
michael@0 | 4355 | |
michael@0 | 4356 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 4357 | if (!EnterBlockScope(cx, bce, &stmtInfo, pn->pn_objbox, 0)) |
michael@0 | 4358 | return false; |
michael@0 | 4359 | |
michael@0 | 4360 | if (!EmitTree(cx, bce, pn->pn_expr)) |
michael@0 | 4361 | return false; |
michael@0 | 4362 | |
michael@0 | 4363 | if (!LeaveNestedScope(cx, bce, &stmtInfo)) |
michael@0 | 4364 | return false; |
michael@0 | 4365 | |
michael@0 | 4366 | return true; |
michael@0 | 4367 | } |
michael@0 | 4368 | |
michael@0 | 4369 | static bool |
michael@0 | 4370 | EmitWith(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 4371 | { |
michael@0 | 4372 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 4373 | if (!EmitTree(cx, bce, pn->pn_left)) |
michael@0 | 4374 | return false; |
michael@0 | 4375 | if (!EnterNestedScope(cx, bce, &stmtInfo, pn->pn_binary_obj, STMT_WITH)) |
michael@0 | 4376 | return false; |
michael@0 | 4377 | if (!EmitTree(cx, bce, pn->pn_right)) |
michael@0 | 4378 | return false; |
michael@0 | 4379 | if (!LeaveNestedScope(cx, bce, &stmtInfo)) |
michael@0 | 4380 | return false; |
michael@0 | 4381 | return true; |
michael@0 | 4382 | } |
michael@0 | 4383 | |
michael@0 | 4384 | static bool |
michael@0 | 4385 | EmitForOf(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) |
michael@0 | 4386 | { |
michael@0 | 4387 | ParseNode *forHead = pn->pn_left; |
michael@0 | 4388 | ParseNode *forBody = pn->pn_right; |
michael@0 | 4389 | |
michael@0 | 4390 | ParseNode *pn1 = forHead->pn_kid1; |
michael@0 | 4391 | bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); |
michael@0 | 4392 | JS_ASSERT_IF(letDecl, pn1->isLet()); |
michael@0 | 4393 | |
michael@0 | 4394 | // If the left part is 'var x', emit code to define x if necessary using a |
michael@0 | 4395 | // prolog opcode, but do not emit a pop. |
michael@0 | 4396 | if (pn1) { |
michael@0 | 4397 | ParseNode *decl = letDecl ? pn1->pn_expr : pn1; |
michael@0 | 4398 | JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET)); |
michael@0 | 4399 | bce->emittingForInit = true; |
michael@0 | 4400 | if (!EmitVariables(cx, bce, decl, DefineVars)) |
michael@0 | 4401 | return false; |
michael@0 | 4402 | bce->emittingForInit = false; |
michael@0 | 4403 | } |
michael@0 | 4404 | |
michael@0 | 4405 | // For-of loops run with two values on the stack: the iterator and the |
michael@0 | 4406 | // current result object. |
michael@0 | 4407 | |
michael@0 | 4408 | // Compile the object expression to the right of 'of'. |
michael@0 | 4409 | if (!EmitTree(cx, bce, forHead->pn_kid3)) |
michael@0 | 4410 | return false; |
michael@0 | 4411 | |
michael@0 | 4412 | // Convert iterable to iterator. |
michael@0 | 4413 | if (Emit1(cx, bce, JSOP_DUP) < 0) // OBJ OBJ |
michael@0 | 4414 | return false; |
michael@0 | 4415 | if (!EmitAtomOp(cx, cx->names().std_iterator, JSOP_CALLPROP, bce)) // OBJ @@ITERATOR |
michael@0 | 4416 | return false; |
michael@0 | 4417 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // @@ITERATOR OBJ |
michael@0 | 4418 | return false; |
michael@0 | 4419 | if (EmitCall(cx, bce, JSOP_CALL, 0) < 0) // ITER |
michael@0 | 4420 | return false; |
michael@0 | 4421 | CheckTypeSet(cx, bce, JSOP_CALL); |
michael@0 | 4422 | |
michael@0 | 4423 | // Push a dummy result so that we properly enter iteration midstream. |
michael@0 | 4424 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) // ITER RESULT |
michael@0 | 4425 | return false; |
michael@0 | 4426 | |
michael@0 | 4427 | // Enter the block before the loop body, after evaluating the obj. |
michael@0 | 4428 | StmtInfoBCE letStmt(cx); |
michael@0 | 4429 | if (letDecl) { |
michael@0 | 4430 | if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0)) |
michael@0 | 4431 | return false; |
michael@0 | 4432 | } |
michael@0 | 4433 | |
michael@0 | 4434 | LoopStmtInfo stmtInfo(cx); |
michael@0 | 4435 | PushLoopStatement(bce, &stmtInfo, STMT_FOR_OF_LOOP, top); |
michael@0 | 4436 | |
michael@0 | 4437 | // Jump down to the loop condition to minimize overhead assuming at least |
michael@0 | 4438 | // one iteration, as the other loop forms do. Annotate so IonMonkey can |
michael@0 | 4439 | // find the loop-closing jump. |
michael@0 | 4440 | int noteIndex = NewSrcNote(cx, bce, SRC_FOR_OF); |
michael@0 | 4441 | if (noteIndex < 0) |
michael@0 | 4442 | return false; |
michael@0 | 4443 | ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0); |
michael@0 | 4444 | if (jmp < 0) |
michael@0 | 4445 | return false; |
michael@0 | 4446 | |
michael@0 | 4447 | top = bce->offset(); |
michael@0 | 4448 | SET_STATEMENT_TOP(&stmtInfo, top); |
michael@0 | 4449 | if (EmitLoopHead(cx, bce, nullptr) < 0) |
michael@0 | 4450 | return false; |
michael@0 | 4451 | |
michael@0 | 4452 | #ifdef DEBUG |
michael@0 | 4453 | int loopDepth = bce->stackDepth; |
michael@0 | 4454 | #endif |
michael@0 | 4455 | |
michael@0 | 4456 | // Emit code to assign result.value to the iteration variable. |
michael@0 | 4457 | if (Emit1(cx, bce, JSOP_DUP) < 0) // ITER RESULT RESULT |
michael@0 | 4458 | return false; |
michael@0 | 4459 | if (!EmitAtomOp(cx, cx->names().value, JSOP_GETPROP, bce)) // ITER RESULT VALUE |
michael@0 | 4460 | return false; |
michael@0 | 4461 | if (!EmitAssignment(cx, bce, forHead->pn_kid2, JSOP_NOP, nullptr)) // ITER RESULT VALUE |
michael@0 | 4462 | return false; |
michael@0 | 4463 | if (Emit1(cx, bce, JSOP_POP) < 0) // ITER RESULT |
michael@0 | 4464 | return false; |
michael@0 | 4465 | |
michael@0 | 4466 | // The stack should be balanced around the assignment opcode sequence. |
michael@0 | 4467 | JS_ASSERT(bce->stackDepth == loopDepth); |
michael@0 | 4468 | |
michael@0 | 4469 | // Emit code for the loop body. |
michael@0 | 4470 | if (!EmitTree(cx, bce, forBody)) |
michael@0 | 4471 | return false; |
michael@0 | 4472 | |
michael@0 | 4473 | // Set loop and enclosing "update" offsets, for continue. |
michael@0 | 4474 | StmtInfoBCE *stmt = &stmtInfo; |
michael@0 | 4475 | do { |
michael@0 | 4476 | stmt->update = bce->offset(); |
michael@0 | 4477 | } while ((stmt = stmt->down) != nullptr && stmt->type == STMT_LABEL); |
michael@0 | 4478 | |
michael@0 | 4479 | // COME FROM the beginning of the loop to here. |
michael@0 | 4480 | SetJumpOffsetAt(bce, jmp); |
michael@0 | 4481 | if (!EmitLoopEntry(cx, bce, nullptr)) |
michael@0 | 4482 | return false; |
michael@0 | 4483 | |
michael@0 | 4484 | if (Emit1(cx, bce, JSOP_POP) < 0) // ITER |
michael@0 | 4485 | return false; |
michael@0 | 4486 | if (Emit1(cx, bce, JSOP_DUP) < 0) // ITER ITER |
michael@0 | 4487 | return false; |
michael@0 | 4488 | if (Emit1(cx, bce, JSOP_DUP) < 0) // ITER ITER ITER |
michael@0 | 4489 | return false; |
michael@0 | 4490 | if (!EmitAtomOp(cx, cx->names().next, JSOP_CALLPROP, bce)) // ITER ITER NEXT |
michael@0 | 4491 | return false; |
michael@0 | 4492 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // ITER NEXT ITER |
michael@0 | 4493 | return false; |
michael@0 | 4494 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) // ITER NEXT ITER UNDEFINED |
michael@0 | 4495 | return false; |
michael@0 | 4496 | if (EmitCall(cx, bce, JSOP_CALL, 1) < 0) // ITER RESULT |
michael@0 | 4497 | return false; |
michael@0 | 4498 | CheckTypeSet(cx, bce, JSOP_CALL); |
michael@0 | 4499 | if (Emit1(cx, bce, JSOP_DUP) < 0) // ITER RESULT RESULT |
michael@0 | 4500 | return false; |
michael@0 | 4501 | if (!EmitAtomOp(cx, cx->names().done, JSOP_GETPROP, bce)) // ITER RESULT DONE? |
michael@0 | 4502 | return false; |
michael@0 | 4503 | |
michael@0 | 4504 | ptrdiff_t beq = EmitJump(cx, bce, JSOP_IFEQ, top - bce->offset()); // ITER RESULT |
michael@0 | 4505 | if (beq < 0) |
michael@0 | 4506 | return false; |
michael@0 | 4507 | |
michael@0 | 4508 | JS_ASSERT(bce->stackDepth == loopDepth); |
michael@0 | 4509 | |
michael@0 | 4510 | // Let Ion know where the closing jump of this loop is. |
michael@0 | 4511 | if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, beq - jmp)) |
michael@0 | 4512 | return false; |
michael@0 | 4513 | |
michael@0 | 4514 | // Fixup breaks and continues. |
michael@0 | 4515 | if (!PopStatementBCE(cx, bce)) |
michael@0 | 4516 | return false; |
michael@0 | 4517 | |
michael@0 | 4518 | if (letDecl) { |
michael@0 | 4519 | if (!LeaveNestedScope(cx, bce, &letStmt)) |
michael@0 | 4520 | return false; |
michael@0 | 4521 | } |
michael@0 | 4522 | |
michael@0 | 4523 | // Pop the result and the iter. |
michael@0 | 4524 | EMIT_UINT16_IMM_OP(JSOP_POPN, 2); |
michael@0 | 4525 | |
michael@0 | 4526 | return true; |
michael@0 | 4527 | } |
michael@0 | 4528 | |
michael@0 | 4529 | static bool |
michael@0 | 4530 | EmitForIn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) |
michael@0 | 4531 | { |
michael@0 | 4532 | ParseNode *forHead = pn->pn_left; |
michael@0 | 4533 | ParseNode *forBody = pn->pn_right; |
michael@0 | 4534 | |
michael@0 | 4535 | ParseNode *pn1 = forHead->pn_kid1; |
michael@0 | 4536 | bool letDecl = pn1 && pn1->isKind(PNK_LEXICALSCOPE); |
michael@0 | 4537 | JS_ASSERT_IF(letDecl, pn1->isLet()); |
michael@0 | 4538 | |
michael@0 | 4539 | /* |
michael@0 | 4540 | * If the left part is 'var x', emit code to define x if necessary |
michael@0 | 4541 | * using a prolog opcode, but do not emit a pop. If the left part was |
michael@0 | 4542 | * originally 'var x = i', the parser will have rewritten it; see |
michael@0 | 4543 | * Parser::forStatement. 'for (let x = i in o)' is mercifully banned. |
michael@0 | 4544 | */ |
michael@0 | 4545 | if (pn1) { |
michael@0 | 4546 | ParseNode *decl = letDecl ? pn1->pn_expr : pn1; |
michael@0 | 4547 | JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET)); |
michael@0 | 4548 | bce->emittingForInit = true; |
michael@0 | 4549 | if (!EmitVariables(cx, bce, decl, DefineVars)) |
michael@0 | 4550 | return false; |
michael@0 | 4551 | bce->emittingForInit = false; |
michael@0 | 4552 | } |
michael@0 | 4553 | |
michael@0 | 4554 | /* Compile the object expression to the right of 'in'. */ |
michael@0 | 4555 | if (!EmitTree(cx, bce, forHead->pn_kid3)) |
michael@0 | 4556 | return false; |
michael@0 | 4557 | |
michael@0 | 4558 | /* |
michael@0 | 4559 | * Emit a bytecode to convert top of stack value to the iterator |
michael@0 | 4560 | * object depending on the loop variant (for-in, for-each-in, or |
michael@0 | 4561 | * destructuring for-in). |
michael@0 | 4562 | */ |
michael@0 | 4563 | JS_ASSERT(pn->isOp(JSOP_ITER)); |
michael@0 | 4564 | if (Emit2(cx, bce, JSOP_ITER, (uint8_t) pn->pn_iflags) < 0) |
michael@0 | 4565 | return false; |
michael@0 | 4566 | |
michael@0 | 4567 | /* Enter the block before the loop body, after evaluating the obj. */ |
michael@0 | 4568 | StmtInfoBCE letStmt(cx); |
michael@0 | 4569 | if (letDecl) { |
michael@0 | 4570 | if (!EnterBlockScope(cx, bce, &letStmt, pn1->pn_objbox, 0)) |
michael@0 | 4571 | return false; |
michael@0 | 4572 | } |
michael@0 | 4573 | |
michael@0 | 4574 | LoopStmtInfo stmtInfo(cx); |
michael@0 | 4575 | PushLoopStatement(bce, &stmtInfo, STMT_FOR_IN_LOOP, top); |
michael@0 | 4576 | |
michael@0 | 4577 | /* Annotate so IonMonkey can find the loop-closing jump. */ |
michael@0 | 4578 | int noteIndex = NewSrcNote(cx, bce, SRC_FOR_IN); |
michael@0 | 4579 | if (noteIndex < 0) |
michael@0 | 4580 | return false; |
michael@0 | 4581 | |
michael@0 | 4582 | /* |
michael@0 | 4583 | * Jump down to the loop condition to minimize overhead assuming at |
michael@0 | 4584 | * least one iteration, as the other loop forms do. |
michael@0 | 4585 | */ |
michael@0 | 4586 | ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0); |
michael@0 | 4587 | if (jmp < 0) |
michael@0 | 4588 | return false; |
michael@0 | 4589 | |
michael@0 | 4590 | top = bce->offset(); |
michael@0 | 4591 | SET_STATEMENT_TOP(&stmtInfo, top); |
michael@0 | 4592 | if (EmitLoopHead(cx, bce, nullptr) < 0) |
michael@0 | 4593 | return false; |
michael@0 | 4594 | |
michael@0 | 4595 | #ifdef DEBUG |
michael@0 | 4596 | int loopDepth = bce->stackDepth; |
michael@0 | 4597 | #endif |
michael@0 | 4598 | |
michael@0 | 4599 | /* |
michael@0 | 4600 | * Emit code to get the next enumeration value and assign it to the |
michael@0 | 4601 | * left hand side. |
michael@0 | 4602 | */ |
michael@0 | 4603 | if (Emit1(cx, bce, JSOP_ITERNEXT) < 0) |
michael@0 | 4604 | return false; |
michael@0 | 4605 | if (!EmitAssignment(cx, bce, forHead->pn_kid2, JSOP_NOP, nullptr)) |
michael@0 | 4606 | return false; |
michael@0 | 4607 | |
michael@0 | 4608 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 4609 | return false; |
michael@0 | 4610 | |
michael@0 | 4611 | /* The stack should be balanced around the assignment opcode sequence. */ |
michael@0 | 4612 | JS_ASSERT(bce->stackDepth == loopDepth); |
michael@0 | 4613 | |
michael@0 | 4614 | /* Emit code for the loop body. */ |
michael@0 | 4615 | if (!EmitTree(cx, bce, forBody)) |
michael@0 | 4616 | return false; |
michael@0 | 4617 | |
michael@0 | 4618 | /* Set loop and enclosing "update" offsets, for continue. */ |
michael@0 | 4619 | StmtInfoBCE *stmt = &stmtInfo; |
michael@0 | 4620 | do { |
michael@0 | 4621 | stmt->update = bce->offset(); |
michael@0 | 4622 | } while ((stmt = stmt->down) != nullptr && stmt->type == STMT_LABEL); |
michael@0 | 4623 | |
michael@0 | 4624 | /* |
michael@0 | 4625 | * Fixup the goto that starts the loop to jump down to JSOP_MOREITER. |
michael@0 | 4626 | */ |
michael@0 | 4627 | SetJumpOffsetAt(bce, jmp); |
michael@0 | 4628 | if (!EmitLoopEntry(cx, bce, nullptr)) |
michael@0 | 4629 | return false; |
michael@0 | 4630 | if (Emit1(cx, bce, JSOP_MOREITER) < 0) |
michael@0 | 4631 | return false; |
michael@0 | 4632 | ptrdiff_t beq = EmitJump(cx, bce, JSOP_IFNE, top - bce->offset()); |
michael@0 | 4633 | if (beq < 0) |
michael@0 | 4634 | return false; |
michael@0 | 4635 | |
michael@0 | 4636 | /* Set the srcnote offset so we can find the closing jump. */ |
michael@0 | 4637 | if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, beq - jmp)) |
michael@0 | 4638 | return false; |
michael@0 | 4639 | |
michael@0 | 4640 | // Fix up breaks and continues. |
michael@0 | 4641 | if (!PopStatementBCE(cx, bce)) |
michael@0 | 4642 | return false; |
michael@0 | 4643 | |
michael@0 | 4644 | if (!bce->tryNoteList.append(JSTRY_ITER, bce->stackDepth, top, bce->offset())) |
michael@0 | 4645 | return false; |
michael@0 | 4646 | if (Emit1(cx, bce, JSOP_ENDITER) < 0) |
michael@0 | 4647 | return false; |
michael@0 | 4648 | |
michael@0 | 4649 | if (letDecl) { |
michael@0 | 4650 | if (!LeaveNestedScope(cx, bce, &letStmt)) |
michael@0 | 4651 | return false; |
michael@0 | 4652 | } |
michael@0 | 4653 | |
michael@0 | 4654 | return true; |
michael@0 | 4655 | } |
michael@0 | 4656 | |
michael@0 | 4657 | static bool |
michael@0 | 4658 | EmitNormalFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) |
michael@0 | 4659 | { |
michael@0 | 4660 | LoopStmtInfo stmtInfo(cx); |
michael@0 | 4661 | PushLoopStatement(bce, &stmtInfo, STMT_FOR_LOOP, top); |
michael@0 | 4662 | |
michael@0 | 4663 | ParseNode *forHead = pn->pn_left; |
michael@0 | 4664 | ParseNode *forBody = pn->pn_right; |
michael@0 | 4665 | |
michael@0 | 4666 | /* C-style for (init; cond; update) ... loop. */ |
michael@0 | 4667 | JSOp op = JSOP_POP; |
michael@0 | 4668 | ParseNode *pn3 = forHead->pn_kid1; |
michael@0 | 4669 | if (!pn3) { |
michael@0 | 4670 | // No initializer, but emit a nop so that there's somewhere to put the |
michael@0 | 4671 | // SRC_FOR annotation that IonBuilder will look for. |
michael@0 | 4672 | op = JSOP_NOP; |
michael@0 | 4673 | } else { |
michael@0 | 4674 | bce->emittingForInit = true; |
michael@0 | 4675 | if (pn3->isKind(PNK_ASSIGN)) { |
michael@0 | 4676 | JS_ASSERT(pn3->isOp(JSOP_NOP)); |
michael@0 | 4677 | if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op)) |
michael@0 | 4678 | return false; |
michael@0 | 4679 | } |
michael@0 | 4680 | if (op == JSOP_POP) { |
michael@0 | 4681 | if (!UpdateSourceCoordNotes(cx, bce, pn3->pn_pos.begin)) |
michael@0 | 4682 | return false; |
michael@0 | 4683 | if (!EmitTree(cx, bce, pn3)) |
michael@0 | 4684 | return false; |
michael@0 | 4685 | if (pn3->isKind(PNK_VAR) || pn3->isKind(PNK_CONST) || pn3->isKind(PNK_LET)) { |
michael@0 | 4686 | /* |
michael@0 | 4687 | * Check whether a destructuring-initialized var decl |
michael@0 | 4688 | * was optimized to a group assignment. If so, we do |
michael@0 | 4689 | * not need to emit a pop below, so switch to a nop, |
michael@0 | 4690 | * just for IonBuilder. |
michael@0 | 4691 | */ |
michael@0 | 4692 | JS_ASSERT(pn3->isArity(PN_LIST) || pn3->isArity(PN_BINARY)); |
michael@0 | 4693 | if (pn3->pn_xflags & PNX_GROUPINIT) |
michael@0 | 4694 | op = JSOP_NOP; |
michael@0 | 4695 | } |
michael@0 | 4696 | } |
michael@0 | 4697 | bce->emittingForInit = false; |
michael@0 | 4698 | } |
michael@0 | 4699 | |
michael@0 | 4700 | /* |
michael@0 | 4701 | * NB: the SRC_FOR note has offsetBias 1 (JSOP_{NOP,POP}_LENGTH). |
michael@0 | 4702 | * Use tmp to hold the biased srcnote "top" offset, which differs |
michael@0 | 4703 | * from the top local variable by the length of the JSOP_GOTO |
michael@0 | 4704 | * emitted in between tmp and top if this loop has a condition. |
michael@0 | 4705 | */ |
michael@0 | 4706 | int noteIndex = NewSrcNote(cx, bce, SRC_FOR); |
michael@0 | 4707 | if (noteIndex < 0 || Emit1(cx, bce, op) < 0) |
michael@0 | 4708 | return false; |
michael@0 | 4709 | ptrdiff_t tmp = bce->offset(); |
michael@0 | 4710 | |
michael@0 | 4711 | ptrdiff_t jmp = -1; |
michael@0 | 4712 | if (forHead->pn_kid2) { |
michael@0 | 4713 | /* Goto the loop condition, which branches back to iterate. */ |
michael@0 | 4714 | jmp = EmitJump(cx, bce, JSOP_GOTO, 0); |
michael@0 | 4715 | if (jmp < 0) |
michael@0 | 4716 | return false; |
michael@0 | 4717 | } else { |
michael@0 | 4718 | if (op != JSOP_NOP && Emit1(cx, bce, JSOP_NOP) < 0) |
michael@0 | 4719 | return false; |
michael@0 | 4720 | } |
michael@0 | 4721 | |
michael@0 | 4722 | top = bce->offset(); |
michael@0 | 4723 | SET_STATEMENT_TOP(&stmtInfo, top); |
michael@0 | 4724 | |
michael@0 | 4725 | /* Emit code for the loop body. */ |
michael@0 | 4726 | if (EmitLoopHead(cx, bce, forBody) < 0) |
michael@0 | 4727 | return false; |
michael@0 | 4728 | if (jmp == -1 && !EmitLoopEntry(cx, bce, forBody)) |
michael@0 | 4729 | return false; |
michael@0 | 4730 | if (!EmitTree(cx, bce, forBody)) |
michael@0 | 4731 | return false; |
michael@0 | 4732 | |
michael@0 | 4733 | /* Set the second note offset so we can find the update part. */ |
michael@0 | 4734 | JS_ASSERT(noteIndex != -1); |
michael@0 | 4735 | ptrdiff_t tmp2 = bce->offset(); |
michael@0 | 4736 | |
michael@0 | 4737 | /* Set loop and enclosing "update" offsets, for continue. */ |
michael@0 | 4738 | StmtInfoBCE *stmt = &stmtInfo; |
michael@0 | 4739 | do { |
michael@0 | 4740 | stmt->update = bce->offset(); |
michael@0 | 4741 | } while ((stmt = stmt->down) != nullptr && stmt->type == STMT_LABEL); |
michael@0 | 4742 | |
michael@0 | 4743 | /* Check for update code to do before the condition (if any). */ |
michael@0 | 4744 | pn3 = forHead->pn_kid3; |
michael@0 | 4745 | if (pn3) { |
michael@0 | 4746 | if (!UpdateSourceCoordNotes(cx, bce, pn3->pn_pos.begin)) |
michael@0 | 4747 | return false; |
michael@0 | 4748 | op = JSOP_POP; |
michael@0 | 4749 | if (pn3->isKind(PNK_ASSIGN)) { |
michael@0 | 4750 | JS_ASSERT(pn3->isOp(JSOP_NOP)); |
michael@0 | 4751 | if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op)) |
michael@0 | 4752 | return false; |
michael@0 | 4753 | } |
michael@0 | 4754 | if (op == JSOP_POP && !EmitTree(cx, bce, pn3)) |
michael@0 | 4755 | return false; |
michael@0 | 4756 | |
michael@0 | 4757 | /* Always emit the POP or NOP to help IonBuilder. */ |
michael@0 | 4758 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 4759 | return false; |
michael@0 | 4760 | |
michael@0 | 4761 | /* Restore the absolute line number for source note readers. */ |
michael@0 | 4762 | uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.end); |
michael@0 | 4763 | if (bce->currentLine() != lineNum) { |
michael@0 | 4764 | if (NewSrcNote2(cx, bce, SRC_SETLINE, ptrdiff_t(lineNum)) < 0) |
michael@0 | 4765 | return false; |
michael@0 | 4766 | bce->current->currentLine = lineNum; |
michael@0 | 4767 | bce->current->lastColumn = 0; |
michael@0 | 4768 | } |
michael@0 | 4769 | } |
michael@0 | 4770 | |
michael@0 | 4771 | ptrdiff_t tmp3 = bce->offset(); |
michael@0 | 4772 | |
michael@0 | 4773 | if (forHead->pn_kid2) { |
michael@0 | 4774 | /* Fix up the goto from top to target the loop condition. */ |
michael@0 | 4775 | JS_ASSERT(jmp >= 0); |
michael@0 | 4776 | SetJumpOffsetAt(bce, jmp); |
michael@0 | 4777 | if (!EmitLoopEntry(cx, bce, forHead->pn_kid2)) |
michael@0 | 4778 | return false; |
michael@0 | 4779 | |
michael@0 | 4780 | if (!EmitTree(cx, bce, forHead->pn_kid2)) |
michael@0 | 4781 | return false; |
michael@0 | 4782 | } |
michael@0 | 4783 | |
michael@0 | 4784 | /* Set the first note offset so we can find the loop condition. */ |
michael@0 | 4785 | if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 0, tmp3 - tmp)) |
michael@0 | 4786 | return false; |
michael@0 | 4787 | if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 1, tmp2 - tmp)) |
michael@0 | 4788 | return false; |
michael@0 | 4789 | /* The third note offset helps us find the loop-closing jump. */ |
michael@0 | 4790 | if (!SetSrcNoteOffset(cx, bce, (unsigned)noteIndex, 2, bce->offset() - tmp)) |
michael@0 | 4791 | return false; |
michael@0 | 4792 | |
michael@0 | 4793 | /* If no loop condition, just emit a loop-closing jump. */ |
michael@0 | 4794 | op = forHead->pn_kid2 ? JSOP_IFNE : JSOP_GOTO; |
michael@0 | 4795 | if (EmitJump(cx, bce, op, top - bce->offset()) < 0) |
michael@0 | 4796 | return false; |
michael@0 | 4797 | |
michael@0 | 4798 | if (!bce->tryNoteList.append(JSTRY_LOOP, bce->stackDepth, top, bce->offset())) |
michael@0 | 4799 | return false; |
michael@0 | 4800 | |
michael@0 | 4801 | /* Now fixup all breaks and continues. */ |
michael@0 | 4802 | return PopStatementBCE(cx, bce); |
michael@0 | 4803 | } |
michael@0 | 4804 | |
michael@0 | 4805 | static inline bool |
michael@0 | 4806 | EmitFor(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) |
michael@0 | 4807 | { |
michael@0 | 4808 | if (pn->pn_left->isKind(PNK_FORIN)) |
michael@0 | 4809 | return EmitForIn(cx, bce, pn, top); |
michael@0 | 4810 | |
michael@0 | 4811 | if (pn->pn_left->isKind(PNK_FOROF)) |
michael@0 | 4812 | return EmitForOf(cx, bce, pn, top); |
michael@0 | 4813 | |
michael@0 | 4814 | JS_ASSERT(pn->pn_left->isKind(PNK_FORHEAD)); |
michael@0 | 4815 | return EmitNormalFor(cx, bce, pn, top); |
michael@0 | 4816 | } |
michael@0 | 4817 | |
michael@0 | 4818 | static MOZ_NEVER_INLINE bool |
michael@0 | 4819 | EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 4820 | { |
michael@0 | 4821 | FunctionBox *funbox = pn->pn_funbox; |
michael@0 | 4822 | RootedFunction fun(cx, funbox->function()); |
michael@0 | 4823 | JS_ASSERT_IF(fun->isInterpretedLazy(), fun->lazyScript()); |
michael@0 | 4824 | |
michael@0 | 4825 | /* |
michael@0 | 4826 | * Set the EMITTEDFUNCTION flag in function definitions once they have been |
michael@0 | 4827 | * emitted. Function definitions that need hoisting to the top of the |
michael@0 | 4828 | * function will be seen by EmitFunc in two places. |
michael@0 | 4829 | */ |
michael@0 | 4830 | if (pn->pn_dflags & PND_EMITTEDFUNCTION) { |
michael@0 | 4831 | JS_ASSERT_IF(fun->hasScript(), fun->nonLazyScript()); |
michael@0 | 4832 | JS_ASSERT(pn->functionIsHoisted()); |
michael@0 | 4833 | JS_ASSERT(bce->sc->isFunctionBox()); |
michael@0 | 4834 | return true; |
michael@0 | 4835 | } |
michael@0 | 4836 | |
michael@0 | 4837 | pn->pn_dflags |= PND_EMITTEDFUNCTION; |
michael@0 | 4838 | |
michael@0 | 4839 | /* |
michael@0 | 4840 | * Mark as singletons any function which will only be executed once, or |
michael@0 | 4841 | * which is inner to a lambda we only expect to run once. In the latter |
michael@0 | 4842 | * case, if the lambda runs multiple times then CloneFunctionObject will |
michael@0 | 4843 | * make a deep clone of its contents. |
michael@0 | 4844 | */ |
michael@0 | 4845 | if (fun->isInterpreted()) { |
michael@0 | 4846 | bool singleton = |
michael@0 | 4847 | bce->script->compileAndGo() && |
michael@0 | 4848 | fun->isInterpreted() && |
michael@0 | 4849 | (bce->checkSingletonContext() || |
michael@0 | 4850 | (!bce->isInLoop() && bce->isRunOnceLambda())); |
michael@0 | 4851 | if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) |
michael@0 | 4852 | return false; |
michael@0 | 4853 | |
michael@0 | 4854 | if (fun->isInterpretedLazy()) { |
michael@0 | 4855 | if (!fun->lazyScript()->sourceObject()) { |
michael@0 | 4856 | JSObject *scope = bce->staticScope; |
michael@0 | 4857 | if (!scope && bce->sc->isFunctionBox()) |
michael@0 | 4858 | scope = bce->sc->asFunctionBox()->function(); |
michael@0 | 4859 | JSObject *source = bce->script->sourceObject(); |
michael@0 | 4860 | fun->lazyScript()->setParent(scope, &source->as<ScriptSourceObject>()); |
michael@0 | 4861 | } |
michael@0 | 4862 | if (bce->emittingRunOnceLambda) |
michael@0 | 4863 | fun->lazyScript()->setTreatAsRunOnce(); |
michael@0 | 4864 | } else { |
michael@0 | 4865 | SharedContext *outersc = bce->sc; |
michael@0 | 4866 | |
michael@0 | 4867 | if (outersc->isFunctionBox() && outersc->asFunctionBox()->mightAliasLocals()) |
michael@0 | 4868 | funbox->setMightAliasLocals(); // inherit mightAliasLocals from parent |
michael@0 | 4869 | JS_ASSERT_IF(outersc->strict, funbox->strict); |
michael@0 | 4870 | |
michael@0 | 4871 | // Inherit most things (principals, version, etc) from the parent. |
michael@0 | 4872 | Rooted<JSScript*> parent(cx, bce->script); |
michael@0 | 4873 | CompileOptions options(cx, bce->parser->options()); |
michael@0 | 4874 | options.setOriginPrincipals(parent->originPrincipals()) |
michael@0 | 4875 | .setCompileAndGo(parent->compileAndGo()) |
michael@0 | 4876 | .setSelfHostingMode(parent->selfHosted()) |
michael@0 | 4877 | .setNoScriptRval(false) |
michael@0 | 4878 | .setForEval(false) |
michael@0 | 4879 | .setVersion(parent->getVersion()); |
michael@0 | 4880 | |
michael@0 | 4881 | Rooted<JSObject*> enclosingScope(cx, EnclosingStaticScope(bce)); |
michael@0 | 4882 | Rooted<JSObject*> sourceObject(cx, bce->script->sourceObject()); |
michael@0 | 4883 | Rooted<JSScript*> script(cx, JSScript::Create(cx, enclosingScope, false, options, |
michael@0 | 4884 | parent->staticLevel() + 1, |
michael@0 | 4885 | sourceObject, |
michael@0 | 4886 | funbox->bufStart, funbox->bufEnd)); |
michael@0 | 4887 | if (!script) |
michael@0 | 4888 | return false; |
michael@0 | 4889 | |
michael@0 | 4890 | script->bindings = funbox->bindings; |
michael@0 | 4891 | |
michael@0 | 4892 | uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); |
michael@0 | 4893 | BytecodeEmitter bce2(bce, bce->parser, funbox, script, bce->insideEval, |
michael@0 | 4894 | bce->evalCaller, bce->hasGlobalScope, lineNum, |
michael@0 | 4895 | bce->emitterMode); |
michael@0 | 4896 | if (!bce2.init()) |
michael@0 | 4897 | return false; |
michael@0 | 4898 | |
michael@0 | 4899 | /* We measured the max scope depth when we parsed the function. */ |
michael@0 | 4900 | if (!EmitFunctionScript(cx, &bce2, pn->pn_body)) |
michael@0 | 4901 | return false; |
michael@0 | 4902 | |
michael@0 | 4903 | if (funbox->usesArguments && funbox->usesApply) |
michael@0 | 4904 | script->setUsesArgumentsAndApply(); |
michael@0 | 4905 | } |
michael@0 | 4906 | } else { |
michael@0 | 4907 | JS_ASSERT(IsAsmJSModuleNative(fun->native())); |
michael@0 | 4908 | } |
michael@0 | 4909 | |
michael@0 | 4910 | /* Make the function object a literal in the outer script's pool. */ |
michael@0 | 4911 | unsigned index = bce->objectList.add(pn->pn_funbox); |
michael@0 | 4912 | |
michael@0 | 4913 | /* Non-hoisted functions simply emit their respective op. */ |
michael@0 | 4914 | if (!pn->functionIsHoisted()) { |
michael@0 | 4915 | /* JSOP_LAMBDA_ARROW is always preceded by JSOP_THIS. */ |
michael@0 | 4916 | MOZ_ASSERT(fun->isArrow() == (pn->getOp() == JSOP_LAMBDA_ARROW)); |
michael@0 | 4917 | if (fun->isArrow() && Emit1(cx, bce, JSOP_THIS) < 0) |
michael@0 | 4918 | return false; |
michael@0 | 4919 | return EmitIndex32(cx, pn->getOp(), index, bce); |
michael@0 | 4920 | } |
michael@0 | 4921 | |
michael@0 | 4922 | /* |
michael@0 | 4923 | * For a script we emit the code as we parse. Thus the bytecode for |
michael@0 | 4924 | * top-level functions should go in the prolog to predefine their |
michael@0 | 4925 | * names in the variable object before the already-generated main code |
michael@0 | 4926 | * is executed. This extra work for top-level scripts is not necessary |
michael@0 | 4927 | * when we emit the code for a function. It is fully parsed prior to |
michael@0 | 4928 | * invocation of the emitter and calls to EmitTree for function |
michael@0 | 4929 | * definitions can be scheduled before generating the rest of code. |
michael@0 | 4930 | */ |
michael@0 | 4931 | if (!bce->sc->isFunctionBox()) { |
michael@0 | 4932 | JS_ASSERT(pn->pn_cookie.isFree()); |
michael@0 | 4933 | JS_ASSERT(pn->getOp() == JSOP_NOP); |
michael@0 | 4934 | JS_ASSERT(!bce->topStmt); |
michael@0 | 4935 | bce->switchToProlog(); |
michael@0 | 4936 | if (!EmitIndex32(cx, JSOP_DEFFUN, index, bce)) |
michael@0 | 4937 | return false; |
michael@0 | 4938 | if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin)) |
michael@0 | 4939 | return false; |
michael@0 | 4940 | bce->switchToMain(); |
michael@0 | 4941 | } else { |
michael@0 | 4942 | #ifdef DEBUG |
michael@0 | 4943 | BindingIter bi(bce->script); |
michael@0 | 4944 | while (bi->name() != fun->atom()) |
michael@0 | 4945 | bi++; |
michael@0 | 4946 | JS_ASSERT(bi->kind() == Binding::VARIABLE || bi->kind() == Binding::CONSTANT || |
michael@0 | 4947 | bi->kind() == Binding::ARGUMENT); |
michael@0 | 4948 | JS_ASSERT(bi.frameIndex() < JS_BIT(20)); |
michael@0 | 4949 | #endif |
michael@0 | 4950 | pn->pn_index = index; |
michael@0 | 4951 | if (!EmitIndexOp(cx, JSOP_LAMBDA, index, bce)) |
michael@0 | 4952 | return false; |
michael@0 | 4953 | JS_ASSERT(pn->getOp() == JSOP_GETLOCAL || pn->getOp() == JSOP_GETARG); |
michael@0 | 4954 | JSOp setOp = pn->getOp() == JSOP_GETLOCAL ? JSOP_SETLOCAL : JSOP_SETARG; |
michael@0 | 4955 | if (!EmitVarOp(cx, pn, setOp, bce)) |
michael@0 | 4956 | return false; |
michael@0 | 4957 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 4958 | return false; |
michael@0 | 4959 | } |
michael@0 | 4960 | |
michael@0 | 4961 | return true; |
michael@0 | 4962 | } |
michael@0 | 4963 | |
michael@0 | 4964 | static bool |
michael@0 | 4965 | EmitDo(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 4966 | { |
michael@0 | 4967 | /* Emit an annotated nop so IonBuilder can recognize the 'do' loop. */ |
michael@0 | 4968 | ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_WHILE); |
michael@0 | 4969 | if (noteIndex < 0 || Emit1(cx, bce, JSOP_NOP) < 0) |
michael@0 | 4970 | return false; |
michael@0 | 4971 | |
michael@0 | 4972 | ptrdiff_t noteIndex2 = NewSrcNote(cx, bce, SRC_WHILE); |
michael@0 | 4973 | if (noteIndex2 < 0) |
michael@0 | 4974 | return false; |
michael@0 | 4975 | |
michael@0 | 4976 | /* Compile the loop body. */ |
michael@0 | 4977 | ptrdiff_t top = EmitLoopHead(cx, bce, pn->pn_left); |
michael@0 | 4978 | if (top < 0) |
michael@0 | 4979 | return false; |
michael@0 | 4980 | |
michael@0 | 4981 | LoopStmtInfo stmtInfo(cx); |
michael@0 | 4982 | PushLoopStatement(bce, &stmtInfo, STMT_DO_LOOP, top); |
michael@0 | 4983 | |
michael@0 | 4984 | if (!EmitLoopEntry(cx, bce, nullptr)) |
michael@0 | 4985 | return false; |
michael@0 | 4986 | |
michael@0 | 4987 | if (!EmitTree(cx, bce, pn->pn_left)) |
michael@0 | 4988 | return false; |
michael@0 | 4989 | |
michael@0 | 4990 | /* Set loop and enclosing label update offsets, for continue. */ |
michael@0 | 4991 | ptrdiff_t off = bce->offset(); |
michael@0 | 4992 | StmtInfoBCE *stmt = &stmtInfo; |
michael@0 | 4993 | do { |
michael@0 | 4994 | stmt->update = off; |
michael@0 | 4995 | } while ((stmt = stmt->down) != nullptr && stmt->type == STMT_LABEL); |
michael@0 | 4996 | |
michael@0 | 4997 | /* Compile the loop condition, now that continues know where to go. */ |
michael@0 | 4998 | if (!EmitTree(cx, bce, pn->pn_right)) |
michael@0 | 4999 | return false; |
michael@0 | 5000 | |
michael@0 | 5001 | ptrdiff_t beq = EmitJump(cx, bce, JSOP_IFNE, top - bce->offset()); |
michael@0 | 5002 | if (beq < 0) |
michael@0 | 5003 | return false; |
michael@0 | 5004 | |
michael@0 | 5005 | if (!bce->tryNoteList.append(JSTRY_LOOP, bce->stackDepth, top, bce->offset())) |
michael@0 | 5006 | return false; |
michael@0 | 5007 | |
michael@0 | 5008 | /* |
michael@0 | 5009 | * Update the annotations with the update and back edge positions, for |
michael@0 | 5010 | * IonBuilder. |
michael@0 | 5011 | * |
michael@0 | 5012 | * Be careful: We must set noteIndex2 before noteIndex in case the noteIndex |
michael@0 | 5013 | * note gets bigger. |
michael@0 | 5014 | */ |
michael@0 | 5015 | if (!SetSrcNoteOffset(cx, bce, noteIndex2, 0, beq - top)) |
michael@0 | 5016 | return false; |
michael@0 | 5017 | if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, 1 + (off - top))) |
michael@0 | 5018 | return false; |
michael@0 | 5019 | |
michael@0 | 5020 | return PopStatementBCE(cx, bce); |
michael@0 | 5021 | } |
michael@0 | 5022 | |
michael@0 | 5023 | static bool |
michael@0 | 5024 | EmitWhile(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) |
michael@0 | 5025 | { |
michael@0 | 5026 | /* |
michael@0 | 5027 | * Minimize bytecodes issued for one or more iterations by jumping to |
michael@0 | 5028 | * the condition below the body and closing the loop if the condition |
michael@0 | 5029 | * is true with a backward branch. For iteration count i: |
michael@0 | 5030 | * |
michael@0 | 5031 | * i test at the top test at the bottom |
michael@0 | 5032 | * = =============== ================== |
michael@0 | 5033 | * 0 ifeq-pass goto; ifne-fail |
michael@0 | 5034 | * 1 ifeq-fail; goto; ifne-pass goto; ifne-pass; ifne-fail |
michael@0 | 5035 | * 2 2*(ifeq-fail; goto); ifeq-pass goto; 2*ifne-pass; ifne-fail |
michael@0 | 5036 | * . . . |
michael@0 | 5037 | * N N*(ifeq-fail; goto); ifeq-pass goto; N*ifne-pass; ifne-fail |
michael@0 | 5038 | */ |
michael@0 | 5039 | LoopStmtInfo stmtInfo(cx); |
michael@0 | 5040 | PushLoopStatement(bce, &stmtInfo, STMT_WHILE_LOOP, top); |
michael@0 | 5041 | |
michael@0 | 5042 | ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_WHILE); |
michael@0 | 5043 | if (noteIndex < 0) |
michael@0 | 5044 | return false; |
michael@0 | 5045 | |
michael@0 | 5046 | ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0); |
michael@0 | 5047 | if (jmp < 0) |
michael@0 | 5048 | return false; |
michael@0 | 5049 | |
michael@0 | 5050 | top = EmitLoopHead(cx, bce, pn->pn_right); |
michael@0 | 5051 | if (top < 0) |
michael@0 | 5052 | return false; |
michael@0 | 5053 | |
michael@0 | 5054 | if (!EmitTree(cx, bce, pn->pn_right)) |
michael@0 | 5055 | return false; |
michael@0 | 5056 | |
michael@0 | 5057 | SetJumpOffsetAt(bce, jmp); |
michael@0 | 5058 | if (!EmitLoopEntry(cx, bce, pn->pn_left)) |
michael@0 | 5059 | return false; |
michael@0 | 5060 | if (!EmitTree(cx, bce, pn->pn_left)) |
michael@0 | 5061 | return false; |
michael@0 | 5062 | |
michael@0 | 5063 | ptrdiff_t beq = EmitJump(cx, bce, JSOP_IFNE, top - bce->offset()); |
michael@0 | 5064 | if (beq < 0) |
michael@0 | 5065 | return false; |
michael@0 | 5066 | |
michael@0 | 5067 | if (!bce->tryNoteList.append(JSTRY_LOOP, bce->stackDepth, top, bce->offset())) |
michael@0 | 5068 | return false; |
michael@0 | 5069 | |
michael@0 | 5070 | if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, beq - jmp)) |
michael@0 | 5071 | return false; |
michael@0 | 5072 | |
michael@0 | 5073 | return PopStatementBCE(cx, bce); |
michael@0 | 5074 | } |
michael@0 | 5075 | |
michael@0 | 5076 | static bool |
michael@0 | 5077 | EmitBreak(ExclusiveContext *cx, BytecodeEmitter *bce, PropertyName *label) |
michael@0 | 5078 | { |
michael@0 | 5079 | StmtInfoBCE *stmt = bce->topStmt; |
michael@0 | 5080 | SrcNoteType noteType; |
michael@0 | 5081 | if (label) { |
michael@0 | 5082 | while (stmt->type != STMT_LABEL || stmt->label != label) |
michael@0 | 5083 | stmt = stmt->down; |
michael@0 | 5084 | noteType = SRC_BREAK2LABEL; |
michael@0 | 5085 | } else { |
michael@0 | 5086 | while (!stmt->isLoop() && stmt->type != STMT_SWITCH) |
michael@0 | 5087 | stmt = stmt->down; |
michael@0 | 5088 | noteType = (stmt->type == STMT_SWITCH) ? SRC_SWITCHBREAK : SRC_BREAK; |
michael@0 | 5089 | } |
michael@0 | 5090 | |
michael@0 | 5091 | return EmitGoto(cx, bce, stmt, &stmt->breaks, noteType) >= 0; |
michael@0 | 5092 | } |
michael@0 | 5093 | |
michael@0 | 5094 | static bool |
michael@0 | 5095 | EmitContinue(ExclusiveContext *cx, BytecodeEmitter *bce, PropertyName *label) |
michael@0 | 5096 | { |
michael@0 | 5097 | StmtInfoBCE *stmt = bce->topStmt; |
michael@0 | 5098 | if (label) { |
michael@0 | 5099 | /* Find the loop statement enclosed by the matching label. */ |
michael@0 | 5100 | StmtInfoBCE *loop = nullptr; |
michael@0 | 5101 | while (stmt->type != STMT_LABEL || stmt->label != label) { |
michael@0 | 5102 | if (stmt->isLoop()) |
michael@0 | 5103 | loop = stmt; |
michael@0 | 5104 | stmt = stmt->down; |
michael@0 | 5105 | } |
michael@0 | 5106 | stmt = loop; |
michael@0 | 5107 | } else { |
michael@0 | 5108 | while (!stmt->isLoop()) |
michael@0 | 5109 | stmt = stmt->down; |
michael@0 | 5110 | } |
michael@0 | 5111 | |
michael@0 | 5112 | return EmitGoto(cx, bce, stmt, &stmt->continues, SRC_CONTINUE) >= 0; |
michael@0 | 5113 | } |
michael@0 | 5114 | |
michael@0 | 5115 | static bool |
michael@0 | 5116 | EmitReturn(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 5117 | { |
michael@0 | 5118 | if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin)) |
michael@0 | 5119 | return false; |
michael@0 | 5120 | |
michael@0 | 5121 | if (bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isStarGenerator()) { |
michael@0 | 5122 | if (!EmitPrepareIteratorResult(cx, bce)) |
michael@0 | 5123 | return false; |
michael@0 | 5124 | } |
michael@0 | 5125 | |
michael@0 | 5126 | /* Push a return value */ |
michael@0 | 5127 | if (ParseNode *pn2 = pn->pn_kid) { |
michael@0 | 5128 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5129 | return false; |
michael@0 | 5130 | } else { |
michael@0 | 5131 | /* No explicit return value provided */ |
michael@0 | 5132 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) |
michael@0 | 5133 | return false; |
michael@0 | 5134 | } |
michael@0 | 5135 | |
michael@0 | 5136 | if (bce->sc->isFunctionBox() && bce->sc->asFunctionBox()->isStarGenerator()) { |
michael@0 | 5137 | if (!EmitFinishIteratorResult(cx, bce, true)) |
michael@0 | 5138 | return false; |
michael@0 | 5139 | } |
michael@0 | 5140 | |
michael@0 | 5141 | /* |
michael@0 | 5142 | * EmitNonLocalJumpFixup may add fixup bytecode to close open try |
michael@0 | 5143 | * blocks having finally clauses and to exit intermingled let blocks. |
michael@0 | 5144 | * We can't simply transfer control flow to our caller in that case, |
michael@0 | 5145 | * because we must gosub to those finally clauses from inner to outer, |
michael@0 | 5146 | * with the correct stack pointer (i.e., after popping any with, |
michael@0 | 5147 | * for/in, etc., slots nested inside the finally's try). |
michael@0 | 5148 | * |
michael@0 | 5149 | * In this case we mutate JSOP_RETURN into JSOP_SETRVAL and add an |
michael@0 | 5150 | * extra JSOP_RETRVAL after the fixups. |
michael@0 | 5151 | */ |
michael@0 | 5152 | ptrdiff_t top = bce->offset(); |
michael@0 | 5153 | |
michael@0 | 5154 | if (Emit1(cx, bce, JSOP_RETURN) < 0) |
michael@0 | 5155 | return false; |
michael@0 | 5156 | |
michael@0 | 5157 | NonLocalExitScope nle(cx, bce); |
michael@0 | 5158 | |
michael@0 | 5159 | if (!nle.prepareForNonLocalJump(nullptr)) |
michael@0 | 5160 | return false; |
michael@0 | 5161 | |
michael@0 | 5162 | if (top + static_cast<ptrdiff_t>(JSOP_RETURN_LENGTH) != bce->offset()) { |
michael@0 | 5163 | bce->code()[top] = JSOP_SETRVAL; |
michael@0 | 5164 | if (Emit1(cx, bce, JSOP_RETRVAL) < 0) |
michael@0 | 5165 | return false; |
michael@0 | 5166 | } |
michael@0 | 5167 | |
michael@0 | 5168 | return true; |
michael@0 | 5169 | } |
michael@0 | 5170 | |
michael@0 | 5171 | static bool |
michael@0 | 5172 | EmitYieldStar(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *iter) |
michael@0 | 5173 | { |
michael@0 | 5174 | JS_ASSERT(bce->sc->isFunctionBox()); |
michael@0 | 5175 | JS_ASSERT(bce->sc->asFunctionBox()->isStarGenerator()); |
michael@0 | 5176 | |
michael@0 | 5177 | if (!EmitTree(cx, bce, iter)) // ITERABLE |
michael@0 | 5178 | return false; |
michael@0 | 5179 | |
michael@0 | 5180 | // Convert iterable to iterator. |
michael@0 | 5181 | if (Emit1(cx, bce, JSOP_DUP) < 0) // ITERABLE ITERABLE |
michael@0 | 5182 | return false; |
michael@0 | 5183 | if (!EmitAtomOp(cx, cx->names().std_iterator, JSOP_CALLPROP, bce)) // ITERABLE @@ITERATOR |
michael@0 | 5184 | return false; |
michael@0 | 5185 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // @@ITERATOR ITERABLE |
michael@0 | 5186 | return false; |
michael@0 | 5187 | if (EmitCall(cx, bce, JSOP_CALL, 0) < 0) // ITER |
michael@0 | 5188 | return false; |
michael@0 | 5189 | CheckTypeSet(cx, bce, JSOP_CALL); |
michael@0 | 5190 | |
michael@0 | 5191 | int depth = bce->stackDepth; |
michael@0 | 5192 | JS_ASSERT(depth >= 1); |
michael@0 | 5193 | |
michael@0 | 5194 | // Initial send value is undefined. |
michael@0 | 5195 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) // ITER RECEIVED |
michael@0 | 5196 | return false; |
michael@0 | 5197 | ptrdiff_t initialSend = -1; |
michael@0 | 5198 | if (EmitBackPatchOp(cx, bce, &initialSend) < 0) // goto initialSend |
michael@0 | 5199 | return false; |
michael@0 | 5200 | |
michael@0 | 5201 | // Try prologue. // ITER RESULT |
michael@0 | 5202 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 5203 | PushStatementBCE(bce, &stmtInfo, STMT_TRY, bce->offset()); |
michael@0 | 5204 | ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_TRY); |
michael@0 | 5205 | if (noteIndex < 0 || Emit1(cx, bce, JSOP_TRY) < 0) |
michael@0 | 5206 | return false; |
michael@0 | 5207 | ptrdiff_t tryStart = bce->offset(); // tryStart: |
michael@0 | 5208 | JS_ASSERT(bce->stackDepth == depth + 1); |
michael@0 | 5209 | |
michael@0 | 5210 | // Yield RESULT as-is, without re-boxing. |
michael@0 | 5211 | if (Emit1(cx, bce, JSOP_YIELD) < 0) // ITER RECEIVED |
michael@0 | 5212 | return false; |
michael@0 | 5213 | |
michael@0 | 5214 | // Try epilogue. |
michael@0 | 5215 | if (!SetSrcNoteOffset(cx, bce, noteIndex, 0, bce->offset() - tryStart + JSOP_TRY_LENGTH)) |
michael@0 | 5216 | return false; |
michael@0 | 5217 | ptrdiff_t subsequentSend = -1; |
michael@0 | 5218 | if (EmitBackPatchOp(cx, bce, &subsequentSend) < 0) // goto subsequentSend |
michael@0 | 5219 | return false; |
michael@0 | 5220 | ptrdiff_t tryEnd = bce->offset(); // tryEnd: |
michael@0 | 5221 | |
michael@0 | 5222 | // Catch location. |
michael@0 | 5223 | // THROW? = 'throw' in ITER // ITER |
michael@0 | 5224 | bce->stackDepth = (uint32_t) depth; |
michael@0 | 5225 | if (Emit1(cx, bce, JSOP_EXCEPTION) < 0) // ITER EXCEPTION |
michael@0 | 5226 | return false; |
michael@0 | 5227 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER |
michael@0 | 5228 | return false; |
michael@0 | 5229 | if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER |
michael@0 | 5230 | return false; |
michael@0 | 5231 | if (!EmitAtomOp(cx, cx->names().throw_, JSOP_STRING, bce)) // EXCEPTION ITER ITER "throw" |
michael@0 | 5232 | return false; |
michael@0 | 5233 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER "throw" ITER |
michael@0 | 5234 | return false; |
michael@0 | 5235 | if (Emit1(cx, bce, JSOP_IN) < 0) // EXCEPTION ITER THROW? |
michael@0 | 5236 | return false; |
michael@0 | 5237 | // if (THROW?) goto delegate |
michael@0 | 5238 | ptrdiff_t checkThrow = EmitJump(cx, bce, JSOP_IFNE, 0); // EXCEPTION ITER |
michael@0 | 5239 | if (checkThrow < 0) |
michael@0 | 5240 | return false; |
michael@0 | 5241 | if (Emit1(cx, bce, JSOP_POP) < 0) // EXCEPTION |
michael@0 | 5242 | return false; |
michael@0 | 5243 | if (Emit1(cx, bce, JSOP_THROW) < 0) // throw EXCEPTION |
michael@0 | 5244 | return false; |
michael@0 | 5245 | |
michael@0 | 5246 | SetJumpOffsetAt(bce, checkThrow); // delegate: |
michael@0 | 5247 | // RESULT = ITER.throw(EXCEPTION) // EXCEPTION ITER |
michael@0 | 5248 | bce->stackDepth = (uint32_t) depth + 1; |
michael@0 | 5249 | if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER |
michael@0 | 5250 | return false; |
michael@0 | 5251 | if (Emit1(cx, bce, JSOP_DUP) < 0) // EXCEPTION ITER ITER ITER |
michael@0 | 5252 | return false; |
michael@0 | 5253 | if (!EmitAtomOp(cx, cx->names().throw_, JSOP_CALLPROP, bce)) // EXCEPTION ITER ITER THROW |
michael@0 | 5254 | return false; |
michael@0 | 5255 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // EXCEPTION ITER THROW ITER |
michael@0 | 5256 | return false; |
michael@0 | 5257 | if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0) // ITER THROW ITER EXCEPTION |
michael@0 | 5258 | return false; |
michael@0 | 5259 | if (EmitCall(cx, bce, JSOP_CALL, 1) < 0) // ITER RESULT |
michael@0 | 5260 | return false; |
michael@0 | 5261 | CheckTypeSet(cx, bce, JSOP_CALL); |
michael@0 | 5262 | JS_ASSERT(bce->stackDepth == depth + 1); |
michael@0 | 5263 | ptrdiff_t checkResult = -1; |
michael@0 | 5264 | if (EmitBackPatchOp(cx, bce, &checkResult) < 0) // goto checkResult |
michael@0 | 5265 | return false; |
michael@0 | 5266 | |
michael@0 | 5267 | // Catch epilogue. |
michael@0 | 5268 | if (!PopStatementBCE(cx, bce)) |
michael@0 | 5269 | return false; |
michael@0 | 5270 | // This is a peace offering to ReconstructPCStack. See the note in EmitTry. |
michael@0 | 5271 | if (Emit1(cx, bce, JSOP_NOP) < 0) |
michael@0 | 5272 | return false; |
michael@0 | 5273 | if (!bce->tryNoteList.append(JSTRY_CATCH, depth, tryStart, tryEnd)) |
michael@0 | 5274 | return false; |
michael@0 | 5275 | |
michael@0 | 5276 | // After the try/catch block: send the received value to the iterator. |
michael@0 | 5277 | if (!BackPatch(cx, bce, initialSend, bce->code().end(), JSOP_GOTO)) // initialSend: |
michael@0 | 5278 | return false; |
michael@0 | 5279 | if (!BackPatch(cx, bce, subsequentSend, bce->code().end(), JSOP_GOTO)) // subsequentSend: |
michael@0 | 5280 | return false; |
michael@0 | 5281 | |
michael@0 | 5282 | // Send location. |
michael@0 | 5283 | // result = iter.next(received) // ITER RECEIVED |
michael@0 | 5284 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // RECEIVED ITER |
michael@0 | 5285 | return false; |
michael@0 | 5286 | if (Emit1(cx, bce, JSOP_DUP) < 0) // RECEIVED ITER ITER |
michael@0 | 5287 | return false; |
michael@0 | 5288 | if (Emit1(cx, bce, JSOP_DUP) < 0) // RECEIVED ITER ITER ITER |
michael@0 | 5289 | return false; |
michael@0 | 5290 | if (!EmitAtomOp(cx, cx->names().next, JSOP_CALLPROP, bce)) // RECEIVED ITER ITER NEXT |
michael@0 | 5291 | return false; |
michael@0 | 5292 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // RECEIVED ITER NEXT ITER |
michael@0 | 5293 | return false; |
michael@0 | 5294 | if (Emit2(cx, bce, JSOP_PICK, (jsbytecode)3) < 0) // ITER NEXT ITER RECEIVED |
michael@0 | 5295 | return false; |
michael@0 | 5296 | if (EmitCall(cx, bce, JSOP_CALL, 1) < 0) // ITER RESULT |
michael@0 | 5297 | return false; |
michael@0 | 5298 | CheckTypeSet(cx, bce, JSOP_CALL); |
michael@0 | 5299 | JS_ASSERT(bce->stackDepth == depth + 1); |
michael@0 | 5300 | |
michael@0 | 5301 | if (!BackPatch(cx, bce, checkResult, bce->code().end(), JSOP_GOTO)) // checkResult: |
michael@0 | 5302 | return false; |
michael@0 | 5303 | // if (!result.done) goto tryStart; // ITER RESULT |
michael@0 | 5304 | if (Emit1(cx, bce, JSOP_DUP) < 0) // ITER RESULT RESULT |
michael@0 | 5305 | return false; |
michael@0 | 5306 | if (!EmitAtomOp(cx, cx->names().done, JSOP_GETPROP, bce)) // ITER RESULT DONE |
michael@0 | 5307 | return false; |
michael@0 | 5308 | // if (!DONE) goto tryStart; |
michael@0 | 5309 | if (EmitJump(cx, bce, JSOP_IFEQ, tryStart - bce->offset()) < 0) // ITER RESULT |
michael@0 | 5310 | return false; |
michael@0 | 5311 | |
michael@0 | 5312 | // result.value |
michael@0 | 5313 | if (Emit1(cx, bce, JSOP_SWAP) < 0) // RESULT ITER |
michael@0 | 5314 | return false; |
michael@0 | 5315 | if (Emit1(cx, bce, JSOP_POP) < 0) // RESULT |
michael@0 | 5316 | return false; |
michael@0 | 5317 | if (!EmitAtomOp(cx, cx->names().value, JSOP_GETPROP, bce)) // VALUE |
michael@0 | 5318 | return false; |
michael@0 | 5319 | |
michael@0 | 5320 | JS_ASSERT(bce->stackDepth == depth); |
michael@0 | 5321 | |
michael@0 | 5322 | return true; |
michael@0 | 5323 | } |
michael@0 | 5324 | |
michael@0 | 5325 | static bool |
michael@0 | 5326 | EmitStatementList(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) |
michael@0 | 5327 | { |
michael@0 | 5328 | JS_ASSERT(pn->isArity(PN_LIST)); |
michael@0 | 5329 | |
michael@0 | 5330 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 5331 | PushStatementBCE(bce, &stmtInfo, STMT_BLOCK, top); |
michael@0 | 5332 | |
michael@0 | 5333 | ParseNode *pnchild = pn->pn_head; |
michael@0 | 5334 | |
michael@0 | 5335 | if (pn->pn_xflags & PNX_DESTRUCT) |
michael@0 | 5336 | pnchild = pnchild->pn_next; |
michael@0 | 5337 | |
michael@0 | 5338 | for (ParseNode *pn2 = pnchild; pn2; pn2 = pn2->pn_next) { |
michael@0 | 5339 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5340 | return false; |
michael@0 | 5341 | } |
michael@0 | 5342 | |
michael@0 | 5343 | return PopStatementBCE(cx, bce); |
michael@0 | 5344 | } |
michael@0 | 5345 | |
michael@0 | 5346 | static bool |
michael@0 | 5347 | EmitStatement(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 5348 | { |
michael@0 | 5349 | JS_ASSERT(pn->isKind(PNK_SEMI)); |
michael@0 | 5350 | |
michael@0 | 5351 | ParseNode *pn2 = pn->pn_kid; |
michael@0 | 5352 | if (!pn2) |
michael@0 | 5353 | return true; |
michael@0 | 5354 | |
michael@0 | 5355 | if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin)) |
michael@0 | 5356 | return false; |
michael@0 | 5357 | |
michael@0 | 5358 | /* |
michael@0 | 5359 | * Top-level or called-from-a-native JS_Execute/EvaluateScript, |
michael@0 | 5360 | * debugger, and eval frames may need the value of the ultimate |
michael@0 | 5361 | * expression statement as the script's result, despite the fact |
michael@0 | 5362 | * that it appears useless to the compiler. |
michael@0 | 5363 | * |
michael@0 | 5364 | * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when |
michael@0 | 5365 | * calling JS_Compile* to suppress JSOP_SETRVAL. |
michael@0 | 5366 | */ |
michael@0 | 5367 | bool wantval = false; |
michael@0 | 5368 | bool useful = false; |
michael@0 | 5369 | if (bce->sc->isFunctionBox()) { |
michael@0 | 5370 | JS_ASSERT(!bce->script->noScriptRval()); |
michael@0 | 5371 | } else { |
michael@0 | 5372 | useful = wantval = !bce->script->noScriptRval(); |
michael@0 | 5373 | } |
michael@0 | 5374 | |
michael@0 | 5375 | /* Don't eliminate expressions with side effects. */ |
michael@0 | 5376 | if (!useful) { |
michael@0 | 5377 | if (!CheckSideEffects(cx, bce, pn2, &useful)) |
michael@0 | 5378 | return false; |
michael@0 | 5379 | |
michael@0 | 5380 | /* |
michael@0 | 5381 | * Don't eliminate apparently useless expressions if they are |
michael@0 | 5382 | * labeled expression statements. The pc->topStmt->update test |
michael@0 | 5383 | * catches the case where we are nesting in EmitTree for a labeled |
michael@0 | 5384 | * compound statement. |
michael@0 | 5385 | */ |
michael@0 | 5386 | if (bce->topStmt && |
michael@0 | 5387 | bce->topStmt->type == STMT_LABEL && |
michael@0 | 5388 | bce->topStmt->update >= bce->offset()) |
michael@0 | 5389 | { |
michael@0 | 5390 | useful = true; |
michael@0 | 5391 | } |
michael@0 | 5392 | } |
michael@0 | 5393 | |
michael@0 | 5394 | if (useful) { |
michael@0 | 5395 | JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP; |
michael@0 | 5396 | JS_ASSERT_IF(pn2->isKind(PNK_ASSIGN), pn2->isOp(JSOP_NOP)); |
michael@0 | 5397 | if (!wantval && |
michael@0 | 5398 | pn2->isKind(PNK_ASSIGN) && |
michael@0 | 5399 | !MaybeEmitGroupAssignment(cx, bce, op, pn2, GroupIsNotDecl, &op)) |
michael@0 | 5400 | { |
michael@0 | 5401 | return false; |
michael@0 | 5402 | } |
michael@0 | 5403 | if (op != JSOP_NOP) { |
michael@0 | 5404 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5405 | return false; |
michael@0 | 5406 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 5407 | return false; |
michael@0 | 5408 | } |
michael@0 | 5409 | } else if (!pn->isDirectivePrologueMember()) { |
michael@0 | 5410 | /* Don't complain about directive prologue members; just don't emit their code. */ |
michael@0 | 5411 | bce->current->currentLine = bce->parser->tokenStream.srcCoords.lineNum(pn2->pn_pos.begin); |
michael@0 | 5412 | bce->current->lastColumn = 0; |
michael@0 | 5413 | if (!bce->reportStrictWarning(pn2, JSMSG_USELESS_EXPR)) |
michael@0 | 5414 | return false; |
michael@0 | 5415 | } |
michael@0 | 5416 | |
michael@0 | 5417 | return true; |
michael@0 | 5418 | } |
michael@0 | 5419 | |
michael@0 | 5420 | static bool |
michael@0 | 5421 | EmitDelete(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 5422 | { |
michael@0 | 5423 | /* |
michael@0 | 5424 | * Under ECMA 3, deleting a non-reference returns true -- but alas we |
michael@0 | 5425 | * must evaluate the operand if it appears it might have side effects. |
michael@0 | 5426 | */ |
michael@0 | 5427 | ParseNode *pn2 = pn->pn_kid; |
michael@0 | 5428 | switch (pn2->getKind()) { |
michael@0 | 5429 | case PNK_NAME: |
michael@0 | 5430 | { |
michael@0 | 5431 | if (!BindNameToSlot(cx, bce, pn2)) |
michael@0 | 5432 | return false; |
michael@0 | 5433 | JSOp op = pn2->getOp(); |
michael@0 | 5434 | if (op == JSOP_FALSE) { |
michael@0 | 5435 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 5436 | return false; |
michael@0 | 5437 | } else { |
michael@0 | 5438 | if (!EmitAtomOp(cx, pn2, op, bce)) |
michael@0 | 5439 | return false; |
michael@0 | 5440 | } |
michael@0 | 5441 | break; |
michael@0 | 5442 | } |
michael@0 | 5443 | case PNK_DOT: |
michael@0 | 5444 | if (!EmitPropOp(cx, pn2, JSOP_DELPROP, bce)) |
michael@0 | 5445 | return false; |
michael@0 | 5446 | break; |
michael@0 | 5447 | case PNK_ELEM: |
michael@0 | 5448 | if (!EmitElemOp(cx, pn2, JSOP_DELELEM, bce)) |
michael@0 | 5449 | return false; |
michael@0 | 5450 | break; |
michael@0 | 5451 | default: |
michael@0 | 5452 | { |
michael@0 | 5453 | /* |
michael@0 | 5454 | * If useless, just emit JSOP_TRUE; otherwise convert delete foo() |
michael@0 | 5455 | * to foo(), true (a comma expression). |
michael@0 | 5456 | */ |
michael@0 | 5457 | bool useful = false; |
michael@0 | 5458 | if (!CheckSideEffects(cx, bce, pn2, &useful)) |
michael@0 | 5459 | return false; |
michael@0 | 5460 | |
michael@0 | 5461 | if (useful) { |
michael@0 | 5462 | JS_ASSERT_IF(pn2->isKind(PNK_CALL), !(pn2->pn_xflags & PNX_SETCALL)); |
michael@0 | 5463 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5464 | return false; |
michael@0 | 5465 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 5466 | return false; |
michael@0 | 5467 | } |
michael@0 | 5468 | |
michael@0 | 5469 | if (Emit1(cx, bce, JSOP_TRUE) < 0) |
michael@0 | 5470 | return false; |
michael@0 | 5471 | } |
michael@0 | 5472 | } |
michael@0 | 5473 | |
michael@0 | 5474 | return true; |
michael@0 | 5475 | } |
michael@0 | 5476 | |
michael@0 | 5477 | static bool |
michael@0 | 5478 | EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, uint32_t count); |
michael@0 | 5479 | |
michael@0 | 5480 | static bool |
michael@0 | 5481 | EmitCallOrNew(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 5482 | { |
michael@0 | 5483 | bool callop = pn->isKind(PNK_CALL); |
michael@0 | 5484 | |
michael@0 | 5485 | /* |
michael@0 | 5486 | * Emit callable invocation or operator new (constructor call) code. |
michael@0 | 5487 | * First, emit code for the left operand to evaluate the callable or |
michael@0 | 5488 | * constructable object expression. |
michael@0 | 5489 | * |
michael@0 | 5490 | * For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc. |
michael@0 | 5491 | * This is necessary to interpose the lambda-initialized method read |
michael@0 | 5492 | * barrier -- see the code in jsinterp.cpp for JSOP_LAMBDA followed by |
michael@0 | 5493 | * JSOP_{SET,INIT}PROP. |
michael@0 | 5494 | * |
michael@0 | 5495 | * Then (or in a call case that has no explicit reference-base |
michael@0 | 5496 | * object) we emit JSOP_UNDEFINED to produce the undefined |this| |
michael@0 | 5497 | * value required for calls (which non-strict mode functions |
michael@0 | 5498 | * will box into the global object). |
michael@0 | 5499 | */ |
michael@0 | 5500 | uint32_t argc = pn->pn_count - 1; |
michael@0 | 5501 | |
michael@0 | 5502 | if (argc >= ARGC_LIMIT) { |
michael@0 | 5503 | bce->parser->tokenStream.reportError(callop |
michael@0 | 5504 | ? JSMSG_TOO_MANY_FUN_ARGS |
michael@0 | 5505 | : JSMSG_TOO_MANY_CON_ARGS); |
michael@0 | 5506 | return false; |
michael@0 | 5507 | } |
michael@0 | 5508 | |
michael@0 | 5509 | bool emitArgs = true; |
michael@0 | 5510 | ParseNode *pn2 = pn->pn_head; |
michael@0 | 5511 | bool spread = JOF_OPTYPE(pn->getOp()) == JOF_BYTE; |
michael@0 | 5512 | switch (pn2->getKind()) { |
michael@0 | 5513 | case PNK_NAME: |
michael@0 | 5514 | if (bce->emitterMode == BytecodeEmitter::SelfHosting && |
michael@0 | 5515 | pn2->name() == cx->names().callFunction && |
michael@0 | 5516 | !spread) |
michael@0 | 5517 | { |
michael@0 | 5518 | /* |
michael@0 | 5519 | * Special-casing of callFunction to emit bytecode that directly |
michael@0 | 5520 | * invokes the callee with the correct |this| object and arguments. |
michael@0 | 5521 | * callFunction(fun, thisArg, arg0, arg1) thus becomes: |
michael@0 | 5522 | * - emit lookup for fun |
michael@0 | 5523 | * - emit lookup for thisArg |
michael@0 | 5524 | * - emit lookups for arg0, arg1 |
michael@0 | 5525 | * |
michael@0 | 5526 | * argc is set to the amount of actually emitted args and the |
michael@0 | 5527 | * emitting of args below is disabled by setting emitArgs to false. |
michael@0 | 5528 | */ |
michael@0 | 5529 | if (pn->pn_count < 3) { |
michael@0 | 5530 | bce->reportError(pn, JSMSG_MORE_ARGS_NEEDED, "callFunction", "1", "s"); |
michael@0 | 5531 | return false; |
michael@0 | 5532 | } |
michael@0 | 5533 | ParseNode *funNode = pn2->pn_next; |
michael@0 | 5534 | if (!EmitTree(cx, bce, funNode)) |
michael@0 | 5535 | return false; |
michael@0 | 5536 | ParseNode *thisArg = funNode->pn_next; |
michael@0 | 5537 | if (!EmitTree(cx, bce, thisArg)) |
michael@0 | 5538 | return false; |
michael@0 | 5539 | bool oldEmittingForInit = bce->emittingForInit; |
michael@0 | 5540 | bce->emittingForInit = false; |
michael@0 | 5541 | for (ParseNode *argpn = thisArg->pn_next; argpn; argpn = argpn->pn_next) { |
michael@0 | 5542 | if (!EmitTree(cx, bce, argpn)) |
michael@0 | 5543 | return false; |
michael@0 | 5544 | } |
michael@0 | 5545 | bce->emittingForInit = oldEmittingForInit; |
michael@0 | 5546 | argc -= 2; |
michael@0 | 5547 | emitArgs = false; |
michael@0 | 5548 | break; |
michael@0 | 5549 | } |
michael@0 | 5550 | if (!EmitNameOp(cx, bce, pn2, callop)) |
michael@0 | 5551 | return false; |
michael@0 | 5552 | break; |
michael@0 | 5553 | case PNK_DOT: |
michael@0 | 5554 | if (!EmitPropOp(cx, pn2, callop ? JSOP_CALLPROP : JSOP_GETPROP, bce)) |
michael@0 | 5555 | return false; |
michael@0 | 5556 | break; |
michael@0 | 5557 | case PNK_ELEM: |
michael@0 | 5558 | if (!EmitElemOp(cx, pn2, callop ? JSOP_CALLELEM : JSOP_GETELEM, bce)) |
michael@0 | 5559 | return false; |
michael@0 | 5560 | break; |
michael@0 | 5561 | case PNK_FUNCTION: |
michael@0 | 5562 | /* |
michael@0 | 5563 | * Top level lambdas which are immediately invoked should be |
michael@0 | 5564 | * treated as only running once. Every time they execute we will |
michael@0 | 5565 | * create new types and scripts for their contents, to increase |
michael@0 | 5566 | * the quality of type information within them and enable more |
michael@0 | 5567 | * backend optimizations. Note that this does not depend on the |
michael@0 | 5568 | * lambda being invoked at most once (it may be named or be |
michael@0 | 5569 | * accessed via foo.caller indirection), as multiple executions |
michael@0 | 5570 | * will just cause the inner scripts to be repeatedly cloned. |
michael@0 | 5571 | */ |
michael@0 | 5572 | JS_ASSERT(!bce->emittingRunOnceLambda); |
michael@0 | 5573 | if (bce->checkSingletonContext() || (!bce->isInLoop() && bce->isRunOnceLambda())) { |
michael@0 | 5574 | bce->emittingRunOnceLambda = true; |
michael@0 | 5575 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5576 | return false; |
michael@0 | 5577 | bce->emittingRunOnceLambda = false; |
michael@0 | 5578 | } else { |
michael@0 | 5579 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5580 | return false; |
michael@0 | 5581 | } |
michael@0 | 5582 | callop = false; |
michael@0 | 5583 | break; |
michael@0 | 5584 | default: |
michael@0 | 5585 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5586 | return false; |
michael@0 | 5587 | callop = false; /* trigger JSOP_UNDEFINED after */ |
michael@0 | 5588 | break; |
michael@0 | 5589 | } |
michael@0 | 5590 | if (!callop) { |
michael@0 | 5591 | JSOp thisop = pn->isKind(PNK_GENEXP) ? JSOP_THIS : JSOP_UNDEFINED; |
michael@0 | 5592 | if (Emit1(cx, bce, thisop) < 0) |
michael@0 | 5593 | return false; |
michael@0 | 5594 | } |
michael@0 | 5595 | |
michael@0 | 5596 | if (emitArgs) { |
michael@0 | 5597 | /* |
michael@0 | 5598 | * Emit code for each argument in order, then emit the JSOP_*CALL or |
michael@0 | 5599 | * JSOP_NEW bytecode with a two-byte immediate telling how many args |
michael@0 | 5600 | * were pushed on the operand stack. |
michael@0 | 5601 | */ |
michael@0 | 5602 | bool oldEmittingForInit = bce->emittingForInit; |
michael@0 | 5603 | bce->emittingForInit = false; |
michael@0 | 5604 | if (!spread) { |
michael@0 | 5605 | for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) { |
michael@0 | 5606 | if (!EmitTree(cx, bce, pn3)) |
michael@0 | 5607 | return false; |
michael@0 | 5608 | } |
michael@0 | 5609 | } else { |
michael@0 | 5610 | if (!EmitArray(cx, bce, pn2->pn_next, argc)) |
michael@0 | 5611 | return false; |
michael@0 | 5612 | } |
michael@0 | 5613 | bce->emittingForInit = oldEmittingForInit; |
michael@0 | 5614 | } |
michael@0 | 5615 | |
michael@0 | 5616 | if (!spread) { |
michael@0 | 5617 | if (EmitCall(cx, bce, pn->getOp(), argc) < 0) |
michael@0 | 5618 | return false; |
michael@0 | 5619 | } else { |
michael@0 | 5620 | if (Emit1(cx, bce, pn->getOp()) < 0) |
michael@0 | 5621 | return false; |
michael@0 | 5622 | } |
michael@0 | 5623 | CheckTypeSet(cx, bce, pn->getOp()); |
michael@0 | 5624 | if (pn->isOp(JSOP_EVAL) || pn->isOp(JSOP_SPREADEVAL)) { |
michael@0 | 5625 | uint32_t lineNum = bce->parser->tokenStream.srcCoords.lineNum(pn->pn_pos.begin); |
michael@0 | 5626 | EMIT_UINT16_IMM_OP(JSOP_LINENO, lineNum); |
michael@0 | 5627 | } |
michael@0 | 5628 | if (pn->pn_xflags & PNX_SETCALL) { |
michael@0 | 5629 | if (Emit1(cx, bce, JSOP_SETCALL) < 0) |
michael@0 | 5630 | return false; |
michael@0 | 5631 | } |
michael@0 | 5632 | return true; |
michael@0 | 5633 | } |
michael@0 | 5634 | |
michael@0 | 5635 | static bool |
michael@0 | 5636 | EmitLogical(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 5637 | { |
michael@0 | 5638 | /* |
michael@0 | 5639 | * JSOP_OR converts the operand on the stack to boolean, leaves the original |
michael@0 | 5640 | * value on the stack and jumps if true; otherwise it falls into the next |
michael@0 | 5641 | * bytecode, which pops the left operand and then evaluates the right operand. |
michael@0 | 5642 | * The jump goes around the right operand evaluation. |
michael@0 | 5643 | * |
michael@0 | 5644 | * JSOP_AND converts the operand on the stack to boolean and jumps if false; |
michael@0 | 5645 | * otherwise it falls into the right operand's bytecode. |
michael@0 | 5646 | */ |
michael@0 | 5647 | |
michael@0 | 5648 | if (pn->isArity(PN_BINARY)) { |
michael@0 | 5649 | if (!EmitTree(cx, bce, pn->pn_left)) |
michael@0 | 5650 | return false; |
michael@0 | 5651 | ptrdiff_t top = EmitJump(cx, bce, JSOP_BACKPATCH, 0); |
michael@0 | 5652 | if (top < 0) |
michael@0 | 5653 | return false; |
michael@0 | 5654 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 5655 | return false; |
michael@0 | 5656 | if (!EmitTree(cx, bce, pn->pn_right)) |
michael@0 | 5657 | return false; |
michael@0 | 5658 | ptrdiff_t off = bce->offset(); |
michael@0 | 5659 | jsbytecode *pc = bce->code(top); |
michael@0 | 5660 | SET_JUMP_OFFSET(pc, off - top); |
michael@0 | 5661 | *pc = pn->getOp(); |
michael@0 | 5662 | return true; |
michael@0 | 5663 | } |
michael@0 | 5664 | |
michael@0 | 5665 | JS_ASSERT(pn->isArity(PN_LIST)); |
michael@0 | 5666 | JS_ASSERT(pn->pn_head->pn_next->pn_next); |
michael@0 | 5667 | |
michael@0 | 5668 | /* Left-associative operator chain: avoid too much recursion. */ |
michael@0 | 5669 | ParseNode *pn2 = pn->pn_head; |
michael@0 | 5670 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5671 | return false; |
michael@0 | 5672 | ptrdiff_t top = EmitJump(cx, bce, JSOP_BACKPATCH, 0); |
michael@0 | 5673 | if (top < 0) |
michael@0 | 5674 | return false; |
michael@0 | 5675 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 5676 | return false; |
michael@0 | 5677 | |
michael@0 | 5678 | /* Emit nodes between the head and the tail. */ |
michael@0 | 5679 | ptrdiff_t jmp = top; |
michael@0 | 5680 | while ((pn2 = pn2->pn_next)->pn_next) { |
michael@0 | 5681 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5682 | return false; |
michael@0 | 5683 | ptrdiff_t off = EmitJump(cx, bce, JSOP_BACKPATCH, 0); |
michael@0 | 5684 | if (off < 0) |
michael@0 | 5685 | return false; |
michael@0 | 5686 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 5687 | return false; |
michael@0 | 5688 | SET_JUMP_OFFSET(bce->code(jmp), off - jmp); |
michael@0 | 5689 | jmp = off; |
michael@0 | 5690 | } |
michael@0 | 5691 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5692 | return false; |
michael@0 | 5693 | |
michael@0 | 5694 | pn2 = pn->pn_head; |
michael@0 | 5695 | ptrdiff_t off = bce->offset(); |
michael@0 | 5696 | do { |
michael@0 | 5697 | jsbytecode *pc = bce->code(top); |
michael@0 | 5698 | ptrdiff_t tmp = GET_JUMP_OFFSET(pc); |
michael@0 | 5699 | SET_JUMP_OFFSET(pc, off - top); |
michael@0 | 5700 | *pc = pn->getOp(); |
michael@0 | 5701 | top += tmp; |
michael@0 | 5702 | } while ((pn2 = pn2->pn_next)->pn_next); |
michael@0 | 5703 | |
michael@0 | 5704 | return true; |
michael@0 | 5705 | } |
michael@0 | 5706 | |
michael@0 | 5707 | /* |
michael@0 | 5708 | * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
michael@0 | 5709 | * the comment on EmitSwitch. |
michael@0 | 5710 | */ |
michael@0 | 5711 | MOZ_NEVER_INLINE static bool |
michael@0 | 5712 | EmitIncOrDec(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 5713 | { |
michael@0 | 5714 | /* Emit lvalue-specialized code for ++/-- operators. */ |
michael@0 | 5715 | ParseNode *pn2 = pn->pn_kid; |
michael@0 | 5716 | switch (pn2->getKind()) { |
michael@0 | 5717 | case PNK_DOT: |
michael@0 | 5718 | if (!EmitPropIncDec(cx, pn, bce)) |
michael@0 | 5719 | return false; |
michael@0 | 5720 | break; |
michael@0 | 5721 | case PNK_ELEM: |
michael@0 | 5722 | if (!EmitElemIncDec(cx, pn, bce)) |
michael@0 | 5723 | return false; |
michael@0 | 5724 | break; |
michael@0 | 5725 | case PNK_CALL: |
michael@0 | 5726 | JS_ASSERT(pn2->pn_xflags & PNX_SETCALL); |
michael@0 | 5727 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5728 | return false; |
michael@0 | 5729 | break; |
michael@0 | 5730 | default: |
michael@0 | 5731 | JS_ASSERT(pn2->isKind(PNK_NAME)); |
michael@0 | 5732 | pn2->setOp(JSOP_SETNAME); |
michael@0 | 5733 | if (!BindNameToSlot(cx, bce, pn2)) |
michael@0 | 5734 | return false; |
michael@0 | 5735 | JSOp op = pn2->getOp(); |
michael@0 | 5736 | bool maySet; |
michael@0 | 5737 | switch (op) { |
michael@0 | 5738 | case JSOP_SETLOCAL: |
michael@0 | 5739 | case JSOP_SETARG: |
michael@0 | 5740 | case JSOP_SETALIASEDVAR: |
michael@0 | 5741 | case JSOP_SETNAME: |
michael@0 | 5742 | case JSOP_SETGNAME: |
michael@0 | 5743 | maySet = true; |
michael@0 | 5744 | break; |
michael@0 | 5745 | default: |
michael@0 | 5746 | maySet = false; |
michael@0 | 5747 | } |
michael@0 | 5748 | if (op == JSOP_CALLEE) { |
michael@0 | 5749 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 5750 | return false; |
michael@0 | 5751 | } else if (!pn2->pn_cookie.isFree()) { |
michael@0 | 5752 | if (maySet) { |
michael@0 | 5753 | if (!EmitVarIncDec(cx, pn, bce)) |
michael@0 | 5754 | return false; |
michael@0 | 5755 | } else { |
michael@0 | 5756 | if (!EmitVarOp(cx, pn2, op, bce)) |
michael@0 | 5757 | return false; |
michael@0 | 5758 | } |
michael@0 | 5759 | } else { |
michael@0 | 5760 | JS_ASSERT(JOF_OPTYPE(op) == JOF_ATOM); |
michael@0 | 5761 | if (maySet) { |
michael@0 | 5762 | if (!EmitNameIncDec(cx, pn, bce)) |
michael@0 | 5763 | return false; |
michael@0 | 5764 | } else { |
michael@0 | 5765 | if (!EmitAtomOp(cx, pn2, op, bce)) |
michael@0 | 5766 | return false; |
michael@0 | 5767 | } |
michael@0 | 5768 | break; |
michael@0 | 5769 | } |
michael@0 | 5770 | if (pn2->isConst()) { |
michael@0 | 5771 | if (Emit1(cx, bce, JSOP_POS) < 0) |
michael@0 | 5772 | return false; |
michael@0 | 5773 | bool post; |
michael@0 | 5774 | JSOp binop = GetIncDecInfo(pn->getKind(), &post); |
michael@0 | 5775 | if (!post) { |
michael@0 | 5776 | if (Emit1(cx, bce, JSOP_ONE) < 0) |
michael@0 | 5777 | return false; |
michael@0 | 5778 | if (Emit1(cx, bce, binop) < 0) |
michael@0 | 5779 | return false; |
michael@0 | 5780 | } |
michael@0 | 5781 | } |
michael@0 | 5782 | } |
michael@0 | 5783 | return true; |
michael@0 | 5784 | } |
michael@0 | 5785 | |
michael@0 | 5786 | /* |
michael@0 | 5787 | * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
michael@0 | 5788 | * the comment on EmitSwitch. |
michael@0 | 5789 | */ |
michael@0 | 5790 | MOZ_NEVER_INLINE static bool |
michael@0 | 5791 | EmitLabeledStatement(ExclusiveContext *cx, BytecodeEmitter *bce, const LabeledStatement *pn) |
michael@0 | 5792 | { |
michael@0 | 5793 | /* |
michael@0 | 5794 | * Emit a JSOP_LABEL instruction. The argument is the offset to the statement |
michael@0 | 5795 | * following the labeled statement. |
michael@0 | 5796 | */ |
michael@0 | 5797 | jsatomid index; |
michael@0 | 5798 | if (!bce->makeAtomIndex(pn->label(), &index)) |
michael@0 | 5799 | return false; |
michael@0 | 5800 | |
michael@0 | 5801 | ptrdiff_t top = EmitJump(cx, bce, JSOP_LABEL, 0); |
michael@0 | 5802 | if (top < 0) |
michael@0 | 5803 | return false; |
michael@0 | 5804 | |
michael@0 | 5805 | /* Emit code for the labeled statement. */ |
michael@0 | 5806 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 5807 | PushStatementBCE(bce, &stmtInfo, STMT_LABEL, bce->offset()); |
michael@0 | 5808 | stmtInfo.label = pn->label(); |
michael@0 | 5809 | if (!EmitTree(cx, bce, pn->statement())) |
michael@0 | 5810 | return false; |
michael@0 | 5811 | if (!PopStatementBCE(cx, bce)) |
michael@0 | 5812 | return false; |
michael@0 | 5813 | |
michael@0 | 5814 | /* Patch the JSOP_LABEL offset. */ |
michael@0 | 5815 | SetJumpOffsetAt(bce, top); |
michael@0 | 5816 | return true; |
michael@0 | 5817 | } |
michael@0 | 5818 | |
michael@0 | 5819 | static bool |
michael@0 | 5820 | EmitSyntheticStatements(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, ptrdiff_t top) |
michael@0 | 5821 | { |
michael@0 | 5822 | JS_ASSERT(pn->isArity(PN_LIST)); |
michael@0 | 5823 | StmtInfoBCE stmtInfo(cx); |
michael@0 | 5824 | PushStatementBCE(bce, &stmtInfo, STMT_SEQ, top); |
michael@0 | 5825 | ParseNode *pn2 = pn->pn_head; |
michael@0 | 5826 | if (pn->pn_xflags & PNX_DESTRUCT) |
michael@0 | 5827 | pn2 = pn2->pn_next; |
michael@0 | 5828 | for (; pn2; pn2 = pn2->pn_next) { |
michael@0 | 5829 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 5830 | return false; |
michael@0 | 5831 | } |
michael@0 | 5832 | return PopStatementBCE(cx, bce); |
michael@0 | 5833 | } |
michael@0 | 5834 | |
michael@0 | 5835 | static bool |
michael@0 | 5836 | EmitConditionalExpression(ExclusiveContext *cx, BytecodeEmitter *bce, ConditionalExpression &conditional) |
michael@0 | 5837 | { |
michael@0 | 5838 | /* Emit the condition, then branch if false to the else part. */ |
michael@0 | 5839 | if (!EmitTree(cx, bce, &conditional.condition())) |
michael@0 | 5840 | return false; |
michael@0 | 5841 | ptrdiff_t noteIndex = NewSrcNote(cx, bce, SRC_COND); |
michael@0 | 5842 | if (noteIndex < 0) |
michael@0 | 5843 | return false; |
michael@0 | 5844 | ptrdiff_t beq = EmitJump(cx, bce, JSOP_IFEQ, 0); |
michael@0 | 5845 | if (beq < 0 || !EmitTree(cx, bce, &conditional.thenExpression())) |
michael@0 | 5846 | return false; |
michael@0 | 5847 | |
michael@0 | 5848 | /* Jump around else, fixup the branch, emit else, fixup jump. */ |
michael@0 | 5849 | ptrdiff_t jmp = EmitJump(cx, bce, JSOP_GOTO, 0); |
michael@0 | 5850 | if (jmp < 0) |
michael@0 | 5851 | return false; |
michael@0 | 5852 | SetJumpOffsetAt(bce, beq); |
michael@0 | 5853 | |
michael@0 | 5854 | /* |
michael@0 | 5855 | * Because each branch pushes a single value, but our stack budgeting |
michael@0 | 5856 | * analysis ignores branches, we now have to adjust bce->stackDepth to |
michael@0 | 5857 | * ignore the value pushed by the first branch. Execution will follow |
michael@0 | 5858 | * only one path, so we must decrement bce->stackDepth. |
michael@0 | 5859 | * |
michael@0 | 5860 | * Failing to do this will foil code, such as let expression and block |
michael@0 | 5861 | * code generation, which must use the stack depth to compute local |
michael@0 | 5862 | * stack indexes correctly. |
michael@0 | 5863 | */ |
michael@0 | 5864 | JS_ASSERT(bce->stackDepth > 0); |
michael@0 | 5865 | bce->stackDepth--; |
michael@0 | 5866 | if (!EmitTree(cx, bce, &conditional.elseExpression())) |
michael@0 | 5867 | return false; |
michael@0 | 5868 | SetJumpOffsetAt(bce, jmp); |
michael@0 | 5869 | return SetSrcNoteOffset(cx, bce, noteIndex, 0, jmp - beq); |
michael@0 | 5870 | } |
michael@0 | 5871 | |
michael@0 | 5872 | /* |
michael@0 | 5873 | * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See |
michael@0 | 5874 | * the comment on EmitSwitch. |
michael@0 | 5875 | */ |
michael@0 | 5876 | MOZ_NEVER_INLINE static bool |
michael@0 | 5877 | EmitObject(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 5878 | { |
michael@0 | 5879 | if (pn->pn_xflags & PNX_DESTRUCT) { |
michael@0 | 5880 | bce->reportError(pn, JSMSG_BAD_OBJECT_INIT); |
michael@0 | 5881 | return false; |
michael@0 | 5882 | } |
michael@0 | 5883 | |
michael@0 | 5884 | if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext()) |
michael@0 | 5885 | return EmitSingletonInitialiser(cx, bce, pn); |
michael@0 | 5886 | |
michael@0 | 5887 | /* |
michael@0 | 5888 | * Emit code for {p:a, '%q':b, 2:c} that is equivalent to constructing |
michael@0 | 5889 | * a new object and defining (in source order) each property on the object |
michael@0 | 5890 | * (or mutating the object's [[Prototype]], in the case of __proto__). |
michael@0 | 5891 | */ |
michael@0 | 5892 | ptrdiff_t offset = bce->offset(); |
michael@0 | 5893 | if (!EmitNewInit(cx, bce, JSProto_Object)) |
michael@0 | 5894 | return false; |
michael@0 | 5895 | |
michael@0 | 5896 | /* |
michael@0 | 5897 | * Try to construct the shape of the object as we go, so we can emit a |
michael@0 | 5898 | * JSOP_NEWOBJECT with the final shape instead. |
michael@0 | 5899 | */ |
michael@0 | 5900 | RootedObject obj(cx); |
michael@0 | 5901 | if (bce->script->compileAndGo()) { |
michael@0 | 5902 | gc::AllocKind kind = GuessObjectGCKind(pn->pn_count); |
michael@0 | 5903 | obj = NewBuiltinClassInstance(cx, &JSObject::class_, kind, TenuredObject); |
michael@0 | 5904 | if (!obj) |
michael@0 | 5905 | return false; |
michael@0 | 5906 | } |
michael@0 | 5907 | |
michael@0 | 5908 | for (ParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) { |
michael@0 | 5909 | if (!UpdateSourceCoordNotes(cx, bce, pn2->pn_pos.begin)) |
michael@0 | 5910 | return false; |
michael@0 | 5911 | |
michael@0 | 5912 | /* Emit an index for t[2] for later consumption by JSOP_INITELEM. */ |
michael@0 | 5913 | ParseNode *pn3 = pn2->pn_left; |
michael@0 | 5914 | bool isIndex = false; |
michael@0 | 5915 | if (pn3->isKind(PNK_NUMBER)) { |
michael@0 | 5916 | if (!EmitNumberOp(cx, pn3->pn_dval, bce)) |
michael@0 | 5917 | return false; |
michael@0 | 5918 | isIndex = true; |
michael@0 | 5919 | } else { |
michael@0 | 5920 | // The parser already checked for atoms representing indexes and |
michael@0 | 5921 | // used PNK_NUMBER instead, but also watch for ids which TI treats |
michael@0 | 5922 | // as indexes for simpliciation of downstream analysis. |
michael@0 | 5923 | JS_ASSERT(pn3->isKind(PNK_NAME) || pn3->isKind(PNK_STRING)); |
michael@0 | 5924 | jsid id = NameToId(pn3->pn_atom->asPropertyName()); |
michael@0 | 5925 | if (id != types::IdToTypeId(id)) { |
michael@0 | 5926 | if (!EmitTree(cx, bce, pn3)) |
michael@0 | 5927 | return false; |
michael@0 | 5928 | isIndex = true; |
michael@0 | 5929 | } |
michael@0 | 5930 | } |
michael@0 | 5931 | |
michael@0 | 5932 | /* Emit code for the property initializer. */ |
michael@0 | 5933 | if (!EmitTree(cx, bce, pn2->pn_right)) |
michael@0 | 5934 | return false; |
michael@0 | 5935 | |
michael@0 | 5936 | JSOp op = pn2->getOp(); |
michael@0 | 5937 | JS_ASSERT(op == JSOP_INITPROP || |
michael@0 | 5938 | op == JSOP_INITPROP_GETTER || |
michael@0 | 5939 | op == JSOP_INITPROP_SETTER); |
michael@0 | 5940 | |
michael@0 | 5941 | if (op == JSOP_INITPROP_GETTER || op == JSOP_INITPROP_SETTER) |
michael@0 | 5942 | obj = nullptr; |
michael@0 | 5943 | |
michael@0 | 5944 | if (isIndex) { |
michael@0 | 5945 | obj = nullptr; |
michael@0 | 5946 | switch (op) { |
michael@0 | 5947 | case JSOP_INITPROP: op = JSOP_INITELEM; break; |
michael@0 | 5948 | case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; |
michael@0 | 5949 | case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; |
michael@0 | 5950 | default: MOZ_ASSUME_UNREACHABLE("Invalid op"); |
michael@0 | 5951 | } |
michael@0 | 5952 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 5953 | return false; |
michael@0 | 5954 | } else { |
michael@0 | 5955 | JS_ASSERT(pn3->isKind(PNK_NAME) || pn3->isKind(PNK_STRING)); |
michael@0 | 5956 | |
michael@0 | 5957 | // If we have { __proto__: expr }, implement prototype mutation. |
michael@0 | 5958 | if (op == JSOP_INITPROP && pn3->pn_atom == cx->names().proto) { |
michael@0 | 5959 | obj = nullptr; |
michael@0 | 5960 | if (Emit1(cx, bce, JSOP_MUTATEPROTO) < 0) |
michael@0 | 5961 | return false; |
michael@0 | 5962 | continue; |
michael@0 | 5963 | } |
michael@0 | 5964 | |
michael@0 | 5965 | jsatomid index; |
michael@0 | 5966 | if (!bce->makeAtomIndex(pn3->pn_atom, &index)) |
michael@0 | 5967 | return false; |
michael@0 | 5968 | |
michael@0 | 5969 | MOZ_ASSERT(op == JSOP_INITPROP || |
michael@0 | 5970 | op == JSOP_INITPROP_GETTER || |
michael@0 | 5971 | op == JSOP_INITPROP_SETTER); |
michael@0 | 5972 | |
michael@0 | 5973 | if (obj) { |
michael@0 | 5974 | JS_ASSERT(!obj->inDictionaryMode()); |
michael@0 | 5975 | Rooted<jsid> id(cx, AtomToId(pn3->pn_atom)); |
michael@0 | 5976 | RootedValue undefinedValue(cx, UndefinedValue()); |
michael@0 | 5977 | if (!DefineNativeProperty(cx, obj, id, undefinedValue, nullptr, |
michael@0 | 5978 | nullptr, JSPROP_ENUMERATE)) |
michael@0 | 5979 | { |
michael@0 | 5980 | return false; |
michael@0 | 5981 | } |
michael@0 | 5982 | if (obj->inDictionaryMode()) |
michael@0 | 5983 | obj = nullptr; |
michael@0 | 5984 | } |
michael@0 | 5985 | |
michael@0 | 5986 | if (!EmitIndex32(cx, op, index, bce)) |
michael@0 | 5987 | return false; |
michael@0 | 5988 | } |
michael@0 | 5989 | } |
michael@0 | 5990 | |
michael@0 | 5991 | if (Emit1(cx, bce, JSOP_ENDINIT) < 0) |
michael@0 | 5992 | return false; |
michael@0 | 5993 | |
michael@0 | 5994 | if (obj) { |
michael@0 | 5995 | /* |
michael@0 | 5996 | * The object survived and has a predictable shape: update the original |
michael@0 | 5997 | * bytecode. |
michael@0 | 5998 | */ |
michael@0 | 5999 | ObjectBox *objbox = bce->parser->newObjectBox(obj); |
michael@0 | 6000 | if (!objbox) |
michael@0 | 6001 | return false; |
michael@0 | 6002 | |
michael@0 | 6003 | static_assert(JSOP_NEWINIT_LENGTH == JSOP_NEWOBJECT_LENGTH, |
michael@0 | 6004 | "newinit and newobject must have equal length to edit in-place"); |
michael@0 | 6005 | |
michael@0 | 6006 | uint32_t index = bce->objectList.add(objbox); |
michael@0 | 6007 | jsbytecode *code = bce->code(offset); |
michael@0 | 6008 | code[0] = JSOP_NEWOBJECT; |
michael@0 | 6009 | code[1] = jsbytecode(index >> 24); |
michael@0 | 6010 | code[2] = jsbytecode(index >> 16); |
michael@0 | 6011 | code[3] = jsbytecode(index >> 8); |
michael@0 | 6012 | code[4] = jsbytecode(index); |
michael@0 | 6013 | } |
michael@0 | 6014 | |
michael@0 | 6015 | return true; |
michael@0 | 6016 | } |
michael@0 | 6017 | |
michael@0 | 6018 | static bool |
michael@0 | 6019 | EmitArrayComp(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 6020 | { |
michael@0 | 6021 | if (!EmitNewInit(cx, bce, JSProto_Array)) |
michael@0 | 6022 | return false; |
michael@0 | 6023 | |
michael@0 | 6024 | /* |
michael@0 | 6025 | * Pass the new array's stack index to the PNK_ARRAYPUSH case via |
michael@0 | 6026 | * bce->arrayCompDepth, then simply traverse the PNK_FOR node and |
michael@0 | 6027 | * its kids under pn2 to generate this comprehension. |
michael@0 | 6028 | */ |
michael@0 | 6029 | JS_ASSERT(bce->stackDepth > 0); |
michael@0 | 6030 | uint32_t saveDepth = bce->arrayCompDepth; |
michael@0 | 6031 | bce->arrayCompDepth = (uint32_t) (bce->stackDepth - 1); |
michael@0 | 6032 | if (!EmitTree(cx, bce, pn->pn_head)) |
michael@0 | 6033 | return false; |
michael@0 | 6034 | bce->arrayCompDepth = saveDepth; |
michael@0 | 6035 | |
michael@0 | 6036 | /* Emit the usual op needed for decompilation. */ |
michael@0 | 6037 | return Emit1(cx, bce, JSOP_ENDINIT) >= 0; |
michael@0 | 6038 | } |
michael@0 | 6039 | |
michael@0 | 6040 | static bool |
michael@0 | 6041 | EmitArray(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, uint32_t count) |
michael@0 | 6042 | { |
michael@0 | 6043 | /* |
michael@0 | 6044 | * Emit code for [a, b, c] that is equivalent to constructing a new |
michael@0 | 6045 | * array and in source order evaluating each element value and adding |
michael@0 | 6046 | * it to the array, without invoking latent setters. We use the |
michael@0 | 6047 | * JSOP_NEWINIT and JSOP_INITELEM_ARRAY bytecodes to ignore setters and |
michael@0 | 6048 | * to avoid dup'ing and popping the array as each element is added, as |
michael@0 | 6049 | * JSOP_SETELEM/JSOP_SETPROP would do. |
michael@0 | 6050 | */ |
michael@0 | 6051 | |
michael@0 | 6052 | int32_t nspread = 0; |
michael@0 | 6053 | for (ParseNode *elt = pn; elt; elt = elt->pn_next) { |
michael@0 | 6054 | if (elt->isKind(PNK_SPREAD)) |
michael@0 | 6055 | nspread++; |
michael@0 | 6056 | } |
michael@0 | 6057 | |
michael@0 | 6058 | ptrdiff_t off = EmitN(cx, bce, JSOP_NEWARRAY, 3); |
michael@0 | 6059 | if (off < 0) |
michael@0 | 6060 | return false; |
michael@0 | 6061 | CheckTypeSet(cx, bce, JSOP_NEWARRAY); |
michael@0 | 6062 | jsbytecode *pc = bce->code(off); |
michael@0 | 6063 | |
michael@0 | 6064 | // For arrays with spread, this is a very pessimistic allocation, the |
michael@0 | 6065 | // minimum possible final size. |
michael@0 | 6066 | SET_UINT24(pc, count - nspread); |
michael@0 | 6067 | |
michael@0 | 6068 | ParseNode *pn2 = pn; |
michael@0 | 6069 | jsatomid atomIndex; |
michael@0 | 6070 | if (nspread && !EmitNumberOp(cx, 0, bce)) |
michael@0 | 6071 | return false; |
michael@0 | 6072 | for (atomIndex = 0; pn2; atomIndex++, pn2 = pn2->pn_next) { |
michael@0 | 6073 | if (!UpdateSourceCoordNotes(cx, bce, pn2->pn_pos.begin)) |
michael@0 | 6074 | return false; |
michael@0 | 6075 | if (pn2->isKind(PNK_ELISION)) { |
michael@0 | 6076 | if (Emit1(cx, bce, JSOP_HOLE) < 0) |
michael@0 | 6077 | return false; |
michael@0 | 6078 | } else { |
michael@0 | 6079 | ParseNode *expr = pn2->isKind(PNK_SPREAD) ? pn2->pn_kid : pn2; |
michael@0 | 6080 | if (!EmitTree(cx, bce, expr)) |
michael@0 | 6081 | return false; |
michael@0 | 6082 | } |
michael@0 | 6083 | if (pn2->isKind(PNK_SPREAD)) { |
michael@0 | 6084 | if (Emit1(cx, bce, JSOP_SPREAD) < 0) |
michael@0 | 6085 | return false; |
michael@0 | 6086 | } else if (nspread) { |
michael@0 | 6087 | if (Emit1(cx, bce, JSOP_INITELEM_INC) < 0) |
michael@0 | 6088 | return false; |
michael@0 | 6089 | } else { |
michael@0 | 6090 | off = EmitN(cx, bce, JSOP_INITELEM_ARRAY, 3); |
michael@0 | 6091 | if (off < 0) |
michael@0 | 6092 | return false; |
michael@0 | 6093 | SET_UINT24(bce->code(off), atomIndex); |
michael@0 | 6094 | } |
michael@0 | 6095 | } |
michael@0 | 6096 | JS_ASSERT(atomIndex == count); |
michael@0 | 6097 | if (nspread) { |
michael@0 | 6098 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 6099 | return false; |
michael@0 | 6100 | } |
michael@0 | 6101 | |
michael@0 | 6102 | /* Emit an op to finish the array and aid in decompilation. */ |
michael@0 | 6103 | return Emit1(cx, bce, JSOP_ENDINIT) >= 0; |
michael@0 | 6104 | } |
michael@0 | 6105 | |
michael@0 | 6106 | static bool |
michael@0 | 6107 | EmitUnary(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 6108 | { |
michael@0 | 6109 | if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin)) |
michael@0 | 6110 | return false; |
michael@0 | 6111 | /* Unary op, including unary +/-. */ |
michael@0 | 6112 | JSOp op = pn->getOp(); |
michael@0 | 6113 | ParseNode *pn2 = pn->pn_kid; |
michael@0 | 6114 | |
michael@0 | 6115 | if (op == JSOP_TYPEOF && !pn2->isKind(PNK_NAME)) |
michael@0 | 6116 | op = JSOP_TYPEOFEXPR; |
michael@0 | 6117 | |
michael@0 | 6118 | bool oldEmittingForInit = bce->emittingForInit; |
michael@0 | 6119 | bce->emittingForInit = false; |
michael@0 | 6120 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 6121 | return false; |
michael@0 | 6122 | |
michael@0 | 6123 | bce->emittingForInit = oldEmittingForInit; |
michael@0 | 6124 | return Emit1(cx, bce, op) >= 0; |
michael@0 | 6125 | } |
michael@0 | 6126 | |
michael@0 | 6127 | static bool |
michael@0 | 6128 | EmitDefaults(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 6129 | { |
michael@0 | 6130 | JS_ASSERT(pn->isKind(PNK_ARGSBODY)); |
michael@0 | 6131 | |
michael@0 | 6132 | ParseNode *arg, *pnlast = pn->last(); |
michael@0 | 6133 | for (arg = pn->pn_head; arg != pnlast; arg = arg->pn_next) { |
michael@0 | 6134 | if (!(arg->pn_dflags & PND_DEFAULT) || !arg->isKind(PNK_NAME)) |
michael@0 | 6135 | continue; |
michael@0 | 6136 | if (!BindNameToSlot(cx, bce, arg)) |
michael@0 | 6137 | return false; |
michael@0 | 6138 | if (!EmitVarOp(cx, arg, JSOP_GETARG, bce)) |
michael@0 | 6139 | return false; |
michael@0 | 6140 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) |
michael@0 | 6141 | return false; |
michael@0 | 6142 | if (Emit1(cx, bce, JSOP_STRICTEQ) < 0) |
michael@0 | 6143 | return false; |
michael@0 | 6144 | // Emit source note to enable ion compilation. |
michael@0 | 6145 | if (NewSrcNote(cx, bce, SRC_IF) < 0) |
michael@0 | 6146 | return false; |
michael@0 | 6147 | ptrdiff_t jump = EmitJump(cx, bce, JSOP_IFEQ, 0); |
michael@0 | 6148 | if (jump < 0) |
michael@0 | 6149 | return false; |
michael@0 | 6150 | if (!EmitTree(cx, bce, arg->expr())) |
michael@0 | 6151 | return false; |
michael@0 | 6152 | if (!EmitVarOp(cx, arg, JSOP_SETARG, bce)) |
michael@0 | 6153 | return false; |
michael@0 | 6154 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 6155 | return false; |
michael@0 | 6156 | SET_JUMP_OFFSET(bce->code(jump), bce->offset() - jump); |
michael@0 | 6157 | } |
michael@0 | 6158 | |
michael@0 | 6159 | return true; |
michael@0 | 6160 | } |
michael@0 | 6161 | |
michael@0 | 6162 | bool |
michael@0 | 6163 | frontend::EmitTree(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn) |
michael@0 | 6164 | { |
michael@0 | 6165 | JS_CHECK_RECURSION(cx, return false); |
michael@0 | 6166 | |
michael@0 | 6167 | EmitLevelManager elm(bce); |
michael@0 | 6168 | |
michael@0 | 6169 | bool ok = true; |
michael@0 | 6170 | ptrdiff_t top = bce->offset(); |
michael@0 | 6171 | pn->pn_offset = top; |
michael@0 | 6172 | |
michael@0 | 6173 | /* Emit notes to tell the current bytecode's source line number. */ |
michael@0 | 6174 | if (!UpdateLineNumberNotes(cx, bce, pn->pn_pos.begin)) |
michael@0 | 6175 | return false; |
michael@0 | 6176 | |
michael@0 | 6177 | switch (pn->getKind()) { |
michael@0 | 6178 | case PNK_FUNCTION: |
michael@0 | 6179 | ok = EmitFunc(cx, bce, pn); |
michael@0 | 6180 | break; |
michael@0 | 6181 | |
michael@0 | 6182 | case PNK_ARGSBODY: |
michael@0 | 6183 | { |
michael@0 | 6184 | RootedFunction fun(cx, bce->sc->asFunctionBox()->function()); |
michael@0 | 6185 | ParseNode *pnlast = pn->last(); |
michael@0 | 6186 | |
michael@0 | 6187 | // Carefully emit everything in the right order: |
michael@0 | 6188 | // 1. Destructuring |
michael@0 | 6189 | // 2. Functions |
michael@0 | 6190 | // 3. Defaults |
michael@0 | 6191 | ParseNode *pnchild = pnlast->pn_head; |
michael@0 | 6192 | if (pnlast->pn_xflags & PNX_DESTRUCT) { |
michael@0 | 6193 | // Assign the destructuring arguments before defining any functions, |
michael@0 | 6194 | // see bug 419662. |
michael@0 | 6195 | JS_ASSERT(pnchild->isKind(PNK_SEMI)); |
michael@0 | 6196 | JS_ASSERT(pnchild->pn_kid->isKind(PNK_VAR) || pnchild->pn_kid->isKind(PNK_CONST)); |
michael@0 | 6197 | if (!EmitTree(cx, bce, pnchild)) |
michael@0 | 6198 | return false; |
michael@0 | 6199 | pnchild = pnchild->pn_next; |
michael@0 | 6200 | } |
michael@0 | 6201 | if (pnlast->pn_xflags & PNX_FUNCDEFS) { |
michael@0 | 6202 | // This block contains top-level function definitions. To ensure |
michael@0 | 6203 | // that we emit the bytecode defining them before the rest of code |
michael@0 | 6204 | // in the block we use a separate pass over functions. During the |
michael@0 | 6205 | // main pass later the emitter will add JSOP_NOP with source notes |
michael@0 | 6206 | // for the function to preserve the original functions position |
michael@0 | 6207 | // when decompiling. |
michael@0 | 6208 | // |
michael@0 | 6209 | // Currently this is used only for functions, as compile-as-we go |
michael@0 | 6210 | // mode for scripts does not allow separate emitter passes. |
michael@0 | 6211 | for (ParseNode *pn2 = pnchild; pn2; pn2 = pn2->pn_next) { |
michael@0 | 6212 | if (pn2->isKind(PNK_FUNCTION) && pn2->functionIsHoisted()) { |
michael@0 | 6213 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 6214 | return false; |
michael@0 | 6215 | } |
michael@0 | 6216 | } |
michael@0 | 6217 | } |
michael@0 | 6218 | bool hasDefaults = bce->sc->asFunctionBox()->hasDefaults(); |
michael@0 | 6219 | if (hasDefaults) { |
michael@0 | 6220 | ParseNode *rest = nullptr; |
michael@0 | 6221 | bool restIsDefn = false; |
michael@0 | 6222 | if (fun->hasRest()) { |
michael@0 | 6223 | JS_ASSERT(!bce->sc->asFunctionBox()->argumentsHasLocalBinding()); |
michael@0 | 6224 | |
michael@0 | 6225 | // Defaults with a rest parameter need special handling. The |
michael@0 | 6226 | // rest parameter needs to be undefined while defaults are being |
michael@0 | 6227 | // processed. To do this, we create the rest argument and let it |
michael@0 | 6228 | // sit on the stack while processing defaults. The rest |
michael@0 | 6229 | // parameter's slot is set to undefined for the course of |
michael@0 | 6230 | // default processing. |
michael@0 | 6231 | rest = pn->pn_head; |
michael@0 | 6232 | while (rest->pn_next != pnlast) |
michael@0 | 6233 | rest = rest->pn_next; |
michael@0 | 6234 | restIsDefn = rest->isDefn(); |
michael@0 | 6235 | if (Emit1(cx, bce, JSOP_REST) < 0) |
michael@0 | 6236 | return false; |
michael@0 | 6237 | CheckTypeSet(cx, bce, JSOP_REST); |
michael@0 | 6238 | |
michael@0 | 6239 | // Only set the rest parameter if it's not aliased by a nested |
michael@0 | 6240 | // function in the body. |
michael@0 | 6241 | if (restIsDefn) { |
michael@0 | 6242 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) |
michael@0 | 6243 | return false; |
michael@0 | 6244 | if (!BindNameToSlot(cx, bce, rest)) |
michael@0 | 6245 | return false; |
michael@0 | 6246 | if (!EmitVarOp(cx, rest, JSOP_SETARG, bce)) |
michael@0 | 6247 | return false; |
michael@0 | 6248 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 6249 | return false; |
michael@0 | 6250 | } |
michael@0 | 6251 | } |
michael@0 | 6252 | if (!EmitDefaults(cx, bce, pn)) |
michael@0 | 6253 | return false; |
michael@0 | 6254 | if (fun->hasRest()) { |
michael@0 | 6255 | if (restIsDefn && !EmitVarOp(cx, rest, JSOP_SETARG, bce)) |
michael@0 | 6256 | return false; |
michael@0 | 6257 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 6258 | return false; |
michael@0 | 6259 | } |
michael@0 | 6260 | } |
michael@0 | 6261 | for (ParseNode *pn2 = pn->pn_head; pn2 != pnlast; pn2 = pn2->pn_next) { |
michael@0 | 6262 | // Only bind the parameter if it's not aliased by a nested function |
michael@0 | 6263 | // in the body. |
michael@0 | 6264 | if (!pn2->isDefn()) |
michael@0 | 6265 | continue; |
michael@0 | 6266 | if (!BindNameToSlot(cx, bce, pn2)) |
michael@0 | 6267 | return false; |
michael@0 | 6268 | if (pn2->pn_next == pnlast && fun->hasRest() && !hasDefaults) { |
michael@0 | 6269 | // Fill rest parameter. We handled the case with defaults above. |
michael@0 | 6270 | JS_ASSERT(!bce->sc->asFunctionBox()->argumentsHasLocalBinding()); |
michael@0 | 6271 | bce->switchToProlog(); |
michael@0 | 6272 | if (Emit1(cx, bce, JSOP_REST) < 0) |
michael@0 | 6273 | return false; |
michael@0 | 6274 | CheckTypeSet(cx, bce, JSOP_REST); |
michael@0 | 6275 | if (!EmitVarOp(cx, pn2, JSOP_SETARG, bce)) |
michael@0 | 6276 | return false; |
michael@0 | 6277 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 6278 | return false; |
michael@0 | 6279 | bce->switchToMain(); |
michael@0 | 6280 | } |
michael@0 | 6281 | } |
michael@0 | 6282 | ok = EmitTree(cx, bce, pnlast); |
michael@0 | 6283 | break; |
michael@0 | 6284 | } |
michael@0 | 6285 | |
michael@0 | 6286 | case PNK_IF: |
michael@0 | 6287 | ok = EmitIf(cx, bce, pn); |
michael@0 | 6288 | break; |
michael@0 | 6289 | |
michael@0 | 6290 | case PNK_SWITCH: |
michael@0 | 6291 | ok = EmitSwitch(cx, bce, pn); |
michael@0 | 6292 | break; |
michael@0 | 6293 | |
michael@0 | 6294 | case PNK_WHILE: |
michael@0 | 6295 | ok = EmitWhile(cx, bce, pn, top); |
michael@0 | 6296 | break; |
michael@0 | 6297 | |
michael@0 | 6298 | case PNK_DOWHILE: |
michael@0 | 6299 | ok = EmitDo(cx, bce, pn); |
michael@0 | 6300 | break; |
michael@0 | 6301 | |
michael@0 | 6302 | case PNK_FOR: |
michael@0 | 6303 | ok = EmitFor(cx, bce, pn, top); |
michael@0 | 6304 | break; |
michael@0 | 6305 | |
michael@0 | 6306 | case PNK_BREAK: |
michael@0 | 6307 | ok = EmitBreak(cx, bce, pn->as<BreakStatement>().label()); |
michael@0 | 6308 | break; |
michael@0 | 6309 | |
michael@0 | 6310 | case PNK_CONTINUE: |
michael@0 | 6311 | ok = EmitContinue(cx, bce, pn->as<ContinueStatement>().label()); |
michael@0 | 6312 | break; |
michael@0 | 6313 | |
michael@0 | 6314 | case PNK_WITH: |
michael@0 | 6315 | ok = EmitWith(cx, bce, pn); |
michael@0 | 6316 | break; |
michael@0 | 6317 | |
michael@0 | 6318 | case PNK_TRY: |
michael@0 | 6319 | if (!EmitTry(cx, bce, pn)) |
michael@0 | 6320 | return false; |
michael@0 | 6321 | break; |
michael@0 | 6322 | |
michael@0 | 6323 | case PNK_CATCH: |
michael@0 | 6324 | if (!EmitCatch(cx, bce, pn)) |
michael@0 | 6325 | return false; |
michael@0 | 6326 | break; |
michael@0 | 6327 | |
michael@0 | 6328 | case PNK_VAR: |
michael@0 | 6329 | case PNK_CONST: |
michael@0 | 6330 | if (!EmitVariables(cx, bce, pn, InitializeVars)) |
michael@0 | 6331 | return false; |
michael@0 | 6332 | break; |
michael@0 | 6333 | |
michael@0 | 6334 | case PNK_RETURN: |
michael@0 | 6335 | ok = EmitReturn(cx, bce, pn); |
michael@0 | 6336 | break; |
michael@0 | 6337 | |
michael@0 | 6338 | case PNK_YIELD_STAR: |
michael@0 | 6339 | ok = EmitYieldStar(cx, bce, pn->pn_kid); |
michael@0 | 6340 | break; |
michael@0 | 6341 | |
michael@0 | 6342 | case PNK_YIELD: |
michael@0 | 6343 | JS_ASSERT(bce->sc->isFunctionBox()); |
michael@0 | 6344 | if (bce->sc->asFunctionBox()->isStarGenerator()) { |
michael@0 | 6345 | if (!EmitPrepareIteratorResult(cx, bce)) |
michael@0 | 6346 | return false; |
michael@0 | 6347 | } |
michael@0 | 6348 | if (pn->pn_kid) { |
michael@0 | 6349 | if (!EmitTree(cx, bce, pn->pn_kid)) |
michael@0 | 6350 | return false; |
michael@0 | 6351 | } else { |
michael@0 | 6352 | if (Emit1(cx, bce, JSOP_UNDEFINED) < 0) |
michael@0 | 6353 | return false; |
michael@0 | 6354 | } |
michael@0 | 6355 | if (bce->sc->asFunctionBox()->isStarGenerator()) { |
michael@0 | 6356 | if (!EmitFinishIteratorResult(cx, bce, false)) |
michael@0 | 6357 | return false; |
michael@0 | 6358 | } |
michael@0 | 6359 | if (Emit1(cx, bce, JSOP_YIELD) < 0) |
michael@0 | 6360 | return false; |
michael@0 | 6361 | break; |
michael@0 | 6362 | |
michael@0 | 6363 | case PNK_STATEMENTLIST: |
michael@0 | 6364 | ok = EmitStatementList(cx, bce, pn, top); |
michael@0 | 6365 | break; |
michael@0 | 6366 | |
michael@0 | 6367 | case PNK_SEQ: |
michael@0 | 6368 | ok = EmitSyntheticStatements(cx, bce, pn, top); |
michael@0 | 6369 | break; |
michael@0 | 6370 | |
michael@0 | 6371 | case PNK_SEMI: |
michael@0 | 6372 | ok = EmitStatement(cx, bce, pn); |
michael@0 | 6373 | break; |
michael@0 | 6374 | |
michael@0 | 6375 | case PNK_LABEL: |
michael@0 | 6376 | ok = EmitLabeledStatement(cx, bce, &pn->as<LabeledStatement>()); |
michael@0 | 6377 | break; |
michael@0 | 6378 | |
michael@0 | 6379 | case PNK_COMMA: |
michael@0 | 6380 | { |
michael@0 | 6381 | for (ParseNode *pn2 = pn->pn_head; ; pn2 = pn2->pn_next) { |
michael@0 | 6382 | if (!UpdateSourceCoordNotes(cx, bce, pn2->pn_pos.begin)) |
michael@0 | 6383 | return false; |
michael@0 | 6384 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 6385 | return false; |
michael@0 | 6386 | if (!pn2->pn_next) |
michael@0 | 6387 | break; |
michael@0 | 6388 | if (Emit1(cx, bce, JSOP_POP) < 0) |
michael@0 | 6389 | return false; |
michael@0 | 6390 | } |
michael@0 | 6391 | break; |
michael@0 | 6392 | } |
michael@0 | 6393 | |
michael@0 | 6394 | case PNK_ASSIGN: |
michael@0 | 6395 | case PNK_ADDASSIGN: |
michael@0 | 6396 | case PNK_SUBASSIGN: |
michael@0 | 6397 | case PNK_BITORASSIGN: |
michael@0 | 6398 | case PNK_BITXORASSIGN: |
michael@0 | 6399 | case PNK_BITANDASSIGN: |
michael@0 | 6400 | case PNK_LSHASSIGN: |
michael@0 | 6401 | case PNK_RSHASSIGN: |
michael@0 | 6402 | case PNK_URSHASSIGN: |
michael@0 | 6403 | case PNK_MULASSIGN: |
michael@0 | 6404 | case PNK_DIVASSIGN: |
michael@0 | 6405 | case PNK_MODASSIGN: |
michael@0 | 6406 | if (!EmitAssignment(cx, bce, pn->pn_left, pn->getOp(), pn->pn_right)) |
michael@0 | 6407 | return false; |
michael@0 | 6408 | break; |
michael@0 | 6409 | |
michael@0 | 6410 | case PNK_CONDITIONAL: |
michael@0 | 6411 | ok = EmitConditionalExpression(cx, bce, pn->as<ConditionalExpression>()); |
michael@0 | 6412 | break; |
michael@0 | 6413 | |
michael@0 | 6414 | case PNK_OR: |
michael@0 | 6415 | case PNK_AND: |
michael@0 | 6416 | ok = EmitLogical(cx, bce, pn); |
michael@0 | 6417 | break; |
michael@0 | 6418 | |
michael@0 | 6419 | case PNK_ADD: |
michael@0 | 6420 | case PNK_SUB: |
michael@0 | 6421 | case PNK_BITOR: |
michael@0 | 6422 | case PNK_BITXOR: |
michael@0 | 6423 | case PNK_BITAND: |
michael@0 | 6424 | case PNK_STRICTEQ: |
michael@0 | 6425 | case PNK_EQ: |
michael@0 | 6426 | case PNK_STRICTNE: |
michael@0 | 6427 | case PNK_NE: |
michael@0 | 6428 | case PNK_LT: |
michael@0 | 6429 | case PNK_LE: |
michael@0 | 6430 | case PNK_GT: |
michael@0 | 6431 | case PNK_GE: |
michael@0 | 6432 | case PNK_IN: |
michael@0 | 6433 | case PNK_INSTANCEOF: |
michael@0 | 6434 | case PNK_LSH: |
michael@0 | 6435 | case PNK_RSH: |
michael@0 | 6436 | case PNK_URSH: |
michael@0 | 6437 | case PNK_STAR: |
michael@0 | 6438 | case PNK_DIV: |
michael@0 | 6439 | case PNK_MOD: |
michael@0 | 6440 | if (pn->isArity(PN_LIST)) { |
michael@0 | 6441 | /* Left-associative operator chain: avoid too much recursion. */ |
michael@0 | 6442 | ParseNode *pn2 = pn->pn_head; |
michael@0 | 6443 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 6444 | return false; |
michael@0 | 6445 | JSOp op = pn->getOp(); |
michael@0 | 6446 | while ((pn2 = pn2->pn_next) != nullptr) { |
michael@0 | 6447 | if (!EmitTree(cx, bce, pn2)) |
michael@0 | 6448 | return false; |
michael@0 | 6449 | if (Emit1(cx, bce, op) < 0) |
michael@0 | 6450 | return false; |
michael@0 | 6451 | } |
michael@0 | 6452 | } else { |
michael@0 | 6453 | /* Binary operators that evaluate both operands unconditionally. */ |
michael@0 | 6454 | if (!EmitTree(cx, bce, pn->pn_left)) |
michael@0 | 6455 | return false; |
michael@0 | 6456 | if (!EmitTree(cx, bce, pn->pn_right)) |
michael@0 | 6457 | return false; |
michael@0 | 6458 | if (Emit1(cx, bce, pn->getOp()) < 0) |
michael@0 | 6459 | return false; |
michael@0 | 6460 | } |
michael@0 | 6461 | break; |
michael@0 | 6462 | |
michael@0 | 6463 | case PNK_THROW: |
michael@0 | 6464 | case PNK_TYPEOF: |
michael@0 | 6465 | case PNK_VOID: |
michael@0 | 6466 | case PNK_NOT: |
michael@0 | 6467 | case PNK_BITNOT: |
michael@0 | 6468 | case PNK_POS: |
michael@0 | 6469 | case PNK_NEG: |
michael@0 | 6470 | ok = EmitUnary(cx, bce, pn); |
michael@0 | 6471 | break; |
michael@0 | 6472 | |
michael@0 | 6473 | case PNK_PREINCREMENT: |
michael@0 | 6474 | case PNK_PREDECREMENT: |
michael@0 | 6475 | case PNK_POSTINCREMENT: |
michael@0 | 6476 | case PNK_POSTDECREMENT: |
michael@0 | 6477 | ok = EmitIncOrDec(cx, bce, pn); |
michael@0 | 6478 | break; |
michael@0 | 6479 | |
michael@0 | 6480 | case PNK_DELETE: |
michael@0 | 6481 | ok = EmitDelete(cx, bce, pn); |
michael@0 | 6482 | break; |
michael@0 | 6483 | |
michael@0 | 6484 | case PNK_DOT: |
michael@0 | 6485 | ok = EmitPropOp(cx, pn, JSOP_GETPROP, bce); |
michael@0 | 6486 | break; |
michael@0 | 6487 | |
michael@0 | 6488 | case PNK_ELEM: |
michael@0 | 6489 | ok = EmitElemOp(cx, pn, JSOP_GETELEM, bce); |
michael@0 | 6490 | break; |
michael@0 | 6491 | |
michael@0 | 6492 | case PNK_NEW: |
michael@0 | 6493 | case PNK_CALL: |
michael@0 | 6494 | case PNK_GENEXP: |
michael@0 | 6495 | ok = EmitCallOrNew(cx, bce, pn); |
michael@0 | 6496 | break; |
michael@0 | 6497 | |
michael@0 | 6498 | case PNK_LEXICALSCOPE: |
michael@0 | 6499 | ok = EmitLexicalScope(cx, bce, pn); |
michael@0 | 6500 | break; |
michael@0 | 6501 | |
michael@0 | 6502 | case PNK_LET: |
michael@0 | 6503 | ok = pn->isArity(PN_BINARY) |
michael@0 | 6504 | ? EmitLet(cx, bce, pn) |
michael@0 | 6505 | : EmitVariables(cx, bce, pn, InitializeVars); |
michael@0 | 6506 | break; |
michael@0 | 6507 | |
michael@0 | 6508 | case PNK_IMPORT: |
michael@0 | 6509 | case PNK_EXPORT: |
michael@0 | 6510 | // TODO: Implement emitter support for modules |
michael@0 | 6511 | bce->reportError(nullptr, JSMSG_MODULES_NOT_IMPLEMENTED); |
michael@0 | 6512 | return false; |
michael@0 | 6513 | |
michael@0 | 6514 | case PNK_ARRAYPUSH: { |
michael@0 | 6515 | /* |
michael@0 | 6516 | * The array object's stack index is in bce->arrayCompDepth. See below |
michael@0 | 6517 | * under the array initialiser code generator for array comprehension |
michael@0 | 6518 | * special casing. Note that the array object is a pure stack value, |
michael@0 | 6519 | * unaliased by blocks, so we can EmitUnaliasedVarOp. |
michael@0 | 6520 | */ |
michael@0 | 6521 | if (!EmitTree(cx, bce, pn->pn_kid)) |
michael@0 | 6522 | return false; |
michael@0 | 6523 | if (!EmitDupAt(cx, bce, bce->arrayCompDepth)) |
michael@0 | 6524 | return false; |
michael@0 | 6525 | if (Emit1(cx, bce, JSOP_ARRAYPUSH) < 0) |
michael@0 | 6526 | return false; |
michael@0 | 6527 | break; |
michael@0 | 6528 | } |
michael@0 | 6529 | |
michael@0 | 6530 | case PNK_ARRAY: |
michael@0 | 6531 | if (!(pn->pn_xflags & PNX_NONCONST) && pn->pn_head && bce->checkSingletonContext()) |
michael@0 | 6532 | ok = EmitSingletonInitialiser(cx, bce, pn); |
michael@0 | 6533 | else |
michael@0 | 6534 | ok = EmitArray(cx, bce, pn->pn_head, pn->pn_count); |
michael@0 | 6535 | break; |
michael@0 | 6536 | |
michael@0 | 6537 | case PNK_ARRAYCOMP: |
michael@0 | 6538 | ok = EmitArrayComp(cx, bce, pn); |
michael@0 | 6539 | break; |
michael@0 | 6540 | |
michael@0 | 6541 | case PNK_OBJECT: |
michael@0 | 6542 | ok = EmitObject(cx, bce, pn); |
michael@0 | 6543 | break; |
michael@0 | 6544 | |
michael@0 | 6545 | case PNK_NAME: |
michael@0 | 6546 | if (!EmitNameOp(cx, bce, pn, false)) |
michael@0 | 6547 | return false; |
michael@0 | 6548 | break; |
michael@0 | 6549 | |
michael@0 | 6550 | case PNK_STRING: |
michael@0 | 6551 | ok = EmitAtomOp(cx, pn, pn->getOp(), bce); |
michael@0 | 6552 | break; |
michael@0 | 6553 | |
michael@0 | 6554 | case PNK_NUMBER: |
michael@0 | 6555 | ok = EmitNumberOp(cx, pn->pn_dval, bce); |
michael@0 | 6556 | break; |
michael@0 | 6557 | |
michael@0 | 6558 | case PNK_REGEXP: |
michael@0 | 6559 | ok = EmitRegExp(cx, bce->regexpList.add(pn->as<RegExpLiteral>().objbox()), bce); |
michael@0 | 6560 | break; |
michael@0 | 6561 | |
michael@0 | 6562 | case PNK_TRUE: |
michael@0 | 6563 | case PNK_FALSE: |
michael@0 | 6564 | case PNK_THIS: |
michael@0 | 6565 | case PNK_NULL: |
michael@0 | 6566 | if (Emit1(cx, bce, pn->getOp()) < 0) |
michael@0 | 6567 | return false; |
michael@0 | 6568 | break; |
michael@0 | 6569 | |
michael@0 | 6570 | case PNK_DEBUGGER: |
michael@0 | 6571 | if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.begin)) |
michael@0 | 6572 | return false; |
michael@0 | 6573 | if (Emit1(cx, bce, JSOP_DEBUGGER) < 0) |
michael@0 | 6574 | return false; |
michael@0 | 6575 | break; |
michael@0 | 6576 | |
michael@0 | 6577 | case PNK_NOP: |
michael@0 | 6578 | JS_ASSERT(pn->getArity() == PN_NULLARY); |
michael@0 | 6579 | break; |
michael@0 | 6580 | |
michael@0 | 6581 | default: |
michael@0 | 6582 | JS_ASSERT(0); |
michael@0 | 6583 | } |
michael@0 | 6584 | |
michael@0 | 6585 | /* bce->emitLevel == 1 means we're last on the stack, so finish up. */ |
michael@0 | 6586 | if (ok && bce->emitLevel == 1) { |
michael@0 | 6587 | if (!UpdateSourceCoordNotes(cx, bce, pn->pn_pos.end)) |
michael@0 | 6588 | return false; |
michael@0 | 6589 | } |
michael@0 | 6590 | |
michael@0 | 6591 | return ok; |
michael@0 | 6592 | } |
michael@0 | 6593 | |
michael@0 | 6594 | static int |
michael@0 | 6595 | AllocSrcNote(ExclusiveContext *cx, SrcNotesVector ¬es) |
michael@0 | 6596 | { |
michael@0 | 6597 | // Start it off moderately large to avoid repeated resizings early on. |
michael@0 | 6598 | if (notes.capacity() == 0 && !notes.reserve(1024)) |
michael@0 | 6599 | return -1; |
michael@0 | 6600 | |
michael@0 | 6601 | jssrcnote dummy = 0; |
michael@0 | 6602 | if (!notes.append(dummy)) { |
michael@0 | 6603 | js_ReportOutOfMemory(cx); |
michael@0 | 6604 | return -1; |
michael@0 | 6605 | } |
michael@0 | 6606 | return notes.length() - 1; |
michael@0 | 6607 | } |
michael@0 | 6608 | |
michael@0 | 6609 | int |
michael@0 | 6610 | frontend::NewSrcNote(ExclusiveContext *cx, BytecodeEmitter *bce, SrcNoteType type) |
michael@0 | 6611 | { |
michael@0 | 6612 | SrcNotesVector ¬es = bce->notes(); |
michael@0 | 6613 | int index; |
michael@0 | 6614 | |
michael@0 | 6615 | index = AllocSrcNote(cx, notes); |
michael@0 | 6616 | if (index < 0) |
michael@0 | 6617 | return -1; |
michael@0 | 6618 | |
michael@0 | 6619 | /* |
michael@0 | 6620 | * Compute delta from the last annotated bytecode's offset. If it's too |
michael@0 | 6621 | * big to fit in sn, allocate one or more xdelta notes and reset sn. |
michael@0 | 6622 | */ |
michael@0 | 6623 | ptrdiff_t offset = bce->offset(); |
michael@0 | 6624 | ptrdiff_t delta = offset - bce->lastNoteOffset(); |
michael@0 | 6625 | bce->current->lastNoteOffset = offset; |
michael@0 | 6626 | if (delta >= SN_DELTA_LIMIT) { |
michael@0 | 6627 | do { |
michael@0 | 6628 | ptrdiff_t xdelta = Min(delta, SN_XDELTA_MASK); |
michael@0 | 6629 | SN_MAKE_XDELTA(¬es[index], xdelta); |
michael@0 | 6630 | delta -= xdelta; |
michael@0 | 6631 | index = AllocSrcNote(cx, notes); |
michael@0 | 6632 | if (index < 0) |
michael@0 | 6633 | return -1; |
michael@0 | 6634 | } while (delta >= SN_DELTA_LIMIT); |
michael@0 | 6635 | } |
michael@0 | 6636 | |
michael@0 | 6637 | /* |
michael@0 | 6638 | * Initialize type and delta, then allocate the minimum number of notes |
michael@0 | 6639 | * needed for type's arity. Usually, we won't need more, but if an offset |
michael@0 | 6640 | * does take two bytes, SetSrcNoteOffset will grow notes. |
michael@0 | 6641 | */ |
michael@0 | 6642 | SN_MAKE_NOTE(¬es[index], type, delta); |
michael@0 | 6643 | for (int n = (int)js_SrcNoteSpec[type].arity; n > 0; n--) { |
michael@0 | 6644 | if (NewSrcNote(cx, bce, SRC_NULL) < 0) |
michael@0 | 6645 | return -1; |
michael@0 | 6646 | } |
michael@0 | 6647 | return index; |
michael@0 | 6648 | } |
michael@0 | 6649 | |
michael@0 | 6650 | int |
michael@0 | 6651 | frontend::NewSrcNote2(ExclusiveContext *cx, BytecodeEmitter *bce, SrcNoteType type, ptrdiff_t offset) |
michael@0 | 6652 | { |
michael@0 | 6653 | int index; |
michael@0 | 6654 | |
michael@0 | 6655 | index = NewSrcNote(cx, bce, type); |
michael@0 | 6656 | if (index >= 0) { |
michael@0 | 6657 | if (!SetSrcNoteOffset(cx, bce, index, 0, offset)) |
michael@0 | 6658 | return -1; |
michael@0 | 6659 | } |
michael@0 | 6660 | return index; |
michael@0 | 6661 | } |
michael@0 | 6662 | |
michael@0 | 6663 | int |
michael@0 | 6664 | frontend::NewSrcNote3(ExclusiveContext *cx, BytecodeEmitter *bce, SrcNoteType type, ptrdiff_t offset1, |
michael@0 | 6665 | ptrdiff_t offset2) |
michael@0 | 6666 | { |
michael@0 | 6667 | int index; |
michael@0 | 6668 | |
michael@0 | 6669 | index = NewSrcNote(cx, bce, type); |
michael@0 | 6670 | if (index >= 0) { |
michael@0 | 6671 | if (!SetSrcNoteOffset(cx, bce, index, 0, offset1)) |
michael@0 | 6672 | return -1; |
michael@0 | 6673 | if (!SetSrcNoteOffset(cx, bce, index, 1, offset2)) |
michael@0 | 6674 | return -1; |
michael@0 | 6675 | } |
michael@0 | 6676 | return index; |
michael@0 | 6677 | } |
michael@0 | 6678 | |
michael@0 | 6679 | bool |
michael@0 | 6680 | frontend::AddToSrcNoteDelta(ExclusiveContext *cx, BytecodeEmitter *bce, jssrcnote *sn, ptrdiff_t delta) |
michael@0 | 6681 | { |
michael@0 | 6682 | /* |
michael@0 | 6683 | * Called only from FinishTakingSrcNotes to add to main script note |
michael@0 | 6684 | * deltas, and only by a small positive amount. |
michael@0 | 6685 | */ |
michael@0 | 6686 | JS_ASSERT(bce->current == &bce->main); |
michael@0 | 6687 | JS_ASSERT((unsigned) delta < (unsigned) SN_XDELTA_LIMIT); |
michael@0 | 6688 | |
michael@0 | 6689 | ptrdiff_t base = SN_DELTA(sn); |
michael@0 | 6690 | ptrdiff_t limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT; |
michael@0 | 6691 | ptrdiff_t newdelta = base + delta; |
michael@0 | 6692 | if (newdelta < limit) { |
michael@0 | 6693 | SN_SET_DELTA(sn, newdelta); |
michael@0 | 6694 | } else { |
michael@0 | 6695 | jssrcnote xdelta; |
michael@0 | 6696 | SN_MAKE_XDELTA(&xdelta, delta); |
michael@0 | 6697 | if (!(sn = bce->main.notes.insert(sn, xdelta))) |
michael@0 | 6698 | return false; |
michael@0 | 6699 | } |
michael@0 | 6700 | return true; |
michael@0 | 6701 | } |
michael@0 | 6702 | |
michael@0 | 6703 | static bool |
michael@0 | 6704 | SetSrcNoteOffset(ExclusiveContext *cx, BytecodeEmitter *bce, unsigned index, unsigned which, |
michael@0 | 6705 | ptrdiff_t offset) |
michael@0 | 6706 | { |
michael@0 | 6707 | if (size_t(offset) > SN_MAX_OFFSET) { |
michael@0 | 6708 | ReportStatementTooLarge(bce->parser->tokenStream, bce->topStmt); |
michael@0 | 6709 | return false; |
michael@0 | 6710 | } |
michael@0 | 6711 | |
michael@0 | 6712 | SrcNotesVector ¬es = bce->notes(); |
michael@0 | 6713 | |
michael@0 | 6714 | /* Find the offset numbered which (i.e., skip exactly which offsets). */ |
michael@0 | 6715 | jssrcnote *sn = notes.begin() + index; |
michael@0 | 6716 | JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA); |
michael@0 | 6717 | JS_ASSERT((int) which < js_SrcNoteSpec[SN_TYPE(sn)].arity); |
michael@0 | 6718 | for (sn++; which; sn++, which--) { |
michael@0 | 6719 | if (*sn & SN_4BYTE_OFFSET_FLAG) |
michael@0 | 6720 | sn += 3; |
michael@0 | 6721 | } |
michael@0 | 6722 | |
michael@0 | 6723 | /* |
michael@0 | 6724 | * See if the new offset requires three bytes either by being too big or if |
michael@0 | 6725 | * the offset has already been inflated (in which case, we need to stay big |
michael@0 | 6726 | * to not break the srcnote encoding if this isn't the last srcnote). |
michael@0 | 6727 | */ |
michael@0 | 6728 | if (offset > (ptrdiff_t)SN_4BYTE_OFFSET_MASK || (*sn & SN_4BYTE_OFFSET_FLAG)) { |
michael@0 | 6729 | /* Maybe this offset was already set to a three-byte value. */ |
michael@0 | 6730 | if (!(*sn & SN_4BYTE_OFFSET_FLAG)) { |
michael@0 | 6731 | /* Insert two dummy bytes that will be overwritten shortly. */ |
michael@0 | 6732 | jssrcnote dummy = 0; |
michael@0 | 6733 | if (!(sn = notes.insert(sn, dummy)) || |
michael@0 | 6734 | !(sn = notes.insert(sn, dummy)) || |
michael@0 | 6735 | !(sn = notes.insert(sn, dummy))) |
michael@0 | 6736 | { |
michael@0 | 6737 | js_ReportOutOfMemory(cx); |
michael@0 | 6738 | return false; |
michael@0 | 6739 | } |
michael@0 | 6740 | } |
michael@0 | 6741 | *sn++ = (jssrcnote)(SN_4BYTE_OFFSET_FLAG | (offset >> 24)); |
michael@0 | 6742 | *sn++ = (jssrcnote)(offset >> 16); |
michael@0 | 6743 | *sn++ = (jssrcnote)(offset >> 8); |
michael@0 | 6744 | } |
michael@0 | 6745 | *sn = (jssrcnote)offset; |
michael@0 | 6746 | return true; |
michael@0 | 6747 | } |
michael@0 | 6748 | |
michael@0 | 6749 | /* |
michael@0 | 6750 | * Finish taking source notes in cx's notePool. |
michael@0 | 6751 | * If successful, the final source note count is stored in the out outparam. |
michael@0 | 6752 | */ |
michael@0 | 6753 | bool |
michael@0 | 6754 | frontend::FinishTakingSrcNotes(ExclusiveContext *cx, BytecodeEmitter *bce, uint32_t *out) |
michael@0 | 6755 | { |
michael@0 | 6756 | JS_ASSERT(bce->current == &bce->main); |
michael@0 | 6757 | |
michael@0 | 6758 | unsigned prologCount = bce->prolog.notes.length(); |
michael@0 | 6759 | if (prologCount && bce->prolog.currentLine != bce->firstLine) { |
michael@0 | 6760 | bce->switchToProlog(); |
michael@0 | 6761 | if (NewSrcNote2(cx, bce, SRC_SETLINE, (ptrdiff_t)bce->firstLine) < 0) |
michael@0 | 6762 | return false; |
michael@0 | 6763 | bce->switchToMain(); |
michael@0 | 6764 | } else { |
michael@0 | 6765 | /* |
michael@0 | 6766 | * Either no prolog srcnotes, or no line number change over prolog. |
michael@0 | 6767 | * We don't need a SRC_SETLINE, but we may need to adjust the offset |
michael@0 | 6768 | * of the first main note, by adding to its delta and possibly even |
michael@0 | 6769 | * prepending SRC_XDELTA notes to it to account for prolog bytecodes |
michael@0 | 6770 | * that came at and after the last annotated bytecode. |
michael@0 | 6771 | */ |
michael@0 | 6772 | ptrdiff_t offset = bce->prologOffset() - bce->prolog.lastNoteOffset; |
michael@0 | 6773 | JS_ASSERT(offset >= 0); |
michael@0 | 6774 | if (offset > 0 && bce->main.notes.length() != 0) { |
michael@0 | 6775 | /* NB: Use as much of the first main note's delta as we can. */ |
michael@0 | 6776 | jssrcnote *sn = bce->main.notes.begin(); |
michael@0 | 6777 | ptrdiff_t delta = SN_IS_XDELTA(sn) |
michael@0 | 6778 | ? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK) |
michael@0 | 6779 | : SN_DELTA_MASK - (*sn & SN_DELTA_MASK); |
michael@0 | 6780 | if (offset < delta) |
michael@0 | 6781 | delta = offset; |
michael@0 | 6782 | for (;;) { |
michael@0 | 6783 | if (!AddToSrcNoteDelta(cx, bce, sn, delta)) |
michael@0 | 6784 | return false; |
michael@0 | 6785 | offset -= delta; |
michael@0 | 6786 | if (offset == 0) |
michael@0 | 6787 | break; |
michael@0 | 6788 | delta = Min(offset, SN_XDELTA_MASK); |
michael@0 | 6789 | sn = bce->main.notes.begin(); |
michael@0 | 6790 | } |
michael@0 | 6791 | } |
michael@0 | 6792 | } |
michael@0 | 6793 | |
michael@0 | 6794 | // The prolog count might have changed, so we can't reuse prologCount. |
michael@0 | 6795 | // The + 1 is to account for the final SN_MAKE_TERMINATOR that is appended |
michael@0 | 6796 | // when the notes are copied to their final destination by CopySrcNotes. |
michael@0 | 6797 | *out = bce->prolog.notes.length() + bce->main.notes.length() + 1; |
michael@0 | 6798 | return true; |
michael@0 | 6799 | } |
michael@0 | 6800 | |
michael@0 | 6801 | void |
michael@0 | 6802 | frontend::CopySrcNotes(BytecodeEmitter *bce, jssrcnote *destination, uint32_t nsrcnotes) |
michael@0 | 6803 | { |
michael@0 | 6804 | unsigned prologCount = bce->prolog.notes.length(); |
michael@0 | 6805 | unsigned mainCount = bce->main.notes.length(); |
michael@0 | 6806 | unsigned totalCount = prologCount + mainCount; |
michael@0 | 6807 | MOZ_ASSERT(totalCount == nsrcnotes - 1); |
michael@0 | 6808 | if (prologCount) |
michael@0 | 6809 | PodCopy(destination, bce->prolog.notes.begin(), prologCount); |
michael@0 | 6810 | PodCopy(destination + prologCount, bce->main.notes.begin(), mainCount); |
michael@0 | 6811 | SN_MAKE_TERMINATOR(&destination[totalCount]); |
michael@0 | 6812 | } |
michael@0 | 6813 | |
michael@0 | 6814 | void |
michael@0 | 6815 | CGConstList::finish(ConstArray *array) |
michael@0 | 6816 | { |
michael@0 | 6817 | JS_ASSERT(length() == array->length); |
michael@0 | 6818 | |
michael@0 | 6819 | for (unsigned i = 0; i < length(); i++) |
michael@0 | 6820 | array->vector[i] = list[i]; |
michael@0 | 6821 | } |
michael@0 | 6822 | |
michael@0 | 6823 | /* |
michael@0 | 6824 | * Find the index of the given object for code generator. |
michael@0 | 6825 | * |
michael@0 | 6826 | * Since the emitter refers to each parsed object only once, for the index we |
michael@0 | 6827 | * use the number of already indexes objects. We also add the object to a list |
michael@0 | 6828 | * to convert the list to a fixed-size array when we complete code generation, |
michael@0 | 6829 | * see js::CGObjectList::finish below. |
michael@0 | 6830 | * |
michael@0 | 6831 | * Most of the objects go to BytecodeEmitter::objectList but for regexp we use |
michael@0 | 6832 | * a separated BytecodeEmitter::regexpList. In this way the emitted index can |
michael@0 | 6833 | * be directly used to store and fetch a reference to a cloned RegExp object |
michael@0 | 6834 | * that shares the same JSRegExp private data created for the object literal in |
michael@0 | 6835 | * objbox. We need a cloned object to hold lastIndex and other direct |
michael@0 | 6836 | * properties that should not be shared among threads sharing a precompiled |
michael@0 | 6837 | * function or script. |
michael@0 | 6838 | * |
michael@0 | 6839 | * If the code being compiled is function code, allocate a reserved slot in |
michael@0 | 6840 | * the cloned function object that shares its precompiled script with other |
michael@0 | 6841 | * cloned function objects and with the compiler-created clone-parent. There |
michael@0 | 6842 | * are nregexps = script->regexps()->length such reserved slots in each |
michael@0 | 6843 | * function object cloned from fun->object. NB: during compilation, a funobj |
michael@0 | 6844 | * slots element must never be allocated, because JSObject::allocSlot could |
michael@0 | 6845 | * hand out one of the slots that should be given to a regexp clone. |
michael@0 | 6846 | * |
michael@0 | 6847 | * If the code being compiled is global code, the cloned regexp are stored in |
michael@0 | 6848 | * fp->vars slot and to protect regexp slots from GC we set fp->nvars to |
michael@0 | 6849 | * nregexps. |
michael@0 | 6850 | * |
michael@0 | 6851 | * The slots initially contain undefined or null. We populate them lazily when |
michael@0 | 6852 | * JSOP_REGEXP is executed for the first time. |
michael@0 | 6853 | * |
michael@0 | 6854 | * Why clone regexp objects? ECMA specifies that when a regular expression |
michael@0 | 6855 | * literal is scanned, a RegExp object is created. In the spec, compilation |
michael@0 | 6856 | * and execution happen indivisibly, but in this implementation and many of |
michael@0 | 6857 | * its embeddings, code is precompiled early and re-executed in multiple |
michael@0 | 6858 | * threads, or using multiple global objects, or both, for efficiency. |
michael@0 | 6859 | * |
michael@0 | 6860 | * In such cases, naively following ECMA leads to wrongful sharing of RegExp |
michael@0 | 6861 | * objects, which makes for collisions on the lastIndex property (especially |
michael@0 | 6862 | * for global regexps) and on any ad-hoc properties. Also, __proto__ refers to |
michael@0 | 6863 | * the pre-compilation prototype, a pigeon-hole problem for instanceof tests. |
michael@0 | 6864 | */ |
michael@0 | 6865 | unsigned |
michael@0 | 6866 | CGObjectList::add(ObjectBox *objbox) |
michael@0 | 6867 | { |
michael@0 | 6868 | JS_ASSERT(!objbox->emitLink); |
michael@0 | 6869 | objbox->emitLink = lastbox; |
michael@0 | 6870 | lastbox = objbox; |
michael@0 | 6871 | return length++; |
michael@0 | 6872 | } |
michael@0 | 6873 | |
michael@0 | 6874 | unsigned |
michael@0 | 6875 | CGObjectList::indexOf(JSObject *obj) |
michael@0 | 6876 | { |
michael@0 | 6877 | JS_ASSERT(length > 0); |
michael@0 | 6878 | unsigned index = length - 1; |
michael@0 | 6879 | for (ObjectBox *box = lastbox; box->object != obj; box = box->emitLink) |
michael@0 | 6880 | index--; |
michael@0 | 6881 | return index; |
michael@0 | 6882 | } |
michael@0 | 6883 | |
michael@0 | 6884 | void |
michael@0 | 6885 | CGObjectList::finish(ObjectArray *array) |
michael@0 | 6886 | { |
michael@0 | 6887 | JS_ASSERT(length <= INDEX_LIMIT); |
michael@0 | 6888 | JS_ASSERT(length == array->length); |
michael@0 | 6889 | |
michael@0 | 6890 | js::HeapPtrObject *cursor = array->vector + array->length; |
michael@0 | 6891 | ObjectBox *objbox = lastbox; |
michael@0 | 6892 | do { |
michael@0 | 6893 | --cursor; |
michael@0 | 6894 | JS_ASSERT(!*cursor); |
michael@0 | 6895 | *cursor = objbox->object; |
michael@0 | 6896 | } while ((objbox = objbox->emitLink) != nullptr); |
michael@0 | 6897 | JS_ASSERT(cursor == array->vector); |
michael@0 | 6898 | } |
michael@0 | 6899 | |
michael@0 | 6900 | ObjectBox* |
michael@0 | 6901 | CGObjectList::find(uint32_t index) |
michael@0 | 6902 | { |
michael@0 | 6903 | JS_ASSERT(index < length); |
michael@0 | 6904 | ObjectBox *box = lastbox; |
michael@0 | 6905 | for (unsigned n = length - 1; n > index; n--) |
michael@0 | 6906 | box = box->emitLink; |
michael@0 | 6907 | return box; |
michael@0 | 6908 | } |
michael@0 | 6909 | |
michael@0 | 6910 | bool |
michael@0 | 6911 | CGTryNoteList::append(JSTryNoteKind kind, uint32_t stackDepth, size_t start, size_t end) |
michael@0 | 6912 | { |
michael@0 | 6913 | JS_ASSERT(start <= end); |
michael@0 | 6914 | JS_ASSERT(size_t(uint32_t(start)) == start); |
michael@0 | 6915 | JS_ASSERT(size_t(uint32_t(end)) == end); |
michael@0 | 6916 | |
michael@0 | 6917 | JSTryNote note; |
michael@0 | 6918 | note.kind = kind; |
michael@0 | 6919 | note.stackDepth = stackDepth; |
michael@0 | 6920 | note.start = uint32_t(start); |
michael@0 | 6921 | note.length = uint32_t(end - start); |
michael@0 | 6922 | |
michael@0 | 6923 | return list.append(note); |
michael@0 | 6924 | } |
michael@0 | 6925 | |
michael@0 | 6926 | void |
michael@0 | 6927 | CGTryNoteList::finish(TryNoteArray *array) |
michael@0 | 6928 | { |
michael@0 | 6929 | JS_ASSERT(length() == array->length); |
michael@0 | 6930 | |
michael@0 | 6931 | for (unsigned i = 0; i < length(); i++) |
michael@0 | 6932 | array->vector[i] = list[i]; |
michael@0 | 6933 | } |
michael@0 | 6934 | |
michael@0 | 6935 | bool |
michael@0 | 6936 | CGBlockScopeList::append(uint32_t scopeObject, uint32_t offset, uint32_t parent) |
michael@0 | 6937 | { |
michael@0 | 6938 | BlockScopeNote note; |
michael@0 | 6939 | mozilla::PodZero(¬e); |
michael@0 | 6940 | |
michael@0 | 6941 | note.index = scopeObject; |
michael@0 | 6942 | note.start = offset; |
michael@0 | 6943 | note.parent = parent; |
michael@0 | 6944 | |
michael@0 | 6945 | return list.append(note); |
michael@0 | 6946 | } |
michael@0 | 6947 | |
michael@0 | 6948 | uint32_t |
michael@0 | 6949 | CGBlockScopeList::findEnclosingScope(uint32_t index) |
michael@0 | 6950 | { |
michael@0 | 6951 | JS_ASSERT(index < length()); |
michael@0 | 6952 | JS_ASSERT(list[index].index != BlockScopeNote::NoBlockScopeIndex); |
michael@0 | 6953 | |
michael@0 | 6954 | DebugOnly<uint32_t> pos = list[index].start; |
michael@0 | 6955 | while (index--) { |
michael@0 | 6956 | JS_ASSERT(list[index].start <= pos); |
michael@0 | 6957 | if (list[index].length == 0) { |
michael@0 | 6958 | // We are looking for the nearest enclosing live scope. If the |
michael@0 | 6959 | // scope contains POS, it should still be open, so its length should |
michael@0 | 6960 | // be zero. |
michael@0 | 6961 | return list[index].index; |
michael@0 | 6962 | } else { |
michael@0 | 6963 | // Conversely, if the length is not zero, it should not contain |
michael@0 | 6964 | // POS. |
michael@0 | 6965 | JS_ASSERT(list[index].start + list[index].length <= pos); |
michael@0 | 6966 | } |
michael@0 | 6967 | } |
michael@0 | 6968 | |
michael@0 | 6969 | return BlockScopeNote::NoBlockScopeIndex; |
michael@0 | 6970 | } |
michael@0 | 6971 | |
michael@0 | 6972 | void |
michael@0 | 6973 | CGBlockScopeList::recordEnd(uint32_t index, uint32_t offset) |
michael@0 | 6974 | { |
michael@0 | 6975 | JS_ASSERT(index < length()); |
michael@0 | 6976 | JS_ASSERT(offset >= list[index].start); |
michael@0 | 6977 | JS_ASSERT(list[index].length == 0); |
michael@0 | 6978 | |
michael@0 | 6979 | list[index].length = offset - list[index].start; |
michael@0 | 6980 | } |
michael@0 | 6981 | |
michael@0 | 6982 | void |
michael@0 | 6983 | CGBlockScopeList::finish(BlockScopeArray *array) |
michael@0 | 6984 | { |
michael@0 | 6985 | JS_ASSERT(length() == array->length); |
michael@0 | 6986 | |
michael@0 | 6987 | for (unsigned i = 0; i < length(); i++) |
michael@0 | 6988 | array->vector[i] = list[i]; |
michael@0 | 6989 | } |
michael@0 | 6990 | |
michael@0 | 6991 | /* |
michael@0 | 6992 | * We should try to get rid of offsetBias (always 0 or 1, where 1 is |
michael@0 | 6993 | * JSOP_{NOP,POP}_LENGTH), which is used only by SRC_FOR. |
michael@0 | 6994 | */ |
michael@0 | 6995 | const JSSrcNoteSpec js_SrcNoteSpec[] = { |
michael@0 | 6996 | #define DEFINE_SRC_NOTE_SPEC(sym, name, arity) { name, arity }, |
michael@0 | 6997 | FOR_EACH_SRC_NOTE_TYPE(DEFINE_SRC_NOTE_SPEC) |
michael@0 | 6998 | #undef DEFINE_SRC_NOTE_SPEC |
michael@0 | 6999 | }; |
michael@0 | 7000 | |
michael@0 | 7001 | static int |
michael@0 | 7002 | SrcNoteArity(jssrcnote *sn) |
michael@0 | 7003 | { |
michael@0 | 7004 | JS_ASSERT(SN_TYPE(sn) < SRC_LAST); |
michael@0 | 7005 | return js_SrcNoteSpec[SN_TYPE(sn)].arity; |
michael@0 | 7006 | } |
michael@0 | 7007 | |
michael@0 | 7008 | JS_FRIEND_API(unsigned) |
michael@0 | 7009 | js_SrcNoteLength(jssrcnote *sn) |
michael@0 | 7010 | { |
michael@0 | 7011 | unsigned arity; |
michael@0 | 7012 | jssrcnote *base; |
michael@0 | 7013 | |
michael@0 | 7014 | arity = SrcNoteArity(sn); |
michael@0 | 7015 | for (base = sn++; arity; sn++, arity--) { |
michael@0 | 7016 | if (*sn & SN_4BYTE_OFFSET_FLAG) |
michael@0 | 7017 | sn += 3; |
michael@0 | 7018 | } |
michael@0 | 7019 | return sn - base; |
michael@0 | 7020 | } |
michael@0 | 7021 | |
michael@0 | 7022 | JS_FRIEND_API(ptrdiff_t) |
michael@0 | 7023 | js_GetSrcNoteOffset(jssrcnote *sn, unsigned which) |
michael@0 | 7024 | { |
michael@0 | 7025 | /* Find the offset numbered which (i.e., skip exactly which offsets). */ |
michael@0 | 7026 | JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA); |
michael@0 | 7027 | JS_ASSERT((int) which < SrcNoteArity(sn)); |
michael@0 | 7028 | for (sn++; which; sn++, which--) { |
michael@0 | 7029 | if (*sn & SN_4BYTE_OFFSET_FLAG) |
michael@0 | 7030 | sn += 3; |
michael@0 | 7031 | } |
michael@0 | 7032 | if (*sn & SN_4BYTE_OFFSET_FLAG) { |
michael@0 | 7033 | return (ptrdiff_t)(((uint32_t)(sn[0] & SN_4BYTE_OFFSET_MASK) << 24) |
michael@0 | 7034 | | (sn[1] << 16) |
michael@0 | 7035 | | (sn[2] << 8) |
michael@0 | 7036 | | sn[3]); |
michael@0 | 7037 | } |
michael@0 | 7038 | return (ptrdiff_t)*sn; |
michael@0 | 7039 | } |