Interpreter.java

/* -*- Mode: java; 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/. */

package org.mozilla.javascript;

import static org.mozilla.javascript.UniqueTag.DOUBLE_MARK;

import java.io.PrintStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import org.mozilla.javascript.ScriptRuntime.NoSuchMethodShim;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.debug.DebugFrame;

public final class Interpreter extends Icode implements Evaluator {
    // data for parsing
    InterpreterData itsData;

    static final int EXCEPTION_TRY_START_SLOT = 0;
    static final int EXCEPTION_TRY_END_SLOT = 1;
    static final int EXCEPTION_HANDLER_SLOT = 2;
    static final int EXCEPTION_TYPE_SLOT = 3;
    static final int EXCEPTION_LOCAL_SLOT = 4;
    static final int EXCEPTION_SCOPE_SLOT = 5;
    // SLOT_SIZE: space for try start/end, handler, start, handler type,
    //            exception local and scope local
    static final int EXCEPTION_SLOT_SIZE = 6;

    /** Class to hold data corresponding to one interpreted call stack frame. */
    private static class CallFrame implements Cloneable, Serializable {
        private static final long serialVersionUID = -2843792508994958978L;

        // fields marked "final" in a comment are effectively final except when they're modified
        // immediately after cloning.

        final CallFrame parentFrame;
        // amount of stack frames before this one on the interpretation stack
        final short frameIndex;
        // The frame that the iterator was executing.
        final CallFrame previousInterpreterFrame;
        final int parentPC;
        // If true indicates read-only frame that is a part of continuation
        boolean frozen;

        final InterpretedFunction fnOrScript;
        final InterpreterData idata;

        // Stack structure
        // stack[0 <= i < localShift]: arguments and local variables
        // stack[localShift <= i <= emptyStackTop]: used for local temporaries
        // stack[emptyStackTop < i < stack.length]: stack data
        // sDbl[i]: if stack[i] is UniqueTag.DOUBLE_MARK, sDbl[i] holds the number value

        final Object[] stack;
        final byte[] stackAttributes;
        final double[] sDbl;

        final CallFrame varSource; // defaults to this unless continuation frame
        final short emptyStackTop;

        final DebugFrame debuggerFrame;
        final boolean useActivation;
        boolean isContinuationsTopFrame;

        final Scriptable thisObj;

        // The values that change during interpretation

        Object result;
        double resultDbl;
        int pc;
        int pcPrevBranch;
        int pcSourceLineStart;
        Scriptable scope;

        int savedStackTop;
        int savedCallOp;
        Object throwable;

        CallFrame(
                Context cx,
                Scriptable thisObj,
                InterpretedFunction fnOrScript,
                CallFrame parentFrame,
                CallFrame previousInterpreterFrame) {
            idata = fnOrScript.idata;
            debuggerFrame = cx.debugger != null ? cx.debugger.getFrame(cx, idata) : null;
            useActivation = debuggerFrame != null || idata.itsNeedsActivation;

            emptyStackTop = (short) (idata.itsMaxVars + idata.itsMaxLocals - 1);
            int maxFrameArray = idata.itsMaxFrameArray;
            if (maxFrameArray != emptyStackTop + idata.itsMaxStack + 1) Kit.codeBug();

            stack = new Object[maxFrameArray];
            stackAttributes = new byte[maxFrameArray];
            sDbl = new double[maxFrameArray];

            this.fnOrScript = fnOrScript;
            varSource = this;
            this.thisObj = thisObj;

            this.parentFrame = parentFrame;
            if (parentFrame == null) {
                this.parentPC =
                        previousInterpreterFrame == null
                                ? -1
                                : previousInterpreterFrame.pcSourceLineStart;
            } else {
                this.parentPC = parentFrame.pcSourceLineStart;
            }
            this.previousInterpreterFrame = previousInterpreterFrame;
            frameIndex = (short) ((parentFrame == null) ? 0 : parentFrame.frameIndex + 1);
            if (frameIndex > cx.getMaximumInterpreterStackDepth()) {
                throw Context.reportRuntimeError("Exceeded maximum stack depth");
            }

            // Initialize initial values of variables that change during
            // interpretation.
            result = Undefined.instance;
            pcSourceLineStart = idata.firstLinePC;

            savedStackTop = emptyStackTop;
        }

        private CallFrame(CallFrame original, boolean makeOrphan) {
            this(
                    original,
                    makeOrphan ? null : original.parentFrame,
                    makeOrphan ? null : original.previousInterpreterFrame);
        }

        /* Copy the frame for *continuations*. Here we want to make
        fresh copies of the stack and everything related to it. */
        private CallFrame(
                CallFrame original, CallFrame parentFrame, CallFrame previousInterpreterFrame) {
            if (!original.frozen) Kit.codeBug();

            stack = Arrays.copyOf(original.stack, original.stack.length);
            stackAttributes =
                    Arrays.copyOf(original.stackAttributes, original.stackAttributes.length);
            sDbl = Arrays.copyOf(original.sDbl, original.sDbl.length);

            frozen = false;
            this.parentFrame = parentFrame;
            this.previousInterpreterFrame = previousInterpreterFrame;
            if (parentFrame == null) {
                frameIndex = 0;
                parentPC =
                        previousInterpreterFrame == null
                                ? -1
                                : previousInterpreterFrame.pcSourceLineStart;
            } else {
                frameIndex = original.frameIndex;
                parentPC = parentFrame.pcSourceLineStart;
            }

            fnOrScript = original.fnOrScript;
            idata = original.idata;

            varSource = original.varSource;
            emptyStackTop = original.emptyStackTop;

            debuggerFrame = original.debuggerFrame;
            useActivation = original.useActivation;
            isContinuationsTopFrame = original.isContinuationsTopFrame;

            thisObj = original.thisObj;

            result = original.result;
            resultDbl = original.resultDbl;
            pc = original.pc;
            pcPrevBranch = original.pcPrevBranch;
            pcSourceLineStart = original.pcSourceLineStart;
            scope = original.scope;

            savedStackTop = original.savedStackTop;
            savedCallOp = original.savedCallOp;
            throwable = original.throwable;
        }

        /* Copy the stack for running a generator. We're only doing
        this to maintain the correct chain of parents for exception
        stacks, so we'll reuse the existing stack arrays. */
        private CallFrame(
                CallFrame original,
                CallFrame parentFrame,
                CallFrame previousInterpreterFrame,
                boolean keepFrozen) {
            if (!original.frozen) Kit.codeBug();

            stack = original.stack;
            stackAttributes = original.stackAttributes;
            sDbl = original.sDbl;

            frozen = keepFrozen;
            this.parentFrame = parentFrame;
            this.previousInterpreterFrame = previousInterpreterFrame;
            if (parentFrame == null) {
                frameIndex = 0;
                parentPC =
                        previousInterpreterFrame == null
                                ? -1
                                : previousInterpreterFrame.pcSourceLineStart;
            } else {
                frameIndex = original.frameIndex;
                parentPC = parentFrame.pcSourceLineStart;
            }

            fnOrScript = original.fnOrScript;
            idata = original.idata;

            varSource = original.varSource;
            emptyStackTop = original.emptyStackTop;

            debuggerFrame = original.debuggerFrame;
            useActivation = original.useActivation;
            isContinuationsTopFrame = original.isContinuationsTopFrame;

            thisObj = original.thisObj;

            result = original.result;
            resultDbl = original.resultDbl;
            pc = original.pc;
            pcPrevBranch = original.pcPrevBranch;
            pcSourceLineStart = original.pcSourceLineStart;
            scope = original.scope;

            savedStackTop = original.savedStackTop;
            savedCallOp = original.savedCallOp;
            throwable = original.throwable;
        }

        void initializeArgs(
                Context cx,
                Scriptable callerScope,
                Object[] args,
                double[] argsDbl,
                Object[] boundArgs,
                int argShift,
                int argCount,
                Scriptable homeObject) {
            if (useActivation) {
                // Copy args to new array to pass to enterActivationFunction
                // or debuggerFrame.onEnter
                if (argsDbl != null || boundArgs != null) {
                    int blen = boundArgs == null ? 0 : boundArgs.length;
                    args = getArgsArray(args, argsDbl, boundArgs, blen, argShift, argCount);
                }
                argShift = 0;
                argsDbl = null;
                boundArgs = null;
            }

            if (idata.itsFunctionType != 0) {
                scope = fnOrScript.getParentScope();

                if (useActivation) {
                    if (idata.itsFunctionType == FunctionNode.ARROW_FUNCTION) {
                        scope =
                                ScriptRuntime.createArrowFunctionActivation(
                                        fnOrScript,
                                        cx,
                                        scope,
                                        args,
                                        fnOrScript.isStrict(),
                                        idata.argsHasRest,
                                        idata.itsRequiresArgumentObject,
                                        homeObject);
                    } else {
                        scope =
                                ScriptRuntime.createFunctionActivation(
                                        fnOrScript,
                                        cx,
                                        scope,
                                        args,
                                        fnOrScript.isStrict(),
                                        idata.argsHasRest,
                                        idata.itsRequiresArgumentObject,
                                        homeObject);
                    }
                }
            } else {
                scope = callerScope;
                ScriptRuntime.initScript(fnOrScript, thisObj, cx, scope, idata.evalScriptFlag);
            }

            if (idata.itsNestedFunctions != null) {
                if (idata.itsFunctionType != 0 && !idata.itsNeedsActivation) Kit.codeBug();
                for (int i = 0; i < idata.itsNestedFunctions.length; i++) {
                    InterpreterData fdata = idata.itsNestedFunctions[i];
                    if (fdata.itsFunctionType == FunctionNode.FUNCTION_STATEMENT) {
                        initFunction(cx, scope, fnOrScript, i);
                    }
                }
            }

            int varCount = idata.getParamAndVarCount();
            for (int i = 0; i < varCount; i++) {
                if (idata.getParamOrVarConst(i)) stackAttributes[i] = ScriptableObject.CONST;
            }
            int definedArgs = idata.argCount;
            if (definedArgs > argCount) {
                definedArgs = argCount;
            }

            // Fill the frame structure

            int blen = 0;
            if (boundArgs != null) {
                blen = Math.min(definedArgs, boundArgs.length);
                System.arraycopy(boundArgs, 0, stack, 0, blen);
            }

            System.arraycopy(args, argShift, stack, blen, definedArgs - blen);
            if (argsDbl != null) {
                System.arraycopy(argsDbl, argShift, sDbl, blen, definedArgs - blen);
            }
            for (int i = definedArgs; i != idata.itsMaxVars; ++i) {
                stack[i] = Undefined.instance;
            }

            if (idata.argsHasRest) {
                Object[] vals;
                int offset = idata.argCount - 1;
                if (argCount >= idata.argCount) {
                    vals = new Object[argCount - offset];

                    argShift = argShift + offset;
                    for (int valsIdx = 0; valsIdx != vals.length; ++argShift, ++valsIdx) {
                        Object val = args[argShift];
                        if (val == UniqueTag.DOUBLE_MARK) {
                            val = ScriptRuntime.wrapNumber(argsDbl[argShift]);
                        }
                        vals[valsIdx] = val;
                    }
                } else {
                    vals = ScriptRuntime.emptyArgs;
                }
                stack[offset] = cx.newArray(scope, vals);
            }
        }

        CallFrame cloneFrozen() {
            return new CallFrame(this, false);
        }

        CallFrame shallowCloneFrozen(CallFrame newPreviousInterpreeterFrame) {
            return new CallFrame(this, this.parentFrame, newPreviousInterpreeterFrame, true);
        }

        void syncStateToFrame(CallFrame otherFrame) {
            otherFrame.frozen = frozen;
            otherFrame.isContinuationsTopFrame = isContinuationsTopFrame;
            otherFrame.result = result;
            otherFrame.resultDbl = resultDbl;
            otherFrame.pc = pc;
            otherFrame.pcPrevBranch = pcPrevBranch;
            otherFrame.pcSourceLineStart = pcSourceLineStart;
            otherFrame.scope = scope;

            otherFrame.savedStackTop = savedStackTop;
            otherFrame.savedCallOp = savedCallOp;
            otherFrame.throwable = throwable;
        }

        @Override
        public boolean equals(Object other) {
            // Overridden for semantic equality comparison. These objects
            // are typically exposed as NativeContinuation.implementation,
            // comparing them allows establishing whether the continuations
            // are semantically equal.
            if (other instanceof CallFrame) {
                // If the call is not within a Context with a top call, we force
                // one. It is required as some objects within fully initialized
                // global scopes (notably, XMLLibImpl) need to have a top scope
                // in order to evaluate their attributes.
                try (Context cx = Context.enter()) {
                    if (ScriptRuntime.hasTopCall(cx)) {
                        return equalsInTopScope(other).booleanValue();
                    }
                    final Scriptable top = ScriptableObject.getTopLevelScope(scope);
                    return ((Boolean)
                                    ScriptRuntime.doTopCall(
                                            (c, scope, thisObj, args) -> equalsInTopScope(other),
                                            cx,
                                            top,
                                            top,
                                            ScriptRuntime.emptyArgs,
                                            isStrictTopFrame()))
                            .booleanValue();
                }
            }
            return false;
        }

        @Override
        public int hashCode() {
            // Overridden for consistency with equals.
            // Trying to strike a balance between speed of calculation and
            // distribution. Not hashing stack variables as those could have
            // unbounded computational cost and limit it to topmost 8 frames.
            int depth = 0;
            CallFrame f = this;
            int h = 0;
            do {
                h = 31 * (31 * h + f.pc) + f.idata.icodeHashCode();
                f = f.parentFrame;
            } while (f != null && depth++ < 8);
            return h;
        }

        private Boolean equalsInTopScope(Object other) {
            return EqualObjectGraphs.withThreadLocal(eq -> equals(this, (CallFrame) other, eq));
        }

        private boolean isStrictTopFrame() {
            CallFrame f = this;
            for (; ; ) {
                final CallFrame p = f.parentFrame;
                if (p == null) {
                    return f.fnOrScript.isStrict();
                }
                f = p;
            }
        }

        @SuppressWarnings("ReferenceEquality")
        private static Boolean equals(CallFrame f1, CallFrame f2, EqualObjectGraphs equal) {
            // Iterative instead of recursive, as interpreter stack depth can
            // be larger than JVM stack depth.
            for (; ; ) {
                if (f1 == f2) {
                    return Boolean.TRUE;
                } else if (f1 == null || f2 == null) {
                    return Boolean.FALSE;
                } else if (!f1.fieldsEqual(f2, equal)) {
                    return Boolean.FALSE;
                } else {
                    f1 = f1.parentFrame;
                    f2 = f2.parentFrame;
                }
            }
        }

        private boolean fieldsEqual(CallFrame other, EqualObjectGraphs equal) {
            return frameIndex == other.frameIndex
                    && pc == other.pc
                    && compareIdata(idata, other.idata)
                    && equal.equalGraphs(varSource.stack, other.varSource.stack)
                    && Arrays.equals(varSource.sDbl, other.varSource.sDbl)
                    && equal.equalGraphs(thisObj, other.thisObj)
                    && equal.equalGraphs(fnOrScript, other.fnOrScript)
                    && equal.equalGraphs(scope, other.scope);
        }

        CallFrame captureForGenerator() {
            return new CallFrame(this, true);
        }
    }

    private static boolean compareIdata(InterpreterData i1, InterpreterData i2) {
        return i1 == i2 || Objects.equals(getRawSource(i1), getRawSource(i2));
    }

    private static final class ContinuationJump implements Serializable {
        private static final long serialVersionUID = 7687739156004308247L;

        CallFrame capturedFrame;
        CallFrame branchFrame;
        Object result;
        double resultDbl;

        ContinuationJump(NativeContinuation c, CallFrame current) {
            this.capturedFrame = (CallFrame) c.getImplementation();
            if (this.capturedFrame == null || current == null) {
                // Continuation and current execution does not share
                // any frames if there is nothing to capture or
                // if there is no currently executed frames
                this.branchFrame = null;
            } else {
                // Search for branch frame where parent frame chains starting
                // from captured and current meet.
                CallFrame chain1 = this.capturedFrame;
                CallFrame chain2 = current;

                // First work parents of chain1 or chain2 until the same
                // frame depth.
                int diff = chain1.frameIndex - chain2.frameIndex;
                if (diff != 0) {
                    if (diff < 0) {
                        // swap to make sure that
                        // chain1.frameIndex > chain2.frameIndex and diff > 0
                        chain1 = current;
                        chain2 = this.capturedFrame;
                        diff = -diff;
                    }
                    do {
                        chain1 = chain1.parentFrame;
                    } while (--diff != 0);
                    if (chain1.frameIndex != chain2.frameIndex) Kit.codeBug();
                }

                // Now walk parents in parallel until a shared frame is found
                // or until the root is reached.
                while (!Objects.equals(chain1, chain2) && chain1 != null) {
                    chain1 = chain1.parentFrame;
                    chain2 = chain2.parentFrame;
                }

                this.branchFrame = chain1;
                if (this.branchFrame != null && !this.branchFrame.frozen) Kit.codeBug();
            }
        }
    }

    private static CallFrame captureFrameForGenerator(CallFrame frame) {
        frame.frozen = true;
        CallFrame result = frame.captureForGenerator();
        frame.frozen = false;

        return result;
    }

    static {
        // Checks for byte code consistencies, good compiler can eliminate them

        if (Token.LAST_BYTECODE_TOKEN > 127) {
            String str = "Violation of Token.LAST_BYTECODE_TOKEN <= 127";
            System.err.println(str);
            throw new IllegalStateException(str);
        }
        if (MIN_ICODE < -128) {
            String str = "Violation of Interpreter.MIN_ICODE >= -128";
            System.err.println(str);
            throw new IllegalStateException(str);
        }
    }

    @Override
    public Object compile(
            CompilerEnvirons compilerEnv,
            ScriptNode tree,
            String rawSource,
            boolean returnFunction) {
        CodeGenerator cgen = new CodeGenerator();
        itsData = cgen.compile(compilerEnv, tree, rawSource, returnFunction);
        return itsData;
    }

    @Override
    public Script createScriptObject(Object bytecode, Object staticSecurityDomain) {
        if (bytecode != itsData) {
            Kit.codeBug();
        }
        return InterpretedFunction.createScript(itsData, staticSecurityDomain);
    }

    @Override
    public void setEvalScriptFlag(Script script) {
        ((InterpretedFunction) script).idata.evalScriptFlag = true;
    }

    @Override
    public Function createFunctionObject(
            Context cx, Scriptable scope, Object bytecode, Object staticSecurityDomain) {
        if (bytecode != itsData) {
            Kit.codeBug();
        }
        return InterpretedFunction.createFunction(cx, scope, itsData, staticSecurityDomain);
    }

