gecko-dev/js/src/jsemit.c

2810 lines
94 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* The contents of this file are subject to the Netscape 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/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr
* 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 Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the
* terms of the GNU Public License (the "GPL"), in which case the
* provisions of the GPL are applicable instead of those above.
* If you wish to allow use of your version of this file only
* under the terms of the GPL and not to allow others to use your
* version of this file under the NPL, indicate your decision by
* deleting the provisions above and replace them with the notice
* and other provisions required by the GPL. If you do not delete
* the provisions above, a recipient may use your version of this
* file under either the NPL or the GPL.
*/
/*
* 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 "jsprf.h"
#include "jsapi.h"
#include "jscntxt.h"
#include "jsconfig.h"
#include "jsemit.h"
#include "jsfun.h"
#include "jsnum.h"
#include "jsopcode.h"
#include "jsparse.h"
#include "jsscope.h"
#include "jsscript.h"
#define CGINCR 256 /* code allocation increment */
#define SNINCR 64 /* srcnote allocation increment */
#define TNINCR 64 /* trynote allocation increment */
#define CGINCR_SIZE (CGINCR * sizeof(jsbytecode))
#define SNINCR_SIZE (SNINCR * sizeof(jssrcnote))
#define TNINCR_SIZE (TNINCR * sizeof(JSTryNote))
JS_FRIEND_API(JSBool)
js_InitCodeGenerator(JSContext *cx, JSCodeGenerator *cg,
const char *filename, uintN lineno,
JSPrincipals *principals)
{
memset(cg, 0, sizeof *cg);
cg->codeMark = JS_ARENA_MARK(&cx->codePool);
cg->noteMark = JS_ARENA_MARK(&cx->notePool);
cg->tempMark = JS_ARENA_MARK(&cx->tempPool);
cg->current = &cg->main;
cg->filename = filename;
cg->firstLine = cg->currentLine = lineno;
cg->principals = principals;
TREE_CONTEXT_INIT(&cg->treeContext);
ATOM_LIST_INIT(&cg->atomList);
return JS_TRUE;
}
JS_FRIEND_API(void)
js_FinishCodeGenerator(JSContext *cx, JSCodeGenerator *cg)
{
JS_ARENA_RELEASE(&cx->codePool, cg->codeMark);
JS_ARENA_RELEASE(&cx->notePool, cg->noteMark);
JS_ARENA_RELEASE(&cx->tempPool, cg->tempMark);
TREE_CONTEXT_FREE(&cg->treeContext);
}
static ptrdiff_t
EmitCheck(JSContext *cx, JSCodeGenerator *cg, JSOp op, ptrdiff_t delta)
{
jsbytecode *base, *limit, *next;
ptrdiff_t offset, length;
size_t cgincr, cgsize;
base = CG_BASE(cg);
next = CG_NEXT(cg);
limit = CG_LIMIT(cg);
offset = PTRDIFF(next, base, jsbytecode);
if ((jsuword)(next + delta) > (jsuword)limit) {
length = PTRDIFF(limit, base, jsbytecode);
delta = JS_ROUNDUP(delta, CGINCR);
cgincr = delta * sizeof(jsbytecode);
if (base) {
cgsize = length * sizeof(jsbytecode);
JS_ARENA_GROW(base, &cx->codePool, cgsize, cgincr);
} else {
JS_ARENA_ALLOCATE(base, &cx->codePool, cgincr);
}
if (!base) {
JS_ReportOutOfMemory(cx);
return -1;
}
CG_BASE(cg) = base;
CG_LIMIT(cg) = base + length + delta;
CG_NEXT(cg) = base + offset;
}
return offset;
}
static void
UpdateDepth(JSContext *cx, JSCodeGenerator *cg, ptrdiff_t target)
{
jsbytecode *pc;
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;
if (cg->stackDepth < 0) {
char numBuf[12];
JS_snprintf(numBuf, sizeof numBuf, "%d", target);
JS_ReportErrorNumber(cx, 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;
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";
static const char *statementName[] = {
"block", /* BLOCK */
"label statement", /* LABEL */
"if statement", /* IF */
"else statement", /* ELSE */
"switch statement", /* SWITCH */
js_with_statement_str, /* WITH */
"try statement", /* TRY */
"catch block", /* CATCH */
"finally statement", /* FINALLY */
"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 "script";
return statementName[cg->treeContext.topStmt->type];
}
static void
ReportStatementTooLarge(JSContext *cx, JSCodeGenerator *cg)
{
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEED_DIET,
StatementName(cg));
}
JSBool
js_SetJumpOffset(JSContext *cx, JSCodeGenerator *cg, jsbytecode *pc,
ptrdiff_t off)
{
if (off < JUMP_OFFSET_MIN || JUMP_OFFSET_MAX < off) {
ReportStatementTooLarge(cx, cg);
return JS_FALSE;
}
SET_JUMP_OFFSET(pc, off);
return JS_TRUE;
}
JSBool
js_InWithStatement(JSTreeContext *tc)
{
JSStmtInfo *stmt;
for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
if (stmt->type == STMT_WITH)
return JS_TRUE;
}
return JS_FALSE;
}
JSBool
js_InCatchBlock(JSTreeContext *tc, JSAtom *atom)
{
JSStmtInfo *stmt;
for (stmt = tc->topStmt; stmt; stmt = stmt->down) {
if (stmt->type == STMT_CATCH && stmt->label == atom)
return JS_TRUE;
}
return JS_FALSE;
}
void
js_PushStatement(JSTreeContext *tc, JSStmtInfo *stmt, JSStmtType type,
ptrdiff_t top)
{
stmt->type = type;
SET_STATEMENT_TOP(stmt, top);
stmt->label = NULL;
stmt->down = tc->topStmt;
tc->topStmt = stmt;
if (!stmt->down)
tc->flags &= ~TCF_TOP_LEVEL;
}
/*
* Emit a jump op with offset pointing to the previous jump of this type,
* so that we can walk back up the chain fixing up the final destination.
*/
#define EMIT_CHAINED_JUMP(cx, cg, last, op, jmp) \
JS_BEGIN_MACRO \
ptrdiff_t offset, delta; \
offset = CG_OFFSET(cg); \
delta = offset - (last); \
last = offset; \
jmp = js_Emit3((cx), (cg), (op), JUMP_OFFSET_HI(delta), \
JUMP_OFFSET_LO(delta)); \
JS_END_MACRO
static ptrdiff_t
EmitGoto(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *toStmt,
ptrdiff_t *last, JSAtomListElement *label, JSSrcNoteType noteType)
{
JSStmtInfo *stmt;
intN index;
ptrdiff_t jmp;
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 -1;
EMIT_CHAINED_JUMP(cx, cg, stmt->gosub, JSOP_GOSUB, jmp);
if (jmp < 0)
return -1;
break;
case STMT_WITH:
case STMT_CATCH:
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return -1;
cg->stackDepth++;
if (js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0)
return -1;
break;
case STMT_FOR_IN_LOOP:
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return -1;
cg->stackDepth += 2;
if (js_Emit1(cx, cg, JSOP_POP2) < 0)
return -1;
break;
default:;
}
}
if (label) {
index = js_NewSrcNote(cx, cg, noteType);
if (index < 0)
return -1;
if (!js_SetSrcNoteOffset(cx, cg, (uintN)index, 0,
(ptrdiff_t) ALE_INDEX(label))) {
return -1;
}
}
EMIT_CHAINED_JUMP(cx, cg, *last, JSOP_GOTO, jmp);
return jmp;
}
static JSBool
PatchGotos(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *stmt,
ptrdiff_t last, jsbytecode *target, jsbytecode op)
{
jsbytecode *pc, *top;
ptrdiff_t delta, jumpOffset;
pc = CG_CODE(cg, last);
top = CG_CODE(cg, stmt->top);
while (pc != CG_CODE(cg, -1)) {
JS_ASSERT(*pc == op);
delta = GET_JUMP_OFFSET(pc);
jumpOffset = PTRDIFF(target, pc, jsbytecode);
CHECK_AND_SET_JUMP_OFFSET(cx, cg, pc, jumpOffset);
pc -= delta;
}
return JS_TRUE;
}
ptrdiff_t
js_EmitBreak(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *stmt,
JSAtomListElement *label)
{
return EmitGoto(cx, cg, stmt, &stmt->breaks, label, SRC_BREAK2LABEL);
}
ptrdiff_t
js_EmitContinue(JSContext *cx, JSCodeGenerator *cg, JSStmtInfo *stmt,
JSAtomListElement *label)
{
return EmitGoto(cx, cg, stmt, &stmt->continues, label, SRC_CONT2LABEL);
}
extern void
js_PopStatement(JSTreeContext *tc)
{
JSStmtInfo *stmt = tc->topStmt;
tc->topStmt = stmt->down;
if (!stmt->down)
tc->flags |= TCF_TOP_LEVEL;
}
JSBool
js_PopStatementCG(JSContext *cx, JSCodeGenerator *cg)
{
JSStmtInfo *stmt;
stmt = cg->treeContext.topStmt;
if (!PatchGotos(cx, cg, stmt, stmt->breaks, CG_NEXT(cg), JSOP_GOTO) ||
!PatchGotos(cx, cg, stmt, stmt->continues, CG_CODE(cg, stmt->update),
JSOP_GOTO)) {
return JS_FALSE;
}
js_PopStatement(&cg->treeContext);
return JS_TRUE;
}
/*
* Emit a bytecode and its 2-byte constant (atom) index immediate operand.
* NB: We use cx and cg from our caller's lexical environment, and return
* false on error.
*/
#define EMIT_ATOM_INDEX_OP(op, atomIndex) \
JS_BEGIN_MACRO \
if (js_Emit3(cx, cg, op, ATOM_INDEX_HI(atomIndex), \
ATOM_INDEX_LO(atomIndex)) < 0) { \
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;
EMIT_ATOM_INDEX_OP(op, ALE_INDEX(ale));
return JS_TRUE;
}
static JSBool
EmitPropOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg)
{
JSParseNode *pn2;
JSAtomListElement *ale;
pn2 = pn->pn_expr;
if (!js_EmitTree(cx, cg, pn2))
return JS_FALSE;
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(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 {
ale = js_IndexAtom(cx, pn->pn_atom, &cg->atomList);
if (!ale)
return JS_FALSE;
EMIT_ATOM_INDEX_OP(op, ALE_INDEX(ale));
}
return JS_TRUE;
}
static JSBool
EmitElemOp(JSContext *cx, JSParseNode *pn, JSOp op, JSCodeGenerator *cg)
{
JSParseNode *pn2;
pn2 = pn->pn_left;
if (!js_EmitTree(cx, cg, pn2) || !js_EmitTree(cx, cg, pn->pn_right))
return JS_FALSE;
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(CG_OFFSET(cg) - pn2->pn_offset)) < 0) {
return JS_FALSE;
}
return js_Emit1(cx, cg, op) >= 0;
}
static JSBool
EmitNumberOp(JSContext *cx, jsdouble dval, JSCodeGenerator *cg)
{
jsint ival;
jsatomid atomIndex;
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;
if ((jsuint)ival < (jsuint)ATOM_INDEX_LIMIT) {
atomIndex = (jsatomid)ival;
EMIT_ATOM_INDEX_OP(JSOP_UINT16, 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;
EMIT_ATOM_INDEX_OP(JSOP_NUMBER, ALE_INDEX(ale));
return JS_TRUE;
}
JSBool
js_EmitFunctionBody(JSContext *cx, JSCodeGenerator *cg, JSParseNode *body,
JSFunction *fun)
{
JSStackFrame *fp, frame;
JSObject *funobj;
JSBool ok;
if (!js_AllocTryNotes(cx, cg))
return JS_FALSE;
fp = cx->fp;
funobj = fun->object;
if (!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;
cx->fp = &frame;
}
ok = js_EmitTree(cx, cg, body);
cx->fp = fp;
if (!ok)
return JS_FALSE;
fun->script = js_NewScriptFromCG(cx, cg, fun);
if (!fun->script)
return JS_FALSE;
if (cg->treeContext.flags & TCF_FUN_HEAVYWEIGHT)
fun->flags |= JSFUN_HEAVYWEIGHT;
return JS_TRUE;
}
/* A macro for inlining at the top of js_EmitTree (whence it came). */
#define UPDATE_LINENO_NOTES(cx, cg, pn) \
JS_BEGIN_MACRO \
uintN lineno, delta; \
lineno = pn->pn_pos.begin.lineno; \
delta = lineno - cg->currentLine; \
cg->currentLine = lineno; \
if (delta) { \
/* \
* Encode any change in the current source line number by using \
* either several SRC_NEWLINE notes or one SRC_SETLINE note, \
* whichever consumes less space. \
*/ \
if (delta >= (uintN)(2 + ((lineno > SN_3BYTE_OFFSET_MASK) << 1))) { \
if (js_NewSrcNote2(cx, cg, SRC_SETLINE, (ptrdiff_t)lineno) < 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
UpdateLinenoNotes(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
{
UPDATE_LINENO_NOTES(cx, cg, pn);
return JS_TRUE;
}
/*
* This routine tries to optimize name gets and sets to stack slot loads and
* stores, given the variable 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 LookupArgOrVar also updated
* pn->pn_attrs and *opp.
*/
static JSBool
LookupArgOrVar(JSContext *cx, JSTreeContext *tc, JSParseNode *pn, JSOp *opp)
{
JSObject *obj, *pobj;
JSClass *clasp;
JSAtom *atom;
JSScopeProperty *sprop;
JSOp op;
JS_ASSERT(pn->pn_type == TOK_NAME);
/*
* We can't optimize if var and function collide.
* XXX suboptimal: keep track of colliding names and deoptimize only those
*/
if (tc->flags & TCF_FUN_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 = cx->fp->varobj;
clasp = OBJ_GET_CLASS(cx, obj);
if (clasp != &js_FunctionClass && clasp != &js_CallClass)
return JS_TRUE;
/*
* We can't optimize if we're in an eval called inside a with statement,
* or we're compiling a with statement and its body, or we're in a catch
* block whose exception variable has the same name as pn.
*/
atom = pn->pn_atom;
if (cx->fp->scopeChain != obj ||
js_InWithStatement(tc) ||
js_InCatchBlock(tc, atom)) {
return JS_TRUE;
}
/*
* Ok, we may be able to optimize name to stack slot. 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 (atom == cx->runtime->atomState.argumentsAtom) {
if (!(tc->flags & TCF_FUN_SETS_ARGS)) {
JS_ASSERT(*opp == JSOP_NAME);
*opp = JSOP_ARGUMENTS;
}
return JS_TRUE;
}
/*
* Look for an argument or variable property in the function, or its call
* object, not found in any prototype object. Rewrite *opp and update pn
* accordingly. NB: We know that JSOP_DELNAME on an argument or variable
* must evaluate to false, due to JSPROP_PERMANENT.
*/
if (!js_LookupProperty(cx, obj, (jsid)atom, &pobj, (JSProperty **)&sprop))
return JS_FALSE;
op = *opp;
if (sprop) {
if (pobj == obj) {
JSPropertyOp getter = SPROP_GETTER(sprop, pobj);
if (getter == js_GetArgument) {
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);
}
} 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_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);
}
}
if (op != *opp) {
*opp = op;
pn->pn_slot = JSVAL_TO_INT(sprop->id);
}
pn->pn_attrs = sprop->attrs;
}
OBJ_DROP_PROPERTY(cx, pobj, (JSProperty *)sprop);
}
return JS_TRUE;
}
JSBool
js_EmitTree(JSContext *cx, JSCodeGenerator *cg, JSParseNode *pn)
{
JSBool ok;
JSCodeGenerator cg2;
JSStmtInfo *stmt, stmtInfo;
ptrdiff_t top, off, tmp, beq, jmp;
JSParseNode *pn2, *pn3, *pn4;
JSAtom *atom;
JSAtomListElement *ale;
jsatomid atomIndex;
intN noteIndex;
JSOp op;
uint32 argc;
pn->pn_offset = top = CG_OFFSET(cg);
/* Emit notes to tell the current bytecode's source line number. */
UPDATE_LINENO_NOTES(cx, cg, pn);
switch (pn->pn_type) {
case TOK_FUNCTION:
{
JSFunction *fun;
/* Generate code for the function's body. */
pn2 = pn->pn_body;
if (!js_InitCodeGenerator(cx, &cg2, cg->filename,
pn->pn_pos.begin.lineno,
cg->principals)) {
return JS_FALSE;
}
cg2.treeContext.flags = pn->pn_flags;
cg2.treeContext.tryCount = pn->pn_tryCount;
fun = pn->pn_fun;
if (!js_EmitFunctionBody(cx, &cg2, pn2, fun))
return JS_FALSE;
js_FinishCodeGenerator(cx, &cg2);
/* Make the function object a literal in the outer script's pool. */
atom = js_AtomizeObject(cx, fun->object, 0);
if (!atom)
return JS_FALSE;
ale = js_IndexAtom(cx, atom, &cg->atomList);
if (!ale)
return JS_FALSE;
atomIndex = ALE_INDEX(ale);
#if JS_HAS_LEXICAL_CLOSURE
/* 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;
}
#endif
/* 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. */
CG_SWITCH_TO_PROLOG(cg);
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 variable 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:
/* Emit code for the condition before pushing stmtInfo. */
if (!js_EmitTree(cx, cg, pn->pn_kid1))
return JS_FALSE;
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_IF, CG_OFFSET(cg));
/* Emit an annotated branch-if-false around the then part. */
noteIndex = js_NewSrcNote(cx, cg, SRC_IF);
if (noteIndex < 0)
return JS_FALSE;
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 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;
pn3 = pn->pn_kid3;
if (pn3) {
/* Modify stmtInfo and the branch-if-false source note. */
stmtInfo.type = STMT_ELSE;
SN_SET_TYPE(&cg->notes[noteIndex], SRC_IF_ELSE);
/* Jump at end of then part around the else part. */
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
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 (!js_EmitTree(cx, cg, pn3))
return JS_FALSE;
/* Fixup the jump around the else part. */
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
} else {
/* No else part, fixup the branch-if-false to come here. */
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
}
return js_PopStatementCG(cx, cg);
#if JS_HAS_SWITCH_STATEMENT
case TOK_SWITCH:
{
JSOp switchop;
uint32 ncases, tablen = 0;
JSScript *script;
jsint i, low, high;
jsdouble d;
size_t switchsize, tablesize;
void *mark;
JSParseNode **table;
jsbytecode *pc;
JSBool hasDefault = JS_FALSE;
JSBool isEcmaSwitch = cx->version == JSVERSION_DEFAULT ||
cx->version >= JSVERSION_1_4;
ptrdiff_t defaultOffset = -1;
/* Try for most optimal, fall back if not dense ints, and per ECMAv2. */
switchop = JSOP_TABLESWITCH;
/* Emit code for the discriminant first. */
if (!js_EmitTree(cx, cg, pn->pn_kid1))
return JS_FALSE;
/* Switch bytecodes run from here till end of final case. */
top = CG_OFFSET(cg);
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_SWITCH, top);
pn2 = pn->pn_kid2;
ncases = pn2->pn_count;
if (pn2->pn_count == 0) {
low = high = 0;
tablen = 0;
ok = JS_TRUE;
} else {
low = JSVAL_INT_MAX;
high = JSVAL_INT_MIN;
cg2.current = NULL;
for (pn3 = pn2->pn_head; pn3; pn3 = pn3->pn_next) {
if (pn3->pn_type == TOK_DEFAULT) {
hasDefault = JS_TRUE;
ncases--; /* one of the "cases" was the default */
continue;
}
JS_ASSERT(pn3->pn_type == TOK_CASE);
pn4 = pn3->pn_left;
if (isEcmaSwitch) {
if (switchop == JSOP_CONDSWITCH)
continue;
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)
return JS_FALSE;
pn3->pn_val = ATOM_KEY(atom);
}
break;
case TOK_STRING:
pn3->pn_val = ATOM_KEY(pn4->pn_atom);
break;
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;
}
} else {
/* Pre-ECMAv2 switch evals case exprs at compile time. */
if (!js_InitCodeGenerator(cx, &cg2, cg->filename,
pn3->pn_pos.begin.lineno,
cg->principals)) {
return JS_FALSE;
}
cg2.currentLine = pn4->pn_pos.begin.lineno;
if (!js_EmitTree(cx, &cg2, pn4))
return JS_FALSE;
if (js_Emit1(cx, &cg2, JSOP_POPV) < 0)
return JS_FALSE;
script = js_NewScriptFromCG(cx, &cg2, NULL);
if (!script)
return JS_FALSE;
ok = js_Execute(cx, cx->fp->scopeChain, script, NULL,
cx->fp, 0, &pn3->pn_val);
js_DestroyScript(cx, script);
if (!ok)
return JS_FALSE;
}
if (!JSVAL_IS_NUMBER(pn3->pn_val) &&
!JSVAL_IS_STRING(pn3->pn_val) &&
!JSVAL_IS_BOOLEAN(pn3->pn_val)) {
char numBuf[12];
JS_snprintf(numBuf, sizeof numBuf, "%u",
pn4->pn_pos.begin.lineno);
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL,
JSMSG_BAD_CASE,
cg2.filename ? cg2.filename : "stdin",
numBuf);
return JS_FALSE;
}
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;
}
if (switchop == JSOP_CONDSWITCH) {
JS_ASSERT(!cg2.current);
} else {
if (cg2.current)
js_FinishCodeGenerator(cx, &cg2);
if (switchop == JSOP_TABLESWITCH) {
tablen = (uint32)(high - low + 1);
if (tablen >= JS_BIT(16) || tablen > 2 * ncases)
switchop = JSOP_LOOKUPSWITCH;
}
}
}
/*
* 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)(6 + 2 * tablen);
} 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)(4 + 4 * ncases);
}
/* Emit switchop and switchsize bytes of jump or lookup table. */
if (js_EmitN(cx, cg, switchop, switchsize) < 0)
return JS_FALSE;
off = -1;
if (switchop == JSOP_CONDSWITCH) {
intN caseNoteIndex = -1;
/* 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 (pn3->pn_type == TOK_DEFAULT)
continue;
caseNoteIndex = js_NewSrcNote2(cx, cg, SRC_PCDELTA, 0);
if (caseNoteIndex < 0)
return JS_FALSE;
off = js_Emit3(cx, cg, JSOP_CASE, 0, 0);
if (off < 0)
return JS_FALSE;
pn3->pn_offset = off;
if (pn3 == pn2->pn_head) {
/* Switch note's second offset is to first JSOP_CASE. */
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 1,
off - top)) {
return JS_FALSE;
}
}
}
/* Emit default even if no explicit default statement. */
defaultOffset = js_Emit3(cx, cg, JSOP_DEFAULT, 0, 0);
if (defaultOffset < 0)
return JS_FALSE;
}
/* 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) {
pn3->pn_val = INT_TO_JSVAL(pn3->pn_offset - top);
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, pn3->pn_offset);
}
pn4 = pn3->pn_right;
if (!js_EmitTree(cx, cg, pn4))
return JS_FALSE;
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). */
pc = NULL;
if (switchop == JSOP_CONDSWITCH) {
JS_ASSERT(defaultOffset != -1);
if (!js_SetJumpOffset(cx, cg, CG_CODE(cg, defaultOffset),
off - (defaultOffset - top))) {
return JS_FALSE;
}
} else {
pc = CG_CODE(cg, top);
if (!js_SetJumpOffset(cx, cg, pc, off))
return JS_FALSE;
pc += 2;
}
/* Set the SRC_SWITCH note's offset operand to tell end of switch. */
off = CG_OFFSET(cg) - top;
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0, off))
return JS_FALSE;
if (switchop == JSOP_TABLESWITCH) {
/* Fill in jump table. */
if (!js_SetJumpOffset(cx, cg, pc, low))
return JS_FALSE;
pc += 2;
if (!js_SetJumpOffset(cx, cg, pc, high))
return JS_FALSE;
pc += 2;
if (tablen) {
/* Avoid bloat for a compilation unit with many switches. */
mark = JS_ARENA_MARK(&cx->tempPool);
tablesize = (size_t)tablen * sizeof *table;
JS_ARENA_ALLOCATE(table, &cx->tempPool, tablesize);
if (!table) {
JS_ReportOutOfMemory(cx);
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 < tablen);
table[i] = pn3;
}
for (i = 0; i < (jsint)tablen; i++) {
pn3 = table[i];
off = pn3 ? pn3->pn_offset - top : 0;
if (!js_SetJumpOffset(cx, cg, pc, off))
return JS_FALSE;
pc += 2;
}
JS_ARENA_RELEASE(&cx->tempPool, mark);
}
} else if (switchop == JSOP_LOOKUPSWITCH) {
/* Fill in lookup table. */
SET_ATOM_INDEX(pc, ncases);
pc += 2;
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)
return JS_FALSE;
ale = js_IndexAtom(cx, atom, &cg->atomList);
if (!ale)
return JS_FALSE;
SET_ATOM_INDEX(pc, ALE_INDEX(ale));
pc += 2;
off = pn3->pn_offset - top;
if (!js_SetJumpOffset(cx, cg, pc, off))
return JS_FALSE;
pc += 2;
}
}
return js_PopStatementCG(cx, cg);
}
#endif /* JS_HAS_SWITCH_STATEMENT */
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 = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0)
return JS_FALSE;
if (!js_EmitTree(cx, cg, pn->pn_right))
return JS_FALSE;
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET(cx, cg, CG_CODE(cg,jmp), top - jmp);
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
return js_PopStatementCG(cx, cg);
#if JS_HAS_DO_WHILE_LOOP
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;
/* Re-use the SRC_WHILE note, this time for the JSOP_IFNE opcode. */
if (js_NewSrcNote(cx, cg, SRC_WHILE) < 0)
return JS_FALSE;
jmp = top - CG_OFFSET(cg);
if (js_Emit3(cx, cg, JSOP_IFNE,
JUMP_OFFSET_HI(jmp), JUMP_OFFSET_LO(jmp)) < 0) {
return JS_FALSE;
}
return js_PopStatementCG(cx, cg);
#endif /* JS_HAS_DO_WHILE_LOOP */
case TOK_FOR:
pn2 = pn->pn_left;
js_PushStatement(&cg->treeContext, &stmtInfo, STMT_FOR_LOOP, top);
if (pn2->pn_type == TOK_IN) {
/* If the left part is var x = i, bind x, evaluate i, and pop. */
pn3 = pn2->pn_left;
if (pn3->pn_type == TOK_VAR && pn3->pn_head->pn_expr) {
if (!js_EmitTree(cx, cg, pn3))
return JS_FALSE;
/* Set pn3 to the variable name, to avoid another var note. */
pn3 = pn3->pn_head;
JS_ASSERT(pn3->pn_type == TOK_NAME);
}
/* Fix stmtInfo and emit a push to allocate the iterator. */
stmtInfo.type = STMT_FOR_IN_LOOP;
noteIndex = -1;
if (js_Emit1(cx, cg, JSOP_PUSH) < 0)
return JS_FALSE;
/* Compile the object expression to the right of 'in'. */
if (!js_EmitTree(cx, cg, pn2->pn_right))
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_TOOBJECT) < 0)
return JS_FALSE;
top = CG_OFFSET(cg);
SET_STATEMENT_TOP(&stmtInfo, top);
/* Compile a JSOP_FOR* bytecode based on the left hand side. */
switch (pn3->pn_type) {
case TOK_VAR:
pn3 = pn3->pn_head;
if (js_NewSrcNote(cx, cg, SRC_VAR) < 0)
return JS_FALSE;
/* FALL THROUGH */
case TOK_NAME:
op = JSOP_FORNAME;
if (!LookupArgOrVar(cx, &cg->treeContext, pn3, &op))
return JS_FALSE;
pn3->pn_op = op;
if (pn3->pn_slot >= 0) {
if (pn3->pn_attrs & JSPROP_READONLY)
op = JSOP_GETVAR;
atomIndex = (jsatomid) pn3->pn_slot;
EMIT_ATOM_INDEX_OP(op, atomIndex);
} else {
if (!EmitAtomOp(cx, pn3, op, cg))
return JS_FALSE;
}
break;
case TOK_DOT:
if (!EmitPropOp(cx, pn3, JSOP_FORPROP, cg))
return JS_FALSE;
break;
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).
*/
if (!js_Emit1(cx, cg, JSOP_FORELEM))
return JS_FALSE;
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0)
return JS_FALSE;
if (!EmitElemOp(cx, pn3, JSOP_ENUMELEM, cg))
return JS_FALSE;
break;
default:
JS_ASSERT(0);
}
if (pn3->pn_type != TOK_LB) {
/* Pop and test the loop condition generated by JSOP_FOR*. */
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0)
return JS_FALSE;
}
} else {
if (!pn2->pn_kid1) {
/* No initializer: emit an annotated nop for the decompiler. */
noteIndex = js_NewSrcNote(cx, cg, SRC_FOR);
if (noteIndex < 0 ||
js_Emit1(cx, cg, JSOP_NOP) < 0) {
return JS_FALSE;
}
} else {
if (!js_EmitTree(cx, cg, pn2->pn_kid1))
return JS_FALSE;
noteIndex = js_NewSrcNote(cx, cg, SRC_FOR);
if (noteIndex < 0 ||
js_Emit1(cx, cg, JSOP_POP) < 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;
beq = 0;
} else {
if (!js_EmitTree(cx, cg, pn2->pn_kid2))
return JS_FALSE;
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 0,
(ptrdiff_t)(CG_OFFSET(cg) - top))) {
return JS_FALSE;
}
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (beq < 0)
return JS_FALSE;
}
}
/* 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,
(ptrdiff_t)(CG_OFFSET(cg) - top))) {
return JS_FALSE;
}
pn3 = pn2->pn_kid3;
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);
if (!js_EmitTree(cx, cg, pn3))
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_POP) < 0)
return JS_FALSE;
/* Restore the absolute line number for source note readers. */
off = (ptrdiff_t) pn->pn_pos.end.lineno;
if (cg->currentLine != (uintN) off) {
if (js_NewSrcNote2(cx, cg, SRC_SETLINE, off) < 0)
return JS_FALSE;
cg->currentLine = (uintN) off;
}
}
/* The third note offset helps us find the loop-closing jump. */
if (!js_SetSrcNoteOffset(cx, cg, (uintN)noteIndex, 2,
(ptrdiff_t)(CG_OFFSET(cg) - top))) {
return JS_FALSE;
}
}
/* Emit the loop-closing jump and fixup all jump offsets. */
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET(cx, cg, CG_CODE(cg,jmp), top - jmp);
if (beq > 0)
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
/* Now fixup all breaks and continues (before for/in's final POP2). */
if (!js_PopStatementCG(cx, cg))
return JS_FALSE;
if (pn2->pn_type == TOK_IN) {
/*
* Generate the object and iterator pop opcodes after popping the
* stmtInfo stack, so breaks will go to this pop bytecode.
*/
if (pn3->pn_type != TOK_LB) {
if (js_Emit1(cx, cg, JSOP_POP2) < 0)
return JS_FALSE;
} else {
/*
* With 'for(x[i]...)', there's only the object on the stack,
* so we need to hide the pop.
*/
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_POP) < 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->label != atom)
stmt = stmt->down;
} else {
ale = NULL;
while (!STMT_IS_LOOP(stmt) && stmt->type != STMT_SWITCH)
stmt = stmt->down;
}
if (js_EmitBreak(cx, cg, stmt, ale) < 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->label != atom) {
if (STMT_IS_LOOP(stmt))
loop = stmt;
stmt = stmt->down;
}
stmt = loop;
} else {
ale = NULL;
while (!STMT_IS_LOOP(stmt))
stmt = stmt->down;
if (js_NewSrcNote(cx, cg, SRC_CONTINUE) < 0)
return JS_FALSE;
}
if (js_EmitContinue(cx, cg, stmt, ale) < 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;
return js_PopStatementCG(cx, cg);
#if JS_HAS_EXCEPTIONS
case TOK_TRY: {
ptrdiff_t start, end;
ptrdiff_t catchStart = -1, finallyCatch = -1, catchjmp = -1;
JSParseNode *iter;
uint16 depth;
/* Emit JSOP_GOTO that points to the first op after the catch/finally blocks */
#define EMIT_CATCH_GOTO(cx, cg, jmp) \
EMIT_CHAINED_JUMP(cx, cg, stmtInfo.catchJump, JSOP_GOTO, jmp);
/* Emit JSOP_GOSUB that points to the finally block. */
#define EMIT_FINALLY_GOSUB(cx, cg, jmp) \
EMIT_CHAINED_JUMP(cx, cg, stmtInfo.gosub, JSOP_GOSUB, jmp);
/*
* 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.
* EMIT_CHAINED_JUMP and PatchGotos).
*/
js_PushStatement(&cg->treeContext, &stmtInfo,
pn->pn_kid3 ? STMT_FINALLY : STMT_BLOCK,
CG_OFFSET(cg));
/*
* About JSOP_SETSP:
* An exception can be thrown while the stack is in an unbalanced
* state, and this 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_NewSrcNote2(cx, cg, SRC_TRYFIN, 0) < 0 ||
js_Emit1(cx, cg, JSOP_NOP) < 0)
return JS_FALSE;
start = CG_OFFSET(cg);
if(!js_EmitTree(cx, cg, pn->pn_kid1))
return JS_FALSE;
/* Emit (hidden) jump over catch and/or finally. */
if (pn->pn_kid3) {
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
EMIT_FINALLY_GOSUB(cx, cg, jmp);
if (jmp < 0)
return JS_FALSE;
}
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
EMIT_CATCH_GOTO(cx, cg, jmp);
if (jmp < 0)
return JS_FALSE;
end = CG_OFFSET(cg);
/* If this try has a catch block, emit it. */
iter = pn->pn_kid2;
if (iter) {
catchStart = end;
/*
* The emitted code for a catch block looks like:
*
* [ popscope ] only if 2nd+ catch block
* name Object
* pushobj
* newinit
* exception
* initcatchvar <atom>
* enterwith
* [< catchguard code >] if there's a catchguard
* [ifeq <offset to next catch block>] " "
* < catch block contents >
* leavewith
* 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 (;;) {
JSStmtInfo stmtInfo2;
JSParseNode *disc;
ptrdiff_t guardnote;
if (!UpdateLinenoNotes(cx, cg, iter))
return JS_FALSE;
if (catchjmp != -1) {
/* Fix up and clean up previous catch block. */
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, catchjmp);
if ((uintN)++cg->stackDepth > cg->maxStackDepth)
cg->maxStackDepth = cg->stackDepth;
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0)
return JS_FALSE;
} else {
/* Set stack to original depth (see SETSP comment above). */
EMIT_ATOM_INDEX_OP(JSOP_SETSP, (jsatomid)depth);
}
/* Non-zero guardnote is length of catchguard. */
guardnote = js_NewSrcNote2(cx, cg, SRC_CATCH, 0);
if (guardnote < 0 || js_Emit1(cx, cg, JSOP_NOP) < 0)
return JS_FALSE;
/* Construct the scope holder and push it on. */
ale = js_IndexAtom(cx, cx->runtime->atomState.ObjectAtom,
&cg->atomList);
if (!ale)
return JS_FALSE;
EMIT_ATOM_INDEX_OP(JSOP_NAME, ALE_INDEX(ale));
if (js_Emit1(cx, cg, JSOP_PUSHOBJ) < 0 ||
js_Emit1(cx, cg, JSOP_NEWINIT) < 0 ||
js_Emit1(cx, cg, JSOP_EXCEPTION) < 0) {
return JS_FALSE;
}
/* initcatchvar <atomIndex> */
disc = iter->pn_kid1;
ale = js_IndexAtom(cx, disc->pn_atom, &cg->atomList);
if (!ale)
return JS_FALSE;
EMIT_ATOM_INDEX_OP(JSOP_INITCATCHVAR, ALE_INDEX(ale));
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_ENTERWITH) < 0) {
return JS_FALSE;
}
/* boolean_expr */
if (disc->pn_expr) {
ptrdiff_t guardstart = CG_OFFSET(cg);
if (!js_EmitTree(cx, cg, disc->pn_expr))
return JS_FALSE;
/* ifeq <next block> */
catchjmp = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
if (catchjmp < 0)
return JS_FALSE;
if (!js_SetSrcNoteOffset(cx, cg, guardnote, 0,
(ptrdiff_t)CG_OFFSET(cg) -
guardstart)) {
return JS_FALSE;
}
}
/* Emit catch block. */
js_PushStatement(&cg->treeContext, &stmtInfo2, STMT_CATCH,
CG_OFFSET(cg));
stmtInfo2.label = disc->pn_atom;
if (!js_EmitTree(cx, cg, iter->pn_kid3))
return JS_FALSE;
js_PopStatementCG(cx, cg);
/*
* Jump over the remaining catch blocks.
* This counts as a non-local jump, so do the finally thing.
*/
/* popscope */
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0) {
return JS_FALSE;
}
/* gosub <finally>, if required */
if (pn->pn_kid3) {
EMIT_FINALLY_GOSUB(cx, cg, jmp);
if (jmp < 0)
return JS_FALSE;
}
/* This will get fixed up to jump to after catch/finally. */
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0)
return JS_FALSE;
EMIT_CATCH_GOTO(cx, cg, jmp);
if (jmp < 0)
return JS_FALSE;
if (!iter->pn_kid2) /* leave iter at last catch */
break;
iter = iter->pn_kid2;
}
}
/*
* we use a [leavewith],[gosub],rethrow block for rethrowing
* when there's no unguarded catch, and also for
* running finally code while letting an uncaught exception
* pass through
*/
if (pn->pn_kid3 ||
(catchjmp != -1 && iter->pn_kid1->pn_expr)) {
/*
* Emit another stack fix, because the catch could itself
* throw an exception in an unbalanced state, and the finally
* may need to call functions etc.
*/
EMIT_ATOM_INDEX_OP(JSOP_SETSP, (jsatomid)depth);
if (catchjmp != -1 && iter->pn_kid1->pn_expr) {
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, catchjmp);
}
/* last discriminant jumps to rethrow if none match */
if ((uintN)++cg->stackDepth > cg->maxStackDepth)
cg->maxStackDepth = cg->stackDepth;
if (pn->pn_kid2 &&
(js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_LEAVEWITH) < 0))
return JS_FALSE;
if (pn->pn_kid3) {
finallyCatch = CG_OFFSET(cg);
EMIT_FINALLY_GOSUB(cx, cg, jmp);
if (jmp < 0)
return JS_FALSE;
}
if (js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_EXCEPTION) < 0 ||
js_NewSrcNote(cx, cg, SRC_HIDDEN) < 0 ||
js_Emit1(cx, cg, JSOP_THROW) < 0)
return JS_FALSE;
}
/*
* If we've got a finally, it goes here, and we have to fix up
* the gosubs that might have been emitted before non-local jumps.
*/
if (pn->pn_kid3) {
if (!PatchGotos(cx, cg, &stmtInfo, stmtInfo.gosub, CG_NEXT(cg),
JSOP_GOSUB))
return JS_FALSE;
js_PopStatementCG(cx, cg);
if (!UpdateLinenoNotes(cx, cg, pn->pn_kid3))
return JS_FALSE;
if (js_NewSrcNote2(cx, cg, SRC_TRYFIN, 1) < 0 ||
js_Emit1(cx, cg, JSOP_NOP) < 0 ||
!js_EmitTree(cx, cg, pn->pn_kid3) ||
js_Emit1(cx, cg, JSOP_RETSUB) < 0)
return JS_FALSE;
} else {
js_PopStatementCG(cx, cg);
}
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 (!PatchGotos(cx, cg, &stmtInfo, stmtInfo.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, inner to outer).
*/
if (pn->pn_kid2 &&
!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_NewTryNote(cx, cg, start, finallyCatch-1, finallyCatch)) {
return JS_FALSE;
}
break;
}
#endif /* JS_HAS_EXCEPTIONS */
case TOK_VAR:
off = noteIndex = -1;
for (pn2 = pn->pn_head; ; pn2 = pn2->pn_next) {
JS_ASSERT(pn2->pn_type == TOK_NAME);
op = pn2->pn_op;
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 a prolog bytecode to predefine the var w/ void value. */
CG_SWITCH_TO_PROLOG(cg);
EMIT_ATOM_INDEX_OP(pn->pn_op, atomIndex);
CG_SWITCH_TO_MAIN(cg);
}
if (pn2->pn_expr) {
if (op == JSOP_SETNAME)
EMIT_ATOM_INDEX_OP(JSOP_BINDNAME, atomIndex);
if (!js_EmitTree(cx, cg, pn2->pn_expr))
return JS_FALSE;
}
if (pn2 == pn->pn_head &&
js_NewSrcNote(cx, cg,
(pn->pn_op == JSOP_DEFCONST)
? SRC_CONST
: SRC_VAR) < 0) {
return JS_FALSE;
}
EMIT_ATOM_INDEX_OP(op, atomIndex);
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 (pn->pn_extra) {
if (js_Emit1(cx, cg, JSOP_POP) < 0)
return JS_FALSE;
}
break;
case TOK_RETURN:
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;
}
if (js_Emit1(cx, cg, JSOP_RETURN) < 0)
return JS_FALSE;
break;
case TOK_LC:
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;
}
return js_PopStatementCG(cx, cg);
case TOK_SEMI:
if (pn->pn_kid) {
if (!js_EmitTree(cx, cg, pn->pn_kid))
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_POPV) < 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;
noteIndex = js_NewSrcNote2(cx, cg,
(pn2->pn_type == TOK_LC)
? SRC_LABELBRACE
: SRC_LABEL,
(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.label = 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 (pn2->pn_type == TOK_LC) {
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; /* Suppress warning. */
switch (pn2->pn_type) {
case TOK_NAME:
if (!LookupArgOrVar(cx, &cg->treeContext, pn2, &pn2->pn_op))
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:
if (!js_EmitTree(cx, cg, pn2->pn_left))
return JS_FALSE;
if (!js_EmitTree(cx, cg, pn2->pn_right))
return JS_FALSE;
break;
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. */
} 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_ATOM_INDEX_OP((pn2->pn_op == JSOP_SETARG)
? JSOP_GETARG
: JSOP_GETVAR,
atomIndex);
break;
}
/* FALL THROUGH */
case TOK_DOT:
if (js_Emit1(cx, cg, JSOP_DUP) < 0)
return JS_FALSE;
EMIT_ATOM_INDEX_OP(JSOP_GETPROP, atomIndex);
break;
case TOK_LB:
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) {
if (js_NewSrcNote(cx, cg, SRC_ASSIGNOP) < 0 ||
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_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(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 || !(pn2->pn_attrs & JSPROP_READONLY)) {
case TOK_DOT:
EMIT_ATOM_INDEX_OP(pn2->pn_op, atomIndex);
}
break;
case TOK_LB:
if (js_Emit1(cx, cg, JSOP_SETELEM) < 0)
return JS_FALSE;
break;
default:;
}
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;
if (js_NewSrcNote(cx, cg, SRC_COND) < 0)
return JS_FALSE;
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 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 = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
if (!js_EmitTree(cx, cg, pn->pn_kid3))
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
break;
case TOK_OR:
/* Emit left operand, emit pop-if-converts-to-false-else-jump. */
if (!js_EmitTree(cx, cg, pn->pn_left))
return JS_FALSE;
#if JS_BUG_SHORT_CIRCUIT
beq = js_Emit3(cx, cg, JSOP_IFEQ, 0, 0);
tmp = js_Emit1(cx, cg, JSOP_TRUE);
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (beq < 0 || tmp < 0 || jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
#else
/*
* 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.
*/
jmp = js_Emit3(cx, cg, JSOP_OR, 0, 0);
if (jmp < 0)
return JS_FALSE;
#endif
if (!js_EmitTree(cx, cg, pn->pn_right))
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
break;
case TOK_AND:
/* && is like || except it uses a pop-if-converts-to-true-else-jump. */
if (!js_EmitTree(cx, cg, pn->pn_left))
return JS_FALSE;
#if JS_BUG_SHORT_CIRCUIT
beq = js_Emit3(cx, cg, JSOP_IFNE, 0, 0);
tmp = js_Emit1(cx, cg, JSOP_FALSE);
jmp = js_Emit3(cx, cg, JSOP_GOTO, 0, 0);
if (beq < 0 || tmp < 0 || jmp < 0)
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, beq);
#else
jmp = js_Emit3(cx, cg, JSOP_AND, 0, 0);
if (jmp < 0)
return JS_FALSE;
#endif
if (!js_EmitTree(cx, cg, pn->pn_right))
return JS_FALSE;
CHECK_AND_SET_JUMP_OFFSET_AT(cx, cg, jmp);
break;
case TOK_BITOR:
case TOK_BITXOR:
case TOK_BITAND:
case TOK_EQOP:
case TOK_RELOP:
#if JS_HAS_IN_OPERATOR
case TOK_IN:
#endif
#if JS_HAS_INSTANCEOF
case TOK_INSTANCEOF:
#endif
case TOK_SHOP:
case TOK_PLUS:
case TOK_MINUS:
case TOK_STAR:
case TOK_DIVOP:
/* 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_Emit1(cx, cg, pn->pn_op) < 0)
return JS_FALSE;
break;
#if JS_HAS_EXCEPTIONS
case TOK_THROW:
#endif
case TOK_UNARYOP:
/* Unary op, including 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;
case TOK_INC:
case TOK_DEC:
/* Emit lvalue-specialized code for ++/-- operators. */
pn2 = pn->pn_kid;
JS_ASSERT(pn2->pn_type != TOK_RP);
op = pn->pn_op;
switch (pn2->pn_type) {
case TOK_NAME:
if (!LookupArgOrVar(cx, &cg->treeContext, pn2, &op))
return JS_FALSE;
pn->pn_op = op;
if (pn2->pn_slot >= 0) {
if (pn2->pn_attrs & JSPROP_READONLY)
op = JSOP_GETVAR;
atomIndex = (jsatomid) pn2->pn_slot;
EMIT_ATOM_INDEX_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;
break;
case TOK_LB:
if (!EmitElemOp(cx, pn2, op, cg))
return JS_FALSE;
break;
default:
JS_ASSERT(0);
}
break;
case TOK_NEW:
/* Code for (new f()) and f() are the same, except for the opcode. */
op = JSOP_NEW;
goto emit_call;
case TOK_DELETE:
/* Under ECMA 3, deleting a non-reference returns true. */
pn2 = pn->pn_kid;
switch (pn2->pn_type) {
case TOK_NAME:
op = JSOP_DELNAME;
if (!LookupArgOrVar(cx, &cg->treeContext, pn2, &op))
return JS_FALSE;
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;
case TOK_LB:
if (!EmitElemOp(cx, pn2, JSOP_DELELEM, cg))
return JS_FALSE;
break;
default:
if (js_Emit1(cx, cg, JSOP_TRUE) < 0)
return JS_FALSE;
}
break;
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.
*/
return EmitPropOp(cx, pn, pn->pn_op, cg);
case TOK_LB:
/*
* 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.
*/
return EmitElemOp(cx, pn, pn->pn_op, cg);
case TOK_LP:
/*
* Emit function call or operator new (constructor call) code. First
* emit code for the left operand to evaluate the call- or construct-
* able object expression.
*/
op = JSOP_CALL;
emit_call:
pn2 = pn->pn_head;
if (pn2->pn_op == JSOP_NAME &&
pn2->pn_atom == cx->runtime->atomState.evalAtom) {
if (JSVERSION_IS_ECMA(cx->version))
op = JSOP_EVAL;
cg->treeContext.flags |= TCF_FUN_HEAVYWEIGHT;
}
if (!js_EmitTree(cx, cg, pn2))
return JS_FALSE;
/* Remember start of callable-object bytecode for decompilation hint. */
off = pn2->pn_offset;
/*
* 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;
/*
* 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.
*/
for (pn2 = pn2->pn_next; pn2; pn2 = pn2->pn_next) {
if (!js_EmitTree(cx, cg, pn2))
return JS_FALSE;
}
argc = pn->pn_count - 1;
if (js_NewSrcNote2(cx, cg, SRC_PCBASE,
(ptrdiff_t)(CG_OFFSET(cg) - off)) < 0 ||
js_Emit3(cx, cg, op, ARGC_HI(argc), ARGC_LO(argc)) < 0)
return JS_FALSE;
break;
#if JS_HAS_INITIALIZERS
case TOK_RB:
/*
* 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, cx->runtime->atomState.ArrayAtom,
&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_ATOM_INDEX_OP(JSOP_DEFSHARP, (jsatomid)pn2->pn_num);
pn2 = pn2->pn_next;
}
#endif
for (atomIndex = 0; pn2; pn2 = pn2->pn_next) {
/* PrimaryExpr enforced ATOM_INDEX_LIMIT, so in-line optimize. */
JS_ASSERT(atomIndex < ATOM_INDEX_LIMIT);
if (atomIndex == 0) {
if (js_Emit1(cx, cg, JSOP_ZERO) < 0)
return JS_FALSE;
} else if (atomIndex == 1) {
if (js_Emit1(cx, cg, JSOP_ONE) < 0)
return JS_FALSE;
} else {
EMIT_ATOM_INDEX_OP(JSOP_UINT16, (jsatomid)atomIndex);
}
/* Sub-optimal: 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;
atomIndex++;
}
if (pn->pn_extra) {
/* 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, cx->runtime->atomState.ObjectAtom,
&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_ATOM_INDEX_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 (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_LABEL) < 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_ATOM_INDEX_OP(JSOP_DEFSHARP, (jsatomid) pn->pn_num);
break;
case TOK_USESHARP:
EMIT_ATOM_INDEX_OP(JSOP_USESHARP, (jsatomid) pn->pn_num);
break;
#endif /* JS_HAS_SHARP_VARS */
#endif /* JS_HAS_INITIALIZERS */
case TOK_RP:
/*
* 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.
*/
if (!js_EmitTree(cx, cg, pn->pn_kid))
return JS_FALSE;
if (js_Emit1(cx, cg, JSOP_GROUP) < 0)
return JS_FALSE;
break;
case TOK_NAME:
if (!LookupArgOrVar(cx, &cg->treeContext, pn, &pn->pn_op))
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_ATOM_INDEX_OP(op, atomIndex);
break;
}
/* FALL THROUGH */
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.
*/
return EmitAtomOp(cx, pn, pn->pn_op, cg);
case TOK_NUMBER:
return EmitNumberOp(cx, pn->pn_dval, cg);
case TOK_PRIMARY:
return js_Emit1(cx, cg, pn->pn_op) >= 0;
#if JS_HAS_DEBUGGER_KEYWORD
case TOK_DEBUGGER:
return js_Emit1(cx, cg, JSOP_DEBUGGER) >= 0;
#endif /* JS_HAS_DEBUGGER_KEYWORD */
default:
JS_ASSERT(0);
}
return JS_TRUE;
}
JS_FRIEND_DATA(const char *) js_SrcNoteName[] = {
"null",
"if",
"if-else",
"while",
"for",
"continue",
"var",
"pcdelta",
"assignop",
"cond",
"reserved0",
"hidden",
"pcbase",
"label",
"labelbrace",
"endbrace",
"break2label",
"cont2label",
"switch",
"funcdef",
"tryfin",
"catch",
"const",
"reserved1",
"reserved2",
"reserved3",
"newline",
"setline",
"xdelta"
};
uint8 js_SrcNoteArity[] = {
0, /* SRC_NULL */
0, /* SRC_IF */
0, /* SRC_IF_ELSE */
0, /* SRC_WHILE */
3, /* SRC_FOR */
0, /* SRC_CONTINUE */
0, /* SRC_VAR */
1, /* SRC_PCDELTA */
0, /* SRC_ASSIGNOP */
0, /* SRC_COND */
0, /* SRC_RESERVED0 */
0, /* SRC_HIDDEN */
1, /* SRC_PCBASE */
1, /* SRC_LABEL */
1, /* SRC_LABELBRACE */
0, /* SRC_ENDBRACE */
1, /* SRC_BREAK2LABEL */
1, /* SRC_CONT2LABEL */
2, /* SRC_SWITCH */
1, /* SRC_FUNCDEF */
1, /* SRC_TRYFIN */
1, /* SRC_CATCH */
0, /* SRC_CONST */
0, /* SRC_RESERVED1 */
0, /* SRC_RESERVED2 */
0, /* SRC_RESERVED3 */
0, /* SRC_NEWLINE */
1, /* SRC_SETLINE */
0 /* SRC_XDELTA */
};
static intN
AllocSrcNote(JSContext *cx, JSCodeGenerator *cg)
{
intN index;
JSArenaPool *pool;
size_t incr, size;
index = cg->noteCount;
if ((uintN)index % SNINCR == 0) {
pool = &cx->notePool;
incr = SNINCR_SIZE;
if (!cg->notes) {
JS_ARENA_ALLOCATE(cg->notes, pool, incr);
} else {
size = cg->noteCount * sizeof(jssrcnote);
JS_ARENA_GROW(cg->notes, pool, size, incr);
}
if (!cg->notes) {
JS_ReportOutOfMemory(cx);
return -1;
}
}
cg->noteCount = 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 by growing it if necessary and then
* incrementing cg->noteCount.
*/
index = AllocSrcNote(cx, cg);
sn = &cg->notes[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->lastNoteOffset;
cg->lastNoteOffset = 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[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.
*/
SN_MAKE_NOTE(sn, type, delta);
for (n = (intN)js_SrcNoteArity[type]; 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 incr, size;
pool = &cx->notePool;
incr = SNINCR_SIZE;
size = cg->noteCount * sizeof(jssrcnote);
size = JS_ROUNDUP(size, incr);
JS_ARENA_GROW(cg->notes, pool, size, incr);
if (!cg->notes) {
JS_ReportOutOfMemory(cx);
return JS_FALSE;
}
return JS_TRUE;
}
uintN
js_SrcNoteLength(jssrcnote *sn)
{
uintN arity;
jssrcnote *base;
arity = (intN)js_SrcNoteArity[SN_TYPE(sn)];
for (base = sn++; arity--; sn++) {
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_SrcNoteArity[SN_TYPE(sn)]);
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 (offset >= (((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[index];
JS_ASSERT(SN_TYPE(sn) != SRC_XDELTA);
JS_ASSERT(which < js_SrcNoteArity[SN_TYPE(sn)]);
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, 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->noteCount + 1) % SNINCR <= 1) {
if (!GrowSrcNotes(cx, cg))
return JS_FALSE;
sn = cg->notes + index;
}
cg->noteCount += 2;
diff = cg->noteCount - (index + 3);
JS_ASSERT(diff >= 0);
if (diff > 0)
memmove(sn + 3, sn + 1, diff * sizeof(jssrcnote));
}
*sn++ = (jssrcnote)(SN_3BYTE_OFFSET_FLAG | (offset >> 16));
*sn++ = (jssrcnote)(offset >> 8);
}
*sn = (jssrcnote)offset;
return JS_TRUE;
}
jssrcnote *
js_FinishTakingSrcNotes(JSContext *cx, JSCodeGenerator *cg)
{
uintN count;
jssrcnote *tmp, *final;
count = cg->noteCount;
tmp = cg->notes;
final = (jssrcnote *) JS_malloc(cx, (count + 1) * sizeof(jssrcnote));
if (!final)
return NULL;
memcpy(final, tmp, count * sizeof(jssrcnote));
SN_MAKE_TERMINATOR(&final[count]);
return final;
}
JSBool
js_AllocTryNotes(JSContext *cx, JSCodeGenerator *cg)
{
size_t size, incr;
ptrdiff_t delta;
size = cg->treeContext.tryCount * sizeof(JSTryNote);
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.
*/
if (!cg->tryBase) {
size = JS_ROUNDUP(size, TNINCR_SIZE);
JS_ARENA_ALLOCATE(cg->tryBase, &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, TNINCR_SIZE);
size = cg->tryNoteSpace;
JS_ARENA_GROW(cg->tryBase, &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;
}
JSBool
js_FinishTakingTryNotes(JSContext *cx, JSCodeGenerator *cg, JSTryNote **tryp)
{
uintN count;
JSTryNote *tmp, *final;
count = PTRDIFF(cg->tryNext, cg->tryBase, JSTryNote);
if (!count) {
*tryp = NULL;
return JS_TRUE;
}
tmp = cg->tryBase;
final = (JSTryNote *) JS_malloc(cx, (count + 1) * sizeof(JSTryNote));
if (!final) {
*tryp = NULL;
return JS_FALSE;
}
memcpy(final, tmp, count * sizeof(JSTryNote));
final[count].start = 0;
final[count].length = CG_OFFSET(cg);
final[count].catchStart = 0;
*tryp = final;
return JS_TRUE;
}