1 /*
2  * Copyright (c) 2010, 2013, 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 
26 package jdk.nashorn.internal.codegen;
27 
28 import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX;
29 
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.List;
33 import java.util.Map;
34 import jdk.nashorn.internal.ir.Block;
35 import jdk.nashorn.internal.ir.FunctionNode;
36 import jdk.nashorn.internal.ir.LiteralNode;
37 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
38 import jdk.nashorn.internal.ir.Node;
39 import jdk.nashorn.internal.ir.ObjectNode;
40 import jdk.nashorn.internal.ir.PropertyNode;
41 import jdk.nashorn.internal.ir.SplitNode;
42 import jdk.nashorn.internal.ir.Splittable;
43 import jdk.nashorn.internal.ir.Statement;
44 import jdk.nashorn.internal.ir.VarNode;
45 import jdk.nashorn.internal.ir.visitor.SimpleNodeVisitor;
46 import jdk.nashorn.internal.runtime.Context;
47 import jdk.nashorn.internal.runtime.logging.DebugLogger;
48 import jdk.nashorn.internal.runtime.logging.Loggable;
49 import jdk.nashorn.internal.runtime.logging.Logger;
50 import jdk.nashorn.internal.runtime.options.Options;
51 
52 /**
53  * Split the IR into smaller compile units.
54  */
55 @Logger(name="splitter")
56 final class Splitter extends SimpleNodeVisitor implements Loggable {
57     /** Current compiler. */
58     private final Compiler compiler;
59 
60     /** IR to be broken down. */
61     private final FunctionNode outermost;
62 
63     /** Compile unit for the main script. */
64     private final CompileUnit outermostCompileUnit;
65 
66     /** Cache for calculated block weights. */
67     private final Map<Node, Long> weightCache = new HashMap<>();
68 
69     /** Weight threshold for when to start a split. */
70     public static final long SPLIT_THRESHOLD = Options.getIntProperty("nashorn.compiler.splitter.threshold", 32 * 1024);
71 
72     private final DebugLogger log;
73 
74     /**
75      * Constructor.
76      *
77      * @param compiler              the compiler
78      * @param functionNode          function node to split
79      * @param outermostCompileUnit  compile unit for outermost function, if non-lazy this is the script's compile unit
80      */
Splitter(final Compiler compiler, final FunctionNode functionNode, final CompileUnit outermostCompileUnit)81     public Splitter(final Compiler compiler, final FunctionNode functionNode, final CompileUnit outermostCompileUnit) {
82         this.compiler             = compiler;
83         this.outermost            = functionNode;
84         this.outermostCompileUnit = outermostCompileUnit;
85         this.log                  = initLogger(compiler.getContext());
86     }
87 
88     @Override
initLogger(final Context context)89     public DebugLogger initLogger(final Context context) {
90         return context.getLogger(this.getClass());
91     }
92 
93     @Override
getLogger()94     public DebugLogger getLogger() {
95         return log;
96     }
97 
98     /**
99      * Execute the split.
100      * @param fn the function to split
101      * @param top whether this is the topmost compiled function (it's either a program, or we're doing a recompilation).
102      */
split(final FunctionNode fn, final boolean top)103     FunctionNode split(final FunctionNode fn, final boolean top) {
104         FunctionNode functionNode = fn;
105 
106         log.fine("Initiating split of '", functionNode.getName(), "'");
107 
108         long weight = WeighNodes.weigh(functionNode);
109 
110         // We know that our LexicalContext is empty outside the call to functionNode.accept(this) below,
111         // so we can pass null to all methods expecting a LexicalContext parameter.
112         assert lc.isEmpty() : "LexicalContext not empty";
113 
114         if (weight >= SPLIT_THRESHOLD) {
115             log.info("Splitting function '", functionNode.getName(), "' as its weight ", weight, " exceeds split threshold ", SPLIT_THRESHOLD);
116             functionNode = (FunctionNode)functionNode.accept(this);
117 
118             if (functionNode.isSplit()) {
119                 // Weight has changed so weigh again, this time using block weight cache
120                 weight = WeighNodes.weigh(functionNode, weightCache);
121                 functionNode = functionNode.setBody(null, functionNode.getBody().setNeedsScope(null));
122             }
123 
124             if (weight >= SPLIT_THRESHOLD) {
125                 functionNode = functionNode.setBody(null, splitBlock(functionNode.getBody(), functionNode));
126                 functionNode = functionNode.setFlag(null, FunctionNode.IS_SPLIT);
127                 weight = WeighNodes.weigh(functionNode.getBody(), weightCache);
128             }
129         }
130 
131         assert functionNode.getCompileUnit() == null : "compile unit already set for " + functionNode.getName();
132 
133         if (top) {
134             assert outermostCompileUnit != null : "outermost compile unit is null";
135             functionNode = functionNode.setCompileUnit(null, outermostCompileUnit);
136             outermostCompileUnit.addWeight(weight + WeighNodes.FUNCTION_WEIGHT);
137         } else {
138             functionNode = functionNode.setCompileUnit(null, findUnit(weight));
139         }
140 
141         final Block body = functionNode.getBody();
142         final List<FunctionNode> dc = directChildren(functionNode);
143 
144         final Block newBody = (Block)body.accept(new SimpleNodeVisitor() {
145             @Override
146             public boolean enterFunctionNode(final FunctionNode nestedFunction) {
147                 return dc.contains(nestedFunction);
148             }
149 
150             @Override
151             public Node leaveFunctionNode(final FunctionNode nestedFunction) {
152                 final FunctionNode split = new Splitter(compiler, nestedFunction, outermostCompileUnit).split(nestedFunction, false);
153                 lc.replace(nestedFunction, split);
154                 return split;
155             }
156         });
157         functionNode = functionNode.setBody(null, newBody);
158 
159         assert functionNode.getCompileUnit() != null;
160 
161         return functionNode;
162     }
163 
directChildren(final FunctionNode functionNode)164     private static List<FunctionNode> directChildren(final FunctionNode functionNode) {
165         final List<FunctionNode> dc = new ArrayList<>();
166         functionNode.accept(new SimpleNodeVisitor() {
167             @Override
168             public boolean enterFunctionNode(final FunctionNode child) {
169                 if (child == functionNode) {
170                     return true;
171                 }
172                 if (lc.getParentFunction(child) == functionNode) {
173                     dc.add(child);
174                 }
175                 return false;
176             }
177         });
178         return dc;
179     }
180 
181     /**
182      * Override this logic to look up compile units in a different way
183      * @param weight weight needed
184      * @return compile unit
185      */
findUnit(final long weight)186     protected CompileUnit findUnit(final long weight) {
187         return compiler.findUnit(weight);
188     }
189 
190     /**
191      * Split a block into sub methods.
192      *
193      * @param block Block or function to split.
194      *
195      * @return new weight for the resulting block.
196      */
splitBlock(final Block block, final FunctionNode function)197     private Block splitBlock(final Block block, final FunctionNode function) {
198 
199         final List<Statement> splits = new ArrayList<>();
200         List<Statement> statements = new ArrayList<>();
201         long statementsWeight = 0;
202 
203         for (final Statement statement : block.getStatements()) {
204             final long weight = WeighNodes.weigh(statement, weightCache);
205             final boolean isBlockScopedVarNode = isBlockScopedVarNode(statement);
206 
207             if (statementsWeight + weight >= SPLIT_THRESHOLD || statement.isTerminal() || isBlockScopedVarNode) {
208                 if (!statements.isEmpty()) {
209                     splits.add(createBlockSplitNode(block, function, statements, statementsWeight));
210                     statements = new ArrayList<>();
211                     statementsWeight = 0;
212                 }
213             }
214 
215             if (statement.isTerminal() || isBlockScopedVarNode) {
216                 splits.add(statement);
217             } else {
218                 statements.add(statement);
219                 statementsWeight += weight;
220             }
221         }
222 
223         if (!statements.isEmpty()) {
224             splits.add(createBlockSplitNode(block, function, statements, statementsWeight));
225         }
226 
227         return block.setStatements(lc, splits);
228     }
229 
230     /**
231      * Create a new split node from statements contained in a parent block.
232      *
233      * @param parent     Parent block.
234      * @param statements Statements to include.
235      *
236      * @return New split node.
237      */
createBlockSplitNode(final Block parent, final FunctionNode function, final List<Statement> statements, final long weight)238     private SplitNode createBlockSplitNode(final Block parent, final FunctionNode function, final List<Statement> statements, final long weight) {
239         final long   token      = parent.getToken();
240         final int    finish     = parent.getFinish();
241         final String name       = function.uniqueName(SPLIT_PREFIX.symbolName());
242 
243         final Block newBlock = new Block(token, finish, statements);
244 
245         return new SplitNode(name, newBlock, compiler.findUnit(weight + WeighNodes.FUNCTION_WEIGHT));
246     }
247 
isBlockScopedVarNode(final Statement statement)248     private boolean isBlockScopedVarNode(final Statement statement) {
249         return statement instanceof VarNode && ((VarNode) statement).isBlockScoped();
250     }
251 
252     @Override
enterBlock(final Block block)253     public boolean enterBlock(final Block block) {
254         if (block.isCatchBlock()) {
255             return false;
256         }
257 
258         final long weight = WeighNodes.weigh(block, weightCache);
259 
260         if (weight < SPLIT_THRESHOLD) {
261             weightCache.put(block, weight);
262             return false;
263         }
264 
265         return true;
266     }
267 
268     @Override
leaveBlock(final Block block)269     public Node leaveBlock(final Block block) {
270         assert !block.isCatchBlock();
271 
272         Block newBlock = block;
273 
274         // Block was heavier than SLIT_THRESHOLD in enter, but a sub-block may have
275         // been split already, so weigh again before splitting.
276         long weight = WeighNodes.weigh(block, weightCache);
277         if (weight >= SPLIT_THRESHOLD) {
278             final FunctionNode currentFunction = lc.getCurrentFunction();
279             newBlock = splitBlock(block, currentFunction);
280             weight   = WeighNodes.weigh(newBlock, weightCache);
281             lc.setFlag(currentFunction, FunctionNode.IS_SPLIT);
282         }
283         weightCache.put(newBlock, weight);
284         return newBlock;
285     }
286 
287     @SuppressWarnings("rawtypes")
288     @Override
leaveLiteralNode(final LiteralNode literal)289     public Node leaveLiteralNode(final LiteralNode literal) {
290         final long weight = WeighNodes.weigh(literal);
291 
292         if (weight < SPLIT_THRESHOLD) {
293             return literal;
294         }
295 
296         final FunctionNode functionNode = lc.getCurrentFunction();
297 
298         lc.setFlag(functionNode, FunctionNode.IS_SPLIT);
299 
300         if (literal instanceof ArrayLiteralNode) {
301             final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode) literal;
302             final Node[]           value            = arrayLiteralNode.getValue();
303             final int[]            postsets         = arrayLiteralNode.getPostsets();
304             final List<Splittable.SplitRange> ranges = new ArrayList<>();
305 
306             long totalWeight = 0;
307             int  lo          = 0;
308 
309             for (int i = 0; i < postsets.length; i++) {
310                 final int  postset = postsets[i];
311                 final Node element = value[postset];
312 
313                 final long elementWeight = WeighNodes.weigh(element);
314                 totalWeight += WeighNodes.AASTORE_WEIGHT + elementWeight;
315 
316                 if (totalWeight >= SPLIT_THRESHOLD) {
317                     final CompileUnit unit = compiler.findUnit(totalWeight - elementWeight);
318                     ranges.add(new Splittable.SplitRange(unit, lo, i));
319                     lo = i;
320                     totalWeight = elementWeight;
321                 }
322             }
323 
324             if (lo != postsets.length) {
325                 final CompileUnit unit = compiler.findUnit(totalWeight);
326                 ranges.add(new Splittable.SplitRange(unit, lo, postsets.length));
327             }
328 
329             log.info("Splitting array literal in '", functionNode.getName(), "' as its weight ", weight, " exceeds split threshold ", SPLIT_THRESHOLD);
330 
331             return arrayLiteralNode.setSplitRanges(lc, ranges);
332         }
333 
334         return literal;
335     }
336 
337     @Override
leaveObjectNode(final ObjectNode objectNode)338     public Node leaveObjectNode(final ObjectNode objectNode) {
339         final long weight = WeighNodes.weigh(objectNode);
340 
341         if (weight < SPLIT_THRESHOLD) {
342             return objectNode;
343         }
344 
345         final FunctionNode functionNode = lc.getCurrentFunction();
346         lc.setFlag(functionNode, FunctionNode.IS_SPLIT);
347 
348         final List<Splittable.SplitRange> ranges        = new ArrayList<>();
349         final List<PropertyNode>          properties    = objectNode.getElements();
350         final boolean                     isSpillObject = properties.size() > CodeGenerator.OBJECT_SPILL_THRESHOLD;
351         long totalWeight = 0;
352         int  lo          = 0;
353 
354         for (int i = 0; i < properties.size(); i++) {
355 
356             final PropertyNode property = properties.get(i);
357             final boolean isConstant = LiteralNode.isConstant(property.getValue());
358 
359             if (!isConstant || !isSpillObject) {
360                 final long propertyWeight = isConstant ? 0 : WeighNodes.weigh(property.getValue());
361                 totalWeight += WeighNodes.AASTORE_WEIGHT + propertyWeight;
362 
363                 if (totalWeight >= SPLIT_THRESHOLD) {
364                     final CompileUnit unit = compiler.findUnit(totalWeight - propertyWeight);
365                     ranges.add(new Splittable.SplitRange(unit, lo, i));
366                     lo = i;
367                     totalWeight = propertyWeight;
368                 }
369             }
370         }
371 
372         if (lo != properties.size()) {
373             final CompileUnit unit = compiler.findUnit(totalWeight);
374             ranges.add(new Splittable.SplitRange(unit, lo, properties.size()));
375         }
376 
377         log.info("Splitting object node in '", functionNode.getName(), "' as its weight ", weight, " exceeds split threshold ", SPLIT_THRESHOLD);
378 
379         return objectNode.setSplitRanges(lc, ranges);
380     }
381 
382     @Override
enterFunctionNode(final FunctionNode node)383     public boolean enterFunctionNode(final FunctionNode node) {
384         //only go into the function node for this splitter. any subfunctions are rejected
385         return node == outermost;
386     }
387 }
388 
389