blob: d08352b7cc275dbf1af6c3b53ac2c0fa2ac7c658 [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.IOException;
import java.io.ObjectInputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
import java.util.StringTokenizer;
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;
/**
* Symbol is a symbolic address for a value ("variable" if you wish). Identifiers in JavaScript source, as well as
* certain synthetic variables created by the compiler are represented by Symbol objects. Symbols can address either
* local variable slots in bytecode ("slotted symbol"), or properties in scope objects ("scoped symbol"). A symbol can
* also end up being defined but then not used during symbol assignment calculations; such symbol will be neither
* scoped, nor slotted; it represents a dead variable (it might be written to, but is never read). Finally, a symbol can
* be both slotted and in scope. This special case can only occur with bytecode method parameters. They all come in as
* slotted, but if they are used by a nested function (or eval) then they will be copied into the scope object, and used
* from there onwards. Two further special cases are parameters stored in {@code NativeArguments} objects and parameters
* stored in {@code Object[]} parameter to variable-arity functions. Those use the {@code #getFieldIndex()} property to
* refer to their location.
*/
public final class Symbol implements Comparable<Symbol>, Cloneable, Serializable {
private static final long serialVersionUID = 1L;
/** Is this Global */
public static final int IS_GLOBAL = 1;
/** Is this a variable */
public static final int IS_VAR = 2;
/** Is this a parameter */
public static final int IS_PARAM = 3;
/** Mask for kind flags */
public static final int KINDMASK = (1 << 2) - 1; // Kinds are represented by lower two bits
/** Is this symbol in scope */
public static final int IS_SCOPE = 1 << 2;
/** Is this a this symbol */
public static final int IS_THIS = 1 << 3;
/** Is this a let */
public static final int IS_LET = 1 << 4;
/** Is this a const */
public static final int IS_CONST = 1 << 5;
/** Is this an internal symbol, never represented explicitly in source code */
public static final int IS_INTERNAL = 1 << 6;
/** Is this a function self-reference symbol */
public static final int IS_FUNCTION_SELF = 1 << 7;
/** Is this a function declaration? */
public static final int IS_FUNCTION_DECLARATION = 1 << 8;
/** Is this a program level symbol? */
public static final int IS_PROGRAM_LEVEL = 1 << 9;
/** Are this symbols' values stored in local variable slots? */
public static final int HAS_SLOT = 1 << 10;
/** Is this symbol known to store an int value ? */
public static final int HAS_INT_VALUE = 1 << 11;
/** Is this symbol known to store a double value ? */
public static final int HAS_DOUBLE_VALUE = 1 << 12;
/** Is this symbol known to store an object value ? */
public static final int HAS_OBJECT_VALUE = 1 << 13;
/** Is this symbol seen a declaration? Used for block scoped LET and CONST symbols only. */
public static final int HAS_BEEN_DECLARED = 1 << 14;
/** Null or name identifying symbol. */
private final String name;
/** Symbol flags. */
private int flags;
/** First bytecode method local variable slot for storing the value(s) of this variable. -1 indicates the variable
* is not stored in local variable slots or it is not yet known. */
private transient int firstSlot = -1;
/** Field number in scope or property; array index in varargs when not using arguments object. */
private transient int fieldIndex = -1;
/** Number of times this symbol is used in code */
private int useCount;
/** 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 (final 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 (final 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
*/
public Symbol(final String name, final int flags) {
this.name = name;
this.flags = flags;
if(shouldTrace()) {
trace("CREATE SYMBOL " + name);
}
}
@Override
public Symbol clone() {
try {
return (Symbol)super.clone();
} catch (final CloneNotSupportedException e) {
throw new AssertionError(e);
}
}
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();
}
/**
* Debugging .
*
* @param stream Stream to print to.
*/
void print(final PrintWriter stream) {
final StringBuilder sb = new StringBuilder();
sb.append(align(name, 20)).
append(": ").
append(", ").
append(align(firstSlot == -1 ? "none" : "" + firstSlot, 10));
switch (flags & KINDMASK) {
case IS_GLOBAL:
sb.append(" global");
break;
case IS_VAR:
if (isConst()) {
sb.append(" const");
} else if (isLet()) {
sb.append(" let");
} else {
sb.append(" var");
}
break;
case IS_PARAM:
sb.append(" param");
break;
default:
break;
}
if (isScope()) {
sb.append(" scope");
}
if (isInternal()) {
sb.append(" internal");
}
if (isThis()) {
sb.append(" this");
}
if (isProgramLevel()) {
sb.append(" program");
}
sb.append('\n');
stream.print(sb.toString());
}
/**
* 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.
* @return the symbol
*/
public Symbol setNeedsSlot(final boolean needsSlot) {
if(needsSlot) {
assert !isScope();
flags |= HAS_SLOT;
} else {
flags &= ~HAS_SLOT;
}
return this;
}
/**
* Return the number of slots required for the symbol.
*
* @return Number of slots.
*/
public int slotCount() {
return ((flags & HAS_INT_VALUE) == 0 ? 0 : 1) +
((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2) +
((flags & HAS_OBJECT_VALUE) == 0 ? 0 : 1);
}
private boolean isSlotted() {
return firstSlot != -1 && ((flags & HAS_SLOT) != 0);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append(name).
append(' ');
if (hasSlot()) {
sb.append(' ').
append('(').
append("slot=").
append(firstSlot).append(' ');
if((flags & HAS_INT_VALUE) != 0) { sb.append('I'); }
if((flags & HAS_DOUBLE_VALUE) != 0) { sb.append('D'); }
if((flags & HAS_OBJECT_VALUE) != 0) { sb.append('O'); }
sb.append(')');
}
if (isScope()) {
if(isGlobal()) {
sb.append(" G");
} else {
sb.append(" S");
}
}
return sb.toString();
}
@Override
public int compareTo(final Symbol other) {
return name.compareTo(other.name);
}
/**
* Does this symbol have an allocated bytecode slot? Note that having an allocated bytecode slot doesn't necessarily
* mean the symbol's value will be stored in it. Namely, a function parameter can have a bytecode slot, but if it is
* in scope, then the bytecode slot will not be used. See {@link #isBytecodeLocal()}.
*
* @return true if this symbol has a local bytecode slot
*/
public boolean hasSlot() {
return (flags & HAS_SLOT) != 0;
}
/**
* Is this symbol a local variable stored in bytecode local variable slots? This is true for a slotted variable that
* is not in scope. (E.g. a parameter that is in scope is slotted, but it will not be a local variable).
* @return true if this symbol is using bytecode local slots for its storage.
*/
public boolean isBytecodeLocal() {
return hasSlot() && !isScope();
}
/**
* Returns true if this symbol is dead (it is a local variable that is statically proven to never be read in any type).
* @return true if this symbol is dead
*/
public boolean isDead() {
return (flags & (HAS_SLOT | IS_SCOPE)) == 0;
}
/**
* 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) != 0;
}
/**
* Check if this symbol is a function declaration
* @return true if a function declaration
*/
public boolean isFunctionDeclaration() {
return (flags & IS_FUNCTION_DECLARATION) != 0;
}
/**
* Flag this symbol as scope as described in {@link Symbol#isScope()}
* @return the symbol
*/
public Symbol setIsScope() {
if (!isScope()) {
if(shouldTrace()) {
trace("SET IS SCOPE");
}
flags |= IS_SCOPE;
if(!isParam()) {
flags &= ~HAS_SLOT;
}
}
return this;
}
/**
* Mark this symbol as a function declaration.
*/
public void setIsFunctionDeclaration() {
if (!isFunctionDeclaration()) {
if(shouldTrace()) {
trace("SET IS FUNCTION DECLARATION");
}
flags |= IS_FUNCTION_DECLARATION;
}
}
/**
* 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 is a program (script) level definition
* @return true if program level
*/
public boolean isProgramLevel() {
return (flags & IS_PROGRAM_LEVEL) != 0;
}
/**
* Check if this symbol is a constant
* @return true if a constant
*/
public boolean isConst() {
return (flags & IS_CONST) != 0;
}
/**
* 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) != 0;
}
/**
* 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) != 0;
}
/**
* Is this a block scoped symbol
* @return true if block scoped
*/
public boolean isBlockScoped() {
return isLet() || isConst();
}
/**
* Has this symbol been declared
* @return true if declared
*/
public boolean hasBeenDeclared() {
return (flags & HAS_BEEN_DECLARED) != 0;
}
/**
* Mark this symbol as declared
*/
public void setHasBeenDeclared() {
if (!hasBeenDeclared()) {
flags |= HAS_BEEN_DECLARED;
}
}
/**
* 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
* @return the symbol
*/
public Symbol setFieldIndex(final int fieldIndex) {
if (this.fieldIndex != fieldIndex) {
this.fieldIndex = fieldIndex;
}
return this;
}
/**
* Get the symbol flags
* @return flags
*/
public int getFlags() {
return flags;
}
/**
* Set the symbol flags
* @param flags flags
* @return the symbol
*/
public Symbol setFlags(final int flags) {
if (this.flags != flags) {
this.flags = flags;
}
return this;
}
/**
* Set a single symbol flag
* @param flag flag to set
* @return the symbol
*/
public Symbol setFlag(final int flag) {
if ((this.flags & flag) == 0) {
this.flags |= flag;
}
return this;
}
/**
* Clears a single symbol flag
* @param flag flag to set
* @return the symbol
*/
public Symbol clearFlag(final int flag) {
if ((this.flags & flag) != 0) {
this.flags &= ~flag;
}
return this;
}
/**
* Get the name of this symbol
* @return symbol name
*/
public String getName() {
return name;
}
/**
* Get the index of the first bytecode slot for this symbol
* @return byte code slot
*/
public int getFirstSlot() {
assert isSlotted();
return firstSlot;
}
/**
* Get the index of the bytecode slot for this symbol for storing a value of the specified type.
* @param type the requested type
* @return byte code slot
*/
public int getSlot(final Type type) {
assert isSlotted();
int typeSlot = firstSlot;
if(type.isBoolean() || type.isInteger()) {
assert (flags & HAS_INT_VALUE) != 0;
return typeSlot;
}
typeSlot += ((flags & HAS_INT_VALUE) == 0 ? 0 : 1);
if(type.isNumber()) {
assert (flags & HAS_DOUBLE_VALUE) != 0;
return typeSlot;
}
assert type.isObject();
assert (flags & HAS_OBJECT_VALUE) != 0 : name;
return typeSlot + ((flags & HAS_DOUBLE_VALUE) == 0 ? 0 : 2);
}
/**
* Returns true if this symbol has a local variable slot for storing a value of specific type.
* @param type the type
* @return true if this symbol has a local variable slot for storing a value of specific type.
*/
public boolean hasSlotFor(final Type type) {
if(type.isBoolean() || type.isInteger()) {
return (flags & HAS_INT_VALUE) != 0;
} else if(type.isNumber()) {
return (flags & HAS_DOUBLE_VALUE) != 0;
}
assert type.isObject();
return (flags & HAS_OBJECT_VALUE) != 0;
}
/**
* Marks this symbol as having a local variable slot for storing a value of specific type.
* @param type the type
*/
public void setHasSlotFor(final Type type) {
if(type.isBoolean() || type.isInteger()) {
setFlag(HAS_INT_VALUE);
} else if(type.isNumber()) {
setFlag(HAS_DOUBLE_VALUE);
} else {
assert type.isObject();
setFlag(HAS_OBJECT_VALUE);
}
}
/**
* Increase the symbol's use count by one.
*/
public void increaseUseCount() {
if (isScope()) { // Avoid dirtying a cache line; we only need the use count for scoped symbols
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 firstSlot valid bytecode slot
* @return the symbol
*/
public Symbol setFirstSlot(final int firstSlot) {
assert firstSlot >= 0 && firstSlot <= 65535;
if (firstSlot != this.firstSlot) {
if(shouldTrace()) {
trace("SET SLOT " + firstSlot);
}
this.firstSlot = firstSlot;
}
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
* @return the symbol
*/
public static Symbol setSymbolIsScope(final LexicalContext lc, final Symbol symbol) {
symbol.setIsScope();
if (!symbol.isGlobal()) {
lc.setBlockNeedsScope(lc.getDefiningBlock(symbol));
}
return symbol;
}
private boolean shouldTrace() {
return TRACE_SYMBOLS != null && (TRACE_SYMBOLS.isEmpty() || TRACE_SYMBOLS.contains(name));
}
private void trace(final String desc) {
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());
}
}
private void readObject(final ObjectInputStream in) throws ClassNotFoundException, IOException {
in.defaultReadObject();
firstSlot = -1;
fieldIndex = -1;
}
}