/* * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.codegen; import java.util.ArrayDeque; import java.util.Collection; import java.util.Collections; import java.util.Deque; import java.util.HashMap; import java.util.Map; import jdk.nashorn.internal.IntDeque; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.Expression; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LexicalContextNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.WithNode; /** * A lexical context that also tracks if we have any dynamic scopes in the context. Such scopes can have new * variables introduced into them at run time - a with block or a function directly containing an eval call. * Furthermore, this class keeps track of current discard state, which the current method emitter being used is, * the current compile unit, and local variable indexes */ final class CodeGeneratorLexicalContext extends LexicalContext { private int dynamicScopeCount; /** Map of shared scope call sites */ private final Map<SharedScopeCall, SharedScopeCall> scopeCalls = new HashMap<>(); /** Compile unit stack - every time we start a sub method (e.g. a split) we push one */ private final Deque<CompileUnit> compileUnits = new ArrayDeque<>(); /** Method emitter stack - every time we start a sub method (e.g. a split) we push one */ private final Deque<MethodEmitter> methodEmitters = new ArrayDeque<>(); /** The discard stack - whenever we evaluate an expression that will be discarded, we push it on this stack. Various * implementations of expression code emitter can choose to emit code that'll discard the expression themselves, or * ignore it in which case CodeGenerator.loadAndDiscard() will explicitly emit a pop instruction. */ private final Deque<Expression> discard = new ArrayDeque<>(); private final Deque<Map<String, Collection<Label>>> unwarrantedOptimismHandlers = new ArrayDeque<>(); private final Deque<StringBuilder> slotTypesDescriptors = new ArrayDeque<>(); private final IntDeque splitNodes = new IntDeque(); /** A stack tracking the next free local variable slot in the blocks. There's one entry for every block * currently on the lexical context stack. */ private int[] nextFreeSlots = new int[16]; /** size of next free slot vector */ private int nextFreeSlotsSize; private boolean isWithBoundary(final Object node) { return node instanceof Block && !isEmpty() && peek() instanceof WithNode; } @Override public <T extends LexicalContextNode> T push(final T node) { if (isWithBoundary(node)) { dynamicScopeCount++; } else if (node instanceof FunctionNode) { if (((FunctionNode)node).inDynamicContext()) { dynamicScopeCount++; } splitNodes.push(0); } return super.push(node); } void enterSplitNode() { splitNodes.getAndIncrement(); pushFreeSlots(methodEmitters.peek().getUsedSlotsWithLiveTemporaries()); } void exitSplitNode() { final int count = splitNodes.decrementAndGet(); assert count >= 0; } @Override public <T extends Node> T pop(final T node) { final T popped = super.pop(node); if (isWithBoundary(node)) { dynamicScopeCount--; assert dynamicScopeCount >= 0; } else if (node instanceof FunctionNode) { if (((FunctionNode)node).inDynamicContext()) { dynamicScopeCount--; assert dynamicScopeCount >= 0; } assert splitNodes.peek() == 0; splitNodes.pop(); } return popped; } boolean inDynamicScope() { return dynamicScopeCount > 0; } boolean inSplitNode() { return !splitNodes.isEmpty() && splitNodes.peek() > 0; } MethodEmitter pushMethodEmitter(final MethodEmitter newMethod) { methodEmitters.push(newMethod); return newMethod; } MethodEmitter popMethodEmitter(final MethodEmitter oldMethod) { assert methodEmitters.peek() == oldMethod; methodEmitters.pop(); return methodEmitters.isEmpty() ? null : methodEmitters.peek(); } void pushUnwarrantedOptimismHandlers() { unwarrantedOptimismHandlers.push(new HashMap<String, Collection<Label>>()); slotTypesDescriptors.push(new StringBuilder()); } Map<String, Collection<Label>> getUnwarrantedOptimismHandlers() { return unwarrantedOptimismHandlers.peek(); } Map<String, Collection<Label>> popUnwarrantedOptimismHandlers() { slotTypesDescriptors.pop(); return unwarrantedOptimismHandlers.pop(); } CompileUnit pushCompileUnit(final CompileUnit newUnit) { compileUnits.push(newUnit); return newUnit; } CompileUnit popCompileUnit(final CompileUnit oldUnit) { assert compileUnits.peek() == oldUnit; final CompileUnit unit = compileUnits.pop(); assert unit.hasCode() : "compile unit popped without code"; unit.setUsed(); return compileUnits.isEmpty() ? null : compileUnits.peek(); } boolean hasCompileUnits() { return !compileUnits.isEmpty(); } Collection<SharedScopeCall> getScopeCalls() { return Collections.unmodifiableCollection(scopeCalls.values()); } /** * Get a shared static method representing a dynamic scope callsite. * * @param unit current compile unit * @param symbol the symbol * @param valueType the value type of the symbol * @param returnType the return type * @param paramTypes the parameter types * @param flags the callsite flags * @return an object representing a shared scope call */ SharedScopeCall getScopeCall(final CompileUnit unit, final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, final int flags) { final SharedScopeCall scopeCall = new SharedScopeCall(symbol, valueType, returnType, paramTypes, flags); if (scopeCalls.containsKey(scopeCall)) { return scopeCalls.get(scopeCall); } scopeCall.setClassAndName(unit, getCurrentFunction().uniqueName(":scopeCall")); scopeCalls.put(scopeCall, scopeCall); return scopeCall; } /** * Get a shared static method representing a dynamic scope get access. * * @param unit current compile unit * @param symbol the symbol * @param valueType the type of the variable * @param flags the callsite flags * @return an object representing a shared scope call */ SharedScopeCall getScopeGet(final CompileUnit unit, final Symbol symbol, final Type valueType, final int flags) { return getScopeCall(unit, symbol, valueType, valueType, null, flags); } void onEnterBlock(final Block block) { pushFreeSlots(assignSlots(block, isFunctionBody() ? 0 : getUsedSlotCount())); } private void pushFreeSlots(final int freeSlots) { if (nextFreeSlotsSize == nextFreeSlots.length) { final int[] newNextFreeSlots = new int[nextFreeSlotsSize * 2]; System.arraycopy(nextFreeSlots, 0, newNextFreeSlots, 0, nextFreeSlotsSize); nextFreeSlots = newNextFreeSlots; } nextFreeSlots[nextFreeSlotsSize++] = freeSlots; } int getUsedSlotCount() { return nextFreeSlots[nextFreeSlotsSize - 1]; } void releaseSlots() { --nextFreeSlotsSize; final int undefinedFromSlot = nextFreeSlotsSize == 0 ? 0 : nextFreeSlots[nextFreeSlotsSize - 1]; if(!slotTypesDescriptors.isEmpty()) { slotTypesDescriptors.peek().setLength(undefinedFromSlot); } methodEmitters.peek().undefineLocalVariables(undefinedFromSlot, false); } private int assignSlots(final Block block, final int firstSlot) { int fromSlot = firstSlot; final MethodEmitter method = methodEmitters.peek(); for (final Symbol symbol : block.getSymbols()) { if (symbol.hasSlot()) { symbol.setFirstSlot(fromSlot); final int toSlot = fromSlot + symbol.slotCount(); method.defineBlockLocalVariable(fromSlot, toSlot); fromSlot = toSlot; } } return fromSlot; } static Type getTypeForSlotDescriptor(final char typeDesc) { // Recognizing both lowercase and uppercase as we're using both to signify symbol boundaries; see // MethodEmitter.markSymbolBoundariesInLvarTypesDescriptor(). switch (typeDesc) { case 'I': case 'i': return Type.INT; case 'J': case 'j': return Type.LONG; case 'D': case 'd': return Type.NUMBER; case 'A': case 'a': return Type.OBJECT; case 'U': case 'u': return Type.UNKNOWN; default: throw new AssertionError(); } } void pushDiscard(final Expression expr) { discard.push(expr); } boolean popDiscardIfCurrent(final Expression expr) { if (isCurrentDiscard(expr)) { discard.pop(); return true; } return false; } boolean isCurrentDiscard(final Expression expr) { return discard.peek() == expr; } int quickSlot(final Type type) { return methodEmitters.peek().defineTemporaryLocalVariable(type.getSlots()); } }