// -*- 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) 2000 Netscape Communications Corporation. All // Rights Reserved. #include "interpreter.h" #include "world.h" #include #include namespace JavaScript { using std::map; using std::less; using std::pair; #if defined(XP_MAC) // copied from default template parameters in map. typedef gc_allocator > gc_map_allocator; #elif defined(XP_UNIX) // FIXME: in libg++, they assume the map's allocator is a byte allocator, // which is wrapped in a simple_allocator. this is crap. typedef char _Char[1]; typedef gc_allocator<_Char> gc_map_allocator; #elif defined(_WIN32) // FIXME: MSVC++'s notion. this is why we had to add _Charalloc(). typedef gc_allocator gc_map_allocator; #endif /** * Basic behavior of all JS objects, mapping a name to a value. * This is provided mainly to avoid having an awkward implementation * of JSObject & JSArray, which must each define its own * gc_allocator. This is all in flux. */ class JSMap : public gc_base { map, gc_map_allocator> properties; public: JSValue& operator[](const String& name) { return properties[name]; } }; /** * Private representation of a JavaScript object. * This will change over time, so it is treated as an opaque * type everywhere else but here. */ class JSObject : public JSMap {}; /** * Private representation of a JavaScript array. */ class JSArray : public JSMap { JSValues elements; public: JSArray() : elements(1) {} JSArray(uint32 size) : elements(size) {} JSArray(const JSValues &v) : elements(v) {} uint32 length() { return elements.size(); } JSValue& operator[](const JSValue& index) { // for now, we can only handle f64 index values. uint32 n = (uint32)index.f64; // obviously, a sparse representation might be better. uint32 size = elements.size(); if (n >= size) expand(n, size); return elements[n]; } JSValue& operator[](uint32 n) { // obviously, a sparse representation might be better. uint32 size = elements.size(); if (n >= size) expand(n, size); return elements[n]; } void resize(uint32 size) { elements.resize(size); } private: void expand(uint32 n, uint32 size) { do { size *= 2; } while (n >= size); elements.resize(size); } }; class JSFunction : public JSMap { ICodeModule* mICode; public: JSFunction(ICodeModule* iCode) : mICode(iCode) {} ICodeModule* getICode() { return mICode; } }; /** * Represents the current function's invocation state. */ struct JSActivation : public gc_base { JSValues mRegisters; JSValues mLocals; JSActivation(ICodeModule* iCode, const JSValues& args) : mRegisters(iCode->itsMaxRegister + 1), mLocals(args) { // ensure that locals array is large enough. uint32 localsSize = iCode->itsMaxVariable + 1; if (localsSize > mLocals.size()) mLocals.resize(localsSize); } JSActivation(ICodeModule* iCode, JSActivation* caller, const RegisterList& list) : mRegisters(iCode->itsMaxRegister + 1), mLocals(iCode->itsMaxVariable + 1) { // copy caller's parameter list in to locals. JSValues::iterator dest = mLocals.begin(); const JSValues& params = caller->mRegisters; for (RegisterList::const_iterator src = list.begin(), end = list.end(); src != end; ++src, ++dest) { *dest = params[*src]; } } }; /** * Stores saved state from the *previous* activation, the current activation is alive * and well in locals of the interpreter loop. */ struct JSFrame : public gc_base { JSFrame(InstructionIterator returnPC, InstructionIterator basePC, JSActivation* activation, Register result) : itsReturnPC(returnPC), itsBasePC(basePC), itsActivation(activation), itsResult(result) { } InstructionIterator itsReturnPC; InstructionIterator itsBasePC; JSActivation* itsActivation; // caller's activation. Register itsResult; // the desired target register for the return value }; // a stack of JSFrames. typedef std::stack > > JSFrameStack; // operand access macros. #define op1(i) (i->itsOperand1) #define op2(i) (i->itsOperand2) #define op3(i) (i->itsOperand3) // mnemonic names for operands. #define dst(i) op1(i) #define src1(i) op2(i) #define src2(i) op3(i) static JSObject globals; JSValue& defineGlobalProperty(const String& name, const JSValue& value) { return (globals[name] = value); } // FIXME: need to copy the ICodeModule's instruction stream. JSValue& defineFunction(const String& name, ICodeModule* iCode) { JSValue value; value.function = new JSFunction(iCode); return defineGlobalProperty(name, value); } JSValue interpret(ICodeModule* iCode, const JSValues& args) { // stack of JSFrames. // XXX is a linked list of activation's sufficient? JSFrameStack frames; // initial activation. JSActivation* activation = new JSActivation(iCode, args); JSValues* locals = &activation->mLocals; JSValues* registers = &activation->mRegisters; InstructionIterator begin_pc = iCode->its_iCode->begin(); InstructionIterator pc = begin_pc; while (true) { Instruction* instruction = *pc; switch (instruction->opcode()) { case CALL: { Call* call = static_cast(instruction); frames.push(new JSFrame(++pc, begin_pc, activation, op1(call))); ICodeModule* target = (*registers)[op2(call)].function->getICode(); activation = new JSActivation(target, activation, op3(call)); locals = &activation->mLocals; registers = &activation->mRegisters; begin_pc = pc = target->its_iCode->begin(); } continue; case RETURN: { Return* ret = static_cast(instruction); JSValue result; if (op1(ret) != NotARegister) result = (*registers)[op1(ret)]; if (frames.empty()) return result; JSFrame *frame = frames.top(); frames.pop(); activation = frame->itsActivation; registers = &activation->mRegisters; locals = &activation->mLocals; (*registers)[frame->itsResult] = result; pc = frame->itsReturnPC; begin_pc = frame->itsBasePC; } continue; case MOVE_TO: { Move* mov = static_cast(instruction); (*registers)[dst(mov)] = (*registers)[src1(mov)]; } break; case LOAD_NAME: { LoadName* ln = static_cast(instruction); (*registers)[dst(ln)] = globals[*src1(ln)]; } break; case SAVE_NAME: { SaveName* sn = static_cast(instruction); globals[*dst(sn)] = (*registers)[src1(sn)]; } break; case NEW_OBJECT: { NewObject* no = static_cast(instruction); (*registers)[dst(no)].object = new JSObject(); } break; case NEW_ARRAY: { NewArray* na = static_cast(instruction); (*registers)[dst(na)].array = new JSArray(); } break; case GET_PROP: { GetProp* gp = static_cast(instruction); JSObject* object = (*registers)[src1(gp)].object; (*registers)[dst(gp)] = (*object)[*src2(gp)]; } break; case SET_PROP: { SetProp* sp = static_cast(instruction); JSObject* object = (*registers)[dst(sp)].object; (*object)[*src1(sp)] = (*registers)[src2(sp)]; } break; case GET_ELEMENT: { GetElement* ge = static_cast(instruction); JSArray* array = (*registers)[src1(ge)].array; (*registers)[dst(ge)] = (*array)[(*registers)[src2(ge)]]; } break; case SET_ELEMENT: { SetElement* se = static_cast(instruction); JSArray* array = (*registers)[dst(se)].array; (*array)[(*registers)[src1(se)]] = (*registers)[src2(se)]; } break; case LOAD_IMMEDIATE: { LoadImmediate* li = static_cast(instruction); (*registers)[dst(li)] = JSValue(src1(li)); } break; case LOAD_VAR: { LoadVar* lv = static_cast(instruction); (*registers)[dst(lv)] = (*locals)[src1(lv)]; } break; case SAVE_VAR: { SaveVar* sv = static_cast(instruction); (*locals)[dst(sv)] = (*registers)[src1(sv)]; } break; case BRANCH: { ResolvedBranch* bra = static_cast(instruction); pc = begin_pc + dst(bra); continue; } break; case BRANCH_LT: { ResolvedBranchCond* bc = static_cast(instruction); if ((*registers)[src1(bc)].i32 < 0) { pc = begin_pc + dst(bc); continue; } } break; case BRANCH_LE: { ResolvedBranchCond* bc = static_cast(instruction); if ((*registers)[src1(bc)].i32 <= 0) { pc = begin_pc + dst(bc); continue; } } break; case BRANCH_EQ: { ResolvedBranchCond* bc = static_cast(instruction); if ((*registers)[src1(bc)].i32 == 0) { pc = begin_pc + dst(bc); continue; } } break; case BRANCH_NE: { ResolvedBranchCond* bc = static_cast(instruction); if ((*registers)[src1(bc)].i32 != 0) { pc = begin_pc + dst(bc); continue; } } break; case BRANCH_GE: { ResolvedBranchCond* bc = static_cast(instruction); if ((*registers)[src1(bc)].i32 >= 0) { pc = begin_pc + dst(bc); continue; } } break; case BRANCH_GT: { ResolvedBranchCond* bc = static_cast(instruction); if ((*registers)[src1(bc)].i32 > 0) { pc = begin_pc + dst(bc); continue; } } break; case ADD: { // could get clever here with Functional forms. Arithmetic* add = static_cast(instruction); (*registers)[dst(add)] = JSValue((*registers)[src1(add)].f64 + (*registers)[src2(add)].f64); } break; case SUBTRACT: { Arithmetic* sub = static_cast(instruction); (*registers)[dst(sub)] = JSValue((*registers)[src1(sub)].f64 - (*registers)[src2(sub)].f64); } break; case MULTIPLY: { Arithmetic* mul = static_cast(instruction); (*registers)[dst(mul)] = JSValue((*registers)[src1(mul)].f64 * (*registers)[src2(mul)].f64); } break; case DIVIDE: { Arithmetic* div = static_cast(instruction); (*registers)[dst(div)] = JSValue((*registers)[src1(div)].f64 / (*registers)[src2(div)].f64); } break; case COMPARE_LT: case COMPARE_LE: case COMPARE_EQ: case COMPARE_NE: case COMPARE_GT: case COMPARE_GE: { Arithmetic* cmp = static_cast(instruction); float64 diff = ((*registers)[src1(cmp)].f64 - (*registers)[src2(cmp)].f64); (*registers)[dst(cmp)].i32 = (diff == 0.0 ? 0 : (diff > 0.0 ? 1 : -1)); } break; case NOT: { Move* nt = static_cast(instruction); (*registers)[dst(nt)].i32 = !(*registers)[src1(nt)].i32; } break; default: NOT_REACHED("bad opcode"); break; } // increment the program counter. ++pc; } } }