/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "frontend/ElemOpEmitter.h" #include "frontend/BytecodeEmitter.h" #include "frontend/SharedContext.h" #include "vm/Opcodes.h" using namespace js; using namespace js::frontend; ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind) : bce_(bce), kind_(kind), objKind_(objKind) {} bool ElemOpEmitter::prepareForObj() { MOZ_ASSERT(state_ == State::Start); #ifdef DEBUG state_ = State::Obj; #endif return true; } bool ElemOpEmitter::prepareForKey() { MOZ_ASSERT(state_ == State::Obj); if (!isSuper() && isIncDec()) { if (!bce_->emit1(JSOP_CHECKOBJCOERCIBLE)) { // OBJ return false; } } if (isCall()) { // We need a second |this| that will be consumed during computation of // the property value. (The original |this| is passed to the call.) if (!bce_->emit1(JSOP_DUP)) { // [Super] // // THIS THIS // // [Other] // // OBJ OBJ return false; } } #ifdef DEBUG state_ = State::Key; #endif return true; } bool ElemOpEmitter::emitGet() { MOZ_ASSERT(state_ == State::Key); // We need to convert the key to an object id first, so that we do not do // it inside both the GETELEM and the SETELEM. if (isIncDec() || isCompoundAssignment()) { if (!bce_->emit1(JSOP_TOID)) { // [Super] // // THIS KEY // // [Other] // // OBJ KEY return false; } } if (isSuper()) { if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS? THIS KEY SUPERBASE return false; } } if (isIncDec() || isCompoundAssignment()) { if (isSuper()) { // There's no such thing as JSOP_DUP3, so we have to be creative. // Note that pushing things again is no fewer JSOps. if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS return false; } if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS KEY return false; } if (!bce_->emitDupAt(2)) { // THIS KEY SUPERBASE THIS KEY SUPERBASE return false; } } else { if (!bce_->emit1(JSOP_DUP2)) { // OBJ KEY OBJ KEY return false; } } } JSOp op; if (isSuper()) { op = JSOP_GETELEM_SUPER; } else if (isCall()) { op = JSOP_CALLELEM; } else { op = JSOP_GETELEM; } if (!bce_->emitElemOpBase(op)) { // [Get] // // ELEM // // [Call] // // THIS ELEM // // [Inc/Dec/Assignment, // // Super] // // THIS KEY SUPERBASE ELEM // // [Inc/Dec/Assignment, // // Other] // // OBJ KEY ELEM return false; } if (isCall()) { if (!bce_->emit1(JSOP_SWAP)) { // ELEM THIS return false; } } #ifdef DEBUG state_ = State::Get; #endif return true; } bool ElemOpEmitter::prepareForRhs() { MOZ_ASSERT(isSimpleAssignment() || isPropInit()|| isCompoundAssignment()); MOZ_ASSERT_IF(isSimpleAssignment() || isPropInit(), state_ == State::Key); MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get); if (isSimpleAssignment() || isPropInit()) { // For CompoundAssignment, SUPERBASE is already emitted by emitGet. if (isSuper()) { if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE return false; } } } #ifdef DEBUG state_ = State::Rhs; #endif return true; } bool ElemOpEmitter::skipObjAndKeyAndRhs() { MOZ_ASSERT(state_ == State::Start); MOZ_ASSERT(isSimpleAssignment() || isPropInit()); #ifdef DEBUG state_ = State::Rhs; #endif return true; } bool ElemOpEmitter::emitDelete() { MOZ_ASSERT(state_ == State::Key); MOZ_ASSERT(isDelete()); if (isSuper()) { if (!bce_->emit1(JSOP_TOID)) { // THIS KEY return false; } if (!bce_->emit1(JSOP_SUPERBASE)) { // THIS KEY SUPERBASE return false; } // Unconditionally throw when attempting to delete a super-reference. if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) { return false; // THIS KEY SUPERBASE } // Another wrinkle: Balance the stack from the emitter's point of view. // Execution will not reach here, as the last bytecode threw. if (!bce_->emitPopN(2)) { // THIS return false; } } else { JSOp op = bce_->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM; if (!bce_->emitElemOpBase(op)){ // SUCCEEDED return false; } } #ifdef DEBUG state_ = State::Delete; #endif return true; } bool ElemOpEmitter::emitAssignment() { MOZ_ASSERT(isSimpleAssignment() || isPropInit() || isCompoundAssignment()); MOZ_ASSERT(state_ == State::Rhs); MOZ_ASSERT_IF(isPropInit(), !isSuper()); JSOp setOp = isPropInit() ? JSOP_INITELEM : isSuper() ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM; if (!bce_->emitElemOpBase(setOp)) { // ELEM return false; } #ifdef DEBUG state_ = State::Assignment; #endif return true; } bool ElemOpEmitter::emitIncDec() { MOZ_ASSERT(state_ == State::Key); MOZ_ASSERT(isIncDec()); if (!emitGet()) { // ... ELEM return false; } MOZ_ASSERT(state_ == State::Get); JSOp incOp = isInc() ? JSOP_INC : JSOP_DEC; if (!bce_->emit1(JSOP_POS)) { // ... N return false; } if (isPostIncDec()) { if (!bce_->emit1(JSOP_DUP)) { // ... N? N return false; } } if (!bce_->emit1(incOp)) { // ... N? N+1 return false; } if (isPostIncDec()) { if (isSuper()) { // THIS KEY OBJ N N+1 if (!bce_->emit2(JSOP_PICK, 4)) { // KEY SUPERBASE N N+1 THIS return false; } if (!bce_->emit2(JSOP_PICK, 4)) { // SUPERBASE N N+1 THIS KEY return false; } if (!bce_->emit2(JSOP_PICK, 4)) { // N N+1 THIS KEY SUPERBASE return false; } if (!bce_->emit2(JSOP_PICK, 3)) { // N THIS KEY SUPERBASE N+1 return false; } } else { // OBJ KEY N N+1 if (!bce_->emit2(JSOP_PICK, 3)) { // KEY N N+1 OBJ return false; } if (!bce_->emit2(JSOP_PICK, 3)) { // N N+1 OBJ KEY return false; } if (!bce_->emit2(JSOP_PICK, 2)) { // N OBJ KEY N+1 return false; } } } JSOp setOp = isSuper() ? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER) : (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM); if (!bce_->emitElemOpBase(setOp)) { // N? N+1 return false; } if (isPostIncDec()) { if (!bce_->emit1(JSOP_POP)) { // N return false; } } #ifdef DEBUG state_ = State::IncDec; #endif return true; }