/*
 * Copyright (c) 2014, 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 static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.WithNode;
import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import jdk.nashorn.internal.runtime.logging.Loggable;
import jdk.nashorn.internal.runtime.logging.Logger;

Establishes depth of scope for non local symbols at the start of method. If this is a recompilation, the previous data from eager compilation is stored in the RecompilableScriptFunctionData and is transferred to the FunctionNode being compiled
/** * Establishes depth of scope for non local symbols at the start of method. * If this is a recompilation, the previous data from eager compilation is * stored in the RecompilableScriptFunctionData and is transferred to the * FunctionNode being compiled */
@Logger(name="scopedepths") final class FindScopeDepths extends SimpleNodeVisitor implements Loggable { private final Compiler compiler; private final Map<Integer, Map<Integer, RecompilableScriptFunctionData>> fnIdToNestedFunctions = new HashMap<>(); private final Map<Integer, Map<String, Integer>> externalSymbolDepths = new HashMap<>(); private final Map<Integer, Set<String>> internalSymbols = new HashMap<>(); private final Set<Block> withBodies = new HashSet<>(); private final DebugLogger log; private int dynamicScopeCount; FindScopeDepths(final Compiler compiler) { this.compiler = compiler; this.log = initLogger(compiler.getContext()); } @Override public DebugLogger getLogger() { return log; } @Override public DebugLogger initLogger(final Context context) { return context.getLogger(this.getClass()); } static int findScopesToStart(final LexicalContext lc, final FunctionNode fn, final Block block) { final Block bodyBlock = findBodyBlock(lc, fn, block); final Iterator<Block> iter = lc.getBlocks(block); Block b = iter.next(); int scopesToStart = 0; while (true) { if (b.needsScope()) { scopesToStart++; } if (b == bodyBlock) { break; } b = iter.next(); } return scopesToStart; } static int findInternalDepth(final LexicalContext lc, final FunctionNode fn, final Block block, final Symbol symbol) { final Block bodyBlock = findBodyBlock(lc, fn, block); final Iterator<Block> iter = lc.getBlocks(block); Block b = iter.next(); int scopesToStart = 0; while (true) { if (definedInBlock(b, symbol)) { return scopesToStart; } if (b.needsScope()) { scopesToStart++; } if (b == bodyBlock) { break; //don't go past body block, but process it } b = iter.next(); } return -1; } private static boolean definedInBlock(final Block block, final Symbol symbol) { if (symbol.isGlobal()) { //globals cannot be defined anywhere else return block.isGlobalScope(); } return block.getExistingSymbol(symbol.getName()) == symbol; } static Block findBodyBlock(final LexicalContext lc, final FunctionNode fn, final Block block) { final Iterator<Block> iter = lc.getBlocks(block); while (iter.hasNext()) { final Block next = iter.next(); if (fn.getBody() == next) { return next; } } return null; } private static Block findGlobalBlock(final LexicalContext lc, final Block block) { final Iterator<Block> iter = lc.getBlocks(block); Block globalBlock = null; while (iter.hasNext()) { globalBlock = iter.next(); } return globalBlock; } private static boolean isDynamicScopeBoundary(final FunctionNode fn) { return fn.needsDynamicScope(); } private boolean isDynamicScopeBoundary(final Block block) { return withBodies.contains(block); } @Override public boolean enterFunctionNode(final FunctionNode functionNode) { if (compiler.isOnDemandCompilation()) { return true; } if (isDynamicScopeBoundary(functionNode)) { increaseDynamicScopeCount(functionNode); } final int fnId = functionNode.getId(); Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.get(fnId); if (nestedFunctions == null) { nestedFunctions = new HashMap<>(); fnIdToNestedFunctions.put(fnId, nestedFunctions); } return true; } //external symbols hold the scope depth of sc11 from global at the start of the method @Override public Node leaveFunctionNode(final FunctionNode functionNode) { final String name = functionNode.getName(); FunctionNode newFunctionNode = functionNode; if (compiler.isOnDemandCompilation()) { final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(newFunctionNode.getId()); if (data.inDynamicContext()) { log.fine("Reviving scriptfunction ", quote(name), " as defined in previous (now lost) dynamic scope."); newFunctionNode = newFunctionNode.setInDynamicContext(lc); } if (newFunctionNode == lc.getOutermostFunction() && !newFunctionNode.hasApplyToCallSpecialization()) { data.setCachedAst(newFunctionNode); } return newFunctionNode; } if (inDynamicScope()) { log.fine("Tagging ", quote(name), " as defined in dynamic scope"); newFunctionNode = newFunctionNode.setInDynamicContext(lc); } //create recompilable scriptfunctiondata final int fnId = newFunctionNode.getId(); final Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.remove(fnId); assert nestedFunctions != null; // Generate the object class and property map in case this function is ever used as constructor final RecompilableScriptFunctionData data = new RecompilableScriptFunctionData( newFunctionNode, compiler.getCodeInstaller(), ObjectClassGenerator.createAllocationStrategy(newFunctionNode.getThisProperties(), compiler.getContext().useDualFields()), nestedFunctions, externalSymbolDepths.get(fnId), internalSymbols.get(fnId)); if (lc.getOutermostFunction() != newFunctionNode) { final FunctionNode parentFn = lc.getParentFunction(newFunctionNode); if (parentFn != null) { fnIdToNestedFunctions.get(parentFn.getId()).put(fnId, data); } } else { compiler.setData(data); } if (isDynamicScopeBoundary(functionNode)) { decreaseDynamicScopeCount(functionNode); } return newFunctionNode; } private boolean inDynamicScope() { return dynamicScopeCount > 0; } private void increaseDynamicScopeCount(final Node node) { assert dynamicScopeCount >= 0; ++dynamicScopeCount; if (log.isEnabled()) { log.finest(quote(lc.getCurrentFunction().getName()), " ++dynamicScopeCount = ", dynamicScopeCount, " at: ", node, node.getClass()); } } private void decreaseDynamicScopeCount(final Node node) { --dynamicScopeCount; assert dynamicScopeCount >= 0; if (log.isEnabled()) { log.finest(quote(lc.getCurrentFunction().getName()), " --dynamicScopeCount = ", dynamicScopeCount, " at: ", node, node.getClass()); } } @Override public boolean enterWithNode(final WithNode node) { withBodies.add(node.getBody()); return true; } @Override public boolean enterBlock(final Block block) { if (compiler.isOnDemandCompilation()) { return true; } if (isDynamicScopeBoundary(block)) { increaseDynamicScopeCount(block); } if (!lc.isFunctionBody()) { return true; } //the below part only happens on eager compilation when we have the entire hierarchy //block is a function body final FunctionNode fn = lc.getCurrentFunction(); //get all symbols that are referenced inside this function body final Set<Symbol> symbols = new HashSet<>(); block.accept(new SimpleNodeVisitor() { @Override public boolean enterIdentNode(final IdentNode identNode) { final Symbol symbol = identNode.getSymbol(); if (symbol != null && symbol.isScope()) { //if this is an internal symbol, skip it. symbols.add(symbol); } return true; } }); final Map<String, Integer> internals = new HashMap<>(); final Block globalBlock = findGlobalBlock(lc, block); final Block bodyBlock = findBodyBlock(lc, fn, block); assert globalBlock != null; assert bodyBlock != null; for (final Symbol symbol : symbols) { Iterator<Block> iter; final int internalDepth = findInternalDepth(lc, fn, block, symbol); final boolean internal = internalDepth >= 0; if (internal) { internals.put(symbol.getName(), internalDepth); } // if not internal, we have to continue walking until we reach the top. We // start outside the body and each new scope adds a depth count. When we // find the symbol, we store its depth count if (!internal) { int depthAtStart = 0; //not internal - keep looking. iter = lc.getAncestorBlocks(bodyBlock); while (iter.hasNext()) { final Block b2 = iter.next(); if (definedInBlock(b2, symbol)) { addExternalSymbol(fn, symbol, depthAtStart); break; } if (b2.needsScope()) { depthAtStart++; } } } } addInternalSymbols(fn, internals.keySet()); if (log.isEnabled()) { log.info(fn.getName() + " internals=" + internals + " externals=" + externalSymbolDepths.get(fn.getId())); } return true; } @Override public Node leaveBlock(final Block block) { if (compiler.isOnDemandCompilation()) { return block; } if (isDynamicScopeBoundary(block)) { decreaseDynamicScopeCount(block); } return block; } private void addInternalSymbols(final FunctionNode functionNode, final Set<String> symbols) { final int fnId = functionNode.getId(); assert internalSymbols.get(fnId) == null || internalSymbols.get(fnId).equals(symbols); //e.g. cloned finally block internalSymbols.put(fnId, symbols); } private void addExternalSymbol(final FunctionNode functionNode, final Symbol symbol, final int depthAtStart) { final int fnId = functionNode.getId(); Map<String, Integer> depths = externalSymbolDepths.get(fnId); if (depths == null) { depths = new HashMap<>(); externalSymbolDepths.put(fnId, depths); } depths.put(symbol.getName(), depthAtStart); } }