/* -*- Mode: C++; tab-width: 4; 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 the JavaScript 2 Prototype. * * 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. */ #include "interpreter.h" #include "jsclasses.h" #include "world.h" #include "parser.h" #include "jsmath.h" #include namespace JavaScript { namespace Interpreter { // operand access macros. #define op1(i) (i->o1()) #define op2(i) (i->o2()) #define op3(i) (i->o3()) #define op4(i) (i->o4()) // mnemonic names for operands. #define dst(i) op1(i) #define src1(i) op2(i) #define src2(i) op3(i) #define ofs(i) (i->getOffset()) #define val2(i) op2(i) #define val3(i) op3(i) #define val4(i) op4(i) using namespace ICG; using namespace JSTypes; using namespace JSClasses; using namespace JSMathClass; // These classes are private to the JS interpreter. /** * */ struct Handler: public gc_base { Handler(Label *catchLabel, Label *finallyLabel) : catchTarget(catchLabel), finallyTarget(finallyLabel) {} Label *catchTarget; Label *finallyTarget; }; typedef std::vector CatchStack; /** * Represents the current function's invocation state. */ struct Activation : public gc_base { JSValues mRegisters; ICodeModule* mICode; CatchStack catchStack; Activation(ICodeModule* iCode, const JSValues& args) : mRegisters(iCode->itsMaxRegister + 1), mICode(iCode) { // copy arg list to initial registers. JSValues::iterator dest = mRegisters.begin(); for (JSValues::const_iterator src = args.begin(), end = args.end(); src != end; ++src, ++dest) { *dest = *src; } } Activation(ICodeModule* iCode, Activation* caller, const JSValue thisArg, const ArgumentList& list) : mRegisters(iCode->itsMaxRegister + 1), mICode(iCode) { // copy caller's parameter list to initial registers. JSValues::iterator dest = mRegisters.begin(); *dest++ = thisArg; const JSValues& params = caller->mRegisters; for (ArgumentList::const_iterator src = list.begin(), end = list.end(); src != end; ++src, ++dest) { Register r = (*src).first.first; if (r != NotARegister) *dest = params[r]; else *dest = JSValue(JSValue::uninitialized_tag); } } // calling a binary operator, no 'this' Activation(ICodeModule* iCode, const JSValue arg1, const JSValue arg2) : mRegisters(iCode->itsMaxRegister + 1), mICode(iCode) { mRegisters[1] = arg1; mRegisters[2] = arg2; } // calling a getter function, no arguments Activation(ICodeModule* iCode) : mRegisters(iCode->itsMaxRegister + 1), mICode(iCode) { } // calling a setter function, 1 argument Activation(ICodeModule* iCode, const JSValue arg) : mRegisters(iCode->itsMaxRegister + 1), mICode(iCode) { mRegisters[1] = arg; } }; /** * exception-safe class to save off values. */ template class autosaver { T& mRef; T mOld; public: autosaver(T& ref, T val = T()) : mRef(ref), mOld(ref) { ref = val; } ~autosaver() { mRef = mOld; } }; ICodeModule* Context::compileFunction(const String &source) { Arena a; String filename = widenCString("Some source source"); Parser p(getWorld(), a, source, filename); ExprNode* e = p.parseExpression(false); ICodeGenerator icg(&getWorld(), getGlobalObject()); ASSERT(e->getKind() == ExprNode::functionLiteral); FunctionExprNode* f = static_cast(e); icg.allocateParameter(getWorld().identifiers["this"]); // always parameter #0 VariableBinding* v = f->function.parameters; while (v) { if (v->name && (v->name->getKind() == ExprNode::identifier)) icg.allocateParameter((static_cast(v->name))->name); v = v->next; } icg.genStmt(f->function.body); ICodeModule* result = icg.complete(); result->setFileName(filename); return result; } JSValue Context::readEvalFile(FILE* in, const String& fileName) { String buffer; string line; LineReader inReader(in); JSValues emptyArgs; JSValue result; // save off important member variables, to enable recursive call to interpret. // this is a little stinky, but should be exception-safe. autosaver activation(mActivation, 0); autosaver linkage(mLinkage, 0); autosaver pc(mPC); while (inReader.readLine(line) != 0) { appendChars(buffer, line.data(), line.size()); try { Arena a; Parser p(getWorld(), a, buffer, fileName); StmtNode *parsedStatements = p.parseProgram(); ASSERT(p.lexer.peek(true).hasKind(Token::end)); { PrettyPrinter f(stdOut, 30); { PrettyPrinter::Block b(f, 2); f << "Program ="; f.linearBreak(1); StmtNode::printStatements(f, parsedStatements); } f.end(); } stdOut << '\n'; // Generate code for parsedStatements, which is a linked // list of zero or more statements ICodeModule* icm = genCode(parsedStatements, fileName); if (icm) { result = interpret(icm, emptyArgs); delete icm; } clear(buffer); } catch (Exception &e) { /* If we got a syntax error on the end of input, * then wait for a continuation * of input rather than printing the error message. */ if (!(e.hasKind(Exception::syntaxError) && e.lineNum && e.pos == buffer.size() && e.sourceFile == fileName)) { stdOut << '\n' << e.fullMessage(); clear(buffer); } } } return result; } ICodeModule* Context::genCode(StmtNode *p, const String &fileName) { ICodeGenerator icg(&getWorld(), getGlobalObject()); TypedRegister ret(NotARegister, &None_Type); while (p) { ret = icg.genStmt(p); p = p->next; } icg.returnStmt(ret); ICodeModule *icm = icg.complete(); icm->setFileName (fileName); return icm; } JSValues& Context::getRegisters() { return mActivation->mRegisters; } ICodeModule* Context::getICode() { return mActivation->mICode; } /** * Stores saved state from the *previous* activation, the current * activation is alive and well in locals of the interpreter loop. */ struct Linkage : public Context::Frame, public gc_base { Linkage* mNext; // next linkage in linkage stack. InstructionIterator mReturnPC; Activation* mActivation; // caller's activation. JSScope* mScope; TypedRegister mResult; // the desired target register for the return value Linkage(Linkage* linkage, InstructionIterator returnPC, Activation* activation, JSScope* scope, TypedRegister result) : mNext(linkage), mReturnPC(returnPC), mActivation(activation), mScope(scope), mResult(result) { } Context::Frame* getNext() { return mNext; } void getState(InstructionIterator& pc, JSValues*& registers, ICodeModule*& iCode) { pc = mReturnPC; registers = &mActivation->mRegisters; iCode = mActivation->mICode; } }; static JSValue shiftLeft_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toInt32()); JSValue num2(r2.toUInt32()); return JSValue(num1.i32 << (num2.u32 & 0x1F)); } static JSValue shiftRight_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toInt32()); JSValue num2(r2.toUInt32()); return JSValue(num1.i32 >> (num2.u32 & 0x1F)); } static JSValue UshiftRight_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toUInt32()); JSValue num2(r2.toUInt32()); return JSValue(num1.u32 >> (num2.u32 & 0x1F)); } static JSValue and_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toInt32()); JSValue num2(r2.toInt32()); return JSValue(num1.i32 & num2.i32); } static JSValue or_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toInt32()); JSValue num2(r2.toInt32()); return JSValue(num1.i32 | num2.i32); } static JSValue xor_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toInt32()); JSValue num2(r2.toInt32()); return JSValue(num1.i32 ^ num2.i32); } static JSValue add_Default(const JSValue& r1, const JSValue& r2) { // // could also handle these as separate entries in the override table for add // by specifying add(String, Any), add(Any, String), add(String, String) // if (r1.isString() || r2.isString()) { JSString& str1 = *r1.toString().string; JSString& str2 = *r2.toString().string; return JSValue(new JSString(str1 + str2)); } else { JSValue num1(r1.toNumber()); JSValue num2(r2.toNumber()); return JSValue(num1.f64 + num2.f64); } } /* static JSValue add_String1(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toNumber()); JSValue num2(r2.toNumber()); return JSValue(num1.f64 + num2.f64); } */ static JSValue subtract_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toNumber()); JSValue num2(r2.toNumber()); return JSValue(num1.f64 - num2.f64); } static JSValue multiply_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toNumber()); JSValue num2(r2.toNumber()); return JSValue(num1.f64 * num2.f64); } static JSValue divide_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toNumber()); JSValue num2(r2.toNumber()); return JSValue(num1.f64 / num2.f64); } static JSValue remainder_Default(const JSValue& r1, const JSValue& r2) { JSValue num1(r1.toNumber()); JSValue num2(r2.toNumber()); return JSValue(fmod(num1.f64, num2.f64)); } static JSValue less_Default(const JSValue& r1, const JSValue& r2) { JSValue lv = r1.toPrimitive(JSValue::Number); JSValue rv = r2.toPrimitive(JSValue::Number); if (lv.isString() && rv.isString()) { return JSValue(bool(lv.string->compare(*rv.string) < 0)); } else { lv = lv.toNumber(); rv = rv.toNumber(); if (lv.isNaN() || rv.isNaN()) return JSValue(); else return JSValue(lv.f64 < rv.f64); } } static JSValue lessOrEqual_Default(const JSValue& r1, const JSValue& r2) { JSValue lv = r1.toPrimitive(JSValue::Number); JSValue rv = r2.toPrimitive(JSValue::Number); if (lv.isString() && rv.isString()) { return JSValue(bool(lv.string->compare(*rv.string) <= 0)); } else { lv = lv.toNumber(); rv = rv.toNumber(); if (lv.isNaN() || rv.isNaN()) return JSValue(); else return JSValue(lv.f64 <= rv.f64); } } static JSValue equal_Default(const JSValue& r1, const JSValue& r2) { JSValue lv = r1.toPrimitive(JSValue::Number); JSValue rv = r2.toPrimitive(JSValue::Number); if (lv.isString() && rv.isString()) { return JSValue(bool(lv.string->compare(*rv.string) == 0)); } else { lv = lv.toNumber(); rv = rv.toNumber(); if (lv.isNaN() || rv.isNaN()) return JSValue(); else return JSValue(lv.f64 == rv.f64); } } static JSValue identical_Default(const JSValue& r1, const JSValue& r2) { if (r1.getType() != r2.getType()) return kFalseValue; if (r1.isUndefined() ) return kTrueValue; if (r1.isNull()) return kTrueValue; if (r1.isNumber()) { if (r1.isNaN()) return kFalseValue; if (r2.isNaN()) return kFalseValue; return JSValue(r1.f64 == r2.f64); } else { if (r1.isString()) return JSValue(bool(r1.string->compare(*r2.string) == 0)); if (r1.isBoolean()) return JSValue(bool(r1.boolean == r2.boolean)); if (r1.isObject()) return JSValue(bool(r1.object == r2.object)); return kFalseValue; } } static JSValue defineAdd(Context *cx, const JSValues& argv) { // should be three args, first two are types, third is a function. ASSERT(argv[0].isType()); ASSERT(argv[1].isType()); ASSERT(argv[2].isFunction()); // XXX need to prove that argv[2].function takes T1 and T2 as args and returns Boolean for the relational operators ? cx->addBinaryOperator(BinaryOperator::Add, new BinaryOperator(argv[0].type, argv[1].type, argv[2].function)); return kUndefinedValue; } #define DEFINE_OBO(NAME) \ static JSValue define##NAME(Context *cx, const JSValues& argv) \ { \ ASSERT(argv[0].isType()); \ ASSERT(argv[1].isType()); \ ASSERT(argv[2].isFunction()); \ cx->addBinaryOperator(BinaryOperator::##NAME, \ new BinaryOperator(argv[0].type, argv[1].type, argv[2].function)); \ return kUndefinedValue; \ } \ DEFINE_OBO(Subtract) DEFINE_OBO(Multiply) DEFINE_OBO(Divide) DEFINE_OBO(Remainder) DEFINE_OBO(LeftShift) DEFINE_OBO(RightShift) DEFINE_OBO(LogicalRightShift) DEFINE_OBO(BitwiseOr) DEFINE_OBO(BitwiseXor) DEFINE_OBO(BitwiseAnd) DEFINE_OBO(Less) DEFINE_OBO(LessOrEqual) DEFINE_OBO(Equal) DEFINE_OBO(Identical) void Context::initOperatorsPackage() { // hack - the following should be available only after importing the 'Operators' package // (hmm, how will that work - the import needs to connect the functions into this mechanism // do we watch for the specific package name???) struct OBO { char *name; JSNativeFunction::JSCode fun; } OBOs[] = { { "defineAdd", defineAdd }, { "defineSubtract", defineSubtract }, { "defineMultiply", defineMultiply }, { "defineDivide", defineDivide }, { "defineRemainder", defineRemainder }, { "defineLeftShift", defineLeftShift }, { "defineRightShift", defineRightShift }, { "defineLogicalRightShift",defineLogicalRightShift }, { "defineBitwiseOr", defineBitwiseOr }, { "defineBitwiseXor", defineBitwiseXor }, { "defineBitwiseAnd", defineBitwiseAnd }, { "defineLess", defineLess }, { "defineLessOrEqual", defineLessOrEqual }, { "defineEqual", defineEqual }, { "defineIdentical", defineIdentical }, }; for (int i = 0; i < sizeof(OBOs) / sizeof(struct OBO); i++) mGlobal->defineNativeFunction(mWorld.identifiers[widenCString(OBOs[i].name)], OBOs[i].fun); mHasOperatorsPackageLoaded = true; } void Context::initContext() { // if global has a parent, assume it's been initialized already. if (mGlobal->getParent()) return; // predefine the umm, predefined types; struct PDT { char *name; JSType *type; } PDTs[] = { { "any", &Any_Type }, { "Integer", &Integer_Type }, { "Number", &Number_Type }, { "Character", &Character_Type }, { "String", &String_Type }, { "Function", &Function_Type }, { "Array", &Array_Type }, { "Type", &Type_Type }, { "Boolean", &Boolean_Type }, { "Null", &Null_Type }, { "Void", &Void_Type }, { "none", &None_Type } }; for (int i = 0; i < sizeof(PDTs) / sizeof(struct PDT); i++) mGlobal->defineVariable(widenCString(PDTs[i].name), &Type_Type, JSValue(PDTs[i].type)); // set up the correct [[Class]] for the global object (matching SpiderMonkey) mGlobal->setClass(new JSString("global")); // add (XXX some) of the global object properties mGlobal->setProperty(widenCString("NaN"), kNaNValue); mGlobal->setProperty(widenCString("undefined"), kUndefinedValue); // 'Object', 'Date', 'RegExp', 'Array' etc are all (constructor) properties of the global object // Some of these overlap with the predefined types above. The way we handle this is to set a // 'constructor' function for types. When new is encountered, the type is queried for // a 'constructor' function to be invoked. JSObject::initObjectObject(mGlobal); JSFunction::initFunctionObject(mGlobal); JSBoolean::initBooleanObject(mGlobal); // the 'Math' object just has some useful properties JSMath::initMathObject(mGlobal); // This initializes the state of the binary operator overload mechanism. // One could argue that it is unneccessary to do this until the 'Operators' // package is loaded and that all (un-typed) binary operators should use a // form of icode that performed the inline operation instead. JSBinaryOperator::JSBinaryCode defaultFunction[] = { add_Default, subtract_Default, multiply_Default, divide_Default, remainder_Default, shiftLeft_Default, shiftRight_Default, UshiftRight_Default, or_Default, xor_Default, and_Default, less_Default, lessOrEqual_Default, equal_Default, identical_Default }; for (BinaryOperator::BinaryOp b = BinaryOperator::BinaryOperatorFirst; b < BinaryOperator::BinaryOperatorCount; b = (BinaryOperator::BinaryOp)(b + 1) ) addBinaryOperator(b, new BinaryOperator(&Any_Type, &Any_Type, new JSBinaryOperator(defaultFunction[b]))); } const JSValue Context::findBinaryOverride(JSValue &operand1, JSValue &operand2, BinaryOperator::BinaryOp op) { int32 bestDist1 = JSType::NoRelation; int32 bestDist2 = JSType::NoRelation; BinaryOperatorList::iterator candidate = NULL; for (BinaryOperatorList::iterator i = mBinaryOperators[op].begin(); i != mBinaryOperators[op].end(); i++) { int32 dist1 = operand1.getType()->distance((*i)->t1); int32 dist2 = operand2.getType()->distance((*i)->t2); if ((dist1 < bestDist1) && (dist2 < bestDist2)) { bestDist1 = dist1; bestDist2 = dist2; candidate = i; } } ASSERT(candidate); return JSValue((*candidate)->function); } bool Context::hasNamedArguments(ArgumentList &args) { Argument* e = args.end(); for (ArgumentList::iterator r = args.begin(); r != e; r++) { if ((*r).second) return true; } return false; } JSValue Context::interpret(ICodeModule* iCode, const JSValues& args) { assert(mActivation == 0); /* recursion == bad */ JSValue rv; // when invoked with empty args, make sure that 'this' is // going to be the global object. mActivation = new Activation(iCode, args); JSValues* registers = &mActivation->mRegisters; if (args.size() == 0) (*registers)[0] = mGlobal; mPC = mActivation->mICode->its_iCode->begin(); InstructionIterator endPC = mActivation->mICode->its_iCode->end(); // stack of all catch/finally handlers available for the current activation // to implement jsr/rts for finally code std::stack subroutineStack; while (true) { try { // tell any listeners about the current execution state. // XXX should only do this if we're single stepping/tracing. if (mListeners.size()) broadcast(EV_STEP); assert(mPC != endPC); Instruction* instruction = *mPC; switch (instruction->op()) { case CAST: { Cast* c = static_cast(instruction); JSType *toType = op3(c); (*registers)[dst(c).first] = (*registers)[src1(c).first].convert(toType); } break; case SUPER: { Super* su = static_cast(instruction); ASSERT((*registers)[0].isObject()); // should be scope of current class JSScope *s = static_cast((*registers)[0].object->getPrototype()); (*registers)[dst(su).first] = s; } break; case GET_METHOD: { GetMethod* gm = static_cast(instruction); JSValue base = (*registers)[src1(gm).first]; ASSERT(base.isObject()); // XXX runtime error JSClass *theClass = dynamic_cast(base.object->getType()); ASSERT(theClass); (*registers)[dst(gm).first] = theClass->getMethod(src2(gm)); } break; case CALL: { Call* call = static_cast(instruction); JSValue v = (*registers)[op2(call).first]; JSFunction *target = NULL; if (v.isFunction()) target = v.function; else if (v.isObject()) { JSType *t = dynamic_cast(v.object); if (t) target = t->getInvokor(); } if (!target) throw new JSException("Call to non callable object"); if (target->isNative()) { ArgumentList ¶ms = op4(call); JSValues argv(params.size() + 1); argv[0] = (*registers)[op3(call).first]; JSValues::size_type i = 1; for (ArgumentList::const_iterator src = params.begin(), end = params.end(); src != end; ++src, ++i) { argv[i] = (*registers)[src->first.first]; } JSValue result = static_cast(target)->mCode(this, argv); if (op1(call).first != NotARegister) (*registers)[op1(call).first] = result; break; } else { ICodeModule *icm = target->getICode(); ArgumentList &args = op4(call); // mParameterCount includes 'this' and also 1 for a named rest parameter // uint32 pCount = icm->mParameterCount - 1; ArgumentList callArgs(pCount, Argument(TypedRegister(NotARegister, &Null_Type), NULL)); if (icm->mHasNamedRestParameter) pCount--; // walk along the given arguments, handling each. // might be good to optimize the case of calls without named arguments and/or rest parameters JSArray *restArg = NULL; uint32 restIndex = 0; uint32 i; for (i = 0; i < args.size(); i++) { if (args[i].second) { // a named argument TypedRegister r = icm->itsVariables->findVariable(*(args[i].second)); bool isParameter = false; if (r.first != NotABanana) { // we found the name in the target's list of variables if (r.first < icm->mParameterCount) { // make sure we didn't match a local var ASSERT(r.first <= callArgs.size()); // the named argument is arriving in slot i, but needs to be r instead // r.first is the intended target register, we subtract 1 since the callArgs array doesn't include 'this' // here's where we could detect over-writing a positional arg with a named one if that is illegal // if (callArgs[r.first - 1].first.first != NotARegister)... (*registers)[args[i].first.first] = (*registers)[args[i].first.first].convert(r.second); callArgs[r.first - 1] = Argument(args[i].first, NULL); // no need to copy the name through? isParameter = true; } } if (!isParameter) { // wasn't a parameter, make it a property of the rest parameter (if there is one) if (icm->mHasRestParameter) { if (icm->mHasNamedRestParameter) { if (restArg == NULL) { restArg = new JSArray(); restArg->setProperty(*args[i].second, (*registers)[args[i].first.first]); (*registers)[args[i].first.first] = restArg; callArgs[pCount] = Argument(TypedRegister(args[i].first.first, &Array_Type), NULL); } else restArg->setProperty(*args[i].second, (*registers)[args[i].first.first]); } // else just throw it away } else throw new JSException("Named argument doesn't match parameter name in call with no rest parameter"); // what about matching the named rest parameter ? aaarrggh! } } else { if (i >= pCount) { // more args than expected if (icm->mHasRestParameter) { if (icm->mHasNamedRestParameter) { if (restArg == NULL) { restArg = new JSArray(); (*restArg)[restIndex++] = (*registers)[args[i].first.first]; (*registers)[args[i].first.first] = restArg; callArgs[pCount] = Argument(TypedRegister(args[i].first.first, &Array_Type), NULL); } else (*restArg)[restIndex++] = (*registers)[args[i].first.first]; } // else just throw it away } else throw new JSException("Too many arguments in call"); } else { TypedRegister r = icm->itsVariables->getRegister(i + 1); // the variable list includes 'this' (*registers)[args[i].first.first] = (*registers)[args[i].first.first].convert(r.second); callArgs[i] = args[i]; // it's a positional, just slap it in place } } } uint32 contiguousArgs = 0; for (i = 0; i < args.size(); i++) { Argument &arg = args[i]; if (arg.first.first == NotARegister) break; contiguousArgs++; } if ((contiguousArgs + 1) < icm->mNonOptionalParameterCount) // there's always a 'this' in R0 (even though it might be null) throw new JSException("Too few arguments in call"); mLinkage = new Linkage(mLinkage, ++mPC, mActivation, mGlobal, op1(call)); mActivation = new Activation(icm, mActivation, (*registers)[op3(call).first], callArgs); registers = &mActivation->mRegisters; mPC = mActivation->mICode->its_iCode->begin(); endPC = mActivation->mICode->its_iCode->end(); continue; } } case DIRECT_CALL: { DirectCall* call = static_cast(instruction); JSFunction *target = op2(call); if (target->isNative()) { ArgumentList ¶ms = op3(call); JSValues argv(params.size() + 1); JSValues::size_type i = 1; for (ArgumentList::const_iterator src = params.begin(), end = params.end(); src != end; ++src, ++i) { argv[i] = (*registers)[src->first.first]; } JSValue result = static_cast(target)->mCode(this, argv); if (op1(call).first != NotARegister) (*registers)[op1(call).first] = result; break; } else { mLinkage = new Linkage(mLinkage, ++mPC, mActivation, mGlobal, op1(call)); mActivation = new Activation(target->getICode(), mActivation, kNullValue, op3(call)); registers = &mActivation->mRegisters; mPC = mActivation->mICode->its_iCode->begin(); endPC = mActivation->mICode->its_iCode->end(); continue; } } case RETURN_VOID: { Linkage *linkage = mLinkage; if (!linkage) { // let the garbage collector free activations. mActivation = 0; return kUndefinedValue; } mLinkage = linkage->mNext; mActivation = linkage->mActivation; mGlobal = linkage->mScope; registers = &mActivation->mRegisters; if (linkage->mResult.first != NotARegister) (*registers)[linkage->mResult.first] = kUndefinedValue; mPC = linkage->mReturnPC; endPC = mActivation->mICode->its_iCode->end(); } continue; case RETURN: { Return* ret = static_cast(instruction); JSValue result; if (op1(ret).first != NotARegister) result = (*registers)[op1(ret).first]; Linkage* linkage = mLinkage; if (!linkage) { // let the garbage collector free activations. mActivation = 0; return result; } mLinkage = linkage->mNext; mActivation = linkage->mActivation; mGlobal = linkage->mScope; registers = &mActivation->mRegisters; if (linkage->mResult.first != NotARegister) (*registers)[linkage->mResult.first] = result; mPC = linkage->mReturnPC; endPC = mActivation->mICode->its_iCode->end(); } continue; case MOVE: { Move* mov = static_cast(instruction); (*registers)[dst(mov).first] = (*registers)[src1(mov).first]; } break; case LOAD_NAME: { LoadName* ln = static_cast(instruction); JSFunction *getter = mGlobal->getter(*src1(ln)); if (getter) { ASSERT(!getter->isNative()); mLinkage = new Linkage(mLinkage, ++mPC, mActivation, mGlobal, dst(ln)); mActivation = new Activation(getter->getICode()); registers = &mActivation->mRegisters; mPC = mActivation->mICode->its_iCode->begin(); endPC = mActivation->mICode->its_iCode->end(); continue; } else (*registers)[dst(ln).first] = mGlobal->getVariable(*src1(ln)); } break; case SAVE_NAME: { SaveName* sn = static_cast(instruction); JSFunction *setter = mGlobal->setter(*dst(sn)); if (setter) { ASSERT(!setter->isNative()); mLinkage = new Linkage(mLinkage, ++mPC, mActivation, mGlobal, TypedRegister(NotARegister, &Null_Type)); mActivation = new Activation(setter->getICode(), (*registers)[src1(sn).first]); registers = &mActivation->mRegisters; mPC = mActivation->mICode->its_iCode->begin(); endPC = mActivation->mICode->its_iCode->end(); continue; } else mGlobal->setVariable(*dst(sn), (*registers)[src1(sn).first]); } break; case NEW_OBJECT: { NewObject* no = static_cast(instruction); if (src1(no).first != NotARegister) (*registers)[dst(no).first] = new JSObject((*registers)[src1(no).first]); else (*registers)[dst(no).first] = new JSObject(); } break; case NEW_CLASS: { NewClass* nc = static_cast(instruction); JSClass* thisClass = src1(nc); JSInstance* thisInstance = new(thisClass) JSInstance(thisClass); (*registers)[dst(nc).first] = thisInstance; } break; case NEW_FUNCTION: { NewFunction* nf = static_cast(instruction); (*registers)[dst(nf).first] = new JSFunction(src1(nf)); } break; case NEW_ARRAY: { NewArray* na = static_cast(instruction); (*registers)[dst(na).first] = new JSArray(); } break; case DELETE_PROP: { DeleteProp* dp = static_cast(instruction); JSValue& value = (*registers)[src1(dp).first]; if (value.isObject() && !value.isType()) { (*registers)[dst(dp).first] = value.object->deleteProperty(*src2(dp)); } } break; case GET_PROP: { GetProp* gp = static_cast(instruction); JSValue& value = (*registers)[src1(gp).first]; if (value.isObject()) { if (value.isType()) { // REVISIT: should signal error if slot doesn't exist. NOT_REACHED("tell me I'm wrong"); JSClass* thisClass = dynamic_cast(value.type); if (thisClass && thisClass->hasStatic(*src2(gp))) { const JSSlot& slot = thisClass->getStatic(*src2(gp)); (*registers)[dst(gp).first] = (*thisClass)[slot.mIndex]; } else (*registers)[dst(gp).first] = value.object->getProperty(*src2(gp)); } else { (*registers)[dst(gp).first] = value.object->getProperty(*src2(gp)); } } // XXX runtime error } break; case SET_PROP: { SetProp* sp = static_cast(instruction); JSValue& value = (*registers)[dst(sp).first]; if (value.isObject()) { if (value.isType()) { // REVISIT: should signal error if slot doesn't exist. NOT_REACHED("tell me I'm wrong"); JSClass* thisClass = dynamic_cast(value.object); if (thisClass && thisClass->hasStatic(*src1(sp))) { const JSSlot& slot = thisClass->getStatic(*src1(sp)); (*thisClass)[slot.mIndex] = (*registers)[src2(sp).first]; } else value.object->setProperty(*src1(sp), (*registers)[src2(sp).first]); } else { value.object->setProperty(*src1(sp), (*registers)[src2(sp).first]); } } } break; case GET_STATIC: { GetStatic* gs = static_cast(instruction); JSClass* c = src1(gs); (*registers)[dst(gs).first] = (*c)[src2(gs)]; } break; case SET_STATIC: { SetStatic* ss = static_cast(instruction); JSClass* c = dst(ss); (*c)[src1(ss)] = (*registers)[src2(ss).first]; } break; /* In 1.5, there is no 'array' type really, the index operation turns into a get_property call like this, only we need to be using JSString throughout. case GET_ELEMENT: { GetElement* ge = static_cast(instruction); JSValue& base = (*registers)[src1(ge)]; JSValue index = (*registers)[src2(ge)].toString(); if (base.tag == JSValue::object_tag) { JSObject* object = base.object; (*registers)[dst(ge)] = object->getProperty(*index.string); } } break; */ case GET_ELEMENT: { GetElement* ge = static_cast(instruction); JSValue& value = (*registers)[src1(ge).first]; if (value.tag == JSValue::array_tag) { JSArray* array = value.array; (*registers)[dst(ge).first] = (*array)[(*registers)[src2(ge).first]]; } } break; case SET_ELEMENT: { SetElement* se = static_cast(instruction); JSValue& value = (*registers)[dst(se).first]; if (value.tag == JSValue::array_tag) { JSArray* array = value.array; (*array)[(*registers)[src1(se).first]] = (*registers)[src2(se).first]; } } break; case GET_SLOT: { GetSlot* gs = static_cast(instruction); JSValue& value = (*registers)[src1(gs).first]; if (value.isObject()) { JSInstance* inst = static_cast(value.object); (*registers)[dst(gs).first] = (*inst)[src2(gs)]; } // XXX runtime error } break; case SET_SLOT: { SetSlot* ss = static_cast(instruction); JSValue& value = (*registers)[dst(ss).first]; if (value.isObject()) { JSInstance* inst = static_cast(value.object); (*inst)[src1(ss)] = (*registers)[src2(ss).first]; } } break; case LOAD_IMMEDIATE: { LoadImmediate* li = static_cast(instruction); (*registers)[dst(li).first] = src1(li); } break; case LOAD_STRING: { LoadString* ls = static_cast(instruction); (*registers)[dst(ls).first] = src1(ls); } break; case LOAD_BOOLEAN: { LoadBoolean* lb = static_cast(instruction); (*registers)[dst(lb).first] = src1(lb); } break; case BRANCH: { GenericBranch* bra = static_cast(instruction); mPC = mActivation->mICode->its_iCode->begin() + ofs(bra); continue; } break; case BRANCH_TRUE: { GenericBranch* bc = static_cast(instruction); ASSERT((*registers)[src1(bc).first].isBoolean()); if ((*registers)[src1(bc).first].boolean) { mPC = mActivation->mICode->its_iCode->begin() + ofs(bc); continue; } } break; case BRANCH_FALSE: { GenericBranch* bc = static_cast(instruction); ASSERT((*registers)[src1(bc).first].isBoolean()); if (!(*registers)[src1(bc).first].boolean) { mPC = mActivation->mICode->its_iCode->begin() + ofs(bc); continue; } } break; case BRANCH_INITIALIZED: { GenericBranch* bc = static_cast(instruction); if ((*registers)[src1(bc).first].isInitialized()) { mPC = mActivation->mICode->its_iCode->begin() + ofs(bc); continue; } } break; case GENERIC_BINARY_OP: { GenericBinaryOP* gbo = static_cast(instruction); JSValue& dest = (*registers)[dst(gbo).first]; JSValue& r1 = (*registers)[val3(gbo).first]; JSValue& r2 = (*registers)[val4(gbo).first]; const JSValue ovr = findBinaryOverride(r1, r2, val2(gbo)); JSFunction *target = ovr.function; if (target->isNative()) { JSValues argv(2); argv[0] = r1; argv[1] = r2; dest = static_cast(target)->mCode(r1, r2); break; } else { mLinkage = new Linkage(mLinkage, ++mPC, mActivation, mGlobal, dst(gbo)); mActivation = new Activation(target->getICode(), r1, r2); registers = &mActivation->mRegisters; mPC = mActivation->mICode->its_iCode->begin(); endPC = mActivation->mICode->its_iCode->end(); continue; } } break; case SHIFTLEFT: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; JSValue num1(r1.toInt32()); JSValue num2(r2.toUInt32()); dest = JSValue(num1.i32 << (num2.u32 & 0x1F)); } break; case SHIFTRIGHT: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; JSValue num1(r1.toInt32()); JSValue num2(r2.toUInt32()); dest = JSValue(num1.i32 >> (num2.u32 & 0x1F)); } break; case USHIFTRIGHT: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; JSValue num1(r1.toUInt32()); JSValue num2(r2.toUInt32()); dest = JSValue(num1.u32 >> (num2.u32 & 0x1F)); } break; case AND: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; JSValue num1(r1.toInt32()); JSValue num2(r2.toInt32()); dest = JSValue(num1.i32 & num2.i32); } break; case OR: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; JSValue num1(r1.toInt32()); JSValue num2(r2.toInt32()); dest = JSValue(num1.i32 | num2.i32); } break; case XOR: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; JSValue num1(r1.toInt32()); JSValue num2(r2.toInt32()); dest = JSValue(num1.i32 ^ num2.i32); } break; case ADD: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); dest = JSValue(r1.f64 + r2.f64); } break; case SUBTRACT: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); dest = JSValue(r1.f64 - r2.f64); } break; case MULTIPLY: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); dest = JSValue(r1.f64 * r2.f64); } break; case DIVIDE: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); dest = JSValue(r1.f64 / r2.f64); } break; case REMAINDER: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); dest = JSValue(fmod(r1.f64, r2.f64)); } break; case COMPARE_LT: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); if (r1.isNaN() || r2.isNaN()) dest = JSValue(); else dest = JSValue(r1.f64 < r2.f64); } break; case COMPARE_LE: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); if (r1.isNaN() || r2.isNaN()) dest = JSValue(); else dest = JSValue(r1.f64 <= r2.f64); } break; case COMPARE_EQ: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); if (r1.isNaN() || r2.isNaN()) dest = JSValue(); else dest = JSValue(r1.f64 == r2.f64); } break; case STRICT_EQ: { Arithmetic* a = static_cast(instruction); JSValue& dest = (*registers)[dst(a).first]; JSValue& r1 = (*registers)[src1(a).first]; JSValue& r2 = (*registers)[src2(a).first]; ASSERT(r1.isNumber()); ASSERT(r2.isNumber()); if (r1.isNaN() || r2.isNaN()) dest = kFalseValue; else dest = JSValue(r1.f64 == r2.f64); } break; case VAR_XCR: { VarXcr *vx = static_cast(instruction); JSValue& dest = (*registers)[dst(vx).first]; JSValue r = (*registers)[src1(vx).first].toNumber(); dest = r; r.f64 += val3(vx); (*registers)[src1(vx).first] = r; } break; case PROP_XCR: { PropXcr *px = static_cast(instruction); JSValue& dest = (*registers)[dst(px).first]; JSValue& base = (*registers)[src1(px).first]; JSObject *object = base.object; JSValue r = object->getProperty(*src2(px)).toNumber(); dest = r; r.f64 += val4(px); object->setProperty(*src2(px), r); } break; case SLOT_XCR: { SlotXcr *sx = static_cast(instruction); JSValue& dest = (*registers)[dst(sx).first]; JSValue& base = (*registers)[src1(sx).first]; JSInstance *inst = static_cast(base.object); JSValue r = (*inst)[src2(sx)].toNumber(); dest = r; r.f64 += val4(sx); (*inst)[src2(sx)] = r; } break; case STATIC_XCR: { StaticXcr *sx = static_cast(instruction); JSValue& dest = (*registers)[dst(sx).first]; JSClass* thisClass = src1(sx); JSValue r = (*thisClass)[src2(sx)].toNumber(); dest = r; r.f64 += val4(sx); (*thisClass)[src2(sx)] = r; } break; case NAME_XCR: { NameXcr *nx = static_cast(instruction); JSValue& dest = (*registers)[dst(nx).first]; JSValue r = mGlobal->getVariable(*src1(nx)).toNumber(); dest = r; r.f64 += val3(nx); mGlobal->setVariable(*src1(nx), r); } break; case TEST: { Test* tst = static_cast(instruction); (*registers)[dst(tst).first] = (*registers)[src1(tst).first].toBoolean(); } break; case NEGATE: { Negate* neg = static_cast(instruction); (*registers)[dst(neg).first] = -(*registers)[src1(neg).first].toNumber().f64; } break; case POSATE: { Posate* pos = static_cast(instruction); (*registers)[dst(pos).first] = (*registers)[src1(pos).first].toNumber(); } break; case BITNOT: { Bitnot* bn = static_cast(instruction); (*registers)[dst(bn).first] = ~(*registers)[src1(bn).first].toInt32().i32; } break; case NOT: { Not* nt = static_cast(instruction); ASSERT((*registers)[src1(nt).first].isBoolean()); (*registers)[dst(nt).first] = !(*registers)[src1(nt).first].boolean; } break; case THROW: { Throw* thrw = static_cast(instruction); throw new JSException((*registers)[op1(thrw).first]); } case TRYIN: { // push the catch handler address onto the try stack Tryin* tri = static_cast(instruction); mActivation->catchStack.push_back(new Handler(op1(tri), op2(tri))); } break; case TRYOUT: { Handler *h = mActivation->catchStack.back(); mActivation->catchStack.pop_back(); delete h; } break; case JSR: { subroutineStack.push(++mPC); Jsr* jsr = static_cast(instruction); uint32 offset = ofs(jsr); mPC = mActivation->mICode->its_iCode->begin() + offset; continue; } case RTS: { ASSERT(!subroutineStack.empty()); mPC = subroutineStack.top(); subroutineStack.pop(); continue; } case WITHIN: { Within* within = static_cast(instruction); JSValue& value = (*registers)[op1(within).first]; assert(value.tag == JSValue::object_tag); mGlobal = new JSScope(mGlobal, value.object); } break; case WITHOUT: { // Without* without = static_cast(instruction); mGlobal = mGlobal->getParent(); } break; case DEBUGGER: { if (mListeners.size()) broadcast(EV_DEBUG); break; } default: NOT_REACHED("bad opcode"); break; } // increment the program counter. ++mPC; } catch (JSException *x) { if (mLinkage) { if (mActivation->catchStack.empty()) { Linkage *pLinkage = mLinkage; for (; pLinkage != NULL; pLinkage = pLinkage->mNext) { if (!pLinkage->mActivation->catchStack.empty()) { mActivation = pLinkage->mActivation; Handler *h = mActivation->catchStack.back(); registers = &mActivation->mRegisters; if (h->catchTarget) { mPC = mActivation->mICode->its_iCode->begin() + h->catchTarget->mOffset; } else { ASSERT(h->finallyTarget); mPC = mActivation->mICode->its_iCode->begin() + h->finallyTarget->mOffset; } mLinkage = pLinkage; break; } } if (pLinkage) continue; } else { Handler *h = mActivation->catchStack.back(); if (h->catchTarget) { mPC = mActivation->mICode->its_iCode->begin() + h->catchTarget->mOffset; } else { ASSERT(h->finallyTarget); mPC = mActivation->mICode->its_iCode->begin() + h->finallyTarget->mOffset; } continue; } } rv = x->value; break; } } mActivation = 0; return rv; } /* interpret */ void Context::addListener(Listener* listener) { mListeners.push_back(listener); } void Context::removeListener(Listener* listener) { ListenerIterator e = mListeners.end(); ListenerIterator l = std::find(mListeners.begin(), e, listener); if (l != e) mListeners.erase(l); } void Context::broadcast(Event event) { for (ListenerIterator i = mListeners.begin(), e = mListeners.end(); i != e; ++i) { Listener* listener = *i; listener->listen(this, event); } } Context::Frame* Context::getFrames() { return mLinkage; } } /* namespace Interpreter */ } /* namespace JavaScript */