    private static int getShort(byte[] iCode, int pc) {
        return (iCode[pc] << 8) | (iCode[pc + 1] & 0xFF);
    }

    private static int getIndex(byte[] iCode, int pc) {
        return ((iCode[pc] & 0xFF) << 8) | (iCode[pc + 1] & 0xFF);
    }

    private static int getInt(byte[] iCode, int pc) {
        return (iCode[pc] << 24)
                | ((iCode[pc + 1] & 0xFF) << 16)
                | ((iCode[pc + 2] & 0xFF) << 8)
                | (iCode[pc + 3] & 0xFF);
    }

    private static int getExceptionHandler(CallFrame frame, boolean onlyFinally) {
        int[] exceptionTable = frame.idata.itsExceptionTable;
        if (exceptionTable == null) {
            // No exception handlers
            return -1;
        }

        // Icode switch in the interpreter increments PC immediately
        // and it is necessary to subtract 1 from the saved PC
        // to point it before the start of the next instruction.
        int pc = frame.pc - 1;

        // OPT: use binary search
        int best = -1, bestStart = 0, bestEnd = 0;
        for (int i = 0; i != exceptionTable.length; i += EXCEPTION_SLOT_SIZE) {
            int start = exceptionTable[i + EXCEPTION_TRY_START_SLOT];
            int end = exceptionTable[i + EXCEPTION_TRY_END_SLOT];
            if (!(start <= pc && pc < end)) {
                continue;
            }
            if (onlyFinally && exceptionTable[i + EXCEPTION_TYPE_SLOT] != 1) {
                continue;
            }
            if (best >= 0) {
                // Since handlers always nest and they never have shared end
                // although they can share start  it is sufficient to compare
                // handlers ends
                if (bestEnd < end) {
                    continue;
                }
                // Check the above assumption
                if (bestStart > start) Kit.codeBug(); // should be nested
                if (bestEnd == end) Kit.codeBug(); // no ens sharing
            }
            best = i;
            bestStart = start;
            bestEnd = end;
        }
        return best;
    }

    static void dumpICode(InterpreterData idata) {
        if (!Token.printICode) {
            return;
        }

        byte[] iCode = idata.itsICode;
        int iCodeLength = iCode.length;
        String[] strings = idata.itsStringTable;
        BigInteger[] bigInts = idata.itsBigIntTable;
        PrintStream out = System.out;
        out.println("ICode dump, for " + idata.itsName + ", length = " + iCodeLength);
        out.println("MaxStack = " + idata.itsMaxStack);

        int indexReg = 0;
        for (int pc = 0; pc < iCodeLength; ) {
            out.flush();
            out.print(" [" + pc + "] ");
            int token = iCode[pc];
            int icodeLength = bytecodeSpan(token);
            String tname = Icode.bytecodeName(token);
            int old_pc = pc;
            ++pc;
            switch (token) {
                default:
                    if (icodeLength != 1) Kit.codeBug();
                    out.println(tname);
                    break;

                case Icode_GOSUB:
                case Token.GOTO:
                case Token.IFEQ:
                case Token.IFNE:
                case Icode_IFEQ_POP:
                case Icode_IF_NULL_UNDEF:
                case Icode_IF_NOT_NULL_UNDEF:
                case Icode_LEAVEDQ:
                    {
                        int newPC = pc + getShort(iCode, pc) - 1;
                        out.println(tname + " " + newPC);
                        pc += 2;
                        break;
                    }
                case Icode_VAR_INC_DEC:
                case Icode_NAME_INC_DEC:
                case Icode_PROP_INC_DEC:
                case Icode_ELEM_INC_DEC:
                case Icode_REF_INC_DEC:
                    {
                        int incrDecrType = iCode[pc];
                        out.println(tname + " " + incrDecrType);
                        ++pc;
                        break;
                    }

                case Icode_CALLSPECIAL:
                case Icode_CALLSPECIAL_OPTIONAL:
                    {
                        int callType = iCode[pc] & 0xFF;
                        boolean isNew = (iCode[pc + 1] != 0);
                        int line = getIndex(iCode, pc + 2);
                        out.println(
                                tname + " " + callType + " " + isNew + " " + indexReg + " " + line);
                        pc += 4;
                        break;
                    }

                case Token.CATCH_SCOPE:
                    {
                        boolean afterFisrtFlag = (iCode[pc] != 0);
                        out.println(tname + " " + afterFisrtFlag);
                        ++pc;
                    }
                    break;
                case Token.REGEXP:
                    out.println(tname + " " + idata.itsRegExpLiterals[indexReg]);
                    break;
                case Icode_LITERAL_NEW_OBJECT:
                    {
                        boolean copyArray = iCode[pc++] != 0;
                        if (indexReg < 0) {
                            out.println(tname + " length: " + (-indexReg - 1));
                        } else {
                            Object[] keys = (Object[]) idata.literalIds[indexReg];
                            out.println(tname + " " + Arrays.toString(keys) + " " + copyArray);
                        }
                        break;
                    }
                case Icode_SPARE_ARRAYLIT:
                    out.println(tname + " " + idata.literalIds[indexReg]);
                    break;
                case Icode_CLOSURE_EXPR:
                case Icode_CLOSURE_STMT:
                    out.println(tname + " " + idata.itsNestedFunctions[indexReg]);
                    break;
                case Token.CALL:
                case Icode_CALL_ON_SUPER:
                case Icode_TAIL_CALL:
                case Token.REF_CALL:
                case Token.NEW:
                    out.println(tname + ' ' + indexReg);
                    break;
                case Token.THROW:
                case Token.YIELD:
                case Icode_YIELD_STAR:
                case Icode_GENERATOR:
                case Icode_GENERATOR_END:
                case Icode_GENERATOR_RETURN:
                    {
                        int line = getIndex(iCode, pc);
                        out.println(tname + " : " + line);
                        pc += 2;
                        break;
                    }
                case Icode_SHORTNUMBER:
                    {
                        int value = getShort(iCode, pc);
                        out.println(tname + " " + value);
                        pc += 2;
                        break;
                    }
                case Icode_INTNUMBER:
                    {
                        int value = getInt(iCode, pc);
                        out.println(tname + " " + value);
                        pc += 4;
                        break;
                    }
                case Token.NUMBER:
                    {
                        double value = idata.itsDoubleTable[indexReg];
                        out.println(tname + " " + value);
                        break;
                    }
                case Icode_LINE:
                    {
                        int line = getIndex(iCode, pc);
                        out.println(tname + " : " + line);
                        pc += 2;
                        break;
                    }
                case Icode_REG_STR1:
                    {
                        String str = strings[0xFF & iCode[pc]];
                        out.println(tname + " \"" + str + '"');
                        ++pc;
                        break;
                    }
                case Icode_REG_STR2:
                    {
                        String str = strings[getIndex(iCode, pc)];
                        out.println(tname + " \"" + str + '"');
                        pc += 2;
                        break;
                    }
                case Icode_REG_STR4:
                    {
                        String str = strings[getInt(iCode, pc)];
                        out.println(tname + " \"" + str + '"');
                        pc += 4;
                        break;
                    }
                case Icode_REG_STR_C0:
                    {
                        String str = strings[0];
                        out.println(tname + " \"" + str + '"');
                        break;
                    }
                case Icode_REG_STR_C1:
                    {
                        String str = strings[1];
                        out.println(tname + " \"" + str + '"');
                        break;
                    }
                case Icode_REG_STR_C2:
                    {
                        String str = strings[2];
                        out.println(tname + " \"" + str + '"');
                        break;
                    }
                case Icode_REG_STR_C3:
                    {
                        String str = strings[3];
                        out.println(tname + " \"" + str + '"');
                        break;
                    }
                case Icode_REG_IND_C0:
                    indexReg = 0;
                    out.println(tname);
                    break;
                case Icode_REG_IND_C1:
                    indexReg = 1;
                    out.println(tname);
                    break;
                case Icode_REG_IND_C2:
                    indexReg = 2;
                    out.println(tname);
                    break;
                case Icode_REG_IND_C3:
                    indexReg = 3;
                    out.println(tname);
                    break;
                case Icode_REG_IND_C4:
                    indexReg = 4;
                    out.println(tname);
                    break;
                case Icode_REG_IND_C5:
                    indexReg = 5;
                    out.println(tname);
                    break;
                case Icode_REG_IND1:
                    {
                        indexReg = 0xFF & iCode[pc];
                        out.println(tname + " " + indexReg);
                        ++pc;
                        break;
                    }
                case Icode_REG_IND2:
                    {
                        indexReg = getIndex(iCode, pc);
                        out.println(tname + " " + indexReg);
                        pc += 2;
                        break;
                    }
                case Icode_REG_IND4:
                    {
                        indexReg = getInt(iCode, pc);
                        out.println(tname + " " + indexReg);
                        pc += 4;
                        break;
                    }
                case Icode_GETVAR1:
                case Icode_SETVAR1:
                case Icode_SETCONSTVAR1:
                    indexReg = iCode[pc];
                    out.println(tname + " " + indexReg);
                    ++pc;
                    break;
                // TODO: Icode_REG_STR_C0-3 is not dump. I made this the same it.
                case Icode_REG_BIGINT_C0:
                case Icode_REG_BIGINT_C1:
                case Icode_REG_BIGINT_C2:
                case Icode_REG_BIGINT_C3:
                    Kit.codeBug();
                    break;
                case Icode_REG_BIGINT1:
                    {
                        BigInteger bigInt = bigInts[0xFF & iCode[pc]];
                        out.println(tname + " " + bigInt.toString() + 'n');
                        ++pc;
                        break;
                    }
                case Icode_REG_BIGINT2:
                    {
                        BigInteger bigInt = bigInts[getIndex(iCode, pc)];
                        out.println(tname + " " + bigInt.toString() + 'n');
                        pc += 2;
                        break;
                    }
                case Icode_REG_BIGINT4:
                    {
                        BigInteger bigInt = bigInts[getInt(iCode, pc)];
                        out.println(tname + " " + bigInt.toString() + 'n');
                        pc += 4;
                        break;
                    }
            }
            if (old_pc + icodeLength != pc) Kit.codeBug();
        }

        int[] table = idata.itsExceptionTable;
        if (table != null) {
            out.println("Exception handlers: " + table.length / EXCEPTION_SLOT_SIZE);
            for (int i = 0; i != table.length; i += EXCEPTION_SLOT_SIZE) {
                int tryStart = table[i + EXCEPTION_TRY_START_SLOT];
                int tryEnd = table[i + EXCEPTION_TRY_END_SLOT];
                int handlerStart = table[i + EXCEPTION_HANDLER_SLOT];
                int type = table[i + EXCEPTION_TYPE_SLOT];
                int exceptionLocal = table[i + EXCEPTION_LOCAL_SLOT];

                out.println(
                        " tryStart="
                                + tryStart
                                + " tryEnd="
                                + tryEnd
                                + " handlerStart="
                                + handlerStart
                                + " type="
                                + (type == 0 ? "catch" : "finally")
                                + " exceptionLocal="
                                + exceptionLocal);
            }
        }
        out.flush();
    }

    private static int bytecodeSpan(int bytecode) {
        switch (bytecode) {
            case Token.THROW:
            case Token.YIELD:
            case Icode_YIELD_STAR:
            case Icode_GENERATOR:
            case Icode_GENERATOR_END:
            case Icode_GENERATOR_RETURN:
                // source line
                return 1 + 2;

            case Icode_GOSUB:
            case Token.GOTO:
            case Token.IFEQ:
            case Token.IFNE:
            case Icode_IFEQ_POP:
            case Icode_IF_NULL_UNDEF:
            case Icode_IF_NOT_NULL_UNDEF:
            case Icode_LEAVEDQ:
                // target pc offset
                return 1 + 2;

            case Icode_CALLSPECIAL:
            case Icode_CALLSPECIAL_OPTIONAL:
                // call type
                // is new
                // line number
                return 1 + 1 + 1 + 2;

            case Token.CATCH_SCOPE:
                // scope flag
                return 1 + 1;

            case Icode_VAR_INC_DEC:
            case Icode_NAME_INC_DEC:
            case Icode_PROP_INC_DEC:
            case Icode_ELEM_INC_DEC:
            case Icode_REF_INC_DEC:
                // type of ++/--
                return 1 + 1;

            case Icode_SHORTNUMBER:
                // short number
                return 1 + 2;

            case Icode_INTNUMBER:
                // int number
                return 1 + 4;

            case Icode_REG_IND1:
                // ubyte index
                return 1 + 1;

            case Icode_REG_IND2:
                // ushort index
                return 1 + 2;

            case Icode_REG_IND4:
                // int index
                return 1 + 4;

            case Icode_REG_STR1:
                // ubyte string index
                return 1 + 1;

            case Icode_REG_STR2:
                // ushort string index
                return 1 + 2;

            case Icode_REG_STR4:
                // int string index
                return 1 + 4;

            case Icode_GETVAR1:
            case Icode_SETVAR1:
            case Icode_SETCONSTVAR1:
                // byte var index
                return 1 + 1;

            case Icode_LINE:
                // line number
                return 1 + 2;

            case Icode_LITERAL_NEW_OBJECT:
                // make a copy or not flag
                return 1 + 1;
        }
        if (!validBytecode(bytecode)) throw Kit.codeBug();
        return 1;
    }

    static int[] getLineNumbers(InterpreterData data) {
        HashSet<Integer> presentLines = new HashSet<>();

        byte[] iCode = data.itsICode;
        int iCodeLength = iCode.length;
        for (int pc = 0; pc != iCodeLength; ) {
            int bytecode = iCode[pc];
            int span = bytecodeSpan(bytecode);
            if (bytecode == Icode_LINE) {
                if (span != 3) Kit.codeBug();
                int line = getIndex(iCode, pc + 1);
                presentLines.add(line);
            }
            pc += span;
        }

        int[] ret = new int[presentLines.size()];
        int i = 0;
        for (int num : presentLines) {
            ret[i++] = num;
        }
        return ret;
    }

    @Override
    public void captureStackInfo(RhinoException ex) {
        Context cx = Context.getCurrentContext();
        if (cx == null || cx.lastInterpreterFrame == null) {
            // No interpreter invocations
            ex.interpreterStackInfo = null;
        } else {
            ex.interpreterStackInfo = cx.lastInterpreterFrame;
        }
    }

    @Override
    public String getSourcePositionFromStack(Context cx, int[] linep) {
        CallFrame frame = (CallFrame) cx.lastInterpreterFrame;
        InterpreterData idata = frame.idata;
        if (frame.pcSourceLineStart >= 0) {
            linep[0] = getIndex(idata.itsICode, frame.pcSourceLineStart);
        } else {
            linep[0] = 0;
        }
        return idata.itsSourceFile;
    }

    @Override
    public String getPatchedStack(RhinoException ex, String nativeStackTrace) {
        String tag = "org.mozilla.javascript.Interpreter.interpretLoop";
        StringBuilder sb = new StringBuilder(nativeStackTrace.length() + 1000);
        String lineSeparator = SecurityUtilities.getSystemProperty("line.separator");

        CallFrame calleeFrame = null;
        CallFrame frame = (CallFrame) ex.interpreterStackInfo;
        int offset = 0;
        while (frame != null) {
            CallFrame callerFrame = frame;
            int pos = nativeStackTrace.indexOf(tag, offset);
            if (pos < 0) {
                break;
            }

            // Skip tag length
            pos += tag.length();
            // Skip until the end of line
            for (; pos != nativeStackTrace.length(); ++pos) {
                char c = nativeStackTrace.charAt(pos);
                if (c == '\n' || c == '\r') {
                    break;
                }
            }
            sb.append(nativeStackTrace, offset, pos);
            offset = pos;

            while (callerFrame != null) {
                InterpreterData idata = callerFrame.idata;
                sb.append(lineSeparator);
                sb.append("\tat script");
                if (idata.itsName != null && idata.itsName.length() != 0) {
                    sb.append('.');
                    sb.append(idata.itsName);
                }
                sb.append('(');
                sb.append(idata.itsSourceFile);
                int pc = calleeFrame == null ? callerFrame.pcSourceLineStart : calleeFrame.parentPC;
                if (pc >= 0) {
                    // Include line info only if available
                    sb.append(':');
                    sb.append(getIndex(idata.itsICode, pc));
                }
                sb.append(')');
                calleeFrame = callerFrame;
                callerFrame = callerFrame.parentFrame;
            }
            frame = calleeFrame.previousInterpreterFrame;
        }
        sb.append(nativeStackTrace.substring(offset));

        return sb.toString();
    }

    @Override
    public List<String> getScriptStack(RhinoException ex) {
        ScriptStackElement[][] stack = getScriptStackElements(ex);
        List<String> list = new ArrayList<>(stack.length);
        String lineSeparator = SecurityUtilities.getSystemProperty("line.separator");
        for (ScriptStackElement[] group : stack) {
            StringBuilder sb = new StringBuilder();
            for (ScriptStackElement elem : group) {
                elem.renderJavaStyle(sb);
                sb.append(lineSeparator);
            }
            list.add(sb.toString());
        }
        return list;
    }

    public ScriptStackElement[][] getScriptStackElements(RhinoException ex) {
        if (ex.interpreterStackInfo == null) {
            return null;
        }

        List<ScriptStackElement[]> list = new ArrayList<>();

        CallFrame calleeFrame = null;
        CallFrame frame = (CallFrame) ex.interpreterStackInfo;
        while (frame != null) {
            CallFrame callerFrame = frame;
            List<ScriptStackElement> group = new ArrayList<>();
            while (callerFrame != null) {
                InterpreterData idata = callerFrame.fnOrScript.idata;
                String fileName = idata.itsSourceFile;
                String functionName = null;
                int lineNumber = -1;
                int pc = calleeFrame == null ? callerFrame.pcSourceLineStart : calleeFrame.parentPC;
                if (pc >= 0) {
                    lineNumber = getIndex(idata.itsICode, pc);
                }
                if (idata.itsName != null && idata.itsName.length() != 0) {
                    functionName = idata.itsName;
                }
                calleeFrame = callerFrame;
                callerFrame = callerFrame.parentFrame;
                group.add(new ScriptStackElement(fileName, functionName, lineNumber));
            }
            list.add(group.toArray(new ScriptStackElement[0]));
            frame = calleeFrame.previousInterpreterFrame;
        }
        return list.toArray(new ScriptStackElement[list.size()][]);
    }

    static String getRawSource(InterpreterData idata) {
        if (idata.rawSource == null) {
            return null;
        }
        return idata.rawSource.substring(idata.rawSourceStart, idata.rawSourceEnd);
    }

    private static void initFunction(
            Context cx, Scriptable scope, InterpretedFunction parent, int index) {
        InterpretedFunction fn;
        fn = InterpretedFunction.createFunction(cx, scope, parent, index);
        ScriptRuntime.initFunction(
                cx, scope, fn, fn.idata.itsFunctionType, parent.idata.evalScriptFlag);
    }

