mirror of
https://bitbucket.org/smil3y/kdelibs.git
synced 2025-02-23 18:32:49 +00:00
1678 lines
51 KiB
C++
1678 lines
51 KiB
C++
/*
|
|
* This file is part of the KDE libraries
|
|
* Copyright (C) 1999-2002 Harri Porten (porten@kde.org)
|
|
* Copyright (C) 2001 Peter Kelly (pmk@post.com)
|
|
* Copyright (C) 2003, 2004, 2005, 2006, 2007 Apple Inc. All rights reserved.
|
|
* Copyright (C) 2007, 2008 Maksim Orlovich (maksim@kde.org)
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public License
|
|
* along with this library; see the file COPYING.LIB. If not, write to
|
|
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
#include "nodes2bytecode.h"
|
|
#include "CompileState.h"
|
|
#include <wtf/Assertions.h>
|
|
|
|
#include <typeinfo>
|
|
#include <iostream>
|
|
|
|
namespace KJS {
|
|
|
|
// A few helpers..
|
|
static void emitError(CompileState* comp, Node* node, ErrorType type, const char* msgStr)
|
|
{
|
|
OpValue me = OpValue::immNode(node);
|
|
OpValue se = OpValue::immInt32(type);
|
|
OpValue msg = OpValue::immCStr(msgStr);
|
|
CodeGen::emitOp(comp, Op_RaiseError, 0, &me, &se, &msg);
|
|
}
|
|
|
|
static void emitSyntaxError(CompileState* comp, Node* node, const char* msgStr)
|
|
{
|
|
emitError(comp, node, SyntaxError, msgStr);
|
|
}
|
|
|
|
static void emitReferenceError(CompileState* comp, Node* node, const char* msgStr)
|
|
{
|
|
emitError(comp, node, ReferenceError, msgStr);
|
|
}
|
|
|
|
|
|
OpValue Node::generateEvalCode(CompileState*)
|
|
{
|
|
std::cerr << "WARNING: no generateEvalCode for:" << typeid(*this).name() << "\n";
|
|
ASSERT(0);
|
|
|
|
return OpValue::immInt32(42);
|
|
}
|
|
|
|
void StatementNode::generateExecCode(CompileState*)
|
|
{
|
|
std::cerr << "WARNING: no generateExecCode for:" << typeid(*this).name() << "\n";
|
|
ASSERT(0);
|
|
}
|
|
|
|
void StatementNode::generateDebugInfo(CompileState* comp)
|
|
{
|
|
OpValue me = OpValue::immNode(this);
|
|
CodeGen::emitOp(comp, Op_AtStatement, 0, &me);
|
|
}
|
|
|
|
static inline bool exitContextNeeded(CompileState* comp) {
|
|
return comp->compileType() == Debug &&
|
|
comp->codeType() == FunctionCode;
|
|
}
|
|
|
|
static void generateExitContextIfNeeded(CompileState* comp) {
|
|
if (exitContextNeeded(comp)) {
|
|
OpValue ourNode = OpValue::immNode(comp->functionBody());
|
|
CodeGen::emitOp(comp, Op_ExitDebugContext, 0, &ourNode);
|
|
}
|
|
}
|
|
|
|
// ------------------------------ Basic literals -----------------------------------------
|
|
|
|
OpValue NullNode::generateEvalCode(CompileState*)
|
|
{
|
|
return OpValue::immValue(jsNull());
|
|
}
|
|
|
|
OpValue BooleanNode::generateEvalCode(CompileState*)
|
|
{
|
|
return OpValue::immBool(value());
|
|
}
|
|
|
|
OpValue NumberNode::generateEvalCode(CompileState*)
|
|
{
|
|
#if 0
|
|
if (typeHint == OpType_Value) {
|
|
// Try to fit into a JSValue if at all possible..
|
|
JSValue* im = JSImmediate::from(value());
|
|
if (im) {
|
|
OpValue res = mkImmediateVal(OpType_value);
|
|
return res;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Numeric-like..
|
|
double d = value();
|
|
int32_t i32 = JSValue::toInt32(d);
|
|
if (double(i32) == d && !(i32 == 0 && signbit(d))) // be careful with -0.0 here
|
|
return OpValue::immInt32(i32);
|
|
else
|
|
return OpValue::immNumber(d);
|
|
}
|
|
|
|
|
|
OpValue StringNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
// For now, just generate a JSValue
|
|
// We may want to permit string pointers as well, to help overload resolution,
|
|
// but it's not clear whether that's useful, since we can't MM them. Perhaps
|
|
// a special StringInstance type may be of use eventually.
|
|
|
|
if (interned) // we're re-compiling.. just reuse it
|
|
return OpValue::immValue(interned);
|
|
|
|
// Intern shorter strings
|
|
if (val.size() < 16) {
|
|
interned = Interpreter::internString(val);
|
|
return OpValue::immValue(interned);
|
|
} else {
|
|
OpValue inStr = OpValue::immString(&val);
|
|
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_OwnedString, &out, &inStr);
|
|
return out;
|
|
}
|
|
}
|
|
|
|
StringNode::~StringNode()
|
|
{
|
|
if (interned)
|
|
Interpreter::releaseInternedString(val);
|
|
}
|
|
|
|
OpValue RegExpNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
// ### TODO: cache the engine object?
|
|
OpValue out;
|
|
OpValue patternV = OpValue::immString(&pattern);
|
|
OpValue flagsV = OpValue::immString(&flags);
|
|
CodeGen::emitOp(comp, Op_NewRegExp, &out, &patternV, &flagsV);
|
|
return out;
|
|
}
|
|
|
|
OpValue ThisNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
return *comp->thisValue();
|
|
}
|
|
|
|
// ------------------------------ VarAccessNode ----------------------------------------
|
|
|
|
size_t VarAccessNode::classifyVariable(CompileState* comp, Classification& classify)
|
|
{
|
|
// Are we inside a with or catch? In that case, it's all dynamic. Boo.
|
|
// Ditto for eval.
|
|
// ### actually that may be improvable if we can
|
|
// distinguish eval-from-global and eval-from-local, since
|
|
// we'd have an activation or global object available for access.
|
|
if (comp->inNestedScope() || comp->codeType() == EvalCode) {
|
|
classify = Dynamic;
|
|
return missingSymbolMarker();
|
|
}
|
|
|
|
// If we're inside global scope (and as per above, not inside any nested scope!)
|
|
// we can always used the global object
|
|
if (comp->codeType() == GlobalCode) {
|
|
classify = Global;
|
|
return missingSymbolMarker();
|
|
}
|
|
|
|
// We're inside a function...
|
|
if (ident == CommonIdentifiers::shared()->arguments) {
|
|
// arguments is too much of a pain to handle in general path..
|
|
classify = Dynamic;
|
|
return missingSymbolMarker();
|
|
}
|
|
|
|
// Do we know this?
|
|
size_t index = comp->functionBody()->lookupSymbolID(ident);
|
|
if (index == missingSymbolMarker())
|
|
classify = NonLocal;
|
|
else
|
|
classify = Local;
|
|
|
|
return index;
|
|
}
|
|
|
|
OpValue VarAccessNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
Classification classify;
|
|
size_t index = classifyVariable(comp, classify);
|
|
|
|
OpValue out;
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
switch (classify) {
|
|
case Local: {
|
|
// Register read.
|
|
out = comp->localReadVal(index);
|
|
break;
|
|
}
|
|
case NonLocal:
|
|
CodeGen::emitOp(comp, Op_NonLocalVarGet, &out, &varName);
|
|
break;
|
|
case Global:
|
|
CodeGen::emitOp(comp, Op_GlobalObjectGet, &out, &varName);
|
|
break;
|
|
case Dynamic:
|
|
CodeGen::emitOp(comp, Op_VarGet, &out, &varName);
|
|
break;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
OpValue VarAccessNode::valueForTypeOf(CompileState* comp)
|
|
{
|
|
// ### some code dupe here.
|
|
Classification classify;
|
|
size_t index = classifyVariable(comp, classify);
|
|
|
|
OpValue scopeTemp;
|
|
OpValue out, outReg;
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
switch (classify) {
|
|
case Local:
|
|
// Register read. Easy.
|
|
out = comp->localReadVal(index);
|
|
break;
|
|
case Global:
|
|
CodeGen::emitOp(comp, Op_SymGetKnownObject, &out, comp->globalScope(), &varName);
|
|
break;
|
|
case NonLocal:
|
|
comp->requestTemporary(OpType_value, &out, &outReg);
|
|
CodeGen::emitOp(comp, Op_NonLocalScopeLookupAndGet, &scopeTemp, &outReg, &varName);
|
|
break;
|
|
case Dynamic:
|
|
comp->requestTemporary(OpType_value, &out, &outReg);
|
|
CodeGen::emitOp(comp, Op_ScopeLookupAndGet, &scopeTemp, &outReg, &varName);
|
|
break;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
CompileReference* VarAccessNode::generateRefBind(CompileState* comp)
|
|
{
|
|
Classification classify;
|
|
classifyVariable(comp, classify);
|
|
|
|
if (classify == Local || classify == Global)
|
|
return 0; // nothing to do, we know where it is
|
|
|
|
// Otherwise, we need to find the scope for writing
|
|
CompileReference* ref = new CompileReference;
|
|
|
|
OpValue quiet = OpValue::immNode(0);
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
CodeGen::emitOp(comp, classify == Dynamic ? Op_ScopeLookup : Op_NonLocalScopeLookup,
|
|
&ref->baseObj, &varName, &quiet);
|
|
return ref;
|
|
}
|
|
|
|
CompileReference* VarAccessNode::generateRefRead(CompileState* comp, OpValue* out)
|
|
{
|
|
Classification classify;
|
|
classifyVariable(comp, classify);
|
|
|
|
// We want to bind and read, also issuing an error.
|
|
|
|
// If we don't need any binding, just use normal read code..
|
|
if (classify == Local || classify == Global) {
|
|
*out = generateEvalCode(comp);
|
|
return 0;
|
|
}
|
|
|
|
// For others, use the lookup-and-fetch ops
|
|
CompileReference* ref = new CompileReference;
|
|
|
|
OpValue readReg;
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
comp->requestTemporary(OpType_value, out, &readReg);
|
|
|
|
OpName op;
|
|
if (classify == Dynamic)
|
|
op = Op_ScopeLookupAndGetChecked;
|
|
else
|
|
op = Op_NonLocalScopeLookupAndGetChecked;
|
|
CodeGen::emitOp(comp, op, &ref->baseObj, &readReg, &varName);
|
|
|
|
return ref;
|
|
}
|
|
|
|
void VarAccessNode::generateRefWrite(CompileState* comp,
|
|
CompileReference* ref, OpValue& valToStore)
|
|
{
|
|
Classification classify;
|
|
size_t index = classifyVariable(comp, classify);
|
|
|
|
if (classify == Local) {
|
|
// Straight register put..
|
|
OpValue destReg = comp->localWriteRef(comp->codeBlock(), index);
|
|
CodeGen::emitOp(comp, Op_RegPutValue, 0, &destReg, &valToStore);
|
|
} else {
|
|
// Symbolic write to the appropriate scope..
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
CodeGen::emitOp(comp, Op_SymPutKnownObject, 0,
|
|
(classify == Global ? comp->globalScope() : &ref->baseObj), &varName, &valToStore);
|
|
}
|
|
}
|
|
|
|
OpValue VarAccessNode::generateRefDelete(CompileState* comp)
|
|
{
|
|
Classification classify;
|
|
classifyVariable(comp, classify);
|
|
|
|
if (classify == Local) {
|
|
// Normal locals are DontDelete, so this always fails.
|
|
return OpValue::immBool(false);
|
|
}
|
|
|
|
// Otherwise, fetch the appropriate scope
|
|
OpValue base;
|
|
if (classify == Global) {
|
|
base = *comp->globalScope();
|
|
} else {
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
OpValue silent = OpValue::immNode(0);
|
|
CodeGen::emitOp(comp, classify == Dynamic ? Op_ScopeLookup : Op_NonLocalScopeLookup,
|
|
&base, &varName, &silent);
|
|
}
|
|
|
|
// Remove the property..
|
|
OpValue out;
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
CodeGen::emitOp(comp, Op_SymDeleteKnownObject, &out, &base, &varName);
|
|
return out;
|
|
}
|
|
|
|
void VarAccessNode::generateRefFunc(CompileState* comp, OpValue* funOut, OpValue* thisOut)
|
|
{
|
|
Classification classify;
|
|
classifyVariable(comp, classify);
|
|
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
|
|
OpValue thisReg;
|
|
switch (classify) {
|
|
case Local:
|
|
case Global:
|
|
// Both of these use global object for this, and use straightforward lookup for value
|
|
*funOut = generateEvalCode(comp);
|
|
*thisOut = *comp->globalScope();
|
|
break;
|
|
case NonLocal:
|
|
comp->requestTemporary(OpType_value, thisOut, &thisReg);
|
|
CodeGen::emitOp(comp, Op_NonLocalFunctionLookupAndGet, funOut, &thisReg, &varName);
|
|
break;
|
|
case Dynamic:
|
|
comp->requestTemporary(OpType_value, thisOut, &thisReg);
|
|
CodeGen::emitOp(comp, Op_FunctionLookupAndGet, funOut, &thisReg, &varName);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ------------------------------ GroupNode----------------------------------------
|
|
|
|
OpValue GroupNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
return group->generateEvalCode(comp);
|
|
}
|
|
|
|
// ------------------------------ Object + Array literals --------------------------
|
|
|
|
OpValue ArrayNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue arr;
|
|
CodeGen::emitOp(comp, Op_NewEmptyArray, &arr);
|
|
|
|
OpValue und = OpValue::immValue(jsUndefined());
|
|
|
|
int pos = 0;
|
|
for (ElementNode* el = element.get(); el; el = el->next.get()) {
|
|
if (!el->node) {
|
|
// Fill elision w/undefined, unless we can just skip over to a value
|
|
for (int i = 0; i < el->elision; i++) {
|
|
OpValue ind = OpValue::immInt32(pos);
|
|
CodeGen::emitOp(comp, Op_BracketPutKnownObject, 0, &arr, &ind, &und);
|
|
++pos;
|
|
}
|
|
} else {
|
|
pos += el->elision;
|
|
}
|
|
|
|
if (el->node) {
|
|
OpValue val = el->node->generateEvalCode(comp);
|
|
OpValue ind = OpValue::immInt32(pos);
|
|
CodeGen::emitOp(comp, Op_BracketPutKnownObject, 0, &arr, &ind, &val);
|
|
++pos;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < elision; i++) {
|
|
OpValue ind = OpValue::immInt32(pos);
|
|
CodeGen::emitOp(comp, Op_BracketPutKnownObject, 0, &arr, &ind, &und);
|
|
++pos;
|
|
}
|
|
|
|
return arr;
|
|
}
|
|
|
|
OpValue ObjectLiteralNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue obj;
|
|
CodeGen::emitOp(comp, Op_NewObject, &obj);
|
|
|
|
for (PropertyListNode* entry = list.get(); entry; entry = entry->next.get()) {
|
|
PropertyNode* prop = entry->node.get();
|
|
OpValue name = OpValue::immIdent(&prop->name->str);
|
|
OpValue val = prop->assign->generateEvalCode(comp);
|
|
|
|
switch (prop->type) {
|
|
case PropertyNode::Getter:
|
|
CodeGen::emitOp(comp, Op_DefineGetter, 0, &obj, &name, &val);
|
|
break;
|
|
case PropertyNode::Setter:
|
|
CodeGen::emitOp(comp, Op_DefineSetter, 0, &obj, &name, &val);
|
|
break;
|
|
case PropertyNode::Constant:
|
|
CodeGen::emitOp(comp, Op_SymPutKnownObject, 0, &obj, &name, &val);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return obj;
|
|
}
|
|
|
|
// ------------------------------ BracketAccessorNode --------------------------------
|
|
OpValue BracketAccessorNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue ret;
|
|
OpValue base = expr1->generateEvalCode(comp);
|
|
OpValue index = expr2->generateEvalCode(comp);
|
|
|
|
// ### optimize foo["bar"] ?
|
|
CodeGen::emitOp(comp, Op_BracketGet, &ret, &base, &index);
|
|
return ret;
|
|
}
|
|
|
|
CompileReference* BracketAccessorNode::generateRefBind(CompileState* comp)
|
|
{
|
|
// Per 11.2.1, the following steps must happen when evaluating foo[bar]
|
|
// 1) eval foo
|
|
// 2) eval bar
|
|
// 3) call toObject on [[foo]]
|
|
// 4) call toString on [[bar]]
|
|
// ... all of which are part of reference evaluation. Fun.
|
|
// ### FIXME FIXME FIXME: we don't do step 4 in right spot yet!
|
|
CompileReference* ref = new CompileReference;
|
|
OpValue baseV = expr1->generateEvalCode(comp);
|
|
ref->indexVal = expr2->generateEvalCode(comp);
|
|
CodeGen::emitOp(comp, Op_ToObject, &ref->baseObj, &baseV);
|
|
return ref;
|
|
}
|
|
|
|
CompileReference* BracketAccessorNode::generateRefRead(CompileState* comp, OpValue* out)
|
|
{
|
|
CompileReference* ref = new CompileReference;
|
|
|
|
// ### As above, this sequence should store the toString on reference, if there will be a follow up
|
|
// write --- need a hint for that..
|
|
OpValue baseV = expr1->generateEvalCode(comp);
|
|
ref->indexVal = expr2->generateEvalCode(comp);
|
|
|
|
// Store the object for future use.
|
|
OpValue baseReg;
|
|
comp->requestTemporary(OpType_value, &ref->baseObj, &baseReg);
|
|
|
|
CodeGen::emitOp(comp, Op_BracketGetAndBind, out, &baseReg, &baseV, &ref->indexVal);
|
|
return ref;
|
|
}
|
|
|
|
void BracketAccessorNode::generateRefWrite(CompileState* comp,
|
|
CompileReference* ref, OpValue& valToStore)
|
|
{
|
|
CodeGen::emitOp(comp, Op_BracketPutKnownObject, 0, &ref->baseObj, &ref->indexVal, &valToStore);
|
|
}
|
|
|
|
OpValue BracketAccessorNode::generateRefDelete(CompileState* comp)
|
|
{
|
|
OpValue base = expr1->generateEvalCode(comp);
|
|
OpValue index = expr2->generateEvalCode(comp);
|
|
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_BracketDelete, &out, &base, &index);
|
|
return out;
|
|
}
|
|
|
|
void BracketAccessorNode::generateRefFunc(CompileState* comp, OpValue* funOut, OpValue* thisOut)
|
|
{
|
|
OpValue baseV = expr1->generateEvalCode(comp);
|
|
OpValue indexV = expr2->generateEvalCode(comp);
|
|
|
|
// We need to memorize the toObject for 'this'
|
|
OpValue baseReg;
|
|
comp->requestTemporary(OpType_value, thisOut, &baseReg);
|
|
|
|
CodeGen::emitOp(comp, Op_BracketGetAndBind, funOut, &baseReg, &baseV, &indexV);
|
|
}
|
|
|
|
// ------------------------------ DotAccessorNode --------------------------------
|
|
|
|
// ECMA 11.2.1b
|
|
OpValue DotAccessorNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue ret;
|
|
OpValue base = expr->generateEvalCode(comp);
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
CodeGen::emitOp(comp, Op_SymGet, &ret, &base, &varName);
|
|
return ret;
|
|
}
|
|
|
|
CompileReference* DotAccessorNode::generateRefBind(CompileState* comp)
|
|
{
|
|
CompileReference* ref = new CompileReference;
|
|
OpValue baseV = expr->generateEvalCode(comp);
|
|
CodeGen::emitOp(comp, Op_ToObject, &ref->baseObj, &baseV);
|
|
return ref;
|
|
}
|
|
|
|
CompileReference* DotAccessorNode::generateRefRead(CompileState* comp, OpValue* out)
|
|
{
|
|
CompileReference* ref = new CompileReference;
|
|
OpValue baseV = expr->generateEvalCode(comp);
|
|
OpValue baseReg;
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
comp->requestTemporary(OpType_value, &ref->baseObj, &baseReg);
|
|
CodeGen::emitOp(comp, Op_SymGetAndBind, out, &baseReg, &baseV, &varName);
|
|
return ref;
|
|
}
|
|
|
|
void DotAccessorNode::generateRefWrite(CompileState* comp,
|
|
CompileReference* ref, OpValue& valToStore)
|
|
{
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
CodeGen::emitOp(comp, Op_SymPutKnownObject, 0, &ref->baseObj, &varName, &valToStore);
|
|
}
|
|
|
|
OpValue DotAccessorNode::generateRefDelete(CompileState* comp)
|
|
{
|
|
OpValue base = expr->generateEvalCode(comp);
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_SymDelete, &out, &base, &varName);
|
|
return out;
|
|
}
|
|
|
|
void DotAccessorNode::generateRefFunc(CompileState* comp, OpValue* funOut, OpValue* thisOut)
|
|
{
|
|
OpValue baseV = expr->generateEvalCode(comp);
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
|
|
OpValue baseReg;
|
|
comp->requestTemporary(OpType_value, thisOut, &baseReg);
|
|
CodeGen::emitOp(comp, Op_SymGetAndBind, funOut, &baseReg, &baseV, &varName);
|
|
}
|
|
|
|
// ------------------ ........
|
|
|
|
void ArgumentsNode::generateEvalArguments(CompileState* comp)
|
|
{
|
|
WTF::Vector<OpValue> args;
|
|
|
|
// We need evaluate arguments and push them in separate steps as there may be
|
|
// function/ctor calls inside.
|
|
for (ArgumentListNode* arg = list.get(); arg; arg = arg->next.get()) {
|
|
args.append(arg->expr->generateEvalCode(comp));
|
|
}
|
|
|
|
CodeGen::emitOp(comp, Op_ClearArgs, 0);
|
|
|
|
size_t c = 0;
|
|
while (c < args.size()) {
|
|
if (c + 3 <= args.size()) {
|
|
CodeGen::emitOp(comp, Op_Add3Arg, 0, &args[c], &args[c+1], &args[c+2]);
|
|
c += 3;
|
|
} else if (c + 2 <= args.size()) {
|
|
CodeGen::emitOp(comp, Op_Add2Arg, 0, &args[c], &args[c+1]);
|
|
c += 2;
|
|
} else {
|
|
CodeGen::emitOp(comp, Op_AddArg, 0, &args[c]);
|
|
c += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
OpValue NewExprNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue v = expr->generateEvalCode(comp);
|
|
|
|
if (args)
|
|
args->generateEvalArguments(comp);
|
|
else
|
|
CodeGen::emitOp(comp, Op_ClearArgs, 0);
|
|
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_CtorCall, &out, &v);
|
|
return out;
|
|
}
|
|
|
|
OpValue FunctionCallValueNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue v = expr->generateEvalCode(comp);
|
|
args->generateEvalArguments(comp);
|
|
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_FunctionCall, &out, &v, comp->globalScope());
|
|
return out;
|
|
}
|
|
|
|
OpValue FunctionCallReferenceNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
Node* cand = expr->nodeInsideAllParens();
|
|
ASSERT(cand->isLocation());
|
|
LocationNode* loc = static_cast<LocationNode*>(cand);
|
|
|
|
OpValue funVal, thisVal;
|
|
loc->generateRefFunc(comp, &funVal, &thisVal);
|
|
args->generateEvalArguments(comp);
|
|
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_FunctionCall, &out, &funVal, &thisVal);
|
|
return out;
|
|
}
|
|
|
|
OpValue PostfixNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
Node* cand = m_loc->nodeInsideAllParens();
|
|
if (!cand->isLocation()) {
|
|
emitReferenceError(comp, this,
|
|
m_oper == OpPlusPlus ?
|
|
"Postfix ++ operator applied to value that is not a reference." :
|
|
"Postfix -- operator applied to value that is not a reference.");
|
|
return OpValue::immValue(jsUndefined());
|
|
}
|
|
|
|
LocationNode* loc = static_cast<LocationNode*>(cand);
|
|
|
|
// ### we want to fold this in if the kid is a local -- any elegant way?
|
|
|
|
//read current value
|
|
OpValue curV;
|
|
CompileReference* ref = loc->generateRefRead(comp, &curV);
|
|
|
|
// We need it to be a number..
|
|
if (curV.type != OpType_number) {
|
|
OpValue numVal;
|
|
CodeGen::emitConvertTo(comp, &curV, OpType_number, &numVal);
|
|
curV = numVal;
|
|
}
|
|
|
|
// Compute new one
|
|
OpValue newV;
|
|
CodeGen::emitOp(comp, (m_oper == OpPlusPlus) ? Op_Add1 : Op_Sub1,
|
|
&newV, &curV);
|
|
|
|
loc->generateRefWrite(comp, ref, newV);
|
|
delete ref;
|
|
return curV;
|
|
}
|
|
|
|
OpValue DeleteReferenceNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
return loc->generateRefDelete(comp);
|
|
}
|
|
|
|
OpValue DeleteValueNode::generateEvalCode(CompileState*)
|
|
{
|
|
return OpValue::immBool(true);
|
|
}
|
|
|
|
OpValue VoidNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
(void)expr->generateEvalCode(comp);
|
|
return OpValue::immValue(jsUndefined());
|
|
}
|
|
|
|
OpValue TypeOfVarNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue v = loc->valueForTypeOf(comp);
|
|
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_TypeOf, &out, &v);
|
|
return out;
|
|
}
|
|
|
|
OpValue TypeOfValueNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue v = m_expr->generateEvalCode(comp);
|
|
OpValue typeOfV;
|
|
CodeGen::emitOp(comp, Op_TypeOf, &typeOfV, &v);
|
|
return typeOfV;
|
|
}
|
|
|
|
OpValue PrefixNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
Node* cand = m_loc->nodeInsideAllParens();
|
|
if (!cand->isLocation()) {
|
|
emitReferenceError(comp, this,
|
|
m_oper == OpPlusPlus ?
|
|
"Prefix ++ operator applied to value that is not a reference." :
|
|
"Prefix -- operator applied to value that is not a reference.");
|
|
return OpValue::immValue(jsUndefined());
|
|
}
|
|
|
|
LocationNode* loc = static_cast<LocationNode*>(cand);
|
|
|
|
// ### we want to fold this in if the kid is a local -- any elegant way?
|
|
|
|
//read current value
|
|
OpValue curV;
|
|
CompileReference* ref = loc->generateRefRead(comp, &curV);
|
|
|
|
OpValue newV;
|
|
CodeGen::emitOp(comp, (m_oper == OpPlusPlus) ? Op_Add1 : Op_Sub1,
|
|
&newV, &curV);
|
|
|
|
// Write out + return new value.
|
|
loc->generateRefWrite(comp, ref, newV);
|
|
delete ref;
|
|
return newV;
|
|
}
|
|
|
|
OpValue UnaryPlusNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
// This is basically just a number cast
|
|
OpValue curV = expr->generateEvalCode(comp);
|
|
|
|
if (curV.type != OpType_number) {
|
|
OpValue numVal;
|
|
CodeGen::emitConvertTo(comp, &curV, OpType_number, &numVal);
|
|
curV = numVal;
|
|
}
|
|
|
|
return curV;
|
|
}
|
|
|
|
OpValue NegateNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue v = expr->generateEvalCode(comp);
|
|
OpValue negV;
|
|
CodeGen::emitOp(comp, Op_Neg, &negV, &v);
|
|
return negV;
|
|
}
|
|
|
|
OpValue BitwiseNotNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue v = expr->generateEvalCode(comp);
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_BitNot, &out, &v);
|
|
return out;
|
|
}
|
|
|
|
OpValue LogicalNotNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue v = expr->generateEvalCode(comp);
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, Op_LogicalNot, &out, &v);
|
|
return out;
|
|
}
|
|
|
|
OpValue BinaryOperatorNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
OpValue v1 = expr1->generateEvalCode(comp);
|
|
OpValue v2 = expr2->generateEvalCode(comp);
|
|
|
|
OpName codeOp; // ### could perhaps skip conversion entirely,
|
|
// and set these in the parser?
|
|
switch (oper) {
|
|
case OpMult:
|
|
// operator *
|
|
codeOp = Op_Mult;
|
|
break;
|
|
case OpDiv:
|
|
// operator /
|
|
codeOp = Op_Div;
|
|
break;
|
|
case OpMod:
|
|
// operator %
|
|
codeOp = Op_Mod;
|
|
break;
|
|
case OpPlus:
|
|
// operator +
|
|
codeOp = Op_Add;
|
|
break;
|
|
case OpMinus:
|
|
// operator -
|
|
codeOp = Op_Sub;
|
|
break;
|
|
case OpLShift:
|
|
// operator <<
|
|
codeOp = Op_LShift;
|
|
break;
|
|
case OpRShift:
|
|
// operator >>
|
|
codeOp = Op_RShift;
|
|
break;
|
|
case OpURShift:
|
|
// operator >>>
|
|
codeOp = Op_URShift;
|
|
break;
|
|
case OpLess:
|
|
// operator <
|
|
codeOp = Op_Less;
|
|
break;
|
|
case OpGreaterEq:
|
|
// operator >=
|
|
codeOp = Op_GreaterEq;
|
|
break;
|
|
case OpGreater:
|
|
// operator >
|
|
codeOp = Op_Greater;
|
|
break;
|
|
case OpLessEq:
|
|
// operator <=
|
|
codeOp = Op_LessEq;
|
|
break;
|
|
case OpEqEq:
|
|
// operator ==
|
|
codeOp = Op_EqEq;
|
|
break;
|
|
case OpNotEq:
|
|
// operator !=
|
|
codeOp = Op_NotEq;
|
|
break;
|
|
case OpStrEq:
|
|
// operator ===
|
|
codeOp = Op_StrEq;
|
|
break;
|
|
case OpStrNEq:
|
|
// operator !==
|
|
codeOp = Op_StrNEq;
|
|
break;
|
|
case OpBitAnd:
|
|
// operator &
|
|
codeOp = Op_BitAnd;
|
|
break;
|
|
case OpBitXOr:
|
|
// operator ^
|
|
codeOp = Op_BitXOr;
|
|
break;
|
|
case OpBitOr:
|
|
// operator |
|
|
codeOp = Op_BitOr;
|
|
break;
|
|
case OpIn:
|
|
codeOp = Op_In;
|
|
break;
|
|
case OpInstanceOf:
|
|
codeOp = Op_InstanceOf;
|
|
break;
|
|
|
|
default:
|
|
assert(!"BinaryOperatorNode: unhandled switch case");
|
|
}
|
|
|
|
OpValue out;
|
|
CodeGen::emitOp(comp, codeOp, &out, &v1, &v2);
|
|
return out;
|
|
}
|
|
|
|
OpValue BinaryLogicalNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
// This is somewhat ugly since we can't patchup labels in already generated
|
|
// code, and don't know the types in advance. It could also benefit from
|
|
// a type hint, since it's easier if we only want a bool, which is quite common
|
|
|
|
OpValue a = expr1->generateEvalCode(comp);
|
|
|
|
// Make a register for storing the result, and put 'a' there, as out first guess.
|
|
OpValue aVal, aReg;
|
|
comp->requestTemporary(a.type, &aVal, &aReg);
|
|
CodeGen::emitRegStore(comp, &aReg, &a);
|
|
|
|
// Is this enough to shortcircuit?
|
|
// if op is && and a is false, we jump out, ditto
|
|
// for || and true.
|
|
Addr jumpToShortCircuit = CodeGen::emitOp(comp, oper == OpAnd ? Op_IfNotJump : Op_IfJump,
|
|
0, &a, OpValue::dummyAddr());
|
|
|
|
// Now, generate the code for b...
|
|
OpValue b = expr2->generateEvalCode(comp);
|
|
|
|
// Hopefully, either the types match, or the result slot is already a value,
|
|
// so we can just promote b (which will happen automatically to produce param for Op_RegPutVal)
|
|
if (a.type == b.type || a.type == OpType_value) {
|
|
if (a.type == OpType_value)
|
|
CodeGen::emitOp(comp, Op_RegPutValue, 0, &aReg, &b);
|
|
else
|
|
CodeGen::emitRegStore(comp, &aReg, &b);
|
|
CodeGen::patchJumpToNext(comp, jumpToShortCircuit, 1);
|
|
return aVal;
|
|
} else {
|
|
// We need to promote 'a' as well, which means we need to skip over the code jumpToShortCircuit
|
|
// went to after handling store of 'b'.
|
|
|
|
// Get a new register for the result, put b there..
|
|
OpValue resVal, resReg;
|
|
comp->requestTemporary(OpType_value, &resVal, &resReg);
|
|
CodeGen::emitOp(comp, Op_RegPutValue, 0, &resReg, &b);
|
|
|
|
// skip to after a promotion..
|
|
Addr jumpToAfter = CodeGen::emitOp(comp, Op_Jump, 0, OpValue::dummyAddr());
|
|
|
|
// a's promotion goes here..
|
|
CodeGen::patchJumpToNext(comp, jumpToShortCircuit, 1);
|
|
CodeGen::emitOp(comp, Op_RegPutValue, 0, &resReg, &a);
|
|
|
|
// now we're after it..
|
|
CodeGen::patchJumpToNext(comp, jumpToAfter, 0);
|
|
|
|
return resVal;
|
|
}
|
|
}
|
|
|
|
OpValue ConditionalNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
// As above, we have some difficulty here, since we do not have a way of knowing
|
|
// the types in advance, but since we can't reasonably speculate on them both being bool,
|
|
// we just always produce a value.
|
|
OpValue resVal, resReg;
|
|
|
|
// Evaluate conditional, and jump..
|
|
OpValue v = logical->generateEvalCode(comp);
|
|
Addr jumpToElse = CodeGen::emitOp(comp, Op_IfNotJump, 0, &v, OpValue::dummyAddr());
|
|
|
|
// True branch
|
|
OpValue v1out = expr1->generateEvalCode(comp);
|
|
|
|
// Request a temporary for the result. (We can't reuse any, since it may be a variable!)
|
|
// ### perhaps do an isTemporary check here?
|
|
comp->requestTemporary(OpType_value, &resVal, &resReg);
|
|
CodeGen::emitOp(comp, Op_RegPutValue, 0, &resReg, &v1out);
|
|
|
|
Addr jumpToAfter = CodeGen::emitOp(comp, Op_Jump, 0, OpValue::dummyAddr());
|
|
|
|
// Jump to else goes here.
|
|
CodeGen::patchJumpToNext(comp, jumpToElse, 1);
|
|
|
|
// : part..
|
|
OpValue v2out = expr2->generateEvalCode(comp);
|
|
CodeGen::emitOp(comp, Op_RegPutValue, 0, &resReg, &v2out);
|
|
|
|
// After everything
|
|
CodeGen::patchJumpToNext(comp, jumpToAfter, 0);
|
|
|
|
return resVal;
|
|
}
|
|
|
|
OpValue FuncExprNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
comp->setNeedsClosures();
|
|
|
|
OpValue out;
|
|
OpValue nameV = OpValue::immIdent(&ident);
|
|
OpValue bodyV = OpValue::immNode(body.get());
|
|
CodeGen::emitOp(comp, Op_EvalFuncExpr, &out, &nameV, &bodyV);
|
|
return out;
|
|
}
|
|
|
|
void FuncDeclNode::generateExecCode(CompileState* comp)
|
|
{
|
|
comp->setNeedsClosures();
|
|
|
|
// No executable content...
|
|
}
|
|
|
|
void SourceElementsNode::generateExecCode(CompileState* comp)
|
|
{
|
|
node->generateExecCode(comp);
|
|
|
|
// ### FIXME: how do we do proper completion?
|
|
for (SourceElementsNode *n = next.get(); n; n = n->next.get()) {
|
|
n->node->generateExecCode(comp);
|
|
}
|
|
}
|
|
|
|
OpValue AssignNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
Node* cand = m_loc->nodeInsideAllParens();
|
|
if (!cand->isLocation()) {
|
|
emitReferenceError(comp, this, "Left side of assignment is not a reference.");
|
|
return OpValue::immValue(jsUndefined());
|
|
}
|
|
|
|
LocationNode* loc = static_cast<LocationNode*>(cand);
|
|
|
|
CompileReference* ref;
|
|
|
|
OpValue v;
|
|
if (m_oper == OpEqual) {
|
|
ref = loc->generateRefBind(comp);
|
|
v = m_right->generateEvalCode(comp);
|
|
} else {
|
|
OpValue v1;
|
|
ref = loc->generateRefRead(comp, &v1);
|
|
OpValue v2 = m_right->generateEvalCode(comp);
|
|
|
|
OpName codeOp;
|
|
switch (m_oper) {
|
|
case OpMultEq:
|
|
codeOp = Op_Mult;
|
|
break;
|
|
case OpDivEq:
|
|
codeOp = Op_Div;
|
|
break;
|
|
case OpModEq:
|
|
codeOp = Op_Mod;
|
|
break;
|
|
case OpPlusEq:
|
|
codeOp = Op_Add;
|
|
break;
|
|
case OpMinusEq:
|
|
codeOp = Op_Sub;
|
|
break;
|
|
case OpLShift:
|
|
codeOp = Op_LShift;
|
|
break;
|
|
case OpRShift:
|
|
codeOp = Op_RShift;
|
|
break;
|
|
case OpURShift:
|
|
codeOp = Op_URShift;
|
|
break;
|
|
case OpAndEq:
|
|
codeOp = Op_BitAnd;
|
|
break;
|
|
case OpXOrEq:
|
|
codeOp = Op_BitXOr;
|
|
break;
|
|
case OpOrEq:
|
|
codeOp = Op_BitOr;
|
|
break;
|
|
default:
|
|
ASSERT(0);
|
|
}
|
|
|
|
CodeGen::emitOp(comp, codeOp, &v, &v1, &v2);
|
|
}
|
|
|
|
loc->generateRefWrite(comp, ref, v);
|
|
|
|
delete ref;
|
|
return v;
|
|
}
|
|
|
|
OpValue CommaNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
expr1->generateEvalCode(comp);
|
|
return expr2->generateEvalCode(comp);
|
|
}
|
|
|
|
OpValue AssignExprNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
return expr->generateEvalCode(comp);
|
|
}
|
|
|
|
void VarDeclNode::generateCode(CompileState* comp)
|
|
{
|
|
// We only care about things which have an initializer ---
|
|
// everything else is a no-op at execution time,
|
|
// and only makes a difference at processVarDecl time
|
|
if (init) {
|
|
if (comp->inNestedScope()) {
|
|
// We need to do the full lookup mess, which includes doing split binding and store
|
|
OpValue quiet = OpValue::immNode(0);
|
|
OpValue varName = OpValue::immIdent(&ident);
|
|
OpValue base;
|
|
CodeGen::emitOp(comp, Op_ScopeLookup, &base, &varName, &quiet);
|
|
|
|
OpValue val = init->generateEvalCode(comp);
|
|
CodeGen::emitOp(comp, Op_SymPutKnownObject, 0, &base, &varName, &val);
|
|
return;
|
|
}
|
|
|
|
OpValue val = init->generateEvalCode(comp);
|
|
size_t localID = comp->functionBody()->lookupSymbolID(ident);
|
|
if (localID == missingSymbolMarker()) {
|
|
// Generate a symbolic assignment, always to local scope
|
|
OpValue identV = OpValue::immIdent(&ident);
|
|
CodeGen::emitOp(comp, Op_SymPutKnownObject, 0, comp->localScope(), &identV, &val);
|
|
} else {
|
|
// Store to the local..
|
|
OpValue dest = comp->localWriteRef(comp->codeBlock(), localID);
|
|
CodeGen::emitOp(comp, Op_RegPutValue, 0, &dest, &val);
|
|
}
|
|
} // if initializer..
|
|
}
|
|
|
|
OpValue VarDeclListNode::generateEvalCode(CompileState* comp)
|
|
{
|
|
for (VarDeclListNode *n = this; n; n = n->next.get())
|
|
n->var->generateCode(comp);
|
|
|
|
return OpValue::immInt32(0); // unused..
|
|
}
|
|
|
|
void VarStatementNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
next->generateEvalCode(comp);
|
|
}
|
|
|
|
void BlockNode::generateExecCode(CompileState* comp)
|
|
{
|
|
if (source) {
|
|
generateDebugInfoIfNeeded(comp);
|
|
source->generateExecCode(comp);
|
|
}
|
|
}
|
|
|
|
void EmptyStatementNode::generateExecCode(CompileState*)
|
|
{}
|
|
|
|
void ExprStatementNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
OpValue val = expr->generateEvalCode(comp);
|
|
|
|
// Update the result for eval or global code
|
|
if (comp->codeType() != FunctionCode)
|
|
CodeGen::emitOp(comp, Op_RegPutValue, 0, comp->evalResultReg(), &val);
|
|
}
|
|
|
|
void IfNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
|
|
// eval the condition
|
|
OpValue cond = expr->generateEvalCode(comp);
|
|
|
|
// If condition is not true, jump to after or else..
|
|
Addr afterTrueJmp = CodeGen::emitOp(comp, Op_IfNotJump, 0, &cond, OpValue::dummyAddr());
|
|
|
|
// Emit the body of true...
|
|
statement1->generateExecCode(comp);
|
|
|
|
// If we have an else, add in a jump to skip over it.
|
|
Addr afterAllJmp = 0;
|
|
if (statement2)
|
|
afterAllJmp = CodeGen::emitOp(comp, Op_Jump, 0, OpValue::dummyAddr());
|
|
|
|
// This is where we go if true fails --- else, or afterwards.
|
|
CodeGen::patchJumpToNext(comp, afterTrueJmp, 1);
|
|
|
|
if (statement2) {
|
|
// Body of else
|
|
statement2->generateExecCode(comp);
|
|
|
|
// Fix up the jump-over code
|
|
CodeGen::patchJumpToNext(comp, afterAllJmp, 0);
|
|
}
|
|
}
|
|
|
|
void DoWhileNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
comp->enterLoop(this);
|
|
|
|
// Body
|
|
OpValue beforeBody = OpValue::immAddr(CodeGen::nextPC(comp));
|
|
statement->generateExecCode(comp);
|
|
|
|
// continues go to just before the test..
|
|
comp->resolvePendingContinues(this, CodeGen::nextPC(comp));
|
|
|
|
// test
|
|
OpValue cond = expr->generateEvalCode(comp);
|
|
CodeGen::emitOp(comp, Op_IfJump, 0, &cond, &beforeBody);
|
|
|
|
comp->exitLoop(this);
|
|
}
|
|
|
|
void WhileNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
comp->enterLoop(this);
|
|
|
|
// Jump to test.
|
|
Addr jumpToTest = CodeGen::emitOp(comp, Op_Jump, 0, OpValue::dummyAddr());
|
|
|
|
// Body
|
|
OpValue beforeBody = OpValue::immAddr(CodeGen::nextPC(comp));
|
|
statement->generateExecCode(comp);
|
|
|
|
// continues go to just before the test..
|
|
comp->resolvePendingContinues(this, CodeGen::nextPC(comp));
|
|
|
|
// patch up the destination of the initial jump to test
|
|
CodeGen::patchJumpToNext(comp, jumpToTest, 0);
|
|
|
|
// test
|
|
OpValue cond = expr->generateEvalCode(comp);
|
|
CodeGen::emitOp(comp, Op_IfJump, 0, &cond, &beforeBody);
|
|
|
|
comp->exitLoop(this);
|
|
}
|
|
|
|
void ForNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
comp->enterLoop(this);
|
|
|
|
// Initializer, if any..
|
|
if (expr1)
|
|
expr1->generateEvalCode(comp);
|
|
|
|
// Insert a jump to the loop test (address not yet known)
|
|
Addr jumpToTest = CodeGen::emitOp(comp, Op_Jump, 0, OpValue::dummyAddr());
|
|
|
|
// Generate loop body..
|
|
OpValue bodyAddr = OpValue::immAddr(CodeGen::nextPC(comp));
|
|
statement->generateExecCode(comp);
|
|
|
|
// We're about to generate the increment... The continues should go here..
|
|
comp->resolvePendingContinues(this, CodeGen::nextPC(comp));
|
|
|
|
// ### there is a CheckTimeout hook here in nodes.cpp...
|
|
|
|
// Generate increment...
|
|
if (expr3)
|
|
expr3->generateEvalCode(comp);
|
|
|
|
// The test goes here, so patch up the previous jump..
|
|
CodeGen::patchJumpToNext(comp, jumpToTest, 0);
|
|
|
|
// Make the test itself --- if it exists..
|
|
if (expr2) {
|
|
OpValue cond = expr2->generateEvalCode(comp);
|
|
CodeGen::emitOp(comp, Op_IfJump, 0, &cond, &bodyAddr);
|
|
} else {
|
|
// Just jump back to the body.
|
|
CodeGen::emitOp(comp, Op_Jump, 0, &bodyAddr);
|
|
}
|
|
|
|
comp->exitLoop(this);
|
|
}
|
|
|
|
void ForInNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
if (varDecl)
|
|
varDecl->generateCode(comp);
|
|
|
|
OpValue val = expr->generateEvalCode(comp);
|
|
OpValue obj; // version of val after toObject, returned by BeginForIn.
|
|
|
|
OpValue stateVal, stateReg;
|
|
comp->requestTemporary(OpType_value, &stateVal, &stateReg);
|
|
|
|
// Fetch the property name array..
|
|
CodeGen::emitOp(comp, Op_BeginForIn, &obj, &val, &stateReg);
|
|
|
|
comp->enterLoop(this);
|
|
|
|
// We put the test first here, since the test and the fetch are combined.
|
|
OpValue sym;
|
|
Addr fetchNext = CodeGen::emitOp(comp, Op_NextForInEntry, &sym, &obj,
|
|
&stateVal, OpValue::dummyAddr());
|
|
|
|
// Write to the variable
|
|
assert (lexpr->isLocation());
|
|
LocationNode* loc = static_cast<LocationNode*>(lexpr.get());
|
|
|
|
CompileReference* ref = loc->generateRefBind(comp);
|
|
loc->generateRefWrite (comp, ref, sym);
|
|
delete ref;
|
|
|
|
// Run the body.
|
|
statement->generateExecCode(comp);
|
|
|
|
// Can fix the continues to go back to the test...
|
|
comp->resolvePendingContinues(this, fetchNext);
|
|
|
|
// Jump back..
|
|
OpValue backVal = OpValue::immAddr(fetchNext);
|
|
CodeGen::emitOp(comp, Op_Jump, 0, &backVal);
|
|
|
|
// The end address is here (3 argument + return val)
|
|
CodeGen::patchJumpToNext(comp, fetchNext, 3);
|
|
|
|
comp->exitLoop(this);
|
|
}
|
|
|
|
// Helper for continue/break -- emits stack cleanup call if needed,
|
|
// and a jump either to the or an ??? exception.
|
|
static void handleJumpOut(CompileState* comp, Node* dest, ComplType breakOrCont)
|
|
{
|
|
// We scan up the nest stack until we get to the target or
|
|
// a try-finally.
|
|
int toUnwind = 0;
|
|
|
|
const WTF::Vector<CompileState::NestInfo>& nests = comp->nestStack();
|
|
|
|
for (int pos = nests.size() - 1; pos >= 0; --pos) {
|
|
switch (nests[pos].type) {
|
|
case CompileState::Scope:
|
|
case CompileState::OtherCleanup:
|
|
++toUnwind;
|
|
break;
|
|
case CompileState::TryFinally: {
|
|
// Uh-oh. We have to handle this via exception machinery, giving it the
|
|
// original address
|
|
Addr pc = CodeGen::nextPC(comp);
|
|
CodeGen::emitOp(comp, Op_ContBreakInTryFinally, 0, OpValue::dummyAddr());
|
|
|
|
// Queue destination for resolution
|
|
if (breakOrCont == Continue)
|
|
comp->addPendingContinue(dest, pc);
|
|
else
|
|
comp->addPendingBreak(dest, pc);
|
|
|
|
return;
|
|
}
|
|
|
|
case CompileState::ContBreakTarget:
|
|
if (nests[pos].node == dest) {
|
|
// Great. We found where we're going! Emit the unwind instr (if needed),
|
|
// and the jump.
|
|
if (toUnwind) {
|
|
OpValue unwind = OpValue::immInt32(toUnwind);
|
|
CodeGen::emitOp(comp, Op_UnwindStacks, 0, &unwind);
|
|
}
|
|
|
|
// Emit a jump...
|
|
Addr pc = CodeGen::nextPC(comp);
|
|
CodeGen::emitOp(comp, Op_Jump, 0, OpValue::dummyAddr());
|
|
|
|
// Queue destination for resolution
|
|
if (breakOrCont == Continue)
|
|
comp->addPendingContinue(dest, pc);
|
|
else
|
|
comp->addPendingBreak(dest, pc);
|
|
|
|
return;
|
|
} // if matching destination..
|
|
}
|
|
}
|
|
|
|
assert (!"Huh? Unable to find continue/break target in the nest stack");
|
|
}
|
|
|
|
void ContinueNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
Node* dest = comp->resolveContinueLabel(ident);
|
|
if (!dest) {
|
|
if (ident.isEmpty())
|
|
emitSyntaxError(comp, this, "Illegal continue without target outside a loop.");
|
|
else
|
|
emitSyntaxError(comp, this, "Invalid label in continue.");
|
|
} else {
|
|
// Continue can only be used for a loop
|
|
if (dest->isIterationStatement()) {
|
|
handleJumpOut(comp, dest, Continue);
|
|
} else {
|
|
emitSyntaxError(comp, this, "Invalid continue target; must be a loop.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void BreakNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
Node* dest = comp->resolveBreakLabel(ident);
|
|
if (!dest) {
|
|
if (ident.isEmpty())
|
|
emitSyntaxError(comp, this, "Illegal break without target outside a loop or switch.");
|
|
else
|
|
emitSyntaxError(comp, this, "Invalid label in break.");
|
|
} else {
|
|
handleJumpOut(comp, dest, Break);
|
|
}
|
|
}
|
|
|
|
void ReturnNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
OpValue arg;
|
|
|
|
// Return is invalid in non-function..
|
|
if (comp->codeType() != FunctionCode) {
|
|
emitSyntaxError(comp, this, "Invalid return.");
|
|
return;
|
|
}
|
|
|
|
if (!value)
|
|
arg = OpValue::immValue(jsUndefined());
|
|
else
|
|
arg = value->generateEvalCode(comp);
|
|
|
|
if (!comp->inTryFinally())
|
|
generateExitContextIfNeeded(comp);
|
|
|
|
CodeGen::emitOp(comp, comp->inTryFinally() ? Op_ReturnInTryFinally : Op_Return, 0, &arg);
|
|
}
|
|
|
|
void WithNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
// ### this may be too conservative --- it only applies if there is
|
|
// a function call within
|
|
comp->setNeedsClosures();
|
|
|
|
OpValue scopeObj = expr->generateEvalCode(comp);
|
|
|
|
comp->pushNest(CompileState::Scope, this);
|
|
CodeGen::emitOp(comp, Op_PushScope, 0, &scopeObj);
|
|
|
|
statement->generateExecCode(comp);
|
|
|
|
CodeGen::emitOp(comp, Op_PopScope, 0);
|
|
comp->popNest();
|
|
}
|
|
|
|
void LabelNode::generateExecCode(CompileState* comp)
|
|
{
|
|
if (!comp->pushLabel(label)) {
|
|
emitSyntaxError(comp, this, "Duplicated label found.");
|
|
return;
|
|
}
|
|
|
|
if (!statement->isLabelNode()) { // we're the last label..
|
|
comp->pushNest(CompileState::ContBreakTarget, statement.get());
|
|
comp->bindLabels(statement.get());
|
|
}
|
|
|
|
// Generate code for stuff inside the label...
|
|
statement->generateExecCode(comp);
|
|
|
|
// Fix up any breaks..
|
|
if (!statement->isLabelNode()) {
|
|
comp->popNest();
|
|
comp->resolvePendingBreaks(statement.get(), CodeGen::nextPC(comp));
|
|
}
|
|
|
|
comp->popLabel();
|
|
}
|
|
|
|
void ThrowNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
OpValue projectile = expr->generateEvalCode(comp);
|
|
CodeGen::emitOp(comp, Op_Throw, 0, &projectile);
|
|
}
|
|
|
|
void TryNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
// ### this may be too conservative --- it only applies if there is
|
|
// a function call within the catch
|
|
comp->setNeedsClosures();
|
|
|
|
// Set the catch handler, run the try clause, pop the try handler..
|
|
Addr setCatchHandler = CodeGen::emitOp(comp, Op_PushExceptionHandler, 0, OpValue::dummyAddr());
|
|
comp->pushNest(finallyBlock ? CompileState::TryFinally : CompileState::OtherCleanup);
|
|
|
|
tryBlock->generateExecCode(comp);
|
|
|
|
CodeGen::emitOp(comp, Op_PopExceptionHandler);
|
|
comp->popNest();
|
|
|
|
// Jump over the catch if try is OK
|
|
Addr jumpOverCatch = 0;
|
|
if (catchBlock)
|
|
jumpOverCatch = CodeGen::emitOp(comp, Op_Jump, 0, OpValue::dummyAddr());
|
|
|
|
// Exceptions would go here --- either in a catch or a finally.
|
|
CodeGen::patchJumpToNext(comp, setCatchHandler, 0);
|
|
|
|
Addr catchToFinallyEH = 0;
|
|
if (catchBlock) {
|
|
// If there is a finally block, that acts as an exception handler for the catch;
|
|
// we need to set it before entering the catch scope, so the cleanup entries for that
|
|
// are on top. Also, that's needed because if the inside raised a non-exception
|
|
// continuation, EnterCatch will re-raise it.
|
|
if (finallyBlock) {
|
|
catchToFinallyEH = CodeGen::emitOp(comp, Op_PushExceptionHandler, 0, OpValue::dummyAddr());
|
|
comp->pushNest(CompileState::TryFinally);
|
|
}
|
|
|
|
// Emit the catch.. Note: the unwinder has already popped the catch handler entry,
|
|
// but the exception object is still set, since we need to make a scope for it.
|
|
// EnterCatch would do that for us, given the name
|
|
OpValue catchVar = OpValue::immIdent(&exceptionIdent);
|
|
CodeGen::emitOp(comp, Op_EnterCatch, 0, &catchVar);
|
|
comp->pushNest(CompileState::Scope);
|
|
|
|
catchBlock->generateExecCode(comp);
|
|
|
|
// If needed, cleanup the binding to finally, and always cleans the catch scope
|
|
CodeGen::emitOp(comp, Op_ExitCatch);
|
|
comp->popNest();
|
|
|
|
if (finallyBlock) {
|
|
CodeGen::emitOp(comp, Op_PopExceptionHandler);
|
|
comp->popNest();
|
|
}
|
|
|
|
// after an OK 'try', we always go to finally, if any, which needs an op if there is a catch block
|
|
CodeGen::patchJumpToNext(comp, jumpOverCatch, 0);
|
|
}
|
|
|
|
|
|
if (finallyBlock) {
|
|
if (catchBlock) // if a catch was using us an EH, patch that instruction to here
|
|
CodeGen::patchJumpToNext(comp, catchToFinallyEH, 0);
|
|
|
|
CodeGen::emitOp(comp, Op_DeferCompletion);
|
|
comp->pushNest(CompileState::OtherCleanup);
|
|
|
|
finallyBlock->generateExecCode(comp);
|
|
|
|
OpValue otherTryFinally = OpValue::immBool(comp->inTryFinally());
|
|
|
|
if (exitContextNeeded(comp)) {
|
|
OpValue ourNode = OpValue::immNode(comp->functionBody());
|
|
CodeGen::emitOp(comp, Op_ReactivateCompletionDebug, 0, &otherTryFinally, &ourNode);
|
|
} else {
|
|
CodeGen::emitOp(comp, Op_ReactivateCompletion, 0, &otherTryFinally);
|
|
}
|
|
comp->popNest();
|
|
}
|
|
}
|
|
|
|
void FunctionBodyNode::generateExecCode(CompileState* comp)
|
|
{
|
|
// Load scope, global and 'this' pointers.
|
|
OpValue scopeVal, scopeReg,
|
|
globalVal, globalReg,
|
|
thisVal, thisReg;
|
|
|
|
comp->requestTemporary(OpType_value, &scopeVal, &scopeReg);
|
|
comp->requestTemporary(OpType_value, &globalVal, &globalReg);
|
|
comp->requestTemporary(OpType_value, &thisVal, &thisReg);
|
|
|
|
CodeGen::emitOp(comp, Op_Preamble, 0, &scopeReg, &globalReg, &thisReg);
|
|
|
|
comp->setPreloadRegs(&scopeVal, &globalVal, &thisVal);
|
|
|
|
OpValue evalResReg, evalResVal;
|
|
if (comp->codeType() != FunctionCode) {
|
|
comp->requestTemporary(OpType_value, &evalResVal, &evalResReg);
|
|
comp->setEvalResultRegister(&evalResReg);
|
|
|
|
// There is no need to initialize this as everything will be set to undefined anyway
|
|
} else {
|
|
if (comp->compileType() == Debug) {
|
|
OpValue ourNode = OpValue::immNode(this);
|
|
CodeGen::emitOp(comp, Op_EnterDebugContext, 0, &ourNode);
|
|
}
|
|
}
|
|
|
|
// Set unwind..
|
|
Addr unwind = CodeGen::emitOp(comp, Op_PushExceptionHandler, 0, OpValue::dummyAddr());
|
|
|
|
// Generate body...
|
|
BlockNode::generateExecCode(comp);
|
|
|
|
// Make sure we exit!
|
|
if (comp->codeType() != FunctionCode) {
|
|
CodeGen::emitOp(comp, Op_Return, 0, &evalResVal);
|
|
} else {
|
|
generateExitContextIfNeeded(comp);
|
|
CodeGen::emitOp(comp, Op_Exit);
|
|
}
|
|
|
|
// Unwind stuff..
|
|
CodeGen::patchJumpToNext(comp, unwind, 0);
|
|
generateExitContextIfNeeded(comp);
|
|
CodeGen::emitOp(comp, Op_PropagateException);
|
|
}
|
|
|
|
void SwitchNode::generateExecCode(CompileState* comp)
|
|
{
|
|
generateDebugInfoIfNeeded(comp);
|
|
CaseBlockNode* caseBlock = this->block.get();
|
|
|
|
// The code we produce has 2 stages: first, we emit all the conditionals, and pick
|
|
// the label to jump to (with the jump to the default being last),
|
|
// then we just emit all the clauses in the row. The breaks will be
|
|
// resolved at the end --- for that, we bind ourselves for label'less break.
|
|
comp->pushNest(CompileState::ContBreakTarget, this);
|
|
comp->pushDefaultBreak(this);
|
|
|
|
// What we compare with
|
|
OpValue switchOn = expr->generateEvalCode(comp);
|
|
|
|
WTF::Vector<Addr> list1jumps;
|
|
WTF::Vector<Addr> list2jumps;
|
|
Addr defJump;
|
|
|
|
// Jumps for list 1..
|
|
for (ClauseListNode* iter = caseBlock->list1.get(); iter; iter = iter->next.get()) {
|
|
OpValue ref = iter->clause->expr->generateEvalCode(comp);
|
|
OpValue match;
|
|
CodeGen::emitOp(comp, Op_StrEq, &match, &switchOn, &ref);
|
|
|
|
Addr jumpToClause = CodeGen::emitOp(comp, Op_IfJump, 0, &match, OpValue::dummyAddr());
|
|
list1jumps.append(jumpToClause);
|
|
}
|
|
|
|
// Jumps for list 2..
|
|
for (ClauseListNode* iter = caseBlock->list2.get(); iter; iter = iter->next.get()) {
|
|
OpValue ref = iter->clause->expr->generateEvalCode(comp);
|
|
OpValue match;
|
|
CodeGen::emitOp(comp, Op_StrEq, &match, &switchOn, &ref);
|
|
|
|
Addr jumpToClause = CodeGen::emitOp(comp, Op_IfJump, 0, &match, OpValue::dummyAddr());
|
|
list2jumps.append(jumpToClause);
|
|
}
|
|
|
|
// Jump to default (or after, if there is no default)
|
|
defJump = CodeGen::emitOp(comp, Op_Jump, 0, OpValue::dummyAddr());
|
|
|
|
// Now, we can actually emit the bodies, fixing the addresses as we go
|
|
int p = 0;
|
|
for (ClauseListNode* iter = caseBlock->list1.get(); iter; iter = iter->next.get()) {
|
|
CodeGen::patchJumpToNext(comp, list1jumps[p], 1);
|
|
if (iter->clause->source)
|
|
iter->clause->source->generateExecCode(comp);
|
|
++p;
|
|
}
|
|
|
|
if (caseBlock->def) {
|
|
CodeGen::patchJumpToNext(comp, defJump, 0);
|
|
if (caseBlock->def->source)
|
|
caseBlock->def->source->generateExecCode(comp);
|
|
}
|
|
|
|
p = 0;
|
|
for (ClauseListNode* iter = caseBlock->list2.get(); iter; iter = iter->next.get()) {
|
|
CodeGen::patchJumpToNext(comp, list2jumps[p], 1);
|
|
if (iter->clause->source)
|
|
iter->clause->source->generateExecCode(comp);
|
|
++p;
|
|
}
|
|
|
|
// If we didn't have a default, that jump is to here..
|
|
if (!caseBlock->def)
|
|
CodeGen::patchJumpToNext(comp, defJump, 0);
|
|
|
|
// Breaks should go after us..
|
|
comp->popDefaultBreak();
|
|
comp->popNest();
|
|
comp->resolvePendingBreaks(this, CodeGen::nextPC(comp));
|
|
}
|
|
|
|
void ImportStatement::generateExecCode(CompileState*)
|
|
{} // handled as a declaration..
|
|
|
|
|
|
}
|
|
|
|
// kate: indent-width 4; replace-tabs on; tab-width 4; space-indent on; hl c++;
|