| /* |
| * 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 |
| */ |
| @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()) { |
| if (block.isGlobalScope()) { |
| return true; |
| } |
| //globals cannot be defined anywhere else |
| return false; |
| } |
| 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); |
| } |
| |
| } |