    static Object interpret(
            InterpretedFunction ifun,
            Context cx,
            Scriptable scope,
            Scriptable thisObj,
            Object[] args) {
        if (!ScriptRuntime.hasTopCall(cx)) Kit.codeBug();

        if (cx.interpreterSecurityDomain != ifun.securityDomain) {
            Object savedDomain = cx.interpreterSecurityDomain;
            cx.interpreterSecurityDomain = ifun.securityDomain;
            try {
                return ifun.securityController.callWithDomain(
                        ifun.securityDomain, cx, ifun, scope, thisObj, args);
            } finally {
                cx.interpreterSecurityDomain = savedDomain;
            }
        }

        CallFrame frame =
                initFrame(
                        cx,
                        scope,
                        thisObj,
                        ifun.getHomeObject(),
                        args,
                        null,
                        null,
                        0,
                        args.length,
                        ifun,
                        null);
        frame.isContinuationsTopFrame = cx.isContinuationsTopCall;
        cx.isContinuationsTopCall = false;

        return interpretLoop(cx, frame, null);
    }

    static class GeneratorState {
        GeneratorState(int operation, Object value) {
            this.operation = operation;
            this.value = value;
        }

        int operation;
        Object value;
        RuntimeException returnedException;
    }

    public static Object resumeGenerator(
            Context cx, Scriptable scope, int operation, Object savedState, Object value) {
        CallFrame frame = (CallFrame) savedState;
        CallFrame activeFrame = frame.shallowCloneFrozen((CallFrame) cx.lastInterpreterFrame);
        try {
            GeneratorState generatorState = new GeneratorState(operation, value);
            if (operation == NativeGenerator.GENERATOR_CLOSE) {
                try {
                    return interpretLoop(cx, activeFrame, generatorState);
                } catch (RuntimeException e) {
                    // Only propagate exceptions other than closingException
                    if (e != value) throw e;
                }
                return Undefined.instance;
            }
            Object result = interpretLoop(cx, activeFrame, generatorState);
            if (generatorState.returnedException != null) throw generatorState.returnedException;
            return result;
        } finally {
            activeFrame.syncStateToFrame(frame);
        }
    }

    public static Object restartContinuation(
            NativeContinuation c, Context cx, Scriptable scope, Object[] args) {
        if (!ScriptRuntime.hasTopCall(cx)) {
            return ScriptRuntime.doTopCall(c, cx, scope, null, args, cx.isTopLevelStrict);
        }

        Object arg;
        if (args.length == 0) {
            arg = Undefined.instance;
        } else {
            arg = args[0];
        }

        CallFrame capturedFrame = (CallFrame) c.getImplementation();
        if (capturedFrame == null) {
            // No frames to restart
            return arg;
        }

        ContinuationJump cjump = new ContinuationJump(c, null);

        cjump.result = arg;
        return interpretLoop(cx, null, cjump);
    }

    // arbitrary number to add to instructionCount when calling
    // other functions
    private static final int INVOCATION_COST = 100;
    // arbitrary exception cost for instruction counting
    private static final int EXCEPTION_COST = 100;

    private static final Object undefined = Undefined.instance;

    private static class NewState {}

    private abstract static class InterpreterResult extends NewState {}

    private static class YieldResult extends InterpreterResult {
        private final Object yielding;

        private YieldResult(Object yielding) {
            this.yielding = yielding;
        }
    }

    private static class StateBreakResult extends InterpreterResult {
        private final CallFrame frame;

        private StateBreakResult(CallFrame frame) {
            this.frame = frame;
        }
    }

    private static class StateContinueResult extends InterpreterResult {
        private final CallFrame frame;
        private final int indexReg;

        private StateContinueResult(CallFrame frame, int indexReg) {
            this.frame = frame;
            this.indexReg = indexReg;
        }
    }

    private static class ThrowableResult extends InterpreterResult {
        private final CallFrame frame;
        private final Object throwable;

        private ThrowableResult(CallFrame frame, Object throwable) {
            this.frame = frame;
            this.throwable = throwable;
        }
    }

    private static final NewState BREAK_LOOP = new NewState() {};

    private static final NewState BREAK_JUMPLESSRUN = new NewState() {};

    private static final NewState BREAK_WITHOUT_EXTENSION = new NewState() {};

    private static class InterpreterState {
        int stackTop;
        int indexReg;
        BigInteger bigIntReg;
        String stringReg;
        final boolean instructionCounting;
        GeneratorState generatorState;
        Object throwable;

        InterpreterState(int stackTop, int indexReg, boolean instructionCounting) {
            this.stackTop = stackTop;
            this.indexReg = indexReg;
            this.instructionCounting = instructionCounting;
        }
    }

    private abstract static class InstructionClass {
        abstract NewState execute(Context cx, CallFrame frame, InterpreterState state, int op);
    }

    private static final InstructionClass[] instructionObjs;

    static {
        instructionObjs = new InstructionClass[Token.LAST_BYTECODE_TOKEN + 1 - MIN_ICODE];
        int base = -MIN_ICODE;
        instructionObjs[base + Icode_GENERATOR] = new DoGenerator();
        instructionObjs[base + Token.YIELD] = new DoYield();
        instructionObjs[base + Icode_YIELD_STAR] = new DoYield();
        instructionObjs[base + Icode_GENERATOR_END] = new DoGeneratorEnd();
        instructionObjs[base + Icode_GENERATOR_RETURN] = new DoGeneratorReturn();
        instructionObjs[base + Token.THROW] = new DoThrow();
        instructionObjs[base + Token.RETHROW] = new DoRethrow();
        instructionObjs[base + Token.GE] = new DoCompare();
        instructionObjs[base + Token.LE] = new DoCompare();
        instructionObjs[base + Token.GT] = new DoCompare();
        instructionObjs[base + Token.LT] = new DoCompare();
        instructionObjs[base + Token.IN] = new DoInOrInstanceof();
        instructionObjs[base + Token.INSTANCEOF] = new DoInOrInstanceof();
        instructionObjs[base + Token.EQ] = new DoEquals();
        instructionObjs[base + Token.NE] = new DoNotEquals();
        instructionObjs[base + Token.SHEQ] = new DoShallowEquals();
        instructionObjs[base + Token.SHNE] = new DoShallowNotEquals();
        instructionObjs[base + Token.IFNE] = new DoIfNE();
        instructionObjs[base + Token.IFEQ] = new DoIfEQ();
        instructionObjs[base + Icode_IFEQ_POP] = new DoIfEQPop();
        instructionObjs[base + Icode_IF_NULL_UNDEF] = new DoIfNullUndef();
        instructionObjs[base + Icode_IF_NOT_NULL_UNDEF] = new DoIfNotNullUndef();
        instructionObjs[base + Token.GOTO] = new DoGoto();
        instructionObjs[base + Icode_GOSUB] = new DoGosub();
        instructionObjs[base + Icode_STARTSUB] = new DoStartSub();
        instructionObjs[base + Icode_RETSUB] = new DoRetsub();
        instructionObjs[base + Icode_POP] = new DoPop();
        instructionObjs[base + Icode_POP_RESULT] = new DoPopResult();
        instructionObjs[base + Icode_DUP] = new DoDup();
        instructionObjs[base + Icode_DUP2] = new DoDup2();
        instructionObjs[base + Icode_SWAP] = new DoSwap();
        instructionObjs[base + Token.RETURN] = new DoReturn();
        instructionObjs[base + Token.RETURN_RESULT] = new DoReturnResult();
        instructionObjs[base + Icode_RETUNDEF] = new DoReturnUndef();
        instructionObjs[base + Token.BITNOT] = new DoBitNot();
        instructionObjs[base + Token.BITAND] = new DoBitOp();
        instructionObjs[base + Token.BITOR] = new DoBitOp();
        instructionObjs[base + Token.BITXOR] = new DoBitOp();
        instructionObjs[base + Token.LSH] = new DoBitOp();
        instructionObjs[base + Token.RSH] = new DoBitOp();
        instructionObjs[base + Token.URSH] = new DoUnsignedRightShift();
        instructionObjs[base + Token.POS] = new DoPositive();
        instructionObjs[base + Token.NEG] = new DoNegative();
        instructionObjs[base + Token.ADD] = new DoAdd();
        instructionObjs[base + Token.SUB] = new DoArithmetic();
        instructionObjs[base + Token.MUL] = new DoArithmetic();
        instructionObjs[base + Token.DIV] = new DoArithmetic();
        instructionObjs[base + Token.MOD] = new DoArithmetic();
        instructionObjs[base + Token.EXP] = new DoArithmetic();
        instructionObjs[base + Token.NOT] = new DoNot();
        instructionObjs[base + Token.BINDNAME] = new DoBindName();
        instructionObjs[base + Token.STRICT_SETNAME] = new DoSetName();
        instructionObjs[base + Token.SETNAME] = new DoSetName();
        instructionObjs[base + Icode_SETCONST] = new DoSetConst();
        instructionObjs[base + Token.DELPROP] = new DoDelName();
        instructionObjs[base + Icode_DELNAME] = new DoDelName();
        instructionObjs[base + Icode_DELPROP_SUPER] = new DoDelPropSuper();
        instructionObjs[base + Token.GETPROPNOWARN] = new DoGetPropNoWarn();
        instructionObjs[base + Token.GETPROP] = new DoGetProp();
        instructionObjs[base + Token.GETPROP_SUPER] = new DoGetPropSuper();
        instructionObjs[base + Token.GETPROPNOWARN_SUPER] = new DoGetPropSuper();
        instructionObjs[base + Token.SETPROP] = new DoSetProp();
        instructionObjs[base + Token.SETPROP_SUPER] = new DoSetPropSuper();
        instructionObjs[base + Icode_PROP_INC_DEC] = new DoPropIncDec();
        instructionObjs[base + Token.GETELEM] = new DoGetElem();
        instructionObjs[base + Token.GETELEM_SUPER] = new DoGetElemSuper();
        instructionObjs[base + Token.SETELEM] = new DoSetElem();
        instructionObjs[base + Token.SETELEM_SUPER] = new DoSetElemSuper();
        instructionObjs[base + Icode_ELEM_INC_DEC] = new DoElemIncDec();
        instructionObjs[base + Token.GET_REF] = new DoGetRef();
        instructionObjs[base + Token.SET_REF] = new DoSetRef();
        instructionObjs[base + Token.DEL_REF] = new DoDelRef();
        instructionObjs[base + Icode_REF_INC_DEC] = new DoRefIncDec();
        instructionObjs[base + Token.LOCAL_LOAD] = new DoLocalLoad();
        instructionObjs[base + Icode_LOCAL_CLEAR] = new DoLocalClear();
        instructionObjs[base + Icode_NAME_AND_THIS] = new DoNameAndThis();
        instructionObjs[base + Icode_NAME_AND_THIS_OPTIONAL] = new DoNameAndThisOptional();
        instructionObjs[base + Icode_PROP_AND_THIS] = new DoPropAndThis();
        instructionObjs[base + Icode_PROP_AND_THIS_OPTIONAL] = new DoPropAndThisOptional();
        instructionObjs[base + Icode_ELEM_AND_THIS] = new DoElemAndThis();
        instructionObjs[base + Icode_ELEM_AND_THIS_OPTIONAL] = new DoElemAndThisOptional();
        instructionObjs[base + Icode_VALUE_AND_THIS] = new DoValueAndThis();
        instructionObjs[base + Icode_VALUE_AND_THIS_OPTIONAL] = new DoValueAndThisOptional();
        instructionObjs[base + Icode_CALLSPECIAL] = new DoCallSpecial();
        instructionObjs[base + Icode_CALLSPECIAL_OPTIONAL] = new DoCallSpecial();
        instructionObjs[base + Token.CALL] = new DoCallByteCode();
        instructionObjs[base + Icode_CALL_ON_SUPER] = new DoCallByteCode();
        instructionObjs[base + Icode_TAIL_CALL] = new DoCallByteCode();
        instructionObjs[base + Token.REF_CALL] = new DoCallByteCode();
        instructionObjs[base + Token.NEW] = new DoNew();
        instructionObjs[base + Token.TYPEOF] = new DoTypeOf();
        instructionObjs[base + Icode_TYPEOFNAME] = new DoTypeOfName();
        instructionObjs[base + Token.STRING] = new DoString();
        instructionObjs[base + Icode_SHORTNUMBER] = new DoShortNumber();
        instructionObjs[base + Icode_INTNUMBER] = new DoIntNumber();
        instructionObjs[base + Token.NUMBER] = new DoNumber();
        instructionObjs[base + Token.BIGINT] = new DoBigInt();
        instructionObjs[base + Token.NAME] = new DoName();
        instructionObjs[base + Icode_NAME_INC_DEC] = new DoNameIncDec();
        instructionObjs[base + Icode_SETCONSTVAR1] = new DoSetConstVar1();
        instructionObjs[base + Icode_SETCONSTVAR] = new DoSetConstVar();
        instructionObjs[base + Icode_SETVAR1] = new DoSetVar1();
        instructionObjs[base + Token.SETVAR] = new DoSetVar();
        instructionObjs[base + Icode_GETVAR1] = new DoGetVar1();
        instructionObjs[base + Token.GETVAR] = new DoGetVar();
        instructionObjs[base + Icode_VAR_INC_DEC] = new DoVarIncDec();
        instructionObjs[base + Icode_ZERO] = new DoZero();
        instructionObjs[base + Icode_ONE] = new DoOne();
        instructionObjs[base + Token.NULL] = new DoNull();
        instructionObjs[base + Token.THIS] = new DoThis();
        instructionObjs[base + Token.SUPER] = new DoSuper();
        instructionObjs[base + Token.THISFN] = new DoThisFunction();
        instructionObjs[base + Token.FALSE] = new DoFalse();
        instructionObjs[base + Token.TRUE] = new DoTrue();
        instructionObjs[base + Icode_UNDEF] = new DoUndef();
        instructionObjs[base + Token.ENTERWITH] = new DoEnterWith();
        instructionObjs[base + Token.LEAVEWITH] = new DoLeaveWith();
        instructionObjs[base + Token.CATCH_SCOPE] = new DoCatchScope();
        instructionObjs[base + Token.ENUM_INIT_KEYS] = new DoEnumInit();
        instructionObjs[base + Token.ENUM_INIT_VALUES] = new DoEnumInit();
        instructionObjs[base + Token.ENUM_INIT_ARRAY] = new DoEnumInit();
        instructionObjs[base + Token.ENUM_INIT_VALUES_IN_ORDER] = new DoEnumInit();
        instructionObjs[base + Token.ENUM_INIT_VALUES_IN_ORDER] = new DoEnumInit();
        instructionObjs[base + Token.ENUM_NEXT] = new DoEnumOp();
        instructionObjs[base + Token.ENUM_ID] = new DoEnumOp();
        instructionObjs[base + Token.REF_SPECIAL] = new DoRefSpecial();
        instructionObjs[base + Token.REF_MEMBER] = new DoRefMember();
        instructionObjs[base + Token.REF_NS_MEMBER] = new DoRefNsMember();
        instructionObjs[base + Token.REF_NAME] = new DoRefName();
        instructionObjs[base + Token.REF_NS_NAME] = new DoRefNsName();
        instructionObjs[base + Icode_SCOPE_LOAD] = new DoScopeLoad();
        instructionObjs[base + Icode_SCOPE_SAVE] = new DoScopeSave();
        instructionObjs[base + Icode_SPREAD] = new DoSpread();
        instructionObjs[base + Icode_CLOSURE_EXPR] = new DoClosureExpr();
        instructionObjs[base + ICode_FN_STORE_HOME_OBJECT] = new DoStoreHomeObject();
        instructionObjs[base + Icode_CLOSURE_STMT] = new DoClosureStatement();
        instructionObjs[base + Token.REGEXP] = new DoRegExp();
        instructionObjs[base + Icode_TEMPLATE_LITERAL_CALLSITE] = new DoTemplateLiteralCallSite();
        instructionObjs[base + Icode_LITERAL_NEW_OBJECT] = new DoLiteralNewObject();
        instructionObjs[base + Icode_LITERAL_NEW_ARRAY] = new DoLiteralNewArray();
        instructionObjs[base + Icode_LITERAL_SET] = new DoLiteralSet();
        instructionObjs[base + Icode_LITERAL_GETTER] = new DoLiteralGetter();
        instructionObjs[base + Icode_LITERAL_SETTER] = new DoLiteralSetter();
        instructionObjs[base + Icode_LITERAL_KEY_SET] = new DoLiteralKeySet();
        instructionObjs[base + Token.OBJECTLIT] = new DoObjectLit();
        instructionObjs[base + Token.ARRAYLIT] = new DoArrayLiteral();
        instructionObjs[base + Icode_SPARE_ARRAYLIT] = new DoArrayLiteral();
        instructionObjs[base + Icode_ENTERDQ] = new DoEnterDotQuery();
        instructionObjs[base + Icode_LEAVEDQ] = new DoLeaveDotQuery();
        instructionObjs[base + Token.DEFAULTNAMESPACE] = new DoDefaultNamespace();
        instructionObjs[base + Token.ESCXMLATTR] = new DoEscXMLAttr();
        instructionObjs[base + Token.ESCXMLTEXT] = new DoEscXMLText();
        instructionObjs[base + Icode_DEBUGGER] = new DoDebug();
        instructionObjs[base + Icode_LINE] = new DoLineChange();
        instructionObjs[base + Icode_REG_IND_C0] = new DoIndexCn();
        instructionObjs[base + Icode_REG_IND_C1] = new DoIndexCn();
        instructionObjs[base + Icode_REG_IND_C2] = new DoIndexCn();
        instructionObjs[base + Icode_REG_IND_C3] = new DoIndexCn();
        instructionObjs[base + Icode_REG_IND_C4] = new DoIndexCn();
        instructionObjs[base + Icode_REG_IND_C5] = new DoIndexCn();
        instructionObjs[base + Icode_REG_IND1] = new DoRegIndex1();
        instructionObjs[base + Icode_REG_IND2] = new DoRegIndex2();
        instructionObjs[base + Icode_REG_IND4] = new DoRegIndex4();
        instructionObjs[base + Icode_REG_STR_C0] = new DoStringCn();
        instructionObjs[base + Icode_REG_STR_C1] = new DoStringCn();
        instructionObjs[base + Icode_REG_STR_C2] = new DoStringCn();
        instructionObjs[base + Icode_REG_STR_C3] = new DoStringCn();
        instructionObjs[base + Icode_REG_STR1] = new DoRegString1();
        instructionObjs[base + Icode_REG_STR2] = new DoRegString2();
        instructionObjs[base + Icode_REG_STR4] = new DoRegString4();
        instructionObjs[base + Icode_REG_BIGINT_C0] = new DoBigIntCn();
        instructionObjs[base + Icode_REG_BIGINT_C1] = new DoBigIntCn();
        instructionObjs[base + Icode_REG_BIGINT_C2] = new DoBigIntCn();
        instructionObjs[base + Icode_REG_BIGINT_C3] = new DoBigIntCn();
        instructionObjs[base + Icode_REG_BIGINT1] = new DoRegBigInt1();
        instructionObjs[base + Icode_REG_BIGINT2] = new DoRegBigInt2();
        instructionObjs[base + Icode_REG_BIGINT4] = new DoRegBigInt4();
    }

