FindScopeDepths.java revision 1426:751ada854e5a
1/*
2 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.codegen;
27
28import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
29
30import java.util.HashMap;
31import java.util.HashSet;
32import java.util.Iterator;
33import java.util.Map;
34import java.util.Set;
35import jdk.nashorn.internal.ir.Block;
36import jdk.nashorn.internal.ir.FunctionNode;
37import jdk.nashorn.internal.ir.IdentNode;
38import jdk.nashorn.internal.ir.LexicalContext;
39import jdk.nashorn.internal.ir.Node;
40import jdk.nashorn.internal.ir.Symbol;
41import jdk.nashorn.internal.ir.WithNode;
42import jdk.nashorn.internal.ir.visitor.NodeVisitor;
43import jdk.nashorn.internal.runtime.Context;
44import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
45import jdk.nashorn.internal.runtime.logging.DebugLogger;
46import jdk.nashorn.internal.runtime.logging.Loggable;
47import jdk.nashorn.internal.runtime.logging.Logger;
48
49/**
50 * Establishes depth of scope for non local symbols at the start of method.
51 * If this is a recompilation, the previous data from eager compilation is
52 * stored in the RecompilableScriptFunctionData and is transferred to the
53 * FunctionNode being compiled
54 */
55@Logger(name="scopedepths")
56final class FindScopeDepths extends NodeVisitor<LexicalContext> implements Loggable {
57
58    private final Compiler compiler;
59    private final Map<Integer, Map<Integer, RecompilableScriptFunctionData>> fnIdToNestedFunctions = new HashMap<>();
60    private final Map<Integer, Map<String, Integer>> externalSymbolDepths = new HashMap<>();
61    private final Map<Integer, Set<String>> internalSymbols = new HashMap<>();
62    private final Set<Block> withBodies = new HashSet<>();
63
64    private final DebugLogger log;
65
66    private int dynamicScopeCount;
67
68    FindScopeDepths(final Compiler compiler) {
69        super(new LexicalContext());
70        this.compiler = compiler;
71        this.log      = initLogger(compiler.getContext());
72    }
73
74    @Override
75    public DebugLogger getLogger() {
76        return log;
77    }
78
79    @Override
80    public DebugLogger initLogger(final Context context) {
81        return context.getLogger(this.getClass());
82    }
83
84    static int findScopesToStart(final LexicalContext lc, final FunctionNode fn, final Block block) {
85        final Block bodyBlock = findBodyBlock(lc, fn, block);
86        final Iterator<Block> iter = lc.getBlocks(block);
87        Block b = iter.next();
88        int scopesToStart = 0;
89        while (true) {
90            if (b.needsScope()) {
91                scopesToStart++;
92            }
93            if (b == bodyBlock) {
94                break;
95            }
96            b = iter.next();
97        }
98        return scopesToStart;
99    }
100
101    static int findInternalDepth(final LexicalContext lc, final FunctionNode fn, final Block block, final Symbol symbol) {
102        final Block bodyBlock = findBodyBlock(lc, fn, block);
103        final Iterator<Block> iter = lc.getBlocks(block);
104        Block b = iter.next();
105        int scopesToStart = 0;
106        while (true) {
107            if (definedInBlock(b, symbol)) {
108                return scopesToStart;
109            }
110            if (b.needsScope()) {
111                scopesToStart++;
112            }
113            if (b == bodyBlock) {
114                break; //don't go past body block, but process it
115            }
116            b = iter.next();
117        }
118        return -1;
119    }
120
121    private static boolean definedInBlock(final Block block, final Symbol symbol) {
122        if (symbol.isGlobal()) {
123            //globals cannot be defined anywhere else
124
125            return block.isGlobalScope();
126        }
127        return block.getExistingSymbol(symbol.getName()) == symbol;
128    }
129
130    static Block findBodyBlock(final LexicalContext lc, final FunctionNode fn, final Block block) {
131        final Iterator<Block> iter = lc.getBlocks(block);
132        while (iter.hasNext()) {
133            final Block next = iter.next();
134            if (fn.getBody() == next) {
135                return next;
136            }
137        }
138        return null;
139    }
140
141    private static Block findGlobalBlock(final LexicalContext lc, final Block block) {
142        final Iterator<Block> iter = lc.getBlocks(block);
143        Block globalBlock = null;
144        while (iter.hasNext()) {
145            globalBlock = iter.next();
146        }
147        return globalBlock;
148    }
149
150    private static boolean isDynamicScopeBoundary(final FunctionNode fn) {
151        return fn.needsDynamicScope();
152    }
153
154    private boolean isDynamicScopeBoundary(final Block block) {
155        return withBodies.contains(block);
156    }
157
158    @Override
159    public boolean enterFunctionNode(final FunctionNode functionNode) {
160        if (compiler.isOnDemandCompilation()) {
161            return true;
162        }
163
164        if (isDynamicScopeBoundary(functionNode)) {
165            increaseDynamicScopeCount(functionNode);
166        }
167
168        final int fnId = functionNode.getId();
169        Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.get(fnId);
170        if (nestedFunctions == null) {
171            nestedFunctions = new HashMap<>();
172            fnIdToNestedFunctions.put(fnId, nestedFunctions);
173        }
174
175        return true;
176    }
177
178    //external symbols hold the scope depth of sc11 from global at the start of the method
179    @Override
180    public Node leaveFunctionNode(final FunctionNode functionNode) {
181        final String name = functionNode.getName();
182        FunctionNode newFunctionNode = functionNode;
183        if (compiler.isOnDemandCompilation()) {
184            final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(newFunctionNode.getId());
185            if (data.inDynamicContext()) {
186                log.fine("Reviving scriptfunction ", quote(name), " as defined in previous (now lost) dynamic scope.");
187                newFunctionNode = newFunctionNode.setInDynamicContext(lc);
188            }
189            if (newFunctionNode == lc.getOutermostFunction() && !newFunctionNode.hasApplyToCallSpecialization()) {
190                data.setCachedAst(newFunctionNode);
191            }
192            return newFunctionNode;
193        }
194
195        if (inDynamicScope()) {
196            log.fine("Tagging ", quote(name), " as defined in dynamic scope");
197            newFunctionNode = newFunctionNode.setInDynamicContext(lc);
198        }
199
200        //create recompilable scriptfunctiondata
201        final int fnId = newFunctionNode.getId();
202        final Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.remove(fnId);
203
204        assert nestedFunctions != null;
205        // Generate the object class and property map in case this function is ever used as constructor
206        final RecompilableScriptFunctionData data = new RecompilableScriptFunctionData(
207                newFunctionNode,
208                compiler.getCodeInstaller(),
209                ObjectClassGenerator.createAllocationStrategy(newFunctionNode.getThisProperties(), compiler.getContext().useDualFields()),
210                nestedFunctions,
211                externalSymbolDepths.get(fnId),
212                internalSymbols.get(fnId));
213
214        if (lc.getOutermostFunction() != newFunctionNode) {
215            final FunctionNode parentFn = lc.getParentFunction(newFunctionNode);
216            if (parentFn != null) {
217                fnIdToNestedFunctions.get(parentFn.getId()).put(fnId, data);
218            }
219        } else {
220            compiler.setData(data);
221        }
222
223        if (isDynamicScopeBoundary(functionNode)) {
224            decreaseDynamicScopeCount(functionNode);
225        }
226
227        return newFunctionNode;
228    }
229
230    private boolean inDynamicScope() {
231        return dynamicScopeCount > 0;
232    }
233
234    private void increaseDynamicScopeCount(final Node node) {
235        assert dynamicScopeCount >= 0;
236        ++dynamicScopeCount;
237        if (log.isEnabled()) {
238            log.finest(quote(lc.getCurrentFunction().getName()), " ++dynamicScopeCount = ", dynamicScopeCount, " at: ", node, node.getClass());
239        }
240    }
241
242    private void decreaseDynamicScopeCount(final Node node) {
243        --dynamicScopeCount;
244        assert dynamicScopeCount >= 0;
245        if (log.isEnabled()) {
246            log.finest(quote(lc.getCurrentFunction().getName()), " --dynamicScopeCount = ", dynamicScopeCount, " at: ", node, node.getClass());
247        }
248    }
249
250    @Override
251    public boolean enterWithNode(final WithNode node) {
252        withBodies.add(node.getBody());
253        return true;
254    }
255
256    @Override
257    public boolean enterBlock(final Block block) {
258        if (compiler.isOnDemandCompilation()) {
259            return true;
260        }
261
262        if (isDynamicScopeBoundary(block)) {
263            increaseDynamicScopeCount(block);
264        }
265
266        if (!lc.isFunctionBody()) {
267            return true;
268        }
269
270        //the below part only happens on eager compilation when we have the entire hierarchy
271        //block is a function body
272        final FunctionNode fn = lc.getCurrentFunction();
273
274        //get all symbols that are referenced inside this function body
275        final Set<Symbol> symbols = new HashSet<>();
276        block.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
277            @Override
278            public final boolean enterDefault(final Node node) {
279                if (!compiler.isOnDemandCompilation()) {
280                    if (node instanceof IdentNode) {
281                        final Symbol symbol = ((IdentNode)node).getSymbol();
282                        if (symbol != null && symbol.isScope()) {
283                            //if this is an internal symbol, skip it.
284                            symbols.add(symbol);
285                        }
286                    }
287                }
288                return true;
289            }
290        });
291
292        final Map<String, Integer> internals = new HashMap<>();
293
294        final Block globalBlock = findGlobalBlock(lc, block);
295        final Block bodyBlock   = findBodyBlock(lc, fn, block);
296
297        assert globalBlock != null;
298        assert bodyBlock   != null;
299
300        for (final Symbol symbol : symbols) {
301            Iterator<Block> iter;
302
303            final int internalDepth = findInternalDepth(lc, fn, block, symbol);
304            final boolean internal = internalDepth >= 0;
305            if (internal) {
306                internals.put(symbol.getName(), internalDepth);
307            }
308
309            // if not internal, we have to continue walking until we reach the top. We
310            // start outside the body and each new scope adds a depth count. When we
311            // find the symbol, we store its depth count
312            if (!internal) {
313                int depthAtStart = 0;
314                //not internal - keep looking.
315                iter = lc.getAncestorBlocks(bodyBlock);
316                while (iter.hasNext()) {
317                    final Block b2 = iter.next();
318                    if (definedInBlock(b2, symbol)) {
319                        addExternalSymbol(fn, symbol, depthAtStart);
320                        break;
321                    }
322                    if (b2.needsScope()) {
323                        depthAtStart++;
324                    }
325                }
326            }
327        }
328
329        addInternalSymbols(fn, internals.keySet());
330
331        if (log.isEnabled()) {
332            log.info(fn.getName() + " internals=" + internals + " externals=" + externalSymbolDepths.get(fn.getId()));
333        }
334
335        return true;
336    }
337
338    @Override
339    public Node leaveBlock(final Block block) {
340        if (compiler.isOnDemandCompilation()) {
341            return block;
342        }
343        if (isDynamicScopeBoundary(block)) {
344            decreaseDynamicScopeCount(block);
345        }
346        return block;
347    }
348
349    private void addInternalSymbols(final FunctionNode functionNode, final Set<String> symbols) {
350        final int fnId = functionNode.getId();
351        assert internalSymbols.get(fnId) == null || internalSymbols.get(fnId).equals(symbols); //e.g. cloned finally block
352        internalSymbols.put(fnId, symbols);
353    }
354
355    private void addExternalSymbol(final FunctionNode functionNode, final Symbol symbol, final int depthAtStart) {
356        final int fnId = functionNode.getId();
357        Map<String, Integer> depths = externalSymbolDepths.get(fnId);
358        if (depths == null) {
359            depths = new HashMap<>();
360            externalSymbolDepths.put(fnId, depths);
361        }
362        depths.put(symbol.getName(), depthAtStart);
363    }
364
365}
366