1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.codegen;
  27 
  28 import java.util.ArrayDeque;
  29 import java.util.Collection;
  30 import java.util.Collections;
  31 import java.util.Deque;
  32 import java.util.HashMap;
  33 import java.util.Map;
  34 
  35 import jdk.nashorn.internal.codegen.types.Type;
  36 import jdk.nashorn.internal.ir.Block;
  37 import jdk.nashorn.internal.ir.FunctionNode;
  38 import jdk.nashorn.internal.ir.LexicalContext;
  39 import jdk.nashorn.internal.ir.LexicalContextNode;
  40 import jdk.nashorn.internal.ir.Node;
  41 import jdk.nashorn.internal.ir.Symbol;
  42 import jdk.nashorn.internal.ir.WithNode;
  43 
  44 /**
  45  * A lexical context that also tracks if we have any dynamic scopes in the context. Such scopes can have new
  46  * variables introduced into them at run time - a with block or a function directly containing an eval call.
  47  * Furthermore, this class keeps track of current discard state, which the current method emitter being used is,
  48  * the current compile unit, and local variable indexes
  49  */
  50 final class CodeGeneratorLexicalContext extends LexicalContext {
  51     private int dynamicScopeCount;
  52 
  53     /** Map of shared scope call sites */
  54     private final Map<SharedScopeCall, SharedScopeCall> scopeCalls = new HashMap<>();
  55 
  56     /** Compile unit stack - every time we start a sub method (e.g. a split) we push one */
  57     private final Deque<CompileUnit> compileUnits = new ArrayDeque<>();
  58 
  59     /** Method emitter stack - every time we start a sub method (e.g. a split) we push one */
  60     private final Deque<MethodEmitter> methodEmitters = new ArrayDeque<>();
  61 
  62     /** The discard stack - whenever we enter a discard node we keep track of its return value status -
  63      *  i.e. should we keep it or throw it away */
  64     private final Deque<Node> discard = new ArrayDeque<>();
  65 
  66     /** A stack tracking the next free local variable slot in the blocks. There's one entry for every block
  67      *  currently on the lexical context stack. */
  68     private int[] nextFreeSlots = new int[16];
  69 
  70     /** size of next free slot vector */
  71     private int nextFreeSlotsSize;
  72 
  73     @Override
  74     public <T extends LexicalContextNode> T push(final T node) {
  75         if (isDynamicScopeBoundary(node)) {
  76             ++dynamicScopeCount;
  77         }
  78         return super.push(node);
  79     }
  80 
  81     @Override
  82     public <T extends LexicalContextNode> T pop(final T node) {
  83         final T popped = super.pop(node);
  84         if (isDynamicScopeBoundary(popped)) {
  85             --dynamicScopeCount;
  86         }
  87         if (node instanceof Block) {
  88             --nextFreeSlotsSize;
  89         }
  90         return popped;
  91     }
  92 
  93     private boolean isDynamicScopeBoundary(final LexicalContextNode node) {
  94         if (node instanceof Block) {
  95             // Block's immediate parent is a with node. Note we aren't testing for a WithNode, as that'd capture
  96             // processing of WithNode.expression too, but it should be unaffected.
  97             return !isEmpty() && peek() instanceof WithNode;
  98         } else if (node instanceof FunctionNode) {
  99             // Function has a direct eval in it (so a top-level "var ..." in the eval code can introduce a new
 100             // variable into the function's scope), and it isn't strict (as evals in strict functions get an
 101             // isolated scope).
 102             return isFunctionDynamicScope((FunctionNode)node);
 103         }
 104         return false;
 105     }
 106 
 107     boolean inDynamicScope() {
 108         return dynamicScopeCount > 0;
 109     }
 110 
 111     static boolean isFunctionDynamicScope(FunctionNode fn) {
 112         return fn.hasEval() && !fn.isStrict();
 113     }
 114 
 115     MethodEmitter pushMethodEmitter(final MethodEmitter newMethod) {
 116         methodEmitters.push(newMethod);
 117         return newMethod;
 118     }
 119 
 120     MethodEmitter popMethodEmitter(final MethodEmitter oldMethod) {
 121         assert methodEmitters.peek() == oldMethod;
 122         methodEmitters.pop();
 123         return methodEmitters.isEmpty() ? null : methodEmitters.peek();
 124     }
 125 
 126     CompileUnit pushCompileUnit(final CompileUnit newUnit) {
 127         compileUnits.push(newUnit);
 128         return newUnit;
 129     }
 130 
 131     CompileUnit popCompileUnit(final CompileUnit oldUnit) {
 132         assert compileUnits.peek() == oldUnit;
 133         compileUnits.pop();
 134         return compileUnits.isEmpty() ? null : compileUnits.peek();
 135     }
 136 
 137     boolean hasCompileUnits() {
 138         return !compileUnits.isEmpty();
 139     }
 140 
 141     Collection<SharedScopeCall> getScopeCalls() {
 142         return Collections.unmodifiableCollection(scopeCalls.values());
 143     }
 144 
 145     /**
 146      * Get a shared static method representing a dynamic scope callsite.
 147      *
 148      * @param unit current compile unit
 149      * @param symbol the symbol
 150      * @param valueType the value type of the symbol
 151      * @param returnType the return type
 152      * @param paramTypes the parameter types
 153      * @param flags the callsite flags
 154      * @return an object representing a shared scope call
 155      */
 156     SharedScopeCall getScopeCall(final CompileUnit unit, final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, final int flags) {
 157         final SharedScopeCall scopeCall = new SharedScopeCall(symbol, valueType, returnType, paramTypes, flags);
 158         if (scopeCalls.containsKey(scopeCall)) {
 159             return scopeCalls.get(scopeCall);
 160         }
 161         scopeCall.setClassAndName(unit, getCurrentFunction().uniqueName(":scopeCall"));
 162         scopeCalls.put(scopeCall, scopeCall);
 163         return scopeCall;
 164     }
 165 
 166     /**
 167      * Get a shared static method representing a dynamic scope get access.
 168      *
 169      * @param unit current compile unit
 170      * @param type the type of the variable
 171      * @param symbol the symbol
 172      * @param flags the callsite flags
 173      * @return an object representing a shared scope call
 174      */
 175     SharedScopeCall getScopeGet(final CompileUnit unit, final Type type, final Symbol symbol, final int flags) {
 176         final SharedScopeCall scopeCall = new SharedScopeCall(symbol, type, type, null, flags);
 177         if (scopeCalls.containsKey(scopeCall)) {
 178             return scopeCalls.get(scopeCall);
 179         }
 180         scopeCall.setClassAndName(unit, getCurrentFunction().uniqueName(":scopeCall"));
 181         scopeCalls.put(scopeCall, scopeCall);
 182         return scopeCall;
 183     }
 184 
 185 
 186     void nextFreeSlot(final Block block) {
 187         final boolean isFunctionBody = isFunctionBody();
 188 
 189         final int nextFreeSlot;
 190         if (isFunctionBody) {
 191             // On entry to function, start with slot 0
 192             nextFreeSlot = 0;
 193         } else {
 194             // Otherwise, continue from previous block's first free slot
 195             nextFreeSlot = nextFreeSlots[nextFreeSlotsSize - 1];
 196         }
 197         if (nextFreeSlotsSize == nextFreeSlots.length) {
 198             final int[] newNextFreeSlots = new int[nextFreeSlotsSize * 2];
 199             System.arraycopy(nextFreeSlots, 0, newNextFreeSlots, 0, nextFreeSlotsSize);
 200             nextFreeSlots = newNextFreeSlots;
 201         }
 202         nextFreeSlots[nextFreeSlotsSize++] = assignSlots(block, nextFreeSlot);
 203     }
 204 
 205     private static int assignSlots(final Block block, final int firstSlot) {
 206         int nextSlot = firstSlot;
 207         for (final Symbol symbol : block.getSymbols()) {
 208             if (symbol.hasSlot()) {
 209                 symbol.setSlot(nextSlot);
 210                 nextSlot += symbol.slotCount();
 211             }
 212         }
 213         return nextSlot;
 214     }
 215 
 216     void pushDiscard(final Node node) {
 217         discard.push(node);
 218     }
 219 
 220     Node popDiscard() {
 221         return discard.pop();
 222     }
 223 
 224     Node getCurrentDiscard() {
 225         return discard.peek();
 226     }
 227 
 228     int quickSlot(final Symbol symbol) {
 229         final int quickSlot = nextFreeSlots[nextFreeSlotsSize - 1];
 230         nextFreeSlots[nextFreeSlotsSize - 1] = quickSlot + symbol.slotCount();
 231         return quickSlot;
 232     }
 233 
 234 }
 235