Symbol.java revision 1070:34d55faf0b3a
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 = -1;
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     */
139    public Symbol(final String name, final int flags) {
140        this.name       = name;
141        this.flags      = flags;
142        if(shouldTrace()) {
143            trace("CREATE SYMBOL " + name);
144        }
145    }
146
147    private static String align(final String string, final int max) {
148        final StringBuilder sb = new StringBuilder();
149        sb.append(string.substring(0, Math.min(string.length(), max)));
150
151        while (sb.length() < max) {
152            sb.append(' ');
153        }
154        return sb.toString();
155    }
156
157    /**
158     * Debugging .
159     *
160     * @param stream Stream to print to.
161     */
162
163    void print(final PrintWriter stream) {
164        final StringBuilder sb = new StringBuilder();
165
166        sb.append(align(name, 20)).
167            append(": ").
168            append(", ").
169            append(align(firstSlot == -1 ? "none" : "" + firstSlot, 10));
170
171        switch (flags & KINDMASK) {
172        case IS_GLOBAL:
173            sb.append(" global");
174            break;
175        case IS_VAR:
176            if (isConst()) {
177                sb.append(" const");
178            } else if (isLet()) {
179                sb.append(" let");
180            } else {
181                sb.append(" var");
182            }
183            break;
184        case IS_PARAM:
185            sb.append(" param");
186            break;
187        default:
188            break;
189        }
190
191        if (isScope()) {
192            sb.append(" scope");
193        }
194
195        if (isInternal()) {
196            sb.append(" internal");
197        }
198
199        if (isThis()) {
200            sb.append(" this");
201        }
202
203        if (isProgramLevel()) {
204            sb.append(" program");
205        }
206
207        sb.append('\n');
208
209        stream.print(sb.toString());
210    }
211
212    /**
213     * Compare the the symbol kind with another.
214     *
215     * @param other Other symbol's flags.
216     * @return True if symbol has less kind.
217     */
218    public boolean less(final int other) {
219        return (flags & KINDMASK) < (other & KINDMASK);
220    }
221
222    /**
223     * Allocate a slot for this symbol.
224     *
225     * @param needsSlot True if symbol needs a slot.
226     * @return the symbol
227     */
228    public Symbol setNeedsSlot(final boolean needsSlot) {
229        if(needsSlot) {
230            assert !isScope();
231            flags |= HAS_SLOT;
232        } else {
233            flags &= ~HAS_SLOT;
234        }
235        return this;
236    }
237
238    /**
239     * Return the number of slots required for the symbol.
240     *
241     * @return Number of slots.
242     */
243    public int slotCount() {
244        return ((flags & HAS_INT_VALUE)    == 0 ? 0 : 1) +
245               ((flags & HAS_LONG_VALUE)   == 0 ? 0 : 2) +
246               ((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2) +
247               ((flags & HAS_OBJECT_VALUE) == 0 ? 0 : 1);
248    }
249
250    private boolean isSlotted() {
251        return firstSlot != -1 && ((flags & HAS_SLOT) != 0);
252    }
253
254    @Override
255    public String toString() {
256        final StringBuilder sb = new StringBuilder();
257
258        sb.append(name).
259            append(' ');
260
261        if (hasSlot()) {
262            sb.append(' ').
263                append('(').
264                append("slot=").
265                append(firstSlot).append(' ');
266            if((flags & HAS_INT_VALUE) != 0) { sb.append('I'); }
267            if((flags & HAS_LONG_VALUE) != 0) { sb.append('J'); }
268            if((flags & HAS_DOUBLE_VALUE) != 0) { sb.append('D'); }
269            if((flags & HAS_OBJECT_VALUE) != 0) { sb.append('O'); }
270            sb.append(')');
271        }
272
273        if (isScope()) {
274            if(isGlobal()) {
275                sb.append(" G");
276            } else {
277                sb.append(" S");
278            }
279        }
280
281        return sb.toString();
282    }
283
284    @Override
285    public int compareTo(final Symbol other) {
286        return name.compareTo(other.name);
287    }
288
289    /**
290     * Does this symbol have an allocated bytecode slot? Note that having an allocated bytecode slot doesn't necessarily
291     * mean the symbol's value will be stored in it. Namely, a function parameter can have a bytecode slot, but if it is
292     * in scope, then the bytecode slot will not be used. See {@link #isBytecodeLocal()}.
293     *
294     * @return true if this symbol has a local bytecode slot
295     */
296    public boolean hasSlot() {
297        return (flags & HAS_SLOT) != 0;
298    }
299
300    /**
301     * Is this symbol a local variable stored in bytecode local variable slots? This is true for a slotted variable that
302     * is not in scope. (E.g. a parameter that is in scope is slotted, but it will not be a local variable).
303     * @return true if this symbol is using bytecode local slots for its storage.
304     */
305    public boolean isBytecodeLocal() {
306        return hasSlot() && !isScope();
307    }
308
309    /**
310     * Returns true if this symbol is dead (it is a local variable that is statically proven to never be read in any type).
311     * @return true if this symbol is dead
312     */
313    public boolean isDead() {
314        return (flags & (HAS_SLOT | IS_SCOPE)) == 0;
315    }
316
317    /**
318     * Check if this is a symbol in scope. Scope symbols cannot, for obvious reasons
319     * be stored in byte code slots on the local frame
320     *
321     * @return true if this is scoped
322     */
323    public boolean isScope() {
324        assert (flags & KINDMASK) != IS_GLOBAL || (flags & IS_SCOPE) == IS_SCOPE : "global without scope flag";
325        return (flags & IS_SCOPE) != 0;
326    }
327
328    /**
329     * Check if this symbol is a function declaration
330     * @return true if a function declaration
331     */
332    public boolean isFunctionDeclaration() {
333        return (flags & IS_FUNCTION_DECLARATION) != 0;
334    }
335
336    /**
337     * Flag this symbol as scope as described in {@link Symbol#isScope()}
338     * @return the symbol
339     */
340     public Symbol setIsScope() {
341        if (!isScope()) {
342            if(shouldTrace()) {
343                trace("SET IS SCOPE");
344            }
345            flags |= IS_SCOPE;
346            if(!isParam()) {
347                flags &= ~HAS_SLOT;
348            }
349        }
350        return this;
351    }
352
353    /**
354     * Mark this symbol as a function declaration.
355     */
356    public void setIsFunctionDeclaration() {
357        if (!isFunctionDeclaration()) {
358            if(shouldTrace()) {
359                trace("SET IS FUNCTION DECLARATION");
360            }
361            flags |= IS_FUNCTION_DECLARATION;
362        }
363    }
364
365    /**
366     * Check if this symbol is a variable
367     * @return true if variable
368     */
369    public boolean isVar() {
370        return (flags & KINDMASK) == IS_VAR;
371    }
372
373    /**
374     * Check if this symbol is a global (undeclared) variable
375     * @return true if global
376     */
377    public boolean isGlobal() {
378        return (flags & KINDMASK) == IS_GLOBAL;
379    }
380
381    /**
382     * Check if this symbol is a function parameter
383     * @return true if parameter
384     */
385    public boolean isParam() {
386        return (flags & KINDMASK) == IS_PARAM;
387    }
388
389    /**
390     * Check if this is a program (script) level definition
391     * @return true if program level
392     */
393    public boolean isProgramLevel() {
394        return (flags & IS_PROGRAM_LEVEL) != 0;
395    }
396
397    /**
398     * Check if this symbol is a constant
399     * @return true if a constant
400     */
401    public boolean isConst() {
402        return (flags & IS_CONST) != 0;
403    }
404
405    /**
406     * Check if this is an internal symbol, without an explicit JavaScript source
407     * code equivalent
408     * @return true if internal
409     */
410    public boolean isInternal() {
411        return (flags & IS_INTERNAL) != 0;
412    }
413
414    /**
415     * Check if this symbol represents {@code this}
416     * @return true if this
417     */
418    public boolean isThis() {
419        return (flags & IS_THIS) != 0;
420    }
421
422    /**
423     * Check if this symbol is a let
424     * @return true if let
425     */
426    public boolean isLet() {
427        return (flags & IS_LET) != 0;
428    }
429
430    /**
431     * Flag this symbol as a function's self-referencing symbol.
432     * @return true if this symbol as a function's self-referencing symbol.
433     */
434    public boolean isFunctionSelf() {
435        return (flags & IS_FUNCTION_SELF) != 0;
436    }
437
438    /**
439     * Is this a block scoped symbol
440     * @return true if block scoped
441     */
442    public boolean isBlockScoped() {
443        return isLet() || isConst();
444    }
445
446    /**
447     * Has this symbol been declared
448     * @return true if declared
449     */
450    public boolean hasBeenDeclared() {
451        return (flags & HAS_BEEN_DECLARED) != 0;
452    }
453
454    /**
455     * Mark this symbol as declared
456     */
457    public void setHasBeenDeclared() {
458        if (!hasBeenDeclared()) {
459            flags |= HAS_BEEN_DECLARED;
460        }
461    }
462
463    /**
464     * Get the index of the field used to store this symbol, should it be an AccessorProperty
465     * and get allocated in a JO-prefixed ScriptObject subclass.
466     *
467     * @return field index
468     */
469    public int getFieldIndex() {
470        assert fieldIndex != -1 : "fieldIndex must be initialized " + fieldIndex;
471        return fieldIndex;
472    }
473
474    /**
475     * Set the index of the field used to store this symbol, should it be an AccessorProperty
476     * and get allocated in a JO-prefixed ScriptObject subclass.
477     *
478     * @param fieldIndex field index - a positive integer
479     * @return the symbol
480     */
481    public Symbol setFieldIndex(final int fieldIndex) {
482        if (this.fieldIndex != fieldIndex) {
483            this.fieldIndex = fieldIndex;
484        }
485        return this;
486    }
487
488    /**
489     * Get the symbol flags
490     * @return flags
491     */
492    public int getFlags() {
493        return flags;
494    }
495
496    /**
497     * Set the symbol flags
498     * @param flags flags
499     * @return the symbol
500     */
501    public Symbol setFlags(final int flags) {
502        if (this.flags != flags) {
503            this.flags = flags;
504        }
505        return this;
506    }
507
508    /**
509     * Set a single symbol flag
510     * @param flag flag to set
511     * @return the symbol
512     */
513    public Symbol setFlag(final int flag) {
514        if ((this.flags & flag) == 0) {
515            this.flags |= flag;
516        }
517        return this;
518    }
519
520    /**
521     * Clears a single symbol flag
522     * @param flag flag to set
523     * @return the symbol
524     */
525    public Symbol clearFlag(final int flag) {
526        if ((this.flags & flag) != 0) {
527            this.flags &= ~flag;
528        }
529        return this;
530    }
531
532    /**
533     * Get the name of this symbol
534     * @return symbol name
535     */
536    public String getName() {
537        return name;
538    }
539
540    /**
541     * Get the index of the first bytecode slot for this symbol
542     * @return byte code slot
543     */
544    public int getFirstSlot() {
545        assert isSlotted();
546        return firstSlot;
547    }
548
549    /**
550     * Get the index of the bytecode slot for this symbol for storing a value of the specified type.
551     * @param type the requested type
552     * @return byte code slot
553     */
554    public int getSlot(final Type type) {
555        assert isSlotted();
556        int typeSlot = firstSlot;
557        if(type.isBoolean() || type.isInteger()) {
558            assert (flags & HAS_INT_VALUE) != 0;
559            return typeSlot;
560        }
561        typeSlot += ((flags & HAS_INT_VALUE) == 0 ? 0 : 1);
562        if(type.isLong()) {
563            assert (flags & HAS_LONG_VALUE) != 0;
564            return typeSlot;
565        }
566        typeSlot += ((flags & HAS_LONG_VALUE) == 0 ? 0 : 2);
567        if(type.isNumber()) {
568            assert (flags & HAS_DOUBLE_VALUE) != 0;
569            return typeSlot;
570        }
571        assert type.isObject();
572        assert (flags & HAS_OBJECT_VALUE) != 0 : name;
573        return typeSlot + ((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2);
574    }
575
576    /**
577     * Returns true if this symbol has a local variable slot for storing a value of specific type.
578     * @param type the type
579     * @return true if this symbol has a local variable slot for storing a value of specific type.
580     */
581    public boolean hasSlotFor(final Type type) {
582        if(type.isBoolean() || type.isInteger()) {
583            return (flags & HAS_INT_VALUE) != 0;
584        } else if(type.isLong()) {
585            return (flags & HAS_LONG_VALUE) != 0;
586        } else if(type.isNumber()) {
587            return (flags & HAS_DOUBLE_VALUE) != 0;
588        }
589        assert type.isObject();
590        return (flags & HAS_OBJECT_VALUE) != 0;
591    }
592
593    /**
594     * Marks this symbol as having a local variable slot for storing a value of specific type.
595     * @param type the type
596     */
597    public void setHasSlotFor(final Type type) {
598        if(type.isBoolean() || type.isInteger()) {
599            setFlag(HAS_INT_VALUE);
600        } else if(type.isLong()) {
601            setFlag(HAS_LONG_VALUE);
602        } else if(type.isNumber()) {
603            setFlag(HAS_DOUBLE_VALUE);
604        } else {
605            assert type.isObject();
606            setFlag(HAS_OBJECT_VALUE);
607        }
608    }
609
610    /**
611     * Increase the symbol's use count by one.
612     * @return the symbol
613     */
614    public Symbol increaseUseCount() {
615        useCount++;
616        return this;
617    }
618
619    /**
620     * Get the symbol's use count
621     * @return the number of times the symbol is used in code.
622     */
623    public int getUseCount() {
624        return useCount;
625    }
626
627    /**
628     * Set the bytecode slot for this symbol
629     * @param  firstSlot valid bytecode slot
630     * @return the symbol
631     */
632    public Symbol setFirstSlot(final int firstSlot) {
633        assert firstSlot >= 0 && firstSlot <= 65535;
634        if (firstSlot != this.firstSlot) {
635            if(shouldTrace()) {
636                trace("SET SLOT " + firstSlot);
637            }
638            this.firstSlot = firstSlot;
639        }
640        return this;
641    }
642
643    /**
644     * From a lexical context, set this symbol as needing scope, which
645     * will set flags for the defining block that will be written when
646     * block is popped from the lexical context stack, used by codegen
647     * when flags need to be tagged, but block is in the
648     * middle of evaluation and cannot be modified.
649     *
650     * @param  lc     lexical context
651     * @param  symbol symbol
652     * @return the symbol
653     */
654    public static Symbol setSymbolIsScope(final LexicalContext lc, final Symbol symbol) {
655        symbol.setIsScope();
656        if (!symbol.isGlobal()) {
657            lc.setBlockNeedsScope(lc.getDefiningBlock(symbol));
658        }
659        return symbol;
660    }
661
662    private boolean shouldTrace() {
663        return TRACE_SYMBOLS != null && (TRACE_SYMBOLS.isEmpty() || TRACE_SYMBOLS.contains(name));
664    }
665
666    private void trace(final String desc) {
667        Context.err(Debug.id(this) + " SYMBOL: '" + name + "' " + desc);
668        if (TRACE_SYMBOLS_STACKTRACE != null && (TRACE_SYMBOLS_STACKTRACE.isEmpty() || TRACE_SYMBOLS_STACKTRACE.contains(name))) {
669            new Throwable().printStackTrace(Context.getCurrentErr());
670        }
671    }
672}
673