blob: 69e98ac6b3d8733228371f2353e2bd6671f719f0 [file] [log] [blame]
/*
* 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());
}
}
}
}