| /* |
| * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.nashorn.internal.ir; |
| |
| import java.io.PrintWriter; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import jdk.nashorn.internal.codegen.types.Range; |
| import jdk.nashorn.internal.codegen.types.Type; |
| import jdk.nashorn.internal.runtime.Context; |
| import jdk.nashorn.internal.runtime.Debug; |
| import jdk.nashorn.internal.runtime.options.Options; |
| |
| import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; |
| |
| /** |
| * Maps a name to specific data. |
| */ |
| |
| public final class Symbol implements Comparable<Symbol> { |
| /** Symbol kinds. Kind ordered by precedence. */ |
| public static final int IS_TEMP = 1; |
| /** Is this Global */ |
| public static final int IS_GLOBAL = 2; |
| /** Is this a variable */ |
| public static final int IS_VAR = 3; |
| /** Is this a parameter */ |
| public static final int IS_PARAM = 4; |
| /** Is this a constant */ |
| public static final int IS_CONSTANT = 5; |
| /** Mask for kind flags */ |
| public static final int KINDMASK = (1 << 3) - 1; // Kinds are represented by lower three bits |
| |
| /** Is this scope */ |
| public static final int IS_SCOPE = 1 << 4; |
| /** Is this a this symbol */ |
| public static final int IS_THIS = 1 << 5; |
| /** Can this symbol ever be undefined */ |
| public static final int CAN_BE_UNDEFINED = 1 << 6; |
| /** Is this symbol always defined? */ |
| public static final int IS_ALWAYS_DEFINED = 1 << 8; |
| /** Can this symbol ever have primitive types */ |
| public static final int CAN_BE_PRIMITIVE = 1 << 9; |
| /** Is this a let */ |
| public static final int IS_LET = 1 << 10; |
| /** Is this an internal symbol, never represented explicitly in source code */ |
| public static final int IS_INTERNAL = 1 << 11; |
| /** Is this a function self-reference symbol */ |
| public static final int IS_FUNCTION_SELF = 1 << 12; |
| /** Is this a specialized param? */ |
| public static final int IS_SPECIALIZED_PARAM = 1 << 13; |
| /** Is this symbol a shared temporary? */ |
| public static final int IS_SHARED = 1 << 14; |
| |
| /** Null or name identifying symbol. */ |
| private final String name; |
| |
| /** Symbol flags. */ |
| private int flags; |
| |
| /** Type of symbol. */ |
| private Type type; |
| |
| /** Local variable slot. -1 indicates external property. */ |
| private int slot; |
| |
| /** Field number in scope or property; array index in varargs when not using arguments object. */ |
| private int fieldIndex; |
| |
| /** Number of times this symbol is used in code */ |
| private int useCount; |
| |
| /** Range for symbol */ |
| private Range range; |
| |
| /** Debugging option - dump info and stack trace when symbols with given names are manipulated */ |
| private static final Set<String> TRACE_SYMBOLS; |
| private static final Set<String> TRACE_SYMBOLS_STACKTRACE; |
| |
| static { |
| final String stacktrace = Options.getStringProperty("nashorn.compiler.symbol.stacktrace", null); |
| final String trace; |
| if (stacktrace != null) { |
| trace = stacktrace; //stacktrace always implies trace as well |
| TRACE_SYMBOLS_STACKTRACE = new HashSet<>(); |
| for (StringTokenizer st = new StringTokenizer(stacktrace, ","); st.hasMoreTokens(); ) { |
| TRACE_SYMBOLS_STACKTRACE.add(st.nextToken()); |
| } |
| } else { |
| trace = Options.getStringProperty("nashorn.compiler.symbol.trace", null); |
| TRACE_SYMBOLS_STACKTRACE = null; |
| } |
| |
| if (trace != null) { |
| TRACE_SYMBOLS = new HashSet<>(); |
| for (StringTokenizer st = new StringTokenizer(trace, ","); st.hasMoreTokens(); ) { |
| TRACE_SYMBOLS.add(st.nextToken()); |
| } |
| } else { |
| TRACE_SYMBOLS = null; |
| } |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param name name of symbol |
| * @param flags symbol flags |
| * @param type type of this symbol |
| * @param slot bytecode slot for this symbol |
| */ |
| protected Symbol(final String name, final int flags, final Type type, final int slot) { |
| this.name = name; |
| this.flags = flags; |
| this.type = type; |
| this.slot = slot; |
| this.fieldIndex = -1; |
| this.range = Range.createUnknownRange(); |
| trace("CREATE SYMBOL"); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param name name of symbol |
| * @param flags symbol flags |
| */ |
| public Symbol(final String name, final int flags) { |
| this(name, flags, Type.UNKNOWN, -1); |
| } |
| |
| /** |
| * Constructor |
| * |
| * @param name name of symbol |
| * @param flags symbol flags |
| * @param type type of this symbol |
| */ |
| public Symbol(final String name, final int flags, final Type type) { |
| this(name, flags, type, -1); |
| } |
| |
| private Symbol(final Symbol base, final String name, final int flags) { |
| this.flags = flags; |
| this.name = name; |
| |
| this.fieldIndex = base.fieldIndex; |
| this.slot = base.slot; |
| this.type = base.type; |
| this.useCount = base.useCount; |
| this.range = base.range; |
| } |
| |
| private static String align(final String string, final int max) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append(string.substring(0, Math.min(string.length(), max))); |
| |
| while (sb.length() < max) { |
| sb.append(' '); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Return the type for this symbol. Normally, if there is no type override, |
| * this is where any type for any node is stored. If the node has a TypeOverride, |
| * it may override this, e.g. when asking for a scoped field as a double |
| * |
| * @return symbol type |
| */ |
| public final Type getSymbolType() { |
| return type; |
| } |
| |
| /** |
| * Debugging . |
| * |
| * @param stream Stream to print to. |
| */ |
| |
| void print(final PrintWriter stream) { |
| final String printName = align(name, 20); |
| final String printType = align(type.toString(), 10); |
| final String printSlot = align(slot == -1 ? "none" : "" + slot, 10); |
| String printFlags = ""; |
| |
| switch (flags & KINDMASK) { |
| case IS_TEMP: |
| printFlags = "temp " + printFlags; |
| break; |
| case IS_GLOBAL: |
| printFlags = "global " + printFlags; |
| break; |
| case IS_VAR: |
| printFlags = "var " + printFlags; |
| break; |
| case IS_PARAM: |
| printFlags = "param " + printFlags; |
| break; |
| case IS_CONSTANT: |
| printFlags = "CONSTANT " + printFlags; |
| break; |
| default: |
| break; |
| } |
| |
| if (isScope()) { |
| printFlags += "scope "; |
| } |
| |
| if (isInternal()) { |
| printFlags += "internal "; |
| } |
| |
| if (isLet()) { |
| printFlags += "let "; |
| } |
| |
| if (isThis()) { |
| printFlags += "this "; |
| } |
| |
| if (!canBeUndefined()) { |
| printFlags += "always_def "; |
| } |
| |
| if (canBePrimitive()) { |
| printFlags += "can_be_prim "; |
| } |
| |
| stream.print(printName + ": " + printType + ", " + printSlot + ", " + printFlags); |
| stream.println(); |
| } |
| |
| /** |
| * Compare the the symbol kind with another. |
| * |
| * @param other Other symbol's flags. |
| * @return True if symbol has less kind. |
| */ |
| public boolean less(final int other) { |
| return (flags & KINDMASK) < (other & KINDMASK); |
| } |
| |
| /** |
| * Allocate a slot for this symbol. |
| * |
| * @param needsSlot True if symbol needs a slot. |
| */ |
| public void setNeedsSlot(final boolean needsSlot) { |
| setSlot(needsSlot ? 0 : -1); |
| } |
| |
| /** |
| * Return the number of slots required for the symbol. |
| * |
| * @return Number of slots. |
| */ |
| public int slotCount() { |
| return type.isCategory2() ? 2 : 1; |
| } |
| |
| @Override |
| public String toString() { |
| final StringBuilder sb = new StringBuilder(); |
| |
| sb.append(name). |
| append(' '). |
| append('('). |
| append(getSymbolType().getTypeClass().getSimpleName()). |
| append(')'); |
| |
| if (hasSlot()) { |
| sb.append(' '). |
| append('('). |
| append("slot="). |
| append(slot). |
| append(')'); |
| } |
| |
| if (isScope()) { |
| if(isGlobal()) { |
| sb.append(" G"); |
| } else { |
| sb.append(" S"); |
| } |
| } |
| |
| if (canBePrimitive()) { |
| sb.append(" P?"); |
| } |
| |
| return sb.toString(); |
| } |
| |
| @Override |
| public int compareTo(final Symbol other) { |
| return name.compareTo(other.name); |
| } |
| |
| /** |
| * Does this symbol have an allocated bytecode slot. If not, it is scope |
| * and must be loaded from memory upon access |
| * |
| * @return true if this symbol has a local bytecode slot |
| */ |
| public boolean hasSlot() { |
| return slot >= 0; |
| } |
| |
| /** |
| * Check if this is a temporary symbol |
| * @return true if temporary |
| */ |
| public boolean isTemp() { |
| return (flags & KINDMASK) == IS_TEMP; |
| } |
| |
| /** |
| * Check if this is a symbol in scope. Scope symbols cannot, for obvious reasons |
| * be stored in byte code slots on the local frame |
| * |
| * @return true if this is scoped |
| */ |
| public boolean isScope() { |
| assert ((flags & KINDMASK) != IS_GLOBAL) || ((flags & IS_SCOPE) == IS_SCOPE) : "global without scope flag"; |
| return (flags & IS_SCOPE) == IS_SCOPE; |
| } |
| |
| /** |
| * Returns true if this symbol is a temporary that is being shared across expressions. |
| * @return true if this symbol is a temporary that is being shared across expressions. |
| */ |
| public boolean isShared() { |
| return (flags & IS_SHARED) == IS_SHARED; |
| } |
| |
| /** |
| * Creates an unshared copy of a symbol. The symbol must be currently shared. |
| * @param newName the name for the new symbol. |
| * @return a new, unshared symbol. |
| */ |
| public Symbol createUnshared(final String newName) { |
| assert isShared(); |
| return new Symbol(this, newName, flags & ~IS_SHARED); |
| } |
| |
| /** |
| * Flag this symbol as scope as described in {@link Symbol#isScope()} |
| */ |
| /** |
| * Flag this symbol as scope as described in {@link Symbol#isScope()} |
| */ |
| public void setIsScope() { |
| if (!isScope()) { |
| trace("SET IS SCOPE"); |
| assert !isShared(); |
| flags |= IS_SCOPE; |
| } |
| } |
| |
| /** |
| * Mark this symbol as one being shared by multiple expressions. The symbol must be a temporary. |
| */ |
| public void setIsShared() { |
| if (!isShared()) { |
| assert isTemp(); |
| trace("SET IS SHARED"); |
| flags |= IS_SHARED; |
| } |
| } |
| |
| |
| /** |
| * Check if this symbol is a variable |
| * @return true if variable |
| */ |
| public boolean isVar() { |
| return (flags & KINDMASK) == IS_VAR; |
| } |
| |
| /** |
| * Check if this symbol is a global (undeclared) variable |
| * @return true if global |
| */ |
| public boolean isGlobal() { |
| return (flags & KINDMASK) == IS_GLOBAL; |
| } |
| |
| /** |
| * Check if this symbol is a function parameter |
| * @return true if parameter |
| */ |
| public boolean isParam() { |
| return (flags & KINDMASK) == IS_PARAM; |
| } |
| |
| /** |
| * Check if this symbol is always defined, which overrides all canBeUndefined tags |
| * @return true if always defined |
| */ |
| public boolean isAlwaysDefined() { |
| return isParam() || (flags & IS_ALWAYS_DEFINED) == IS_ALWAYS_DEFINED; |
| } |
| |
| /** |
| * Get the range for this symbol |
| * @return range for symbol |
| */ |
| public Range getRange() { |
| return range; |
| } |
| |
| /** |
| * Set the range for this symbol |
| * @param range range |
| */ |
| public void setRange(final Range range) { |
| this.range = range; |
| } |
| |
| /** |
| * Check if this symbol represents a return value with a known non-generic type. |
| * @return true if specialized return value |
| */ |
| public boolean isNonGenericReturn() { |
| return getName().equals(RETURN.symbolName()) && type != Type.OBJECT; |
| } |
| |
| /** |
| * Check if this symbol is a function parameter of known |
| * narrowest type |
| * @return true if parameter |
| */ |
| public boolean isSpecializedParam() { |
| return (flags & IS_SPECIALIZED_PARAM) == IS_SPECIALIZED_PARAM; |
| } |
| |
| /** |
| * Check whether this symbol ever has primitive assignments. Conservative |
| * @return true if primitive assignments exist |
| */ |
| public boolean canBePrimitive() { |
| return (flags & CAN_BE_PRIMITIVE) == CAN_BE_PRIMITIVE; |
| } |
| |
| /** |
| * Check if this symbol can ever be undefined |
| * @return true if can be undefined |
| */ |
| public boolean canBeUndefined() { |
| return (flags & CAN_BE_UNDEFINED) == CAN_BE_UNDEFINED; |
| } |
| |
| /** |
| * Flag this symbol as potentially undefined in parts of the program |
| */ |
| public void setCanBeUndefined() { |
| assert type.isObject() : type; |
| if (isAlwaysDefined()) { |
| return; |
| } else if (!canBeUndefined()) { |
| assert !isShared(); |
| flags |= CAN_BE_UNDEFINED; |
| } |
| } |
| |
| /** |
| * Flag this symbol as potentially primitive |
| * @param type the primitive type it occurs with, currently unused but can be used for width guesses |
| */ |
| public void setCanBePrimitive(final Type type) { |
| if(!canBePrimitive()) { |
| assert !isShared(); |
| flags |= CAN_BE_PRIMITIVE; |
| } |
| } |
| |
| /** |
| * Check if this symbol is a constant |
| * @return true if a constant |
| */ |
| public boolean isConstant() { |
| return (flags & KINDMASK) == IS_CONSTANT; |
| } |
| |
| /** |
| * Check if this is an internal symbol, without an explicit JavaScript source |
| * code equivalent |
| * @return true if internal |
| */ |
| public boolean isInternal() { |
| return (flags & IS_INTERNAL) != 0; |
| } |
| |
| /** |
| * Check if this symbol represents {@code this} |
| * @return true if this |
| */ |
| public boolean isThis() { |
| return (flags & IS_THIS) != 0; |
| } |
| |
| /** |
| * Check if this symbol is a let |
| * @return true if let |
| */ |
| public boolean isLet() { |
| return (flags & IS_LET) == IS_LET; |
| } |
| |
| /** |
| * Flag this symbol as a let |
| */ |
| public void setIsLet() { |
| if(!isLet()) { |
| assert !isShared(); |
| flags |= IS_LET; |
| } |
| } |
| |
| /** |
| * Flag this symbol as a function's self-referencing symbol. |
| * @return true if this symbol as a function's self-referencing symbol. |
| */ |
| public boolean isFunctionSelf() { |
| return (flags & IS_FUNCTION_SELF) == IS_FUNCTION_SELF; |
| } |
| |
| /** |
| * Get the index of the field used to store this symbol, should it be an AccessorProperty |
| * and get allocated in a JO-prefixed ScriptObject subclass. |
| * |
| * @return field index |
| */ |
| public int getFieldIndex() { |
| assert fieldIndex != -1 : "fieldIndex must be initialized " + fieldIndex; |
| return fieldIndex; |
| } |
| |
| /** |
| * Set the index of the field used to store this symbol, should it be an AccessorProperty |
| * and get allocated in a JO-prefixed ScriptObject subclass. |
| * |
| * @param fieldIndex field index - a positive integer |
| */ |
| public void setFieldIndex(final int fieldIndex) { |
| if(this.fieldIndex != fieldIndex) { |
| assert !isShared(); |
| this.fieldIndex = fieldIndex; |
| } |
| } |
| |
| /** |
| * Get the symbol flags |
| * @return flags |
| */ |
| public int getFlags() { |
| return flags; |
| } |
| |
| /** |
| * Set the symbol flags |
| * @param flags flags |
| */ |
| public void setFlags(final int flags) { |
| if(this.flags != flags) { |
| assert !isShared(); |
| this.flags = flags; |
| } |
| } |
| |
| /** |
| * Get the name of this symbol |
| * @return symbol name |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Get the byte code slot for this symbol |
| * @return byte code slot, or -1 if no slot allocated/possible |
| */ |
| public int getSlot() { |
| return slot; |
| } |
| |
| /** |
| * Increase the symbol's use count by one. |
| */ |
| public void increaseUseCount() { |
| useCount++; |
| } |
| |
| /** |
| * Get the symbol's use count |
| * @return the number of times the symbol is used in code. |
| */ |
| public int getUseCount() { |
| return useCount; |
| } |
| |
| /** |
| * Set the bytecode slot for this symbol |
| * @param slot valid bytecode slot, or -1 if not available |
| */ |
| public void setSlot(final int slot) { |
| if (slot != this.slot) { |
| assert !isShared(); |
| trace("SET SLOT " + slot); |
| this.slot = slot; |
| } |
| } |
| |
| /** |
| * Assign a specific subclass of Object to the symbol |
| * |
| * @param type the type |
| */ |
| public void setType(final Class<?> type) { |
| assert !type.isPrimitive() && !Number.class.isAssignableFrom(type) : "Class<?> types can only be subclasses of object"; |
| setType(Type.typeFor(type)); |
| } |
| |
| /** |
| * Assign a type to the symbol |
| * |
| * @param type the type |
| */ |
| public void setType(final Type type) { |
| setTypeOverride(Type.widest(this.type, type)); |
| } |
| |
| /** |
| * Returns true if calling {@link #setType(Type)} on this symbol would effectively change its type. |
| * @param newType the new type to test for |
| * @return true if setting this symbols type to a new value would effectively change its type. |
| */ |
| public boolean wouldChangeType(final Type newType) { |
| return Type.widest(this.type, newType) != this.type; |
| } |
| |
| /** |
| * Only use this if you know about an existing type |
| * constraint - otherwise a type can only be |
| * widened |
| * |
| * @param type the type |
| */ |
| public void setTypeOverride(final Type type) { |
| final Type old = this.type; |
| if (old != type) { |
| assert !isShared(); |
| trace("TYPE CHANGE: " + old + "=>" + type + " == " + type); |
| this.type = type; |
| } |
| } |
| |
| /** |
| * Sets the type of the symbol to the specified type. If the type would be changed, but this symbol is a shared |
| * temporary, it will instead return a different temporary symbol of the requested type from the passed temporary |
| * symbols. That way, it never mutates the type of a shared temporary. |
| * @param type the new type for the symbol |
| * @param ts a holder of temporary symbols |
| * @return either this symbol, or a different symbol if this symbol is a shared temporary and it type would have to |
| * be changed. |
| */ |
| public Symbol setTypeOverrideShared(final Type type, final TemporarySymbols ts) { |
| if(getSymbolType() != type) { |
| if(isShared()) { |
| assert !hasSlot(); |
| return ts.getTypedTemporarySymbol(type); |
| } |
| setTypeOverride(type); |
| } |
| return this; |
| } |
| |
| /** |
| * From a lexical context, set this symbol as needing scope, which |
| * will set flags for the defining block that will be written when |
| * block is popped from the lexical context stack, used by codegen |
| * when flags need to be tagged, but block is in the |
| * middle of evaluation and cannot be modified. |
| * |
| * @param lc lexical context |
| * @param symbol symbol |
| */ |
| public static void setSymbolIsScope(final LexicalContext lc, final Symbol symbol) { |
| symbol.setIsScope(); |
| if (!symbol.isGlobal()) { |
| lc.setBlockNeedsScope(lc.getDefiningBlock(symbol)); |
| } |
| } |
| |
| private void trace(final String desc) { |
| if (TRACE_SYMBOLS != null && (TRACE_SYMBOLS.isEmpty() || TRACE_SYMBOLS.contains(name))) { |
| Context.err(Debug.id(this) + " SYMBOL: '" + name + "' " + desc); |
| if (TRACE_SYMBOLS_STACKTRACE != null && (TRACE_SYMBOLS_STACKTRACE.isEmpty() || TRACE_SYMBOLS_STACKTRACE.contains(name))) { |
| new Throwable().printStackTrace(Context.getCurrentErr()); |
| } |
| } |
| } |
| } |