blob: d27bdd953ec84333d08e47c6d61fae4b9ddeac90 [file] [log] [blame]
/*
* 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.codegen.types.Type;
import jdk.nashorn.internal.ir.Block;
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 enter a discard node we keep track of its return value status -
* i.e. should we keep it or throw it away */
private final Deque<Node> discard = new ArrayDeque<>();
/** 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;
@Override
public <T extends LexicalContextNode> T push(final T node) {
if (isDynamicScopeBoundary(node)) {
++dynamicScopeCount;
}
return super.push(node);
}
@Override
public <T extends LexicalContextNode> T pop(final T node) {
final T popped = super.pop(node);
if (isDynamicScopeBoundary(popped)) {
--dynamicScopeCount;
}
if (node instanceof Block) {
--nextFreeSlotsSize;
}
return popped;
}
private boolean isDynamicScopeBoundary(final LexicalContextNode node) {
if (node instanceof Block) {
// Block's immediate parent is a with node. Note we aren't testing for a WithNode, as that'd capture
// processing of WithNode.expression too, but it should be unaffected.
return !isEmpty() && peek() instanceof WithNode;
} else if (node instanceof FunctionNode) {
// Function has a direct eval in it (so a top-level "var ..." in the eval code can introduce a new
// variable into the function's scope), and it isn't strict (as evals in strict functions get an
// isolated scope).
return isFunctionDynamicScope((FunctionNode)node);
}
return false;
}
boolean inDynamicScope() {
return dynamicScopeCount > 0;
}
static boolean isFunctionDynamicScope(FunctionNode fn) {
return fn.hasEval() && !fn.isStrict();
}
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();
}
CompileUnit pushCompileUnit(final CompileUnit newUnit) {
compileUnits.push(newUnit);
return newUnit;
}
CompileUnit popCompileUnit(final CompileUnit oldUnit) {
assert compileUnits.peek() == oldUnit;
compileUnits.pop();
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 type the type of the variable
* @param symbol the symbol
* @param flags the callsite flags
* @return an object representing a shared scope call
*/
SharedScopeCall getScopeGet(final CompileUnit unit, final Type type, final Symbol symbol, final int flags) {
final SharedScopeCall scopeCall = new SharedScopeCall(symbol, type, type, null, flags);
if (scopeCalls.containsKey(scopeCall)) {
return scopeCalls.get(scopeCall);
}
scopeCall.setClassAndName(unit, getCurrentFunction().uniqueName("scopeCall"));
scopeCalls.put(scopeCall, scopeCall);
return scopeCall;
}
void nextFreeSlot(final Block block) {
final boolean isFunctionBody = isFunctionBody();
final int nextFreeSlot;
if (isFunctionBody) {
// On entry to function, start with slot 0
nextFreeSlot = 0;
} else {
// Otherwise, continue from previous block's first free slot
nextFreeSlot = nextFreeSlots[nextFreeSlotsSize - 1];
}
if (nextFreeSlotsSize == nextFreeSlots.length) {
final int[] newNextFreeSlots = new int[nextFreeSlotsSize * 2];
System.arraycopy(nextFreeSlots, 0, newNextFreeSlots, 0, nextFreeSlotsSize);
nextFreeSlots = newNextFreeSlots;
}
nextFreeSlots[nextFreeSlotsSize++] = assignSlots(block, nextFreeSlot);
}
private static int assignSlots(final Block block, final int firstSlot) {
int nextSlot = firstSlot;
for (final Symbol symbol : block.getSymbols()) {
if (symbol.hasSlot()) {
symbol.setSlot(nextSlot);
nextSlot += symbol.slotCount();
}
}
return nextSlot;
}
void pushDiscard(final Node node) {
discard.push(node);
}
Node popDiscard() {
return discard.pop();
}
Node getCurrentDiscard() {
return discard.peek();
}
int quickSlot(final Symbol symbol) {
final int quickSlot = nextFreeSlots[nextFreeSlotsSize - 1];
nextFreeSlots[nextFreeSlotsSize - 1] = quickSlot + symbol.slotCount();
return quickSlot;
}
}