Symbol.java revision 1002:2f0161551858
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.HashSet;
30import java.util.Set;
31import java.util.StringTokenizer;
32import jdk.nashorn.internal.codegen.types.Type;
33import jdk.nashorn.internal.runtime.Context;
34import jdk.nashorn.internal.runtime.Debug;
35import jdk.nashorn.internal.runtime.options.Options;
36
37/**
38 * Symbol is a symbolic address for a value ("variable" if you wish). Identifiers in JavaScript source, as well as
39 * certain synthetic variables created by the compiler are represented by Symbol objects. Symbols can address either
40 * local variable slots in bytecode ("slotted symbol"), or properties in scope objects ("scoped symbol"). A symbol can
41 * also end up being defined but then not used during symbol assignment calculations; such symbol will be neither
42 * scoped, nor slotted; it represents a dead variable (it might be written to, but is never read). Finally, a symbol can
43 * be both slotted and in scope. This special case can only occur with bytecode method parameters. They all come in as
44 * slotted, but if they are used by a nested function (or eval) then they will be copied into the scope object, and used
45 * from there onwards. Two further special cases are parameters stored in {@code NativeArguments} objects and parameters
46 * stored in {@code Object[]} parameter to variable-arity functions. Those use the {@code #getFieldIndex()} property to
47 * refer to their location.
48 */
49
50public final class Symbol implements Comparable<Symbol> {
51    /** Is this Global */
52    public static final int IS_GLOBAL   = 1;
53    /** Is this a variable */
54    public static final int IS_VAR      = 2;
55    /** Is this a parameter */
56    public static final int IS_PARAM    = 3;
57    /** Mask for kind flags */
58    public static final int KINDMASK = (1 << 2) - 1; // Kinds are represented by lower two bits
59
60    /** Is this symbol in scope */
61    public static final int IS_SCOPE                = 1 <<  2;
62    /** Is this a this symbol */
63    public static final int IS_THIS                 = 1 <<  3;
64    /** Is this a let */
65    public static final int IS_LET                  = 1 <<  4;
66    /** Is this a const */
67    public static final int IS_CONST                = 1 <<  5;
68    /** Is this an internal symbol, never represented explicitly in source code */
69    public static final int IS_INTERNAL             = 1 <<  6;
70    /** Is this a function self-reference symbol */
71    public static final int IS_FUNCTION_SELF        = 1 <<  7;
72    /** Is this a function declaration? */
73    public static final int IS_FUNCTION_DECLARATION = 1 <<  8;
74    /** Is this a program level symbol? */
75    public static final int IS_PROGRAM_LEVEL        = 1 <<  9;
76    /** Are this symbols' values stored in local variable slots? */
77    public static final int HAS_SLOT                = 1 << 10;
78    /** Is this symbol known to store an int value ? */
79    public static final int HAS_INT_VALUE           = 1 << 11;
80    /** Is this symbol known to store a long value ? */
81    public static final int HAS_LONG_VALUE          = 1 << 12;
82    /** Is this symbol known to store a double value ? */
83    public static final int HAS_DOUBLE_VALUE        = 1 << 13;
84    /** Is this symbol known to store an object value ? */
85    public static final int HAS_OBJECT_VALUE        = 1 << 14;
86    /** Is this symbol seen a declaration? Used for block scoped LET and CONST symbols only. */
87    public static final int HAS_BEEN_DECLARED       = 1 << 15;
88
89    /** Null or name identifying symbol. */
90    private final String name;
91
92    /** Symbol flags. */
93    private int flags;
94
95    /** First bytecode method local variable slot for storing the value(s) of this variable. -1 indicates the variable
96     * is not stored in local variable slots or it is not yet known. */
97    private int firstSlot = -1;
98
99    /** Field number in scope or property; array index in varargs when not using arguments object. */
100    private int fieldIndex;
101
102    /** Number of times this symbol is used in code */
103    private int useCount;
104
105    /** Debugging option - dump info and stack trace when symbols with given names are manipulated */
106    private static final Set<String> TRACE_SYMBOLS;
107    private static final Set<String> TRACE_SYMBOLS_STACKTRACE;
108
109    static {
110        final String stacktrace = Options.getStringProperty("nashorn.compiler.symbol.stacktrace", null);
111        final String trace;
112        if (stacktrace != null) {
113            trace = stacktrace; //stacktrace always implies trace as well
114            TRACE_SYMBOLS_STACKTRACE = new HashSet<>();
115            for (final StringTokenizer st = new StringTokenizer(stacktrace, ","); st.hasMoreTokens(); ) {
116                TRACE_SYMBOLS_STACKTRACE.add(st.nextToken());
117            }
118        } else {
119            trace = Options.getStringProperty("nashorn.compiler.symbol.trace", null);
120            TRACE_SYMBOLS_STACKTRACE = null;
121        }
122
123        if (trace != null) {
124            TRACE_SYMBOLS = new HashSet<>();
125            for (final StringTokenizer st = new StringTokenizer(trace, ","); st.hasMoreTokens(); ) {
126                TRACE_SYMBOLS.add(st.nextToken());
127            }
128        } else {
129            TRACE_SYMBOLS = null;
130        }
131    }
132
133    /**
134     * Constructor
135     *
136     * @param name  name of symbol
137     * @param flags symbol flags
138     * @param slot  bytecode slot for this symbol
139     */
140    protected Symbol(final String name, final int flags, final int slot) {
141        this.name       = name;
142        this.flags      = flags;
143        this.firstSlot  = slot;
144        this.fieldIndex = -1;
145        if(shouldTrace()) {
146            trace("CREATE SYMBOL " + name);
147        }
148    }
149
150    /**
151     * Constructor
152     *
153     * @param name  name of symbol
154     * @param flags symbol flags
155     */
156    public Symbol(final String name, final int flags) {
157        this(name, flags, -1);
158    }
159
160    private static String align(final String string, final int max) {
161        final StringBuilder sb = new StringBuilder();
162        sb.append(string.substring(0, Math.min(string.length(), max)));
163
164        while (sb.length() < max) {
165            sb.append(' ');
166        }
167        return sb.toString();
168    }
169
170    /**
171     * Debugging .
172     *
173     * @param stream Stream to print to.
174     */
175
176    void print(final PrintWriter stream) {
177        final StringBuilder sb = new StringBuilder();
178
179        sb.append(align(name, 20)).
180            append(": ").
181            append(", ").
182            append(align(firstSlot == -1 ? "none" : "" + firstSlot, 10));
183
184        switch (flags & KINDMASK) {
185        case IS_GLOBAL:
186            sb.append(" global");
187            break;
188        case IS_VAR:
189            if (isConst()) {
190                sb.append(" const");
191            } else if (isLet()) {
192                sb.append(" let");
193            } else {
194                sb.append(" var");
195            }
196            break;
197        case IS_PARAM:
198            sb.append(" param");
199            break;
200        default:
201            break;
202        }
203
204        if (isScope()) {
205            sb.append(" scope");
206        }
207
208        if (isInternal()) {
209            sb.append(" internal");
210        }
211
212        if (isThis()) {
213            sb.append(" this");
214        }
215
216        if (isProgramLevel()) {
217            sb.append(" program");
218        }
219
220        sb.append('\n');
221
222        stream.print(sb.toString());
223    }
224
225    /**
226     * Compare the the symbol kind with another.
227     *
228     * @param other Other symbol's flags.
229     * @return True if symbol has less kind.
230     */
231    public boolean less(final int other) {
232        return (flags & KINDMASK) < (other & KINDMASK);
233    }
234
235    /**
236     * Allocate a slot for this symbol.
237     *
238     * @param needsSlot True if symbol needs a slot.
239     * @return the symbol
240     */
241    public Symbol setNeedsSlot(final boolean needsSlot) {
242        if(needsSlot) {
243            assert !isScope();
244            flags |= HAS_SLOT;
245        } else {
246            flags &= ~HAS_SLOT;
247        }
248        return this;
249    }
250
251    /**
252     * Return the number of slots required for the symbol.
253     *
254     * @return Number of slots.
255     */
256    public int slotCount() {
257        return ((flags & HAS_INT_VALUE)    == 0 ? 0 : 1) +
258               ((flags & HAS_LONG_VALUE)   == 0 ? 0 : 2) +
259               ((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2) +
260               ((flags & HAS_OBJECT_VALUE) == 0 ? 0 : 1);
261    }
262
263    private boolean isSlotted() {
264        return firstSlot != -1 && ((flags & HAS_SLOT) != 0);
265    }
266
267    @Override
268    public String toString() {
269        final StringBuilder sb = new StringBuilder();
270
271        sb.append(name).
272            append(' ');
273
274        if (hasSlot()) {
275            sb.append(' ').
276                append('(').
277                append("slot=").
278                append(firstSlot).append(' ');
279            if((flags & HAS_INT_VALUE) != 0) { sb.append('I'); }
280            if((flags & HAS_LONG_VALUE) != 0) { sb.append('J'); }
281            if((flags & HAS_DOUBLE_VALUE) != 0) { sb.append('D'); }
282            if((flags & HAS_OBJECT_VALUE) != 0) { sb.append('O'); }
283            sb.append(')');
284        }
285
286        if (isScope()) {
287            if(isGlobal()) {
288                sb.append(" G");
289            } else {
290                sb.append(" S");
291            }
292        }
293
294        return sb.toString();
295    }
296
297    @Override
298    public int compareTo(final Symbol other) {
299        return name.compareTo(other.name);
300    }
301
302    /**
303     * Does this symbol have an allocated bytecode slot? Note that having an allocated bytecode slot doesn't necessarily
304     * mean the symbol's value will be stored in it. Namely, a function parameter can have a bytecode slot, but if it is
305     * in scope, then the bytecode slot will not be used. See {@link #isBytecodeLocal()}.
306     *
307     * @return true if this symbol has a local bytecode slot
308     */
309    public boolean hasSlot() {
310        return (flags & HAS_SLOT) != 0;
311    }
312
313    /**
314     * Is this symbol a local variable stored in bytecode local variable slots? This is true for a slotted variable that
315     * is not in scope. (E.g. a parameter that is in scope is slotted, but it will not be a local variable).
316     * @return true if this symbol is using bytecode local slots for its storage.
317     */
318    public boolean isBytecodeLocal() {
319        return hasSlot() && !isScope();
320    }
321
322    /**
323     * Returns true if this symbol is dead (it is a local variable that is statically proven to never be read in any type).
324     * @return true if this symbol is dead
325     */
326    public boolean isDead() {
327        return (flags & (HAS_SLOT | IS_SCOPE)) == 0;
328    }
329
330    /**
331     * Check if this is a symbol in scope. Scope symbols cannot, for obvious reasons
332     * be stored in byte code slots on the local frame
333     *
334     * @return true if this is scoped
335     */
336    public boolean isScope() {
337        assert (flags & KINDMASK) != IS_GLOBAL || (flags & IS_SCOPE) == IS_SCOPE : "global without scope flag";
338        return (flags & IS_SCOPE) != 0;
339    }
340
341    /**
342     * Check if this symbol is a function declaration
343     * @return true if a function declaration
344     */
345    public boolean isFunctionDeclaration() {
346        return (flags & IS_FUNCTION_DECLARATION) != 0;
347    }
348
349    /**
350     * Flag this symbol as scope as described in {@link Symbol#isScope()}
351     * @return the symbol
352     */
353     public Symbol setIsScope() {
354        if (!isScope()) {
355            if(shouldTrace()) {
356                trace("SET IS SCOPE");
357            }
358            flags |= IS_SCOPE;
359            if(!isParam()) {
360                flags &= ~HAS_SLOT;
361            }
362        }
363        return this;
364    }
365
366    /**
367     * Mark this symbol as a function declaration.
368     */
369    public void setIsFunctionDeclaration() {
370        if (!isFunctionDeclaration()) {
371            if(shouldTrace()) {
372                trace("SET IS FUNCTION DECLARATION");
373            }
374            flags |= IS_FUNCTION_DECLARATION;
375        }
376    }
377
378    /**
379     * Check if this symbol is a variable
380     * @return true if variable
381     */
382    public boolean isVar() {
383        return (flags & KINDMASK) == IS_VAR;
384    }
385
386    /**
387     * Check if this symbol is a global (undeclared) variable
388     * @return true if global
389     */
390    public boolean isGlobal() {
391        return (flags & KINDMASK) == IS_GLOBAL;
392    }
393
394    /**
395     * Check if this symbol is a function parameter
396     * @return true if parameter
397     */
398    public boolean isParam() {
399        return (flags & KINDMASK) == IS_PARAM;
400    }
401
402    /**
403     * Check if this is a program (script) level definition
404     * @return true if program level
405     */
406    public boolean isProgramLevel() {
407        return (flags & IS_PROGRAM_LEVEL) != 0;
408    }
409
410    /**
411     * Check if this symbol is a constant
412     * @return true if a constant
413     */
414    public boolean isConst() {
415        return (flags & IS_CONST) != 0;
416    }
417
418    /**
419     * Check if this is an internal symbol, without an explicit JavaScript source
420     * code equivalent
421     * @return true if internal
422     */
423    public boolean isInternal() {
424        return (flags & IS_INTERNAL) != 0;
425    }
426
427    /**
428     * Check if this symbol represents {@code this}
429     * @return true if this
430     */
431    public boolean isThis() {
432        return (flags & IS_THIS) != 0;
433    }
434
435    /**
436     * Check if this symbol is a let
437     * @return true if let
438     */
439    public boolean isLet() {
440        return (flags & IS_LET) != 0;
441    }
442
443    /**
444     * Flag this symbol as a function's self-referencing symbol.
445     * @return true if this symbol as a function's self-referencing symbol.
446     */
447    public boolean isFunctionSelf() {
448        return (flags & IS_FUNCTION_SELF) != 0;
449    }
450
451    public boolean isBlockScoped() {
452        return isLet() || isConst();
453    }
454
455    public boolean hasBeenDeclared() {
456        return (flags & HAS_BEEN_DECLARED) != 0;
457    }
458
459    public void setHasBeenDeclared() {
460        if (!hasBeenDeclared()) {
461            flags |= HAS_BEEN_DECLARED;
462        }
463    }
464
465    /**
466     * Get the index of the field used to store this symbol, should it be an AccessorProperty
467     * and get allocated in a JO-prefixed ScriptObject subclass.
468     *
469     * @return field index
470     */
471    public int getFieldIndex() {
472        assert fieldIndex != -1 : "fieldIndex must be initialized " + fieldIndex;
473        return fieldIndex;
474    }
475
476    /**
477     * Set the index of the field used to store this symbol, should it be an AccessorProperty
478     * and get allocated in a JO-prefixed ScriptObject subclass.
479     *
480     * @param fieldIndex field index - a positive integer
481     * @return the symbol
482     */
483    public Symbol setFieldIndex(final int fieldIndex) {
484        if (this.fieldIndex != fieldIndex) {
485            this.fieldIndex = fieldIndex;
486        }
487        return this;
488    }
489
490    /**
491     * Get the symbol flags
492     * @return flags
493     */
494    public int getFlags() {
495        return flags;
496    }
497
498    /**
499     * Set the symbol flags
500     * @param flags flags
501     * @return the symbol
502     */
503    public Symbol setFlags(final int flags) {
504        if (this.flags != flags) {
505            this.flags = flags;
506        }
507        return this;
508    }
509
510    /**
511     * Set a single symbol flag
512     * @param flag flag to set
513     * @return the symbol
514     */
515    public Symbol setFlag(final int flag) {
516        if ((this.flags & flag) == 0) {
517            this.flags |= flag;
518        }
519        return this;
520    }
521
522    /**
523     * Clears a single symbol flag
524     * @param flag flag to set
525     * @return the symbol
526     */
527    public Symbol clearFlag(final int flag) {
528        if ((this.flags & flag) != 0) {
529            this.flags &= ~flag;
530        }
531        return this;
532    }
533
534    /**
535     * Get the name of this symbol
536     * @return symbol name
537     */
538    public String getName() {
539        return name;
540    }
541
542    /**
543     * Get the index of the first bytecode slot for this symbol
544     * @return byte code slot
545     */
546    public int getFirstSlot() {
547        assert isSlotted();
548        return firstSlot;
549    }
550
551    /**
552     * Get the index of the bytecode slot for this symbol for storing a value of the specified type.
553     * @param type the requested type
554     * @return byte code slot
555     */
556    public int getSlot(final Type type) {
557        assert isSlotted();
558        int typeSlot = firstSlot;
559        if(type.isBoolean() || type.isInteger()) {
560            assert (flags & HAS_INT_VALUE) != 0;
561            return typeSlot;
562        }
563        typeSlot += ((flags & HAS_INT_VALUE) == 0 ? 0 : 1);
564        if(type.isLong()) {
565            assert (flags & HAS_LONG_VALUE) != 0;
566            return typeSlot;
567        }
568        typeSlot += ((flags & HAS_LONG_VALUE) == 0 ? 0 : 2);
569        if(type.isNumber()) {
570            assert (flags & HAS_DOUBLE_VALUE) != 0;
571            return typeSlot;
572        }
573        assert type.isObject();
574        assert (flags & HAS_OBJECT_VALUE) != 0 : name;
575        return typeSlot + ((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2);
576    }
577
578    /**
579     * Returns true if this symbol has a local variable slot for storing a value of specific type.
580     * @param type the type
581     * @return true if this symbol has a local variable slot for storing a value of specific type.
582     */
583    public boolean hasSlotFor(final Type type) {
584        if(type.isBoolean() || type.isInteger()) {
585            return (flags & HAS_INT_VALUE) != 0;
586        } else if(type.isLong()) {
587            return (flags & HAS_LONG_VALUE) != 0;
588        } else if(type.isNumber()) {
589            return (flags & HAS_DOUBLE_VALUE) != 0;
590        }
591        assert type.isObject();
592        return (flags & HAS_OBJECT_VALUE) != 0;
593    }
594
595    /**
596     * Marks this symbol as having a local variable slot for storing a value of specific type.
597     * @param type the type
598     */
599    public void setHasSlotFor(final Type type) {
600        if(type.isBoolean() || type.isInteger()) {
601            setFlag(HAS_INT_VALUE);
602        } else if(type.isLong()) {
603            setFlag(HAS_LONG_VALUE);
604        } else if(type.isNumber()) {
605            setFlag(HAS_DOUBLE_VALUE);
606        } else {
607            assert type.isObject();
608            setFlag(HAS_OBJECT_VALUE);
609        }
610    }
611
612    /**
613     * Increase the symbol's use count by one.
614     * @return the symbol
615     */
616    public Symbol increaseUseCount() {
617        useCount++;
618        return this;
619    }
620
621    /**
622     * Get the symbol's use count
623     * @return the number of times the symbol is used in code.
624     */
625    public int getUseCount() {
626        return useCount;
627    }
628
629    /**
630     * Set the bytecode slot for this symbol
631     * @param  firstSlot valid bytecode slot
632     * @return the symbol
633     */
634    public Symbol setFirstSlot(final int firstSlot) {
635        assert firstSlot >= 0 && firstSlot <= 65535;
636        if (firstSlot != this.firstSlot) {
637            if(shouldTrace()) {
638                trace("SET SLOT " + firstSlot);
639            }
640            this.firstSlot = firstSlot;
641        }
642        return this;
643    }
644
645    /**
646     * From a lexical context, set this symbol as needing scope, which
647     * will set flags for the defining block that will be written when
648     * block is popped from the lexical context stack, used by codegen
649     * when flags need to be tagged, but block is in the
650     * middle of evaluation and cannot be modified.
651     *
652     * @param  lc     lexical context
653     * @param  symbol symbol
654     * @return the symbol
655     */
656    public static Symbol setSymbolIsScope(final LexicalContext lc, final Symbol symbol) {
657        symbol.setIsScope();
658        if (!symbol.isGlobal()) {
659            lc.setBlockNeedsScope(lc.getDefiningBlock(symbol));
660        }
661        return symbol;
662    }
663
664    private boolean shouldTrace() {
665        return TRACE_SYMBOLS != null && (TRACE_SYMBOLS.isEmpty() || TRACE_SYMBOLS.contains(name));
666    }
667
668    private void trace(final String desc) {
669        Context.err(Debug.id(this) + " SYMBOL: '" + name + "' " + desc);
670        if (TRACE_SYMBOLS_STACKTRACE != null && (TRACE_SYMBOLS_STACKTRACE.isEmpty() || TRACE_SYMBOLS_STACKTRACE.contains(name))) {
671            new Throwable().printStackTrace(Context.getCurrentErr());
672        }
673    }
674}
675