Block.java revision 1612:0da44ab8c417
1/*
2 * Copyright (c) 2010, 2016, 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.ir;
27
28import java.io.PrintWriter;
29import java.util.ArrayList;
30import java.util.Arrays;
31import java.util.Collections;
32import java.util.Comparator;
33import java.util.LinkedHashMap;
34import java.util.List;
35import java.util.Map;
36import jdk.nashorn.internal.codegen.Label;
37import jdk.nashorn.internal.ir.annotations.Immutable;
38import jdk.nashorn.internal.ir.visitor.NodeVisitor;
39
40/**
41 * IR representation for a list of statements.
42 */
43@Immutable
44public class Block extends Node implements BreakableNode, Terminal, Flags<Block> {
45    private static final long serialVersionUID = 1L;
46
47    /** List of statements */
48    protected final List<Statement> statements;
49
50    /** Symbol table - keys must be returned in the order they were put in. */
51    protected final Map<String, Symbol> symbols;
52
53    /** Entry label. */
54    private final Label entryLabel;
55
56    /** Break label. */
57    private final Label breakLabel;
58
59    /** Does the block/function need a new scope? Is this synthetic? */
60    protected final int flags;
61
62    /**
63     * @see JoinPredecessor
64     */
65    private final LocalVariableConversion conversion;
66
67    /** Flag indicating that this block needs scope */
68    public static final int NEEDS_SCOPE = 1 << 0;
69
70    /**
71     * Is this block tagged as terminal based on its contents
72     * (usually the last statement)
73     */
74    public static final int IS_TERMINAL = 1 << 2;
75
76    /**
77     * Is this block the eager global scope - i.e. the original program. This isn't true for the
78     * outermost level of recompiles
79     */
80    public static final int IS_GLOBAL_SCOPE = 1 << 3;
81
82    /**
83     * Is this block a synthetic one introduced by Parser?
84     */
85    public static final int IS_SYNTHETIC = 1 << 4;
86
87    /**
88     * Constructor
89     *
90     * @param token      The first token of the block
91     * @param finish     The index of the last character
92     * @param flags      The flags of the block
93     * @param statements All statements in the block
94     */
95    public Block(final long token, final int finish, final int flags, final Statement... statements) {
96        super(token, finish);
97
98        this.statements = Arrays.asList(statements);
99        this.symbols    = new LinkedHashMap<>();
100        this.entryLabel = new Label("block_entry");
101        this.breakLabel = new Label("block_break");
102        final int len = statements.length;
103        final int terminalFlags = len > 0 && statements[len - 1].hasTerminalFlags() ? IS_TERMINAL : 0;
104        this.flags = terminalFlags | flags;
105        this.conversion = null;
106    }
107
108    /**
109     * Constructs a new block
110     *
111     * @param token The first token of the block
112     * @param finish The index of the last character
113     * @param statements All statements in the block
114     */
115    public Block(final long token, final int finish, final Statement...statements){
116        this(token, finish, IS_SYNTHETIC, statements);
117    }
118
119    /**
120     * Constructs a new block
121     *
122     * @param token The first token of the block
123     * @param finish The index of the last character
124     * @param statements All statements in the block
125     */
126    public Block(final long token, final int finish, final List<Statement> statements){
127        this(token, finish, IS_SYNTHETIC, statements);
128    }
129
130    /**
131     * Constructor
132     *
133     * @param token      The first token of the block
134     * @param finish     The index of the last character
135     * @param flags      The flags of the block
136     * @param statements All statements in the block
137     */
138    public Block(final long token, final int finish, final int flags, final List<Statement> statements) {
139        this(token, finish, flags, statements.toArray(new Statement[0]));
140    }
141
142    private Block(final Block block, final int finish, final List<Statement> statements, final int flags, final Map<String, Symbol> symbols, final LocalVariableConversion conversion) {
143        super(block, finish);
144        this.statements = statements;
145        this.flags      = flags;
146        this.symbols    = new LinkedHashMap<>(symbols); //todo - symbols have no dependencies on any IR node and can as far as we understand it be shallow copied now
147        this.entryLabel = new Label(block.entryLabel);
148        this.breakLabel = new Label(block.breakLabel);
149        this.conversion = conversion;
150    }
151
152    /**
153     * Is this block the outermost eager global scope - i.e. the primordial program?
154     * Used for global anchor point for scope depth computation for recompilation code
155     * @return true if outermost eager global scope
156     */
157    public boolean isGlobalScope() {
158        return getFlag(IS_GLOBAL_SCOPE);
159    }
160
161    /**
162     * Returns true if this block defines any symbols.
163     * @return true if this block defines any symbols.
164     */
165    public boolean hasSymbols() {
166        return !symbols.isEmpty();
167    }
168
169    /**
170     * Replaces symbols defined in this block with different symbols. Used to ensure symbol tables are
171     * immutable upon construction and have copy-on-write semantics. Note that this method only replaces the
172     * symbols in the symbol table, it does not act on any contained AST nodes that might reference the symbols.
173     * Those should be updated separately as this method is meant to be used as part of such an update pass.
174     * @param lc the current lexical context
175     * @param replacements the map of symbol replacements
176     * @return a new block with replaced symbols, or this block if none of the replacements modified the symbol
177     * table.
178     */
179    public Block replaceSymbols(final LexicalContext lc, final Map<Symbol, Symbol> replacements) {
180        if (symbols.isEmpty()) {
181            return this;
182        }
183        final LinkedHashMap<String, Symbol> newSymbols = new LinkedHashMap<>(symbols);
184        for (final Map.Entry<String, Symbol> entry: newSymbols.entrySet()) {
185            final Symbol newSymbol = replacements.get(entry.getValue());
186            assert newSymbol != null : "Missing replacement for " + entry.getKey();
187            entry.setValue(newSymbol);
188        }
189        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, newSymbols, conversion));
190    }
191
192    /**
193     * Returns a copy of this block with a shallow copy of the symbol table.
194     * @return a copy of this block with a shallow copy of the symbol table.
195     */
196    public Block copyWithNewSymbols() {
197        return new Block(this, finish, statements, flags, new LinkedHashMap<>(symbols), conversion);
198    }
199
200    @Override
201    public Node ensureUniqueLabels(final LexicalContext lc) {
202        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
203    }
204
205    /**
206     * Assist in IR navigation.
207     *
208     * @param visitor IR navigating visitor.
209     * @return new or same node
210     */
211    @Override
212    public Node accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor) {
213        if (visitor.enterBlock(this)) {
214            return visitor.leaveBlock(setStatements(lc, Node.accept(visitor, statements)));
215        }
216
217        return this;
218    }
219
220    /**
221     * Get a copy of the list for all the symbols defined in this block
222     * @return symbol iterator
223     */
224    public List<Symbol> getSymbols() {
225        return symbols.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(symbols.values()));
226    }
227
228    /**
229     * Retrieves an existing symbol defined in the current block.
230     * @param name the name of the symbol
231     * @return an existing symbol with the specified name defined in the current block, or null if this block doesn't
232     * define a symbol with this name.T
233     */
234    public Symbol getExistingSymbol(final String name) {
235        return symbols.get(name);
236    }
237
238    /**
239     * Test if this block represents a <tt>catch</tt> block in a <tt>try</tt> statement.
240     * This is used by the Splitter as catch blocks are not be subject to splitting.
241     *
242     * @return true if this block represents a catch block in a try statement.
243     */
244    public boolean isCatchBlock() {
245        return statements.size() == 1 && statements.get(0) instanceof CatchNode;
246    }
247
248    @Override
249    public void toString(final StringBuilder sb, final boolean printType) {
250        for (final Node statement : statements) {
251            statement.toString(sb, printType);
252            sb.append(';');
253        }
254    }
255
256    /**
257     * Print symbols in block in alphabetical order, sorted on name
258     * Used for debugging, see the --print-symbols flag
259     *
260     * @param stream print writer to output symbols to
261     *
262     * @return true if symbols were found
263     */
264    public boolean printSymbols(final PrintWriter stream) {
265        final List<Symbol> values = new ArrayList<>(symbols.values());
266
267        Collections.sort(values, new Comparator<Symbol>() {
268            @Override
269            public int compare(final Symbol s0, final Symbol s1) {
270                return s0.getName().compareTo(s1.getName());
271            }
272        });
273
274        for (final Symbol symbol : values) {
275            symbol.print(stream);
276        }
277
278        return !values.isEmpty();
279    }
280
281    /**
282     * Tag block as terminal or non terminal
283     * @param lc          lexical context
284     * @param isTerminal is block terminal
285     * @return same block, or new if flag changed
286     */
287    public Block setIsTerminal(final LexicalContext lc, final boolean isTerminal) {
288        return isTerminal ? setFlag(lc, IS_TERMINAL) : clearFlag(lc, IS_TERMINAL);
289    }
290
291    @Override
292    public int getFlags() {
293        return flags;
294    }
295
296    /**
297     * Is this a terminal block, i.e. does it end control flow like ending with a throw or return?
298     *
299     * @return true if this node statement is terminal
300     */
301    @Override
302    public boolean isTerminal() {
303        return getFlag(IS_TERMINAL);
304    }
305
306    /**
307     * Get the entry label for this block
308     * @return the entry label
309     */
310    public Label getEntryLabel() {
311        return entryLabel;
312    }
313
314    @Override
315    public Label getBreakLabel() {
316        return breakLabel;
317    }
318
319    @Override
320    public Block setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
321        if(this.conversion == conversion) {
322            return this;
323        }
324        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
325    }
326
327    @Override
328    public LocalVariableConversion getLocalVariableConversion() {
329        return conversion;
330    }
331
332    /**
333     * Get the list of statements in this block
334     *
335     * @return a list of statements
336     */
337    public List<Statement> getStatements() {
338        return Collections.unmodifiableList(statements);
339    }
340
341    /**
342     * Returns the number of statements in the block.
343     * @return the number of statements in the block.
344     */
345    public int getStatementCount() {
346        return statements.size();
347    }
348
349    /**
350     * Returns the line number of the first statement in the block.
351     * @return the line number of the first statement in the block, or -1 if the block has no statements.
352     */
353    public int getFirstStatementLineNumber() {
354        if(statements == null || statements.isEmpty()) {
355            return -1;
356        }
357        return statements.get(0).getLineNumber();
358    }
359
360    /**
361     * Returns the last statement in the block.
362     * @return the last statement in the block, or null if the block has no statements.
363     */
364    public Statement getLastStatement() {
365        return statements.isEmpty() ? null : statements.get(statements.size() - 1);
366    }
367
368    /**
369     * Reset the statement list for this block
370     *
371     * @param lc lexical context
372     * @param statements new statement list
373     * @return new block if statements changed, identity of statements == block.statements
374     */
375    public Block setStatements(final LexicalContext lc, final List<Statement> statements) {
376        if (this.statements == statements) {
377            return this;
378        }
379        int lastFinish = 0;
380        if (!statements.isEmpty()) {
381            lastFinish = statements.get(statements.size() - 1).getFinish();
382        }
383        return Node.replaceInLexicalContext(lc, this, new Block(this, Math.max(finish, lastFinish), statements, flags, symbols, conversion));
384    }
385
386    /**
387     * Add or overwrite an existing symbol in the block
388     *
389     * @param symbol symbol
390     */
391    public void putSymbol(final Symbol symbol) {
392        symbols.put(symbol.getName(), symbol);
393    }
394
395    /**
396     * Check whether scope is necessary for this Block
397     *
398     * @return true if this function needs a scope
399     */
400    public boolean needsScope() {
401        return (flags & NEEDS_SCOPE) == NEEDS_SCOPE;
402    }
403
404    /**
405     * Check whether this block is synthetic or not.
406     *
407     * @return true if this is a synthetic block
408     */
409    public boolean isSynthetic() {
410        return (flags & IS_SYNTHETIC) == IS_SYNTHETIC;
411    }
412
413    @Override
414    public Block setFlags(final LexicalContext lc, final int flags) {
415        if (this.flags == flags) {
416            return this;
417        }
418        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
419    }
420
421    @Override
422    public Block clearFlag(final LexicalContext lc, final int flag) {
423        return setFlags(lc, flags & ~flag);
424    }
425
426    @Override
427    public Block setFlag(final LexicalContext lc, final int flag) {
428        return setFlags(lc, flags | flag);
429    }
430
431    @Override
432    public boolean getFlag(final int flag) {
433        return (flags & flag) == flag;
434    }
435
436    /**
437     * Set the needs scope flag.
438     * @param lc lexicalContext
439     * @return new block if state changed, otherwise this
440     */
441    public Block setNeedsScope(final LexicalContext lc) {
442        if (needsScope()) {
443            return this;
444        }
445
446        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags | NEEDS_SCOPE, symbols, conversion));
447    }
448
449    /**
450     * Computationally determine the next slot for this block,
451     * indexed from 0. Use this as a relative base when computing
452     * frames
453     * @return next slot
454     */
455    public int nextSlot() {
456        int next = 0;
457        for (final Symbol symbol : getSymbols()) {
458            if (symbol.hasSlot()) {
459                next += symbol.slotCount();
460            }
461        }
462        return next;
463    }
464
465    @Override
466    public boolean isBreakableWithoutLabel() {
467        return false;
468    }
469
470    @Override
471    public List<Label> getLabels() {
472        return Collections.unmodifiableList(Arrays.asList(entryLabel, breakLabel));
473    }
474
475    @Override
476    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
477        return Acceptor.accept(this, visitor);
478    }
479}
480