    private static Object interpretLoop(Context cx, CallFrame frame, Object throwable) {
        final Object oldFrame = cx.lastInterpreterFrame;
        try {
            // throwable holds exception object to rethrow or catch
            // It is also used for continuation restart in which case
            // it holds ContinuationJump

            final boolean instructionCounting = cx.instructionThreshold != 0;

            String stringReg = null;
            BigInteger bigIntReg = null;
            int indexReg = -1;

            // When restarting continuation throwable is not null and to jump
            // to the code that rewind continuation state indexReg should be set
            // to -1.
            // With the normal call throwable == null and indexReg == -1 allows to
            // catch bugs with using indeReg to access array elements before
            // initializing indexReg.

            GeneratorState generatorState = null;
            if (throwable != null) {
                if (throwable instanceof GeneratorState) {
                    generatorState = (GeneratorState) throwable;

                    // reestablish this call frame
                    enterFrame(cx, frame, ScriptRuntime.emptyArgs, true);
                    throwable = null;
                } else if (!(throwable instanceof ContinuationJump)) {
                    // It should be continuation
                    Kit.codeBug();
                }
            }

            Object interpreterResult = null;
            double interpreterResultDbl = 0.0;

            StateLoop:
            for (; ; ) {

                Withoutexceptions:
                try {

                    if (throwable != null) {
                        // Need to return both 'frame' and 'throwable' from
                        // 'processThrowable', so just added a 'throwable'
                        // member in 'frame'.
                        frame =
                                processThrowable(
                                        cx, throwable, frame, indexReg, instructionCounting);
                        throwable = frame.throwable;
                        frame.throwable = null;
                    } else {
                        if (generatorState == null && frame.frozen) Kit.codeBug();
                    }

                    InterpreterResult result =
                            interpretFunction(
                                    cx,
                                    frame,
                                    throwable,
                                    generatorState,
                                    indexReg,
                                    instructionCounting);
                    if (result instanceof StateContinueResult) {
                        StateContinueResult scr = (StateContinueResult) result;
                        frame = scr.frame;
                        indexReg = scr.indexReg;
                        continue StateLoop;
                    } else if (result instanceof StateBreakResult) {
                        StateBreakResult sbr = (StateBreakResult) result;
                        frame = sbr.frame;
                        interpreterResult = frame.result;
                        interpreterResultDbl = frame.resultDbl;
                        break StateLoop;
                    } else if (result instanceof YieldResult) {
                        return ((YieldResult) result).yielding;
                    } else if (result instanceof ThrowableResult) {
                        ThrowableResult tr = (ThrowableResult) result;
                        frame = tr.frame;
                        throwable = tr.throwable;
                        break Withoutexceptions;
                    } else {
                        Kit.codeBug();
                    }
                } // end of interpreter withoutExceptions: try
                catch (Throwable ex) {
                    if (throwable != null) {
                        // This is serious bug and it is better to track it ASAP
                        ex.printStackTrace(System.err);
                        throw new IllegalStateException();
                    }
                    throwable = ex;
                }

                // This should be reachable only after above catch or from
                // finally when it needs to propagate exception or from
                // explicit throw
                if (throwable == null) Kit.codeBug();

                // Exception type
                final int EX_CATCH_STATE = 2; // Can execute JS catch
                final int EX_FINALLY_STATE = 1; // Can execute JS finally
                final int EX_NO_JS_STATE = 0; // Terminate JS execution

                int exState;
                ContinuationJump cjump = null;

                if (generatorState != null
                        && generatorState.operation == NativeGenerator.GENERATOR_CLOSE
                        && throwable == generatorState.value) {
                    exState = EX_FINALLY_STATE;
                } else if (throwable instanceof JavaScriptException) {
                    exState = EX_CATCH_STATE;
                } else if (throwable instanceof EcmaError) {
                    // an offical ECMA error object,
                    exState = EX_CATCH_STATE;
                } else if (throwable instanceof EvaluatorException) {
                    exState = EX_CATCH_STATE;
                } else if (throwable instanceof ContinuationPending) {
                    exState = EX_NO_JS_STATE;
                } else if (throwable instanceof RuntimeException) {
                    exState =
                            cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)
                                    ? EX_CATCH_STATE
                                    : EX_FINALLY_STATE;
                } else if (throwable instanceof Error) {
                    exState =
                            cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)
                                    ? EX_CATCH_STATE
                                    : EX_NO_JS_STATE;
                } else if (throwable instanceof ContinuationJump) {
                    // It must be ContinuationJump
                    exState = EX_FINALLY_STATE;
                    cjump = (ContinuationJump) throwable;
                } else {
                    exState =
                            cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS)
                                    ? EX_CATCH_STATE
                                    : EX_FINALLY_STATE;
                }

                if (instructionCounting) {
                    try {
                        addInstructionCount(cx, frame, EXCEPTION_COST);
                    } catch (RuntimeException ex) {
                        throwable = ex;
                        exState = EX_FINALLY_STATE;
                    } catch (Error ex) {
                        // Error from instruction counting
                        //     => unconditionally terminate JS
                        throwable = ex;
                        cjump = null;
                        exState = EX_NO_JS_STATE;
                    }
                }
                if (frame.debuggerFrame != null && throwable instanceof RuntimeException) {
                    // Call debugger only for RuntimeException
                    RuntimeException rex = (RuntimeException) throwable;
                    try {
                        frame.debuggerFrame.onExceptionThrown(cx, rex);
                    } catch (Throwable ex) {
                        // Any exception from debugger
                        //     => unconditionally terminate JS
                        throwable = ex;
                        cjump = null;
                        exState = EX_NO_JS_STATE;
                    }
                }

                for (; ; ) {
                    if (exState != EX_NO_JS_STATE) {
                        boolean onlyFinally = (exState != EX_CATCH_STATE);
                        indexReg = getExceptionHandler(frame, onlyFinally);
                        if (indexReg >= 0) {
                            // We caught an exception, restart the loop
                            // with exception pending the processing at the loop
                            // start
                            continue StateLoop;
                        }
                    }
                    // No allowed exception handlers in this frame, unwind
                    // to parent and try to look there

                    exitFrame(cx, frame, throwable);

                    frame = frame.parentFrame;
                    if (frame == null) {
                        break;
                    }
                    if (cjump != null && Objects.equals(cjump.branchFrame, frame)) {
                        // Continuation branch point was hit,
                        // restart the state loop to reenter continuation
                        indexReg = -1;
                        continue StateLoop;
                    }
                }

