Block.java revision 953:221a84ef44c0
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
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    /** List of statements */
46    protected final List<Statement> statements;
47
48    /** Symbol table - keys must be returned in the order they were put in. */
49    protected final Map<String, Symbol> symbols;
50
51    /** Entry label. */
52    private final Label entryLabel;
53
54    /** Break label. */
55    private final Label breakLabel;
56
57    /** Does the block/function need a new scope? */
58    protected final int flags;
59
60    /**
61     * @see JoinPredecessor
62     */
63    private final LocalVariableConversion conversion;
64
65    /** Flag indicating that this block needs scope */
66    public static final int NEEDS_SCOPE = 1 << 0;
67
68    /**
69     * Is this block tagged as terminal based on its contents
70     * (usually the last statement)
71     */
72    public static final int IS_TERMINAL = 1 << 2;
73
74    /**
75     * Is this block the eager global scope - i.e. the original program. This isn't true for the
76     * outermost level of recompiles
77     */
78    public static final int IS_GLOBAL_SCOPE = 1 << 3;
79
80    /**
81     * Constructor
82     *
83     * @param token      token
84     * @param finish     finish
85     * @param statements statements
86     */
87    public Block(final long token, final int finish, final Statement... statements) {
88        super(token, finish);
89
90        this.statements = Arrays.asList(statements);
91        this.symbols    = new LinkedHashMap<>();
92        this.entryLabel = new Label("block_entry");
93        this.breakLabel = new Label("block_break");
94        final int len = statements.length;
95        this.flags = len > 0 && statements[len - 1].hasTerminalFlags() ? IS_TERMINAL : 0;
96        this.conversion = null;
97    }
98
99    /**
100     * Constructor
101     *
102     * @param token      token
103     * @param finish     finish
104     * @param statements statements
105     */
106    public Block(final long token, final int finish, final List<Statement> statements) {
107        this(token, finish, statements.toArray(new Statement[statements.size()]));
108    }
109
110    private Block(final Block block, final int finish, final List<Statement> statements, final int flags, final Map<String, Symbol> symbols, final LocalVariableConversion conversion) {
111        super(block);
112        this.statements = statements;
113        this.flags      = flags;
114        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
115        this.entryLabel = new Label(block.entryLabel);
116        this.breakLabel = new Label(block.breakLabel);
117        this.finish     = finish;
118        this.conversion = conversion;
119    }
120
121    /**
122     * Is this block the outermost eager global scope - i.e. the primordial program?
123     * Used for global anchor point for scope depth computation for recompilation code
124     * @return true if outermost eager global scope
125     */
126    public boolean isGlobalScope() {
127        return getFlag(IS_GLOBAL_SCOPE);
128    }
129
130    /**
131     * Clear the symbols in the block.
132     * TODO: make this immutable.
133     */
134    public void clearSymbols() {
135        symbols.clear();
136    }
137
138    @Override
139    public Node ensureUniqueLabels(final LexicalContext lc) {
140        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
141    }
142
143    /**
144     * Assist in IR navigation.
145     *
146     * @param visitor IR navigating visitor.
147     * @return new or same node
148     */
149    @Override
150    public Node accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor) {
151        if (visitor.enterBlock(this)) {
152            return visitor.leaveBlock(setStatements(lc, Node.accept(visitor, statements)));
153        }
154
155        return this;
156    }
157
158    /**
159     * Get a copy of the list for all the symbols defined in this block
160     * @return symbol iterator
161     */
162    public List<Symbol> getSymbols() {
163        return Collections.unmodifiableList(new ArrayList<>(symbols.values()));
164    }
165
166    /**
167     * Retrieves an existing symbol defined in the current block.
168     * @param name the name of the symbol
169     * @return an existing symbol with the specified name defined in the current block, or null if this block doesn't
170     * define a symbol with this name.T
171     */
172    public Symbol getExistingSymbol(final String name) {
173        return symbols.get(name);
174    }
175
176    /**
177     * Test if this block represents a <tt>catch</tt> block in a <tt>try</tt> statement.
178     * This is used by the Splitter as catch blocks are not be subject to splitting.
179     *
180     * @return true if this block represents a catch block in a try statement.
181     */
182    public boolean isCatchBlock() {
183        return statements.size() == 1 && statements.get(0) instanceof CatchNode;
184    }
185
186    @Override
187    public void toString(final StringBuilder sb, final boolean printType) {
188        for (final Node statement : statements) {
189            statement.toString(sb, printType);
190            sb.append(';');
191        }
192    }
193
194    /**
195     * Print symbols in block in alphabetical order, sorted on name
196     * Used for debugging, see the --print-symbols flag
197     *
198     * @param stream print writer to output symbols to
199     *
200     * @return true if symbols were found
201     */
202    public boolean printSymbols(final PrintWriter stream) {
203        final List<Symbol> values = new ArrayList<>(symbols.values());
204
205        Collections.sort(values, new Comparator<Symbol>() {
206            @Override
207            public int compare(final Symbol s0, final Symbol s1) {
208                return s0.getName().compareTo(s1.getName());
209            }
210        });
211
212        for (final Symbol symbol : values) {
213            symbol.print(stream);
214        }
215
216        return !values.isEmpty();
217    }
218
219    /**
220     * Tag block as terminal or non terminal
221     * @param lc          lexical context
222     * @param isTerminal is block terminal
223     * @return same block, or new if flag changed
224     */
225    public Block setIsTerminal(final LexicalContext lc, final boolean isTerminal) {
226        return isTerminal ? setFlag(lc, IS_TERMINAL) : clearFlag(lc, IS_TERMINAL);
227    }
228
229    @Override
230    public int getFlags() {
231        return flags;
232    }
233
234    /**
235     * Is this a terminal block, i.e. does it end control flow like ending with a throw or return?
236     *
237     * @return true if this node statement is terminal
238     */
239    @Override
240    public boolean isTerminal() {
241        return getFlag(IS_TERMINAL);
242    }
243
244    /**
245     * Get the entry label for this block
246     * @return the entry label
247     */
248    public Label getEntryLabel() {
249        return entryLabel;
250    }
251
252    @Override
253    public Label getBreakLabel() {
254        return breakLabel;
255    }
256
257    @Override
258    public Block setLocalVariableConversion(final LexicalContext lc, final LocalVariableConversion conversion) {
259        if(this.conversion == conversion) {
260            return this;
261        }
262        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
263    }
264
265    @Override
266    public LocalVariableConversion getLocalVariableConversion() {
267        return conversion;
268    }
269
270    /**
271     * Get the list of statements in this block
272     *
273     * @return a list of statements
274     */
275    public List<Statement> getStatements() {
276        return Collections.unmodifiableList(statements);
277    }
278
279    /**
280     * Returns the line number of the first statement in the block.
281     * @return the line number of the first statement in the block, or -1 if the block has no statements.
282     */
283    public int getFirstStatementLineNumber() {
284        if(statements == null || statements.isEmpty()) {
285            return -1;
286        }
287        return statements.get(0).getLineNumber();
288    }
289
290    /**
291     * Reset the statement list for this block
292     *
293     * @param lc lexical context
294     * @param statements new statement list
295     * @return new block if statements changed, identity of statements == block.statements
296     */
297    public Block setStatements(final LexicalContext lc, final List<Statement> statements) {
298        if (this.statements == statements) {
299            return this;
300        }
301        int lastFinish = 0;
302        if (!statements.isEmpty()) {
303            lastFinish = statements.get(statements.size() - 1).getFinish();
304        }
305        return Node.replaceInLexicalContext(lc, this, new Block(this, Math.max(finish, lastFinish), statements, flags, symbols, conversion));
306    }
307
308    /**
309     * Add or overwrite an existing symbol in the block
310     *
311     * @param lc     get lexical context
312     * @param symbol symbol
313     */
314    public void putSymbol(final LexicalContext lc, final Symbol symbol) {
315        symbols.put(symbol.getName(), symbol);
316    }
317
318    /**
319     * Check whether scope is necessary for this Block
320     *
321     * @return true if this function needs a scope
322     */
323    public boolean needsScope() {
324        return (flags & NEEDS_SCOPE) == NEEDS_SCOPE;
325    }
326
327    @Override
328    public Block setFlags(final LexicalContext lc, final int flags) {
329        if (this.flags == flags) {
330            return this;
331        }
332        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags, symbols, conversion));
333    }
334
335    @Override
336    public Block clearFlag(final LexicalContext lc, final int flag) {
337        return setFlags(lc, flags & ~flag);
338    }
339
340    @Override
341    public Block setFlag(final LexicalContext lc, final int flag) {
342        return setFlags(lc, flags | flag);
343    }
344
345    @Override
346    public boolean getFlag(final int flag) {
347        return (flags & flag) == flag;
348    }
349
350    /**
351     * Set the needs scope flag.
352     * @param lc lexicalContext
353     * @return new block if state changed, otherwise this
354     */
355    public Block setNeedsScope(final LexicalContext lc) {
356        if (needsScope()) {
357            return this;
358        }
359
360        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags | NEEDS_SCOPE, symbols, conversion));
361    }
362
363    /**
364     * Computationally determine the next slot for this block,
365     * indexed from 0. Use this as a relative base when computing
366     * frames
367     * @return next slot
368     */
369    public int nextSlot() {
370        int next = 0;
371        for (final Symbol symbol : getSymbols()) {
372            if (symbol.hasSlot()) {
373                next += symbol.slotCount();
374            }
375        }
376        return next;
377    }
378
379    @Override
380    public boolean isBreakableWithoutLabel() {
381        return false;
382    }
383
384    @Override
385    public List<Label> getLabels() {
386        return Collections.unmodifiableList(Arrays.asList(entryLabel, breakLabel));
387    }
388
389    @Override
390    public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
391        return Acceptor.accept(this, visitor);
392    }
393}
394