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;
final class CodeGeneratorLexicalContext extends LexicalContext {
private int dynamicScopeCount;
private final Map<SharedScopeCall, SharedScopeCall> scopeCalls = new HashMap<>();
private final Deque<CompileUnit> compileUnits = new ArrayDeque<>();
private final Deque<MethodEmitter> methodEmitters = new ArrayDeque<>();
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();
private int[] nextFreeSlots = new int[16];
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());
}
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;
}
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) {
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());
}
}