                // No more frames, rethrow the exception or deal with continuation
                if (cjump != null) {
                    if (cjump.branchFrame != null) {
                        // The above loop should locate the top frame
                        Kit.codeBug();
                    }
                    if (cjump.capturedFrame != null) {
                        // Restarting detached continuation
                        indexReg = -1;
                        continue StateLoop;
                    }
                    // Return continuation result to the caller
                    interpreterResult = cjump.result;
                    interpreterResultDbl = cjump.resultDbl;
                    throwable = null;
                }
                break StateLoop;
            } // end of StateLoop: for(;;)

            // Do cleanups/restorations before the final return or throw

            if (frame != null) {
                cx.lastInterpreterFrame =
                        frame.parentFrame == null
                                ? frame.previousInterpreterFrame
                                : frame.parentFrame;
            } else {
                cx.lastInterpreterFrame = null;
            }

            if (throwable != null) {
                if (throwable instanceof RuntimeException) {
                    throw (RuntimeException) throwable;
                }
                // Must be instance of Error or code bug
                throw (Error) throwable;
            }

            return (interpreterResult != DBL_MRK)
                    ? interpreterResult
                    : ScriptRuntime.wrapNumber(interpreterResultDbl);
        } finally {
            cx.lastInterpreterFrame = oldFrame;
        }
    }

    private static final Object DBL_MRK = DOUBLE_MARK;

    private static InterpreterResult interpretFunction(
            Context cx,
            CallFrame frame,
            Object tble,
            GeneratorState genState,
            int iReg,
            boolean instructionCounting) {
        final InterpreterState state =
                new InterpreterState(frame.savedStackTop, iReg, instructionCounting);

        withoutExceptions:
        try {

            // Use local variables for constant values in frame
            // for faster access
            final byte[] iCode = frame.idata.itsICode;

            state.generatorState = genState;
            state.throwable = tble;

            // Store new frame in cx which is used for error reporting etc.
            cx.lastInterpreterFrame = frame;

            Loop:
            for (; ; ) {

                NewState nextState;
                do {
                    // Exception handler assumes that PC is already incremented
                    // pass the instruction start when it searches the
                    // exception handler
                    int op = iCode[frame.pc++];

                    var insn = instructionObjs[-MIN_ICODE + op];

                    nextState = insn.execute(cx, frame, state, op);
                } while (nextState == null);

                if (nextState == BREAK_LOOP) {
                    break Loop;
                } else if (nextState == BREAK_JUMPLESSRUN) {
                    if (instructionCounting) {
                        addInstructionCount(cx, frame, 2);
                    }
                    int offset = getShort(iCode, frame.pc);
                    if (offset != 0) {
                        // -1 accounts for pc pointing to jump opcode + 1
                        frame.pc += offset - 1;
                    } else {
                        frame.pc = frame.idata.longJumps.get(frame.pc);
                    }
                    if (instructionCounting) {
                        frame.pcPrevBranch = frame.pc;
                    }
                } else if (nextState == BREAK_WITHOUT_EXTENSION) {
                    break withoutExceptions;
                } else {
                    return (InterpreterResult) nextState;
                }
            }

            exitFrame(cx, frame, null);
            if (frame.parentFrame != null) {
                CallFrame newFrame = frame.parentFrame;
                if (newFrame.frozen) {
                    newFrame = newFrame.cloneFrozen();
                }
                setCallResult(newFrame, frame.result, frame.resultDbl);
                return new StateContinueResult(newFrame, state.indexReg);
            }
            return new StateBreakResult(frame);

        } // end of interpreter withoutExceptions: try
        catch (Throwable ex) {
            if (state.throwable != null) {
                // This is serious bug and it is better to track it ASAP
                ex.printStackTrace(System.err);
                throw new IllegalStateException();
            }
            state.throwable = ex;
        }
        return new ThrowableResult(frame, state.throwable);
    }

    private static class DoGenerator extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            if (!frame.frozen) {
                // First time encountering this opcode: create new generator
                // object and return
                generatorCreate(cx, frame);
                return BREAK_LOOP;
            }
            /* This is both where we yield from and re-enter the
             * generator.
             */
            if (!frame.frozen) {
                return new YieldResult(
                        freezeGenerator(
                                cx, frame, state, state.generatorState, op == Icode_YIELD_STAR));
            }
            Object obj = thawGenerator(frame, state, state.generatorState, op);
            if (obj != Scriptable.NOT_FOUND) {
                state.throwable = obj;
                return BREAK_WITHOUT_EXTENSION;
            }
            return null;
        }

        private static void generatorCreate(Context cx, CallFrame frame) {
            // First time encountering this opcode: create new generator
            // object and return
            frame.pc--; // we want to come back here when we resume
            CallFrame generatorFrame = captureFrameForGenerator(frame);
            generatorFrame.frozen = true;
            if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
                frame.result =
                        new ES6Generator(frame.scope, generatorFrame.fnOrScript, generatorFrame);
            } else {
                frame.result =
                        new NativeGenerator(frame.scope, generatorFrame.fnOrScript, generatorFrame);
            }
        }
    }

    private static Object freezeGenerator(
            Context cx,
            CallFrame frame,
            InterpreterState state,
            GeneratorState generatorState,
            boolean yieldStar) {
        if (generatorState.operation == NativeGenerator.GENERATOR_CLOSE) {
            // Error: no yields when generator is closing
            throw ScriptRuntime.typeErrorById("msg.yield.closing");
        }
        // return to our caller (which should be a method of NativeGenerator)
        frame.frozen = true;
        frame.result = frame.stack[state.stackTop];
        frame.resultDbl = frame.sDbl[state.stackTop];
        frame.savedStackTop = state.stackTop;
        frame.pc--; // we want to come back here when we resume
        ScriptRuntime.exitActivationFunction(cx);
        final Object result =
                (frame.result != DOUBLE_MARK)
                        ? frame.result
                        : ScriptRuntime.wrapNumber(frame.resultDbl);
        if (yieldStar) {
            return new ES6Generator.YieldStarResult(result);
        }
        return result;
    }

    private static Object thawGenerator(
            CallFrame frame, InterpreterState state, GeneratorState generatorState, int op) {
        // we are resuming execution
        frame.frozen = false;
        int sourceLine = getIndex(frame.idata.itsICode, frame.pc);
        frame.pc += 2; // skip line number data
        if (generatorState.operation == NativeGenerator.GENERATOR_THROW) {
            // processing a call to <generator>.throw(exception): must
            // act as if exception was thrown from resumption point.
            return new JavaScriptException(
                    generatorState.value, frame.idata.itsSourceFile, sourceLine);
        }
        if (generatorState.operation == NativeGenerator.GENERATOR_CLOSE) {
            return generatorState.value;
        }
        if (generatorState.operation != NativeGenerator.GENERATOR_SEND) throw Kit.codeBug();
        if ((op == Token.YIELD) || (op == Icode_YIELD_STAR)) {
            frame.stack[state.stackTop] = generatorState.value;
        }
        return Scriptable.NOT_FOUND;
    }

    private static class DoYield extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            /* This is both where we yield from and re-enter the
             * generator.
             */
            if (!frame.frozen) {
                return new YieldResult(
                        freezeGenerator(
                                cx, frame, state, state.generatorState, op == Icode_YIELD_STAR));
            }
            Object obj = thawGenerator(frame, state, state.generatorState, op);
            if (obj != Scriptable.NOT_FOUND) {
                state.throwable = obj;
                return BREAK_WITHOUT_EXTENSION;
            }
            return null;
        }
    }

    private static class DoGeneratorEnd extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // throw StopIteration
            frame.frozen = true;
            int sourceLine = getIndex(frame.idata.itsICode, frame.pc);
            state.generatorState.returnedException =
                    new JavaScriptException(
                            NativeIterator.getStopIterationObject(frame.scope),
                            frame.idata.itsSourceFile,
                            sourceLine);
            return BREAK_LOOP;
        }
    }

    private static class DoGeneratorReturn extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // throw StopIteration with the value of "return"
            frame.frozen = true;
            frame.result = frame.stack[state.stackTop];
            frame.resultDbl = frame.sDbl[state.stackTop--];

            NativeIterator.StopIteration si =
                    new NativeIterator.StopIteration(
                            (frame.result == DOUBLE_MARK)
                                    ? Double.valueOf(frame.resultDbl)
                                    : frame.result);

            int sourceLine = getIndex(frame.idata.itsICode, frame.pc);
            state.generatorState.returnedException =
                    new JavaScriptException(si, frame.idata.itsSourceFile, sourceLine);
            return BREAK_LOOP;
        }
    }

    private static class DoRethrow extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg += frame.idata.itsMaxVars;
            state.throwable = frame.stack[state.indexReg];
            return BREAK_WITHOUT_EXTENSION;
        }
    }

    private static class DoThrow extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.throwable =
                    throwObject(
                            frame,
                            frame.stack,
                            frame.sDbl,
                            frame.idata,
                            frame.idata.itsICode,
                            state);
            --state.stackTop;
            return BREAK_WITHOUT_EXTENSION;
        }

        private static Object throwObject(
                CallFrame frame,
                final Object[] stack,
                final double[] sDbl,
                final InterpreterData iData,
                final byte[] iCode,
                InterpreterState state) {
            Object throwable;
            Object value = stack[state.stackTop];
            if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);

            int sourceLine = getIndex(iCode, frame.pc);
            throwable = new JavaScriptException(value, iData.itsSourceFile, sourceLine);
            return throwable;
        }
    }

    private static class DoCompare extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            Object lhs = stack[--state.stackTop];
            boolean valBln;
            if (lhs == DOUBLE_MARK && rhs == DOUBLE_MARK) {
                valBln =
                        ScriptRuntime.compareTo(sDbl[state.stackTop], sDbl[state.stackTop + 1], op);
                stack[state.stackTop] = valBln;
                return null;
            }
            object_compare:
            {
                number_compare:
                {
                    Number rNum, lNum;
                    if (rhs == DOUBLE_MARK) {
                        rNum = sDbl[state.stackTop + 1];
                        lNum = stack_numeric(frame, state.stackTop);
                    } else if (lhs == DOUBLE_MARK) {
                        rNum = ScriptRuntime.toNumeric(rhs);
                        lNum = sDbl[state.stackTop];
                    } else {
                        break number_compare;
                    }
                    valBln = ScriptRuntime.compare(lNum, rNum, op);
                    break object_compare;
                }
                valBln = ScriptRuntime.compare(lhs, rhs, op);
            }
            stack[state.stackTop] = valBln;
            return null;
        }
    }

    private static class DoInOrInstanceof extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            Object lhs = stack[--state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            boolean valBln;
            if (op == Token.IN) {
                valBln = ScriptRuntime.in(lhs, rhs, cx);
            } else {
                valBln = ScriptRuntime.instanceOf(lhs, rhs, cx);
            }
            stack[state.stackTop] = valBln;
            return null;
        }
    }

    private static class DoEquals extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            final boolean res = doEquals(state, stack, sDbl);
            stack[state.stackTop] = res;
            return null;
        }
    }

    private static class DoNotEquals extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            final boolean res = doEquals(state, stack, sDbl);
            stack[state.stackTop] = !res;
            return null;
        }
    }

    private static boolean doEquals(InterpreterState state, Object[] stack, double[] sDbl) {
        final Object rhs = stack[state.stackTop--];
        final Object lhs = stack[state.stackTop];
        final boolean res;
        if (rhs == DOUBLE_MARK) {
            if (lhs == DOUBLE_MARK) {
                res = (sDbl[state.stackTop] == sDbl[state.stackTop + 1]);
            } else {
                res = ScriptRuntime.eqNumber(sDbl[state.stackTop + 1], lhs);
            }
        } else if (lhs == DOUBLE_MARK) {
            res = ScriptRuntime.eqNumber(sDbl[state.stackTop], rhs);
        } else {
            res = ScriptRuntime.eq(lhs, rhs);
        }
        return res;
    }

    private static class DoShallowEquals extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            final boolean res = doShallowEquals(state, stack, sDbl);
            stack[state.stackTop] = res;
            return null;
        }
    }

    private static class DoShallowNotEquals extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            final boolean res = doShallowEquals(state, stack, sDbl);
            stack[state.stackTop] = !res;
            return null;
        }
    }

    private static boolean doShallowEquals(InterpreterState state, Object[] stack, double[] sDbl) {
        final Object rhs = stack[state.stackTop--];
        final Object lhs = stack[state.stackTop];
        final boolean res;
        if (rhs == DOUBLE_MARK) {
            double rDbl = sDbl[state.stackTop + 1];
            if (lhs == DOUBLE_MARK) {
                res = rDbl == sDbl[state.stackTop];
            } else if (lhs instanceof Number && !(lhs instanceof BigInteger)) {
                res = rDbl == ((Number) lhs).doubleValue();
            } else {
                res = false;
            }
        } else if (lhs == DOUBLE_MARK) {
            double ldbl = sDbl[state.stackTop];
            if (rhs instanceof Number && !(rhs instanceof BigInteger)) {
                res = ldbl == ((Number) rhs).doubleValue();
            } else {
                res = false;
            }
        } else {
            res = ScriptRuntime.shallowEq(lhs, rhs);
        }
        return res;
    }

    private static class DoIfNE extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            if (stack_boolean(frame, state.stackTop--)) {
                frame.pc += 2;
                return null;
            }
            return BREAK_JUMPLESSRUN;
        }
    }

    private static class DoIfEQ extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            if (!stack_boolean(frame, state.stackTop--)) {
                frame.pc += 2;
                return null;
            }
            return BREAK_JUMPLESSRUN;
        }
    }

    private static class DoIfEQPop extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            if (!stack_boolean(frame, state.stackTop--)) {
                frame.pc += 2;
                return null;
            }
            frame.stack[state.stackTop--] = null;
            return BREAK_JUMPLESSRUN;
        }
    }

    private static class DoIfNullUndef extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object val = frame.stack[state.stackTop];
            --state.stackTop;
            if (val != null && !Undefined.isUndefined(val)) {
                frame.pc += 2;
                return null;
            }
            return BREAK_JUMPLESSRUN;
        }
    }

    private static class DoIfNotNullUndef extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object val = frame.stack[state.stackTop];
            --state.stackTop;
            if (val == null || Undefined.isUndefined(val)) {
                frame.pc += 2;
                return null;
            }
            return BREAK_JUMPLESSRUN;
        }
    }

    private static class DoGoto extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            return BREAK_JUMPLESSRUN;
        }
    }

    private static class DoGosub extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            ++state.stackTop;
            frame.stack[state.stackTop] = DOUBLE_MARK;
            frame.sDbl[state.stackTop] = frame.pc + 2;
            return BREAK_JUMPLESSRUN;
        }
    }

    private static class DoStartSub extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            final InterpreterData iData = frame.idata;
            if (state.stackTop == frame.emptyStackTop + 1) {
                // Call from Icode_GOSUB: store return PC address in the local
                state.indexReg += iData.itsMaxVars;
                stack[state.indexReg] = stack[state.stackTop];
                sDbl[state.indexReg] = sDbl[state.stackTop];
                --state.stackTop;
            } else {
                // Call from exception handler: exception object is already
                // stored
                // in the local
                if (state.stackTop != frame.emptyStackTop) Kit.codeBug();
            }
            return null;
        }
    }

    private static class DoRetsub extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // state.indexReg: local to store return address
            if (state.instructionCounting) {
                addInstructionCount(cx, frame, 0);
            }
            state.indexReg += frame.idata.itsMaxVars;
            Object value = frame.stack[state.indexReg];
            if (value != DOUBLE_MARK) {
                // Invocation from exception handler, restore object to
                // rethrow
                state.throwable = value;
                return BREAK_WITHOUT_EXTENSION;
            }
            // Normal return from GOSUB
            frame.pc = (int) frame.sDbl[state.indexReg];
            if (state.instructionCounting) {
                frame.pcPrevBranch = frame.pc;
            }
            return null;
        }
    }

    private static class DoPop extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            stack[state.stackTop] = null;
            state.stackTop--;
            return null;
        }
    }

    private static class DoPopResult extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            frame.result = stack[state.stackTop];
            frame.resultDbl = sDbl[state.stackTop];
            stack[state.stackTop] = null;
            --state.stackTop;
            return null;
        }
    }

    private static class DoDup extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            stack[state.stackTop + 1] = stack[state.stackTop];
            sDbl[state.stackTop + 1] = sDbl[state.stackTop];
            state.stackTop++;
            return null;
        }
    }

    private static class DoDup2 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            stack[state.stackTop + 1] = stack[state.stackTop - 1];
            sDbl[state.stackTop + 1] = sDbl[state.stackTop - 1];
            stack[state.stackTop + 2] = stack[state.stackTop];
            sDbl[state.stackTop + 2] = sDbl[state.stackTop];
            state.stackTop += 2;
            return null;
        }
    }

    private static class DoSwap extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object o = stack[state.stackTop];
            stack[state.stackTop] = stack[state.stackTop - 1];
            stack[state.stackTop - 1] = o;
            double d = sDbl[state.stackTop];
            sDbl[state.stackTop] = sDbl[state.stackTop - 1];
            sDbl[state.stackTop - 1] = d;
            return null;
        }
    }

    private static class DoReturn extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            frame.result = stack[state.stackTop];
            frame.resultDbl = sDbl[state.stackTop];
            --state.stackTop;
            return BREAK_LOOP;
        }
    }

    private static class DoReturnResult extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            return BREAK_LOOP;
        }
    }

    private static class DoReturnUndef extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.result = undefined;
            return BREAK_LOOP;
        }
    }

    private static class DoBitNot extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            Number value = stack_numeric(frame, state.stackTop);
            Number result = ScriptRuntime.bitwiseNOT(value);
            if (result instanceof BigInteger) {
                stack[state.stackTop] = result;
            } else {
                stack[state.stackTop] = DOUBLE_MARK;
                sDbl[state.stackTop] = result.doubleValue();
            }
            return null;
        }
    }

    private static class DoBitOp extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            if (stack[state.stackTop] == DOUBLE_MARK && stack[state.stackTop - 1] == DOUBLE_MARK) {
                doFastBitOp(cx, frame, state, op);
                return null;
            }
            Number lValue = stack_numeric(frame, state.stackTop - 1);
            Number rValue = stack_numeric(frame, state.stackTop);
            state.stackTop--;

            Number result = null;
            switch (op) {
                case Token.BITAND:
                    result = ScriptRuntime.bitwiseAND(lValue, rValue);
                    break;
                case Token.BITOR:
                    result = ScriptRuntime.bitwiseOR(lValue, rValue);
                    break;
                case Token.BITXOR:
                    result = ScriptRuntime.bitwiseXOR(lValue, rValue);
                    break;
                case Token.LSH:
                    result = ScriptRuntime.leftShift(lValue, rValue);
                    break;
                case Token.RSH:
                    result = ScriptRuntime.signedRightShift(lValue, rValue);
                    break;
            }

            if (result instanceof BigInteger) {
                stack[state.stackTop] = result;
            } else {
                stack[state.stackTop] = DOUBLE_MARK;
                sDbl[state.stackTop] = result.doubleValue();
            }
            return null;
        }

        private static NewState doFastBitOp(
                Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            double lValue = sDbl[state.stackTop - 1];
            double rValue = sDbl[state.stackTop];
            state.stackTop--;

            double result = 0.0;
            switch (op) {
                case Token.BITAND:
                    result = ScriptRuntime.bitwiseAND(lValue, rValue);
                    break;
                case Token.BITOR:
                    result = ScriptRuntime.bitwiseOR(lValue, rValue);
                    break;
                case Token.BITXOR:
                    result = ScriptRuntime.bitwiseXOR(lValue, rValue);
                    break;
                case Token.LSH:
                    result = ScriptRuntime.leftShift(lValue, rValue);
                    break;
                case Token.RSH:
                    result = ScriptRuntime.signedRightShift(lValue, rValue);
                    break;
            }

            stack[state.stackTop] = DOUBLE_MARK;
            sDbl[state.stackTop] = result;
            return null;
        }
    }

    private static class DoUnsignedRightShift extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            double lDbl = stack_double(frame, state.stackTop - 1);
            int rIntValue = stack_int32(frame, state.stackTop) & 0x1F;
            stack[--state.stackTop] = DOUBLE_MARK;
            sDbl[state.stackTop] = ScriptRuntime.toUint32(lDbl) >>> rIntValue;
            return null;
        }
    }

    private static class DoPositive extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            double rDbl = stack_double(frame, state.stackTop);
            stack[state.stackTop] = DOUBLE_MARK;
            sDbl[state.stackTop] = rDbl;
            return null;
        }
    }

    private static class DoNegative extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Number rNum = stack_numeric(frame, state.stackTop);
            Number rNegNum = ScriptRuntime.negate(rNum);
            if (rNegNum instanceof BigInteger) {
                stack[state.stackTop] = rNegNum;
            } else {
                stack[state.stackTop] = DOUBLE_MARK;
                sDbl[state.stackTop] = rNegNum.doubleValue();
            }
            return null;
        }
    }

    private static class DoAdd extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object rhs = frame.stack[state.stackTop];
            Object lhs = frame.stack[--state.stackTop];
            double d;
            boolean leftRightOrder;
            if (rhs == DOUBLE_MARK) {
                d = frame.sDbl[state.stackTop + 1];
                if (lhs == DOUBLE_MARK) {
                    frame.sDbl[state.stackTop] += d;
                    return null;
                }
                leftRightOrder = true;
                // fallthrough to object + number code
            } else if (lhs == DOUBLE_MARK) {
                d = frame.sDbl[state.stackTop];
                lhs = rhs;
                leftRightOrder = false;
                // fallthrough to object + number code
            } else {
                if (lhs instanceof Scriptable || rhs instanceof Scriptable) {
                    frame.stack[state.stackTop] = ScriptRuntime.add(lhs, rhs, cx);

                    // the next two else if branches are a bit more tricky
                    // to reduce method calls
                } else if (lhs instanceof CharSequence) {
                    if (rhs instanceof CharSequence) {
                        frame.stack[state.stackTop] =
                                new ConsString((CharSequence) lhs, (CharSequence) rhs);
                    } else {
                        frame.stack[state.stackTop] =
                                new ConsString(
                                        (CharSequence) lhs, ScriptRuntime.toCharSequence(rhs));
                    }
                } else if (rhs instanceof CharSequence) {
                    frame.stack[state.stackTop] =
                            new ConsString(ScriptRuntime.toCharSequence(lhs), (CharSequence) rhs);

                } else {
                    Number lNum =
                            (lhs instanceof Number) ? (Number) lhs : ScriptRuntime.toNumeric(lhs);
                    Number rNum =
                            (rhs instanceof Number) ? (Number) rhs : ScriptRuntime.toNumeric(rhs);

                    if (lNum instanceof BigInteger && rNum instanceof BigInteger) {
                        frame.stack[state.stackTop] = ((BigInteger) lNum).add((BigInteger) rNum);
                    } else if (lNum instanceof BigInteger || rNum instanceof BigInteger) {
                        throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
                    } else {
                        frame.stack[state.stackTop] = DOUBLE_MARK;
                        frame.sDbl[state.stackTop] = lNum.doubleValue() + rNum.doubleValue();
                    }
                }
                return null;
            }

            // handle object(lhs) + number(d) code
            if (lhs instanceof Scriptable) {
                rhs = ScriptRuntime.wrapNumber(d);
                if (!leftRightOrder) {
                    Object tmp = lhs;
                    lhs = rhs;
                    rhs = tmp;
                }
                frame.stack[state.stackTop] = ScriptRuntime.add(lhs, rhs, cx);
            } else if (lhs instanceof CharSequence) {
                CharSequence rstr = ScriptRuntime.numberToString(d, 10);
                if (leftRightOrder) {
                    frame.stack[state.stackTop] = new ConsString((CharSequence) lhs, rstr);
                } else {
                    frame.stack[state.stackTop] = new ConsString(rstr, (CharSequence) lhs);
                }
            } else {
                Number lNum = (lhs instanceof Number) ? (Number) lhs : ScriptRuntime.toNumeric(lhs);
                if (lNum instanceof BigInteger) {
                    throw ScriptRuntime.typeErrorById("msg.cant.convert.to.number", "BigInt");
                } else {
                    frame.stack[state.stackTop] = DOUBLE_MARK;
                    frame.sDbl[state.stackTop] = lNum.doubleValue() + d;
                }
            }
            return null;
        }
    }

    private static class DoArithmetic extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            if (stack[state.stackTop] == DOUBLE_MARK && stack[state.stackTop - 1] == DOUBLE_MARK) {
                doFastArithemtic(cx, frame, state, op);
                return null;
            }

            Number lNum = stack_numeric(frame, state.stackTop - 1);
            Number rNum = stack_numeric(frame, state.stackTop);
            --state.stackTop;

            Number result = null;
            switch (op) {
                case Token.SUB:
                    result = ScriptRuntime.subtract(lNum, rNum);
                    break;
                case Token.MUL:
                    result = ScriptRuntime.multiply(lNum, rNum);
                    break;
                case Token.DIV:
                    result = ScriptRuntime.divide(lNum, rNum);
                    break;
                case Token.MOD:
                    result = ScriptRuntime.remainder(lNum, rNum);
                    break;
                case Token.EXP:
                    result = ScriptRuntime.exponentiate(lNum, rNum);
                    break;
            }

            if (result instanceof BigInteger) {
                stack[state.stackTop] = result;
            } else {
                stack[state.stackTop] = DOUBLE_MARK;
                sDbl[state.stackTop] = result.doubleValue();
            }
            return null;
        }

        private static NewState doFastArithemtic(
                Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            double lNum = sDbl[state.stackTop - 1];
            double rNum = sDbl[state.stackTop];
            state.stackTop--;

            double result = 0.0;
            switch (op) {
                case Token.SUB:
                    result = lNum - rNum;
                    break;
                case Token.MUL:
                    result = lNum * rNum;
                    break;
                case Token.DIV:
                    result = lNum / rNum;
                    break;
                case Token.MOD:
                    result = lNum % rNum;
                    break;
                case Token.EXP:
                    result = Math.pow(lNum, rNum);
                    break;
            }

            stack[state.stackTop] = DOUBLE_MARK;
            sDbl[state.stackTop] = result;
            return null;
        }
    }

    private static class DoNot extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            stack[state.stackTop] = !stack_boolean(frame, state.stackTop);
            return null;
        }
    }

    private static class DoBindName extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            stack[++state.stackTop] = ScriptRuntime.bind(cx, frame.scope, state.stringReg);
            return null;
        }
    }

    private static class DoSetName extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            Scriptable lhs = (Scriptable) stack[state.stackTop - 1];
            stack[state.stackTop - 1] =
                    op == Token.SETNAME
                            ? ScriptRuntime.setName(lhs, rhs, cx, frame.scope, state.stringReg)
                            : ScriptRuntime.strictSetName(
                                    lhs, rhs, cx, frame.scope, state.stringReg);
            --state.stackTop;
            return null;
        }
    }

    private static class DoSetConst extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            Scriptable lhs = (Scriptable) stack[state.stackTop - 1];
            stack[state.stackTop - 1] = ScriptRuntime.setConst(lhs, rhs, cx, state.stringReg);
            --state.stackTop;
            return null;
        }
    }

    private static class DoDelName extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            --state.stackTop;
            Object lhs = stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[state.stackTop] =
                    ScriptRuntime.delete(lhs, rhs, cx, frame.scope, op == Icode_DELNAME);
            return null;
        }
    }

    private static class DoDelPropSuper extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            state.stackTop -= 1;
            stack[state.stackTop] = Boolean.FALSE;
            ScriptRuntime.throwDeleteOnSuperPropertyNotAllowed();
            return null;
        }
    }

    private static class DoGetPropNoWarn extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object lhs = stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[state.stackTop] =
                    ScriptRuntime.getObjectPropNoWarn(lhs, state.stringReg, cx, frame.scope);
            return null;
        }
    }

    private static class DoGetProp extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object lhs = stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[state.stackTop] =
                    ScriptRuntime.getObjectProp(lhs, state.stringReg, cx, frame.scope);
            return null;
        }
    }

    private static class DoGetPropSuper extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            Object superObject = stack[state.stackTop];
            if (superObject == DOUBLE_MARK) Kit.codeBug();
            stack[state.stackTop] =
                    ScriptRuntime.getSuperProp(
                            superObject,
                            state.stringReg,
                            cx,
                            frame.scope,
                            frame.thisObj,
                            op == Token.GETPROPNOWARN_SUPER);
            return null;
        }
    }

    private static class DoSetProp extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            Object lhs = stack[state.stackTop - 1];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop - 1]);
            stack[--state.stackTop] =
                    ScriptRuntime.setObjectProp(lhs, state.stringReg, rhs, cx, frame.scope);
            return null;
        }
    }

    private static class DoSetPropSuper extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            Object superObject = stack[state.stackTop - 1];
            if (superObject == DOUBLE_MARK) Kit.codeBug();
            stack[--state.stackTop] =
                    ScriptRuntime.setSuperProp(
                            superObject, state.stringReg, rhs, cx, frame.scope, frame.thisObj);
            return null;
        }
    }

    private static class DoPropIncDec extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            final byte[] iCode = frame.idata.itsICode;
            Object lhs = stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[state.stackTop] =
                    ScriptRuntime.propIncrDecr(
                            lhs, state.stringReg, cx, frame.scope, iCode[frame.pc]);
            ++frame.pc;
            return null;
        }
    }

    private static class DoGetElem extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object lhs = stack[--state.stackTop];
            if (lhs == DOUBLE_MARK) {
                lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            }
            Object value;
            Object id = stack[state.stackTop + 1];
            if (id != DOUBLE_MARK) {
                value = ScriptRuntime.getObjectElem(lhs, id, cx, frame.scope);
            } else {
                double d = sDbl[state.stackTop + 1];
                value = ScriptRuntime.getObjectIndex(lhs, d, cx, frame.scope);
            }
            stack[state.stackTop] = value;
            return null;
        }
    }

    private static class DoGetElemSuper extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object superObject = stack[--state.stackTop];
            if (superObject == DOUBLE_MARK) Kit.codeBug();
            Object value;
            Object id = stack[state.stackTop + 1];
            if (id != DOUBLE_MARK) {
                value = ScriptRuntime.getSuperElem(superObject, id, cx, frame.scope, frame.thisObj);
            } else {
                double d = sDbl[state.stackTop + 1];
                value = ScriptRuntime.getSuperIndex(superObject, d, cx, frame.scope, frame.thisObj);
            }
            stack[state.stackTop] = value;
            return null;
        }
    }

    private static class DoSetElem extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) {
                rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            }
            state.stackTop -= 2;
            Object lhs = stack[state.stackTop];
            if (lhs == DOUBLE_MARK) {
                lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            }
            Object value;
            Object id = stack[state.stackTop + 1];
            if (id != DOUBLE_MARK) {
                value = ScriptRuntime.setObjectElem(lhs, id, rhs, cx, frame.scope);
            } else {
                double d = sDbl[state.stackTop + 1];
                value = ScriptRuntime.setObjectIndex(lhs, d, rhs, cx, frame.scope);
            }
            stack[state.stackTop] = value;
            return null;
        }
    }

    private static class DoSetElemSuper extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) {
                rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            }
            state.stackTop -= 2;
            Object superObject = stack[state.stackTop];
            if (superObject == DOUBLE_MARK) Kit.codeBug();
            Object value;
            Object id = stack[state.stackTop + 1];
            if (id != DOUBLE_MARK) {
                value =
                        ScriptRuntime.setSuperElem(
                                superObject, id, rhs, cx, frame.scope, frame.thisObj);
            } else {
                double d = sDbl[state.stackTop + 1];
                value =
                        ScriptRuntime.setSuperIndex(
                                superObject, d, rhs, cx, frame.scope, frame.thisObj);
            }
            stack[state.stackTop] = value;
            return null;
        }
    }

    private static class DoElemIncDec extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            final byte[] iCode = frame.idata.itsICode;
            Object rhs = stack[state.stackTop];
            if (rhs == DOUBLE_MARK) rhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            --state.stackTop;
            Object lhs = stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[state.stackTop] =
                    ScriptRuntime.elemIncrDecr(lhs, rhs, cx, frame.scope, iCode[frame.pc]);
            ++frame.pc;
            return null;
        }
    }

    private static class DoGetRef extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            Ref ref = (Ref) stack[state.stackTop];
            stack[state.stackTop] = ScriptRuntime.refGet(ref, cx);
            return null;
        }
    }

    private static class DoSetRef extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object value = stack[state.stackTop];
            if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            Ref ref = (Ref) stack[state.stackTop - 1];
            stack[--state.stackTop] = ScriptRuntime.refSet(ref, value, cx, frame.scope);
            return null;
        }
    }

    private static class DoDelRef extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            Ref ref = (Ref) stack[state.stackTop];
            stack[state.stackTop] = ScriptRuntime.refDel(ref, cx);
            return null;
        }
    }

    private static class DoRefIncDec extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final byte[] iCode = frame.idata.itsICode;
            Ref ref = (Ref) stack[state.stackTop];
            stack[state.stackTop] =
                    ScriptRuntime.refIncrDecr(ref, cx, frame.scope, iCode[frame.pc]);
            ++frame.pc;
            return null;
        }
    }

    private static class DoLocalLoad extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            final InterpreterData iData = frame.idata;
            ++state.stackTop;
            state.indexReg += iData.itsMaxVars;
            stack[state.stackTop] = stack[state.indexReg];
            sDbl[state.stackTop] = sDbl[state.indexReg];
            return null;
        }
    }

    private static class DoLocalClear extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final InterpreterData iData = frame.idata;
            state.indexReg += iData.itsMaxVars;
            stack[state.indexReg] = null;
            return null;
        }
    }

    private static class DoNameAndThis extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            // stringReg: name
            stack[++state.stackTop] =
                    ScriptRuntime.getNameAndThis(state.stringReg, cx, frame.scope);
            return null;
        }
    }

    private static class DoNameAndThisOptional extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            stack[++state.stackTop] =
                    ScriptRuntime.getNameAndThisOptional(state.stringReg, cx, frame.scope);
            return null;
        }
    }

    private static class DoPropAndThis extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object obj = stack[state.stackTop];
            if (obj == DOUBLE_MARK) obj = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            // stringReg: property
            stack[state.stackTop] =
                    ScriptRuntime.getPropAndThis(obj, state.stringReg, cx, frame.scope);
            return null;
        }
    }

    private static class DoPropAndThisOptional extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object obj = stack[state.stackTop];
            if (obj == DOUBLE_MARK) obj = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            // stringReg: property
            stack[state.stackTop] =
                    ScriptRuntime.getPropAndThisOptional(obj, state.stringReg, cx, frame.scope);
            return null;
        }
    }

    private static class DoElemAndThis extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object obj = stack[state.stackTop - 1];
            if (obj == DOUBLE_MARK) obj = ScriptRuntime.wrapNumber(sDbl[state.stackTop - 1]);
            Object id = stack[state.stackTop];
            if (id == DOUBLE_MARK) id = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[--state.stackTop] = ScriptRuntime.getElemAndThis(obj, id, cx, frame.scope);
            return null;
        }
    }

    private static class DoElemAndThisOptional extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object obj = stack[state.stackTop - 1];
            if (obj == DOUBLE_MARK) obj = ScriptRuntime.wrapNumber(sDbl[state.stackTop - 1]);
            Object id = stack[state.stackTop];
            if (id == DOUBLE_MARK) id = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[--state.stackTop] =
                    ScriptRuntime.getElemAndThisOptional(obj, id, cx, frame.scope);
            return null;
        }
    }

    private static class DoValueAndThis extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            final Object[] stack = frame.stack;
            final double[] sDbl = frame.sDbl;
            Object value = stack[state.stackTop];
            if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[state.stackTop] = ScriptRuntime.getValueAndThis(value, cx);
            return null;
        }
    }

    private static class DoValueAndThisOptional extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            Object value = stack[state.stackTop];
            if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[state.stackTop] = ScriptRuntime.getValueAndThisOptional(value, cx);
            return null;
        }
    }

    private static class DoCallSpecial extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            byte[] iCode = frame.idata.itsICode;
            boolean isOptionalChainingCall = (op == Icode_CALLSPECIAL_OPTIONAL);

            if (state.instructionCounting) {
                cx.instructionCount += INVOCATION_COST;
            }
            int callType = iCode[frame.pc] & 0xFF;
            boolean isNew = (iCode[frame.pc + 1] != 0);
            int sourceLine = getIndex(iCode, frame.pc + 2);

            // indexReg: number of arguments
            if (isNew) {
                // stack change: function arg0 .. argN -> newResult
                state.stackTop -= state.indexReg;

                Object function = stack[state.stackTop];
                if (function == DOUBLE_MARK)
                    function = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
                Object[] outArgs = getArgsArray(stack, sDbl, state.stackTop + 1, state.indexReg);
                stack[state.stackTop] =
                        ScriptRuntime.newSpecial(cx, function, outArgs, frame.scope, callType);
            } else {
                // stack change: function thisObj arg0 .. argN -> result
                state.stackTop -= state.indexReg;

                // Call code generation ensure that stack here
                // is ... Callable Scriptable
                ScriptRuntime.LookupResult result =
                        (ScriptRuntime.LookupResult) stack[state.stackTop];
                Object[] outArgs = getArgsArray(stack, sDbl, state.stackTop + 1, state.indexReg);
                Callable function = result.getCallable();
                stack[state.stackTop] =
                        ScriptRuntime.callSpecial(
                                cx,
                                function,
                                result.getThis(),
                                outArgs,
                                frame.scope,
                                frame.thisObj,
                                callType,
                                frame.idata.itsSourceFile,
                                sourceLine,
                                isOptionalChainingCall);
            }
            frame.pc += 4;
            return null;
        }
    }

    private static class DoCallByteCode extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {

            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            Object[] boundArgs = null;
            int blen = 0;

            if (state.instructionCounting) {
                cx.instructionCount += INVOCATION_COST;
            }
            // stack change: lookup_result arg0 .. argN -> result
            // indexReg: number of arguments
            state.stackTop -= state.indexReg;
            ScriptRuntime.LookupResult result = (ScriptRuntime.LookupResult) stack[state.stackTop];
            // Check if the lookup result is a function and throw if it's not
            // must not be done sooner according to the spec
            Callable fun = result.getCallable();
            Scriptable funThisObj = result.getThis();
            Scriptable funHomeObj =
                    (fun instanceof BaseFunction) ? ((BaseFunction) fun).getHomeObject() : null;
            if (op == Icode_CALL_ON_SUPER) {
                // funThisObj would have been the "super" object, which we
                // used to lookup the function. Now that that's done, we
                // discard it and invoke the function with the current
                // "this".
                funThisObj = frame.thisObj;
            }

            if (op == Token.REF_CALL) {
                Object[] outArgs = getArgsArray(stack, sDbl, state.stackTop + 1, state.indexReg);
                stack[state.stackTop] =
                        ScriptRuntime.callRef(
                                fun, funThisObj,
                                outArgs, cx);
                return null;
            }
            Scriptable calleeScope = frame.scope;
            if (frame.useActivation) {
                calleeScope = ScriptableObject.getTopLevelScope(frame.scope);
            }
            // Iteratively reduce known function types: arrows, lambdas,
            // bound functions, call/apply, and no-such-method-handler in
            // order to make a best-effort to keep them in this interpreter
            // loop so continuations keep working. The loop initializer and
            // condition are formulated so that they short-circuit the loop
            // if the function is already an interpreted function, which
            // should be the majority of cases.
            for (; ; ) {
                if (fun instanceof ArrowFunction) {
                    ArrowFunction afun = (ArrowFunction) fun;
                    fun = afun.getTargetFunction();
                    funThisObj = afun.getCallThis(cx);
                    funHomeObj = afun.getBoundHomeObject();
                } else if (fun instanceof KnownBuiltInFunction) {
                    KnownBuiltInFunction kfun = (KnownBuiltInFunction) fun;
                    // Bug 405654 -- make the best effort to keep
                    // Function.apply and Function.call within this
                    // interpreter loop invocation
                    if (BaseFunction.isApplyOrCall(kfun)) {
                        // funThisObj becomes fun
                        fun = ScriptRuntime.getCallable(funThisObj);
                        // first arg becomes thisObj
                        funThisObj =
                                getApplyThis(
                                        cx,
                                        stack,
                                        sDbl,
                                        boundArgs,
                                        state.stackTop + 1,
                                        state.indexReg,
                                        fun,
                                        frame);
                        if (BaseFunction.isApply(kfun)) {
                            // Apply: second argument after new "this"
                            // should be array-like
                            // and we'll spread its elements on the stack
                            final Object[] callArgs;
                            if (blen > 1) {
                                callArgs = ScriptRuntime.getApplyArguments(cx, boundArgs[1]);
                            } else if (state.indexReg < 2) {
                                callArgs = ScriptRuntime.emptyArgs;
                            } else {
                                callArgs =
                                        ScriptRuntime.getApplyArguments(
                                                cx, stack[state.stackTop - blen + 2]);
                            }

                            int alen = callArgs.length;
                            // We're coming from the outside, so this
                            // is replacing any bound args we might
                            // have had already.
                            boundArgs = callArgs;
                            blen = alen;
                            state.indexReg = alen;
                        } else {
                            // Call: shift args left, starting from 2nd
                            if (state.indexReg > 0) {
                                if (state.indexReg > 1 && blen == 0) {
                                    System.arraycopy(
                                            stack,
                                            state.stackTop + 2,
                                            stack,
                                            state.stackTop + 1,
                                            state.indexReg - 1);
                                    System.arraycopy(
                                            sDbl,
                                            state.stackTop + 2,
                                            sDbl,
                                            state.stackTop + 1,
                                            state.indexReg - 1);
                                } else if (state.indexReg > 1) {
                                    Object[] newBArgs = new Object[boundArgs.length - 1];
                                    System.arraycopy(
                                            boundArgs, 1, newBArgs, 0, boundArgs.length - 1);
                                    boundArgs = newBArgs;
                                    blen--;
                                } else {
                                    // Bound args is 1 long.
                                    boundArgs = new Object[0];
                                    blen--;
                                }
                                state.indexReg--;
                            }
                        }
                    } else {
                        // Some other IdFunctionObject we don't know how to
                        // reduce.
                        break;
                    }
                } else if (fun instanceof LambdaConstructor) {
                    break;
                } else if (fun instanceof LambdaFunction) {
                    fun = ((LambdaFunction) fun).getTarget();
                } else if (fun instanceof BoundFunction) {
                    BoundFunction bfun = (BoundFunction) fun;
                    fun = bfun.getTargetFunction();
                    funThisObj = bfun.getCallThis(cx, calleeScope);
                    Object[] bArgs = bfun.getBoundArgs();
                    boundArgs = addBoundArgs(boundArgs, bArgs);
                    blen = blen + bArgs.length;
                    state.indexReg += blen;
                } else if (fun instanceof NoSuchMethodShim) {
                    NoSuchMethodShim nsmfun = (NoSuchMethodShim) fun;
                    // Bug 447697 -- make best effort to keep
                    // __noSuchMethod__ within this interpreter loop
                    // invocation.
                    Object[] elements =
                            getArgsArray(
                                    stack,
                                    sDbl,
                                    boundArgs,
                                    blen,
                                    state.stackTop + 1,
                                    state.indexReg);
                    fun = nsmfun.noSuchMethodMethod;
                    boundArgs = new Object[2];
                    blen = 2;
                    boundArgs[0] = nsmfun.methodName;
                    boundArgs[1] = cx.newArray(calleeScope, elements);
                    state.indexReg = 2;
                } else if (fun == null) {
                    throw ScriptRuntime.notFunctionError(null, null);
                } else {
                    // Current function is something that we can't reduce
                    // further.
                    break;
                }
            }

            if (fun instanceof InterpretedFunction) {
                InterpretedFunction ifun = (InterpretedFunction) fun;
                if (frame.fnOrScript.securityDomain == ifun.securityDomain) {
                    CallFrame callParentFrame = frame;
                    if (op == Icode_TAIL_CALL) {
                        // In principle tail call can re-use the current
                        // frame and its stack arrays but it is hard to
                        // do properly. Any exceptions that can legally
                        // happen during frame re-initialization including
                        // StackOverflowException during innocent looking
                        // System.arraycopy may leave the current frame
                        // data corrupted leading to undefined behaviour
                        // in the catch code bellow that unwinds JS stack
                        // on exceptions. Then there is issue about frame
                        // release
                        // end exceptions there.
                        // To avoid frame allocation a released frame
                        // can be cached for re-use which would also benefit
                        // non-tail calls but it is not clear that this
                        // caching
                        // would gain in performance due to potentially
                        // bad interaction with GC.
                        callParentFrame = frame.parentFrame;
                        // Release the current frame. See Bug #344501 to see
                        // why
                        // it is being done here.
                        exitFrame(cx, frame, null);
                    }
                    CallFrame calleeFrame =
                            initFrame(
                                    cx,
                                    calleeScope,
                                    funThisObj,
                                    funHomeObj,
                                    stack,
                                    sDbl,
                                    boundArgs,
                                    state.stackTop + 1,
                                    state.indexReg,
                                    ifun,
                                    callParentFrame);
                    if (op != Icode_TAIL_CALL) {
                        frame.savedStackTop = state.stackTop;
                        frame.savedCallOp = op;
                    }
                    return new StateContinueResult(calleeFrame, state.indexReg);
                }
            }

            if (fun instanceof NativeContinuation) {
                // Jump to the captured continuation
                ContinuationJump cjump;
                cjump = new ContinuationJump((NativeContinuation) fun, frame);

                // continuation result is the first argument if any
                // of continuation call
                if (state.indexReg == 0) {
                    cjump.result = undefined;
                } else {
                    cjump.result = stack[state.stackTop + 1];
                    cjump.resultDbl = sDbl[state.stackTop + 1];
                }

                // Start the real unwind job
                state.throwable = cjump;
                return BREAK_WITHOUT_EXTENSION;
            }

            if (fun instanceof IdFunctionObject) {
                IdFunctionObject ifun = (IdFunctionObject) fun;
                if (NativeContinuation.isContinuationConstructor(ifun)) {
                    frame.stack[state.stackTop] = captureContinuation(cx, frame.parentFrame, false);
                    return null;
                }
            }

            frame.savedCallOp = op;
            frame.savedStackTop = state.stackTop;
            stack[state.stackTop] =
                    fun.call(
                            cx,
                            calleeScope,
                            funThisObj,
                            getArgsArray(
                                    stack,
                                    sDbl,
                                    boundArgs,
                                    blen,
                                    state.stackTop + 1,
                                    state.indexReg));

            return null;
        }
    }

    private static class DoNew extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            if (state.instructionCounting) {
                cx.instructionCount += INVOCATION_COST;
            }
            // stack change: function arg0 .. argN -> newResult
            // state.indexReg: number of arguments
            state.stackTop -= state.indexReg;

            Object lhs = frame.stack[state.stackTop];
            if (lhs instanceof InterpretedFunction) {
                InterpretedFunction f = (InterpretedFunction) lhs;
                if (frame.fnOrScript.securityDomain == f.securityDomain) {
                    if (cx.getLanguageVersion() >= Context.VERSION_ES6
                            && f.getHomeObject() != null) {
                        // Only methods have home objects associated with
                        // them
                        throw ScriptRuntime.typeErrorById("msg.not.ctor", f.getFunctionName());
                    }

                    Scriptable newInstance = f.createObject(cx, frame.scope);
                    CallFrame calleeFrame =
                            initFrame(
                                    cx,
                                    frame.scope,
                                    newInstance,
                                    newInstance,
                                    frame.stack,
                                    frame.sDbl,
                                    null,
                                    state.stackTop + 1,
                                    state.indexReg,
                                    f,
                                    frame);

                    frame.stack[state.stackTop] = newInstance;
                    frame.savedStackTop = state.stackTop;
                    frame.savedCallOp = op;
                    return new StateContinueResult(calleeFrame, state.indexReg);
                }
            }
            if (!(lhs instanceof Constructable)) {
                if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
                throw ScriptRuntime.notFunctionError(lhs);
            }
            Constructable ctor = (Constructable) lhs;

            if (ctor instanceof IdFunctionObject) {
                IdFunctionObject ifun = (IdFunctionObject) ctor;
                if (NativeContinuation.isContinuationConstructor(ifun)) {
                    frame.stack[state.stackTop] = captureContinuation(cx, frame.parentFrame, false);
                    return null;
                }
            }

            Object[] outArgs =
                    getArgsArray(frame.stack, frame.sDbl, state.stackTop + 1, state.indexReg);
            frame.stack[state.stackTop] = ctor.construct(cx, frame.scope, outArgs);
            return null;
        }
    }

    private static class DoTypeOf extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] stack = frame.stack;
            double[] sDbl = frame.sDbl;
            Object lhs = stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(sDbl[state.stackTop]);
            stack[state.stackTop] = ScriptRuntime.typeof(lhs);
            return null;
        }
    }

    private static class DoTypeOfName extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = ScriptRuntime.typeofName(frame.scope, state.stringReg);
            return null;
        }
    }

    private static class DoString extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = state.stringReg;
            return null;
        }
    }

    private static class DoShortNumber extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            ++state.stackTop;
            frame.stack[state.stackTop] = DOUBLE_MARK;
            frame.sDbl[state.stackTop] = getShort(frame.idata.itsICode, frame.pc);
            frame.pc += 2;
            return null;
        }
    }

    private static class DoIntNumber extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            ++state.stackTop;
            frame.stack[state.stackTop] = DOUBLE_MARK;
            frame.sDbl[state.stackTop] = getInt(frame.idata.itsICode, frame.pc);
            frame.pc += 4;
            return null;
        }
    }

    private static class DoNumber extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            ++state.stackTop;
            frame.stack[state.stackTop] = DOUBLE_MARK;
            frame.sDbl[state.stackTop] = frame.idata.itsDoubleTable[state.indexReg];
            return null;
        }
    }

    private static class DoBigInt extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = state.bigIntReg;
            return null;
        }
    }

    private static class DoName extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = ScriptRuntime.name(cx, frame.scope, state.stringReg);
            return null;
        }
    }

    private static class DoNameIncDec extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] =
                    ScriptRuntime.nameIncrDecr(
                            frame.scope, state.stringReg, cx, frame.idata.itsICode[frame.pc]);
            ++frame.pc;
            return null;
        }
    }

    private static class DoSetConstVar1 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg = frame.idata.itsICode[frame.pc++];
            var varAttributes = frame.varSource.stackAttributes;
            var vars = frame.varSource.stack;
            var varDbls = frame.varSource.sDbl;
            if (!frame.useActivation) {
                if ((varAttributes[state.indexReg] & ScriptableObject.READONLY) == 0) {
                    throw Context.reportRuntimeErrorById(
                            "msg.var.redecl", frame.idata.argNames[state.indexReg]);
                }
                if ((varAttributes[state.indexReg] & ScriptableObject.UNINITIALIZED_CONST) != 0) {
                    vars[state.indexReg] = frame.stack[state.stackTop];
                    varAttributes[state.indexReg] &= ~ScriptableObject.UNINITIALIZED_CONST;
                    varDbls[state.indexReg] = frame.sDbl[state.stackTop];
                }
            } else {
                Object val = frame.stack[state.stackTop];
                if (val == DOUBLE_MARK) val = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
                String stringReg = frame.idata.argNames[state.indexReg];
                if (frame.scope instanceof ConstProperties) {
                    ConstProperties cp = (ConstProperties) frame.scope;
                    cp.putConst(stringReg, frame.scope, val);
                } else throw Kit.codeBug();
            }
            return null;
        }
    }

    private static class DoSetConstVar extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            var varAttributes = frame.varSource.stackAttributes;
            var vars = frame.varSource.stack;
            var varDbls = frame.varSource.sDbl;
            if (!frame.useActivation) {
                if ((varAttributes[state.indexReg] & ScriptableObject.READONLY) == 0) {
                    throw Context.reportRuntimeErrorById(
                            "msg.var.redecl", frame.idata.argNames[state.indexReg]);
                }
                if ((varAttributes[state.indexReg] & ScriptableObject.UNINITIALIZED_CONST) != 0) {
                    vars[state.indexReg] = frame.stack[state.stackTop];
                    varAttributes[state.indexReg] &= ~ScriptableObject.UNINITIALIZED_CONST;
                    varDbls[state.indexReg] = frame.sDbl[state.stackTop];
                }
            } else {
                Object val = frame.stack[state.stackTop];
                if (val == DOUBLE_MARK) val = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
                String stringReg = frame.idata.argNames[state.indexReg];
                if (frame.scope instanceof ConstProperties) {
                    ConstProperties cp = (ConstProperties) frame.scope;
                    cp.putConst(stringReg, frame.scope, val);
                } else throw Kit.codeBug();
            }
            return null;
        }
    }

    private static class DoSetVar1 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg = frame.idata.itsICode[frame.pc++];
            var varAttributes = frame.varSource.stackAttributes;
            var vars = frame.varSource.stack;
            var varDbls = frame.varSource.sDbl;
            if (!frame.useActivation) {
                if ((varAttributes[state.indexReg] & ScriptableObject.READONLY) == 0) {
                    vars[state.indexReg] = frame.stack[state.stackTop];
                    varDbls[state.indexReg] = frame.sDbl[state.stackTop];
                }
            } else {
                Object val = frame.stack[state.stackTop];
                if (val == DOUBLE_MARK) val = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
                String stringReg = frame.idata.argNames[state.indexReg];
                frame.scope.put(stringReg, frame.scope, val);
            }
            return null;
        }
    }

    private static class DoSetVar extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            var varAttributes = frame.varSource.stackAttributes;
            var vars = frame.varSource.stack;
            var varDbls = frame.varSource.sDbl;
            if (!frame.useActivation) {
                if ((varAttributes[state.indexReg] & ScriptableObject.READONLY) == 0) {
                    vars[state.indexReg] = frame.stack[state.stackTop];
                    varDbls[state.indexReg] = frame.sDbl[state.stackTop];
                }
            } else {
                Object val = frame.stack[state.stackTop];
                if (val == DOUBLE_MARK) val = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
                String stringReg = frame.idata.argNames[state.indexReg];
                frame.scope.put(stringReg, frame.scope, val);
            }
            return null;
        }
    }

    private static class DoGetVar1 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg = frame.idata.itsICode[frame.pc++];
            var vars = frame.varSource.stack;
            var varDbls = frame.varSource.sDbl;
            ++state.stackTop;
            if (!frame.useActivation) {
                frame.stack[state.stackTop] = vars[state.indexReg];
                frame.sDbl[state.stackTop] = varDbls[state.indexReg];
            } else {
                String stringReg = frame.idata.argNames[state.indexReg];
                frame.stack[state.stackTop] = frame.scope.get(stringReg, frame.scope);
            }
            return null;
        }
    }

    private static class DoGetVar extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            var vars = frame.varSource.stack;
            var varDbls = frame.varSource.sDbl;
            ++state.stackTop;
            if (!frame.useActivation) {
                frame.stack[state.stackTop] = vars[state.indexReg];
                frame.sDbl[state.stackTop] = varDbls[state.indexReg];
            } else {
                String stringReg = frame.idata.argNames[state.indexReg];
                frame.stack[state.stackTop] = frame.scope.get(stringReg, frame.scope);
            }
            return null;
        }
    }

    private static class DoVarIncDec extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            var varAttributes = frame.varSource.stackAttributes;
            var vars = frame.varSource.stack;
            var varDbls = frame.varSource.sDbl;
            // indexReg : varindex
            ++state.stackTop;
            int incrDecrMask = frame.idata.itsICode[frame.pc];
            if (!frame.useActivation) {
                Object varValue = vars[state.indexReg];
                double d = 0.0;
                BigInteger bi = null;
                if (varValue == DOUBLE_MARK) {
                    d = varDbls[state.indexReg];
                } else {
                    Number num = ScriptRuntime.toNumeric(varValue);
                    if (num instanceof BigInteger) {
                        bi = (BigInteger) num;
                    } else {
                        d = num.doubleValue();
                    }
                }
                if (bi == null) {
                    // double
                    double d2 = ((incrDecrMask & Node.DECR_FLAG) == 0) ? d + 1.0 : d - 1.0;
                    boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
                    if ((varAttributes[state.indexReg] & ScriptableObject.READONLY) == 0) {
                        if (varValue != DOUBLE_MARK) {
                            vars[state.indexReg] = DOUBLE_MARK;
                        }
                        varDbls[state.indexReg] = d2;
                        frame.stack[state.stackTop] = DOUBLE_MARK;
                        frame.sDbl[state.stackTop] = post ? d : d2;
                    } else {
                        if (post && varValue != DOUBLE_MARK) {
                            frame.stack[state.stackTop] = varValue;
                        } else {
                            frame.stack[state.stackTop] = DOUBLE_MARK;
                            frame.sDbl[state.stackTop] = post ? d : d2;
                        }
                    }
                } else {
                    // BigInt
                    BigInteger result;
                    if ((incrDecrMask & Node.DECR_FLAG) == 0) {
                        result = bi.add(BigInteger.ONE);
                    } else {
                        result = bi.subtract(BigInteger.ONE);
                    }

                    boolean post = ((incrDecrMask & Node.POST_FLAG) != 0);
                    if ((varAttributes[state.indexReg] & ScriptableObject.READONLY) == 0) {
                        vars[state.indexReg] = result;
                        frame.stack[state.stackTop] = post ? bi : result;
                    } else {
                        if (post && varValue != DOUBLE_MARK) {
                            frame.stack[state.stackTop] = varValue;
                        } else {
                            frame.stack[state.stackTop] = post ? bi : result;
                        }
                    }
                }
            } else {
                String varName = frame.idata.argNames[state.indexReg];
                frame.stack[state.stackTop] =
                        ScriptRuntime.nameIncrDecr(frame.scope, varName, cx, incrDecrMask);
            }
            ++frame.pc;
            return null;
        }
    }

    private static class DoZero extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            ++state.stackTop;
            frame.stack[state.stackTop] = Integer.valueOf(0);
            return null;
        }
    }

    private static class DoOne extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            ++state.stackTop;
            frame.stack[state.stackTop] = Integer.valueOf(1);
            return null;
        }
    }

    private static class DoNull extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = null;
            return null;
        }
    }

    private static class DoThis extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = frame.thisObj;
            return null;
        }
    }

    private static class DoSuper extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // If we are referring to "super", then we always have an
            // activation
            // (this is done in IrFactory). The home object is stored as
            // part of the
            // activation frame to propagate it correctly for nested
            // functions.
            Scriptable homeObject = getCurrentFrameHomeObject(frame);
            if (homeObject == null) {
                // This if is specified in the spec, but I cannot imagine
                // how the home object will ever be null since `super` is
                // legal _only_ in method definitions, where we do have a
                // home object!
                frame.stack[++state.stackTop] = Undefined.instance;
            } else {
                frame.stack[++state.stackTop] = homeObject.getPrototype();
            }
            return null;
        }
    }

    private static class DoThisFunction extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = frame.fnOrScript;
            return null;
        }
    }

    private static class DoFalse extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = Boolean.FALSE;
            return null;
        }
    }

    private static class DoTrue extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = Boolean.TRUE;
            return null;
        }
    }

    private static class DoUndef extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.stack[++state.stackTop] = undefined;
            return null;
        }
    }

    private static class DoEnterWith extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object lhs = frame.stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            frame.scope = ScriptRuntime.enterWith(lhs, cx, frame.scope);
            state.stackTop--;
            return null;
        }
    }

    private static class DoLeaveWith extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.scope = ScriptRuntime.leaveWith(frame.scope);
            return null;
        }
    }

    private static class DoCatchScope extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {

            // stack top: exception object
            // state.stringReg: name of exception variable
            // state.indexReg: local for exception scope
            --state.stackTop;
            state.indexReg += frame.idata.itsMaxVars;

            boolean afterFirstScope = (frame.idata.itsICode[frame.pc] != 0);
            Throwable caughtException = (Throwable) frame.stack[state.stackTop + 1];
            Scriptable lastCatchScope;
            if (!afterFirstScope) {
                lastCatchScope = null;
            } else {
                lastCatchScope = (Scriptable) frame.stack[state.indexReg];
            }
            frame.stack[state.indexReg] =
                    ScriptRuntime.newCatchScope(
                            caughtException, lastCatchScope, state.stringReg, cx, frame.scope);
            ++frame.pc;
            return null;
        }
    }

    private static class DoEnumInit extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object lhs = frame.stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            state.indexReg += frame.idata.itsMaxVars;
            int enumType =
                    op == Token.ENUM_INIT_KEYS
                            ? ScriptRuntime.ENUMERATE_KEYS
                            : op == Token.ENUM_INIT_VALUES
                                    ? ScriptRuntime.ENUMERATE_VALUES
                                    : op == Token.ENUM_INIT_VALUES_IN_ORDER
                                            ? ScriptRuntime.ENUMERATE_VALUES_IN_ORDER
                                            : ScriptRuntime.ENUMERATE_ARRAY;
            frame.stack[state.indexReg] = ScriptRuntime.enumInit(lhs, cx, frame.scope, enumType);
            --state.stackTop;
            return null;
        }
    }

    private static class DoEnumOp extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg += frame.idata.itsMaxVars;
            Object val = frame.stack[state.indexReg];
            frame.stack[++state.stackTop] =
                    (op == Token.ENUM_NEXT)
                            ? ScriptRuntime.enumNext(val, cx)
                            : ScriptRuntime.enumId(val, cx);
            return null;
        }
    }

    private static class DoRefSpecial extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // stringReg: name of special property
            Object obj = frame.stack[state.stackTop];
            if (obj == DOUBLE_MARK) obj = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            frame.stack[state.stackTop] =
                    ScriptRuntime.specialRef(obj, state.stringReg, cx, frame.scope);
            return null;
        }
    }

    private static class DoRefMember extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object elem = frame.stack[state.stackTop];
            if (elem == DOUBLE_MARK) elem = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            --state.stackTop;
            Object obj = frame.stack[state.stackTop];
            if (obj == DOUBLE_MARK) obj = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            frame.stack[state.stackTop] = ScriptRuntime.memberRef(obj, elem, cx, state.indexReg);
            return null;
        }
    }

    private static class DoRefNsMember extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object elem = frame.stack[state.stackTop];
            if (elem == DOUBLE_MARK) elem = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            --state.stackTop;
            Object ns = frame.stack[state.stackTop];
            if (ns == DOUBLE_MARK) ns = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            --state.stackTop;
            Object obj = frame.stack[state.stackTop];
            if (obj == DOUBLE_MARK) obj = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            frame.stack[state.stackTop] =
                    ScriptRuntime.memberRef(obj, ns, elem, cx, state.indexReg);
            return null;
        }
    }

    private static class DoRefName extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // indexReg: flags
            Object name = frame.stack[state.stackTop];
            if (name == DOUBLE_MARK) name = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            frame.stack[state.stackTop] =
                    ScriptRuntime.nameRef(name, cx, frame.scope, state.indexReg);
            return null;
        }
    }

    private static class DoRefNsName extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object name = frame.stack[state.stackTop];
            if (name == DOUBLE_MARK) name = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            --state.stackTop;
            Object ns = frame.stack[state.stackTop];
            if (ns == DOUBLE_MARK) ns = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            frame.stack[state.stackTop] =
                    ScriptRuntime.nameRef(ns, name, cx, frame.scope, state.indexReg);
            return null;
        }
    }

    private static class DoScopeLoad extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg += frame.idata.itsMaxVars;
            frame.scope = (Scriptable) frame.stack[state.indexReg];
            return null;
        }
    }

    private static class DoScopeSave extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg += frame.idata.itsMaxVars;
            frame.stack[state.indexReg] = frame.scope;
            return null;
        }
    }

    private static class DoClosureExpr extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            InterpretedFunction fn =
                    InterpretedFunction.createFunction(
                            cx, frame.scope, frame.fnOrScript, state.indexReg);
            if (fn.idata.itsFunctionType == FunctionNode.ARROW_FUNCTION) {
                Scriptable homeObject = getCurrentFrameHomeObject(frame);
                if (fn.idata.itsNeedsActivation) {
                    fn.setHomeObject(homeObject);
                }

                frame.stack[++state.stackTop] =
                        new ArrowFunction(cx, frame.scope, fn, frame.thisObj, homeObject);
            } else {
                frame.stack[++state.stackTop] = fn;
            }
            return null;
        }
    }

    private static class DoStoreHomeObject extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // Stack contains: [object, keysArray, flagsArray, valuesArray,
            // function]
            InterpretedFunction fun = (InterpretedFunction) frame.stack[state.stackTop];
            Scriptable homeObject = (Scriptable) frame.stack[state.stackTop - 2];
            fun.setHomeObject(homeObject);
            return null;
        }
    }

    private static class DoClosureStatement extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            initFunction(cx, frame.scope, frame.fnOrScript, state.indexReg);
            return null;
        }
    }

    private static class DoRegExp extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object re = frame.idata.itsRegExpLiterals[state.indexReg];
            frame.stack[++state.stackTop] = ScriptRuntime.wrapRegExp(cx, frame.scope, re);
            return null;
        }
    }

    private static class DoTemplateLiteralCallSite extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object[] templateLiterals = frame.idata.itsTemplateLiterals;
            frame.stack[++state.stackTop] =
                    ScriptRuntime.getTemplateLiteralCallSite(
                            cx, frame.scope, templateLiterals, state.indexReg);
            return null;
        }
    }

    private static class DoLiteralNewObject extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            ++frame.pc;
            ++state.stackTop;
            frame.stack[state.stackTop] = cx.newObject(frame.scope);
            ++state.stackTop;

            // indexReg > 0: index of constant with the keys
            // indexReg < 0: we have a spread, so no keys array, but we know the length
            if (state.indexReg < 0) {
                frame.stack[state.stackTop] = new NewLiteralStorage(-state.indexReg - 1, true);
            } else {
                Object[] ids = (Object[]) frame.idata.literalIds[state.indexReg];
                boolean copyArray = frame.idata.itsICode[frame.pc] != 0;
                frame.stack[state.stackTop] =
                        new NewLiteralStorage(copyArray ? Arrays.copyOf(ids, ids.length) : ids);
            }

            return null;
        }
    }

    private static class DoLiteralNewArray extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // indexReg: number of values in the literal
            frame.stack[++state.stackTop] = new NewLiteralStorage(state.indexReg, false);
            return null;
        }
    }

    private static class DoLiteralSet extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object value = frame.stack[state.stackTop];
            if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            --state.stackTop;
            var store = (NewLiteralStorage) frame.stack[state.stackTop];
            store.pushValue(value);
            return null;
        }
    }

    private static class DoLiteralGetter extends InstructionClass {
        @Override
        NewState execute(Context cs, CallFrame frame, InterpreterState state, int op) {
            Object value = frame.stack[state.stackTop];
            --state.stackTop;
            var store = (NewLiteralStorage) frame.stack[state.stackTop];
            store.pushGetter(value);
            return null;
        }
    }

    private static class DoLiteralSetter extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object value = frame.stack[state.stackTop];
            --state.stackTop;
            var store = (NewLiteralStorage) frame.stack[state.stackTop];
            store.pushSetter(value);
            return null;
        }
    }

    private static class DoLiteralKeySet extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object key = frame.stack[state.stackTop];
            if (key == DOUBLE_MARK) key = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            --state.stackTop;
            var store = (NewLiteralStorage) frame.stack[state.stackTop];
            store.pushKey(key);
            return null;
        }
    }

    private static class DoSpread extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            // stack: [..., NewLiteralStorage, sourceObj]
            Object source = frame.stack[state.stackTop];
            --state.stackTop;
            NewLiteralStorage store = (NewLiteralStorage) frame.stack[state.stackTop];
            store.spread(cx, frame.scope, source);
            return null;
        }
    }

    private static class DoObjectLit extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            var store = (NewLiteralStorage) frame.stack[state.stackTop];
            --state.stackTop;
            Scriptable object = (Scriptable) frame.stack[state.stackTop];
            ScriptRuntime.fillObjectLiteral(
                    object,
                    store.getKeys(),
                    store.getValues(),
                    store.getGetterSetters(),
                    cx,
                    frame.scope);
            return null;
        }
    }

    private static class DoArrayLiteral extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            var store = (NewLiteralStorage) frame.stack[state.stackTop];
            int[] skipIndexces = null;
            if (op == Icode_SPARE_ARRAYLIT) {
                skipIndexces = (int[]) frame.idata.literalIds[state.indexReg];
            }
            frame.stack[state.stackTop] =
                    ScriptRuntime.newArrayLiteral(store.getValues(), skipIndexces, cx, frame.scope);
            return null;
        }
    }

    private static class DoEnterDotQuery extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object lhs = frame.stack[state.stackTop];
            if (lhs == DOUBLE_MARK) lhs = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            frame.scope = ScriptRuntime.enterDotQuery(lhs, frame.scope);
            state.stackTop--;
            return null;
        }
    }

    private static class DoLeaveDotQuery extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            boolean valBln = stack_boolean(frame, state.stackTop);
            Object x = ScriptRuntime.updateDotQuery(valBln, frame.scope);
            if (x != null) {
                frame.stack[state.stackTop] = x;
                frame.scope = ScriptRuntime.leaveDotQuery(frame.scope);
                frame.pc += 2;
                return null;
            }
            // reset stack and PC to code after ENTERDQ
            --state.stackTop;
            return BREAK_JUMPLESSRUN;
        }
    }

    private static class DoDefaultNamespace extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object value = frame.stack[state.stackTop];
            if (value == DOUBLE_MARK) value = ScriptRuntime.wrapNumber(frame.sDbl[state.stackTop]);
            frame.stack[state.stackTop] = ScriptRuntime.setDefaultNamespace(value, cx);
            return null;
        }
    }

    private static class DoEscXMLAttr extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object value = frame.stack[state.stackTop];
            if (value != DOUBLE_MARK) {
                frame.stack[state.stackTop] = ScriptRuntime.escapeAttributeValue(value, cx);
            }
            return null;
        }
    }

    private static class DoEscXMLText extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            Object value = frame.stack[state.stackTop];
            if (value != DOUBLE_MARK) {
                frame.stack[state.stackTop] = ScriptRuntime.escapeTextValue(value, cx);
            }
            return null;
        }
    }

    private static class DoDebug extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            if (frame.debuggerFrame != null) {
                frame.debuggerFrame.onDebuggerStatement(cx);
            }
            return null;
        }
    }

    private static class DoLineChange extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            frame.pcSourceLineStart = frame.pc;
            if (frame.debuggerFrame != null) {
                int line = getIndex(frame.idata.itsICode, frame.pc);
                frame.debuggerFrame.onLineChange(cx, line);
            }
            frame.pc += 2;
            return null;
        }
    }

    private static class DoIndexCn extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg = Icode_REG_IND_C0 - op;
            return null;
        }
    }

    private static class DoRegIndex1 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg = 0xFF & frame.idata.itsICode[frame.pc];
            ++frame.pc;
            return null;
        }
    }

    private static class DoRegIndex2 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg = getIndex(frame.idata.itsICode, frame.pc);
            frame.pc += 2;
            return null;
        }
    }

    private static class DoRegIndex4 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.indexReg = getInt(frame.idata.itsICode, frame.pc);
            frame.pc += 4;
            return null;
        }
    }

    private static class DoStringCn extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.stringReg = frame.idata.itsStringTable[Icode_REG_STR_C0 - op];
            return null;
        }
    }

    private static class DoRegString1 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.stringReg = frame.idata.itsStringTable[0xFF & frame.idata.itsICode[frame.pc]];
            ++frame.pc;
            return null;
        }
    }

    private static class DoRegString2 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.stringReg = frame.idata.itsStringTable[getIndex(frame.idata.itsICode, frame.pc)];
            frame.pc += 2;
            return null;
        }
    }

    private static class DoRegString4 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.stringReg = frame.idata.itsStringTable[getInt(frame.idata.itsICode, frame.pc)];
            frame.pc += 4;
            return null;
        }
    }

    private static class DoBigIntCn extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.bigIntReg = frame.idata.itsBigIntTable[Icode_REG_BIGINT_C0 - op];
            return null;
        }
    }

    private static class DoRegBigInt1 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.bigIntReg = frame.idata.itsBigIntTable[0xFF & frame.idata.itsICode[frame.pc]];
            ++frame.pc;
            return null;
        }
    }

    private static class DoRegBigInt2 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.bigIntReg = frame.idata.itsBigIntTable[getIndex(frame.idata.itsICode, frame.pc)];
            frame.pc += 2;
            return null;
        }
    }

    private static class DoRegBigInt4 extends InstructionClass {
        @Override
        NewState execute(Context cx, CallFrame frame, InterpreterState state, int op) {
            state.bigIntReg = frame.idata.itsBigIntTable[getInt(frame.idata.itsICode, frame.pc)];
            frame.pc += 4;
            return null;
        }
    }

    /**
     * Adds the new args to the bound args. Because we're unwrapping things in reverse order they
     * are actually prepended.
     */
    private static Object[] addBoundArgs(Object[] boundArgs, Object[] newArgs) {
        if (newArgs.length == 0) {
            return boundArgs;
        } else if (boundArgs == null) {
            return newArgs;
        } else {
            Object[] result = Arrays.copyOf(newArgs, boundArgs.length + newArgs.length);
            System.arraycopy(boundArgs, 0, result, newArgs.length, boundArgs.length);
            return result;
        }
    }

    private static Scriptable getCurrentFrameHomeObject(CallFrame frame) {
        if (frame.scope instanceof NativeCall) {
            return ((NativeCall) frame.scope).getHomeObject();
        } else {
            return null;
        }
    }

    private static CallFrame processThrowable(
            Context cx,
            Object throwable,
            CallFrame frame,
            int indexReg,
            boolean instructionCounting) {
        // Recovering from exception, indexReg contains
        // the index of handler

        if (indexReg >= 0) {
            // Normal exception handler, transfer
            // control appropriately

            if (frame.frozen) {
                // XXX Deal with exceptios!!!
                frame = frame.cloneFrozen();
            }

            int[] table = frame.idata.itsExceptionTable;

            frame.pc = table[indexReg + EXCEPTION_HANDLER_SLOT];
            if (instructionCounting) {
                frame.pcPrevBranch = frame.pc;
            }

            frame.savedStackTop = frame.emptyStackTop;
            int localShift = frame.idata.itsMaxVars;
            int scopeLocal = localShift + table[indexReg + EXCEPTION_SCOPE_SLOT];
            int exLocal = localShift + table[indexReg + EXCEPTION_LOCAL_SLOT];
            frame.scope = (Scriptable) frame.stack[scopeLocal];
            frame.stack[exLocal] = throwable;

            throwable = null;
        } else {
            // Continuation restoration
            ContinuationJump cjump = (ContinuationJump) throwable;

            // Clear throwable to indicate that exceptions are OK
            throwable = null;

            if (!Objects.equals(cjump.branchFrame, frame)) Kit.codeBug();

            // Check that we have at least one frozen frame
            // in the case of detached continuation restoration:
            // unwind code ensure that
            if (cjump.capturedFrame == null) Kit.codeBug();

            // Need to rewind branchFrame, capturedFrame
            // and all frames in between
            int rewindCount = cjump.capturedFrame.frameIndex + 1;
            if (cjump.branchFrame != null) {
                rewindCount -= cjump.branchFrame.frameIndex;
            }

            int enterCount = 0;
            CallFrame[] enterFrames = null;

            CallFrame x = cjump.capturedFrame;
            for (int i = 0; i != rewindCount; ++i) {
                if (!x.frozen) Kit.codeBug();
                if (x.useActivation) {
                    if (enterFrames == null) {
                        // Allocate enough space to store the rest
                        // of rewind frames in case all of them
                        // would require to enter
                        enterFrames = new CallFrame[rewindCount - i];
                    }
                    enterFrames[enterCount] = x;
                    ++enterCount;
                }
                x = x.parentFrame;
            }

            while (enterCount != 0) {
                // execute enter: walk enterFrames in the reverse
                // order since they were stored starting from
                // the capturedFrame, not branchFrame
                --enterCount;
                x = enterFrames[enterCount];
                enterFrame(cx, x, ScriptRuntime.emptyArgs, true);
            }

            // Continuation jump is almost done: capturedFrame
            // points to the call to the function that captured
            // continuation, so clone capturedFrame and
            // emulate return that function with the suplied result
            frame = cjump.capturedFrame.cloneFrozen();
            setCallResult(frame, cjump.result, cjump.resultDbl);
            // restart the execution
        }
        frame.throwable = throwable;
        return frame;
    }

    private static Scriptable getApplyThis(
            Context cx,
            Object[] stack,
            double[] sDbl,
            Object[] boundArgs,
            int thisIdx,
            int indexReg,
            Callable target,
            CallFrame frame) {
        Object obj;
        if (indexReg != 0) {
            if (boundArgs != null && boundArgs.length > 0) {
                obj = boundArgs[0];
            } else {
                obj = stack[thisIdx];
                if (obj == DOUBLE_MARK) obj = ScriptRuntime.wrapNumber(sDbl[thisIdx]);
            }
        } else {
            obj = null;
        }
        return ScriptRuntime.getApplyOrCallThis(cx, frame.scope, obj, indexReg, target);
    }

    private static CallFrame initFrame(
            Context cx,
            Scriptable callerScope,
            Scriptable thisObj,
            Scriptable homeObj,
            Object[] args,
            double[] argsDbl,
            Object[] boundArgs,
            int argShift,
            int argCount,
            InterpretedFunction fnOrScript,
            CallFrame parentFrame) {
        CallFrame frame =
                new CallFrame(
                        cx,
                        thisObj,
                        fnOrScript,
                        parentFrame,
                        parentFrame == null
                                ? (CallFrame) cx.lastInterpreterFrame
                                : parentFrame.previousInterpreterFrame);
        frame.initializeArgs(
                cx, callerScope, args, argsDbl, boundArgs, argShift, argCount, homeObj);
        enterFrame(cx, frame, args, false);
        return frame;
    }

    private static void enterFrame(
            Context cx, CallFrame frame, Object[] args, boolean continuationRestart) {
        boolean usesActivation = frame.idata.itsNeedsActivation;
        boolean isDebugged = frame.debuggerFrame != null;
        if (usesActivation || isDebugged) {
            Scriptable scope = frame.scope;
            if (scope == null) {
                Kit.codeBug();
            } else if (continuationRestart) {
                // Walk the parent chain of frame.scope until a NativeCall is
                // found. Normally, frame.scope is a NativeCall when called
                // from initFrame() for a debugged or activatable function.
                // However, when called from interpretLoop() as part of
                // restarting a continuation, it can also be a NativeWith if
                // the continuation was captured within a "with" or "catch"
                // block ("catch" implicitly uses NativeWith to create a scope
                // to expose the exception variable).
                for (; ; ) {
                    if (scope instanceof NativeWith) {
                        scope = scope.getParentScope();
                        if (scope == null
                                || (frame.parentFrame != null
                                        && frame.parentFrame.scope == scope)) {
                            // If we get here, we didn't find a NativeCall in
                            // the call chain before reaching parent frame's
                            // scope. This should not be possible.
                            Kit.codeBug();
                            break; // Never reached, but keeps the static analyzer
                            // happy about "scope" not being null 5 lines above.
                        }
                    } else {
                        break;
                    }
                }
            }
            if (isDebugged) {
                frame.debuggerFrame.onEnter(cx, scope, frame.thisObj, args);
            }
            // Enter activation only when itsNeedsActivation true,
            // since debugger should not interfere with activation
            // chaining
            if (usesActivation) {
                ScriptRuntime.enterActivationFunction(cx, scope);
            }
        }
    }

    private static void exitFrame(Context cx, CallFrame frame, Object throwable) {
        if (frame.idata.itsNeedsActivation) {
            ScriptRuntime.exitActivationFunction(cx);
        }

        if (frame.debuggerFrame != null) {
            try {
                if (throwable instanceof Throwable) {
                    frame.debuggerFrame.onExit(cx, true, throwable);
                } else {
                    Object result;
                    ContinuationJump cjump = (ContinuationJump) throwable;
                    if (cjump == null) {
                        result = frame.result;
                    } else {
                        result = cjump.result;
                    }
                    if (result == DOUBLE_MARK) {
                        double resultDbl;
                        if (cjump == null) {
                            resultDbl = frame.resultDbl;
                        } else {
                            resultDbl = cjump.resultDbl;
                        }
                        result = ScriptRuntime.wrapNumber(resultDbl);
                    }
                    frame.debuggerFrame.onExit(cx, false, result);
                }
            } catch (Throwable ex) {
                System.err.println("RHINO USAGE WARNING: onExit terminated with exception");
                ex.printStackTrace(System.err);
            }
        }
    }

    private static void setCallResult(CallFrame frame, Object callResult, double callResultDbl) {
        if (frame.savedCallOp == Token.CALL || frame.savedCallOp == Icode_CALL_ON_SUPER) {
            frame.stack[frame.savedStackTop] = callResult;
            frame.sDbl[frame.savedStackTop] = callResultDbl;
        } else if (frame.savedCallOp == Token.NEW) {
            // If construct returns scriptable,
            // then it replaces on stack top saved original instance
            // of the object.
            if (callResult instanceof Scriptable) {
                frame.stack[frame.savedStackTop] = callResult;
            }
        } else {
            Kit.codeBug();
        }
        frame.savedCallOp = 0;
    }

    public static NativeContinuation captureContinuation(Context cx) {
        if (cx.lastInterpreterFrame == null || !(cx.lastInterpreterFrame instanceof CallFrame)) {
            throw new IllegalStateException("Interpreter frames not found");
        }
        return captureContinuation(cx, (CallFrame) cx.lastInterpreterFrame, true);
    }

    private static NativeContinuation captureContinuation(
            Context cx, CallFrame frame, boolean requireContinuationsTopFrame) {
        NativeContinuation c = new NativeContinuation();
        ScriptRuntime.setObjectProtoAndParent(c, ScriptRuntime.getTopCallScope(cx));

        // Make sure that all frames are frozen
        CallFrame x = frame;
        CallFrame outermost = frame;
        while (x != null && !x.frozen) {
            x.frozen = true;
            // Allow to GC unused stack space
            for (int i = x.savedStackTop + 1; i != x.stack.length; ++i) {
                // Allow to GC unused stack space
                x.stack[i] = null;
                x.stackAttributes[i] = ScriptableObject.EMPTY;
            }
            if (x.savedCallOp == Token.CALL || x.savedCallOp == Icode_CALL_ON_SUPER) {
                // the call will always overwrite the stack top with the result
                x.stack[x.savedStackTop] = null;
            } else {
                if (x.savedCallOp != Token.NEW) Kit.codeBug();
                // the new operator uses stack top to store the constructed
                // object so it shall not be cleared: see comments in
                // setCallResult
            }
            outermost = x;
            x = x.parentFrame;
        }

        if (requireContinuationsTopFrame) {
            while (outermost.parentFrame != null) outermost = outermost.parentFrame;

            if (!outermost.isContinuationsTopFrame) {
                throw new IllegalStateException(
                        "Cannot capture continuation "
                                + "from JavaScript code not called directly by "
                                + "executeScriptWithContinuations or "
                                + "callFunctionWithContinuations");
            }
        }

        c.initImplementation(frame);
        return c;
    }

    private static int stack_int32(CallFrame frame, int i) {
        Object x = frame.stack[i];
        if (x == UniqueTag.DOUBLE_MARK) {
            return ScriptRuntime.toInt32(frame.sDbl[i]);
        }
        return ScriptRuntime.toInt32(x);
    }

    private static double stack_double(CallFrame frame, int i) {
        Object x = frame.stack[i];
        if (x != UniqueTag.DOUBLE_MARK) {
            return ScriptRuntime.toNumber(x);
        }
        return frame.sDbl[i];
    }

    private static Number stack_numeric(CallFrame frame, int i) {
        Object x = frame.stack[i];
        if (x != UniqueTag.DOUBLE_MARK) {
            return ScriptRuntime.toNumeric(x);
        }
        return frame.sDbl[i];
    }

    private static boolean stack_boolean(CallFrame frame, int i) {
        Object x = frame.stack[i];
        if (Boolean.TRUE.equals(x)) {
            return true;
        } else if (Boolean.FALSE.equals(x)) {
            return false;
        } else if (x == UniqueTag.DOUBLE_MARK) {
            double d = frame.sDbl[i];
            return !Double.isNaN(d) && d != 0.0;
        } else if (x == null || x == Undefined.instance) {
            return false;
        } else if (x instanceof BigInteger) {
            return !x.equals(BigInteger.ZERO);
        } else if (x instanceof Number) {
            double d = ((Number) x).doubleValue();
            return (!Double.isNaN(d) && d != 0.0);
        } else {
            return ScriptRuntime.toBoolean(x);
        }
    }

    private static Object[] getArgsArray(Object[] stack, double[] sDbl, int shift, int count) {
        return getArgsArray(stack, sDbl, new Object[0], 0, shift, count);
    }

    private static Object[] getArgsArray(
            Object[] stack, double[] sDbl, Object[] bound, int bCount, int shift, int count) {
        if (count == 0) {
            return ScriptRuntime.emptyArgs;
        }
        Object[] args = new Object[count];
        for (int i = 0; i < bCount; i++) {
            args[i] = bound[i];
        }

        for (int i = bCount; i != count; ++i, ++shift) {
            Object val = stack[shift];
            if (val == UniqueTag.DOUBLE_MARK) {
                val = ScriptRuntime.wrapNumber(sDbl[shift]);
            }
            args[i] = val;
        }
        return args;
    }

    private static void addInstructionCount(Context cx, CallFrame frame, int extra) {
        cx.instructionCount += frame.pc - frame.pcPrevBranch + extra;
        if (cx.instructionCount > cx.instructionThreshold) {
            cx.observeInstructionCount(cx.instructionCount);
            cx.instructionCount = 0;
        }
    }
}