mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-11 02:10:17 +01:00
6864 lines
237 KiB
C
6864 lines
237 KiB
C
|
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
||
|
* vim: set ts=8 sw=4 et tw=78:
|
||
|
*
|
||
|
* ***** BEGIN LICENSE BLOCK *****
|
||
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||
|
*
|
||
|
* The contents of this file are subject to the Mozilla Public License Version
|
||
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
||
|
* the License. You may obtain a copy of the License at
|
||
|
* http://www.mozilla.org/MPL/
|
||
|
*
|
||
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
||
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||
|
* for the specific language governing rights and limitations under the
|
||
|
* License.
|
||
|
*
|
||
|
* The Original Code is Mozilla Communicator client code, released
|
||
|
* March 31, 1998.
|
||
|
*
|
||
|
* The Initial Developer of the Original Code is
|
||
|
* Netscape Communications Corporation.
|
||
|
* Portions created by the Initial Developer are Copyright (C) 1998
|
||
|
* the Initial Developer. All Rights Reserved.
|
||
|
*
|
||
|
* Contributor(s):
|
||
|
*
|
||
|
* Alternatively, the contents of this file may be used under the terms of
|
||
|
* either of the GNU General Public License Version 2 or later (the "GPL"),
|
||
|
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||
|
* of those above. If you wish to allow use of your version of this file only
|
||
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||
|
* use your version of this file under the terms of the MPL, indicate your
|
||
|
* decision by deleting the provisions above and replace them with the notice
|
||
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||
|
* the provisions above, a recipient may use your version of this file under
|
||
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
||
|
*
|
||
|
* ***** END LICENSE BLOCK ***** */
|
||
|
|
||
|
/*
|
||
|
* JS bytecode generation.
|
||
|
*/
|
||
|
#include "jsstddef.h"
|
||
|
#ifdef HAVE_MEMORY_H
|
||
|
#include <memory.h>
|
||
|
#endif
|
||
|
#include <string.h>
|
||
|
#include "jstypes.h"
|
||
|
#include "jsarena.h" /* Added by JSIFY */
|
||
|
#include "jsutil.h" /* Added by JSIFY */
|
||
|
#include "jsbit.h"
|
||
|
#include "jsprf.h"
|
||
|
#include "jsapi.h"
|
||
|
#include "jsatom.h"
|
||
|
#include "jscntxt.h"
|
||
|
#include "jsconfig.h"
|
||
|
#include "jsemit.h"
|
||
|
#include "jsfun.h"
|
||
|
#include "jsnum.h"
|
||
|
#include "jsopcode.h"
|
||
|
#include "jsparse.h"
|
||
|
#include "jsregexp.h"
|
||
|
#include "jsscan.h"
|
||
|
#include "jsscope.h"
|
||
|
#include "jsscript.h"
|
||
|
|
||
|
/* Allocation chunk counts, must be powers of two in general. */
|
||
|
#define BYTECODE_CHUNK 256 /* code allocation increment */
|
||
|
#define SRCNOTE_CHUNK 64 /* initial srcnote allocation increment */
|
||
|
#define TRYNOTE_CHUNK 64 /* trynote allocation increment */
|
||
|
|
||
|
/* Macros to compute byte sizes from typed element counts. */
|
||
|
#define BYTECODE_SIZE(n) ((n) * sizeof(jsbytecode))
|
||
|
#define SRCNOTE_SIZE(n) ((n) * sizeof(jssrcnote))
|
||
|
#define TRYNOTE_SIZE(n) ((n) * sizeof(JSTryNote))
|
||
|
|
||
|
JS_FRIEND_API(JSBool)
|
||
|
js_InitCodeGenerator(JSContext *cx, JSCodeGenerator *cg,
|
||
|
JSArenaPool *codePool, JSArenaPool *notePool,
|
||
|
const char *filename, uintN lineno,
|
||
|
JSPrincipals *principals)
|
||
|
{
|
||
|
memset(cg, 0, sizeof *cg);
|
||
|
TREE_CONTEXT_INIT(&cg->treeContext);
|
||
|
cg->treeContext.flags |= TCF_COMPILING;
|
||
|
cg->codePool = codePool;
|
||
|
cg->notePool = notePool;
|
||
|
cg->codeMark = JS_ARENA_MARK(codePool);
|
||
|
cg->noteMark = JS_ARENA_MARK(notePool);
|
||
|
cg->tempMark = JS_ARENA_MARK(&cx->tempPool);
|
||
|
cg->current = &cg->main;
|
||
|
cg->filename = filename;
|
||
|
cg->firstLine = cg->prolog.currentLine = cg->main.currentLine = lineno;
|
||
|
cg->principals = principals;
|
||
|
ATOM_LIST_INIT(&cg->atomList);
|
||
|
cg->prolog.noteMask = cg->main.noteMask = SRCNOTE_CHUNK - 1;
|
||
|
ATOM_LIST_INIT(&cg->constList);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
JS_FRIEND_API(void)
|
||
|
js_FinishCodeGenerator(JSContext *cx, JSCodeGenerator *cg)
|
||
|
{
|
||
|
TREE_CONTEXT_FINISH(&cg->treeContext);
|
||
|
JS_ARENA_RELEASE(cg->codePool, cg->codeMark);
|
||
|
JS_ARENA_RELEASE(cg->notePool, cg->noteMark);
|
||
|
JS_ARENA_RELEASE(&cx->tempPool, cg->tempMark);
|
||
|
}
|
||
|
|
||
|
static ptrdiff_t
|
||
|
EmitCheck(JSContext *cx, JSCodeGenerator *cg, JSOp op, ptrdiff_t delta)
|
||
|
{
|
||
|
jsbytecode *base, *limit, *next;
|
||
|
ptrdiff_t offset, length;
|
||
|
size_t incr, size;
|
||
|
|
||
|
base = CG_BASE(cg);
|
||
|
next = CG_NEXT(cg);
|
||
|
limit = CG_LIMIT(cg);
|
||
|
offset = PTRDIFF(next, base, jsbytecode);
|
||
|
if (next + delta > limit) {
|
||
|
length = offset + delta;
|
||
|
length = (length <= BYTECODE_CHUNK)
|
||
|
? BYTECODE_CHUNK
|
||
|
: JS_BIT(JS_CeilingLog2(length));
|
||
|
incr = BYTECODE_SIZE(length);
|
||
|
if (!base) {
|
||
|
JS_ARENA_ALLOCATE_CAST(base, jsbytecode *, cg->codePool, incr);
|
||
|
} else {
|
||
|
size = BYTECODE_SIZE(PTRDIFF(limit, base, jsbytecode));
|
||
|
incr -= size;
|
||
|
JS_ARENA_GROW_CAST(base, jsbytecode *, cg->codePool, size, incr);
|
||
|
}
|
||
|
if (!base) {
|
||
|
JS_ReportOutOfMemory(cx);
|
||
|
return -1;
|
||
|
}
|
||
|
CG_BASE(cg) = base;
|
||
|
CG_LIMIT(cg) = base + length;
|
||
|
CG_NEXT(cg) = base + offset;
|
||
|
}
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
UpdateDepth(JSContext *cx, JSCodeGenerator *cg, ptrdiff_t target)
|
||
|
{
|
||
|
jsbytecode *pc;
|
||
|
const JSCodeSpec *cs;
|
||
|
intN nuses;
|
||
|
|
||
|
pc = CG_CODE(cg, target);
|
||
|
cs = &js_CodeSpec[pc[0]];
|
||
|
nuses = cs->nuses;
|
||
|
if (nuses < 0)
|
||
|
nuses = 2 + GET_ARGC(pc); /* stack: fun, this, [argc arguments] */
|
||
|
cg->stackDepth -= nuses;
|
||
|
JS_ASSERT(cg->stackDepth >= 0);
|
||
|
if (cg->stackDepth < 0) {
|
||
|
char numBuf[12];
|
||
|
JS_snprintf(numBuf, sizeof numBuf, "%d", target);
|
||
|
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_WARNING,
|
||
|
js_GetErrorMessage, NULL,
|
||
|
JSMSG_STACK_UNDERFLOW,
|
||
|
cg->filename ? cg->filename : "stdin",
|
||
|
numBuf);
|
||
|
}
|
||
|
cg->stackDepth += cs->ndefs;
|
||
|
if ((uintN)cg->stackDepth > cg->maxStackDepth)
|
||
|
cg->maxStackDepth = cg->stackDepth;
|
||
|
}
|
||
|
|
||
|
ptrdiff_t
|
||
|
js_Emit1(JSContext *cx, JSCodeGenerator *cg, JSOp op)
|
||
|
{
|
||
|
ptrdiff_t offset = EmitCheck(cx, cg, op, 1);
|
||
|
|
||
|
if (offset >= 0) {
|
||
|
*CG_NEXT(cg)++ = (jsbytecode)op;
|
||
|
UpdateDepth(cx, cg, offset);
|
||
|
}
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
ptrdiff_t
|
||
|
js_Emit2(JSContext *cx, JSCodeGenerator *cg, JSOp op, jsbytecode op1)
|
||
|
{
|
||
|
ptrdiff_t offset = EmitCheck(cx, cg, op, 2);
|
||
|
|
||
|
if (offset >= 0) {
|
||
|
jsbytecode *next = CG_NEXT(cg);
|
||
|
next[0] = (jsbytecode)op;
|
||
|
next[1] = op1;
|
||
|
CG_NEXT(cg) = next + 2;
|
||
|
UpdateDepth(cx, cg, offset);
|
||
|
}
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
ptrdiff_t
|
||
|
js_Emit3(JSContext *cx, JSCodeGenerator *cg, JSOp op, jsbytecode op1,
|
||
|
jsbytecode op2)
|
||
|
{
|
||
|
ptrdiff_t offset = EmitCheck(cx, cg, op, 3);
|
||
|
|
||
|
if (offset >= 0) {
|
||
|
jsbytecode *next = CG_NEXT(cg);
|
||
|
next[0] = (jsbytecode)op;
|
||
|
next[1] = op1;
|
||
|
next[2] = op2;
|
||
|
CG_NEXT(cg) = next + 3;
|
||
|
UpdateDepth(cx, cg, offset);
|
||
|
}
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
ptrdiff_t
|
||
|
js_EmitN(JSContext *cx, JSCodeGenerator *cg, JSOp op, size_t extra)
|
||
|
{
|
||
|
ptrdiff_t length = 1 + (ptrdiff_t)extra;
|
||
|
ptrdiff_t offset = EmitCheck(cx, cg, op, length);
|
||
|
|
||
|
if (offset >= 0) {
|
||
|
jsbytecode *next = CG_NEXT(cg);
|
||
|
*next = (jsbytecode)op;
|
||
|
memset(next + 1, 0, BYTECODE_SIZE(extra));
|
||
|
CG_NEXT(cg) = next + length;
|
||
|
UpdateDepth(cx, cg, offset);
|
||
|
}
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
/* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */
|
||
|
const char js_with_statement_str[] = "with statement";
|
||
|
const char js_finally_block_str[] = "finally block";
|
||
|
const char js_script_str[] = "script";
|
||
|
|
||
|
static const char *statementName[] = {
|
||
|
"label statement", /* LABEL */
|
||
|
"if statement", /* IF */
|
||
|
"else statement", /* ELSE */
|
||
|
"switch statement", /* SWITCH */
|
||
|
"block", /* BLOCK */
|
||
|
js_with_statement_str, /* WITH */
|
||
|
"catch block", /* CATCH */
|
||
|
"try block", /* TRY */
|
||
|
js_finally_block_str, /* FINALLY */
|
||
|
js_finally_block_str, /* SUBROUTINE */
|
||
|
"do loop", /* DO_LOOP */
|
||
|
"for loop", /* FOR_LOOP */
|
||
|
"for/in loop", /* FOR_IN_LOOP */
|
||
|
"while loop", /* WHILE_LOOP */
|
||
|
};
|
||
|
|
||
|
static const char *
|
||
|
StatementName(JSCodeGenerator *cg)
|
||
|
{
|
||
|
if (!cg->treeContext.topStmt)
|
||
|
return js_script_str;
|
||
|
return statementName[cg->treeContext.topStmt->type];
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
ReportStatementTooLarge(JSContext *cx, JSCodeGenerator *cg)
|
||
|
{
|
||
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEED_DIET,
|
||
|
StatementName(cg));
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Span-dependent instructions in JS bytecode consist of the jump (JOF_JUMP)
|
||
|
and switch (JOF_LOOKUPSWITCH, JOF_TABLESWITCH) format opcodes, subdivided
|
||
|
into unconditional (gotos and gosubs), and conditional jumps or branches
|
||
|
(which pop a value, test it, and jump depending on its value). Most jumps
|
||
|
have just one immediate operand, a signed offset from the jump opcode's pc
|
||
|
to the target bytecode. The lookup and table switch opcodes may contain
|
||
|
many jump offsets.
|
||
|
|
||
|
Mozilla bug #80981 (http://bugzilla.mozilla.org/show_bug.cgi?id=80981) was
|
||
|
fixed by adding extended "X" counterparts to the opcodes/formats (NB: X is
|
||
|
suffixed to prefer JSOP_ORX thereby avoiding a JSOP_XOR name collision for
|
||
|
the extended form of the JSOP_OR branch opcode). The unextended or short
|
||
|
formats have 16-bit signed immediate offset operands, the extended or long
|
||
|
formats have 32-bit signed immediates. The span-dependency problem consists
|
||
|
of selecting as few long instructions as possible, or about as few -- since
|
||
|
jumps can span other jumps, extending one jump may cause another to need to
|
||
|
be extended.
|
||
|
|
||
|
Most JS scripts are short, so need no extended jumps. We optimize for this
|
||
|
case by generating short jumps until we know a long jump is needed. After
|
||
|
that point, we keep generating short jumps, but each jump's 16-bit immediate
|
||
|
offset operand is actually an unsigned index into cg->spanDeps, an array of
|
||
|
JSSpanDep structs. Each struct tells the top offset in the script of the
|
||
|
opcode, the "before" offset of the jump (which will be the same as top for
|
||
|
simplex jumps, but which will index further into the bytecode array for a
|
||
|
non-initial jump offset in a lookup or table switch), the after "offset"
|
||
|
adjusted during span-dependent instruction selection (initially the same
|
||
|
value as the "before" offset), and the jump target (more below).
|
||
|
|
||
|
Since we generate cg->spanDeps lazily, from within js_SetJumpOffset, we must
|
||
|
ensure that all bytecode generated so far can be inspected to discover where
|
||
|
the jump offset immediate operands lie within CG_CODE(cg). But the bonus is
|
||
|
that we generate span-dependency records sorted by their offsets, so we can
|
||
|
binary-search when trying to find a JSSpanDep for a given bytecode offset,
|
||
|
or the nearest JSSpanDep at or above a given pc.
|
||
|
|
||
|
To avoid limiting scripts to 64K jumps, if the cg->spanDeps index overflows
|
||
|
65534, we store SPANDEP_INDEX_HUGE in the jump's immediate operand. This
|
||
|
tells us that we need to binary-search for the cg->spanDeps entry by the
|
||
|
jump opcode's bytecode offset (sd->before).
|
||
|
|
||
|
Jump targets need to be maintained in a data structure that lets us look
|
||
|
up an already-known target by its address (jumps may have a common target),
|
||
|
and that also lets us update the addresses (script-relative, a.k.a. absolute
|
||
|
offsets) of targets that come after a jump target (for when a jump below
|
||
|
that target needs to be extended). We use an AVL tree, implemented using
|
||
|
recursion, but with some tricky optimizations to its height-balancing code
|
||
|
(see http://www.cmcrossroads.com/bradapp/ftp/src/libs/C++/AvlTrees.html).
|
||
|
|
||
|
A final wrinkle: backpatch chains are linked by jump-to-jump offsets with
|
||
|
positive sign, even though they link "backward" (i.e., toward lower bytecode
|
||
|
address). We don't want to waste space and search time in the AVL tree for
|
||
|
such temporary backpatch deltas, so we use a single-bit wildcard scheme to
|
||
|
tag true JSJumpTarget pointers and encode untagged, signed (positive) deltas
|
||
|
in JSSpanDep.target pointers, depending on whether the JSSpanDep has a known
|
||
|
target, or is still awaiting backpatching.
|
||
|
|
||
|
Note that backpatch chains would present a problem for BuildSpanDepTable,
|
||
|
which inspects bytecode to build cg->spanDeps on demand, when the first
|
||
|
short jump offset overflows. To solve this temporary problem, we emit a
|
||
|
proxy bytecode (JSOP_BACKPATCH; JSOP_BACKPATCH_POP for branch ops) whose
|
||
|
nuses/ndefs counts help keep the stack balanced, but whose opcode format
|
||
|
distinguishes its backpatch delta immediate operand from a normal jump
|
||
|
offset.
|
||
|
*/
|
||
|
static int
|
||
|
BalanceJumpTargets(JSJumpTarget **jtp)
|
||
|
{
|
||
|
JSJumpTarget *jt, *jt2, *root;
|
||
|
int dir, otherDir, heightChanged;
|
||
|
JSBool doubleRotate;
|
||
|
|
||
|
jt = *jtp;
|
||
|
JS_ASSERT(jt->balance != 0);
|
||
|
|
||
|
if (jt->balance < -1) {
|
||
|
dir = JT_RIGHT;
|
||
|
doubleRotate = (jt->kids[JT_LEFT]->balance > 0);
|
||
|
} else if (jt->balance > 1) {
|
||
|
dir = JT_LEFT;
|
||
|
doubleRotate = (jt->kids[JT_RIGHT]->balance < 0);
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
otherDir = JT_OTHER_DIR(dir);
|
||
|
if (doubleRotate) {
|
||
|
jt2 = jt->kids[otherDir];
|
||
|
*jtp = root = jt2->kids[dir];
|
||
|
|
||
|
jt->kids[otherDir] = root->kids[dir];
|
||
|
root->kids[dir] = jt;
|
||
|
|
||
|
jt2->kids[dir] = root->kids[otherDir];
|
||
|
root->kids[otherDir] = jt2;
|
||
|
|
||
|
heightChanged = 1;
|
||
|
root->kids[JT_LEFT]->balance = -JS_MAX(root->balance, 0);
|
||
|
root->kids[JT_RIGHT]->balance = -JS_MIN(root->balance, 0);
|
||
|
root->balance = 0;
|
||
|
} else {
|
||
|
*jtp = root = jt->kids[otherDir];
|
||
|
jt->kids[otherDir] = root->kids[dir];
|
||
|
root->kids[dir] = jt;
|
||
|
|
||
|
heightChanged = (root->balance != 0);
|
||
|
jt->balance = -((dir == JT_LEFT) ? --root->balance : ++root->balance);
|
||
|
}
|
||
|
|
||
|
return heightChanged;
|
||
|
}
|
||
|
|
||
|
typedef struct AddJumpTargetArgs {
|
||
|
JSContext *cx;
|
||
|
JSCodeGenerator *cg;
|
||
|
ptrdiff_t offset;
|
||
|
JSJumpTarget *node;
|
||
|
} AddJumpTargetArgs;
|
||
|
|
||
|
static int
|
||
|
AddJumpTarget(AddJumpTargetArgs *args, JSJumpTarget **jtp)
|
||
|
{
|
||
|
JSJumpTarget *jt;
|
||
|
int balanceDelta;
|
||
|
|
||
|
jt = *jtp;
|
||
|
if (!jt) {
|
||
|
JSCodeGenerator *cg = args->cg;
|
||
|
|
||
|
jt = cg->jtFreeList;
|
||
|
if (jt) {
|
||
|
cg->jtFreeList = jt->kids[JT_LEFT];
|
||
|
} else {
|
||
|
JS_ARENA_ALLOCATE_CAST(jt, JSJumpTarget *, &args->cx->tempPool,
|
||
|
sizeof *jt);
|
||
|
if (!jt) {
|
||
|
JS_ReportOutOfMemory(args->cx);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
jt->offset = args->offset;
|
||
|
jt->balance = 0;
|
||
|
jt->kids[JT_LEFT] = jt->kids[JT_RIGHT] = NULL;
|
||
|
cg->numJumpTargets++;
|
||
|
args->node = jt;
|
||
|
*jtp = jt;
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
if (jt->offset == args->offset) {
|
||
|
args->node = jt;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (args->offset < jt->offset)
|
||
|
balanceDelta = -AddJumpTarget(args, &jt->kids[JT_LEFT]);
|
||
|
else
|
||
|
balanceDelta = AddJumpTarget(args, &jt->kids[JT_RIGHT]);
|
||
|
if (!args->node)
|
||
|
return 0;
|
||
|
|
||
|
jt->balance += balanceDelta;
|
||
|
return (balanceDelta && jt->balance)
|
||
|
? 1 - BalanceJumpTargets(jtp)
|
||
|
: 0;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_brendan
|
||
|
static int AVLCheck(JSJumpTarget *jt)
|
||
|
{
|
||
|
int lh, rh;
|
||
|
|
||
|
if (!jt) return 0;
|
||
|
JS_ASSERT(-1 <= jt->balance && jt->balance <= 1);
|
||
|
lh = AVLCheck(jt->kids[JT_LEFT]);
|
||
|
rh = AVLCheck(jt->kids[JT_RIGHT]);
|
||
|
JS_ASSERT(jt->balance == rh - lh);
|
||
|
return 1 + JS_MAX(lh, rh);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
static JSBool
|
||
|
SetSpanDepTarget(JSContext *cx, JSCodeGenerator *cg, JSSpanDep *sd,
|
||
|
ptrdiff_t off)
|
||
|
{
|
||
|
AddJumpTargetArgs args;
|
||
|
|
||
|
if (off < JUMPX_OFFSET_MIN || JUMPX_OFFSET_MAX < off) {
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
args.cx = cx;
|
||
|
args.cg = cg;
|
||
|
args.offset = sd->top + off;
|
||
|
args.node = NULL;
|
||
|
AddJumpTarget(&args, &cg->jumpTargets);
|
||
|
if (!args.node)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
#ifdef DEBUG_brendan
|
||
|
AVLCheck(cg->jumpTargets);
|
||
|
#endif
|
||
|
|
||
|
SD_SET_TARGET(sd, args.node);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
#define SPANDEPS_MIN 256
|
||
|
#define SPANDEPS_SIZE(n) ((n) * sizeof(JSSpanDep))
|
||
|
#define SPANDEPS_SIZE_MIN SPANDEPS_SIZE(SPANDEPS_MIN)
|
||
|
|
||
|
static JSBool
|
||
|
AddSpanDep(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc, jsbytecode *pc2,
|
||
|
ptrdiff_t off)
|
||
|
{
|
||
|
uintN index;
|
||
|
JSSpanDep *sdbase, *sd;
|
||
|
size_t size;
|
||
|
|
||
|
index = cg->numSpanDeps;
|
||
|
if (index + 1 == 0) {
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if ((index & (index - 1)) == 0 &&
|
||
|
(!(sdbase = cg->spanDeps) || index >= SPANDEPS_MIN)) {
|
||
|
if (!sdbase) {
|
||
|
size = SPANDEPS_SIZE_MIN;
|
||
|
JS_ARENA_ALLOCATE_CAST(sdbase, JSSpanDep *, &cx->tempPool, size);
|
||
|
} else {
|
||
|
size = SPANDEPS_SIZE(index);
|
||
|
JS_ARENA_GROW_CAST(sdbase, JSSpanDep *, &cx->tempPool, size, size);
|
||
|
}
|
||
|
if (!sdbase)
|
||
|
return JS_FALSE;
|
||
|
cg->spanDeps = sdbase;
|
||
|
}
|
||
|
|
||
|
cg->numSpanDeps = index + 1;
|
||
|
sd = cg->spanDeps + index;
|
||
|
sd->top = PTRDIFF(pc, CG_BASE(cg), jsbytecode);
|
||
|
sd->offset = sd->before = PTRDIFF(pc2, CG_BASE(cg), jsbytecode);
|
||
|
|
||
|
if (js_CodeSpec[*pc].format & JOF_BACKPATCH) {
|
||
|
/* Jump offset will be backpatched if off is a non-zero "bpdelta". */
|
||
|
if (off != 0) {
|
||
|
JS_ASSERT(off >= 1 + JUMP_OFFSET_LEN);
|
||
|
if (off > BPDELTA_MAX) {
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
SD_SET_BPDELTA(sd, off);
|
||
|
} else if (off == 0) {
|
||
|
/* Jump offset will be patched directly, without backpatch chaining. */
|
||
|
SD_SET_TARGET(sd, NULL);
|
||
|
} else {
|
||
|
/* The jump offset in off is non-zero, therefore it's already known. */
|
||
|
if (!SetSpanDepTarget(cx, cg, sd, off))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (index > SPANDEP_INDEX_MAX)
|
||
|
index = SPANDEP_INDEX_HUGE;
|
||
|
SET_SPANDEP_INDEX(pc2, index);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static jsbytecode *
|
||
|
AddSwitchSpanDeps(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc)
|
||
|
{
|
||
|
JSOp op;
|
||
|
jsbytecode *pc2;
|
||
|
ptrdiff_t off;
|
||
|
jsint low, high;
|
||
|
uintN njumps, indexlen;
|
||
|
|
||
|
op = (JSOp) *pc;
|
||
|
JS_ASSERT(op == JSOP_TABLESWITCH || op == JSOP_LOOKUPSWITCH);
|
||
|
pc2 = pc;
|
||
|
off = GET_JUMP_OFFSET(pc2);
|
||
|
if (!AddSpanDep(cx, cg, pc, pc2, off))
|
||
|
return NULL;
|
||
|
pc2 += JUMP_OFFSET_LEN;
|
||
|
if (op == JSOP_TABLESWITCH) {
|
||
|
low = GET_JUMP_OFFSET(pc2);
|
||
|
pc2 += JUMP_OFFSET_LEN;
|
||
|
high = GET_JUMP_OFFSET(pc2);
|
||
|
pc2 += JUMP_OFFSET_LEN;
|
||
|
njumps = (uintN) (high - low + 1);
|
||
|
indexlen = 0;
|
||
|
} else {
|
||
|
njumps = GET_UINT16(pc2);
|
||
|
pc2 += JUMP_OFFSET_LEN;
|
||
|
indexlen = ATOM_INDEX_LEN;
|
||
|
}
|
||
|
while (njumps) {
|
||
|
--njumps;
|
||
|
pc2 += indexlen;
|
||
|
off = GET_JUMP_OFFSET(pc2);
|
||
|
if (!AddSpanDep(cx, cg, pc, pc2, off))
|
||
|
return NULL;
|
||
|
pc2 += JUMP_OFFSET_LEN;
|
||
|
}
|
||
|
return 1 + pc2;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
BuildSpanDepTable(JSContext *cx, JSCodeGenerator *cg)
|
||
|
{
|
||
|
jsbytecode *pc, *end;
|
||
|
JSOp op;
|
||
|
const JSCodeSpec *cs;
|
||
|
ptrdiff_t off;
|
||
|
|
||
|
pc = CG_BASE(cg) + cg->spanDepTodo;
|
||
|
end = CG_NEXT(cg);
|
||
|
while (pc != end) {
|
||
|
JS_ASSERT(pc < end);
|
||
|
op = (JSOp)*pc;
|
||
|
cs = &js_CodeSpec[op];
|
||
|
|
||
|
switch (cs->format & JOF_TYPEMASK) {
|
||
|
case JOF_TABLESWITCH:
|
||
|
case JOF_LOOKUPSWITCH:
|
||
|
pc = AddSwitchSpanDeps(cx, cg, pc);
|
||
|
if (!pc)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case JOF_JUMP:
|
||
|
off = GET_JUMP_OFFSET(pc);
|
||
|
if (!AddSpanDep(cx, cg, pc, pc, off))
|
||
|
return JS_FALSE;
|
||
|
/* FALL THROUGH */
|
||
|
default:
|
||
|
pc += cs->length;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static JSSpanDep *
|
||
|
GetSpanDep(JSCodeGenerator *cg, jsbytecode *pc)
|
||
|
{
|
||
|
uintN index;
|
||
|
ptrdiff_t offset;
|
||
|
int lo, hi, mid;
|
||
|
JSSpanDep *sd;
|
||
|
|
||
|
index = GET_SPANDEP_INDEX(pc);
|
||
|
if (index != SPANDEP_INDEX_HUGE)
|
||
|
return cg->spanDeps + index;
|
||
|
|
||
|
offset = PTRDIFF(pc, CG_BASE(cg), jsbytecode);
|
||
|
lo = 0;
|
||
|
hi = cg->numSpanDeps - 1;
|
||
|
while (lo <= hi) {
|
||
|
mid = (lo + hi) / 2;
|
||
|
sd = cg->spanDeps + mid;
|
||
|
if (sd->before == offset)
|
||
|
return sd;
|
||
|
if (sd->before < offset)
|
||
|
lo = mid + 1;
|
||
|
else
|
||
|
hi = mid - 1;
|
||
|
}
|
||
|
|
||
|
JS_ASSERT(0);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
SetBackPatchDelta(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc,
|
||
|
ptrdiff_t delta)
|
||
|
{
|
||
|
JSSpanDep *sd;
|
||
|
|
||
|
JS_ASSERT(delta >= 1 + JUMP_OFFSET_LEN);
|
||
|
if (!cg->spanDeps && delta < JUMP_OFFSET_MAX) {
|
||
|
SET_JUMP_OFFSET(pc, delta);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
if (delta > BPDELTA_MAX) {
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (!cg->spanDeps && !BuildSpanDepTable(cx, cg))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
sd = GetSpanDep(cg, pc);
|
||
|
JS_ASSERT(SD_GET_BPDELTA(sd) == 0);
|
||
|
SD_SET_BPDELTA(sd, delta);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
UpdateJumpTargets(JSJumpTarget *jt, ptrdiff_t pivot, ptrdiff_t delta)
|
||
|
{
|
||
|
if (jt->offset > pivot) {
|
||
|
jt->offset += delta;
|
||
|
if (jt->kids[JT_LEFT])
|
||
|
UpdateJumpTargets(jt->kids[JT_LEFT], pivot, delta);
|
||
|
}
|
||
|
if (jt->kids[JT_RIGHT])
|
||
|
UpdateJumpTargets(jt->kids[JT_RIGHT], pivot, delta);
|
||
|
}
|
||
|
|
||
|
static JSSpanDep *
|
||
|
FindNearestSpanDep(JSCodeGenerator *cg, ptrdiff_t offset, int lo,
|
||
|
JSSpanDep *guard)
|
||
|
{
|
||
|
int num, hi, mid;
|
||
|
JSSpanDep *sdbase, *sd;
|
||
|
|
||
|
num = cg->numSpanDeps;
|
||
|
JS_ASSERT(num > 0);
|
||
|
hi = num - 1;
|
||
|
sdbase = cg->spanDeps;
|
||
|
while (lo <= hi) {
|
||
|
mid = (lo + hi) / 2;
|
||
|
sd = sdbase + mid;
|
||
|
if (sd->before == offset)
|
||
|
return sd;
|
||
|
if (sd->before < offset)
|
||
|
lo = mid + 1;
|
||
|
else
|
||
|
hi = mid - 1;
|
||
|
}
|
||
|
if (lo == num)
|
||
|
return guard;
|
||
|
sd = sdbase + lo;
|
||
|
JS_ASSERT(sd->before >= offset && (lo == 0 || sd[-1].before < offset));
|
||
|
return sd;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
FreeJumpTargets(JSCodeGenerator *cg, JSJumpTarget *jt)
|
||
|
{
|
||
|
if (jt->kids[JT_LEFT])
|
||
|
FreeJumpTargets(cg, jt->kids[JT_LEFT]);
|
||
|
if (jt->kids[JT_RIGHT])
|
||
|
FreeJumpTargets(cg, jt->kids[JT_RIGHT]);
|
||
|
jt->kids[JT_LEFT] = cg->jtFreeList;
|
||
|
cg->jtFreeList = jt;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
OptimizeSpanDeps(JSContext *cx, JSCodeGenerator *cg)
|
||
|
{
|
||
|
jsbytecode *pc, *oldpc, *base, *limit, *next;
|
||
|
JSSpanDep *sd, *sd2, *sdbase, *sdlimit, *sdtop, guard;
|
||
|
ptrdiff_t offset, growth, delta, top, pivot, span, length, target;
|
||
|
JSBool done;
|
||
|
JSOp op;
|
||
|
uint32 type;
|
||
|
size_t size, incr;
|
||
|
jssrcnote *sn, *snlimit;
|
||
|
JSSrcNoteSpec *spec;
|
||
|
uintN i, n, noteIndex;
|
||
|
JSTryNote *tn, *tnlimit;
|
||
|
#ifdef DEBUG_brendan
|
||
|
int passes = 0;
|
||
|
#endif
|
||
|
|
||
|
base = CG_BASE(cg);
|
||
|
sdbase = cg->spanDeps;
|
||
|
sdlimit = sdbase + cg->numSpanDeps;
|
||
|
offset = CG_OFFSET(cg);
|
||
|
growth = 0;
|
||
|
|
||
|
do {
|
||
|
done = JS_TRUE;
|
||
|
delta = 0;
|
||
|
top = pivot = -1;
|
||
|
sdtop = NULL;
|
||
|
pc = NULL;
|
||
|
op = JSOP_NOP;
|
||
|
type = 0;
|
||
|
#ifdef DEBUG_brendan
|
||
|
passes++;
|
||
|
#endif
|
||
|
|
||
|
for (sd = sdbase; sd < sdlimit; sd++) {
|
||
|
JS_ASSERT(JT_HAS_TAG(sd->target));
|
||
|
sd->offset += delta;
|
||
|
|
||
|
if (sd->top != top) {
|
||
|
sdtop = sd;
|
||
|
top = sd->top;
|
||
|
JS_ASSERT(top == sd->before);
|
||
|
pivot = sd->offset;
|
||
|
pc = base + top;
|
||
|
op = (JSOp) *pc;
|
||
|
type = (js_CodeSpec[op].format & JOF_TYPEMASK);
|
||
|
if (JOF_TYPE_IS_EXTENDED_JUMP(type)) {
|
||
|
/*
|
||
|
* We already extended all the jump offset operands for
|
||
|
* the opcode at sd->top. Jumps and branches have only
|
||
|
* one jump offset operand, but switches have many, all
|
||
|
* of which are adjacent in cg->spanDeps.
|
||
|
*/
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
JS_ASSERT(type == JOF_JUMP ||
|
||
|
type == JOF_TABLESWITCH ||
|
||
|
type == JOF_LOOKUPSWITCH);
|
||
|
}
|
||
|
|
||
|
if (!JOF_TYPE_IS_EXTENDED_JUMP(type)) {
|
||
|
span = SD_SPAN(sd, pivot);
|
||
|
if (span < JUMP_OFFSET_MIN || JUMP_OFFSET_MAX < span) {
|
||
|
ptrdiff_t deltaFromTop = 0;
|
||
|
|
||
|
done = JS_FALSE;
|
||
|
|
||
|
switch (op) {
|
||
|
case JSOP_GOTO: op = JSOP_GOTOX; break;
|
||
|
case JSOP_IFEQ: op = JSOP_IFEQX; break;
|
||
|
case JSOP_IFNE: op = JSOP_IFNEX; break;
|
||
|
case JSOP_OR: op = JSOP_ORX; break;
|
||
|
case JSOP_AND: op = JSOP_ANDX; break;
|
||
|
case JSOP_GOSUB: op = JSOP_GOSUBX; break;
|
||
|
case JSOP_CASE: op = JSOP_CASEX; break;
|
||
|
case JSOP_DEFAULT: op = JSOP_DEFAULTX; break;
|
||
|
case JSOP_TABLESWITCH: op = JSOP_TABLESWITCHX; break;
|
||
|
case JSOP_LOOKUPSWITCH: op = JSOP_LOOKUPSWITCHX; break;
|
||
|
default:
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
*pc = (jsbytecode) op;
|
||
|
|
||
|
for (sd2 = sdtop; sd2 < sdlimit && sd2->top == top; sd2++) {
|
||
|
if (sd2 <= sd) {
|
||
|
/*
|
||
|
* sd2->offset already includes delta as it stood
|
||
|
* before we entered this loop, but it must also
|
||
|
* include the delta relative to top due to all the
|
||
|
* extended jump offset immediates for the opcode
|
||
|
* starting at top, which we extend in this loop.
|
||
|
*
|
||
|
* If there is only one extended jump offset, then
|
||
|
* sd2->offset won't change and this for loop will
|
||
|
* iterate once only.
|
||
|
*/
|
||
|
sd2->offset += deltaFromTop;
|
||
|
deltaFromTop += JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN;
|
||
|
} else {
|
||
|
/*
|
||
|
* sd2 comes after sd, and won't be revisited by
|
||
|
* the outer for loop, so we have to increase its
|
||
|
* offset by delta, not merely by deltaFromTop.
|
||
|
*/
|
||
|
sd2->offset += delta;
|
||
|
}
|
||
|
|
||
|
delta += JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN;
|
||
|
UpdateJumpTargets(cg->jumpTargets, sd2->offset,
|
||
|
JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN);
|
||
|
}
|
||
|
sd = sd2 - 1;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
growth += delta;
|
||
|
} while (!done);
|
||
|
|
||
|
if (growth) {
|
||
|
#ifdef DEBUG_brendan
|
||
|
printf("%s:%u: %u/%u jumps extended in %d passes (%d=%d+%d)\n",
|
||
|
cg->filename ? cg->filename : "stdin", cg->firstLine,
|
||
|
growth / (JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN), cg->numSpanDeps,
|
||
|
passes, offset + growth, offset, growth);
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Ensure that we have room for the extended jumps, but don't round up
|
||
|
* to a power of two -- we're done generating code, so we cut to fit.
|
||
|
*/
|
||
|
limit = CG_LIMIT(cg);
|
||
|
length = offset + growth;
|
||
|
next = base + length;
|
||
|
if (next > limit) {
|
||
|
JS_ASSERT(length > BYTECODE_CHUNK);
|
||
|
size = BYTECODE_SIZE(PTRDIFF(limit, base, jsbytecode));
|
||
|
incr = BYTECODE_SIZE(length) - size;
|
||
|
JS_ARENA_GROW_CAST(base, jsbytecode *, cg->codePool, size, incr);
|
||
|
if (!base) {
|
||
|
JS_ReportOutOfMemory(cx);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
CG_BASE(cg) = base;
|
||
|
CG_LIMIT(cg) = next = base + length;
|
||
|
}
|
||
|
CG_NEXT(cg) = next;
|
||
|
|
||
|
/*
|
||
|
* Set up a fake span dependency record to guard the end of the code
|
||
|
* being generated. This guard record is returned as a fencepost by
|
||
|
* FindNearestSpanDep if there is no real spandep at or above a given
|
||
|
* unextended code offset.
|
||
|
*/
|
||
|
guard.top = -1;
|
||
|
guard.offset = offset + growth;
|
||
|
guard.before = offset;
|
||
|
guard.target = NULL;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Now work backwards through the span dependencies, copying chunks of
|
||
|
* bytecode between each extended jump toward the end of the grown code
|
||
|
* space, and restoring immediate offset operands for all jump bytecodes.
|
||
|
* The first chunk of bytecodes, starting at base and ending at the first
|
||
|
* extended jump offset (NB: this chunk includes the operation bytecode
|
||
|
* just before that immediate jump offset), doesn't need to be copied.
|
||
|
*/
|
||
|
JS_ASSERT(sd == sdlimit);
|
||
|
top = -1;
|
||
|
while (--sd >= sdbase) {
|
||
|
if (sd->top != top) {
|
||
|
top = sd->top;
|
||
|
op = (JSOp) base[top];
|
||
|
type = (js_CodeSpec[op].format & JOF_TYPEMASK);
|
||
|
|
||
|
for (sd2 = sd - 1; sd2 >= sdbase && sd2->top == top; sd2--)
|
||
|
continue;
|
||
|
sd2++;
|
||
|
pivot = sd2->offset;
|
||
|
JS_ASSERT(top == sd2->before);
|
||
|
}
|
||
|
|
||
|
oldpc = base + sd->before;
|
||
|
span = SD_SPAN(sd, pivot);
|
||
|
|
||
|
/*
|
||
|
* If this jump didn't need to be extended, restore its span immediate
|
||
|
* offset operand now, overwriting the index of sd within cg->spanDeps
|
||
|
* that was stored temporarily after *pc when BuildSpanDepTable ran.
|
||
|
*
|
||
|
* Note that span might fit in 16 bits even for an extended jump op,
|
||
|
* if the op has multiple span operands, not all of which overflowed
|
||
|
* (e.g. JSOP_LOOKUPSWITCH or JSOP_TABLESWITCH where some cases are in
|
||
|
* range for a short jump, but others are not).
|
||
|
*/
|
||
|
if (!JOF_TYPE_IS_EXTENDED_JUMP(type)) {
|
||
|
JS_ASSERT(JUMP_OFFSET_MIN <= span && span <= JUMP_OFFSET_MAX);
|
||
|
SET_JUMP_OFFSET(oldpc, span);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Set up parameters needed to copy the next run of bytecode starting
|
||
|
* at offset (which is a cursor into the unextended, original bytecode
|
||
|
* vector), down to sd->before (a cursor of the same scale as offset,
|
||
|
* it's the index of the original jump pc). Reuse delta to count the
|
||
|
* nominal number of bytes to copy.
|
||
|
*/
|
||
|
pc = base + sd->offset;
|
||
|
delta = offset - sd->before;
|
||
|
JS_ASSERT(delta >= 1 + JUMP_OFFSET_LEN);
|
||
|
|
||
|
/*
|
||
|
* Don't bother copying the jump offset we're about to reset, but do
|
||
|
* copy the bytecode at oldpc (which comes just before its immediate
|
||
|
* jump offset operand), on the next iteration through the loop, by
|
||
|
* including it in offset's new value.
|
||
|
*/
|
||
|
offset = sd->before + 1;
|
||
|
size = BYTECODE_SIZE(delta - (1 + JUMP_OFFSET_LEN));
|
||
|
if (size) {
|
||
|
memmove(pc + 1 + JUMPX_OFFSET_LEN,
|
||
|
oldpc + 1 + JUMP_OFFSET_LEN,
|
||
|
size);
|
||
|
}
|
||
|
|
||
|
SET_JUMPX_OFFSET(pc, span);
|
||
|
}
|
||
|
|
||
|
if (growth) {
|
||
|
/*
|
||
|
* Fix source note deltas. Don't hardwire the delta fixup adjustment,
|
||
|
* even though currently it must be JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN
|
||
|
* at each sd that moved. The future may bring different offset sizes
|
||
|
* for span-dependent instruction operands. However, we fix only main
|
||
|
* notes here, not prolog notes -- we know that prolog opcodes are not
|
||
|
* span-dependent, and aren't likely ever to be.
|
||
|
*/
|
||
|
offset = growth = 0;
|
||
|
sd = sdbase;
|
||
|
for (sn = cg->main.notes, snlimit = sn + cg->main.noteCount;
|
||
|
sn < snlimit;
|
||
|
sn = SN_NEXT(sn)) {
|
||
|
/*
|
||
|
* Recall that the offset of a given note includes its delta, and
|
||
|
* tells the offset of the annotated bytecode from the main entry
|
||
|
* point of the script.
|
||
|
*/
|
||
|
offset += SN_DELTA(sn);
|
||
|
while (sd < sdlimit && sd->before < offset) {
|
||
|
/*
|
||
|
* To compute the delta to add to sn, we need to look at the
|
||
|
* spandep after sd, whose offset - (before + growth) tells by
|
||
|
* how many bytes sd's instruction grew.
|
||
|
*/
|
||
|
sd2 = sd + 1;
|
||
|
if (sd2 == sdlimit)
|
||
|
sd2 = &guard;
|
||
|
delta = sd2->offset - (sd2->before + growth);
|
||
|
if (delta > 0) {
|
||
|
JS_ASSERT(delta == JUMPX_OFFSET_LEN - JUMP_OFFSET_LEN);
|
||
|
sn = js_AddToSrcNoteDelta(cx, cg, sn, delta);
|
||
|
if (!sn)
|
||
|
return JS_FALSE;
|
||
|
snlimit = cg->main.notes + cg->main.noteCount;
|
||
|
growth += delta;
|
||
|
}
|
||
|
sd++;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If sn has span-dependent offset operands, check whether each
|
||
|
* covers further span-dependencies, and increase those operands
|
||
|
* accordingly. Some source notes measure offset not from the
|
||
|
* annotated pc, but from that pc plus some small bias. NB: we
|
||
|
* assume that spec->offsetBias can't itself span span-dependent
|
||
|
* instructions!
|
||
|
*/
|
||
|
spec = &js_SrcNoteSpec[SN_TYPE(sn)];
|
||
|
if (spec->isSpanDep) {
|
||
|
pivot = offset + spec->offsetBias;
|
||
|
n = spec->arity;
|
||
|
for (i = 0; i < n; i++) {
|
||
|
span = js_GetSrcNoteOffset(sn, i);
|
||
|
if (span == 0)
|
||
|
continue;
|
||
|
target = pivot + span * spec->isSpanDep;
|
||
|
sd2 = FindNearestSpanDep(cg, target,
|
||
|
(target >= pivot)
|
||
|
? sd - sdbase
|
||
|
: 0,
|
||
|
&guard);
|
||
|
|
||
|
/*
|
||
|
* Increase target by sd2's before-vs-after offset delta,
|
||
|
* which is absolute (i.e., relative to start of script,
|
||
|
* as is target). Recompute the span by subtracting its
|
||
|
* adjusted pivot from target.
|
||
|
*/
|
||
|
target += sd2->offset - sd2->before;
|
||
|
span = target - (pivot + growth);
|
||
|
span *= spec->isSpanDep;
|
||
|
noteIndex = sn - cg->main.notes;
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, noteIndex, i, span))
|
||
|
return JS_FALSE;
|
||
|
sn = cg->main.notes + noteIndex;
|
||
|
snlimit = cg->main.notes + cg->main.noteCount;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
cg->main.lastNoteOffset += growth;
|
||
|
|
||
|
/*
|
||
|
* Fix try/catch notes (O(numTryNotes * log2(numSpanDeps)), but it's
|
||
|
* not clear how we can beat that).
|
||
|
*/
|
||
|
for (tn = cg->tryBase, tnlimit = cg->tryNext; tn < tnlimit; tn++) {
|
||
|
/*
|
||
|
* First, look for the nearest span dependency at/above tn->start.
|
||
|
* There may not be any such spandep, in which case the guard will
|
||
|
* be returned.
|
||
|
*/
|
||
|
offset = tn->start;
|
||
|
sd = FindNearestSpanDep(cg, offset, 0, &guard);
|
||
|
delta = sd->offset - sd->before;
|
||
|
tn->start = offset + delta;
|
||
|
|
||
|
/*
|
||
|
* Next, find the nearest spandep at/above tn->start + tn->length.
|
||
|
* Use its delta minus tn->start's delta to increase tn->length.
|
||
|
*/
|
||
|
length = tn->length;
|
||
|
sd2 = FindNearestSpanDep(cg, offset + length, sd - sdbase, &guard);
|
||
|
if (sd2 != sd)
|
||
|
tn->length = length + sd2->offset - sd2->before - delta;
|
||
|
|
||
|
/*
|
||
|
* Finally, adjust tn->catchStart upward only if it is non-zero,
|
||
|
* and provided there are spandeps below it that grew.
|
||
|
*/
|
||
|
offset = tn->catchStart;
|
||
|
if (offset != 0) {
|
||
|
sd = FindNearestSpanDep(cg, offset, sd2 - sdbase, &guard);
|
||
|
tn->catchStart = offset + sd->offset - sd->before;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_brendan
|
||
|
{
|
||
|
uintN bigspans = 0;
|
||
|
top = -1;
|
||
|
for (sd = sdbase; sd < sdlimit; sd++) {
|
||
|
offset = sd->offset;
|
||
|
|
||
|
/* NB: sd->top cursors into the original, unextended bytecode vector. */
|
||
|
if (sd->top != top) {
|
||
|
JS_ASSERT(top == -1 ||
|
||
|
!JOF_TYPE_IS_EXTENDED_JUMP(type) ||
|
||
|
bigspans != 0);
|
||
|
bigspans = 0;
|
||
|
top = sd->top;
|
||
|
JS_ASSERT(top == sd->before);
|
||
|
op = (JSOp) base[offset];
|
||
|
type = (js_CodeSpec[op].format & JOF_TYPEMASK);
|
||
|
JS_ASSERT(type == JOF_JUMP ||
|
||
|
type == JOF_JUMPX ||
|
||
|
type == JOF_TABLESWITCH ||
|
||
|
type == JOF_TABLESWITCHX ||
|
||
|
type == JOF_LOOKUPSWITCH ||
|
||
|
type == JOF_LOOKUPSWITCHX);
|
||
|
pivot = offset;
|
||
|
}
|
||
|
|
||
|
pc = base + offset;
|
||
|
if (JOF_TYPE_IS_EXTENDED_JUMP(type)) {
|
||
|
span = GET_JUMPX_OFFSET(pc);
|
||
|
if (span < JUMP_OFFSET_MIN || JUMP_OFFSET_MAX < span) {
|
||
|
bigspans++;
|
||
|
} else {
|
||
|
JS_ASSERT(type == JOF_TABLESWITCHX ||
|
||
|
type == JOF_LOOKUPSWITCHX);
|
||
|
}
|
||
|
} else {
|
||
|
span = GET_JUMP_OFFSET(pc);
|
||
|
}
|
||
|
JS_ASSERT(SD_SPAN(sd, pivot) == span);
|
||
|
}
|
||
|
JS_ASSERT(!JOF_TYPE_IS_EXTENDED_JUMP(type) || bigspans != 0);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Reset so we optimize at most once -- cg may be used for further code
|
||
|
* generation of successive, independent, top-level statements. No jump
|
||
|
* can span top-level statements, because JS lacks goto.
|
||
|
*/
|
||
|
size = SPANDEPS_SIZE(JS_BIT(JS_CeilingLog2(cg->numSpanDeps)));
|
||
|
JS_ArenaFreeAllocation(&cx->tempPool, cg->spanDeps,
|
||
|
JS_MAX(size, SPANDEPS_SIZE_MIN));
|
||
|
cg->spanDeps = NULL;
|
||
|
FreeJumpTargets(cg, cg->jumpTargets);
|
||
|
cg->jumpTargets = NULL;
|
||
|
cg->numSpanDeps = cg->numJumpTargets = 0;
|
||
|
cg->spanDepTodo = CG_OFFSET(cg);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitJump(JSContext *cx, JSCodeGenerator *cg, JSOp op, ptrdiff_t off)
|
||
|
{
|
||
|
JSBool extend;
|
||
|
ptrdiff_t jmp;
|
||
|
jsbytecode *pc;
|
||
|
|
||
|
extend = off < JUMP_OFFSET_MIN || JUMP_OFFSET_MAX < off;
|
||
|
if (extend && !cg->spanDeps && !BuildSpanDepTable(cx, cg))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
jmp = js_Emit3(cx, cg, op, JUMP_OFFSET_HI(off), JUMP_OFFSET_LO(off));
|
||
|
if (jmp >= 0 && (extend || cg->spanDeps)) {
|
||
|
pc = CG_CODE(cg, jmp);
|
||
|
if (!AddSpanDep(cx, cg, pc, pc, off))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
return jmp;
|
||
|
}
|
||
|
|
||
|
static ptrdiff_t
|
||
|
GetJumpOffset(JSCodeGenerator *cg, jsbytecode *pc)
|
||
|
{
|
||
|
JSSpanDep *sd;
|
||
|
JSJumpTarget *jt;
|
||
|
ptrdiff_t top;
|
||
|
|
||
|
if (!cg->spanDeps)
|
||
|
return GET_JUMP_OFFSET(pc);
|
||
|
|
||
|
sd = GetSpanDep(cg, pc);
|
||
|
jt = sd->target;
|
||
|
if (!JT_HAS_TAG(jt))
|
||
|
return JT_TO_BPDELTA(jt);
|
||
|
|
||
|
top = sd->top;
|
||
|
while (--sd >= cg->spanDeps && sd->top == top)
|
||
|
continue;
|
||
|
sd++;
|
||
|
return JT_CLR_TAG(jt)->offset - sd->offset;
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_SetJumpOffset(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc,
|
||
|
ptrdiff_t off)
|
||
|
{
|
||
|
if (!cg->spanDeps) {
|
||
|
if (JUMP_OFFSET_MIN <= off && off <= JUMP_OFFSET_MAX) {
|
||
|
SET_JUMP_OFFSET(pc, off);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
if (!BuildSpanDepTable(cx, cg))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
return SetSpanDepTarget(cx, cg, GetSpanDep(cg, pc), off);
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_InStatement(JSTreeContext *tc, JSStmtType type)
|
||
|
{
|
||
|
JSStmtInfo *stmt;
|
||
|
|
||
|
for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
|
||
|
if (stmt->type == type)
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_IsGlobalReference(JSTreeContext *tc, JSAtom *atom, JSBool *loopyp)
|
||
|
{
|
||
|
JSStmtInfo *stmt;
|
||
|
JSObject *obj;
|
||
|
JSScope *scope;
|
||
|
|
||
|
*loopyp = JS_FALSE;
|
||
|
for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
|
||
|
if (stmt->type == STMT_WITH)
|
||
|
return JS_FALSE;
|
||
|
if (STMT_IS_LOOP(stmt)) {
|
||
|
*loopyp = JS_TRUE;
|
||
|
continue;
|
||
|
}
|
||
|
if (stmt->flags & SIF_SCOPE) {
|
||
|
obj = ATOM_TO_OBJECT(stmt->atom);
|
||
|
JS_ASSERT(LOCKED_OBJ_GET_CLASS(obj) == &js_BlockClass);
|
||
|
scope = OBJ_SCOPE(obj);
|
||
|
if (SCOPE_GET_PROPERTY(scope, ATOM_TO_JSID(atom)))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
js_PushStatement(JSTreeContext *tc, JSStmtInfo *stmt, JSStmtType type,
|
||
|
ptrdiff_t top)
|
||
|
{
|
||
|
stmt->type = type;
|
||
|
stmt->flags = 0;
|
||
|
SET_STATEMENT_TOP(stmt, top);
|
||
|
stmt->atom = NULL;
|
||
|
stmt->down = tc->topStmt;
|
||
|
tc->topStmt = stmt;
|
||
|
if (STMT_LINKS_SCOPE(stmt)) {
|
||
|
stmt->downScope = tc->topScopeStmt;
|
||
|
tc->topScopeStmt = stmt;
|
||
|
} else {
|
||
|
stmt->downScope = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
js_PushBlockScope(JSTreeContext *tc, JSStmtInfo *stmt, JSAtom *blockAtom,
|
||
|
ptrdiff_t top)
|
||
|
{
|
||
|
JSObject *blockObj;
|
||
|
|
||
|
js_PushStatement(tc, stmt, STMT_BLOCK, top);
|
||
|
stmt->flags |= SIF_SCOPE;
|
||
|
blockObj = ATOM_TO_OBJECT(blockAtom);
|
||
|
blockObj->slots[JSSLOT_PARENT] = OBJECT_TO_JSVAL(tc->blockChain);
|
||
|
stmt->downScope = tc->topScopeStmt;
|
||
|
tc->topScopeStmt = stmt;
|
||
|
tc->blockChain = blockObj;
|
||
|
stmt->atom = blockAtom;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Emit a backpatch op with offset pointing to the previous jump of this type,
|
||
|
* so that we can walk back up the chain fixing up the op and jump offset.
|
||
|
*/
|
||
|
static ptrdiff_t
|
||
|
EmitBackPatchOp(JSContext *cx, JSCodeGenerator *cg, JSOp op, ptrdiff_t *lastp)
|
||
|
{
|
||
|
ptrdiff_t offset, delta;
|
||
|
|
||
|
offset = CG_OFFSET(cg);
|
||
|
delta = offset - *lastp;
|
||
|
*lastp = offset;
|
||
|
JS_ASSERT(delta > 0);
|
||
|
return EmitJump(cx, cg, op, delta);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Macro to emit a bytecode followed by a uint16 immediate operand stored in
|
||
|
* big-endian order, used for arg and var numbers as well as for atomIndexes.
|
||
|
* NB: We use cx and cg from our caller's lexical environment, and return
|
||
|
* false on error.
|
||
|
*/
|
||
|
#define EMIT_UINT16_IMM_OP(op, i) \
|
||
|
JS_BEGIN_MACRO \
|
||
|
if (js_Emit3(cx, cg, op, UINT16_HI(i), UINT16_LO(i)) < 0) \
|
||
|
return JS_FALSE; \
|
||
|
JS_END_MACRO
|
||
|
|
||
|
/* Emit additional bytecode(s) for non-local jumps. */
|
||
|
static JSBool
|
||
|
EmitNonLocalJumpFixup(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
|
||
|
JSOp *returnop)
|
||
|
{
|
||
|
intN depth;
|
||
|
JSStmtInfo *stmt;
|
||
|
ptrdiff_t jmp;
|
||
|
|
||
|
/*
|
||
|
* Return from within a try block that has a finally clause must be split
|
||
|
* into two ops: JSOP_SETRVAL, to pop the r.v. and store it in fp->rval;
|
||
|
* and JSOP_RETRVAL, which makes control flow go back to the caller, who
|
||
|
* picks up fp->rval as usual. Otherwise, the stack will be unbalanced
|
||
|
* when executing the finally clause.
|
||
|
*
|
||
|
* We mutate *returnop once only if we find an enclosing try-block (viz,
|
||
|
* STMT_FINALLY) to ensure that we emit just one JSOP_SETRVAL before one
|
||
|
* or more JSOP_GOSUBs and other fixup opcodes emitted by this function.
|
||
|
* Our caller (the TOK_RETURN case of js_EmitTree) then emits *returnop.
|
||
|
* The fixup opcodes and gosubs must interleave in the proper order, from
|
||
|
* inner statement to outer, so that finally clauses run at the correct
|
||
|
* stack depth.
|
||
|
*/
|
||
|
if (returnop) {
|
||
|
JS_ASSERT(*returnop == JSOP_RETURN);
|
||
|
for (stmt = cg->treeContext.topStmt; stmt != toStmt;
|
||
|
stmt = stmt->down) {
|
||
|
if (stmt->type == STMT_FINALLY ||
|
||
|
((cg->treeContext.flags & TCF_FUN_HEAVYWEIGHT) &&
|
||
|
STMT_MAYBE_SCOPE(stmt))) {
|
||
|
if (js_Emit1(cx, cg, JSOP_SETRVAL) < 0)
|
||
|
return JS_FALSE;
|
||
|
*returnop = JSOP_RETRVAL;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If there are no try-with-finally blocks open around this return
|
||
|
* statement, we can generate a return forthwith and skip generating
|
||
|
* any fixup code.
|
||
|
*/
|
||
|
if (*returnop == JSOP_RETURN)
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* The non-local jump fixup we emit will unbalance cg->stackDepth, because
|
||
|
* the fixup replicates balanced code such as JSOP_LEAVEWITH emitted at the
|
||
|
* end of a with statement, so we save cg->stackDepth here and restore it
|
||
|
* just before a successful return.
|
||
|
*/
|
||
|
depth = cg->stackDepth;
|
||
|
for (stmt = cg->treeContext.topStmt; stmt != toStmt; stmt = stmt->down) {
|
||
|
switch (stmt->type) {
|
||
|
case STMT_FINALLY:
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &GOSUBS(*stmt));
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case STMT_WITH:
|
||
|
/* There's a With object on the stack that we need to pop. */
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case STMT_FOR_IN_LOOP:
|
||
|
/*
|
||
|
* The iterator and the object being iterated need to be popped.
|
||
|
*/
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_ENDITER) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case STMT_SUBROUTINE:
|
||
|
/*
|
||
|
* There's a [exception or hole, retsub pc-index] pair on the
|
||
|
* stack that we need to pop.
|
||
|
*/
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_POP2) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
default:;
|
||
|
}
|
||
|
|
||
|
if (stmt->flags & SIF_SCOPE) {
|
||
|
uintN i;
|
||
|
|
||
|
/* There is a Block object with locals on the stack to pop. */
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
i = OBJ_BLOCK_COUNT(cx, ATOM_TO_OBJECT(stmt->atom));
|
||
|
EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, i);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
cg->stackDepth = depth;
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static ptrdiff_t
|
||
|
EmitGoto(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
|
||
|
ptrdiff_t *lastp, JSAtomListElement *label, JSSrcNoteType noteType)
|
||
|
{
|
||
|
intN index;
|
||
|
|
||
|
if (!EmitNonLocalJumpFixup(cx, cg, toStmt, NULL))
|
||
|
return -1;
|
||
|
|
||
|
if (label)
|
||
|
index = js_NewSrcNote2(cx, cg, noteType, (ptrdiff_t) ALE_INDEX(label));
|
||
|
else if (noteType != SRC_NULL)
|
||
|
index = js_NewSrcNote(cx, cg, noteType);
|
||
|
else
|
||
|
index = 0;
|
||
|
if (index < 0)
|
||
|
return -1;
|
||
|
|
||
|
return EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, lastp);
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
BackPatch(JSContext *cx, JSCodeGenerator *cg, ptrdiff_t last,
|
||
|
jsbytecode *target, jsbytecode op)
|
||
|
{
|
||
|
jsbytecode *pc, *stop;
|
||
|
ptrdiff_t delta, span;
|
||
|
|
||
|
pc = CG_CODE(cg, last);
|
||
|
stop = CG_CODE(cg, -1);
|
||
|
while (pc != stop) {
|
||
|
delta = GetJumpOffset(cg, pc);
|
||
|
span = PTRDIFF(target, pc, jsbytecode);
|
||
|
CHECK_AND_SET_JUMP_OFFSET(cx, cg, pc, span);
|
||
|
|
||
|
/*
|
||
|
* Set *pc after jump offset in case bpdelta didn't overflow, but span
|
||
|
* does (if so, CHECK_AND_SET_JUMP_OFFSET might call BuildSpanDepTable
|
||
|
* and need to see the JSOP_BACKPATCH* op at *pc).
|
||
|
*/
|
||
|
*pc = op;
|
||
|
pc -= delta;
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
js_PopStatement(JSTreeContext *tc)
|
||
|
{
|
||
|
JSStmtInfo *stmt;
|
||
|
JSObject *blockObj;
|
||
|
|
||
|
stmt = tc->topStmt;
|
||
|
tc->topStmt = stmt->down;
|
||
|
if (STMT_LINKS_SCOPE(stmt)) {
|
||
|
tc->topScopeStmt = stmt->downScope;
|
||
|
if (stmt->flags & SIF_SCOPE) {
|
||
|
blockObj = ATOM_TO_OBJECT(stmt->atom);
|
||
|
tc->blockChain = JSVAL_TO_OBJECT(blockObj->slots[JSSLOT_PARENT]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_PopStatementCG(JSContext *cx, JSCodeGenerator *cg)
|
||
|
{
|
||
|
JSStmtInfo *stmt;
|
||
|
|
||
|
stmt = cg->treeContext.topStmt;
|
||
|
if (!STMT_IS_TRYING(stmt) &&
|
||
|
(!BackPatch(cx, cg, stmt->breaks, CG_NEXT(cg), JSOP_GOTO) ||
|
||
|
!BackPatch(cx, cg, stmt->continues, CG_CODE(cg, stmt->update),
|
||
|
JSOP_GOTO))) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
js_PopStatement(&cg->treeContext);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_DefineCompileTimeConstant(JSContext *cx, JSCodeGenerator *cg, JSAtom *atom,
|
||
|
JSParseNode *pn)
|
||
|
{
|
||
|
jsdouble dval;
|
||
|
jsint ival;
|
||
|
JSAtom *valueAtom;
|
||
|
JSAtomListElement *ale;
|
||
|
|
||
|
/* XXX just do numbers for now */
|
||
|
if (pn->pn_type == TOK_NUMBER) {
|
||
|
dval = pn->pn_dval;
|
||
|
valueAtom = (JSDOUBLE_IS_INT(dval, ival) && INT_FITS_IN_JSVAL(ival))
|
||
|
? js_AtomizeInt(cx, ival, 0)
|
||
|
: js_AtomizeDouble(cx, dval, 0);
|
||
|
if (!valueAtom)
|
||
|
return JS_FALSE;
|
||
|
ale = js_IndexAtom(cx, atom, &cg->constList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
ALE_SET_VALUE(ale, ATOM_KEY(valueAtom));
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
JSStmtInfo *
|
||
|
js_LexicalLookup(JSTreeContext *tc, JSAtom *atom, jsint *slotp, JSBool letdecl)
|
||
|
{
|
||
|
JSStmtInfo *stmt;
|
||
|
JSObject *obj;
|
||
|
JSScope *scope;
|
||
|
JSScopeProperty *sprop;
|
||
|
jsval v;
|
||
|
|
||
|
for (stmt = tc->topScopeStmt; stmt; stmt = stmt->downScope) {
|
||
|
if (stmt->type == STMT_WITH) {
|
||
|
/* Ignore with statements enclosing a single let declaration. */
|
||
|
if (letdecl)
|
||
|
continue;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Skip "maybe scope" statements that don't contain let bindings. */
|
||
|
if (!(stmt->flags & SIF_SCOPE))
|
||
|
continue;
|
||
|
|
||
|
obj = ATOM_TO_OBJECT(stmt->atom);
|
||
|
JS_ASSERT(LOCKED_OBJ_GET_CLASS(obj) == &js_BlockClass);
|
||
|
scope = OBJ_SCOPE(obj);
|
||
|
sprop = SCOPE_GET_PROPERTY(scope, ATOM_TO_JSID(atom));
|
||
|
if (sprop) {
|
||
|
JS_ASSERT(sprop->flags & SPROP_HAS_SHORTID);
|
||
|
|
||
|
if (slotp) {
|
||
|
/*
|
||
|
* Use LOCKED_OBJ_GET_SLOT since we know obj is single-
|
||
|
* threaded and owned by this compiler activation.
|
||
|
*/
|
||
|
v = LOCKED_OBJ_GET_SLOT(obj, JSSLOT_BLOCK_DEPTH);
|
||
|
JS_ASSERT(JSVAL_IS_INT(v) && JSVAL_TO_INT(v) >= 0);
|
||
|
*slotp = JSVAL_TO_INT(v) + sprop->shortid;
|
||
|
}
|
||
|
return stmt;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (slotp)
|
||
|
*slotp = -1;
|
||
|
return stmt;
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_LookupCompileTimeConstant(JSContext *cx, JSCodeGenerator *cg, JSAtom *atom,
|
||
|
jsval *vp)
|
||
|
{
|
||
|
JSBool ok;
|
||
|
JSStackFrame *fp;
|
||
|
JSStmtInfo *stmt;
|
||
|
jsint slot;
|
||
|
JSAtomListElement *ale;
|
||
|
JSObject *obj, *pobj;
|
||
|
JSProperty *prop;
|
||
|
uintN attrs;
|
||
|
|
||
|
/*
|
||
|
* fp chases cg down the stack, but only until we reach the outermost cg.
|
||
|
* This enables propagating consts from top-level into switch cases in a
|
||
|
* function compiled along with the top-level script. All stack frames
|
||
|
* with matching code generators should be flagged with JSFRAME_COMPILING;
|
||
|
* we check sanity here.
|
||
|
*/
|
||
|
*vp = JSVAL_VOID;
|
||
|
ok = JS_TRUE;
|
||
|
fp = cx->fp;
|
||
|
do {
|
||
|
JS_ASSERT(fp->flags & JSFRAME_COMPILING);
|
||
|
|
||
|
obj = fp->varobj;
|
||
|
if (obj == fp->scopeChain) {
|
||
|
/* XXX this will need revising when 'let const' is added. */
|
||
|
stmt = js_LexicalLookup(&cg->treeContext, atom, &slot, JS_FALSE);
|
||
|
if (stmt)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
ATOM_LIST_SEARCH(ale, &cg->constList, atom);
|
||
|
if (ale) {
|
||
|
*vp = ALE_VALUE(ale);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Try looking in the variable object for a direct property that
|
||
|
* is readonly and permanent. We know such a property can't be
|
||
|
* shadowed by another property on obj's prototype chain, or a
|
||
|
* with object or catch variable; nor can prop's value be changed,
|
||
|
* nor can prop be deleted.
|
||
|
*/
|
||
|
prop = NULL;
|
||
|
if (OBJ_GET_CLASS(cx, obj) == &js_FunctionClass) {
|
||
|
ok = js_LookupHiddenProperty(cx, obj, ATOM_TO_JSID(atom),
|
||
|
&pobj, &prop);
|
||
|
if (!ok)
|
||
|
break;
|
||
|
if (prop) {
|
||
|
#ifdef DEBUG
|
||
|
JSScopeProperty *sprop = (JSScopeProperty *)prop;
|
||
|
|
||
|
/*
|
||
|
* Any hidden property must be a formal arg or local var,
|
||
|
* which will shadow a global const of the same name.
|
||
|
*/
|
||
|
JS_ASSERT(sprop->getter == js_GetArgument ||
|
||
|
sprop->getter == js_GetLocalVariable);
|
||
|
#endif
|
||
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ok = OBJ_LOOKUP_PROPERTY(cx, obj, ATOM_TO_JSID(atom), &pobj, &prop);
|
||
|
if (ok) {
|
||
|
if (pobj == obj &&
|
||
|
(fp->flags & (JSFRAME_EVAL | JSFRAME_COMPILE_N_GO))) {
|
||
|
/*
|
||
|
* We're compiling code that will be executed immediately,
|
||
|
* not re-executed against a different scope chain and/or
|
||
|
* variable object. Therefore we can get constant values
|
||
|
* from our variable object here.
|
||
|
*/
|
||
|
ok = OBJ_GET_ATTRIBUTES(cx, obj, ATOM_TO_JSID(atom), prop,
|
||
|
&attrs);
|
||
|
if (ok && !(~attrs & (JSPROP_READONLY | JSPROP_PERMANENT)))
|
||
|
ok = OBJ_GET_PROPERTY(cx, obj, ATOM_TO_JSID(atom), vp);
|
||
|
}
|
||
|
if (prop)
|
||
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
||
|
}
|
||
|
if (!ok || prop)
|
||
|
break;
|
||
|
}
|
||
|
fp = fp->down;
|
||
|
} while ((cg = cg->parent) != NULL);
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Allocate an index invariant for all activations of the code being compiled
|
||
|
* in cg, that can be used to store and fetch a reference to a cloned RegExp
|
||
|
* object that shares the same JSRegExp private data created for the object
|
||
|
* literal in pn->pn_atom. We need clones to hold lastIndex and other direct
|
||
|
* properties that should not be shared among threads sharing a precompiled
|
||
|
* function or script.
|
||
|
*
|
||
|
* If the code being compiled is function code, allocate a reserved slot in
|
||
|
* the cloned function object that shares its precompiled script with other
|
||
|
* cloned function objects and with the compiler-created clone-parent. There
|
||
|
* are fun->nregexps such reserved slots in each function object cloned from
|
||
|
* fun->object. NB: during compilation, funobj slots must never be allocated,
|
||
|
* because js_AllocSlot could hand out one of the slots that should be given
|
||
|
* to a regexp clone.
|
||
|
*
|
||
|
* If the code being compiled is global code, reserve the fp->vars slot at
|
||
|
* ALE_INDEX(ale), by ensuring that cg->treeContext.numGlobalVars is at least
|
||
|
* one more than this index. For global code, fp->vars is parallel to the
|
||
|
* global script->atomMap.vector array, but possibly shorter for the common
|
||
|
* case (where var declarations and regexp literals cluster toward the front
|
||
|
* of the script or function body).
|
||
|
*
|
||
|
* Global variable name literals in script->atomMap have fast-global slot
|
||
|
* numbers (stored as int-tagged jsvals) in the corresponding fp->vars array
|
||
|
* element. The atomIndex for a regexp object literal thus also addresses an
|
||
|
* fp->vars element that is not used by any optimized global variable, so we
|
||
|
* use that GC-scanned element to keep the regexp object clone alive, as well
|
||
|
* as to lazily create and find it at run-time for the JSOP_REGEXP bytecode.
|
||
|
*
|
||
|
* In no case can cx->fp->varobj be a Call object here, because that implies
|
||
|
* we are compiling eval code, in which case (cx->fp->flags & JSFRAME_EVAL)
|
||
|
* is true, and js_GetToken will have already selected JSOP_OBJECT instead of
|
||
|
* JSOP_REGEXP, to avoid all this RegExp object cloning business.
|
||
|
*
|
||
|
* Why clone regexp objects? ECMA specifies that when a regular expression
|
||
|
* literal is scanned, a RegExp object is created. In the spec, compilation
|
||
|
* and execution happen indivisibly, but in this implementation and many of
|
||
|
* its embeddings, code is precompiled early and re-executed in multiple
|
||
|
* threads, or using multiple global objects, or both, for efficiency.
|
||
|
*
|
||
|
* In such cases, naively following ECMA leads to wrongful sharing of RegExp
|
||
|
* objects, which makes for collisions on the lastIndex property (especially
|
||
|
* for global regexps) and on any ad-hoc properties. Also, __proto__ and
|
||
|
* __parent__ refer to the pre-compilation prototype and global objects, a
|
||
|
* pigeon-hole problem for instanceof tests.
|
||
|
*/
|
||
|
static JSBool
|
||
|
IndexRegExpClone(JSContext *cx, JSParseNode *pn, JSAtomListElement *ale,
|
||
|
JSCodeGenerator *cg)
|
||
|
{
|
||
|
JSObject *varobj, *reobj;
|
||
|
JSClass *clasp;
|
||
|
JSFunction *fun;
|
||
|
JSRegExp *re;
|
||
|
uint16 *countPtr;
|
||
|
uintN cloneIndex;
|
||
|
|
||
|
JS_ASSERT(!(cx->fp->flags & (JSFRAME_EVAL | JSFRAME_COMPILE_N_GO)));
|
||
|
|
||
|
varobj = cx->fp->varobj;
|
||
|
clasp = OBJ_GET_CLASS(cx, varobj);
|
||
|
if (clasp == &js_FunctionClass) {
|
||
|
fun = (JSFunction *) JS_GetPrivate(cx, varobj);
|
||
|
countPtr = &fun->u.i.nregexps;
|
||
|
cloneIndex = *countPtr;
|
||
|
} else {
|
||
|
JS_ASSERT(clasp != &js_CallClass);
|
||
|
countPtr = &cg->treeContext.numGlobalVars;
|
||
|
cloneIndex = ALE_INDEX(ale);
|
||
|
}
|
||
|
|
||
|
if ((cloneIndex + 1) >> 16) {
|
||
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
|
||
|
JSMSG_NEED_DIET, js_script_str);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (cloneIndex >= *countPtr)
|
||
|
*countPtr = cloneIndex + 1;
|
||
|
|
||
|
reobj = ATOM_TO_OBJECT(pn->pn_atom);
|
||
|
JS_ASSERT(OBJ_GET_CLASS(cx, reobj) == &js_RegExpClass);
|
||
|
re = (JSRegExp *) JS_GetPrivate(cx, reobj);
|
||
|
re->cloneIndex = cloneIndex;
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Emit a bytecode and its 2-byte constant (atom) index immediate operand.
|
||
|
* If the atomIndex requires more than 2 bytes, emit a prefix op whose 24-bit
|
||
|
* immediate operand indexes the atom in script->atomMap.
|
||
|
*
|
||
|
* If op has JOF_NAME mode, emit JSOP_FINDNAME to find and push the object in
|
||
|
* the scope chain in which the literal name was found, followed by the name
|
||
|
* as a string. This enables us to use the JOF_ELEM counterpart to op.
|
||
|
*
|
||
|
* Otherwise, if op has JOF_PROP mode, emit JSOP_LITERAL before op, to push
|
||
|
* the atom's value key. For JOF_PROP ops, the object being operated on has
|
||
|
* already been pushed, and JSOP_LITERAL will push the id, leaving the stack
|
||
|
* in the proper state for a JOF_ELEM counterpart.
|
||
|
*
|
||
|
* Otherwise, emit JSOP_LITOPX to push the atom index, then perform a special
|
||
|
* dispatch on op, but getting op's atom index from the stack instead of from
|
||
|
* an unsigned 16-bit immediate operand.
|
||
|
*/
|
||
|
static JSBool
|
||
|
EmitAtomIndexOp(JSContext *cx, JSOp op, jsatomid atomIndex, JSCodeGenerator *cg)
|
||
|
{
|
||
|
uint32 mode;
|
||
|
JSOp prefixOp;
|
||
|
ptrdiff_t off;
|
||
|
jsbytecode *pc;
|
||
|
|
||
|
if (atomIndex >= JS_BIT(16)) {
|
||
|
mode = (js_CodeSpec[op].format & JOF_MODEMASK);
|
||
|
if (op != JSOP_SETNAME) {
|
||
|
prefixOp = ((mode != JOF_NAME && mode != JOF_PROP) ||
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
op == JSOP_GETMETHOD ||
|
||
|
op == JSOP_SETMETHOD ||
|
||
|
#endif
|
||
|
op == JSOP_SETCONST)
|
||
|
? JSOP_LITOPX
|
||
|
: (mode == JOF_NAME)
|
||
|
? JSOP_FINDNAME
|
||
|
: JSOP_LITERAL;
|
||
|
off = js_EmitN(cx, cg, prefixOp, 3);
|
||
|
if (off < 0)
|
||
|
return JS_FALSE;
|
||
|
pc = CG_CODE(cg, off);
|
||
|
SET_LITERAL_INDEX(pc, atomIndex);
|
||
|
}
|
||
|
|
||
|
switch (op) {
|
||
|
case JSOP_DECNAME: op = JSOP_DECELEM; break;
|
||
|
case JSOP_DECPROP: op = JSOP_DECELEM; break;
|
||
|
case JSOP_DELNAME: op = JSOP_DELELEM; break;
|
||
|
case JSOP_DELPROP: op = JSOP_DELELEM; break;
|
||
|
case JSOP_FORNAME: op = JSOP_FORELEM; break;
|
||
|
case JSOP_FORPROP: op = JSOP_FORELEM; break;
|
||
|
case JSOP_GETPROP: op = JSOP_GETELEM; break;
|
||
|
case JSOP_GETXPROP: op = JSOP_GETXELEM; break;
|
||
|
case JSOP_IMPORTPROP: op = JSOP_IMPORTELEM; break;
|
||
|
case JSOP_INCNAME: op = JSOP_INCELEM; break;
|
||
|
case JSOP_INCPROP: op = JSOP_INCELEM; break;
|
||
|
case JSOP_INITPROP: op = JSOP_INITELEM; break;
|
||
|
case JSOP_NAME: op = JSOP_GETELEM; break;
|
||
|
case JSOP_NAMEDEC: op = JSOP_ELEMDEC; break;
|
||
|
case JSOP_NAMEINC: op = JSOP_ELEMINC; break;
|
||
|
case JSOP_PROPDEC: op = JSOP_ELEMDEC; break;
|
||
|
case JSOP_PROPINC: op = JSOP_ELEMINC; break;
|
||
|
case JSOP_BINDNAME: return JS_TRUE;
|
||
|
case JSOP_SETNAME: op = JSOP_SETELEM; break;
|
||
|
case JSOP_SETPROP: op = JSOP_SETELEM; break;
|
||
|
#if JS_HAS_EXPORT_IMPORT
|
||
|
case JSOP_EXPORTNAME:
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
#endif
|
||
|
default:
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
JS_ASSERT(mode == 0 || op == JSOP_SETCONST ||
|
||
|
op == JSOP_GETMETHOD || op == JSOP_SETMETHOD);
|
||
|
#else
|
||
|
JS_ASSERT(mode == 0 || op == JSOP_SETCONST);
|
||
|
#endif
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return js_Emit1(cx, cg, op) >= 0;
|
||
|
}
|
||
|
|
||
|
EMIT_UINT16_IMM_OP(op, atomIndex);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Slight sugar for EmitAtomIndexOp, again accessing cx and cg from the macro
|
||
|
* caller's lexical environment, and embedding a false return on error.
|
||
|
* XXXbe hey, who checks for fun->nvars and fun->nargs overflow?!
|
||
|
*/
|
||
|
#define EMIT_ATOM_INDEX_OP(op, atomIndex) \
|
||
|
JS_BEGIN_MACRO \
|
||
|
if (!EmitAtomIndexOp(cx, op, atomIndex, cg)) \
|
||
|
return JS_FALSE; \
|
||
|
JS_END_MACRO
|
||
|
|
||
|
static JSBool
|
||
|
EmitAtomOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg)
|
||
|
{
|
||
|
JSAtomListElement *ale;
|
||
|
|
||
|
ale = js_IndexAtom(cx, pn->pn_atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
if (op == JSOP_REGEXP && !IndexRegExpClone(cx, pn, ale, cg))
|
||
|
return JS_FALSE;
|
||
|
return EmitAtomIndexOp(cx, op, ALE_INDEX(ale), cg);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* This routine tries to optimize name gets and sets to stack slot loads and
|
||
|
* stores, given the variables object and scope chain in cx's top frame, the
|
||
|
* compile-time context in tc, and a TOK_NAME node pn. It returns false on
|
||
|
* error, true on success.
|
||
|
*
|
||
|
* The caller can inspect pn->pn_slot for a non-negative slot number to tell
|
||
|
* whether optimization occurred, in which case BindNameToSlot also updated
|
||
|
* pn->pn_op. If pn->pn_slot is still -1 on return, pn->pn_op nevertheless
|
||
|
* may have been optimized, e.g., from JSOP_NAME to JSOP_ARGUMENTS. Whether
|
||
|
* or not pn->pn_op was modified, if this function finds an argument or local
|
||
|
* variable name, pn->pn_attrs will contain the property's attributes after a
|
||
|
* successful return.
|
||
|
*
|
||
|
* NB: if you add more opcodes specialized from JSOP_NAME, etc., don't forget
|
||
|
* to update the TOK_FOR (for-in) and TOK_ASSIGN (op=, e.g. +=) special cases
|
||
|
* in js_EmitTree.
|
||
|
*/
|
||
|
static JSBool
|
||
|
BindNameToSlot(JSContext *cx, JSTreeContext *tc, JSParseNode *pn,
|
||
|
JSBool letdecl)
|
||
|
{
|
||
|
JSAtom *atom;
|
||
|
JSStmtInfo *stmt;
|
||
|
jsint slot;
|
||
|
JSOp op;
|
||
|
JSStackFrame *fp;
|
||
|
JSObject *obj, *pobj;
|
||
|
JSClass *clasp;
|
||
|
JSBool optimizeGlobals;
|
||
|
JSPropertyOp getter;
|
||
|
uintN attrs;
|
||
|
JSAtomListElement *ale;
|
||
|
JSProperty *prop;
|
||
|
JSScopeProperty *sprop;
|
||
|
|
||
|
JS_ASSERT(pn->pn_type == TOK_NAME);
|
||
|
if (pn->pn_slot >= 0 || pn->pn_op == JSOP_ARGUMENTS)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
/* QNAME references can never be optimized to use arg/var storage. */
|
||
|
if (pn->pn_op == JSOP_QNAMEPART)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
/*
|
||
|
* We can't optimize if we are compiling a with statement and its body,
|
||
|
* or we're in a catch block whose exception variable has the same name
|
||
|
* as this node. FIXME: we should be able to optimize catch vars to be
|
||
|
* block-locals.
|
||
|
*/
|
||
|
atom = pn->pn_atom;
|
||
|
stmt = js_LexicalLookup(tc, atom, &slot, letdecl);
|
||
|
if (stmt) {
|
||
|
if (stmt->type == STMT_WITH)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
JS_ASSERT(stmt->flags & SIF_SCOPE);
|
||
|
JS_ASSERT(slot >= 0);
|
||
|
op = pn->pn_op;
|
||
|
switch (op) {
|
||
|
case JSOP_NAME: op = JSOP_GETLOCAL; break;
|
||
|
case JSOP_SETNAME: op = JSOP_SETLOCAL; break;
|
||
|
case JSOP_INCNAME: op = JSOP_INCLOCAL; break;
|
||
|
case JSOP_NAMEINC: op = JSOP_LOCALINC; break;
|
||
|
case JSOP_DECNAME: op = JSOP_DECLOCAL; break;
|
||
|
case JSOP_NAMEDEC: op = JSOP_LOCALDEC; break;
|
||
|
case JSOP_FORNAME: op = JSOP_FORLOCAL; break;
|
||
|
case JSOP_DELNAME: op = JSOP_FALSE; break;
|
||
|
default: JS_ASSERT(0);
|
||
|
}
|
||
|
if (op != pn->pn_op) {
|
||
|
pn->pn_op = op;
|
||
|
pn->pn_slot = slot;
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* A Script object can be used to split an eval into a compile step done
|
||
|
* at construction time, and an execute step done separately, possibly in
|
||
|
* a different scope altogether. We therefore cannot do any name-to-slot
|
||
|
* optimizations, but must lookup names at runtime. Note that script_exec
|
||
|
* ensures that its caller's frame has a Call object, so arg and var name
|
||
|
* lookups will succeed.
|
||
|
*/
|
||
|
fp = cx->fp;
|
||
|
if (fp->flags & JSFRAME_SCRIPT_OBJECT)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
/*
|
||
|
* We can't optimize if var and closure (a local function not in a larger
|
||
|
* expression and not at top-level within another's body) collide.
|
||
|
* XXX suboptimal: keep track of colliding names and deoptimize only those
|
||
|
*/
|
||
|
if (tc->flags & TCF_FUN_CLOSURE_VS_VAR)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
/*
|
||
|
* We can't optimize if we're not compiling a function body, whether via
|
||
|
* eval, or directly when compiling a function statement or expression.
|
||
|
*/
|
||
|
obj = fp->varobj;
|
||
|
clasp = OBJ_GET_CLASS(cx, obj);
|
||
|
if (clasp != &js_FunctionClass && clasp != &js_CallClass) {
|
||
|
/* Check for an eval or debugger frame. */
|
||
|
if (fp->flags & JSFRAME_SPECIAL)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
/*
|
||
|
* Optimize global variable accesses if there are at least 100 uses
|
||
|
* in unambiguous contexts, or failing that, if least half of all the
|
||
|
* uses of global vars/consts/functions are in loops.
|
||
|
*/
|
||
|
optimizeGlobals = (tc->globalUses >= 100 ||
|
||
|
(tc->loopyGlobalUses &&
|
||
|
tc->loopyGlobalUses >= tc->globalUses / 2));
|
||
|
if (!optimizeGlobals)
|
||
|
return JS_TRUE;
|
||
|
} else {
|
||
|
optimizeGlobals = JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* We can't optimize if we are in an eval called inside a with statement.
|
||
|
*/
|
||
|
if (fp->scopeChain != obj)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
op = pn->pn_op;
|
||
|
getter = NULL;
|
||
|
#ifdef __GNUC__
|
||
|
attrs = slot = 0; /* quell GCC overwarning */
|
||
|
#endif
|
||
|
if (optimizeGlobals) {
|
||
|
/*
|
||
|
* We are optimizing global variables, and there is no pre-existing
|
||
|
* global property named atom. If atom was declared via const or var,
|
||
|
* optimize pn to access fp->vars using the appropriate JOF_QVAR op.
|
||
|
*/
|
||
|
ATOM_LIST_SEARCH(ale, &tc->decls, atom);
|
||
|
if (!ale) {
|
||
|
/* Use precedes declaration, or name is never declared. */
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
attrs = (ALE_JSOP(ale) == JSOP_DEFCONST)
|
||
|
? JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT
|
||
|
: JSPROP_ENUMERATE | JSPROP_PERMANENT;
|
||
|
|
||
|
/* Index atom so we can map fast global number to name. */
|
||
|
JS_ASSERT(tc->flags & TCF_COMPILING);
|
||
|
ale = js_IndexAtom(cx, atom, &((JSCodeGenerator *) tc)->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Defend against tc->numGlobalVars 16-bit overflow. */
|
||
|
slot = ALE_INDEX(ale);
|
||
|
if ((slot + 1) >> 16)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
if ((uint16)(slot + 1) > tc->numGlobalVars)
|
||
|
tc->numGlobalVars = (uint16)(slot + 1);
|
||
|
} else {
|
||
|
/*
|
||
|
* We may be able to optimize name to stack slot. Look for an argument
|
||
|
* or variable property in the function, or its call object, not found
|
||
|
* in any prototype object. Rewrite pn_op and update pn accordingly.
|
||
|
* NB: We know that JSOP_DELNAME on an argument or variable evaluates
|
||
|
* to false, due to JSPROP_PERMANENT.
|
||
|
*/
|
||
|
if (!js_LookupHiddenProperty(cx, obj, ATOM_TO_JSID(atom), &pobj, &prop))
|
||
|
return JS_FALSE;
|
||
|
sprop = (JSScopeProperty *) prop;
|
||
|
if (sprop) {
|
||
|
if (pobj == obj) {
|
||
|
getter = sprop->getter;
|
||
|
attrs = sprop->attrs;
|
||
|
slot = (sprop->flags & SPROP_HAS_SHORTID) ? sprop->shortid : -1;
|
||
|
}
|
||
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (optimizeGlobals || getter) {
|
||
|
if (optimizeGlobals) {
|
||
|
switch (op) {
|
||
|
case JSOP_NAME: op = JSOP_GETGVAR; break;
|
||
|
case JSOP_SETNAME: op = JSOP_SETGVAR; break;
|
||
|
case JSOP_SETCONST: /* NB: no change */ break;
|
||
|
case JSOP_INCNAME: op = JSOP_INCGVAR; break;
|
||
|
case JSOP_NAMEINC: op = JSOP_GVARINC; break;
|
||
|
case JSOP_DECNAME: op = JSOP_DECGVAR; break;
|
||
|
case JSOP_NAMEDEC: op = JSOP_GVARDEC; break;
|
||
|
case JSOP_FORNAME: /* NB: no change */ break;
|
||
|
case JSOP_DELNAME: /* NB: no change */ break;
|
||
|
default: JS_ASSERT(0);
|
||
|
}
|
||
|
} else if (getter == js_GetLocalVariable ||
|
||
|
getter == js_GetCallVariable) {
|
||
|
switch (op) {
|
||
|
case JSOP_NAME: op = JSOP_GETVAR; break;
|
||
|
case JSOP_SETNAME: op = JSOP_SETVAR; break;
|
||
|
case JSOP_SETCONST: op = JSOP_SETVAR; break;
|
||
|
case JSOP_INCNAME: op = JSOP_INCVAR; break;
|
||
|
case JSOP_NAMEINC: op = JSOP_VARINC; break;
|
||
|
case JSOP_DECNAME: op = JSOP_DECVAR; break;
|
||
|
case JSOP_NAMEDEC: op = JSOP_VARDEC; break;
|
||
|
case JSOP_FORNAME: op = JSOP_FORVAR; break;
|
||
|
case JSOP_DELNAME: op = JSOP_FALSE; break;
|
||
|
default: JS_ASSERT(0);
|
||
|
}
|
||
|
} else if (getter == js_GetArgument ||
|
||
|
(getter == js_CallClass.getProperty &&
|
||
|
fp->fun && (uintN) slot < fp->fun->nargs)) {
|
||
|
switch (op) {
|
||
|
case JSOP_NAME: op = JSOP_GETARG; break;
|
||
|
case JSOP_SETNAME: op = JSOP_SETARG; break;
|
||
|
case JSOP_INCNAME: op = JSOP_INCARG; break;
|
||
|
case JSOP_NAMEINC: op = JSOP_ARGINC; break;
|
||
|
case JSOP_DECNAME: op = JSOP_DECARG; break;
|
||
|
case JSOP_NAMEDEC: op = JSOP_ARGDEC; break;
|
||
|
case JSOP_FORNAME: op = JSOP_FORARG; break;
|
||
|
case JSOP_DELNAME: op = JSOP_FALSE; break;
|
||
|
default: JS_ASSERT(0);
|
||
|
}
|
||
|
}
|
||
|
if (op != pn->pn_op) {
|
||
|
pn->pn_op = op;
|
||
|
pn->pn_slot = slot;
|
||
|
}
|
||
|
pn->pn_attrs = attrs;
|
||
|
}
|
||
|
|
||
|
if (pn->pn_slot < 0) {
|
||
|
/*
|
||
|
* We couldn't optimize pn, so it's not a global or local slot name.
|
||
|
* Now we must check for the predefined arguments variable. It may be
|
||
|
* overridden by assignment, in which case the function is heavyweight
|
||
|
* and the interpreter will look up 'arguments' in the function's call
|
||
|
* object.
|
||
|
*/
|
||
|
if (pn->pn_op == JSOP_NAME &&
|
||
|
atom == cx->runtime->atomState.argumentsAtom) {
|
||
|
pn->pn_op = JSOP_ARGUMENTS;
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
tc->flags |= TCF_FUN_USES_NONLOCALS;
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If pn contains a useful expression, return true with *answer set to true.
|
||
|
* If pn contains a useless expression, return true with *answer set to false.
|
||
|
* Return false on error.
|
||
|
*
|
||
|
* The caller should initialize *answer to false and invoke this function on
|
||
|
* an expression statement or similar subtree to decide whether the tree could
|
||
|
* produce code that has any side effects. For an expression statement, we
|
||
|
* define useless code as code with no side effects, because the main effect,
|
||
|
* the value left on the stack after the code executes, will be discarded by a
|
||
|
* pop bytecode.
|
||
|
*/
|
||
|
static JSBool
|
||
|
CheckSideEffects(JSContext *cx, JSTreeContext *tc, JSParseNode *pn,
|
||
|
JSBool *answer)
|
||
|
{
|
||
|
JSBool ok;
|
||
|
JSFunction *fun;
|
||
|
JSParseNode *pn2;
|
||
|
|
||
|
ok = JS_TRUE;
|
||
|
if (!pn || *answer)
|
||
|
return ok;
|
||
|
|
||
|
switch (pn->pn_arity) {
|
||
|
case PN_FUNC:
|
||
|
/*
|
||
|
* A named function is presumed useful: we can't yet know that it is
|
||
|
* not called. The side effects are the creation of a scope object
|
||
|
* to parent this function object, and the binding of the function's
|
||
|
* name in that scope object. See comments at case JSOP_NAMEDFUNOBJ:
|
||
|
* in jsinterp.c.
|
||
|
*/
|
||
|
fun = (JSFunction *) JS_GetPrivate(cx, ATOM_TO_OBJECT(pn->pn_funAtom));
|
||
|
if (fun->atom)
|
||
|
*answer = JS_TRUE;
|
||
|
break;
|
||
|
|
||
|
case PN_LIST:
|
||
|
if (pn->pn_type == TOK_NEW ||
|
||
|
pn->pn_type == TOK_LP ||
|
||
|
pn->pn_type == TOK_LB ||
|
||
|
pn->pn_type == TOK_RB ||
|
||
|
pn->pn_type == TOK_RC) {
|
||
|
/*
|
||
|
* All invocation operations (construct: TOK_NEW, call: TOK_LP)
|
||
|
* are presumed to be useful, because they may have side effects
|
||
|
* even if their main effect (their return value) is discarded.
|
||
|
*
|
||
|
* TOK_LB binary trees of 3 or more nodes are flattened into lists
|
||
|
* to avoid too much recursion. All such lists must be presumed
|
||
|
* to be useful because each index operation could invoke a getter
|
||
|
* (the JSOP_ARGUMENTS special case below, in the PN_BINARY case,
|
||
|
* does not apply here: arguments[i][j] might invoke a getter).
|
||
|
*
|
||
|
* Array and object initializers (TOK_RB and TOK_RC lists) must be
|
||
|
* considered useful, because they are sugar for constructor calls
|
||
|
* (to Array and Object, respectively).
|
||
|
*/
|
||
|
*answer = JS_TRUE;
|
||
|
} else {
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next)
|
||
|
ok &= CheckSideEffects(cx, tc, pn2, answer);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PN_TERNARY:
|
||
|
ok = CheckSideEffects(cx, tc, pn->pn_kid1, answer) &&
|
||
|
CheckSideEffects(cx, tc, pn->pn_kid2, answer) &&
|
||
|
CheckSideEffects(cx, tc, pn->pn_kid3, answer);
|
||
|
break;
|
||
|
|
||
|
case PN_BINARY:
|
||
|
if (pn->pn_type == TOK_ASSIGN) {
|
||
|
/*
|
||
|
* Assignment is presumed to be useful, even if the next operation
|
||
|
* is another assignment overwriting this one's ostensible effect,
|
||
|
* because the left operand may be a property with a setter that
|
||
|
* has side effects.
|
||
|
*
|
||
|
* The only exception is assignment of a useless value to a const
|
||
|
* declared in the function currently being compiled.
|
||
|
*/
|
||
|
pn2 = pn->pn_left;
|
||
|
if (pn2->pn_type != TOK_NAME) {
|
||
|
*answer = JS_TRUE;
|
||
|
} else {
|
||
|
if (!BindNameToSlot(cx, tc, pn2, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
if (!CheckSideEffects(cx, tc, pn->pn_right, answer))
|
||
|
return JS_FALSE;
|
||
|
if (!*answer &&
|
||
|
(pn2->pn_slot < 0 || !(pn2->pn_attrs & JSPROP_READONLY))) {
|
||
|
*answer = JS_TRUE;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if (pn->pn_type == TOK_LB) {
|
||
|
pn2 = pn->pn_left;
|
||
|
if (pn2->pn_type == TOK_NAME &&
|
||
|
!BindNameToSlot(cx, tc, pn2, JS_FALSE)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (pn2->pn_op != JSOP_ARGUMENTS) {
|
||
|
/*
|
||
|
* Any indexed property reference could call a getter with
|
||
|
* side effects, except for arguments[i] where arguments is
|
||
|
* unambiguous.
|
||
|
*/
|
||
|
*answer = JS_TRUE;
|
||
|
}
|
||
|
}
|
||
|
ok = CheckSideEffects(cx, tc, pn->pn_left, answer) &&
|
||
|
CheckSideEffects(cx, tc, pn->pn_right, answer);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PN_UNARY:
|
||
|
if (pn->pn_type == TOK_INC || pn->pn_type == TOK_DEC ||
|
||
|
pn->pn_type == TOK_THROW ||
|
||
|
#if JS_HAS_GENERATORS
|
||
|
pn->pn_type == TOK_YIELD ||
|
||
|
#endif
|
||
|
pn->pn_type == TOK_DEFSHARP) {
|
||
|
/* All these operations have effects that we must commit. */
|
||
|
*answer = JS_TRUE;
|
||
|
} else if (pn->pn_type == TOK_DELETE) {
|
||
|
pn2 = pn->pn_kid;
|
||
|
switch (pn2->pn_type) {
|
||
|
case TOK_NAME:
|
||
|
case TOK_DOT:
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_DBLDOT:
|
||
|
#endif
|
||
|
#if JS_HAS_LVALUE_RETURN
|
||
|
case TOK_LP:
|
||
|
#endif
|
||
|
case TOK_LB:
|
||
|
/* All these delete addressing modes have effects too. */
|
||
|
*answer = JS_TRUE;
|
||
|
break;
|
||
|
default:
|
||
|
ok = CheckSideEffects(cx, tc, pn2, answer);
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
ok = CheckSideEffects(cx, tc, pn->pn_kid, answer);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case PN_NAME:
|
||
|
/*
|
||
|
* Take care to avoid trying to bind a label name (labels, both for
|
||
|
* statements and property values in object initialisers, have pn_op
|
||
|
* defaulted to JSOP_NOP).
|
||
|
*/
|
||
|
if (pn->pn_type == TOK_NAME && pn->pn_op != JSOP_NOP) {
|
||
|
if (!BindNameToSlot(cx, tc, pn, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
if (pn->pn_slot < 0 && pn->pn_op != JSOP_ARGUMENTS) {
|
||
|
/*
|
||
|
* Not an argument or local variable use, so this expression
|
||
|
* could invoke a getter that has side effects.
|
||
|
*/
|
||
|
*answer = JS_TRUE;
|
||
|
}
|
||
|
}
|
||
|
pn2 = pn->pn_expr;
|
||
|
if (pn->pn_type == TOK_DOT) {
|
||
|
if (pn2->pn_type == TOK_NAME &&
|
||
|
!BindNameToSlot(cx, tc, pn2, JS_FALSE)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!(pn2->pn_op == JSOP_ARGUMENTS &&
|
||
|
pn->pn_atom == cx->runtime->atomState.lengthAtom)) {
|
||
|
/*
|
||
|
* Any dotted property reference could call a getter, except
|
||
|
* for arguments.length where arguments is unambiguous.
|
||
|
*/
|
||
|
*answer = JS_TRUE;
|
||
|
}
|
||
|
}
|
||
|
ok = CheckSideEffects(cx, tc, pn2, answer);
|
||
|
break;
|
||
|
|
||
|
case PN_NULLARY:
|
||
|
if (pn->pn_type == TOK_DEBUGGER)
|
||
|
*answer = JS_TRUE;
|
||
|
break;
|
||
|
}
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Secret handshake with js_EmitTree's TOK_LP/TOK_NEW case logic, to flag all
|
||
|
* uses of JSOP_GETMETHOD that implicitly qualify the method property's name
|
||
|
* with a function:: prefix. All other JSOP_GETMETHOD and JSOP_SETMETHOD uses
|
||
|
* must be explicit, so we need a distinct source note (SRC_METHODBASE rather
|
||
|
* than SRC_PCBASE) for round-tripping through the beloved decompiler.
|
||
|
*/
|
||
|
#define JSPROP_IMPLICIT_FUNCTION_NAMESPACE 0x100
|
||
|
|
||
|
static jssrcnote
|
||
|
SrcNoteForPropOp(JSParseNode *pn, JSOp op)
|
||
|
{
|
||
|
return ((op == JSOP_GETMETHOD &&
|
||
|
!(pn->pn_attrs & JSPROP_IMPLICIT_FUNCTION_NAMESPACE)) ||
|
||
|
op == JSOP_SETMETHOD)
|
||
|
? SRC_METHODBASE
|
||
|
: SRC_PCBASE;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitPropOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg)
|
||
|
{
|
||
|
JSParseNode *pn2, *pndot, *pnup, *pndown;
|
||
|
ptrdiff_t top;
|
||
|
|
||
|
pn2 = pn->pn_expr;
|
||
|
if (op == JSOP_GETPROP &&
|
||
|
pn->pn_type == TOK_DOT &&
|
||
|
pn2->pn_type == TOK_NAME) {
|
||
|
/* Try to optimize arguments.length into JSOP_ARGCNT. */
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, pn2, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
if (pn2->pn_op == JSOP_ARGUMENTS &&
|
||
|
pn->pn_atom == cx->runtime->atomState.lengthAtom) {
|
||
|
return js_Emit1(cx, cg, JSOP_ARGCNT) >= 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If the object operand is also a dotted property reference, reverse the
|
||
|
* list linked via pn_expr temporarily so we can iterate over it from the
|
||
|
* bottom up (reversing again as we go), to avoid excessive recursion.
|
||
|
*/
|
||
|
if (pn2->pn_type == TOK_DOT) {
|
||
|
pndot = pn2;
|
||
|
pnup = NULL;
|
||
|
top = CG_OFFSET(cg);
|
||
|
for (;;) {
|
||
|
/* Reverse pndot->pn_expr to point up, not down. */
|
||
|
pndot->pn_offset = top;
|
||
|
pndown = pndot->pn_expr;
|
||
|
pndot->pn_expr = pnup;
|
||
|
if (pndown->pn_type != TOK_DOT)
|
||
|
break;
|
||
|
pnup = pndot;
|
||
|
pndot = pndown;
|
||
|
}
|
||
|
|
||
|
/* pndown is a primary expression, not a dotted property reference. */
|
||
|
if (!js_EmitTree(cx, cg, pndown))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
do {
|
||
|
/* Walk back up the list, emitting annotated name ops. */
|
||
|
if (js_NewSrcNote2(cx, cg, SrcNoteForPropOp(pndot, pndot->pn_op),
|
||
|
CG_OFFSET(cg) - pndown->pn_offset) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!EmitAtomOp(cx, pndot, pndot->pn_op, cg))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Reverse the pn_expr link again. */
|
||
|
pnup = pndot->pn_expr;
|
||
|
pndot->pn_expr = pndown;
|
||
|
pndown = pndot;
|
||
|
} while ((pndot = pnup) != NULL);
|
||
|
} else {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (js_NewSrcNote2(cx, cg, SrcNoteForPropOp(pn, op),
|
||
|
CG_OFFSET(cg) - pn2->pn_offset) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!pn->pn_atom) {
|
||
|
JS_ASSERT(op == JSOP_IMPORTALL);
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (!EmitAtomOp(cx, pn, op, cg))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitElemOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg)
|
||
|
{
|
||
|
ptrdiff_t top;
|
||
|
JSParseNode *left, *right, *next, ltmp, rtmp;
|
||
|
jsint slot;
|
||
|
|
||
|
top = CG_OFFSET(cg);
|
||
|
if (pn->pn_arity == PN_LIST) {
|
||
|
/* Left-associative operator chain to avoid too much recursion. */
|
||
|
JS_ASSERT(pn->pn_op == JSOP_GETELEM || pn->pn_op == JSOP_IMPORTELEM);
|
||
|
JS_ASSERT(pn->pn_count >= 3);
|
||
|
left = pn->pn_head;
|
||
|
right = PN_LAST(pn);
|
||
|
next = left->pn_next;
|
||
|
JS_ASSERT(next != right);
|
||
|
|
||
|
/*
|
||
|
* Try to optimize arguments[0][j]... into JSOP_ARGSUB<0> followed by
|
||
|
* one or more index expression and JSOP_GETELEM op pairs.
|
||
|
*/
|
||
|
if (left->pn_type == TOK_NAME && next->pn_type == TOK_NUMBER) {
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, left, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
if (left->pn_op == JSOP_ARGUMENTS &&
|
||
|
JSDOUBLE_IS_INT(next->pn_dval, slot) &&
|
||
|
(jsuint)slot < JS_BIT(16)) {
|
||
|
left->pn_offset = next->pn_offset = top;
|
||
|
EMIT_UINT16_IMM_OP(JSOP_ARGSUB, (jsatomid)slot);
|
||
|
left = next;
|
||
|
next = left->pn_next;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Check whether we generated JSOP_ARGSUB, just above, and have only
|
||
|
* one more index expression to emit. Given arguments[0][j], we must
|
||
|
* skip the while loop altogether, falling through to emit code for j
|
||
|
* (in the subtree referenced by right), followed by the annotated op,
|
||
|
* at the bottom of this function.
|
||
|
*/
|
||
|
JS_ASSERT(next != right || pn->pn_count == 3);
|
||
|
if (left == pn->pn_head) {
|
||
|
if (!js_EmitTree(cx, cg, left))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
while (next != right) {
|
||
|
if (!js_EmitTree(cx, cg, next))
|
||
|
return JS_FALSE;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
|
||
|
return JS_FALSE;
|
||
|
next = next->pn_next;
|
||
|
}
|
||
|
} else {
|
||
|
if (pn->pn_arity == PN_NAME) {
|
||
|
/*
|
||
|
* Set left and right so pn appears to be a TOK_LB node, instead
|
||
|
* of a TOK_DOT node. See the TOK_FOR/IN case in js_EmitTree, and
|
||
|
* EmitDestructuringOps nearer below. In the destructuring case,
|
||
|
* the base expression (pn_expr) of the name may be null, which
|
||
|
* means we have to emit a JSOP_BINDNAME.
|
||
|
*/
|
||
|
left = pn->pn_expr;
|
||
|
if (!left) {
|
||
|
left = <mp;
|
||
|
left->pn_type = TOK_OBJECT;
|
||
|
left->pn_op = JSOP_BINDNAME;
|
||
|
left->pn_arity = PN_NULLARY;
|
||
|
left->pn_pos = pn->pn_pos;
|
||
|
left->pn_atom = pn->pn_atom;
|
||
|
}
|
||
|
right = &rtmp;
|
||
|
right->pn_type = TOK_STRING;
|
||
|
JS_ASSERT(ATOM_IS_STRING(pn->pn_atom));
|
||
|
right->pn_op = js_IsIdentifier(ATOM_TO_STRING(pn->pn_atom))
|
||
|
? JSOP_QNAMEPART
|
||
|
: JSOP_STRING;
|
||
|
right->pn_arity = PN_NULLARY;
|
||
|
right->pn_pos = pn->pn_pos;
|
||
|
right->pn_atom = pn->pn_atom;
|
||
|
} else {
|
||
|
JS_ASSERT(pn->pn_arity == PN_BINARY);
|
||
|
left = pn->pn_left;
|
||
|
right = pn->pn_right;
|
||
|
}
|
||
|
|
||
|
/* Try to optimize arguments[0] (e.g.) into JSOP_ARGSUB<0>. */
|
||
|
if (op == JSOP_GETELEM &&
|
||
|
left->pn_type == TOK_NAME &&
|
||
|
right->pn_type == TOK_NUMBER) {
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, left, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
if (left->pn_op == JSOP_ARGUMENTS &&
|
||
|
JSDOUBLE_IS_INT(right->pn_dval, slot) &&
|
||
|
(jsuint)slot < JS_BIT(16)) {
|
||
|
left->pn_offset = right->pn_offset = top;
|
||
|
EMIT_UINT16_IMM_OP(JSOP_ARGSUB, (jsatomid)slot);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!js_EmitTree(cx, cg, left))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* The right side of the descendant operator is implicitly quoted. */
|
||
|
JS_ASSERT(op != JSOP_DESCENDANTS || right->pn_type != TOK_STRING ||
|
||
|
right->pn_op == JSOP_QNAMEPART);
|
||
|
if (!js_EmitTree(cx, cg, right))
|
||
|
return JS_FALSE;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0)
|
||
|
return JS_FALSE;
|
||
|
return js_Emit1(cx, cg, op) >= 0;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitNumberOp(JSContext *cx, jsdouble dval, JSCodeGenerator *cg)
|
||
|
{
|
||
|
jsint ival;
|
||
|
jsatomid atomIndex;
|
||
|
ptrdiff_t off;
|
||
|
jsbytecode *pc;
|
||
|
JSAtom *atom;
|
||
|
JSAtomListElement *ale;
|
||
|
|
||
|
if (JSDOUBLE_IS_INT(dval, ival) && INT_FITS_IN_JSVAL(ival)) {
|
||
|
if (ival == 0)
|
||
|
return js_Emit1(cx, cg, JSOP_ZERO) >= 0;
|
||
|
if (ival == 1)
|
||
|
return js_Emit1(cx, cg, JSOP_ONE) >= 0;
|
||
|
|
||
|
atomIndex = (jsatomid)ival;
|
||
|
if (atomIndex < JS_BIT(16)) {
|
||
|
EMIT_UINT16_IMM_OP(JSOP_UINT16, atomIndex);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
if (atomIndex < JS_BIT(24)) {
|
||
|
off = js_EmitN(cx, cg, JSOP_UINT24, 3);
|
||
|
if (off < 0)
|
||
|
return JS_FALSE;
|
||
|
pc = CG_CODE(cg, off);
|
||
|
SET_LITERAL_INDEX(pc, atomIndex);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
atom = js_AtomizeInt(cx, ival, 0);
|
||
|
} else {
|
||
|
atom = js_AtomizeDouble(cx, dval, 0);
|
||
|
}
|
||
|
if (!atom)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
ale = js_IndexAtom(cx, atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
return EmitAtomIndexOp(cx, JSOP_NUMBER, ALE_INDEX(ale), cg);
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitSwitch(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn,
|
||
|
JSStmtInfo *stmtInfo)
|
||
|
{
|
||
|
JSOp switchOp;
|
||
|
JSBool ok, hasDefault, constPropagated;
|
||
|
ptrdiff_t top, off, defaultOffset;
|
||
|
JSParseNode *pn2, *pn3, *pn4;
|
||
|
uint32 caseCount, tableLength;
|
||
|
JSParseNode **table;
|
||
|
jsdouble d;
|
||
|
jsint i, low, high;
|
||
|
jsval v;
|
||
|
JSAtom *atom;
|
||
|
JSAtomListElement *ale;
|
||
|
intN noteIndex;
|
||
|
size_t switchSize, tableSize;
|
||
|
jsbytecode *pc, *savepc;
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
JSObject *obj;
|
||
|
jsint count;
|
||
|
#endif
|
||
|
|
||
|
/* Try for most optimal, fall back if not dense ints, and per ECMAv2. */
|
||
|
switchOp = JSOP_TABLESWITCH;
|
||
|
ok = JS_TRUE;
|
||
|
hasDefault = constPropagated = JS_FALSE;
|
||
|
defaultOffset = -1;
|
||
|
|
||
|
/*
|
||
|
* If the switch contains let variables scoped by its body, model the
|
||
|
* resulting block on the stack first, before emitting the discriminant's
|
||
|
* bytecode (in case the discriminant contains a stack-model dependency
|
||
|
* such as a let expression).
|
||
|
*/
|
||
|
pn2 = pn->pn_right;
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
if (pn2->pn_type == TOK_LEXICALSCOPE) {
|
||
|
atom = pn2->pn_atom;
|
||
|
obj = ATOM_TO_OBJECT(atom);
|
||
|
OBJ_SET_BLOCK_DEPTH(cx, obj, cg->stackDepth);
|
||
|
|
||
|
/*
|
||
|
* Push the body's block scope before discriminant code-gen for proper
|
||
|
* static block scope linkage in case the discriminant contains a let
|
||
|
* expression. The block's locals must lie under the discriminant on
|
||
|
* the stack so that case-dispatch bytecodes can find the discriminant
|
||
|
* on top of stack.
|
||
|
*/
|
||
|
js_PushBlockScope(&cg->treeContext, stmtInfo, atom, -1);
|
||
|
stmtInfo->type = STMT_SWITCH;
|
||
|
|
||
|
count = OBJ_BLOCK_COUNT(cx, obj);
|
||
|
cg->stackDepth += count;
|
||
|
if ((uintN)cg->stackDepth > cg->maxStackDepth)
|
||
|
cg->maxStackDepth = cg->stackDepth;
|
||
|
|
||
|
/* Emit JSOP_ENTERBLOCK before code to evaluate the discriminant. */
|
||
|
ale = js_IndexAtom(cx, atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_ENTERBLOCK, ALE_INDEX(ale));
|
||
|
|
||
|
/*
|
||
|
* Pop the switch's statement info around discriminant code-gen. Note
|
||
|
* how this leaves cg->treeContext.blockChain referencing the switch's
|
||
|
* block scope object, which is necessary for correct block parenting
|
||
|
* in the case where the discriminant contains a let expression.
|
||
|
*/
|
||
|
cg->treeContext.topStmt = stmtInfo->down;
|
||
|
cg->treeContext.topScopeStmt = stmtInfo->downScope;
|
||
|
}
|
||
|
#ifdef __GNUC__
|
||
|
else {
|
||
|
atom = NULL;
|
||
|
count = -1;
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Emit code for the discriminant first (or nearly first, in the case of a
|
||
|
* switch whose body is a block scope).
|
||
|
*/
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_left))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Switch bytecodes run from here till end of final case. */
|
||
|
top = CG_OFFSET(cg);
|
||
|
#if !JS_HAS_BLOCK_SCOPE
|
||
|
js_PushStatement(&cg->treeContext, stmtInfo, STMT_SWITCH, top);
|
||
|
#else
|
||
|
if (pn2->pn_type == TOK_LC) {
|
||
|
js_PushStatement(&cg->treeContext, stmtInfo, STMT_SWITCH, top);
|
||
|
} else {
|
||
|
/* Re-push the switch's statement info record. */
|
||
|
cg->treeContext.topStmt = cg->treeContext.topScopeStmt = stmtInfo;
|
||
|
|
||
|
/* Set the statement info record's idea of top. */
|
||
|
stmtInfo->update = top;
|
||
|
|
||
|
/* Advance pn2 to refer to the switch case list. */
|
||
|
pn2 = pn2->pn_expr;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
caseCount = pn2->pn_count;
|
||
|
tableLength = 0;
|
||
|
table = NULL;
|
||
|
|
||
|
if (caseCount == 0 ||
|
||
|
(caseCount == 1 &&
|
||
|
(hasDefault = (pn2->pn_head->pn_type == TOK_DEFAULT)))) {
|
||
|
caseCount = 0;
|
||
|
low = 0;
|
||
|
high = -1;
|
||
|
} else {
|
||
|
#define INTMAP_LENGTH 256
|
||
|
jsbitmap intmap_space[INTMAP_LENGTH];
|
||
|
jsbitmap *intmap = NULL;
|
||
|
int32 intmap_bitlen = 0;
|
||
|
|
||
|
low = JSVAL_INT_MAX;
|
||
|
high = JSVAL_INT_MIN;
|
||
|
|
||
|
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
|
||
|
if (pn3->pn_type == TOK_DEFAULT) {
|
||
|
hasDefault = JS_TRUE;
|
||
|
caseCount--; /* one of the "cases" was the default */
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
JS_ASSERT(pn3->pn_type == TOK_CASE);
|
||
|
if (switchOp == JSOP_CONDSWITCH)
|
||
|
continue;
|
||
|
|
||
|
pn4 = pn3->pn_left;
|
||
|
switch (pn4->pn_type) {
|
||
|
case TOK_NUMBER:
|
||
|
d = pn4->pn_dval;
|
||
|
if (JSDOUBLE_IS_INT(d, i) && INT_FITS_IN_JSVAL(i)) {
|
||
|
pn3->pn_val = INT_TO_JSVAL(i);
|
||
|
} else {
|
||
|
atom = js_AtomizeDouble(cx, d, 0);
|
||
|
if (!atom) {
|
||
|
ok = JS_FALSE;
|
||
|
goto release;
|
||
|
}
|
||
|
pn3->pn_val = ATOM_KEY(atom);
|
||
|
}
|
||
|
break;
|
||
|
case TOK_STRING:
|
||
|
pn3->pn_val = ATOM_KEY(pn4->pn_atom);
|
||
|
break;
|
||
|
case TOK_NAME:
|
||
|
if (!pn4->pn_expr) {
|
||
|
ok = js_LookupCompileTimeConstant(cx, cg, pn4->pn_atom, &v);
|
||
|
if (!ok)
|
||
|
goto release;
|
||
|
if (!JSVAL_IS_VOID(v)) {
|
||
|
pn3->pn_val = v;
|
||
|
constPropagated = JS_TRUE;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
case TOK_PRIMARY:
|
||
|
if (pn4->pn_op == JSOP_TRUE) {
|
||
|
pn3->pn_val = JSVAL_TRUE;
|
||
|
break;
|
||
|
}
|
||
|
if (pn4->pn_op == JSOP_FALSE) {
|
||
|
pn3->pn_val = JSVAL_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
default:
|
||
|
switchOp = JSOP_CONDSWITCH;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
JS_ASSERT(JSVAL_IS_NUMBER(pn3->pn_val) ||
|
||
|
JSVAL_IS_STRING(pn3->pn_val) ||
|
||
|
JSVAL_IS_BOOLEAN(pn3->pn_val));
|
||
|
|
||
|
if (switchOp != JSOP_TABLESWITCH)
|
||
|
continue;
|
||
|
if (!JSVAL_IS_INT(pn3->pn_val)) {
|
||
|
switchOp = JSOP_LOOKUPSWITCH;
|
||
|
continue;
|
||
|
}
|
||
|
i = JSVAL_TO_INT(pn3->pn_val);
|
||
|
if ((jsuint)(i + (jsint)JS_BIT(15)) >= (jsuint)JS_BIT(16)) {
|
||
|
switchOp = JSOP_LOOKUPSWITCH;
|
||
|
continue;
|
||
|
}
|
||
|
if (i < low)
|
||
|
low = i;
|
||
|
if (high < i)
|
||
|
high = i;
|
||
|
|
||
|
/*
|
||
|
* Check for duplicates, which require a JSOP_LOOKUPSWITCH.
|
||
|
* We bias i by 65536 if it's negative, and hope that's a rare
|
||
|
* case (because it requires a malloc'd bitmap).
|
||
|
*/
|
||
|
if (i < 0)
|
||
|
i += JS_BIT(16);
|
||
|
if (i >= intmap_bitlen) {
|
||
|
if (!intmap &&
|
||
|
i < (INTMAP_LENGTH << JS_BITS_PER_WORD_LOG2)) {
|
||
|
intmap = intmap_space;
|
||
|
intmap_bitlen = INTMAP_LENGTH << JS_BITS_PER_WORD_LOG2;
|
||
|
} else {
|
||
|
/* Just grab 8K for the worst-case bitmap. */
|
||
|
intmap_bitlen = JS_BIT(16);
|
||
|
intmap = (jsbitmap *)
|
||
|
JS_malloc(cx,
|
||
|
(JS_BIT(16) >> JS_BITS_PER_WORD_LOG2)
|
||
|
* sizeof(jsbitmap));
|
||
|
if (!intmap) {
|
||
|
JS_ReportOutOfMemory(cx);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
memset(intmap, 0, intmap_bitlen >> JS_BITS_PER_BYTE_LOG2);
|
||
|
}
|
||
|
if (JS_TEST_BIT(intmap, i)) {
|
||
|
switchOp = JSOP_LOOKUPSWITCH;
|
||
|
continue;
|
||
|
}
|
||
|
JS_SET_BIT(intmap, i);
|
||
|
}
|
||
|
|
||
|
release:
|
||
|
if (intmap && intmap != intmap_space)
|
||
|
JS_free(cx, intmap);
|
||
|
if (!ok)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Compute table length and select lookup instead if overlarge or
|
||
|
* more than half-sparse.
|
||
|
*/
|
||
|
if (switchOp == JSOP_TABLESWITCH) {
|
||
|
tableLength = (uint32)(high - low + 1);
|
||
|
if (tableLength >= JS_BIT(16) || tableLength > 2 * caseCount)
|
||
|
switchOp = JSOP_LOOKUPSWITCH;
|
||
|
} else if (switchOp == JSOP_LOOKUPSWITCH) {
|
||
|
/*
|
||
|
* Lookup switch supports only atom indexes below 64K limit.
|
||
|
* Conservatively estimate the maximum possible index during
|
||
|
* switch generation and use conditional switch if it exceeds
|
||
|
* the limit.
|
||
|
*/
|
||
|
if (caseCount + cg->atomList.count > JS_BIT(16))
|
||
|
switchOp = JSOP_CONDSWITCH;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Emit a note with two offsets: first tells total switch code length,
|
||
|
* second tells offset to first JSOP_CASE if condswitch.
|
||
|
*/
|
||
|
noteIndex = js_NewSrcNote3(cx, cg, SRC_SWITCH, 0, 0);
|
||
|
if (noteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
if (switchOp == JSOP_CONDSWITCH) {
|
||
|
/*
|
||
|
* 0 bytes of immediate for unoptimized ECMAv2 switch.
|
||
|
*/
|
||
|
switchSize = 0;
|
||
|
} else if (switchOp == JSOP_TABLESWITCH) {
|
||
|
/*
|
||
|
* 3 offsets (len, low, high) before the table, 1 per entry.
|
||
|
*/
|
||
|
switchSize = (size_t)(JUMP_OFFSET_LEN * (3 + tableLength));
|
||
|
} else {
|
||
|
/*
|
||
|
* JSOP_LOOKUPSWITCH:
|
||
|
* 1 offset (len) and 1 atom index (npairs) before the table,
|
||
|
* 1 atom index and 1 jump offset per entry.
|
||
|
*/
|
||
|
switchSize = (size_t)(JUMP_OFFSET_LEN + ATOM_INDEX_LEN +
|
||
|
(ATOM_INDEX_LEN + JUMP_OFFSET_LEN) * caseCount);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Emit switchOp followed by switchSize bytes of jump or lookup table.
|
||
|
*
|
||
|
* If switchOp is JSOP_LOOKUPSWITCH or JSOP_TABLESWITCH, it is crucial
|
||
|
* to emit the immediate operand(s) by which bytecode readers such as
|
||
|
* BuildSpanDepTable discover the length of the switch opcode *before*
|
||
|
* calling js_SetJumpOffset (which may call BuildSpanDepTable). It's
|
||
|
* also important to zero all unknown jump offset immediate operands,
|
||
|
* so they can be converted to span dependencies with null targets to
|
||
|
* be computed later (js_EmitN zeros switchSize bytes after switchOp).
|
||
|
*/
|
||
|
if (js_EmitN(cx, cg, switchOp, switchSize) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
off = -1;
|
||
|
if (switchOp == JSOP_CONDSWITCH) {
|
||
|
intN caseNoteIndex = -1;
|
||
|
JSBool beforeCases = JS_TRUE;
|
||
|
|
||
|
/* Emit code for evaluating cases and jumping to case statements. */
|
||
|
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
|
||
|
pn4 = pn3->pn_left;
|
||
|
if (pn4 && !js_EmitTree(cx, cg, pn4))
|
||
|
return JS_FALSE;
|
||
|
if (caseNoteIndex >= 0) {
|
||
|
/* off is the previous JSOP_CASE's bytecode offset. */
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)caseNoteIndex, 0,
|
||
|
CG_OFFSET(cg) - off)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
if (!pn4) {
|
||
|
JS_ASSERT(pn3->pn_type == TOK_DEFAULT);
|
||
|
continue;
|
||
|
}
|
||
|
caseNoteIndex = js_NewSrcNote2(cx, cg, SRC_PCDELTA, 0);
|
||
|
if (caseNoteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
off = EmitJump(cx, cg, JSOP_CASE, 0);
|
||
|
if (off < 0)
|
||
|
return JS_FALSE;
|
||
|
pn3->pn_offset = off;
|
||
|
if (beforeCases) {
|
||
|
uintN noteCount, noteCountDelta;
|
||
|
|
||
|
/* Switch note's second offset is to first JSOP_CASE. */
|
||
|
noteCount = CG_NOTE_COUNT(cg);
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 1,
|
||
|
off - top)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
noteCountDelta = CG_NOTE_COUNT(cg) - noteCount;
|
||
|
if (noteCountDelta != 0)
|
||
|
caseNoteIndex += noteCountDelta;
|
||
|
beforeCases = JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If we didn't have an explicit default (which could fall in between
|
||
|
* cases, preventing us from fusing this js_SetSrcNoteOffset with the
|
||
|
* call in the loop above), link the last case to the implicit default
|
||
|
* for the decompiler.
|
||
|
*/
|
||
|
if (!hasDefault &&
|
||
|
caseNoteIndex >= 0 &&
|
||
|
!js_SetSrcNoteOffset(cx, cg, (uintN)caseNoteIndex, 0,
|
||
|
CG_OFFSET(cg) - off)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Emit default even if no explicit default statement. */
|
||
|
defaultOffset = EmitJump(cx, cg, JSOP_DEFAULT, 0);
|
||
|
if (defaultOffset < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
pc = CG_CODE(cg, top + JUMP_OFFSET_LEN);
|
||
|
|
||
|
if (switchOp == JSOP_TABLESWITCH) {
|
||
|
/* Fill in switch bounds, which we know fit in 16-bit offsets. */
|
||
|
SET_JUMP_OFFSET(pc, low);
|
||
|
pc += JUMP_OFFSET_LEN;
|
||
|
SET_JUMP_OFFSET(pc, high);
|
||
|
pc += JUMP_OFFSET_LEN;
|
||
|
|
||
|
/*
|
||
|
* Use malloc to avoid arena bloat for programs with many switches.
|
||
|
* We free table if non-null at label out, so all control flow must
|
||
|
* exit this function through goto out or goto bad.
|
||
|
*/
|
||
|
if (tableLength != 0) {
|
||
|
tableSize = (size_t)tableLength * sizeof *table;
|
||
|
table = (JSParseNode **) JS_malloc(cx, tableSize);
|
||
|
if (!table)
|
||
|
return JS_FALSE;
|
||
|
memset(table, 0, tableSize);
|
||
|
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
|
||
|
if (pn3->pn_type == TOK_DEFAULT)
|
||
|
continue;
|
||
|
i = JSVAL_TO_INT(pn3->pn_val);
|
||
|
i -= low;
|
||
|
JS_ASSERT((uint32)i < tableLength);
|
||
|
table[i] = pn3;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
JS_ASSERT(switchOp == JSOP_LOOKUPSWITCH);
|
||
|
|
||
|
/* Fill in the number of cases. */
|
||
|
SET_ATOM_INDEX(pc, caseCount);
|
||
|
pc += ATOM_INDEX_LEN;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* After this point, all control flow involving JSOP_TABLESWITCH
|
||
|
* must set ok and goto out to exit this function. To keep things
|
||
|
* simple, all switchOp cases exit that way.
|
||
|
*/
|
||
|
if (cg->spanDeps) {
|
||
|
/*
|
||
|
* We have already generated at least one big jump so we must
|
||
|
* explicitly add span dependencies for the switch jumps. When
|
||
|
* called below, js_SetJumpOffset can only do it when patching
|
||
|
* the first big jump or when cg->spanDeps is null.
|
||
|
*/
|
||
|
if (!AddSwitchSpanDeps(cx, cg, CG_CODE(cg, top)))
|
||
|
goto bad;
|
||
|
}
|
||
|
|
||
|
if (constPropagated) {
|
||
|
/*
|
||
|
* Skip switchOp, as we are not setting jump offsets in the two
|
||
|
* for loops below. We'll restore CG_NEXT(cg) from savepc after,
|
||
|
* unless there was an error.
|
||
|
*/
|
||
|
savepc = CG_NEXT(cg);
|
||
|
CG_NEXT(cg) = pc + 1;
|
||
|
if (switchOp == JSOP_TABLESWITCH) {
|
||
|
for (i = 0; i < (jsint)tableLength; i++) {
|
||
|
pn3 = table[i];
|
||
|
if (pn3 &&
|
||
|
(pn4 = pn3->pn_left) != NULL &&
|
||
|
pn4->pn_type == TOK_NAME) {
|
||
|
/* Note a propagated constant with the const's name. */
|
||
|
JS_ASSERT(!pn4->pn_expr);
|
||
|
ale = js_IndexAtom(cx, pn4->pn_atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
goto bad;
|
||
|
CG_NEXT(cg) = pc;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_LABEL, (ptrdiff_t)
|
||
|
ALE_INDEX(ale)) < 0) {
|
||
|
goto bad;
|
||
|
}
|
||
|
}
|
||
|
pc += JUMP_OFFSET_LEN;
|
||
|
}
|
||
|
} else {
|
||
|
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
|
||
|
pn4 = pn3->pn_left;
|
||
|
if (pn4 && pn4->pn_type == TOK_NAME) {
|
||
|
/* Note a propagated constant with the const's name. */
|
||
|
JS_ASSERT(!pn4->pn_expr);
|
||
|
ale = js_IndexAtom(cx, pn4->pn_atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
goto bad;
|
||
|
CG_NEXT(cg) = pc;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_LABEL, (ptrdiff_t)
|
||
|
ALE_INDEX(ale)) < 0) {
|
||
|
goto bad;
|
||
|
}
|
||
|
}
|
||
|
pc += ATOM_INDEX_LEN + JUMP_OFFSET_LEN;
|
||
|
}
|
||
|
}
|
||
|
CG_NEXT(cg) = savepc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Emit code for each case's statements, copying pn_offset up to pn3. */
|
||
|
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
|
||
|
if (switchOp == JSOP_CONDSWITCH && pn3->pn_type != TOK_DEFAULT)
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, pn3->pn_offset);
|
||
|
pn4 = pn3->pn_right;
|
||
|
ok = js_EmitTree(cx, cg, pn4);
|
||
|
if (!ok)
|
||
|
goto out;
|
||
|
pn3->pn_offset = pn4->pn_offset;
|
||
|
if (pn3->pn_type == TOK_DEFAULT)
|
||
|
off = pn3->pn_offset - top;
|
||
|
}
|
||
|
|
||
|
if (!hasDefault) {
|
||
|
/* If no default case, offset for default is to end of switch. */
|
||
|
off = CG_OFFSET(cg) - top;
|
||
|
}
|
||
|
|
||
|
/* We better have set "off" by now. */
|
||
|
JS_ASSERT(off != -1);
|
||
|
|
||
|
/* Set the default offset (to end of switch if no default). */
|
||
|
if (switchOp == JSOP_CONDSWITCH) {
|
||
|
pc = NULL;
|
||
|
JS_ASSERT(defaultOffset != -1);
|
||
|
ok = js_SetJumpOffset(cx, cg, CG_CODE(cg, defaultOffset),
|
||
|
off - (defaultOffset - top));
|
||
|
if (!ok)
|
||
|
goto out;
|
||
|
} else {
|
||
|
pc = CG_CODE(cg, top);
|
||
|
ok = js_SetJumpOffset(cx, cg, pc, off);
|
||
|
if (!ok)
|
||
|
goto out;
|
||
|
pc += JUMP_OFFSET_LEN;
|
||
|
}
|
||
|
|
||
|
/* Set the SRC_SWITCH note's offset operand to tell end of switch. */
|
||
|
off = CG_OFFSET(cg) - top;
|
||
|
ok = js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, off);
|
||
|
if (!ok)
|
||
|
goto out;
|
||
|
|
||
|
if (switchOp == JSOP_TABLESWITCH) {
|
||
|
/* Skip over the already-initialized switch bounds. */
|
||
|
pc += 2 * JUMP_OFFSET_LEN;
|
||
|
|
||
|
/* Fill in the jump table, if there is one. */
|
||
|
for (i = 0; i < (jsint)tableLength; i++) {
|
||
|
pn3 = table[i];
|
||
|
off = pn3 ? pn3->pn_offset - top : 0;
|
||
|
ok = js_SetJumpOffset(cx, cg, pc, off);
|
||
|
if (!ok)
|
||
|
goto out;
|
||
|
pc += JUMP_OFFSET_LEN;
|
||
|
}
|
||
|
} else if (switchOp == JSOP_LOOKUPSWITCH) {
|
||
|
/* Skip over the already-initialized number of cases. */
|
||
|
pc += ATOM_INDEX_LEN;
|
||
|
|
||
|
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
|
||
|
if (pn3->pn_type == TOK_DEFAULT)
|
||
|
continue;
|
||
|
atom = js_AtomizeValue(cx, pn3->pn_val, 0);
|
||
|
if (!atom)
|
||
|
goto bad;
|
||
|
ale = js_IndexAtom(cx, atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
goto bad;
|
||
|
SET_ATOM_INDEX(pc, ALE_INDEX(ale));
|
||
|
pc += ATOM_INDEX_LEN;
|
||
|
|
||
|
off = pn3->pn_offset - top;
|
||
|
ok = js_SetJumpOffset(cx, cg, pc, off);
|
||
|
if (!ok)
|
||
|
goto out;
|
||
|
pc += JUMP_OFFSET_LEN;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
out:
|
||
|
if (table)
|
||
|
JS_free(cx, table);
|
||
|
if (ok) {
|
||
|
ok = js_PopStatementCG(cx, cg);
|
||
|
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
if (ok && pn->pn_right->pn_type == TOK_LEXICALSCOPE) {
|
||
|
EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
|
||
|
cg->stackDepth -= count;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
return ok;
|
||
|
|
||
|
bad:
|
||
|
ok = JS_FALSE;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_EmitFunctionBytecode(JSContext *cx, JSCodeGenerator *cg, JSParseNode *body)
|
||
|
{
|
||
|
if (!js_AllocTryNotes(cx, cg))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
if (cg->treeContext.flags & TCF_FUN_IS_GENERATOR) {
|
||
|
/* JSOP_GENERATOR must be the first instruction. */
|
||
|
CG_SWITCH_TO_PROLOG(cg);
|
||
|
JS_ASSERT(CG_NEXT(cg) == CG_BASE(cg));
|
||
|
if (js_Emit1(cx, cg, JSOP_GENERATOR) < 0)
|
||
|
return JS_FALSE;
|
||
|
CG_SWITCH_TO_MAIN(cg);
|
||
|
}
|
||
|
|
||
|
return js_EmitTree(cx, cg, body) &&
|
||
|
js_Emit1(cx, cg, JSOP_STOP) >= 0;
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_EmitFunctionBody(JSContext *cx, JSCodeGenerator *cg, JSParseNode *body,
|
||
|
JSFunction *fun)
|
||
|
{
|
||
|
JSStackFrame *fp, frame;
|
||
|
JSObject *funobj;
|
||
|
JSBool ok;
|
||
|
|
||
|
fp = cx->fp;
|
||
|
funobj = fun->object;
|
||
|
JS_ASSERT(!fp || (fp->fun != fun && fp->varobj != funobj &&
|
||
|
fp->scopeChain != funobj));
|
||
|
memset(&frame, 0, sizeof frame);
|
||
|
frame.fun = fun;
|
||
|
frame.varobj = frame.scopeChain = funobj;
|
||
|
frame.down = fp;
|
||
|
frame.flags = JS_HAS_COMPILE_N_GO_OPTION(cx)
|
||
|
? JSFRAME_COMPILING | JSFRAME_COMPILE_N_GO
|
||
|
: JSFRAME_COMPILING;
|
||
|
cx->fp = &frame;
|
||
|
ok = js_EmitFunctionBytecode(cx, cg, body);
|
||
|
cx->fp = fp;
|
||
|
if (!ok)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
if (!js_NewScriptFromCG(cx, cg, fun))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
JS_ASSERT(FUN_INTERPRETED(fun));
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/* A macro for inlining at the top of js_EmitTree (whence it came). */
|
||
|
#define UPDATE_LINE_NUMBER_NOTES(cx, cg, pn) \
|
||
|
JS_BEGIN_MACRO \
|
||
|
uintN line_ = (pn)->pn_pos.begin.lineno; \
|
||
|
uintN delta_ = line_ - CG_CURRENT_LINE(cg); \
|
||
|
if (delta_ != 0) { \
|
||
|
/* \
|
||
|
* Encode any change in the current source line number by using \
|
||
|
* either several SRC_NEWLINE notes or just one SRC_SETLINE note, \
|
||
|
* whichever consumes less space. \
|
||
|
* \
|
||
|
* NB: We handle backward line number deltas (possible with for \
|
||
|
* loops where the update part is emitted after the body, but its \
|
||
|
* line number is <= any line number in the body) here by letting \
|
||
|
* unsigned delta_ wrap to a very large number, which triggers a \
|
||
|
* SRC_SETLINE. \
|
||
|
*/ \
|
||
|
CG_CURRENT_LINE(cg) = line_; \
|
||
|
if (delta_ >= (uintN)(2 + ((line_ > SN_3BYTE_OFFSET_MASK)<<1))) { \
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_SETLINE, (ptrdiff_t)line_) < 0)\
|
||
|
return JS_FALSE; \
|
||
|
} else { \
|
||
|
do { \
|
||
|
if (js_NewSrcNote(cx, cg, SRC_NEWLINE) < 0) \
|
||
|
return JS_FALSE; \
|
||
|
} while (--delta_ != 0); \
|
||
|
} \
|
||
|
} \
|
||
|
JS_END_MACRO
|
||
|
|
||
|
/* A function, so that we avoid macro-bloating all the other callsites. */
|
||
|
static JSBool
|
||
|
UpdateLineNumberNotes(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
|
||
|
{
|
||
|
UPDATE_LINE_NUMBER_NOTES(cx, cg, pn);
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
MaybeEmitVarDecl(JSContext *cx, JSCodeGenerator *cg, JSOp prologOp,
|
||
|
JSParseNode *pn, jsatomid *result)
|
||
|
{
|
||
|
jsatomid atomIndex;
|
||
|
JSAtomListElement *ale;
|
||
|
|
||
|
if (pn->pn_slot >= 0) {
|
||
|
atomIndex = (jsatomid) pn->pn_slot;
|
||
|
} else {
|
||
|
ale = js_IndexAtom(cx, pn->pn_atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
atomIndex = ALE_INDEX(ale);
|
||
|
}
|
||
|
|
||
|
if ((js_CodeSpec[pn->pn_op].format & JOF_TYPEMASK) == JOF_CONST &&
|
||
|
(!(cg->treeContext.flags & TCF_IN_FUNCTION) ||
|
||
|
(cg->treeContext.flags & TCF_FUN_HEAVYWEIGHT))) {
|
||
|
/* Emit a prolog bytecode to predefine the variable. */
|
||
|
CG_SWITCH_TO_PROLOG(cg);
|
||
|
if (!UpdateLineNumberNotes(cx, cg, pn))
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP(prologOp, atomIndex);
|
||
|
CG_SWITCH_TO_MAIN(cg);
|
||
|
}
|
||
|
|
||
|
if (result)
|
||
|
*result = atomIndex;
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
|
||
|
typedef JSBool
|
||
|
(*DestructuringDeclEmitter)(JSContext *cx, JSCodeGenerator *cg, JSOp prologOp,
|
||
|
JSParseNode *pn);
|
||
|
|
||
|
static JSBool
|
||
|
EmitDestructuringDecl(JSContext *cx, JSCodeGenerator *cg, JSOp prologOp,
|
||
|
JSParseNode *pn)
|
||
|
{
|
||
|
JS_ASSERT(pn->pn_type == TOK_NAME);
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, pn, prologOp == JSOP_NOP))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
JS_ASSERT(pn->pn_op != JSOP_ARGUMENTS);
|
||
|
return MaybeEmitVarDecl(cx, cg, prologOp, pn, NULL);
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitDestructuringDecls(JSContext *cx, JSCodeGenerator *cg, JSOp prologOp,
|
||
|
JSParseNode *pn)
|
||
|
{
|
||
|
JSParseNode *pn2, *pn3;
|
||
|
DestructuringDeclEmitter emitter;
|
||
|
|
||
|
if (pn->pn_type == TOK_RB) {
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||
|
if (pn2->pn_type == TOK_COMMA)
|
||
|
continue;
|
||
|
emitter = (pn2->pn_type == TOK_NAME)
|
||
|
? EmitDestructuringDecl
|
||
|
: EmitDestructuringDecls;
|
||
|
if (!emitter(cx, cg, prologOp, pn2))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
} else {
|
||
|
JS_ASSERT(pn->pn_type == TOK_RC);
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||
|
pn3 = pn2->pn_right;
|
||
|
emitter = (pn3->pn_type == TOK_NAME)
|
||
|
? EmitDestructuringDecl
|
||
|
: EmitDestructuringDecls;
|
||
|
if (!emitter(cx, cg, prologOp, pn3))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitDestructuringOpsHelper(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn);
|
||
|
|
||
|
static JSBool
|
||
|
EmitDestructuringLHS(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn,
|
||
|
JSBool wantpop)
|
||
|
{
|
||
|
jsuint slot;
|
||
|
|
||
|
/* Skip any parenthesization. */
|
||
|
while (pn->pn_type == TOK_RP)
|
||
|
pn = pn->pn_kid;
|
||
|
|
||
|
/*
|
||
|
* Now emit the lvalue opcode sequence. If the lvalue is a nested
|
||
|
* destructuring initialiser-form, call ourselves to handle it, then
|
||
|
* pop the matched value. Otherwise emit an lvalue bytecode sequence
|
||
|
* ending with a JSOP_ENUMELEM or equivalent op.
|
||
|
*/
|
||
|
if (pn->pn_type == TOK_RB || pn->pn_type == TOK_RC) {
|
||
|
if (!EmitDestructuringOpsHelper(cx, cg, pn))
|
||
|
return JS_FALSE;
|
||
|
if (wantpop && js_Emit1(cx, cg, JSOP_POP) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (pn->pn_type == TOK_NAME &&
|
||
|
!BindNameToSlot(cx, &cg->treeContext, pn, JS_FALSE)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
switch (pn->pn_op) {
|
||
|
case JSOP_SETNAME:
|
||
|
/*
|
||
|
* NB: pn is a PN_NAME node, not a PN_BINARY. Nevertheless,
|
||
|
* we want to emit JSOP_ENUMELEM, which has format JOF_ELEM.
|
||
|
* So here and for JSOP_ENUMCONSTELEM, we use EmitElemOp.
|
||
|
*/
|
||
|
if (!EmitElemOp(cx, pn, JSOP_ENUMELEM, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case JSOP_SETCONST:
|
||
|
if (!EmitElemOp(cx, pn, JSOP_ENUMCONSTELEM, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case JSOP_SETLOCAL:
|
||
|
if (wantpop) {
|
||
|
slot = (jsuint) pn->pn_slot;
|
||
|
EMIT_UINT16_IMM_OP(JSOP_SETLOCALPOP, slot);
|
||
|
break;
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
|
||
|
case JSOP_SETARG:
|
||
|
case JSOP_SETVAR:
|
||
|
case JSOP_SETGVAR:
|
||
|
slot = (jsuint) pn->pn_slot;
|
||
|
EMIT_UINT16_IMM_OP(pn->pn_op, slot);
|
||
|
if (wantpop && js_Emit1(cx, cg, JSOP_POP) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
#if JS_HAS_LVALUE_RETURN || JS_HAS_XML_SUPPORT
|
||
|
{
|
||
|
ptrdiff_t top;
|
||
|
|
||
|
top = CG_OFFSET(cg);
|
||
|
if (!js_EmitTree(cx, cg, pn))
|
||
|
return JS_FALSE;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_ENUMELEM) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
case JSOP_ENUMELEM:
|
||
|
JS_ASSERT(0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Recursive helper for EmitDestructuringOps.
|
||
|
*
|
||
|
* Given a value to destructure on the stack, walk over an object or array
|
||
|
* initialiser at pn, emitting bytecodes to match property values and store
|
||
|
* them in the lvalues identified by the matched property names.
|
||
|
*/
|
||
|
static JSBool
|
||
|
EmitDestructuringOpsHelper(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
|
||
|
{
|
||
|
jsuint index;
|
||
|
JSParseNode *pn2, *pn3;
|
||
|
JSBool doElemOp;
|
||
|
|
||
|
#ifdef DEBUG
|
||
|
intN stackDepth = cg->stackDepth;
|
||
|
JS_ASSERT(stackDepth != 0);
|
||
|
JS_ASSERT(pn->pn_arity == PN_LIST);
|
||
|
JS_ASSERT(pn->pn_type == TOK_RB || pn->pn_type == TOK_RC);
|
||
|
#endif
|
||
|
|
||
|
if (pn->pn_count == 0) {
|
||
|
/* Emit a DUP;POP sequence for the decompiler. */
|
||
|
return js_Emit1(cx, cg, JSOP_DUP) >= 0 &&
|
||
|
js_Emit1(cx, cg, JSOP_POP) >= 0;
|
||
|
}
|
||
|
|
||
|
index = 0;
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||
|
/*
|
||
|
* Duplicate the value being destructured to use as a reference base.
|
||
|
*/
|
||
|
if (js_Emit1(cx, cg, JSOP_DUP) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Now push the property name currently being matched, which is either
|
||
|
* the array initialiser's current index, or the current property name
|
||
|
* "label" on the left of a colon in the object initialiser. Set pn3
|
||
|
* to the lvalue node, which is in the value-initializing position.
|
||
|
*/
|
||
|
doElemOp = JS_TRUE;
|
||
|
if (pn->pn_type == TOK_RB) {
|
||
|
if (!EmitNumberOp(cx, index, cg))
|
||
|
return JS_FALSE;
|
||
|
pn3 = pn2;
|
||
|
} else {
|
||
|
JS_ASSERT(pn->pn_type == TOK_RC);
|
||
|
JS_ASSERT(pn2->pn_type == TOK_COLON);
|
||
|
pn3 = pn2->pn_left;
|
||
|
if (pn3->pn_type == TOK_NUMBER) {
|
||
|
/*
|
||
|
* If we are emitting an object destructuring initialiser,
|
||
|
* annotate the index op with SRC_INITPROP so we know we are
|
||
|
* not decompiling an array initialiser.
|
||
|
*/
|
||
|
if (js_NewSrcNote(cx, cg, SRC_INITPROP) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (!EmitNumberOp(cx, pn3->pn_dval, cg))
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
JS_ASSERT(pn3->pn_type == TOK_STRING ||
|
||
|
pn3->pn_type == TOK_NAME);
|
||
|
if (!EmitAtomOp(cx, pn3, JSOP_GETPROP, cg))
|
||
|
return JS_FALSE;
|
||
|
doElemOp = JS_FALSE;
|
||
|
}
|
||
|
pn3 = pn2->pn_right;
|
||
|
}
|
||
|
|
||
|
if (doElemOp) {
|
||
|
/*
|
||
|
* Ok, get the value of the matching property name. This leaves
|
||
|
* that value on top of the value being destructured, so the stack
|
||
|
* is one deeper than when we started.
|
||
|
*/
|
||
|
if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
|
||
|
return JS_FALSE;
|
||
|
JS_ASSERT(cg->stackDepth == stackDepth + 1);
|
||
|
}
|
||
|
|
||
|
/* Nullary comma node makes a hole in the array destructurer. */
|
||
|
if (pn3->pn_type == TOK_COMMA && pn3->pn_arity == PN_NULLARY) {
|
||
|
JS_ASSERT(pn->pn_type == TOK_RB);
|
||
|
JS_ASSERT(pn2 == pn3);
|
||
|
if (js_Emit1(cx, cg, JSOP_POP) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (!EmitDestructuringLHS(cx, cg, pn3, JS_TRUE))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
JS_ASSERT(cg->stackDepth == stackDepth);
|
||
|
++index;
|
||
|
}
|
||
|
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
static ptrdiff_t
|
||
|
OpToDeclType(JSOp op)
|
||
|
{
|
||
|
switch (op) {
|
||
|
case JSOP_NOP:
|
||
|
return SRC_DECL_LET;
|
||
|
case JSOP_DEFCONST:
|
||
|
return SRC_DECL_CONST;
|
||
|
case JSOP_DEFVAR:
|
||
|
return SRC_DECL_VAR;
|
||
|
default:
|
||
|
return SRC_DECL_NONE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitDestructuringOps(JSContext *cx, JSCodeGenerator *cg, JSOp declOp,
|
||
|
JSParseNode *pn)
|
||
|
{
|
||
|
/*
|
||
|
* If we're called from a variable declaration, help the decompiler by
|
||
|
* annotating the first JSOP_DUP that EmitDestructuringOpsHelper emits.
|
||
|
* If the destructuring initialiser is empty, our helper will emit a
|
||
|
* JSOP_DUP followed by a JSOP_POP for the decompiler.
|
||
|
*/
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_DESTRUCT, OpToDeclType(declOp)) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Call our recursive helper to emit the destructuring assignments and
|
||
|
* related stack manipulations.
|
||
|
*/
|
||
|
return EmitDestructuringOpsHelper(cx, cg, pn);
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
EmitGroupAssignment(JSContext *cx, JSCodeGenerator *cg, JSOp declOp,
|
||
|
JSParseNode *lhs, JSParseNode *rhs)
|
||
|
{
|
||
|
jsuint depth, limit, slot;
|
||
|
JSParseNode *pn;
|
||
|
|
||
|
depth = limit = (uintN) cg->stackDepth;
|
||
|
for (pn = rhs->pn_head; pn; pn = pn->pn_next) {
|
||
|
if (limit == JS_BIT(16)) {
|
||
|
js_ReportCompileErrorNumber(cx, rhs,
|
||
|
JSREPORT_PN | JSREPORT_ERROR,
|
||
|
JSMSG_ARRAY_INIT_TOO_BIG);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (pn->pn_type == TOK_COMMA) {
|
||
|
if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
JS_ASSERT(pn->pn_type != TOK_DEFSHARP);
|
||
|
if (!js_EmitTree(cx, cg, pn))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
++limit;
|
||
|
}
|
||
|
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_GROUPASSIGN, OpToDeclType(declOp)) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
slot = depth;
|
||
|
for (pn = lhs->pn_head; pn; pn = pn->pn_next) {
|
||
|
if (slot < limit) {
|
||
|
EMIT_UINT16_IMM_OP(JSOP_GETLOCAL, slot);
|
||
|
} else {
|
||
|
if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (pn->pn_type == TOK_COMMA && pn->pn_arity == PN_NULLARY) {
|
||
|
if (js_Emit1(cx, cg, JSOP_POP) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (!EmitDestructuringLHS(cx, cg, pn, pn->pn_next != NULL))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
++slot;
|
||
|
}
|
||
|
|
||
|
EMIT_UINT16_IMM_OP(JSOP_SETSP, (jsatomid)depth);
|
||
|
cg->stackDepth = (uintN) depth;
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Helper called with pop out param initialized to a JSOP_POP* opcode. If we
|
||
|
* can emit a group assignment sequence, which results in 0 stack depth delta,
|
||
|
* we set *pop to JSOP_NOP so callers can veto emitting pn followed by a pop.
|
||
|
*/
|
||
|
static JSBool
|
||
|
MaybeEmitGroupAssignment(JSContext *cx, JSCodeGenerator *cg, JSOp declOp,
|
||
|
JSParseNode *pn, JSOp *pop)
|
||
|
{
|
||
|
JSParseNode *lhs, *rhs;
|
||
|
|
||
|
JS_ASSERT(pn->pn_type == TOK_ASSIGN);
|
||
|
JS_ASSERT(*pop == JSOP_POP || *pop == JSOP_POPV);
|
||
|
lhs = pn->pn_left;
|
||
|
rhs = pn->pn_right;
|
||
|
if (lhs->pn_type == TOK_RB && rhs->pn_type == TOK_RB &&
|
||
|
lhs->pn_count <= rhs->pn_count &&
|
||
|
(rhs->pn_count == 0 ||
|
||
|
rhs->pn_head->pn_type != TOK_DEFSHARP)) {
|
||
|
if (!EmitGroupAssignment(cx, cg, declOp, lhs, rhs))
|
||
|
return JS_FALSE;
|
||
|
*pop = JSOP_NOP;
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
#endif /* JS_HAS_DESTRUCTURING */
|
||
|
|
||
|
static JSBool
|
||
|
EmitVariables(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn,
|
||
|
JSBool inLetHead, ptrdiff_t *headNoteIndex)
|
||
|
{
|
||
|
JSTreeContext *tc;
|
||
|
JSBool let, forInVar;
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
JSBool forInLet, popScope;
|
||
|
JSStmtInfo *stmt, *scopeStmt;
|
||
|
#endif
|
||
|
ptrdiff_t off, noteIndex, tmp;
|
||
|
JSParseNode *pn2, *pn3;
|
||
|
JSOp op;
|
||
|
jsatomid atomIndex;
|
||
|
uintN oldflags;
|
||
|
|
||
|
/* Default in case of JS_HAS_BLOCK_SCOPE early return, below. */
|
||
|
*headNoteIndex = -1;
|
||
|
|
||
|
/*
|
||
|
* Let blocks and expressions have a parenthesized head in which the new
|
||
|
* scope is not yet open. Initializer evaluation uses the parent node's
|
||
|
* lexical scope. If popScope is true below, then we hide the top lexical
|
||
|
* block from any calls to BindNameToSlot hiding in pn2->pn_expr so that
|
||
|
* it won't find any names in the new let block.
|
||
|
*
|
||
|
* The same goes for let declarations in the head of any kind of for loop.
|
||
|
* Unlike a let declaration 'let x = i' within a block, where x is hoisted
|
||
|
* to the start of the block, a 'for (let x = i...) ...' loop evaluates i
|
||
|
* in the containing scope, and puts x in the loop body's scope.
|
||
|
*/
|
||
|
tc = &cg->treeContext;
|
||
|
let = (pn->pn_op == JSOP_NOP);
|
||
|
forInVar = (pn->pn_extra & PNX_FORINVAR) != 0;
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
forInLet = let && forInVar;
|
||
|
popScope = (inLetHead || (let && (tc->flags & TCF_IN_FOR_INIT)));
|
||
|
JS_ASSERT(!popScope || let);
|
||
|
#endif
|
||
|
|
||
|
off = noteIndex = -1;
|
||
|
for (pn2 = pn->pn_head; ; pn2 = pn2->pn_next) {
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
if (pn2->pn_type != TOK_NAME) {
|
||
|
if (pn2->pn_type == TOK_RB || pn2->pn_type == TOK_RC) {
|
||
|
/*
|
||
|
* Emit variable binding ops, but not destructuring ops.
|
||
|
* The parser (see Variables, jsparse.c) has ensured that
|
||
|
* our caller will be the TOK_FOR/TOK_IN case in js_EmitTree,
|
||
|
* and that case will emit the destructuring code only after
|
||
|
* emitting an enumerating opcode and a branch that tests
|
||
|
* whether the enumeration ended.
|
||
|
*/
|
||
|
JS_ASSERT(forInVar);
|
||
|
JS_ASSERT(pn->pn_count == 1);
|
||
|
if (!EmitDestructuringDecls(cx, cg, pn->pn_op, pn2))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* A destructuring initialiser assignment preceded by var is
|
||
|
* always evaluated promptly, even if it is to the left of 'in'
|
||
|
* in a for-in loop. As with 'for (var x = i in o)...', this
|
||
|
* will cause the entire 'var [a, b] = i' to be hoisted out of
|
||
|
* the head of the loop.
|
||
|
*/
|
||
|
JS_ASSERT(pn2->pn_type == TOK_ASSIGN);
|
||
|
if (pn->pn_count == 1 && !forInLet) {
|
||
|
/*
|
||
|
* If this is the only destructuring assignment in the list,
|
||
|
* try to optimize to a group assignment. If we're in a let
|
||
|
* head, pass JSOP_POP rather than the pseudo-prolog JSOP_NOP
|
||
|
* in pn->pn_op, to suppress a second (and misplaced) 'let'.
|
||
|
*/
|
||
|
JS_ASSERT(noteIndex < 0 && !pn2->pn_next);
|
||
|
op = JSOP_POP;
|
||
|
if (!MaybeEmitGroupAssignment(cx, cg,
|
||
|
inLetHead ? JSOP_POP : pn->pn_op,
|
||
|
pn2, &op)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (op == JSOP_NOP) {
|
||
|
pn->pn_extra = (pn->pn_extra & ~PNX_POPVAR) | PNX_GROUPINIT;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pn3 = pn2->pn_left;
|
||
|
if (!EmitDestructuringDecls(cx, cg, pn->pn_op, pn3))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
/*
|
||
|
* If this is a 'for (let [x, y] = i in o) ...' let declaration,
|
||
|
* throw away i if it is a useless expression.
|
||
|
*/
|
||
|
if (forInLet) {
|
||
|
JSBool useful = JS_FALSE;
|
||
|
|
||
|
JS_ASSERT(pn->pn_count == 1);
|
||
|
if (!CheckSideEffects(cx, tc, pn2->pn_right, &useful))
|
||
|
return JS_FALSE;
|
||
|
if (!useful)
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_right))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
/*
|
||
|
* The expression i in 'for (let [x, y] = i in o) ...', which is
|
||
|
* pn2->pn_right above, appears to have side effects. We've just
|
||
|
* emitted code to evaluate i, but we must not destructure i yet.
|
||
|
* Let the TOK_FOR: code in js_EmitTree do the destructuring to
|
||
|
* emit the right combination of source notes and bytecode for the
|
||
|
* decompiler.
|
||
|
*
|
||
|
* This has the effect of hoisting the evaluation of i out of the
|
||
|
* for-in loop, without hoisting the let variables, which must of
|
||
|
* course be scoped by the loop. Set PNX_POPVAR to cause JSOP_POP
|
||
|
* to be emitted, just before returning from this function.
|
||
|
*/
|
||
|
if (forInVar) {
|
||
|
pn->pn_extra |= PNX_POPVAR;
|
||
|
if (forInLet)
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Veto pn->pn_op if inLetHead to avoid emitting a SRC_DESTRUCT
|
||
|
* that's redundant with respect to the SRC_DECL/SRC_DECL_LET that
|
||
|
* we will emit at the bottom of this function.
|
||
|
*/
|
||
|
if (!EmitDestructuringOps(cx, cg,
|
||
|
inLetHead ? JSOP_POP : pn->pn_op,
|
||
|
pn3)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
goto emit_note_pop;
|
||
|
}
|
||
|
#else
|
||
|
JS_ASSERT(pn2->pn_type == TOK_NAME);
|
||
|
#endif
|
||
|
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, pn2, let))
|
||
|
return JS_FALSE;
|
||
|
JS_ASSERT(pn2->pn_slot >= 0 || !let);
|
||
|
|
||
|
op = pn2->pn_op;
|
||
|
if (op == JSOP_ARGUMENTS) {
|
||
|
/* JSOP_ARGUMENTS => no initializer */
|
||
|
JS_ASSERT(!pn2->pn_expr && !let);
|
||
|
pn3 = NULL;
|
||
|
#ifdef __GNUC__
|
||
|
atomIndex = 0; /* quell GCC overwarning */
|
||
|
#endif
|
||
|
} else {
|
||
|
if (!MaybeEmitVarDecl(cx, cg, pn->pn_op, pn2, &atomIndex))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
pn3 = pn2->pn_expr;
|
||
|
if (pn3) {
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
/*
|
||
|
* If this is a 'for (let x = i in o) ...' let declaration,
|
||
|
* throw away i if it is a useless expression.
|
||
|
*/
|
||
|
if (forInLet) {
|
||
|
JSBool useful = JS_FALSE;
|
||
|
|
||
|
JS_ASSERT(pn->pn_count == 1);
|
||
|
if (!CheckSideEffects(cx, tc, pn3, &useful))
|
||
|
return JS_FALSE;
|
||
|
if (!useful)
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
if (op == JSOP_SETNAME) {
|
||
|
JS_ASSERT(!let);
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_BINDNAME, atomIndex);
|
||
|
}
|
||
|
if (pn->pn_op == JSOP_DEFCONST &&
|
||
|
!js_DefineCompileTimeConstant(cx, cg, pn2->pn_atom,
|
||
|
pn3)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
/* Evaluate expr in the outer lexical scope if requested. */
|
||
|
if (popScope) {
|
||
|
stmt = tc->topStmt;
|
||
|
scopeStmt = tc->topScopeStmt;
|
||
|
|
||
|
tc->topStmt = stmt->down;
|
||
|
tc->topScopeStmt = scopeStmt->downScope;
|
||
|
}
|
||
|
#ifdef __GNUC__
|
||
|
else {
|
||
|
stmt = scopeStmt = NULL; /* quell GCC overwarning */
|
||
|
}
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
oldflags = cg->treeContext.flags;
|
||
|
cg->treeContext.flags &= ~TCF_IN_FOR_INIT;
|
||
|
if (!js_EmitTree(cx, cg, pn3))
|
||
|
return JS_FALSE;
|
||
|
cg->treeContext.flags |= oldflags & TCF_IN_FOR_INIT;
|
||
|
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
if (popScope) {
|
||
|
tc->topStmt = stmt;
|
||
|
tc->topScopeStmt = scopeStmt;
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* 'for (var x in o) ...' and 'for (var x = i in o) ...' call the
|
||
|
* TOK_VAR case, but only the initialized case (a strange one that
|
||
|
* falls out of ECMA-262's grammar) wants to run past this point.
|
||
|
* Both cases must conditionally emit a JSOP_DEFVAR, above. Note
|
||
|
* that the parser error-checks to ensure that pn->pn_count is 1.
|
||
|
*
|
||
|
* 'for (let x = i in o) ...' must evaluate i before the loop, and
|
||
|
* subject it to useless expression elimination. The variable list
|
||
|
* in pn is a single let declaration if pn_op == JSOP_NOP. We test
|
||
|
* the let local in order to break early in this case, as well as in
|
||
|
* the 'for (var x in o)' case.
|
||
|
*
|
||
|
* XXX Narcissus keeps track of variable declarations in the node
|
||
|
* for the script being compiled, so there's no need to share any
|
||
|
* conditional prolog code generation there. We could do likewise,
|
||
|
* but it's a big change, requiring extra allocation, so probably
|
||
|
* not worth the trouble for SpiderMonkey.
|
||
|
*/
|
||
|
JS_ASSERT(pn3 == pn2->pn_expr);
|
||
|
if (forInVar && (!pn3 || let)) {
|
||
|
JS_ASSERT(pn->pn_count == 1);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (pn2 == pn->pn_head &&
|
||
|
!inLetHead &&
|
||
|
js_NewSrcNote2(cx, cg, SRC_DECL,
|
||
|
(pn->pn_op == JSOP_DEFCONST)
|
||
|
? SRC_DECL_CONST
|
||
|
: (pn->pn_op == JSOP_DEFVAR)
|
||
|
? SRC_DECL_VAR
|
||
|
: SRC_DECL_LET) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (op == JSOP_ARGUMENTS) {
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else if (pn2->pn_slot >= 0) {
|
||
|
EMIT_UINT16_IMM_OP(op, atomIndex);
|
||
|
} else {
|
||
|
EMIT_ATOM_INDEX_OP(op, atomIndex);
|
||
|
}
|
||
|
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
emit_note_pop:
|
||
|
#endif
|
||
|
tmp = CG_OFFSET(cg);
|
||
|
if (noteIndex >= 0) {
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, tmp-off))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!pn2->pn_next)
|
||
|
break;
|
||
|
off = tmp;
|
||
|
noteIndex = js_NewSrcNote2(cx, cg, SRC_PCDELTA, 0);
|
||
|
if (noteIndex < 0 || js_Emit1(cx, cg, JSOP_POP) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* If this is a let head, emit and return a srcnote on the pop. */
|
||
|
if (inLetHead) {
|
||
|
*headNoteIndex = js_NewSrcNote(cx, cg, SRC_DECL);
|
||
|
if (*headNoteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
if (!(pn->pn_extra & PNX_POPVAR))
|
||
|
return js_Emit1(cx, cg, JSOP_NOP) >= 0;
|
||
|
}
|
||
|
|
||
|
return !(pn->pn_extra & PNX_POPVAR) || js_Emit1(cx, cg, JSOP_POP) >= 0;
|
||
|
}
|
||
|
|
||
|
#if defined DEBUG_brendan || defined DEBUG_mrbkap
|
||
|
static JSBool
|
||
|
GettableNoteForNextOp(JSCodeGenerator *cg)
|
||
|
{
|
||
|
ptrdiff_t offset, target;
|
||
|
jssrcnote *sn, *end;
|
||
|
|
||
|
offset = 0;
|
||
|
target = CG_OFFSET(cg);
|
||
|
for (sn = CG_NOTES(cg), end = sn + CG_NOTE_COUNT(cg); sn < end;
|
||
|
sn = SN_NEXT(sn)) {
|
||
|
if (offset == target && SN_IS_GETTABLE(sn))
|
||
|
return JS_TRUE;
|
||
|
offset += SN_DELTA(sn);
|
||
|
}
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
JSBool
|
||
|
js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
|
||
|
{
|
||
|
JSBool ok, useful, wantval;
|
||
|
JSStmtInfo *stmt, stmtInfo;
|
||
|
ptrdiff_t top, off, tmp, beq, jmp;
|
||
|
JSParseNode *pn2, *pn3;
|
||
|
JSAtom *atom;
|
||
|
JSAtomListElement *ale;
|
||
|
jsatomid atomIndex;
|
||
|
ptrdiff_t noteIndex;
|
||
|
JSSrcNoteType noteType;
|
||
|
jsbytecode *pc;
|
||
|
JSOp op;
|
||
|
JSTokenType type;
|
||
|
uint32 argc;
|
||
|
int stackDummy;
|
||
|
|
||
|
if (!JS_CHECK_STACK_SIZE(cx, stackDummy)) {
|
||
|
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_OVER_RECURSED);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
ok = JS_TRUE;
|
||
|
cg->emitLevel++;
|
||
|
pn->pn_offset = top = CG_OFFSET(cg);
|
||
|
|
||
|
/* Emit notes to tell the current bytecode's source line number. */
|
||
|
UPDATE_LINE_NUMBER_NOTES(cx, cg, pn);
|
||
|
|
||
|
switch (pn->pn_type) {
|
||
|
case TOK_FUNCTION:
|
||
|
{
|
||
|
void *cg2mark;
|
||
|
JSCodeGenerator *cg2;
|
||
|
JSFunction *fun;
|
||
|
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
if (pn->pn_arity == PN_NULLARY) {
|
||
|
if (js_Emit1(cx, cg, JSOP_GETFUNNS) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Generate code for the function's body. */
|
||
|
cg2mark = JS_ARENA_MARK(cg->codePool);
|
||
|
JS_ARENA_ALLOCATE_TYPE(cg2, JSCodeGenerator, cg->codePool);
|
||
|
if (!cg2) {
|
||
|
JS_ReportOutOfMemory(cx);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!js_InitCodeGenerator(cx, cg2, cg->codePool, cg->notePool,
|
||
|
cg->filename, pn->pn_pos.begin.lineno,
|
||
|
cg->principals)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
cg2->treeContext.flags = (uint16) (pn->pn_flags | TCF_IN_FUNCTION);
|
||
|
cg2->treeContext.tryCount = pn->pn_tryCount;
|
||
|
cg2->parent = cg;
|
||
|
fun = (JSFunction *) JS_GetPrivate(cx, ATOM_TO_OBJECT(pn->pn_funAtom));
|
||
|
if (!js_EmitFunctionBody(cx, cg2, pn->pn_body, fun))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* We need an activation object if an inner peeks out, or if such
|
||
|
* inner-peeking caused one of our inners to become heavyweight.
|
||
|
*/
|
||
|
if (cg2->treeContext.flags &
|
||
|
(TCF_FUN_USES_NONLOCALS | TCF_FUN_HEAVYWEIGHT)) {
|
||
|
cg->treeContext.flags |= TCF_FUN_HEAVYWEIGHT;
|
||
|
}
|
||
|
js_FinishCodeGenerator(cx, cg2);
|
||
|
JS_ARENA_RELEASE(cg->codePool, cg2mark);
|
||
|
|
||
|
/* Make the function object a literal in the outer script's pool. */
|
||
|
ale = js_IndexAtom(cx, pn->pn_funAtom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
atomIndex = ALE_INDEX(ale);
|
||
|
|
||
|
/* Emit a bytecode pointing to the closure object in its immediate. */
|
||
|
if (pn->pn_op != JSOP_NOP) {
|
||
|
EMIT_ATOM_INDEX_OP(pn->pn_op, atomIndex);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* Top-level named functions need a nop for decompilation. */
|
||
|
noteIndex = js_NewSrcNote2(cx, cg, SRC_FUNCDEF, (ptrdiff_t)atomIndex);
|
||
|
if (noteIndex < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_NOP) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Top-levels also need a prolog op to predefine their names in the
|
||
|
* variable object, or if local, to fill their stack slots.
|
||
|
*/
|
||
|
CG_SWITCH_TO_PROLOG(cg);
|
||
|
|
||
|
if (cg->treeContext.flags & TCF_IN_FUNCTION) {
|
||
|
JSObject *obj, *pobj;
|
||
|
JSProperty *prop;
|
||
|
JSScopeProperty *sprop;
|
||
|
uintN slot;
|
||
|
|
||
|
obj = OBJ_GET_PARENT(cx, fun->object);
|
||
|
if (!js_LookupHiddenProperty(cx, obj, ATOM_TO_JSID(fun->atom),
|
||
|
&pobj, &prop)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
JS_ASSERT(prop && pobj == obj);
|
||
|
sprop = (JSScopeProperty *) prop;
|
||
|
JS_ASSERT(sprop->getter == js_GetLocalVariable);
|
||
|
slot = sprop->shortid;
|
||
|
OBJ_DROP_PROPERTY(cx, pobj, prop);
|
||
|
|
||
|
/*
|
||
|
* If this local function is declared in a body block induced by
|
||
|
* let declarations, reparent fun->object to the compiler-created
|
||
|
* body block object so that JSOP_DEFLOCALFUN can clone that block
|
||
|
* into the runtime scope chain.
|
||
|
*/
|
||
|
stmt = cg->treeContext.topStmt;
|
||
|
if (stmt && stmt->type == STMT_BLOCK &&
|
||
|
stmt->down && stmt->down->type == STMT_BLOCK &&
|
||
|
(stmt->down->flags & SIF_SCOPE)) {
|
||
|
obj = ATOM_TO_OBJECT(stmt->down->atom);
|
||
|
JS_ASSERT(LOCKED_OBJ_GET_CLASS(obj) == &js_BlockClass);
|
||
|
OBJ_SET_PARENT(cx, fun->object, obj);
|
||
|
}
|
||
|
|
||
|
if (atomIndex >= JS_BIT(16)) {
|
||
|
/*
|
||
|
* Lots of literals in the outer function, so we have to emit
|
||
|
* [JSOP_LITOPX, atomIndex, JSOP_DEFLOCALFUN, var slot].
|
||
|
*/
|
||
|
off = js_EmitN(cx, cg, JSOP_LITOPX, 3);
|
||
|
if (off < 0)
|
||
|
return JS_FALSE;
|
||
|
pc = CG_CODE(cg, off);
|
||
|
SET_LITERAL_INDEX(pc, atomIndex);
|
||
|
EMIT_UINT16_IMM_OP(JSOP_DEFLOCALFUN, slot);
|
||
|
} else {
|
||
|
/* Emit [JSOP_DEFLOCALFUN, var slot, atomIndex]. */
|
||
|
off = js_EmitN(cx, cg, JSOP_DEFLOCALFUN,
|
||
|
VARNO_LEN + ATOM_INDEX_LEN);
|
||
|
if (off < 0)
|
||
|
return JS_FALSE;
|
||
|
pc = CG_CODE(cg, off);
|
||
|
SET_VARNO(pc, slot);
|
||
|
pc += VARNO_LEN;
|
||
|
SET_ATOM_INDEX(pc, atomIndex);
|
||
|
}
|
||
|
} else {
|
||
|
JS_ASSERT(!cg->treeContext.topStmt);
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_DEFFUN, atomIndex);
|
||
|
}
|
||
|
|
||
|
CG_SWITCH_TO_MAIN(cg);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
#if JS_HAS_EXPORT_IMPORT
|
||
|
case TOK_EXPORT:
|
||
|
pn2 = pn->pn_head;
|
||
|
if (pn2->pn_type == TOK_STAR) {
|
||
|
/*
|
||
|
* 'export *' must have no other elements in the list (what would
|
||
|
* be the point?).
|
||
|
*/
|
||
|
if (js_Emit1(cx, cg, JSOP_EXPORTALL) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
/*
|
||
|
* If not 'export *', the list consists of NAME nodes identifying
|
||
|
* properties of the variables object to flag as exported.
|
||
|
*/
|
||
|
do {
|
||
|
ale = js_IndexAtom(cx, pn2->pn_atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_EXPORTNAME, ALE_INDEX(ale));
|
||
|
} while ((pn2 = pn2->pn_next) != NULL);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_IMPORT:
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||
|
/*
|
||
|
* Each subtree on an import list is rooted by a DOT or LB node.
|
||
|
* A DOT may have a null pn_atom member, in which case pn_op must
|
||
|
* be JSOP_IMPORTALL -- see EmitPropOp above.
|
||
|
*/
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
break;
|
||
|
#endif /* JS_HAS_EXPORT_IMPORT */
|
||
|
|
||
|
case TOK_IF:
|
||
|
/* Initialize so we can detect else-if chains and avoid recursion. */
|
||
|
stmtInfo.type = STMT_IF;
|
||
|
beq = jmp = -1;
|
||
|
noteIndex = -1;
|
||
|
|
||
|
if_again:
|
||
|
/* Emit code for the condition before pushing stmtInfo. */
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid1))
|
||
|
return JS_FALSE;
|
||
|
top = CG_OFFSET(cg);
|
||
|
if (stmtInfo.type == STMT_IF) {
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_IF, top);
|
||
|
} else {
|
||
|
/*
|
||
|
* We came here from the goto further below that detects else-if
|
||
|
* chains, so we must mutate stmtInfo back into a STMT_IF record.
|
||
|
* Also (see below for why) we need a note offset for SRC_IF_ELSE
|
||
|
* to help the decompiler. Actually, we need two offsets, one for
|
||
|
* decompiling any else clause and the second for decompiling an
|
||
|
* else-if chain without bracing, overindenting, or incorrectly
|
||
|
* scoping let declarations.
|
||
|
*/
|
||
|
JS_ASSERT(stmtInfo.type == STMT_ELSE);
|
||
|
stmtInfo.type = STMT_IF;
|
||
|
stmtInfo.update = top;
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 0, jmp - beq))
|
||
|
return JS_FALSE;
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 1, top - jmp))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Emit an annotated branch-if-false around the then part. */
|
||
|
pn3 = pn->pn_kid3;
|
||
|
noteIndex = js_NewSrcNote(cx, cg, pn3 ? SRC_IF_ELSE : SRC_IF);
|
||
|
if (noteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
beq = EmitJump(cx, cg, JSOP_IFEQ, 0);
|
||
|
if (beq < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Emit code for the then and optional else parts. */
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid2))
|
||
|
return JS_FALSE;
|
||
|
if (pn3) {
|
||
|
/* Modify stmtInfo so we know we're in the else part. */
|
||
|
stmtInfo.type = STMT_ELSE;
|
||
|
|
||
|
/*
|
||
|
* Emit a JSOP_BACKPATCH op to jump from the end of our then part
|
||
|
* around the else part. The js_PopStatementCG call at the bottom
|
||
|
* of this switch case will fix up the backpatch chain linked from
|
||
|
* stmtInfo.breaks.
|
||
|
*/
|
||
|
jmp = EmitGoto(cx, cg, &stmtInfo, &stmtInfo.breaks, NULL, SRC_NULL);
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Ensure the branch-if-false comes here, then emit the else. */
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
|
||
|
if (pn3->pn_type == TOK_IF) {
|
||
|
pn = pn3;
|
||
|
goto if_again;
|
||
|
}
|
||
|
|
||
|
if (!js_EmitTree(cx, cg, pn3))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Annotate SRC_IF_ELSE with the offset from branch to jump, for
|
||
|
* the decompiler's benefit. We can't just "back up" from the pc
|
||
|
* of the else clause, because we don't know whether an extended
|
||
|
* jump was required to leap from the end of the then clause over
|
||
|
* the else clause.
|
||
|
*/
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 0, jmp - beq))
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
/* No else part, fixup the branch-if-false to come here. */
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
|
||
|
}
|
||
|
ok = js_PopStatementCG(cx, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_SWITCH:
|
||
|
/* Out of line to avoid bloating js_EmitTree's stack frame size. */
|
||
|
ok = EmitSwitch(cx, cg, pn, &stmtInfo);
|
||
|
break;
|
||
|
|
||
|
case TOK_WHILE:
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_WHILE_LOOP, top);
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_left))
|
||
|
return JS_FALSE;
|
||
|
noteIndex = js_NewSrcNote(cx, cg, SRC_WHILE);
|
||
|
if (noteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
beq = EmitJump(cx, cg, JSOP_IFEQ, 0);
|
||
|
if (beq < 0)
|
||
|
return JS_FALSE;
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_right))
|
||
|
return JS_FALSE;
|
||
|
jmp = EmitJump(cx, cg, JSOP_GOTO, top - CG_OFFSET(cg));
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 0, jmp - beq))
|
||
|
return JS_FALSE;
|
||
|
ok = js_PopStatementCG(cx, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_DO:
|
||
|
/* Emit an annotated nop so we know to decompile a 'do' keyword. */
|
||
|
if (js_NewSrcNote(cx, cg, SRC_WHILE) < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_NOP) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Compile the loop body. */
|
||
|
top = CG_OFFSET(cg);
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_DO_LOOP, top);
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_left))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Set loop and enclosing label update offsets, for continue. */
|
||
|
stmt = &stmtInfo;
|
||
|
do {
|
||
|
stmt->update = CG_OFFSET(cg);
|
||
|
} while ((stmt = stmt->down) != NULL && stmt->type == STMT_LABEL);
|
||
|
|
||
|
/* Compile the loop condition, now that continues know where to go. */
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_right))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* No source note needed, because JSOP_IFNE is used only for do-while.
|
||
|
* If we ever use JSOP_IFNE for other purposes, we can still avoid yet
|
||
|
* another note here, by storing (jmp - top) in the SRC_WHILE note's
|
||
|
* offset, and fetching that delta in order to decompile recursively.
|
||
|
*/
|
||
|
if (EmitJump(cx, cg, JSOP_IFNE, top - CG_OFFSET(cg)) < 0)
|
||
|
return JS_FALSE;
|
||
|
ok = js_PopStatementCG(cx, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_FOR:
|
||
|
beq = 0; /* suppress gcc warnings */
|
||
|
pn2 = pn->pn_left;
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_FOR_LOOP, top);
|
||
|
|
||
|
if (pn2->pn_type == TOK_IN) {
|
||
|
JSBool emitIFEQ;
|
||
|
|
||
|
/* Set stmtInfo type for later testing. */
|
||
|
stmtInfo.type = STMT_FOR_IN_LOOP;
|
||
|
noteIndex = -1;
|
||
|
|
||
|
/*
|
||
|
* If the left part is 'var x', emit code to define x if necessary
|
||
|
* using a prolog opcode, but do not emit a pop. If the left part
|
||
|
* is 'var x = i', emit prolog code to define x if necessary; then
|
||
|
* emit code to evaluate i, assign the result to x, and pop the
|
||
|
* result off the stack.
|
||
|
*
|
||
|
* All the logic to do this is implemented in the outer switch's
|
||
|
* TOK_VAR case, conditioned on pn_extra flags set by the parser.
|
||
|
*
|
||
|
* In the 'for (var x = i in o) ...' case, the js_EmitTree(...pn3)
|
||
|
* called here will generate the proper note for the assignment
|
||
|
* op that sets x = i, hoisting the initialized var declaration
|
||
|
* out of the loop: 'var x = i; for (x in o) ...'.
|
||
|
*
|
||
|
* In the 'for (var x in o) ...' case, nothing but the prolog op
|
||
|
* (if needed) should be generated here, we must emit the note
|
||
|
* just before the JSOP_FOR* opcode in the switch on pn3->pn_type
|
||
|
* a bit below, so nothing is hoisted: 'for (var x in o) ...'.
|
||
|
*
|
||
|
* A 'for (let x = i in o)' loop must not be hoisted, since in
|
||
|
* this form the let variable is scoped by the loop body (but not
|
||
|
* the head). The initializer expression i must be evaluated for
|
||
|
* any side effects. So we hoist only i in the let case.
|
||
|
*/
|
||
|
pn3 = pn2->pn_left;
|
||
|
type = pn3->pn_type;
|
||
|
cg->treeContext.flags |= TCF_IN_FOR_INIT;
|
||
|
if (TOKEN_TYPE_IS_DECL(type) && !js_EmitTree(cx, cg, pn3))
|
||
|
return JS_FALSE;
|
||
|
cg->treeContext.flags &= ~TCF_IN_FOR_INIT;
|
||
|
|
||
|
/* Compile the object expression to the right of 'in'. */
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_right))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Emit a bytecode to convert top of stack value to the iterator
|
||
|
* object depending on the loop variant (for-in, for-each-in, or
|
||
|
* destructuring for-in).
|
||
|
*/
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
JS_ASSERT(pn->pn_op == JSOP_FORIN ||
|
||
|
pn->pn_op == JSOP_FOREACHKEYVAL ||
|
||
|
pn->pn_op == JSOP_FOREACH);
|
||
|
#else
|
||
|
JS_ASSERT(pn->pn_op == JSOP_FORIN || pn->pn_op == JSOP_FOREACH);
|
||
|
#endif
|
||
|
if (js_Emit1(cx, cg, pn->pn_op) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
top = CG_OFFSET(cg);
|
||
|
SET_STATEMENT_TOP(&stmtInfo, top);
|
||
|
|
||
|
/*
|
||
|
* Compile a JSOP_FOR* bytecode based on the left hand side.
|
||
|
*
|
||
|
* Initialize op to JSOP_SETNAME in case of |for ([a, b] in o)...|
|
||
|
* or similar, to signify assignment, rather than declaration, to
|
||
|
* the decompiler. EmitDestructuringOps takes a prolog bytecode
|
||
|
* parameter and emits the appropriate source note, defaulting to
|
||
|
* assignment, so JSOP_SETNAME is not critical here; many similar
|
||
|
* ops could be used -- just not JSOP_NOP (which means 'let').
|
||
|
*/
|
||
|
emitIFEQ = JS_TRUE;
|
||
|
op = JSOP_SETNAME;
|
||
|
switch (type) {
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
case TOK_LET:
|
||
|
#endif
|
||
|
case TOK_VAR:
|
||
|
JS_ASSERT(pn3->pn_arity == PN_LIST && pn3->pn_count == 1);
|
||
|
pn3 = pn3->pn_head;
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
if (pn3->pn_type == TOK_ASSIGN) {
|
||
|
pn3 = pn3->pn_left;
|
||
|
JS_ASSERT(pn3->pn_type == TOK_RB || pn3->pn_type == TOK_RC);
|
||
|
}
|
||
|
if (pn3->pn_type == TOK_RB || pn3->pn_type == TOK_RC) {
|
||
|
op = pn2->pn_left->pn_op;
|
||
|
goto destructuring_for;
|
||
|
}
|
||
|
#else
|
||
|
JS_ASSERT(pn3->pn_type == TOK_NAME);
|
||
|
#endif
|
||
|
/*
|
||
|
* Always annotate JSOP_FORLOCAL if given input of the form
|
||
|
* 'for (let x in * o)' -- the decompiler must not hoist the
|
||
|
* 'let x' out of the loop head, or x will be bound in the
|
||
|
* wrong scope. Likewise, but in this case only for the sake
|
||
|
* of higher decompilation fidelity only, do not hoist 'var x'
|
||
|
* when given 'for (var x in o)'. But 'for (var x = i in o)'
|
||
|
* requires hoisting in order to preserve the initializer i.
|
||
|
* The decompiler can only handle so much!
|
||
|
*/
|
||
|
if ((
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
type == TOK_LET ||
|
||
|
#endif
|
||
|
!pn3->pn_expr) &&
|
||
|
js_NewSrcNote2(cx, cg, SRC_DECL,
|
||
|
type == TOK_VAR
|
||
|
? SRC_DECL_VAR
|
||
|
: SRC_DECL_LET) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
case TOK_NAME:
|
||
|
if (pn3->pn_slot >= 0) {
|
||
|
op = pn3->pn_op;
|
||
|
switch (op) {
|
||
|
case JSOP_GETARG: /* FALL THROUGH */
|
||
|
case JSOP_SETARG: op = JSOP_FORARG; break;
|
||
|
case JSOP_GETVAR: /* FALL THROUGH */
|
||
|
case JSOP_SETVAR: op = JSOP_FORVAR; break;
|
||
|
case JSOP_GETGVAR: /* FALL THROUGH */
|
||
|
case JSOP_SETGVAR: op = JSOP_FORNAME; break;
|
||
|
case JSOP_GETLOCAL: /* FALL THROUGH */
|
||
|
case JSOP_SETLOCAL: op = JSOP_FORLOCAL; break;
|
||
|
default: JS_ASSERT(0);
|
||
|
}
|
||
|
} else {
|
||
|
pn3->pn_op = JSOP_FORNAME;
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, pn3, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
op = pn3->pn_op;
|
||
|
}
|
||
|
if (pn3->pn_slot >= 0) {
|
||
|
if (pn3->pn_attrs & JSPROP_READONLY) {
|
||
|
JS_ASSERT(op == JSOP_FORVAR);
|
||
|
op = JSOP_FORCONST;
|
||
|
}
|
||
|
atomIndex = (jsatomid) pn3->pn_slot;
|
||
|
EMIT_UINT16_IMM_OP(op, atomIndex);
|
||
|
} else {
|
||
|
if (!EmitAtomOp(cx, pn3, op, cg))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_DOT:
|
||
|
useful = JS_FALSE;
|
||
|
if (!CheckSideEffects(cx, &cg->treeContext, pn3->pn_expr,
|
||
|
&useful)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!useful) {
|
||
|
if (!EmitPropOp(cx, pn3, JSOP_FORPROP, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
case TOK_RB:
|
||
|
case TOK_RC:
|
||
|
destructuring_for:
|
||
|
#endif
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_UNARYOP:
|
||
|
#endif
|
||
|
#if JS_HAS_LVALUE_RETURN
|
||
|
case TOK_LP:
|
||
|
#endif
|
||
|
case TOK_LB:
|
||
|
/*
|
||
|
* We separate the first/next bytecode from the enumerator
|
||
|
* variable binding to avoid any side-effects in the index
|
||
|
* expression (e.g., for (x[i++] in {}) should not bind x[i]
|
||
|
* or increment i at all).
|
||
|
*/
|
||
|
emitIFEQ = JS_FALSE;
|
||
|
if (!js_Emit1(cx, cg, JSOP_FORELEM))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Emit a SRC_WHILE note with offset telling the distance to
|
||
|
* the loop-closing jump (we can't reckon from the branch at
|
||
|
* the top of the loop, because the loop-closing jump might
|
||
|
* need to be an extended jump, independent of whether the
|
||
|
* branch is short or long).
|
||
|
*/
|
||
|
noteIndex = js_NewSrcNote(cx, cg, SRC_WHILE);
|
||
|
if (noteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
beq = EmitJump(cx, cg, JSOP_IFEQ, 0);
|
||
|
if (beq < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
if (pn3->pn_type == TOK_RB || pn3->pn_type == TOK_RC) {
|
||
|
if (!EmitDestructuringOps(cx, cg, op, pn3))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_POP) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
#if JS_HAS_LVALUE_RETURN
|
||
|
if (pn3->pn_type == TOK_LP) {
|
||
|
JS_ASSERT(pn3->pn_op == JSOP_SETCALL);
|
||
|
if (!js_EmitTree(cx, cg, pn3))
|
||
|
return JS_FALSE;
|
||
|
if (!js_Emit1(cx, cg, JSOP_ENUMELEM))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
if (pn3->pn_type == TOK_UNARYOP) {
|
||
|
JS_ASSERT(pn3->pn_op == JSOP_BINDXMLNAME);
|
||
|
if (!js_EmitTree(cx, cg, pn3))
|
||
|
return JS_FALSE;
|
||
|
if (!js_Emit1(cx, cg, JSOP_ENUMELEM))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/* Now that we're safely past the IFEQ, commit side effects. */
|
||
|
if (!EmitElemOp(cx, pn3, JSOP_ENUMELEM, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
JS_ASSERT(0);
|
||
|
}
|
||
|
|
||
|
if (emitIFEQ) {
|
||
|
/* Annotate so the decompiler can find the loop-closing jump. */
|
||
|
noteIndex = js_NewSrcNote(cx, cg, SRC_WHILE);
|
||
|
if (noteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Pop and test the loop condition generated by JSOP_FOR*. */
|
||
|
beq = EmitJump(cx, cg, JSOP_IFEQ, 0);
|
||
|
if (beq < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
} else {
|
||
|
op = JSOP_POP;
|
||
|
if (!pn2->pn_kid1) {
|
||
|
/* No initializer: emit an annotated nop for the decompiler. */
|
||
|
op = JSOP_NOP;
|
||
|
} else {
|
||
|
cg->treeContext.flags |= TCF_IN_FOR_INIT;
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
pn3 = pn2->pn_kid1;
|
||
|
if (pn3->pn_type == TOK_ASSIGN &&
|
||
|
!MaybeEmitGroupAssignment(cx, cg, op, pn3, &op)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
#endif
|
||
|
if (op == JSOP_POP) {
|
||
|
if (!js_EmitTree(cx, cg, pn3))
|
||
|
return JS_FALSE;
|
||
|
if (TOKEN_TYPE_IS_DECL(pn3->pn_type)) {
|
||
|
/*
|
||
|
* Check whether a destructuring-initialized var decl
|
||
|
* was optimized to a group assignment. If so, we do
|
||
|
* not need to emit a pop below, so switch to a nop,
|
||
|
* just for the decompiler.
|
||
|
*/
|
||
|
JS_ASSERT(pn3->pn_arity == PN_LIST);
|
||
|
if (pn3->pn_extra & PNX_GROUPINIT)
|
||
|
op = JSOP_NOP;
|
||
|
}
|
||
|
}
|
||
|
cg->treeContext.flags &= ~TCF_IN_FOR_INIT;
|
||
|
}
|
||
|
noteIndex = js_NewSrcNote(cx, cg, SRC_FOR);
|
||
|
if (noteIndex < 0 ||
|
||
|
js_Emit1(cx, cg, op) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
top = CG_OFFSET(cg);
|
||
|
SET_STATEMENT_TOP(&stmtInfo, top);
|
||
|
if (!pn2->pn_kid2) {
|
||
|
/* No loop condition: flag this fact in the source notes. */
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, 0))
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_kid2))
|
||
|
return JS_FALSE;
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
|
||
|
CG_OFFSET(cg) - top)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
beq = EmitJump(cx, cg, JSOP_IFEQ, 0);
|
||
|
if (beq < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Set pn3 (used below) here to avoid spurious gcc warnings. */
|
||
|
pn3 = pn2->pn_kid3;
|
||
|
}
|
||
|
|
||
|
/* Emit code for the loop body. */
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_right))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
if (pn2->pn_type != TOK_IN) {
|
||
|
/* Set the second note offset so we can find the update part. */
|
||
|
JS_ASSERT(noteIndex != -1);
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 1,
|
||
|
CG_OFFSET(cg) - top)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (pn3) {
|
||
|
/* Set loop and enclosing "update" offsets, for continue. */
|
||
|
stmt = &stmtInfo;
|
||
|
do {
|
||
|
stmt->update = CG_OFFSET(cg);
|
||
|
} while ((stmt = stmt->down) != NULL &&
|
||
|
stmt->type == STMT_LABEL);
|
||
|
|
||
|
op = JSOP_POP;
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
if (pn3->pn_type == TOK_ASSIGN &&
|
||
|
!MaybeEmitGroupAssignment(cx, cg, op, pn3, &op)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
#endif
|
||
|
if (op == JSOP_POP) {
|
||
|
if (!js_EmitTree(cx, cg, pn3))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Restore the absolute line number for source note readers. */
|
||
|
off = (ptrdiff_t) pn->pn_pos.end.lineno;
|
||
|
if (CG_CURRENT_LINE(cg) != (uintN) off) {
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_SETLINE, off) < 0)
|
||
|
return JS_FALSE;
|
||
|
CG_CURRENT_LINE(cg) = (uintN) off;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* The third note offset helps us find the loop-closing jump. */
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 2,
|
||
|
CG_OFFSET(cg) - top)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Emit the loop-closing jump and fixup all jump offsets. */
|
||
|
jmp = EmitJump(cx, cg, JSOP_GOTO, top - CG_OFFSET(cg));
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
if (beq > 0)
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
|
||
|
if (pn2->pn_type == TOK_IN) {
|
||
|
/* Set the SRC_WHILE note offset so we can find the closing jump. */
|
||
|
JS_ASSERT(noteIndex != -1);
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, jmp - beq))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Now fixup all breaks and continues (before for/in's JSOP_ENDITER). */
|
||
|
if (!js_PopStatementCG(cx, cg))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
if (pn2->pn_type == TOK_IN) {
|
||
|
if (js_Emit1(cx, cg, JSOP_ENDITER) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_BREAK:
|
||
|
stmt = cg->treeContext.topStmt;
|
||
|
atom = pn->pn_atom;
|
||
|
if (atom) {
|
||
|
ale = js_IndexAtom(cx, atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
while (stmt->type != STMT_LABEL || stmt->atom != atom)
|
||
|
stmt = stmt->down;
|
||
|
noteType = SRC_BREAK2LABEL;
|
||
|
} else {
|
||
|
ale = NULL;
|
||
|
while (!STMT_IS_LOOP(stmt) && stmt->type != STMT_SWITCH)
|
||
|
stmt = stmt->down;
|
||
|
noteType = SRC_NULL;
|
||
|
}
|
||
|
|
||
|
if (EmitGoto(cx, cg, stmt, &stmt->breaks, ale, noteType) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case TOK_CONTINUE:
|
||
|
stmt = cg->treeContext.topStmt;
|
||
|
atom = pn->pn_atom;
|
||
|
if (atom) {
|
||
|
/* Find the loop statement enclosed by the matching label. */
|
||
|
JSStmtInfo *loop = NULL;
|
||
|
ale = js_IndexAtom(cx, atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
while (stmt->type != STMT_LABEL || stmt->atom != atom) {
|
||
|
if (STMT_IS_LOOP(stmt))
|
||
|
loop = stmt;
|
||
|
stmt = stmt->down;
|
||
|
}
|
||
|
stmt = loop;
|
||
|
noteType = SRC_CONT2LABEL;
|
||
|
} else {
|
||
|
ale = NULL;
|
||
|
while (!STMT_IS_LOOP(stmt))
|
||
|
stmt = stmt->down;
|
||
|
noteType = SRC_CONTINUE;
|
||
|
}
|
||
|
|
||
|
if (EmitGoto(cx, cg, stmt, &stmt->continues, ale, noteType) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case TOK_WITH:
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_left))
|
||
|
return JS_FALSE;
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_WITH, CG_OFFSET(cg));
|
||
|
if (js_Emit1(cx, cg, JSOP_ENTERWITH) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_right))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0)
|
||
|
return JS_FALSE;
|
||
|
ok = js_PopStatementCG(cx, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_TRY:
|
||
|
{
|
||
|
ptrdiff_t start, end, catchJump, catchStart, finallyCatch;
|
||
|
intN depth;
|
||
|
JSParseNode *lastCatch;
|
||
|
|
||
|
catchJump = catchStart = finallyCatch = -1;
|
||
|
|
||
|
/*
|
||
|
* Push stmtInfo to track jumps-over-catches and gosubs-to-finally
|
||
|
* for later fixup.
|
||
|
*
|
||
|
* When a finally block is 'active' (STMT_FINALLY on the treeContext),
|
||
|
* non-local jumps (including jumps-over-catches) result in a GOSUB
|
||
|
* being written into the bytecode stream and fixed-up later (c.f.
|
||
|
* EmitBackPatchOp and BackPatch).
|
||
|
*/
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo,
|
||
|
pn->pn_kid3 ? STMT_FINALLY : STMT_TRY,
|
||
|
CG_OFFSET(cg));
|
||
|
|
||
|
/*
|
||
|
* About JSOP_SETSP: an exception can be thrown while the stack is in
|
||
|
* an unbalanced state, and this imbalance causes problems with things
|
||
|
* like function invocation later on.
|
||
|
*
|
||
|
* To fix this, we compute the 'balanced' stack depth upon try entry,
|
||
|
* and then restore the stack to this depth when we hit the first catch
|
||
|
* or finally block. We can't just zero the stack, because things like
|
||
|
* for/in and with that are active upon entry to the block keep state
|
||
|
* variables on the stack.
|
||
|
*/
|
||
|
depth = cg->stackDepth;
|
||
|
|
||
|
/* Mark try location for decompilation, then emit try block. */
|
||
|
if (js_Emit1(cx, cg, JSOP_TRY) < 0)
|
||
|
return JS_FALSE;
|
||
|
start = CG_OFFSET(cg);
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid1))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* GOSUB to finally, if present. */
|
||
|
if (pn->pn_kid3) {
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &GOSUBS(stmtInfo));
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* JSOP_RETSUB pops the return pc-index, balancing the stack. */
|
||
|
cg->stackDepth = depth;
|
||
|
}
|
||
|
|
||
|
/* Emit (hidden) jump over catch and/or finally. */
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &catchJump);
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
end = CG_OFFSET(cg);
|
||
|
|
||
|
/* If this try has a catch block, emit it. */
|
||
|
pn2 = pn->pn_kid2;
|
||
|
lastCatch = NULL;
|
||
|
if (pn2) {
|
||
|
jsint count = 0; /* previous catch block's population */
|
||
|
|
||
|
catchStart = end;
|
||
|
|
||
|
/*
|
||
|
* The emitted code for a catch block looks like:
|
||
|
*
|
||
|
* [throwing] only if 2nd+ catch block
|
||
|
* [leaveblock] only if 2nd+ catch block
|
||
|
* enterblock with SRC_CATCH
|
||
|
* exception
|
||
|
* [dup] only if catchguard
|
||
|
* setlocalpop <slot> or destructuring code
|
||
|
* [< catchguard code >] if there's a catchguard
|
||
|
* [ifeq <offset to next catch block>] " "
|
||
|
* [pop] only if catchguard
|
||
|
* < catch block contents >
|
||
|
* leaveblock
|
||
|
* goto <end of catch blocks> non-local; finally applies
|
||
|
*
|
||
|
* If there's no catch block without a catchguard, the last
|
||
|
* <offset to next catch block> points to rethrow code. This
|
||
|
* code will [gosub] to the finally code if appropriate, and is
|
||
|
* also used for the catch-all trynote for capturing exceptions
|
||
|
* thrown from catch{} blocks.
|
||
|
*/
|
||
|
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
|
||
|
ptrdiff_t guardJump, catchNote;
|
||
|
|
||
|
guardJump = GUARDJUMP(stmtInfo);
|
||
|
if (guardJump == -1) {
|
||
|
/* Set stack to original depth (see SETSP comment above). */
|
||
|
EMIT_UINT16_IMM_OP(JSOP_SETSP, (jsatomid)depth);
|
||
|
cg->stackDepth = depth;
|
||
|
} else {
|
||
|
/* Fix up and clean up previous catch block. */
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, guardJump);
|
||
|
|
||
|
/*
|
||
|
* Account for the pushed exception object that we still
|
||
|
* have after the jumping from the previous guard.
|
||
|
*/
|
||
|
JS_ASSERT(cg->stackDepth == depth);
|
||
|
cg->stackDepth = depth + 1;
|
||
|
|
||
|
/*
|
||
|
* Move exception back to cx->exception to prepare for
|
||
|
* the next catch. We hide [throwing] from the decompiler
|
||
|
* since it compensates for the hidden JSOP_DUP at the
|
||
|
* start of the previous guarded catch.
|
||
|
*/
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_THROWING) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Emit an unbalanced [leaveblock] for the previous catch,
|
||
|
* whose block object count is saved below.
|
||
|
*/
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
JS_ASSERT(count >= 0);
|
||
|
EMIT_UINT16_IMM_OP(JSOP_LEAVEBLOCK, count);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Annotate the JSOP_ENTERBLOCK that's about to be generated
|
||
|
* by the call to js_EmitTree immediately below. Save this
|
||
|
* source note's index in stmtInfo for use by the TOK_CATCH:
|
||
|
* case, where the length of the catch guard is set as the
|
||
|
* note's offset.
|
||
|
*/
|
||
|
catchNote = js_NewSrcNote2(cx, cg, SRC_CATCH, 0);
|
||
|
if (catchNote < 0)
|
||
|
return JS_FALSE;
|
||
|
CATCHNOTE(stmtInfo) = catchNote;
|
||
|
|
||
|
/*
|
||
|
* Emit the lexical scope and catch body. Save the catch's
|
||
|
* block object population via count, for use when targeting
|
||
|
* guardJump at the next catch (the guard mismatch case).
|
||
|
*/
|
||
|
JS_ASSERT(pn3->pn_type == TOK_LEXICALSCOPE);
|
||
|
count = OBJ_BLOCK_COUNT(cx, ATOM_TO_OBJECT(pn3->pn_atom));
|
||
|
if (!js_EmitTree(cx, cg, pn3))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* gosub <finally>, if required */
|
||
|
if (pn->pn_kid3) {
|
||
|
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH,
|
||
|
&GOSUBS(stmtInfo));
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
JS_ASSERT(cg->stackDepth == depth);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Jump over the remaining catch blocks. This will get fixed
|
||
|
* up to jump to after catch/finally.
|
||
|
*/
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
|
||
|
return JS_FALSE;
|
||
|
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH, &catchJump);
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Save a pointer to the last catch node to handle try-finally
|
||
|
* and try-catch(guard)-finally special cases.
|
||
|
*/
|
||
|
lastCatch = pn3->pn_expr;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Last catch guard jumps to the rethrow code sequence if none of the
|
||
|
* guards match. Target guardJump at the beginning of the rethrow
|
||
|
* sequence, just in case a guard expression throws and leaves the
|
||
|
* stack unbalanced.
|
||
|
*/
|
||
|
if (lastCatch && lastCatch->pn_kid2) {
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, GUARDJUMP(stmtInfo));
|
||
|
|
||
|
/* Sync the stack to take into account pushed exception. */
|
||
|
JS_ASSERT(cg->stackDepth == depth);
|
||
|
cg->stackDepth = depth + 1;
|
||
|
|
||
|
/*
|
||
|
* Rethrow the exception, delegating executing of finally if any
|
||
|
* to the exception handler.
|
||
|
*/
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_THROW) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
JS_ASSERT(cg->stackDepth == depth);
|
||
|
|
||
|
/* Emit finally handler if any. */
|
||
|
if (pn->pn_kid3) {
|
||
|
/*
|
||
|
* We emit [setsp][gosub] to call try-finally when an exception is
|
||
|
* thrown from try or try-catch blocks. The [gosub] and [retsub]
|
||
|
* opcodes will take care of stacking and rethrowing any exception
|
||
|
* pending across the finally.
|
||
|
*/
|
||
|
finallyCatch = CG_OFFSET(cg);
|
||
|
EMIT_UINT16_IMM_OP(JSOP_SETSP, (jsatomid)depth);
|
||
|
|
||
|
jmp = EmitBackPatchOp(cx, cg, JSOP_BACKPATCH,
|
||
|
&GOSUBS(stmtInfo));
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
JS_ASSERT(cg->stackDepth == depth);
|
||
|
JS_ASSERT((uintN)depth <= cg->maxStackDepth);
|
||
|
|
||
|
/*
|
||
|
* Fix up the gosubs that might have been emitted before non-local
|
||
|
* jumps to the finally code.
|
||
|
*/
|
||
|
if (!BackPatch(cx, cg, GOSUBS(stmtInfo), CG_NEXT(cg), JSOP_GOSUB))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* The stack budget must be balanced at this point. All [gosub]
|
||
|
* calls emitted before this point will push two stack slots, one
|
||
|
* for the pending exception (or JSVAL_HOLE if there is no pending
|
||
|
* exception) and one for the [retsub] pc-index.
|
||
|
*/
|
||
|
JS_ASSERT(cg->stackDepth == depth);
|
||
|
cg->stackDepth += 2;
|
||
|
if ((uintN)cg->stackDepth > cg->maxStackDepth)
|
||
|
cg->maxStackDepth = cg->stackDepth;
|
||
|
|
||
|
/* Now indicate that we're emitting a subroutine body. */
|
||
|
stmtInfo.type = STMT_SUBROUTINE;
|
||
|
if (!UpdateLineNumberNotes(cx, cg, pn->pn_kid3))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_FINALLY) < 0 ||
|
||
|
!js_EmitTree(cx, cg, pn->pn_kid3) ||
|
||
|
js_Emit1(cx, cg, JSOP_RETSUB) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Restore stack depth budget to its balanced state. */
|
||
|
JS_ASSERT(cg->stackDepth == depth + 2);
|
||
|
cg->stackDepth = depth;
|
||
|
}
|
||
|
if (!js_PopStatementCG(cx, cg))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
if (js_NewSrcNote(cx, cg, SRC_ENDBRACE) < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_NOP) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Fix up the end-of-try/catch jumps to come here. */
|
||
|
if (!BackPatch(cx, cg, catchJump, CG_NEXT(cg), JSOP_GOTO))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Add the try note last, to let post-order give us the right ordering
|
||
|
* (first to last for a given nesting level, inner to outer by level).
|
||
|
*/
|
||
|
if (pn->pn_kid2) {
|
||
|
JS_ASSERT(end != -1 && catchStart != -1);
|
||
|
if (!js_NewTryNote(cx, cg, start, end, catchStart))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* If we've got a finally, mark try+catch region with additional
|
||
|
* trynote to catch exceptions (re)thrown from a catch block or
|
||
|
* for the try{}finally{} case.
|
||
|
*/
|
||
|
if (pn->pn_kid3) {
|
||
|
JS_ASSERT(finallyCatch != -1);
|
||
|
if (!js_NewTryNote(cx, cg, start, finallyCatch, finallyCatch))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TOK_CATCH:
|
||
|
{
|
||
|
ptrdiff_t catchStart, guardJump;
|
||
|
|
||
|
/*
|
||
|
* Morph STMT_BLOCK to STMT_CATCH, note the block entry code offset,
|
||
|
* and save the block object atom.
|
||
|
*/
|
||
|
stmt = cg->treeContext.topStmt;
|
||
|
JS_ASSERT(stmt->type == STMT_BLOCK && (stmt->flags & SIF_SCOPE));
|
||
|
stmt->type = STMT_CATCH;
|
||
|
catchStart = stmt->update;
|
||
|
atom = stmt->atom;
|
||
|
|
||
|
/* Go up one statement info record to the TRY or FINALLY record. */
|
||
|
stmt = stmt->down;
|
||
|
JS_ASSERT(stmt->type == STMT_TRY || stmt->type == STMT_FINALLY);
|
||
|
|
||
|
/* Pick up the pending exception and bind it to the catch variable. */
|
||
|
if (js_Emit1(cx, cg, JSOP_EXCEPTION) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Dup the exception object if there is a guard for rethrowing to use
|
||
|
* it later when rethrowing or in other catches.
|
||
|
*/
|
||
|
if (pn->pn_kid2) {
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_DUP) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pn2 = pn->pn_kid1;
|
||
|
switch (pn2->pn_type) {
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
case TOK_RB:
|
||
|
case TOK_RC:
|
||
|
if (!EmitDestructuringOps(cx, cg, JSOP_NOP, pn2))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_POP) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
case TOK_NAME:
|
||
|
/* Inline BindNameToSlot, adding block depth to pn2->pn_slot. */
|
||
|
pn2->pn_slot += OBJ_BLOCK_DEPTH(cx, ATOM_TO_OBJECT(atom));
|
||
|
EMIT_UINT16_IMM_OP(JSOP_SETLOCALPOP, pn2->pn_slot);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
JS_ASSERT(0);
|
||
|
}
|
||
|
|
||
|
/* Emit the guard expression, if there is one. */
|
||
|
if (pn->pn_kid2) {
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid2))
|
||
|
return JS_FALSE;
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, CATCHNOTE(*stmt), 0,
|
||
|
CG_OFFSET(cg) - catchStart)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
/* ifeq <next block> */
|
||
|
guardJump = EmitJump(cx, cg, JSOP_IFEQ, 0);
|
||
|
if (guardJump < 0)
|
||
|
return JS_FALSE;
|
||
|
GUARDJUMP(*stmt) = guardJump;
|
||
|
|
||
|
/* Pop duplicated exception object as we no longer need it. */
|
||
|
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_POP) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Emit the catch body. */
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid3))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Annotate the JSOP_LEAVEBLOCK that will be emitted as we unwind via
|
||
|
* our TOK_LEXICALSCOPE parent, so the decompiler knows to pop.
|
||
|
*/
|
||
|
off = cg->stackDepth;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_CATCH, off) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TOK_VAR:
|
||
|
if (!EmitVariables(cx, cg, pn, JS_FALSE, ¬eIndex))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case TOK_RETURN:
|
||
|
/* Push a return value */
|
||
|
pn2 = pn->pn_kid;
|
||
|
if (pn2) {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* EmitNonLocalJumpFixup mutates op to JSOP_RETRVAL after emitting a
|
||
|
* JSOP_SETRVAL if there are open try blocks having finally clauses.
|
||
|
* We can't simply transfer control flow to our caller in that case,
|
||
|
* because we must gosub to those clauses from inner to outer, with
|
||
|
* the correct stack pointer (i.e., after popping any with, for/in,
|
||
|
* etc., slots nested inside the finally's try).
|
||
|
*/
|
||
|
op = JSOP_RETURN;
|
||
|
if (!EmitNonLocalJumpFixup(cx, cg, NULL, &op))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
#if JS_HAS_GENERATORS
|
||
|
case TOK_YIELD:
|
||
|
if (pn->pn_kid) {
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid))
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (js_Emit1(cx, cg, JSOP_YIELD) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
case TOK_LC:
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
if (pn->pn_arity == PN_UNARY) {
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, pn->pn_op) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
JS_ASSERT(pn->pn_arity == PN_LIST);
|
||
|
|
||
|
noteIndex = -1;
|
||
|
tmp = CG_OFFSET(cg);
|
||
|
if (pn->pn_extra & PNX_NEEDBRACES) {
|
||
|
noteIndex = js_NewSrcNote2(cx, cg, SRC_BRACE, 0);
|
||
|
if (noteIndex < 0 || js_Emit1(cx, cg, JSOP_NOP) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_BLOCK, top);
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (noteIndex >= 0 &&
|
||
|
!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
|
||
|
CG_OFFSET(cg) - tmp)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
ok = js_PopStatementCG(cx, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_BODY:
|
||
|
JS_ASSERT(pn->pn_arity == PN_LIST);
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_BODY, top);
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
ok = js_PopStatementCG(cx, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_SEMI:
|
||
|
pn2 = pn->pn_kid;
|
||
|
if (pn2) {
|
||
|
/*
|
||
|
* Top-level or called-from-a-native JS_Execute/EvaluateScript,
|
||
|
* debugger, and eval frames may need the value of the ultimate
|
||
|
* expression statement as the script's result, despite the fact
|
||
|
* that it appears useless to the compiler.
|
||
|
*/
|
||
|
useful = wantval = !cx->fp->fun ||
|
||
|
!FUN_INTERPRETED(cx->fp->fun) ||
|
||
|
(cx->fp->flags & JSFRAME_SPECIAL);
|
||
|
if (!useful) {
|
||
|
if (!CheckSideEffects(cx, &cg->treeContext, pn2, &useful))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Don't eliminate apparently useless expressions if they are
|
||
|
* labeled expression statements. The tc->topStmt->update test
|
||
|
* catches the case where we are nesting in js_EmitTree for a
|
||
|
* labeled compound statement.
|
||
|
*/
|
||
|
if (!useful &&
|
||
|
(!cg->treeContext.topStmt ||
|
||
|
cg->treeContext.topStmt->type != STMT_LABEL ||
|
||
|
cg->treeContext.topStmt->update < CG_OFFSET(cg))) {
|
||
|
CG_CURRENT_LINE(cg) = pn2->pn_pos.begin.lineno;
|
||
|
if (!js_ReportCompileErrorNumber(cx, cg,
|
||
|
JSREPORT_CG |
|
||
|
JSREPORT_WARNING |
|
||
|
JSREPORT_STRICT,
|
||
|
JSMSG_USELESS_EXPR)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
} else {
|
||
|
op = wantval ? JSOP_POPV : JSOP_POP;
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
if (!wantval &&
|
||
|
pn2->pn_type == TOK_ASSIGN &&
|
||
|
!MaybeEmitGroupAssignment(cx, cg, op, pn2, &op)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
#endif
|
||
|
if (op != JSOP_NOP) {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_COLON:
|
||
|
/* Emit an annotated nop so we know to decompile a label. */
|
||
|
atom = pn->pn_atom;
|
||
|
ale = js_IndexAtom(cx, atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
pn2 = pn->pn_expr;
|
||
|
noteType = (pn2->pn_type == TOK_LC ||
|
||
|
(pn2->pn_type == TOK_LEXICALSCOPE &&
|
||
|
pn2->pn_expr->pn_type == TOK_LC))
|
||
|
? SRC_LABELBRACE
|
||
|
: SRC_LABEL;
|
||
|
noteIndex = js_NewSrcNote2(cx, cg, noteType,
|
||
|
(ptrdiff_t) ALE_INDEX(ale));
|
||
|
if (noteIndex < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_NOP) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Emit code for the labeled statement. */
|
||
|
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_LABEL,
|
||
|
CG_OFFSET(cg));
|
||
|
stmtInfo.atom = atom;
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
if (!js_PopStatementCG(cx, cg))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* If the statement was compound, emit a note for the end brace. */
|
||
|
if (noteType == SRC_LABELBRACE) {
|
||
|
if (js_NewSrcNote(cx, cg, SRC_ENDBRACE) < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_NOP) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_COMMA:
|
||
|
/*
|
||
|
* Emit SRC_PCDELTA notes on each JSOP_POP between comma operands.
|
||
|
* These notes help the decompiler bracket the bytecodes generated
|
||
|
* from each sub-expression that follows a comma.
|
||
|
*/
|
||
|
off = noteIndex = -1;
|
||
|
for (pn2 = pn->pn_head; ; pn2 = pn2->pn_next) {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
tmp = CG_OFFSET(cg);
|
||
|
if (noteIndex >= 0) {
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, tmp-off))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!pn2->pn_next)
|
||
|
break;
|
||
|
off = tmp;
|
||
|
noteIndex = js_NewSrcNote2(cx, cg, SRC_PCDELTA, 0);
|
||
|
if (noteIndex < 0 ||
|
||
|
js_Emit1(cx, cg, JSOP_POP) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_ASSIGN:
|
||
|
/*
|
||
|
* Check left operand type and generate specialized code for it.
|
||
|
* Specialize to avoid ECMA "reference type" values on the operand
|
||
|
* stack, which impose pervasive runtime "GetValue" costs.
|
||
|
*/
|
||
|
pn2 = pn->pn_left;
|
||
|
JS_ASSERT(pn2->pn_type != TOK_RP);
|
||
|
atomIndex = (jsatomid) -1;
|
||
|
switch (pn2->pn_type) {
|
||
|
case TOK_NAME:
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, pn2, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
if (pn2->pn_slot >= 0) {
|
||
|
atomIndex = (jsatomid) pn2->pn_slot;
|
||
|
} else {
|
||
|
ale = js_IndexAtom(cx, pn2->pn_atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
atomIndex = ALE_INDEX(ale);
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_BINDNAME, atomIndex);
|
||
|
}
|
||
|
break;
|
||
|
case TOK_DOT:
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_expr))
|
||
|
return JS_FALSE;
|
||
|
ale = js_IndexAtom(cx, pn2->pn_atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
atomIndex = ALE_INDEX(ale);
|
||
|
break;
|
||
|
case TOK_LB:
|
||
|
JS_ASSERT(pn2->pn_arity == PN_BINARY);
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_left))
|
||
|
return JS_FALSE;
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_right))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
case TOK_RB:
|
||
|
case TOK_RC:
|
||
|
break;
|
||
|
#endif
|
||
|
#if JS_HAS_LVALUE_RETURN
|
||
|
case TOK_LP:
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_UNARYOP:
|
||
|
JS_ASSERT(pn2->pn_op == JSOP_SETXMLNAME);
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_kid))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_BINDXMLNAME) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
default:
|
||
|
JS_ASSERT(0);
|
||
|
}
|
||
|
|
||
|
op = pn->pn_op;
|
||
|
#if JS_HAS_GETTER_SETTER
|
||
|
if (op == JSOP_GETTER || op == JSOP_SETTER) {
|
||
|
/* We'll emit these prefix bytecodes after emitting the r.h.s. */
|
||
|
if (atomIndex != (jsatomid) -1 && atomIndex >= JS_BIT(16)) {
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (pn2->pn_type == TOK_NAME && pn2->pn_op != JSOP_SETNAME) {
|
||
|
/*
|
||
|
* x getter = y where x is a local or let variable is not
|
||
|
* supported.
|
||
|
*/
|
||
|
js_ReportCompileErrorNumber(cx,
|
||
|
pn2, JSREPORT_PN | JSREPORT_ERROR,
|
||
|
JSMSG_BAD_GETTER_OR_SETTER,
|
||
|
(op == JSOP_GETTER)
|
||
|
? js_getter_str
|
||
|
: js_setter_str);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
} else
|
||
|
#endif
|
||
|
/* If += or similar, dup the left operand and get its value. */
|
||
|
if (op != JSOP_NOP) {
|
||
|
switch (pn2->pn_type) {
|
||
|
case TOK_NAME:
|
||
|
if (pn2->pn_op != JSOP_SETNAME) {
|
||
|
EMIT_UINT16_IMM_OP((pn2->pn_op == JSOP_SETGVAR)
|
||
|
? JSOP_GETGVAR
|
||
|
: (pn2->pn_op == JSOP_SETARG)
|
||
|
? JSOP_GETARG
|
||
|
: (pn2->pn_op == JSOP_SETLOCAL)
|
||
|
? JSOP_GETLOCAL
|
||
|
: JSOP_GETVAR,
|
||
|
atomIndex);
|
||
|
break;
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
case TOK_DOT:
|
||
|
if (js_Emit1(cx, cg, JSOP_DUP) < 0)
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP((pn2->pn_type == TOK_NAME)
|
||
|
? JSOP_GETXPROP
|
||
|
: JSOP_GETPROP,
|
||
|
atomIndex);
|
||
|
break;
|
||
|
case TOK_LB:
|
||
|
#if JS_HAS_LVALUE_RETURN
|
||
|
case TOK_LP:
|
||
|
#endif
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_UNARYOP:
|
||
|
#endif
|
||
|
if (js_Emit1(cx, cg, JSOP_DUP2) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_GETELEM) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
default:;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Now emit the right operand (it may affect the namespace). */
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_right))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* If += etc., emit the binary operator with a decompiler note. */
|
||
|
if (op != JSOP_NOP) {
|
||
|
/*
|
||
|
* Take care to avoid SRC_ASSIGNOP if the left-hand side is a
|
||
|
* const declared in a function (i.e., with non-negative pn_slot
|
||
|
* and JSPROP_READONLY in pn_attrs), as in this case (just a bit
|
||
|
* further below) we will avoid emitting the assignment op.
|
||
|
*/
|
||
|
if (pn2->pn_type != TOK_NAME ||
|
||
|
pn2->pn_slot < 0 ||
|
||
|
!(pn2->pn_attrs & JSPROP_READONLY)) {
|
||
|
if (js_NewSrcNote(cx, cg, SRC_ASSIGNOP) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Left parts such as a.b.c and a[b].c need a decompiler note. */
|
||
|
if (pn2->pn_type != TOK_NAME &&
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
pn2->pn_type != TOK_RB &&
|
||
|
pn2->pn_type != TOK_RC &&
|
||
|
#endif
|
||
|
js_NewSrcNote2(cx, cg, SrcNoteForPropOp(pn2, pn2->pn_op),
|
||
|
CG_OFFSET(cg) - top) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Finally, emit the specialized assignment bytecode. */
|
||
|
switch (pn2->pn_type) {
|
||
|
case TOK_NAME:
|
||
|
if (pn2->pn_slot >= 0) {
|
||
|
if (!(pn2->pn_attrs & JSPROP_READONLY))
|
||
|
EMIT_UINT16_IMM_OP(pn2->pn_op, atomIndex);
|
||
|
break;
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
case TOK_DOT:
|
||
|
EMIT_ATOM_INDEX_OP(pn2->pn_op, atomIndex);
|
||
|
break;
|
||
|
case TOK_LB:
|
||
|
#if JS_HAS_LVALUE_RETURN
|
||
|
case TOK_LP:
|
||
|
#endif
|
||
|
if (js_Emit1(cx, cg, JSOP_SETELEM) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#if JS_HAS_DESTRUCTURING
|
||
|
case TOK_RB:
|
||
|
case TOK_RC:
|
||
|
if (!EmitDestructuringOps(cx, cg, JSOP_SETNAME, pn2))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_UNARYOP:
|
||
|
if (js_Emit1(cx, cg, JSOP_SETXMLNAME) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
default:
|
||
|
JS_ASSERT(0);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_HOOK:
|
||
|
/* Emit the condition, then branch if false to the else part. */
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid1))
|
||
|
return JS_FALSE;
|
||
|
noteIndex = js_NewSrcNote(cx, cg, SRC_COND);
|
||
|
if (noteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
beq = EmitJump(cx, cg, JSOP_IFEQ, 0);
|
||
|
if (beq < 0 || !js_EmitTree(cx, cg, pn->pn_kid2))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Jump around else, fixup the branch, emit else, fixup jump. */
|
||
|
jmp = EmitJump(cx, cg, JSOP_GOTO, 0);
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
|
||
|
|
||
|
/*
|
||
|
* Because each branch pushes a single value, but our stack budgeting
|
||
|
* analysis ignores branches, we now have to adjust cg->stackDepth to
|
||
|
* ignore the value pushed by the first branch. Execution will follow
|
||
|
* only one path, so we must decrement cg->stackDepth.
|
||
|
*
|
||
|
* Failing to do this will foil code, such as the try/catch/finally
|
||
|
* exception handling code generator, that samples cg->stackDepth for
|
||
|
* use at runtime (JSOP_SETSP), or in let expression and block code
|
||
|
* generation, which must use the stack depth to compute local stack
|
||
|
* indexes correctly.
|
||
|
*/
|
||
|
JS_ASSERT(cg->stackDepth > 0);
|
||
|
cg->stackDepth--;
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid3))
|
||
|
return JS_FALSE;
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, noteIndex, 0, jmp - beq))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case TOK_OR:
|
||
|
case TOK_AND:
|
||
|
/*
|
||
|
* JSOP_OR converts the operand on the stack to boolean, and if true,
|
||
|
* leaves the original operand value on the stack and jumps; otherwise
|
||
|
* it pops and falls into the next bytecode, which evaluates the right
|
||
|
* operand. The jump goes around the right operand evaluation.
|
||
|
*
|
||
|
* JSOP_AND converts the operand on the stack to boolean, and if false,
|
||
|
* leaves the original operand value on the stack and jumps; otherwise
|
||
|
* it pops and falls into the right operand's bytecode.
|
||
|
*
|
||
|
* Avoid tail recursion for long ||...|| expressions and long &&...&&
|
||
|
* expressions or long mixtures of ||'s and &&'s that can easily blow
|
||
|
* the stack, by forward-linking and then backpatching all the JSOP_OR
|
||
|
* and JSOP_AND bytecodes' immediate jump-offset operands.
|
||
|
*/
|
||
|
pn3 = pn;
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_left))
|
||
|
return JS_FALSE;
|
||
|
top = EmitJump(cx, cg, JSOP_BACKPATCH_POP, 0);
|
||
|
if (top < 0)
|
||
|
return JS_FALSE;
|
||
|
jmp = top;
|
||
|
pn2 = pn->pn_right;
|
||
|
while (pn2->pn_type == TOK_OR || pn2->pn_type == TOK_AND) {
|
||
|
pn = pn2;
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_left))
|
||
|
return JS_FALSE;
|
||
|
off = EmitJump(cx, cg, JSOP_BACKPATCH_POP, 0);
|
||
|
if (off < 0)
|
||
|
return JS_FALSE;
|
||
|
if (!SetBackPatchDelta(cx, cg, CG_CODE(cg, jmp), off - jmp))
|
||
|
return JS_FALSE;
|
||
|
jmp = off;
|
||
|
pn2 = pn->pn_right;
|
||
|
}
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
off = CG_OFFSET(cg);
|
||
|
do {
|
||
|
pc = CG_CODE(cg, top);
|
||
|
tmp = GetJumpOffset(cg, pc);
|
||
|
CHECK_AND_SET_JUMP_OFFSET(cx, cg, pc, off - top);
|
||
|
*pc = pn3->pn_op;
|
||
|
top += tmp;
|
||
|
} while ((pn3 = pn3->pn_right) != pn2);
|
||
|
break;
|
||
|
|
||
|
case TOK_BITOR:
|
||
|
case TOK_BITXOR:
|
||
|
case TOK_BITAND:
|
||
|
case TOK_EQOP:
|
||
|
case TOK_RELOP:
|
||
|
case TOK_IN:
|
||
|
case TOK_INSTANCEOF:
|
||
|
case TOK_SHOP:
|
||
|
case TOK_PLUS:
|
||
|
case TOK_MINUS:
|
||
|
case TOK_STAR:
|
||
|
case TOK_DIVOP:
|
||
|
if (pn->pn_arity == PN_LIST) {
|
||
|
/* Left-associative operator chain: avoid too much recursion. */
|
||
|
pn2 = pn->pn_head;
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
op = pn->pn_op;
|
||
|
while ((pn2 = pn2->pn_next) != NULL) {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
} else {
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
uintN oldflags;
|
||
|
|
||
|
case TOK_DBLCOLON:
|
||
|
if (pn->pn_arity == PN_NAME) {
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_expr))
|
||
|
return JS_FALSE;
|
||
|
if (!EmitAtomOp(cx, pn, pn->pn_op, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Binary :: has a right operand that brackets arbitrary code,
|
||
|
* possibly including a let (a = b) ... expression. We must clear
|
||
|
* TCF_IN_FOR_INIT to avoid mis-compiling such beasts.
|
||
|
*/
|
||
|
oldflags = cg->treeContext.flags;
|
||
|
cg->treeContext.flags &= ~TCF_IN_FOR_INIT;
|
||
|
#endif
|
||
|
|
||
|
/* Binary operators that evaluate both operands unconditionally. */
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_left))
|
||
|
return JS_FALSE;
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_right))
|
||
|
return JS_FALSE;
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
cg->treeContext.flags |= oldflags & TCF_IN_FOR_INIT;
|
||
|
#endif
|
||
|
if (js_Emit1(cx, cg, pn->pn_op) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_THROW:
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_AT:
|
||
|
case TOK_DEFAULT:
|
||
|
JS_ASSERT(pn->pn_arity == PN_UNARY);
|
||
|
/* FALL THROUGH */
|
||
|
#endif
|
||
|
case TOK_UNARYOP:
|
||
|
{
|
||
|
uintN oldflags;
|
||
|
|
||
|
/* Unary op, including unary +/-. */
|
||
|
pn2 = pn->pn_kid;
|
||
|
op = pn->pn_op;
|
||
|
if (op == JSOP_TYPEOF) {
|
||
|
for (pn3 = pn2; pn3->pn_type == TOK_RP; pn3 = pn3->pn_kid)
|
||
|
continue;
|
||
|
if (pn3->pn_type != TOK_NAME)
|
||
|
op = JSOP_TYPEOFEXPR;
|
||
|
}
|
||
|
oldflags = cg->treeContext.flags;
|
||
|
cg->treeContext.flags &= ~TCF_IN_FOR_INIT;
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
cg->treeContext.flags |= oldflags & TCF_IN_FOR_INIT;
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
if (op == JSOP_XMLNAME &&
|
||
|
js_NewSrcNote2(cx, cg, SRC_PCBASE,
|
||
|
CG_OFFSET(cg) - pn2->pn_offset) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
#endif
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TOK_INC:
|
||
|
case TOK_DEC:
|
||
|
{
|
||
|
intN depth;
|
||
|
|
||
|
/* Emit lvalue-specialized code for ++/-- operators. */
|
||
|
pn2 = pn->pn_kid;
|
||
|
JS_ASSERT(pn2->pn_type != TOK_RP);
|
||
|
op = pn->pn_op;
|
||
|
depth = cg->stackDepth;
|
||
|
switch (pn2->pn_type) {
|
||
|
case TOK_NAME:
|
||
|
pn2->pn_op = op;
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, pn2, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
op = pn2->pn_op;
|
||
|
if (pn2->pn_slot >= 0) {
|
||
|
if (pn2->pn_attrs & JSPROP_READONLY) {
|
||
|
/* Incrementing a declared const: just get its value. */
|
||
|
op = ((js_CodeSpec[op].format & JOF_TYPEMASK) == JOF_CONST)
|
||
|
? JSOP_GETGVAR
|
||
|
: JSOP_GETVAR;
|
||
|
}
|
||
|
atomIndex = (jsatomid) pn2->pn_slot;
|
||
|
EMIT_UINT16_IMM_OP(op, atomIndex);
|
||
|
} else {
|
||
|
if (!EmitAtomOp(cx, pn2, op, cg))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
break;
|
||
|
case TOK_DOT:
|
||
|
if (!EmitPropOp(cx, pn2, op, cg))
|
||
|
return JS_FALSE;
|
||
|
++depth;
|
||
|
break;
|
||
|
case TOK_LB:
|
||
|
if (!EmitElemOp(cx, pn2, op, cg))
|
||
|
return JS_FALSE;
|
||
|
depth += 2;
|
||
|
break;
|
||
|
#if JS_HAS_LVALUE_RETURN
|
||
|
case TOK_LP:
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
depth = cg->stackDepth;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
|
||
|
CG_OFFSET(cg) - pn2->pn_offset) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_UNARYOP:
|
||
|
JS_ASSERT(pn2->pn_op == JSOP_SETXMLNAME);
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_kid))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_BINDXMLNAME) < 0)
|
||
|
return JS_FALSE;
|
||
|
depth = cg->stackDepth;
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
default:
|
||
|
JS_ASSERT(0);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Allocate another stack slot for GC protection in case the initial
|
||
|
* value being incremented or decremented is not a number, but
|
||
|
* converts to a jsdouble. In the TOK_NAME cases, op has 0 operand
|
||
|
* uses and 1 definition, so we don't need an extra stack slot -- we
|
||
|
* can use the one allocated for the def.
|
||
|
*/
|
||
|
if (pn2->pn_type != TOK_NAME &&
|
||
|
(js_CodeSpec[op].format & (JOF_INC | JOF_DEC)) &&
|
||
|
(uintN)depth == cg->maxStackDepth) {
|
||
|
++cg->maxStackDepth;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TOK_DELETE:
|
||
|
/*
|
||
|
* Under ECMA 3, deleting a non-reference returns true -- but alas we
|
||
|
* must evaluate the operand if it appears it might have side effects.
|
||
|
*/
|
||
|
pn2 = pn->pn_kid;
|
||
|
switch (pn2->pn_type) {
|
||
|
case TOK_NAME:
|
||
|
pn2->pn_op = JSOP_DELNAME;
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, pn2, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
op = pn2->pn_op;
|
||
|
if (op == JSOP_FALSE) {
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (!EmitAtomOp(cx, pn2, op, cg))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
break;
|
||
|
case TOK_DOT:
|
||
|
if (!EmitPropOp(cx, pn2, JSOP_DELPROP, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_DBLDOT:
|
||
|
if (!EmitElemOp(cx, pn2, JSOP_DELDESC, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
#if JS_HAS_LVALUE_RETURN
|
||
|
case TOK_LP:
|
||
|
if (pn2->pn_op != JSOP_SETCALL) {
|
||
|
JS_ASSERT(pn2->pn_op == JSOP_CALL || pn2->pn_op == JSOP_EVAL);
|
||
|
pn2->pn_op = JSOP_SETCALL;
|
||
|
}
|
||
|
top = CG_OFFSET(cg);
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_DELELEM) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif
|
||
|
case TOK_LB:
|
||
|
if (!EmitElemOp(cx, pn2, JSOP_DELELEM, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
default:
|
||
|
/*
|
||
|
* If useless, just emit JSOP_TRUE; otherwise convert delete foo()
|
||
|
* to foo(), true (a comma expression, requiring SRC_PCDELTA).
|
||
|
*/
|
||
|
useful = JS_FALSE;
|
||
|
if (!CheckSideEffects(cx, &cg->treeContext, pn2, &useful))
|
||
|
return JS_FALSE;
|
||
|
if (!useful) {
|
||
|
off = noteIndex = -1;
|
||
|
} else {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
off = CG_OFFSET(cg);
|
||
|
noteIndex = js_NewSrcNote2(cx, cg, SRC_PCDELTA, 0);
|
||
|
if (noteIndex < 0 || js_Emit1(cx, cg, JSOP_POP) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (js_Emit1(cx, cg, JSOP_TRUE) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (noteIndex >= 0) {
|
||
|
tmp = CG_OFFSET(cg);
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, tmp-off))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_FILTER:
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_left))
|
||
|
return JS_FALSE;
|
||
|
jmp = js_Emit3(cx, cg, JSOP_FILTER, 0, 0);
|
||
|
if (jmp < 0)
|
||
|
return JS_FALSE;
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_right))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_ENDFILTER) < 0)
|
||
|
return JS_FALSE;
|
||
|
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
case TOK_DOT:
|
||
|
/*
|
||
|
* Pop a stack operand, convert it to object, get a property named by
|
||
|
* this bytecode's immediate-indexed atom operand, and push its value
|
||
|
* (not a reference to it). This bytecode sets the virtual machine's
|
||
|
* "obj" register to the left operand's ToObject conversion result,
|
||
|
* for use by JSOP_PUSHOBJ.
|
||
|
*/
|
||
|
ok = EmitPropOp(cx, pn, pn->pn_op, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_LB:
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_DBLDOT:
|
||
|
#endif
|
||
|
/*
|
||
|
* Pop two operands, convert the left one to object and the right one
|
||
|
* to property name (atom or tagged int), get the named property, and
|
||
|
* push its value. Set the "obj" register to the result of ToObject
|
||
|
* on the left operand.
|
||
|
*/
|
||
|
ok = EmitElemOp(cx, pn, pn->pn_op, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_NEW:
|
||
|
case TOK_LP:
|
||
|
{
|
||
|
uintN oldflags;
|
||
|
|
||
|
/*
|
||
|
* Emit function call or operator new (constructor call) code.
|
||
|
* First, emit code for the left operand to evaluate the callable or
|
||
|
* constructable object expression.
|
||
|
*
|
||
|
* For E4X, if this expression is a dotted member reference, select
|
||
|
* JSOP_GETMETHOD instead of JSOP_GETPROP. ECMA-357 separates XML
|
||
|
* method lookup from the normal property id lookup done for native
|
||
|
* objects.
|
||
|
*/
|
||
|
pn2 = pn->pn_head;
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
if (pn2->pn_type == TOK_DOT && pn2->pn_op != JSOP_GETMETHOD) {
|
||
|
JS_ASSERT(pn2->pn_op == JSOP_GETPROP);
|
||
|
pn2->pn_op = JSOP_GETMETHOD;
|
||
|
pn2->pn_attrs |= JSPROP_IMPLICIT_FUNCTION_NAMESPACE;
|
||
|
}
|
||
|
#endif
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/*
|
||
|
* Push the virtual machine's "obj" register, which was set by a
|
||
|
* name, property, or element get (or set) bytecode.
|
||
|
*/
|
||
|
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Remember start of callable-object bytecode for decompilation hint. */
|
||
|
off = top;
|
||
|
|
||
|
/*
|
||
|
* Emit code for each argument in order, then emit the JSOP_*CALL or
|
||
|
* JSOP_NEW bytecode with a two-byte immediate telling how many args
|
||
|
* were pushed on the operand stack.
|
||
|
*/
|
||
|
oldflags = cg->treeContext.flags;
|
||
|
cg->treeContext.flags &= ~TCF_IN_FOR_INIT;
|
||
|
for (pn2 = pn2->pn_next; pn2; pn2 = pn2->pn_next) {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
cg->treeContext.flags |= oldflags & TCF_IN_FOR_INIT;
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - off) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
argc = pn->pn_count - 1;
|
||
|
if (js_Emit3(cx, cg, pn->pn_op, ARGC_HI(argc), ARGC_LO(argc)) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TOK_LEXICALSCOPE:
|
||
|
{
|
||
|
JSObject *obj;
|
||
|
jsint count;
|
||
|
|
||
|
atom = pn->pn_atom;
|
||
|
obj = ATOM_TO_OBJECT(atom);
|
||
|
js_PushBlockScope(&cg->treeContext, &stmtInfo, atom, CG_OFFSET(cg));
|
||
|
|
||
|
OBJ_SET_BLOCK_DEPTH(cx, obj, cg->stackDepth);
|
||
|
count = OBJ_BLOCK_COUNT(cx, obj);
|
||
|
cg->stackDepth += count;
|
||
|
if ((uintN)cg->stackDepth > cg->maxStackDepth)
|
||
|
cg->maxStackDepth = cg->stackDepth;
|
||
|
|
||
|
/*
|
||
|
* If this lexical scope is not for a catch block, let block or let
|
||
|
* expression, or any kind of for loop (where the scope starts in the
|
||
|
* head after the first part if for (;;), else in the body if for-in);
|
||
|
* and if our container is top-level but not a function body, or else
|
||
|
* a block statement; then emit a SRC_BRACE note. All other container
|
||
|
* statements get braces by default from the decompiler.
|
||
|
*/
|
||
|
noteIndex = -1;
|
||
|
type = pn->pn_expr->pn_type;
|
||
|
if (type != TOK_CATCH && type != TOK_LET && type != TOK_FOR &&
|
||
|
(!(stmt = stmtInfo.down)
|
||
|
? !(cg->treeContext.flags & TCF_IN_FUNCTION)
|
||
|
: stmt->type == STMT_BLOCK)) {
|
||
|
#if defined DEBUG_brendan || defined DEBUG_mrbkap
|
||
|
/* There must be no source note already output for the next op. */
|
||
|
JS_ASSERT(CG_NOTE_COUNT(cg) == 0 ||
|
||
|
CG_LAST_NOTE_OFFSET(cg) != CG_OFFSET(cg) ||
|
||
|
!GettableNoteForNextOp(cg));
|
||
|
#endif
|
||
|
noteIndex = js_NewSrcNote2(cx, cg, SRC_BRACE, 0);
|
||
|
if (noteIndex < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
ale = js_IndexAtom(cx, atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
JS_ASSERT(CG_OFFSET(cg) == top);
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_ENTERBLOCK, ALE_INDEX(ale));
|
||
|
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_expr))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
op = pn->pn_op;
|
||
|
if (op == JSOP_LEAVEBLOCKEXPR) {
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_PCBASE, CG_OFFSET(cg) - top) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (noteIndex >= 0 &&
|
||
|
!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
|
||
|
CG_OFFSET(cg) - top)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Emit the JSOP_LEAVEBLOCK or JSOP_LEAVEBLOCKEXPR opcode. */
|
||
|
EMIT_UINT16_IMM_OP(op, count);
|
||
|
cg->stackDepth -= count;
|
||
|
|
||
|
ok = js_PopStatementCG(cx, cg);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
#if JS_HAS_BLOCK_SCOPE
|
||
|
case TOK_LET:
|
||
|
/* Let statements have their variable declarations on the left. */
|
||
|
if (pn->pn_arity == PN_BINARY) {
|
||
|
pn2 = pn->pn_right;
|
||
|
pn = pn->pn_left;
|
||
|
} else {
|
||
|
pn2 = NULL;
|
||
|
}
|
||
|
|
||
|
/* Non-null pn2 means that pn is the variable list from a let head. */
|
||
|
JS_ASSERT(pn->pn_arity == PN_LIST);
|
||
|
if (!EmitVariables(cx, cg, pn, pn2 != NULL, ¬eIndex))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* Thus non-null pn2 is the body of the let block or expression. */
|
||
|
tmp = CG_OFFSET(cg);
|
||
|
if (pn2 && !js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
if (noteIndex >= 0 &&
|
||
|
!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
|
||
|
CG_OFFSET(cg) - tmp)) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
break;
|
||
|
#endif /* JS_HAS_BLOCK_SCOPE */
|
||
|
|
||
|
#if JS_HAS_GENERATORS
|
||
|
case TOK_ARRAYPUSH:
|
||
|
/*
|
||
|
* The array object's stack index is in cg->arrayCompSlot. See below
|
||
|
* under the array initialiser code generator for array comprehension
|
||
|
* special casing.
|
||
|
*/
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid))
|
||
|
return JS_FALSE;
|
||
|
EMIT_UINT16_IMM_OP(pn->pn_op, cg->arrayCompSlot);
|
||
|
break;
|
||
|
#endif
|
||
|
|
||
|
case TOK_RB:
|
||
|
#if JS_HAS_GENERATORS
|
||
|
case TOK_ARRAYCOMP:
|
||
|
#endif
|
||
|
/*
|
||
|
* Emit code for [a, b, c] of the form:
|
||
|
* t = new Array; t[0] = a; t[1] = b; t[2] = c; t;
|
||
|
* but use a stack slot for t and avoid dup'ing and popping it via
|
||
|
* the JSOP_NEWINIT and JSOP_INITELEM bytecodes.
|
||
|
*/
|
||
|
ale = js_IndexAtom(cx, CLASS_ATOM(cx, Array), &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_NAME, ALE_INDEX(ale));
|
||
|
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_NEWINIT) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
pn2 = pn->pn_head;
|
||
|
#if JS_HAS_SHARP_VARS
|
||
|
if (pn2 && pn2->pn_type == TOK_DEFSHARP) {
|
||
|
EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid)pn2->pn_num);
|
||
|
pn2 = pn2->pn_next;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#if JS_HAS_GENERATORS
|
||
|
if (pn->pn_type == TOK_ARRAYCOMP) {
|
||
|
uintN saveSlot;
|
||
|
|
||
|
/*
|
||
|
* Pass the new array's stack index to the TOK_ARRAYPUSH case by
|
||
|
* storing it in pn->pn_extra, then simply traverse the TOK_FOR
|
||
|
* node and its kids under pn2 to generate this comprehension.
|
||
|
*/
|
||
|
JS_ASSERT(cg->stackDepth > 0);
|
||
|
saveSlot = cg->arrayCompSlot;
|
||
|
cg->arrayCompSlot = (uint32) (cg->stackDepth - 1);
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
cg->arrayCompSlot = saveSlot;
|
||
|
|
||
|
/* Emit the usual op needed for decompilation. */
|
||
|
if (js_Emit1(cx, cg, JSOP_ENDINIT) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
#endif /* JS_HAS_GENERATORS */
|
||
|
|
||
|
for (atomIndex = 0; pn2; atomIndex++, pn2 = pn2->pn_next) {
|
||
|
if (!EmitNumberOp(cx, atomIndex, cg))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
/* FIXME 260106: holes in a sparse initializer are void-filled. */
|
||
|
if (pn2->pn_type == TOK_COMMA) {
|
||
|
if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (js_Emit1(cx, cg, JSOP_INITELEM) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (pn->pn_extra & PNX_ENDCOMMA) {
|
||
|
/* Emit a source note so we know to decompile an extra comma. */
|
||
|
if (js_NewSrcNote(cx, cg, SRC_CONTINUE) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Emit an op for sharp array cleanup and decompilation. */
|
||
|
if (js_Emit1(cx, cg, JSOP_ENDINIT) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
case TOK_RC:
|
||
|
/*
|
||
|
* Emit code for {p:a, '%q':b, 2:c} of the form:
|
||
|
* t = new Object; t.p = a; t['%q'] = b; t[2] = c; t;
|
||
|
* but use a stack slot for t and avoid dup'ing and popping it via
|
||
|
* the JSOP_NEWINIT and JSOP_INITELEM bytecodes.
|
||
|
*/
|
||
|
ale = js_IndexAtom(cx, CLASS_ATOM(cx, Object), &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_NAME, ALE_INDEX(ale));
|
||
|
|
||
|
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_NEWINIT) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
pn2 = pn->pn_head;
|
||
|
#if JS_HAS_SHARP_VARS
|
||
|
if (pn2 && pn2->pn_type == TOK_DEFSHARP) {
|
||
|
EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid)pn2->pn_num);
|
||
|
pn2 = pn2->pn_next;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
for (; pn2; pn2 = pn2->pn_next) {
|
||
|
/* Emit an index for t[2], else map an atom for t.p or t['%q']. */
|
||
|
pn3 = pn2->pn_left;
|
||
|
switch (pn3->pn_type) {
|
||
|
case TOK_NUMBER:
|
||
|
if (!EmitNumberOp(cx, pn3->pn_dval, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
case TOK_NAME:
|
||
|
case TOK_STRING:
|
||
|
ale = js_IndexAtom(cx, pn3->pn_atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
default:
|
||
|
JS_ASSERT(0);
|
||
|
}
|
||
|
|
||
|
/* Emit code for the property initializer. */
|
||
|
if (!js_EmitTree(cx, cg, pn2->pn_right))
|
||
|
return JS_FALSE;
|
||
|
|
||
|
#if JS_HAS_GETTER_SETTER
|
||
|
op = pn2->pn_op;
|
||
|
if (op == JSOP_GETTER || op == JSOP_SETTER) {
|
||
|
if (pn3->pn_type != TOK_NUMBER &&
|
||
|
ALE_INDEX(ale) >= JS_BIT(16)) {
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
#endif
|
||
|
/* Annotate JSOP_INITELEM so we decompile 2:c and not just c. */
|
||
|
if (pn3->pn_type == TOK_NUMBER) {
|
||
|
if (js_NewSrcNote(cx, cg, SRC_INITPROP) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_INITELEM) < 0)
|
||
|
return JS_FALSE;
|
||
|
} else {
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_INITPROP, ALE_INDEX(ale));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Emit an op for sharpArray cleanup and decompilation. */
|
||
|
if (js_Emit1(cx, cg, JSOP_ENDINIT) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
#if JS_HAS_SHARP_VARS
|
||
|
case TOK_DEFSHARP:
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid))
|
||
|
return JS_FALSE;
|
||
|
EMIT_UINT16_IMM_OP(JSOP_DEFSHARP, (jsatomid) pn->pn_num);
|
||
|
break;
|
||
|
|
||
|
case TOK_USESHARP:
|
||
|
EMIT_UINT16_IMM_OP(JSOP_USESHARP, (jsatomid) pn->pn_num);
|
||
|
break;
|
||
|
#endif /* JS_HAS_SHARP_VARS */
|
||
|
|
||
|
case TOK_RP:
|
||
|
{
|
||
|
uintN oldflags;
|
||
|
|
||
|
/*
|
||
|
* The node for (e) has e as its kid, enabling users who want to nest
|
||
|
* assignment expressions in conditions to avoid the error correction
|
||
|
* done by Condition (from x = y to x == y) by double-parenthesizing.
|
||
|
*/
|
||
|
oldflags = cg->treeContext.flags;
|
||
|
cg->treeContext.flags &= ~TCF_IN_FOR_INIT;
|
||
|
if (!js_EmitTree(cx, cg, pn->pn_kid))
|
||
|
return JS_FALSE;
|
||
|
cg->treeContext.flags |= oldflags & TCF_IN_FOR_INIT;
|
||
|
if (js_Emit1(cx, cg, JSOP_GROUP) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TOK_NAME:
|
||
|
if (!BindNameToSlot(cx, &cg->treeContext, pn, JS_FALSE))
|
||
|
return JS_FALSE;
|
||
|
op = pn->pn_op;
|
||
|
if (op == JSOP_ARGUMENTS) {
|
||
|
if (js_Emit1(cx, cg, op) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
if (pn->pn_slot >= 0) {
|
||
|
atomIndex = (jsatomid) pn->pn_slot;
|
||
|
EMIT_UINT16_IMM_OP(op, atomIndex);
|
||
|
break;
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_XMLATTR:
|
||
|
case TOK_XMLSPACE:
|
||
|
case TOK_XMLTEXT:
|
||
|
case TOK_XMLCDATA:
|
||
|
case TOK_XMLCOMMENT:
|
||
|
#endif
|
||
|
case TOK_STRING:
|
||
|
case TOK_OBJECT:
|
||
|
/*
|
||
|
* The scanner and parser associate JSOP_NAME with TOK_NAME, although
|
||
|
* other bytecodes may result instead (JSOP_BINDNAME/JSOP_SETNAME,
|
||
|
* JSOP_FORNAME, etc.). Among JSOP_*NAME* variants, only JSOP_NAME
|
||
|
* may generate the first operand of a call or new expression, so only
|
||
|
* it sets the "obj" virtual machine register to the object along the
|
||
|
* scope chain in which the name was found.
|
||
|
*
|
||
|
* Token types for STRING and OBJECT have corresponding bytecode ops
|
||
|
* in pn_op and emit the same format as NAME, so they share this code.
|
||
|
*/
|
||
|
ok = EmitAtomOp(cx, pn, pn->pn_op, cg);
|
||
|
break;
|
||
|
|
||
|
case TOK_NUMBER:
|
||
|
ok = EmitNumberOp(cx, pn->pn_dval, cg);
|
||
|
break;
|
||
|
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_ANYNAME:
|
||
|
#endif
|
||
|
case TOK_PRIMARY:
|
||
|
if (js_Emit1(cx, cg, pn->pn_op) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
|
||
|
#if JS_HAS_DEBUGGER_KEYWORD
|
||
|
case TOK_DEBUGGER:
|
||
|
if (js_Emit1(cx, cg, JSOP_DEBUGGER) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif /* JS_HAS_DEBUGGER_KEYWORD */
|
||
|
|
||
|
#if JS_HAS_XML_SUPPORT
|
||
|
case TOK_XMLELEM:
|
||
|
case TOK_XMLLIST:
|
||
|
if (pn->pn_op == JSOP_XMLOBJECT) {
|
||
|
ok = EmitAtomOp(cx, pn, pn->pn_op, cg);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
JS_ASSERT(pn->pn_type == TOK_XMLLIST || pn->pn_count != 0);
|
||
|
switch (pn->pn_head ? pn->pn_head->pn_type : TOK_XMLLIST) {
|
||
|
case TOK_XMLETAGO:
|
||
|
JS_ASSERT(0);
|
||
|
/* FALL THROUGH */
|
||
|
case TOK_XMLPTAGC:
|
||
|
case TOK_XMLSTAGO:
|
||
|
break;
|
||
|
default:
|
||
|
if (js_Emit1(cx, cg, JSOP_STARTXML) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||
|
if (pn2->pn_type == TOK_LC &&
|
||
|
js_Emit1(cx, cg, JSOP_STARTXMLEXPR) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
if (pn2 != pn->pn_head && js_Emit1(cx, cg, JSOP_ADD) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
if (pn->pn_extra & PNX_XMLROOT) {
|
||
|
if (pn->pn_count == 0) {
|
||
|
JS_ASSERT(pn->pn_type == TOK_XMLLIST);
|
||
|
atom = cx->runtime->atomState.emptyAtom;
|
||
|
ale = js_IndexAtom(cx, atom, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_STRING, ALE_INDEX(ale));
|
||
|
}
|
||
|
if (js_Emit1(cx, cg, pn->pn_op) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
#ifdef DEBUG
|
||
|
else
|
||
|
JS_ASSERT(pn->pn_count != 0);
|
||
|
#endif
|
||
|
break;
|
||
|
|
||
|
case TOK_XMLPTAGC:
|
||
|
if (pn->pn_op == JSOP_XMLOBJECT) {
|
||
|
ok = EmitAtomOp(cx, pn, pn->pn_op, cg);
|
||
|
break;
|
||
|
}
|
||
|
/* FALL THROUGH */
|
||
|
|
||
|
case TOK_XMLSTAGO:
|
||
|
case TOK_XMLETAGO:
|
||
|
{
|
||
|
uint32 i;
|
||
|
|
||
|
if (js_Emit1(cx, cg, JSOP_STARTXML) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
ale = js_IndexAtom(cx,
|
||
|
(pn->pn_type == TOK_XMLETAGO)
|
||
|
? cx->runtime->atomState.etagoAtom
|
||
|
: cx->runtime->atomState.stagoAtom,
|
||
|
&cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_STRING, ALE_INDEX(ale));
|
||
|
|
||
|
JS_ASSERT(pn->pn_count != 0);
|
||
|
pn2 = pn->pn_head;
|
||
|
if (pn2->pn_type == TOK_LC && js_Emit1(cx, cg, JSOP_STARTXMLEXPR) < 0)
|
||
|
return JS_FALSE;
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
if (js_Emit1(cx, cg, JSOP_ADD) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
for (pn2 = pn2->pn_next, i = 0; pn2; pn2 = pn2->pn_next, i++) {
|
||
|
if (pn2->pn_type == TOK_LC &&
|
||
|
js_Emit1(cx, cg, JSOP_STARTXMLEXPR) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
if ((i & 1) && pn2->pn_type == TOK_LC) {
|
||
|
if (js_Emit1(cx, cg, JSOP_TOATTRVAL) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
if (js_Emit1(cx, cg,
|
||
|
(i & 1) ? JSOP_ADDATTRVAL : JSOP_ADDATTRNAME) < 0) {
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ale = js_IndexAtom(cx,
|
||
|
(pn->pn_type == TOK_XMLPTAGC)
|
||
|
? cx->runtime->atomState.ptagcAtom
|
||
|
: cx->runtime->atomState.tagcAtom,
|
||
|
&cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
EMIT_ATOM_INDEX_OP(JSOP_STRING, ALE_INDEX(ale));
|
||
|
if (js_Emit1(cx, cg, JSOP_ADD) < 0)
|
||
|
return JS_FALSE;
|
||
|
|
||
|
if ((pn->pn_extra & PNX_XMLROOT) && js_Emit1(cx, cg, pn->pn_op) < 0)
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case TOK_XMLNAME:
|
||
|
if (pn->pn_arity == PN_LIST) {
|
||
|
JS_ASSERT(pn->pn_count != 0);
|
||
|
for (pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
|
||
|
if (!js_EmitTree(cx, cg, pn2))
|
||
|
return JS_FALSE;
|
||
|
if (pn2 != pn->pn_head && js_Emit1(cx, cg, JSOP_ADD) < 0)
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
} else {
|
||
|
JS_ASSERT(pn->pn_arity == PN_NULLARY);
|
||
|
ok = EmitAtomOp(cx, pn, pn->pn_op, cg);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case TOK_XMLPI:
|
||
|
ale = js_IndexAtom(cx, pn->pn_atom2, &cg->atomList);
|
||
|
if (!ale)
|
||
|
return JS_FALSE;
|
||
|
if (!EmitAtomIndexOp(cx, JSOP_QNAMEPART, ALE_INDEX(ale), cg))
|
||
|
return JS_FALSE;
|
||
|
if (!EmitAtomOp(cx, pn, JSOP_XMLPI, cg))
|
||
|
return JS_FALSE;
|
||
|
break;
|
||
|
#endif /* JS_HAS_XML_SUPPORT */
|
||
|
|
||
|
default:
|
||
|
JS_ASSERT(0);
|
||
|
}
|
||
|
|
||
|
if (ok && --cg->emitLevel == 0 && cg->spanDeps)
|
||
|
ok = OptimizeSpanDeps(cx, cg);
|
||
|
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
/* XXX get rid of offsetBias, it's used only by SRC_FOR and SRC_DECL */
|
||
|
JS_FRIEND_DATA(JSSrcNoteSpec) js_SrcNoteSpec[] = {
|
||
|
{"null", 0, 0, 0},
|
||
|
{"if", 0, 0, 0},
|
||
|
{"if-else", 2, 0, 1},
|
||
|
{"while", 1, 0, 1},
|
||
|
{"for", 3, 1, 1},
|
||
|
{"continue", 0, 0, 0},
|
||
|
{"decl", 1, 1, 1},
|
||
|
{"pcdelta", 1, 0, 1},
|
||
|
{"assignop", 0, 0, 0},
|
||
|
{"cond", 1, 0, 1},
|
||
|
{"brace", 1, 0, 1},
|
||
|
{"hidden", 0, 0, 0},
|
||
|
{"pcbase", 1, 0, -1},
|
||
|
{"label", 1, 0, 0},
|
||
|
{"labelbrace", 1, 0, 0},
|
||
|
{"endbrace", 0, 0, 0},
|
||
|
{"break2label", 1, 0, 0},
|
||
|
{"cont2label", 1, 0, 0},
|
||
|
{"switch", 2, 0, 1},
|
||
|
{"funcdef", 1, 0, 0},
|
||
|
{"catch", 1, 0, 1},
|
||
|
{"extended", -1, 0, 0},
|
||
|
{"newline", 0, 0, 0},
|
||
|
{"setline", 1, 0, 0},
|
||
|
{"xdelta", 0, 0, 0},
|
||
|
};
|
||
|
|
||
|
static intN
|
||
|
AllocSrcNote(JSContext *cx, JSCodeGenerator *cg)
|
||
|
{
|
||
|
intN index;
|
||
|
JSArenaPool *pool;
|
||
|
size_t size;
|
||
|
|
||
|
index = CG_NOTE_COUNT(cg);
|
||
|
if (((uintN)index & CG_NOTE_MASK(cg)) == 0) {
|
||
|
pool = cg->notePool;
|
||
|
size = SRCNOTE_SIZE(CG_NOTE_MASK(cg) + 1);
|
||
|
if (!CG_NOTES(cg)) {
|
||
|
/* Allocate the first note array lazily; leave noteMask alone. */
|
||
|
JS_ARENA_ALLOCATE_CAST(CG_NOTES(cg), jssrcnote *, pool, size);
|
||
|
} else {
|
||
|
/* Grow by doubling note array size; update noteMask on success. */
|
||
|
JS_ARENA_GROW_CAST(CG_NOTES(cg), jssrcnote *, pool, size, size);
|
||
|
if (CG_NOTES(cg))
|
||
|
CG_NOTE_MASK(cg) = (CG_NOTE_MASK(cg) << 1) | 1;
|
||
|
}
|
||
|
if (!CG_NOTES(cg)) {
|
||
|
JS_ReportOutOfMemory(cx);
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
CG_NOTE_COUNT(cg) = index + 1;
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
intN
|
||
|
js_NewSrcNote(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type)
|
||
|
{
|
||
|
intN index, n;
|
||
|
jssrcnote *sn;
|
||
|
ptrdiff_t offset, delta, xdelta;
|
||
|
|
||
|
/*
|
||
|
* Claim a note slot in CG_NOTES(cg) by growing it if necessary and then
|
||
|
* incrementing CG_NOTE_COUNT(cg).
|
||
|
*/
|
||
|
index = AllocSrcNote(cx, cg);
|
||
|
if (index < 0)
|
||
|
return -1;
|
||
|
sn = &CG_NOTES(cg)[index];
|
||
|
|
||
|
/*
|
||
|
* Compute delta from the last annotated bytecode's offset. If it's too
|
||
|
* big to fit in sn, allocate one or more xdelta notes and reset sn.
|
||
|
*/
|
||
|
offset = CG_OFFSET(cg);
|
||
|
delta = offset - CG_LAST_NOTE_OFFSET(cg);
|
||
|
CG_LAST_NOTE_OFFSET(cg) = offset;
|
||
|
if (delta >= SN_DELTA_LIMIT) {
|
||
|
do {
|
||
|
xdelta = JS_MIN(delta, SN_XDELTA_MASK);
|
||
|
SN_MAKE_XDELTA(sn, xdelta);
|
||
|
delta -= xdelta;
|
||
|
index = AllocSrcNote(cx, cg);
|
||
|
if (index < 0)
|
||
|
return -1;
|
||
|
sn = &CG_NOTES(cg)[index];
|
||
|
} while (delta >= SN_DELTA_LIMIT);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Initialize type and delta, then allocate the minimum number of notes
|
||
|
* needed for type's arity. Usually, we won't need more, but if an offset
|
||
|
* does take two bytes, js_SetSrcNoteOffset will grow CG_NOTES(cg).
|
||
|
*/
|
||
|
SN_MAKE_NOTE(sn, type, delta);
|
||
|
for (n = (intN)js_SrcNoteSpec[type].arity; n > 0; n--) {
|
||
|
if (js_NewSrcNote(cx, cg, SRC_NULL) < 0)
|
||
|
return -1;
|
||
|
}
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
intN
|
||
|
js_NewSrcNote2(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type,
|
||
|
ptrdiff_t offset)
|
||
|
{
|
||
|
intN index;
|
||
|
|
||
|
index = js_NewSrcNote(cx, cg, type);
|
||
|
if (index >= 0) {
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, index, 0, offset))
|
||
|
return -1;
|
||
|
}
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
intN
|
||
|
js_NewSrcNote3(JSContext *cx, JSCodeGenerator *cg, JSSrcNoteType type,
|
||
|
ptrdiff_t offset1, ptrdiff_t offset2)
|
||
|
{
|
||
|
intN index;
|
||
|
|
||
|
index = js_NewSrcNote(cx, cg, type);
|
||
|
if (index >= 0) {
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, index, 0, offset1))
|
||
|
return -1;
|
||
|
if (!js_SetSrcNoteOffset(cx, cg, index, 1, offset2))
|
||
|
return -1;
|
||
|
}
|
||
|
return index;
|
||
|
}
|
||
|
|
||
|
static JSBool
|
||
|
GrowSrcNotes(JSContext *cx, JSCodeGenerator *cg)
|
||
|
{
|
||
|
JSArenaPool *pool;
|
||
|
size_t size;
|
||
|
|
||
|
/* Grow by doubling note array size; update noteMask on success. */
|
||
|
pool = cg->notePool;
|
||
|
size = SRCNOTE_SIZE(CG_NOTE_MASK(cg) + 1);
|
||
|
JS_ARENA_GROW_CAST(CG_NOTES(cg), jssrcnote *, pool, size, size);
|
||
|
if (!CG_NOTES(cg)) {
|
||
|
JS_ReportOutOfMemory(cx);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
CG_NOTE_MASK(cg) = (CG_NOTE_MASK(cg) << 1) | 1;
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
jssrcnote *
|
||
|
js_AddToSrcNoteDelta(JSContext *cx, JSCodeGenerator *cg, jssrcnote *sn,
|
||
|
ptrdiff_t delta)
|
||
|
{
|
||
|
ptrdiff_t base, limit, newdelta, diff;
|
||
|
intN index;
|
||
|
|
||
|
/*
|
||
|
* Called only from OptimizeSpanDeps and js_FinishTakingSrcNotes to add to
|
||
|
* main script note deltas, and only by a small positive amount.
|
||
|
*/
|
||
|
JS_ASSERT(cg->current == &cg->main);
|
||
|
JS_ASSERT((unsigned) delta < (unsigned) SN_XDELTA_LIMIT);
|
||
|
|
||
|
base = SN_DELTA(sn);
|
||
|
limit = SN_IS_XDELTA(sn) ? SN_XDELTA_LIMIT : SN_DELTA_LIMIT;
|
||
|
newdelta = base + delta;
|
||
|
if (newdelta < limit) {
|
||
|
SN_SET_DELTA(sn, newdelta);
|
||
|
} else {
|
||
|
index = sn - cg->main.notes;
|
||
|
if ((cg->main.noteCount & cg->main.noteMask) == 0) {
|
||
|
if (!GrowSrcNotes(cx, cg))
|
||
|
return NULL;
|
||
|
sn = cg->main.notes + index;
|
||
|
}
|
||
|
diff = cg->main.noteCount - index;
|
||
|
cg->main.noteCount++;
|
||
|
memmove(sn + 1, sn, SRCNOTE_SIZE(diff));
|
||
|
SN_MAKE_XDELTA(sn, delta);
|
||
|
sn++;
|
||
|
}
|
||
|
return sn;
|
||
|
}
|
||
|
|
||
|
JS_FRIEND_API(uintN)
|
||
|
js_SrcNoteLength(jssrcnote *sn)
|
||
|
{
|
||
|
uintN arity;
|
||
|
jssrcnote *base;
|
||
|
|
||
|
arity = (intN)js_SrcNoteSpec[SN_TYPE(sn)].arity;
|
||
|
for (base = sn++; arity; sn++, arity--) {
|
||
|
if (*sn & SN_3BYTE_OFFSET_FLAG)
|
||
|
sn += 2;
|
||
|
}
|
||
|
return sn - base;
|
||
|
}
|
||
|
|
||
|
JS_FRIEND_API(ptrdiff_t)
|
||
|
js_GetSrcNoteOffset(jssrcnote *sn, uintN which)
|
||
|
{
|
||
|
/* Find the offset numbered which (i.e., skip exactly which offsets). */
|
||
|
JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
|
||
|
JS_ASSERT(which < js_SrcNoteSpec[SN_TYPE(sn)].arity);
|
||
|
for (sn++; which; sn++, which--) {
|
||
|
if (*sn & SN_3BYTE_OFFSET_FLAG)
|
||
|
sn += 2;
|
||
|
}
|
||
|
if (*sn & SN_3BYTE_OFFSET_FLAG) {
|
||
|
return (ptrdiff_t)(((uint32)(sn[0] & SN_3BYTE_OFFSET_MASK) << 16)
|
||
|
| (sn[1] << 8)
|
||
|
| sn[2]);
|
||
|
}
|
||
|
return (ptrdiff_t)*sn;
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_SetSrcNoteOffset(JSContext *cx, JSCodeGenerator *cg, uintN index,
|
||
|
uintN which, ptrdiff_t offset)
|
||
|
{
|
||
|
jssrcnote *sn;
|
||
|
ptrdiff_t diff;
|
||
|
|
||
|
if ((jsuword)offset >= (jsuword)((ptrdiff_t)SN_3BYTE_OFFSET_FLAG << 16)) {
|
||
|
ReportStatementTooLarge(cx, cg);
|
||
|
return JS_FALSE;
|
||
|
}
|
||
|
|
||
|
/* Find the offset numbered which (i.e., skip exactly which offsets). */
|
||
|
sn = &CG_NOTES(cg)[index];
|
||
|
JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
|
||
|
JS_ASSERT(which < js_SrcNoteSpec[SN_TYPE(sn)].arity);
|
||
|
for (sn++; which; sn++, which--) {
|
||
|
if (*sn & SN_3BYTE_OFFSET_FLAG)
|
||
|
sn += 2;
|
||
|
}
|
||
|
|
||
|
/* See if the new offset requires three bytes. */
|
||
|
if (offset > (ptrdiff_t)SN_3BYTE_OFFSET_MASK) {
|
||
|
/* Maybe this offset was already set to a three-byte value. */
|
||
|
if (!(*sn & SN_3BYTE_OFFSET_FLAG)) {
|
||
|
/* Losing, need to insert another two bytes for this offset. */
|
||
|
index = PTRDIFF(sn, CG_NOTES(cg), jssrcnote);
|
||
|
|
||
|
/*
|
||
|
* Simultaneously test to see if the source note array must grow to
|
||
|
* accomodate either the first or second byte of additional storage
|
||
|
* required by this 3-byte offset.
|
||
|
*/
|
||
|
if (((CG_NOTE_COUNT(cg) + 1) & CG_NOTE_MASK(cg)) <= 1) {
|
||
|
if (!GrowSrcNotes(cx, cg))
|
||
|
return JS_FALSE;
|
||
|
sn = CG_NOTES(cg) + index;
|
||
|
}
|
||
|
CG_NOTE_COUNT(cg) += 2;
|
||
|
|
||
|
diff = CG_NOTE_COUNT(cg) - (index + 3);
|
||
|
JS_ASSERT(diff >= 0);
|
||
|
if (diff > 0)
|
||
|
memmove(sn + 3, sn + 1, SRCNOTE_SIZE(diff));
|
||
|
}
|
||
|
*sn++ = (jssrcnote)(SN_3BYTE_OFFSET_FLAG | (offset >> 16));
|
||
|
*sn++ = (jssrcnote)(offset >> 8);
|
||
|
}
|
||
|
*sn = (jssrcnote)offset;
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
#ifdef DEBUG_notme
|
||
|
#define DEBUG_srcnotesize
|
||
|
#endif
|
||
|
|
||
|
#ifdef DEBUG_srcnotesize
|
||
|
#define NBINS 10
|
||
|
static uint32 hist[NBINS];
|
||
|
|
||
|
void DumpSrcNoteSizeHist()
|
||
|
{
|
||
|
static FILE *fp;
|
||
|
int i, n;
|
||
|
|
||
|
if (!fp) {
|
||
|
fp = fopen("/tmp/srcnotes.hist", "w");
|
||
|
if (!fp)
|
||
|
return;
|
||
|
setvbuf(fp, NULL, _IONBF, 0);
|
||
|
}
|
||
|
fprintf(fp, "SrcNote size histogram:\n");
|
||
|
for (i = 0; i < NBINS; i++) {
|
||
|
fprintf(fp, "%4u %4u ", JS_BIT(i), hist[i]);
|
||
|
for (n = (int) JS_HOWMANY(hist[i], 10); n > 0; --n)
|
||
|
fputc('*', fp);
|
||
|
fputc('\n', fp);
|
||
|
}
|
||
|
fputc('\n', fp);
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/*
|
||
|
* Fill in the storage at notes with prolog and main srcnotes; the space at
|
||
|
* notes was allocated using the CG_COUNT_FINAL_SRCNOTES macro from jsemit.h.
|
||
|
* SO DON'T CHANGE THIS FUNCTION WITHOUT AT LEAST CHECKING WHETHER jsemit.h's
|
||
|
* CG_COUNT_FINAL_SRCNOTES MACRO NEEDS CORRESPONDING CHANGES!
|
||
|
*/
|
||
|
JSBool
|
||
|
js_FinishTakingSrcNotes(JSContext *cx, JSCodeGenerator *cg, jssrcnote *notes)
|
||
|
{
|
||
|
uintN prologCount, mainCount, totalCount;
|
||
|
ptrdiff_t offset, delta;
|
||
|
jssrcnote *sn;
|
||
|
|
||
|
JS_ASSERT(cg->current == &cg->main);
|
||
|
|
||
|
prologCount = cg->prolog.noteCount;
|
||
|
if (prologCount && cg->prolog.currentLine != cg->firstLine) {
|
||
|
CG_SWITCH_TO_PROLOG(cg);
|
||
|
if (js_NewSrcNote2(cx, cg, SRC_SETLINE, (ptrdiff_t)cg->firstLine) < 0)
|
||
|
return JS_FALSE;
|
||
|
prologCount = cg->prolog.noteCount;
|
||
|
CG_SWITCH_TO_MAIN(cg);
|
||
|
} else {
|
||
|
/*
|
||
|
* Either no prolog srcnotes, or no line number change over prolog.
|
||
|
* We don't need a SRC_SETLINE, but we may need to adjust the offset
|
||
|
* of the first main note, by adding to its delta and possibly even
|
||
|
* prepending SRC_XDELTA notes to it to account for prolog bytecodes
|
||
|
* that came at and after the last annotated bytecode.
|
||
|
*/
|
||
|
offset = CG_PROLOG_OFFSET(cg) - cg->prolog.lastNoteOffset;
|
||
|
JS_ASSERT(offset >= 0);
|
||
|
if (offset > 0 && cg->main.noteCount != 0) {
|
||
|
/* NB: Use as much of the first main note's delta as we can. */
|
||
|
sn = cg->main.notes;
|
||
|
delta = SN_IS_XDELTA(sn)
|
||
|
? SN_XDELTA_MASK - (*sn & SN_XDELTA_MASK)
|
||
|
: SN_DELTA_MASK - (*sn & SN_DELTA_MASK);
|
||
|
if (offset < delta)
|
||
|
delta = offset;
|
||
|
for (;;) {
|
||
|
if (!js_AddToSrcNoteDelta(cx, cg, sn, delta))
|
||
|
return JS_FALSE;
|
||
|
offset -= delta;
|
||
|
if (offset == 0)
|
||
|
break;
|
||
|
delta = JS_MIN(offset, SN_XDELTA_MASK);
|
||
|
sn = cg->main.notes;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mainCount = cg->main.noteCount;
|
||
|
totalCount = prologCount + mainCount;
|
||
|
if (prologCount)
|
||
|
memcpy(notes, cg->prolog.notes, SRCNOTE_SIZE(prologCount));
|
||
|
memcpy(notes + prologCount, cg->main.notes, SRCNOTE_SIZE(mainCount));
|
||
|
SN_MAKE_TERMINATOR(¬es[totalCount]);
|
||
|
|
||
|
#ifdef DEBUG_notme
|
||
|
{ int bin = JS_CeilingLog2(totalCount);
|
||
|
if (bin >= NBINS)
|
||
|
bin = NBINS - 1;
|
||
|
++hist[bin];
|
||
|
}
|
||
|
#endif
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
JSBool
|
||
|
js_AllocTryNotes(JSContext *cx, JSCodeGenerator *cg)
|
||
|
{
|
||
|
size_t size, incr;
|
||
|
ptrdiff_t delta;
|
||
|
|
||
|
size = TRYNOTE_SIZE(cg->treeContext.tryCount);
|
||
|
if (size <= cg->tryNoteSpace)
|
||
|
return JS_TRUE;
|
||
|
|
||
|
/*
|
||
|
* Allocate trynotes from cx->tempPool.
|
||
|
* XXX Too much growing and we bloat, as other tempPool allocators block
|
||
|
* in-place growth, and we never recycle old free space in an arena.
|
||
|
* YYY But once we consume an entire arena, we'll realloc it, letting the
|
||
|
* malloc heap recycle old space, while still freeing _en masse_ via the
|
||
|
* arena pool.
|
||
|
*/
|
||
|
if (!cg->tryBase) {
|
||
|
size = JS_ROUNDUP(size, TRYNOTE_SIZE(TRYNOTE_CHUNK));
|
||
|
JS_ARENA_ALLOCATE_CAST(cg->tryBase, JSTryNote *, &cx->tempPool, size);
|
||
|
if (!cg->tryBase)
|
||
|
return JS_FALSE;
|
||
|
cg->tryNoteSpace = size;
|
||
|
cg->tryNext = cg->tryBase;
|
||
|
} else {
|
||
|
delta = PTRDIFF((char *)cg->tryNext, (char *)cg->tryBase, char);
|
||
|
incr = size - cg->tryNoteSpace;
|
||
|
incr = JS_ROUNDUP(incr, TRYNOTE_SIZE(TRYNOTE_CHUNK));
|
||
|
size = cg->tryNoteSpace;
|
||
|
JS_ARENA_GROW_CAST(cg->tryBase, JSTryNote *, &cx->tempPool, size, incr);
|
||
|
if (!cg->tryBase)
|
||
|
return JS_FALSE;
|
||
|
cg->tryNoteSpace = size + incr;
|
||
|
cg->tryNext = (JSTryNote *)((char *)cg->tryBase + delta);
|
||
|
}
|
||
|
return JS_TRUE;
|
||
|
}
|
||
|
|
||
|
JSTryNote *
|
||
|
js_NewTryNote(JSContext *cx, JSCodeGenerator *cg, ptrdiff_t start,
|
||
|
ptrdiff_t end, ptrdiff_t catchStart)
|
||
|
{
|
||
|
JSTryNote *tn;
|
||
|
|
||
|
JS_ASSERT(cg->tryBase <= cg->tryNext);
|
||
|
JS_ASSERT(catchStart >= 0);
|
||
|
tn = cg->tryNext++;
|
||
|
tn->start = start;
|
||
|
tn->length = end - start;
|
||
|
tn->catchStart = catchStart;
|
||
|
return tn;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
js_FinishTakingTryNotes(JSContext *cx, JSCodeGenerator *cg, JSTryNote *notes)
|
||
|
{
|
||
|
uintN count;
|
||
|
|
||
|
count = PTRDIFF(cg->tryNext, cg->tryBase, JSTryNote);
|
||
|
if (!count)
|
||
|
return;
|
||
|
|
||
|
memcpy(notes, cg->tryBase, TRYNOTE_SIZE(count));
|
||
|
notes[count].start = 0;
|
||
|
notes[count].length = CG_OFFSET(cg);
|
||
|
notes[count].catchStart = 0;
|
||
|
}
|