diff options
Diffstat (limited to 'src')
264 files changed, 84712 insertions, 0 deletions
diff --git a/src/META-INF/MANIFEST.MF b/src/META-INF/MANIFEST.MF new file mode 100644 index 00000000..b61190e5 --- /dev/null +++ b/src/META-INF/MANIFEST.MF @@ -0,0 +1,5 @@ +Manifest-Version: 1.0
+Main-Class: jdk.nashorn.tools.Shell
+
+Name: jdk/nashorn/
+Implementation-Vendor: Oracle Corporation
diff --git a/src/META-INF/services/javax.script.ScriptEngineFactory b/src/META-INF/services/javax.script.ScriptEngineFactory new file mode 100644 index 00000000..ec7da013 --- /dev/null +++ b/src/META-INF/services/javax.script.ScriptEngineFactory @@ -0,0 +1,25 @@ +# +# Copyright (c) 2010, 2012, 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. +# +jdk.nashorn.api.scripting.NashornScriptEngineFactory diff --git a/src/jdk/nashorn/api/scripting/NashornException.java b/src/jdk/nashorn/api/scripting/NashornException.java new file mode 100644 index 00000000..f1a42648 --- /dev/null +++ b/src/jdk/nashorn/api/scripting/NashornException.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2010, 2012, 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.api.scripting; + +/** + * This is base exception for all Nashorn exceptions. These originate from user's + * ECMAScript code. Example: script parse errors, exceptions thrown from scripts. + * Note that ScriptEngine methods like "eval", "invokeMethod", "invokeFunction" + * will wrap this as ScriptException and throw it. But, there are cases where user + * may need to access this exception (or implementation defined subtype of this). + * For example, if java interface is implemented by a script object or Java access + * to script object properties via java.util.Map interface. In these cases, user + * code will get an instance of this or implementation defined subclass. + */ +@SuppressWarnings("serial") +public class NashornException extends RuntimeException { + // script file name + private String fileName; + // script line number + private int line; + // script column number + private int column; + + /** + * Constructor + * + * @param msg exception message + */ + protected NashornException(final String msg) { + super(msg); + } + + /** + * Constructor + * @param msg exception message + * @param cause exception cause + */ + protected NashornException(final String msg, final Throwable cause) { + super(msg, cause); + } + + /** + * Constructor + * + * @param cause exception cause + */ + protected NashornException(final Throwable cause) { + super(cause); + } + + /** + * Get the source file name for this {@code NashornException} + * @return the file name + */ + public final String getFileName() { + return fileName; + } + + /** + * Set the source file name for this {@code NashornException} + * @param fileName file name + */ + protected final void setFileName(final String fileName) { + this.fileName = fileName; + } + + /** + * Get the line number for this {@code NashornException} + * @return the line number + */ + public final int getLineNumber() { + return line; + } + + /** + * Set the line number for this {@code NashornException} + * @param line line number + */ + protected final void setLineNumber(final int line) { + this.line = line; + } + + /** + * Get the column for this {@code NashornException} + * @return the column + */ + public final int getColumnNumber() { + return column; + } + + /** + * Set the column number for this {@code NashornException} + * @param column the column + */ + public final void setColumnNumber(final int column) { + this.column = column; + } +} diff --git a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java new file mode 100644 index 00000000..3fde40aa --- /dev/null +++ b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java @@ -0,0 +1,521 @@ +/* + * Copyright (c) 2010, 2012, 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.api.scripting; + +import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; +import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; +import static jdk.nashorn.internal.runtime.linker.Lookup.MH; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.invoke.MethodHandle; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.net.URL; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import javax.script.AbstractScriptEngine; +import javax.script.Bindings; +import javax.script.Compilable; +import javax.script.CompiledScript; +import javax.script.Invocable; +import javax.script.ScriptContext; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import javax.script.ScriptException; +import javax.script.SimpleBindings; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ErrorManager; +import jdk.nashorn.internal.runtime.GlobalObject; +import jdk.nashorn.internal.runtime.Property; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.ScriptRuntime; +import jdk.nashorn.internal.runtime.Source; +import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory; +import jdk.nashorn.internal.runtime.options.Options; + +/** + * JSR-223 compliant script engine for Nashorn. Instances are not created directly, but rather returned through + * {@link NashornScriptEngineFactory#getScriptEngine()}. Note that this engine implements the {@link Compilable} and + * {@link Invocable} interfaces, allowing for efficient precompilation and repeated execution of scripts. + * @see NashornScriptEngineFactory + */ + +public final class NashornScriptEngine extends AbstractScriptEngine implements Compilable, Invocable { + + private final ScriptEngineFactory factory; + private final Context nashornContext; + private final ScriptObject global; + + // default options passed to Nashorn Options object + private static final String[] DEFAULT_OPTIONS = new String[] { "-scripting", "-af", "-doe" }; + + NashornScriptEngine(final NashornScriptEngineFactory factory) { + this(factory, DEFAULT_OPTIONS); + } + + @SuppressWarnings("LeakingThisInConstructor") + NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args) { + this.factory = factory; + final Options options = new Options("nashorn"); + options.process(args); + + // throw ParseException on first error from script + final ErrorManager errors = new Context.ThrowErrorManager(); + // create new Nashorn Context and get global object + this.nashornContext = AccessController.doPrivileged(new PrivilegedAction<Context>() { + @Override + public Context run() { + try { + return new Context(options, errors); + } catch (final RuntimeException e) { + if (Context.DEBUG) { + e.printStackTrace(); + } + throw e; + } + } + }); + + // create new global object + this.global = nashornContext.createGlobal(); + + // current ScriptContext exposed as "context" + global.addOwnProperty("context", Property.NOT_ENUMERABLE, context); + // current ScriptEngine instance exposed as "engine". We added @SuppressWarnings("LeakingThisInConstructor") as + // NetBeans identifies this assignment as such a leak - this is a false positive as we're setting this property + // in the Global of a Context we just created - both the Context and the Global were just created and can not be + // seen from another thread outside of this constructor. + global.addOwnProperty("engine", Property.NOT_ENUMERABLE, this); + // global script arguments + global.addOwnProperty("arguments", Property.NOT_ENUMERABLE, UNDEFINED); + + // evaluate engine initial script + try { + AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() { + @Override + public Void run() throws ScriptException { + evalEngineScript(); + return null; + } + }); + } catch (final PrivilegedActionException e) { + if (Context.DEBUG) { + e.printStackTrace(); + } + throw new RuntimeException(e); + } + } + + @Override + public Object eval(final Reader reader, final ScriptContext ctxt) throws ScriptException { + try { + return evalImpl(Source.readFully(reader), ctxt); + } catch (final IOException e) { + throw new ScriptException(e); + } + } + + @Override + public Object eval(final String script, final ScriptContext ctxt) throws ScriptException { + return evalImpl(script.toCharArray(), ctxt); + } + + /* + * FIXME: This is not exactly right. Script execution should actually + * put the variables in ENGINE_SCOPE bindings. But, it is difficult + * (not possible?) with the way ScriptObject is implemented. So, for now + * giving access to script variables by accessing it from "globals". This + * way at least engine.get("foo") will work whenever "foo" is a global var + * defined by eval'ed scripts. + */ + @Override + public Object get(final String key) { + Object value = super.get(key); + if (value == null) { + value = ScriptObjectMirror.wrap(global.get(key), global); + } + return (value == UNDEFINED) ? null : value; + } + + @Override + public ScriptEngineFactory getFactory() { + return factory; + } + + @Override + public Bindings createBindings() { + return new SimpleBindings(); + } + + // Compilable methods + + @Override + public CompiledScript compile(final Reader reader) throws ScriptException { + try { + return asCompiledScript(compileImpl(Source.readFully(reader), context)); + } catch (final IOException e) { + throw new ScriptException(e); + } + } + + @Override + public CompiledScript compile(final String str) throws ScriptException { + return asCompiledScript(compileImpl(str.toCharArray(), context)); + } + + // Invocable methods + + @Override + public Object invokeFunction(final String name, final Object... args) + throws ScriptException, NoSuchMethodException { + return invokeImpl(null, name, args); + } + + @Override + public Object invokeMethod(final Object self, final String name, final Object... args) + throws ScriptException, NoSuchMethodException { + if (self == null) { + throw new IllegalArgumentException("script object can not be null"); + } + return invokeImpl(self, name, args); + } + + private <T> T getInterfaceInner(final Object self, final Class<T> clazz) { + final Object realSelf; + if(self == null) { + realSelf = global; + } else if (!(self instanceof ScriptObject)) { + realSelf = ScriptObjectMirror.unwrap(self, global); + } else { + realSelf = self; + } + try { + final ScriptObject oldGlobal = Context.getGlobal(); + try { + if(oldGlobal != global) { + setNashornGlobal(global); + } + return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz).invoke(realSelf)); + } finally { + if(oldGlobal != global) { + setNashornGlobal(oldGlobal); + } + } + } catch(final RuntimeException|Error e) { + throw e; + } catch(final Throwable t) { + throw new RuntimeException(t); + } + } + + @Override + public <T> T getInterface(final Class<T> clazz) { + return getInterfaceInner(null, clazz); + } + + @Override + public <T> T getInterface(final Object self, final Class<T> clazz) { + if (self == null) { + throw new IllegalArgumentException("script object can not be null"); + } + return getInterfaceInner(self, clazz); + } + + // These are called from the "engine.js" script + + /** + * This hook is used to search js global variables exposed from Java code. + * + * @param self 'this' passed from the script + * @param ctxt current ScriptContext in which name is searched + * @param name name of the variable searched + * @return the value of the named variable + */ + public Object __noSuchProperty__(final Object self, final ScriptContext ctxt, final String name) { + final int scope = ctxt.getAttributesScope(name); + if (scope != -1) { + return ScriptObjectMirror.unwrap(ctxt.getAttribute(name, scope), global); + } + + if (self == UNDEFINED) { + // scope access and so throw ReferenceError + referenceError(global, "not.defined", name); + } + + return UNDEFINED; + } + + /** + * This hook is used to call js global functions exposed from Java code. + * + * @param self 'this' passed from the script + * @param ctxt current ScriptContext in which method is searched + * @param name name of the method + * @param args arguments to be passed to the method + * @return return value of the called method + */ + public Object __noSuchMethod__(final Object self, final ScriptContext ctxt, final String name, final Object args) { + final int scope = ctxt.getAttributesScope(name); + Object value; + + if (scope != -1) { + value = ctxt.getAttribute(name, scope); + } else { + if (self == UNDEFINED) { + referenceError(global, "not.defined", name); + } else { + typeError(global, "no.such.function", name, ScriptRuntime.safeToString(global)); + } + return UNDEFINED; + } + + value = ScriptObjectMirror.unwrap(value, global); + + if (value instanceof Method) { + final Method method = (Method) value; + final int mods = method.getModifiers(); + if (Modifier.isStatic(mods) && Modifier.isPublic(mods)) { + value = MH.find((Method)value); + } + } + + if (value instanceof MethodHandle) { + value = ((GlobalObject)global).newScriptFunction(name, (MethodHandle)value, null, false); + } + + if (!(value instanceof ScriptFunction)) { + typeError(global, "not.a.function", name); + } + + if (value instanceof ScriptFunction) { + return ScriptObjectMirror.unwrap(ScriptRuntime.apply((ScriptFunction)value, global, args), global); + } + + typeError(global, "not.a.function", ScriptRuntime.safeToString(name)); + + return UNDEFINED; + } + + private void evalEngineScript() throws ScriptException { + final URL url = NashornScriptEngine.class.getResource("resources/engine.js"); + try { + final InputStream is = url.openStream(); + put(ScriptEngine.FILENAME, url); + try (final InputStreamReader isr = new InputStreamReader(is)) { + eval(isr); + } + } catch (final IOException e) { + throw new ScriptException(e); + } finally { + put(ScriptEngine.FILENAME, null); + } + } + + // scripts should see "context" and "engine" as variables + private void setContextVariables(final ScriptContext ctxt) { + ctxt.setAttribute("context", ctxt, ScriptContext.ENGINE_SCOPE); + // current ScriptContext exposed as "context" + global.set("context", ctxt, false); + Object args = ctxt.getAttribute("arguments"); + // if no arguments passed, make it empty array + if (args == null) { + args = ScriptRuntime.EMPTY_ARRAY; + } + args = ((GlobalObject)global).wrapAsObject(args); + global.set("arguments", args, false); + } + + private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException { + final ScriptObject oldGlobal = Context.getGlobal(); + final boolean globalChanged = (oldGlobal != global); + + Object self = selfObject; + + try { + if (globalChanged) { + setNashornGlobal(global); + } + + ScriptObject sobj; + Object value; + + self = ScriptObjectMirror.unwrap(self, global); + + // FIXME: should convert when self is not ScriptObject + if (self instanceof ScriptObject) { + sobj = (ScriptObject)self; + value = sobj.get(name); + } else if (self == null) { + self = global; + sobj = global; + value = sobj.get(name); + } else { + // Find the java method and make a ScriptFunction of it. + final Method[] methods = self.getClass().getMethods(); + Method target = null; + + for (final Method mth : methods) { + // choose the right overload by number of arguments -- don't + // care overload resolution for now.. + if (mth.getName().equals(name) && + mth.getParameterTypes().length == args.length) { + target = mth; + break; + } + } + if (target == null) { + throw new NoSuchMethodException(name); + } + final GlobalObject gobj = (GlobalObject) global; + value = gobj.newScriptFunction(name, MH.find(target), null, false); + } + + if (value instanceof ScriptFunction) { + final Object res; + try { + res = ScriptRuntime.apply((ScriptFunction)value, self, ScriptObjectMirror.unwrapArray(args, global)); + } catch (final Exception e) { + throwAsScriptException(e); + throw new AssertionError("should not reach here"); + } + return ScriptObjectMirror.wrap(res, global); + } + + throw new NoSuchMethodException(name); + } finally { + if (globalChanged) { + setNashornGlobal(oldGlobal); + } + } + } + + private Object evalImpl(final char[] buf, final ScriptContext ctxt) throws ScriptException { + return evalImpl(compileImpl(buf, ctxt), ctxt); + } + + private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt) throws ScriptException { + if (script == null) { + return null; + } + final ScriptObject oldGlobal = Context.getGlobal(); + final boolean globalChanged = (oldGlobal != global); + try { + if (globalChanged) { + setNashornGlobal(global); + } + + setContextVariables(ctxt); + Object res = ScriptRuntime.apply(script, global); + res = ScriptObjectMirror.wrap(res, global); + return (res == UNDEFINED) ? null : res; + } catch (final Exception e) { + throwAsScriptException(e); + throw new AssertionError("should not reach here"); + } finally { + if (globalChanged) { + setNashornGlobal(oldGlobal); + } + } + } + + private static void throwAsScriptException(final Exception e) throws ScriptException { + if (e instanceof ScriptException) { + throw (ScriptException)e; + } else if (e instanceof NashornException) { + final NashornException ne = (NashornException)e; + final ScriptException se = new ScriptException( + ne.getMessage(), ne.getFileName(), + ne.getLineNumber(), ne.getColumnNumber()); + se.initCause(e); + throw se; + } else if (e instanceof RuntimeException) { + throw (RuntimeException)e; + } else { + // wrap any other exception as ScriptException + throw new ScriptException(e); + } + } + + private CompiledScript asCompiledScript(final ScriptFunction script) { + return new CompiledScript() { + @Override + public Object eval(final ScriptContext ctxt) throws ScriptException { + return evalImpl(script, ctxt); + } + @Override + public ScriptEngine getEngine() { + return NashornScriptEngine.this; + } + }; + } + + private ScriptFunction compileImpl(final char[] buf, final ScriptContext ctxt) throws ScriptException { + final ScriptObject oldGlobal = Context.getGlobal(); + final boolean globalChanged = (oldGlobal != global); + try { + final Object val = ctxt.getAttribute(ScriptEngine.FILENAME); + final String fileName = (val != null) ? val.toString() : "<eval>"; + + // !!HACK!! do not evaluate "init.js" from jrunscript tool!! + if ("<system-init>".equals(fileName)) { + return null; + } + + final Source source = new Source(fileName, buf); + if (globalChanged) { + setNashornGlobal(global); + } + + setContextVariables(ctxt); + return nashornContext.compileScript(source, global, nashornContext._strict); + } catch (final Exception e) { + throwAsScriptException(e); + throw new AssertionError("should not reach here"); + } finally { + if (globalChanged) { + setNashornGlobal(oldGlobal); + } + } + } + + // don't make this public!! + static void setNashornGlobal(final ScriptObject global) { + AccessController.doPrivileged(new PrivilegedAction<Void>() { + @Override + public Void run() { + Context.setGlobal(global); + return null; + } + }); + } +} diff --git a/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java b/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java new file mode 100644 index 00000000..2bb6a348 --- /dev/null +++ b/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2010, 2012, 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.api.scripting; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; +import jdk.nashorn.internal.runtime.Version; + +/** + * JSR-223 compliant script engine factory for Nashorn. The engine answers for: + * <ul> + * <li>names {@code "nashorn"}, {@code "Nashorn"}, {@code "js"}, {@code "JS"}, {@code "JavaScript"}, + * {@code "javascript"}, {@code "ECMAScript"}, and {@code "ecmascript"};</li> + * <li>MIME types {@code "application/javascript"}, {@code "application/ecmascript"}, {@code "text/javascript"}, and + * {@code "text/ecmascript"};</li> + * <li>as well as for the extension {@code "js"}.</li> + * </ul> + * Programs executing in engines created using {@link #getScriptEngine(String[])} will have the passed arguments + * accessible as a global variable named {@code "arguments"}. + */ +public final class NashornScriptEngineFactory implements ScriptEngineFactory { + @Override + public String getEngineName() { + return (String) getParameter(ScriptEngine.ENGINE); + } + + @Override + public String getEngineVersion() { + return (String) getParameter(ScriptEngine.ENGINE_VERSION); + } + + @Override + public List<String> getExtensions() { + return Collections.unmodifiableList(extensions); + } + + @Override + public String getLanguageName() { + return (String) getParameter(ScriptEngine.LANGUAGE); + } + + @Override + public String getLanguageVersion() { + return (String) getParameter(ScriptEngine.LANGUAGE_VERSION); + } + + @Override + public String getMethodCallSyntax(final String obj, final String method, final String... args) { + final StringBuilder sb = new StringBuilder().append(obj).append('.').append(method).append('('); + final int len = args.length; + + if (len > 0) { + sb.append(args[0]); + } + for (int i = 1; i < len; i++) { + sb.append(',').append(args[i]); + } + sb.append(')'); + + return sb.toString(); + } + + @Override + public List<String> getMimeTypes() { + return Collections.unmodifiableList(mimeTypes); + } + + @Override + public List<String> getNames() { + return Collections.unmodifiableList(names); + } + + @Override + public String getOutputStatement(final String toDisplay) { + return "print(" + toDisplay + ")"; + } + + @Override + public Object getParameter(final String key) { + switch (key) { + case ScriptEngine.NAME: + return "javascript"; + case ScriptEngine.ENGINE: + return "Oracle Nashorn"; + case ScriptEngine.ENGINE_VERSION: + return Version.version(); + case ScriptEngine.LANGUAGE: + return "ECMAScript"; + case ScriptEngine.LANGUAGE_VERSION: + return "ECMA - 262 Edition 5.1"; + case "THREADING": + // The engine implementation is not thread-safe. Can't be + // used to execute scripts concurrently on multiple threads. + return null; + default: + throw new IllegalArgumentException("Invalid key"); + } + } + + @Override + public String getProgram(final String... statements) { + final StringBuilder sb = new StringBuilder(); + + for (final String statement : statements) { + sb.append(statement).append(';'); + } + + return sb.toString(); + } + + @Override + public ScriptEngine getScriptEngine() { + return new NashornScriptEngine(this); + } + + /** + * Create a new Script engine initialized by given arguments. + * + * @param args arguments array passed to script engine. + * @return newly created script engine. + */ + public ScriptEngine getScriptEngine(final String[] args) { + return new NashornScriptEngine(this, args); + } + + // -- Internals only below this point + + private static final List<String> names; + private static final List<String> mimeTypes; + private static final List<String> extensions; + + static { + names = immutableList( + "nashorn", "Nashorn", + "js", "JS", + "JavaScript", "javascript", + "ECMAScript", "ecmascript" + ); + + mimeTypes = immutableList( + "application/javascript", + "application/ecmascript", + "text/javascript", + "text/ecmascript" + ); + + extensions = immutableList("js"); + } + + private static List<String> immutableList(final String... elements) { + return Collections.unmodifiableList(Arrays.asList(elements)); + } +} diff --git a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java new file mode 100644 index 00000000..44021fad --- /dev/null +++ b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2010, 2012, 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.api.scripting; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; +import netscape.javascript.JSObject; + +/** + * Mirror object that wraps a given ScriptObject instance. User can + * access ScriptObject via the java.util.Map interface. + */ +final class ScriptObjectMirror extends JSObject implements Map<Object, Object> { + private final ScriptObject sobj; + private final ScriptObject global; + + ScriptObjectMirror(final ScriptObject sobj, final ScriptObject global) { + this.sobj = sobj; + this.global = global; + } + + @Override + public boolean equals(final Object other) { + if (other instanceof ScriptObjectMirror) { + return sobj.equals(((ScriptObjectMirror)other).sobj); + } + + return false; + } + + @Override + public int hashCode() { + return sobj.hashCode(); + } + + private <V> V inGlobal(final Callable<V> callable) { + final ScriptObject oldGlobal = Context.getGlobal(); + final boolean globalChanged = (oldGlobal != global); + if (globalChanged) { + NashornScriptEngine.setNashornGlobal(global); + } + try { + return callable.call(); + } catch (final RuntimeException e) { + throw e; + } catch (final Exception e) { + throw new AssertionError("Cannot happen", e); + } finally { + if (globalChanged) { + NashornScriptEngine.setNashornGlobal(oldGlobal); + } + } + } + + // JSObject methods + @Override + public Object call(final String methodName, final Object args[]) { + final Object val = sobj.get(methodName); + final ScriptObject oldGlobal = Context.getGlobal(); + final boolean globalChanged = (oldGlobal != global); + + if (val instanceof ScriptFunction) { + final Object[] modifiedArgs = unwrapArray(args, global); + if (modifiedArgs != null) { + for (int i = 0; i < modifiedArgs.length; i++) { + final Object arg = modifiedArgs[i]; + if (arg instanceof ScriptObject) { + modifiedArgs[i] = wrap(arg, oldGlobal); + } + } + } + + try { + if (globalChanged) { + NashornScriptEngine.setNashornGlobal(global); + } + return wrap(((ScriptFunction)val).invoke(sobj, modifiedArgs), global); + } catch (final RuntimeException | Error e) { + throw e; + } catch (final Throwable t) { + throw new RuntimeException(t); + } finally { + if (globalChanged) { + NashornScriptEngine.setNashornGlobal(oldGlobal); + } + } + } + + throw new RuntimeException("No such method: " + methodName); + } + + @Override + public Object eval(final String s) { + return inGlobal(new Callable<Object>() { + @Override + public Object call() { + return wrap(global.getContext().eval(global, s, null, null, false), global); + } + }); + } + + @Override + public Object getMember(final String name) { + return get(name); + } + + @Override + public Object getSlot(final int index) { + return get(Integer.valueOf(index)); + } + + @Override + public void removeMember(final String name) { + remove(name); + } + + @Override + public void setMember(final String name, final Object value) { + put(name, wrap(value, Context.getGlobal())); + } + + @Override + public void setSlot(final int index, final Object value) { + put(Integer.valueOf(index), wrap(value, Context.getGlobal())); + } + + @Override + public void clear() { + inGlobal(new Callable<Object>() { + @Override public Object call() { + sobj.clear(); + return null; + }}); + } + + @Override + public boolean containsKey(final Object key) { + return inGlobal(new Callable<Boolean>() { + @Override public Boolean call() { + return sobj.containsKey(unwrap(key, global)); + }}); + } + + @Override + public boolean containsValue(final Object value) { + return inGlobal(new Callable<Boolean>() { + @Override public Boolean call() { + return sobj.containsValue(unwrap(value, global)); + }}); + } + + @Override + public Set<Map.Entry<Object, Object>> entrySet() { + return inGlobal(new Callable<Set<Map.Entry<Object, Object>>>() { + @Override public Set<Map.Entry<Object, Object>> call() { + final Iterator<String> iter = sobj.propertyIterator(); + final Set<Map.Entry<Object, Object>> entries = new HashSet<>(); + + while (iter.hasNext()) { + final Object key = wrap(iter.next(), global); + final Object value = wrap(sobj.get(key), global); + entries.add(new AbstractMap.SimpleImmutableEntry<>(key, value)); + } + + return Collections.unmodifiableSet(entries); + } + }); + } + + @Override + public Object get(final Object key) { + return inGlobal(new Callable<Object>() { @Override public Object call() { + return wrap(sobj.get(key), global); + }}); + } + + @Override + public boolean isEmpty() { + return inGlobal(new Callable<Boolean>() { @Override public Boolean call() { + return sobj.isEmpty(); + }}); + } + + @Override + public Set<Object> keySet() { + return inGlobal(new Callable<Set<Object>>() { @Override public Set<Object> call() { + final Iterator<String> iter = sobj.propertyIterator(); + final Set<Object> keySet = new HashSet<>(); + + while (iter.hasNext()) { + keySet.add(wrap(iter.next(), global)); + } + + return Collections.unmodifiableSet(keySet); + }}); + } + + @Override + public Object put(final Object key, final Object value) { + return inGlobal(new Callable<Object>() { + @Override public Object call() { + return sobj.put(unwrap(key, global), unwrap(value, global)); + }}); + } + + @Override + public void putAll(final Map<?, ?> map) { + final boolean strict = sobj.getContext()._strict; + inGlobal(new Callable<Object>() { @Override public Object call() { + for (final Map.Entry<?, ?> entry : map.entrySet()) { + sobj.set(unwrap(entry.getKey(), global), unwrap(entry.getValue(), global), strict); + } + return null; + }}); + } + + @Override + public Object remove(final Object key) { + return inGlobal(new Callable<Object>() { + @Override public Object call() { + return wrap(sobj.remove(unwrap(key, global)), global); + } + }); + } + + @Override + public int size() { + return inGlobal(new Callable<Integer>() { + @Override public Integer call() { + return sobj.size(); + } + }); + } + + @Override + public Collection<Object> values() { + return inGlobal(new Callable<Collection<Object>>() { @Override public Collection<Object> call() { + final List<Object> values = new ArrayList<>(size()); + final Iterator<Object> iter = sobj.valueIterator(); + + while (iter.hasNext()) { + values.add(wrap(iter.next(), global)); + } + + return Collections.unmodifiableList(values); + }}); + } + + static Object wrap(final Object obj, final ScriptObject homeGlobal) { + return (obj instanceof ScriptObject) ? new ScriptObjectMirror((ScriptObject)obj, homeGlobal) : obj; + } + + static Object unwrap(final Object obj, final ScriptObject homeGlobal) { + if (obj instanceof ScriptObjectMirror) { + final ScriptObjectMirror mirror = (ScriptObjectMirror)obj; + return (mirror.global == homeGlobal)? mirror.sobj : obj; + } + + return obj; + } + + static Object[] wrapArray(final Object[] args, final ScriptObject homeGlobal) { + if (args == null || args.length == 0) { + return args; + } + + final Object[] newArgs = new Object[args.length]; + int index = 0; + for (final Object obj : args) { + newArgs[index] = wrap(obj, homeGlobal); + index++; + } + return newArgs; + } + + static Object[] unwrapArray(final Object[] args, final ScriptObject homeGlobal) { + if (args == null || args.length == 0) { + return args; + } + + final Object[] newArgs = new Object[args.length]; + int index = 0; + for (final Object obj : args) { + newArgs[index] = unwrap(obj, homeGlobal); + index++; + } + return newArgs; + } +} diff --git a/src/jdk/nashorn/api/scripting/package-info.java b/src/jdk/nashorn/api/scripting/package-info.java new file mode 100644 index 00000000..32472901 --- /dev/null +++ b/src/jdk/nashorn/api/scripting/package-info.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2010, 2012, 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. + */ + +/** + * This package provides the {@code javax.script} integration, which is the preferred way to use Nashorn. + * You will ordinarily do this to obtain an instance of a Nashorn script engine: + * <pre> + * import javax.script.*; + * ... + * ScriptEngine nashornEngine = new ScriptEngineManager().getEngineByName("Nashorn"); + * </pre> + * <p>Nashorn script engines implement the optional {@link javax.script.Invocable} and {@link javax.script.Compilable} + * interfaces, allowing for efficient pre-compilation and repeated execution of scripts. See + * {@link jdk.nashorn.api.scripting.NashornScriptEngineFactory} for further details. + */ +package jdk.nashorn.api.scripting; diff --git a/src/jdk/nashorn/api/scripting/resources/engine.js b/src/jdk/nashorn/api/scripting/resources/engine.js new file mode 100644 index 00000000..49e8d45b --- /dev/null +++ b/src/jdk/nashorn/api/scripting/resources/engine.js @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2010, 2012, 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. + * + * 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. + */ + +/** + * This script file is executed by script engine at the construction + * of the engine. The functions here assume global variables "context" + * of type javax.script.ScriptContext and "engine" of the type + * jdk.nashorn.api.scripting.NashornScriptEngine. + * + **/ + +Object.defineProperty(this, "__noSuchProperty__", { + configurable: true, + enumerable: false, + writable: true, + value: function (name) { + 'use strict'; + return engine.__noSuchProperty__(this, context, name); + } +}); + +Object.defineProperty(this, "__noSuchMethod__", { + configurable: true, + enumerable: false, + writable: true, + value: function (name, args) { + 'use strict'; + return engine.__noSuchMethod__(this, context, name, args); + } +}); + +function print(str) { + var writer = context.getWriter(); + if (! (writer instanceof java.io.PrintWriter)) { + writer = new java.io.PrintWriter(writer); + } + writer.println(String(str)); +} diff --git a/src/jdk/nashorn/internal/codegen/AccessSpecializer.java b/src/jdk/nashorn/internal/codegen/AccessSpecializer.java new file mode 100644 index 00000000..56a3e7ec --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/AccessSpecializer.java @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import java.util.HashSet; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.AccessNode; +import jdk.nashorn.internal.ir.Assignment; +import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.CallNode; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.IndexNode; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.ReferenceNode; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.ir.TypeOverride; +import jdk.nashorn.internal.ir.UnaryNode; +import jdk.nashorn.internal.ir.VarNode; +import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.parser.Token; +import jdk.nashorn.internal.parser.TokenType; +import jdk.nashorn.internal.runtime.Debug; +import jdk.nashorn.internal.runtime.DebugLogger; + +/** + * This is a post pass for Lower, that removes casts for accessors to objects in + * Scope, and replaces the accessor type with a narrower one with possible. + * + * Any node that implements TypeOverride will be subject to access specialization. + * + * TypeOverride basically means "type inference has determined that the field x + * is an object, but if you do x & 17, it can be read as an int, which we hope + * coincides with its internal representation. In that case there is no boxing + * that may or may not be removed by the JVM and less data bandwidth. + * + * Ideally this should not be a post pass, but it requires slot AND scope info, and has + * to be run where it is, which is called from {@link CodeGenerator}. + * + * @see TypeOverride + */ + +final class AccessSpecializer extends NodeOperatorVisitor implements Transform { + /** Debug logger for access specialization. Enable it with --log=access:level + or -Dnashorn.specializations.debug */ + private static final DebugLogger LOG = new DebugLogger("access", "nashorn.callsiteaccess.debug"); + private static final boolean DEBUG = LOG.isEnabled(); + + @Override + public Node enter(final FunctionNode node) { + if (node.isTransformApplied(AccessSpecializer.class)) { + return null; + } + + return node; + } + + @Override + public Node leave(final FunctionNode node) { + node.registerTransform(AccessSpecializer.class); + return node; + } + + @Override + public Node leave(final VarNode varNode) { + if (varNode.isAssignment()) { + return leaveAssign(varNode); + } + + return varNode; + } + + @Override + public Node leave(final CallNode callNode) { + final Node function = callNode.getFunction(); + if (function instanceof ReferenceNode) { + changeType(callNode, ((ReferenceNode)function).getReference().getType()); + } + return callNode; + } + + @Override + public Node leaveASSIGN(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_ADD(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_BIT_AND(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_BIT_OR(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_BIT_XOR(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_DIV(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_MOD(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_MUL(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_SAR(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_SHL(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_SHR(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_SUB(final BinaryNode binaryNode) { + return leaveAssign(binaryNode); + } + + @Override + public Node leaveCOMMALEFT(final BinaryNode binaryNode) { + return propagateResultType(binaryNode, binaryNode.lhs().getType()); + } + + @Override + public Node leaveCOMMARIGHT(final BinaryNode binaryNode) { + return propagateResultType(binaryNode, binaryNode.rhs().getType()); + } + + @Override + public Node leaveCONVERT(final UnaryNode unaryNode) { + final Type castTo = unaryNode.getType(); + + Node rhs = unaryNode.rhs(); + + // Go through all conversions until we find the first non-convert node. + while (rhs.tokenType() == TokenType.CONVERT) { + rhs = ((UnaryNode)rhs).rhs(); + } + + // If this node can be type changed + if (canHaveCallSiteType(rhs) && isSupportedCallSiteType(castTo)) { + /* + * Just add a callsite type and throw away the cast, appropriate + * getter/setter is selected, which will do the conversion for us + */ + changeType(rhs, castTo); + fine("*** cast: converting " + debugNode(unaryNode) + " to " + debugNode(rhs)); + + return rhs; + } + + // Micro optimization for node hygiene and pattern detection - remove unnecessary same type cast + if (unaryNode.getType().isEquivalentTo(rhs.getType())) { + return rhs; + } + + return unaryNode; + } + + @Override + public Node leaveDECINC(final UnaryNode unaryNode) { + assert unaryNode.isAssignment(); + + final Node dest = unaryNode.getAssignmentDest(); + if (canHaveCallSiteType(dest) && isSupportedCallSiteType(unaryNode.getType())) { + changeTypeInAssignment(dest, unaryNode.getType()); + } + + return unaryNode; + } + + /** + * Is this a node that can have its type overridden. This is true for + * AccessNodes, IndexNodes and IdentNodes + * + * @param node the node to check + * @return true if node can have a callsite type + */ + private static boolean canHaveCallSiteType(final Node node) { + return node instanceof TypeOverride && ((TypeOverride)node).canHaveCallSiteType(); + } + + /** + * Is the specialization type supported. Currently we treat booleans as objects + * and have no special boolean type accessor, thus booleans are ignored. + * TODO - support booleans? NASHORN-590 + * + * @param castTo the type to check + * @return true if call site type is supported + */ + private static boolean isSupportedCallSiteType(final Type castTo) { + return castTo.isNumeric(); // don't specializable for boolean + } + + /** + * Turn a node into a covert node + * + * @param node the node + * @return the node as a convert node + */ + private static Node convert(final Node node) { + return new UnaryNode(node.getSource(), + Token.recast(node.getToken(), TokenType.CONVERT), + node); + } + + private static Node leaveAssign(final Node node) { + assert node.isAssignment() : node + " is not an assignment"; + + + final Node lhs = ((Assignment<?>)node).getAssignmentDest(); + Node rhs = ((Assignment<?>)node).getAssignmentSource(); + + /** + * Nodes with local variable slots are assumed to be of their optimal type + * already and aren't affected here. This is not strictly true, for instance + * with doubles instead of in a bounded loop. TODO - range check: NASHORN-363 + * + * This is also not strictly true for var y = x = 55; where y has no other uses + * Then y can be an int, but lower conservatively does an object of the assign to + * scope + */ + final Symbol lhsSymbol = lhs.getSymbol(); + + if (lhsSymbol.hasSlot() && !lhsSymbol.isScope()) { + finest(lhs.getSymbol() + " has slot!"); + if (!lhs.getType().isEquivalentTo(rhs.getType())) { + finest("\tslot assignment: " +lhs.getType()+ " " +rhs.getType() + " " + debugNode(node)); + + final Node c = convert(rhs); + c.setSymbol(lhsSymbol); + ((Assignment<?>)node).setAssignmentSource(c); + + fine("*** slot assignment turned to : " + debugNode(node)); + } else { + finest("aborted - type equivalence between lhs and rhs"); + } + + return node; + } + + // e.g. __DIR__, __LINE__, __FILE__ - don't try to change these + if (lhs instanceof IdentNode && ((IdentNode)lhs).isSpecialIdentity()) { + return node; + } + + /** + * Try to cast to the type of the right hand side, now pruned. E.g. an (object)17 should + * now be just 17, an int. + */ + Type castTo = rhs.getType(); + + // If LHS can't get a new type, neither can rhs - retain the convert + if (!canHaveCallSiteType(lhs)) { + return node; + } + + // Take the narrowest type of the entire cast sequence + while (rhs.tokenType() == TokenType.CONVERT) { + rhs = ((UnaryNode)rhs).rhs(); + castTo = Type.narrowest(rhs.getType(), castTo); //e.g. (object)(int) -> int even though object is outermost + } + + // If castTo is wider than widestOperationType, castTo can be further slowed down + final Type widestOperationType = node.getWidestOperationType(); + finest("node wants to be " + castTo + " and its widest operation is " + widestOperationType); + + if (widestOperationType != castTo && Type.widest(castTo, widestOperationType) == castTo) { + info("###" + node + " castTo was " + castTo + " but could be downgraded to " + node.getWidestOperationType()); + castTo = node.getWidestOperationType(); + if (rhs instanceof TypeOverride) { + changeType(rhs, castTo); + } + } + + /* + * If this is a self modifying op, we can't be narrower than the widest optype + * or e.g. x = x + 12 and x += 12 will turn into different things + */ + if (node.isSelfModifying()) { + castTo = Type.widest(widestOperationType, castTo); + } + + // We only specialize for numerics, not for booleans. + if (isSupportedCallSiteType(castTo)) { + if (rhs.getType() != castTo) { + finest("cast was necessary, abort: " + node + " " + rhs.getType() + " != " + castTo); + return node; + } + + finest("assign: " + debugNode(node)); + + changeTypeInAssignment(lhs, castTo); + ((Assignment<?>)node).setAssignmentSource(rhs); + + info("### modified to " + debugNode(node) + " (given type override " + castTo + ")"); + + propagateResultType(node, castTo); + } + + return node; + } + + private static Node propagateResultType(final Node node, final Type type) { + //warning! this CANNOT be done for non temporaries as they are used in other computations + if (isSupportedCallSiteType(type)) { + if (node.getSymbol().isTemp()) { + finest("changing temporary type: " + debugNode(node) + " to " + type); + node.getSymbol().setTypeOverride(type); + info("### node modified to " + debugNode(node) + " (given type override " + type + ")"); + } + } + return node; + } + + private static void changeTypeInAssignment(final Node dest, final Type newType) { + if (changeType(dest, newType)) { + finest("changed assignment " + dest + " " + dest.getSymbol()); + assert !newType.isObject(); + + final HashSet<Node> exclude = new HashSet<>(); + + dest.accept(new NodeVisitor() { + + private void setCanBePrimitive(final Symbol symbol) { + fine("*** can be primitive symbol " + symbol + " " + Debug.id(symbol)); + symbol.setCanBePrimitive(newType); + } + + @Override + public Node enter(final IdentNode identNode) { + if (!exclude.contains(identNode)) { + setCanBePrimitive(identNode.getSymbol()); + } + return null; + } + + @Override + public Node enter(final AccessNode accessNode) { + setCanBePrimitive(accessNode.getProperty().getSymbol()); + return null; + } + + @Override + public Node enter(final IndexNode indexNode) { + exclude.add(indexNode.getBase()); //prevent array base node to be flagged as primitive, but k in a[k++] is fine + return indexNode; + } + + }); + } + } + + private static boolean changeType(final Node node, final Type newType) { + if (!node.getType().equals(newType)) { + ((TypeOverride)node).setType(newType); + return true; + } + return false; + } + + private static String debugNode(final Node node) { + if (DEBUG) { + return node.toString(); + } + return ""; + } + + private static void info(final String str) { + LOG.info(str); + } + + private static void fine(final String str) { + LOG.fine(str); + } + + private static void finest(final String str) { + LOG.finest(str); + } + +} diff --git a/src/jdk/nashorn/internal/codegen/BranchOptimizer.java b/src/jdk/nashorn/internal/codegen/BranchOptimizer.java new file mode 100644 index 00000000..e15633f8 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/BranchOptimizer.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.nashorn.internal.codegen.MethodEmitter.Condition.EQ; +import static jdk.nashorn.internal.codegen.MethodEmitter.Condition.GE; +import static jdk.nashorn.internal.codegen.MethodEmitter.Condition.GT; +import static jdk.nashorn.internal.codegen.MethodEmitter.Condition.LE; +import static jdk.nashorn.internal.codegen.MethodEmitter.Condition.LT; +import static jdk.nashorn.internal.codegen.MethodEmitter.Condition.NE; + +import jdk.nashorn.internal.codegen.MethodEmitter.Label; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.TernaryNode; +import jdk.nashorn.internal.ir.UnaryNode; + +/** + * Branch optimizer for CodeGenerator. Given a jump condition this helper + * class attempts to simplify the control flow + */ +final class BranchOptimizer { + + private final CodeGenerator codegen; + private final MethodEmitter method; + + BranchOptimizer(final CodeGenerator codegen, final MethodEmitter method) { + this.codegen = codegen; + this.method = method; + } + + void execute(final Node node, final Label label, final boolean state) { + branchOptimizer(node, label, state); + } + + private void load(final Node node) { + codegen.load(node); + } + + private void branchOptimizer(final UnaryNode unaryNode, final Label label, final boolean state) { + final Node rhs = unaryNode.rhs(); + + switch (unaryNode.tokenType()) { + case NOT: + branchOptimizer(rhs, label, !state); + return; + case CONVERT: + if (unaryNode.getType().isBoolean()) { + branchOptimizer(rhs, label, state); + return; + } + break; + default: + break; + } + + // convert to boolean + load(unaryNode); + method.convert(Type.BOOLEAN); + if (state) { + method.ifne(label); + } else { + method.ifeq(label); + } + } + + private void branchOptimizer(final BinaryNode binaryNode, final Label label, final boolean state) { + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + switch (binaryNode.tokenType()) { + case AND: + if (state) { + final Label skip = new Label("skip"); + branchOptimizer(lhs, skip, false); + branchOptimizer(rhs, label, true); + method.label(skip); + } else { + branchOptimizer(lhs, label, false); + branchOptimizer(rhs, label, false); + } + return; + + case OR: + if (state) { + branchOptimizer(lhs, label, true); + branchOptimizer(rhs, label, true); + } else { + final Label skip = new Label("skip"); + branchOptimizer(lhs, skip, true); + branchOptimizer(rhs, label, false); + method.label(skip); + } + return; + + case EQ: + case EQ_STRICT: + assert rhs.getType().isEquivalentTo(lhs.getType()) : "type mismatch: " + lhs.getSymbol() + " to " + rhs.getSymbol(); + load(lhs); + load(rhs); + method.conditionalJump(state ? EQ : NE, true, label); + return; + + case NE: + case NE_STRICT: + assert rhs.getType().isEquivalentTo(lhs.getType()) : "type mismatch: " + lhs.getSymbol() + " to " + rhs.getSymbol(); + load(lhs); + load(rhs); + method.conditionalJump(state ? NE : EQ, true, label); + return; + + case GE: + assert rhs.getType().isEquivalentTo(lhs.getType()) : "type mismatch: " + lhs.getSymbol() + " to " + rhs.getSymbol(); + load(lhs); + load(rhs); + method.conditionalJump(state ? GE : LT, !state, label); + return; + + case GT: + assert rhs.getType().isEquivalentTo(lhs.getType()) : "type mismatch: " + lhs.getSymbol() + " to " + rhs.getSymbol(); + load(lhs); + load(rhs); + method.conditionalJump(state ? GT : LE, !state, label); + return; + + case LE: + assert rhs.getType().isEquivalentTo(lhs.getType()) : "type mismatch: " + lhs.getSymbol() + " to " + rhs.getSymbol(); + load(lhs); + load(rhs); + method.conditionalJump(state ? LE : GT, state, label); + return; + + case LT: + assert rhs.getType().isEquivalentTo(lhs.getType()) : "type mismatch: " + lhs.getSymbol() + " to " + rhs.getSymbol() + " in " + binaryNode; + load(lhs); + load(rhs); + method.conditionalJump(state ? LT : GE, state, label); + return; + + default: + break; + } + + load(binaryNode); + method.convert(Type.BOOLEAN); + if (state) { + method.ifne(label); + } else { + method.ifeq(label); + } + } + + private void branchOptimizer(final Node node, final Label label, final boolean state) { + if (!(node instanceof TernaryNode)) { + + if (node instanceof BinaryNode) { + branchOptimizer((BinaryNode)node, label, state); + return; + } + + if (node instanceof UnaryNode) { + branchOptimizer((UnaryNode)node, label, state); + return; + } + } + + load(node); + method.convert(Type.BOOLEAN); + if (state) { + method.ifne(label); + } else { + method.ifeq(label); + } + } +} diff --git a/src/jdk/nashorn/internal/codegen/ClassEmitter.java b/src/jdk/nashorn/internal/codegen/ClassEmitter.java new file mode 100644 index 00000000..9b95cf83 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/ClassEmitter.java @@ -0,0 +1,620 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_FINAL; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_VARARGS; +import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKEINTERFACE; +import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESPECIAL; +import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKEVIRTUAL; +import static jdk.internal.org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL; +import static jdk.internal.org.objectweb.asm.Opcodes.V1_7; +import static jdk.nashorn.internal.codegen.CompilerConstants.CLINIT; +import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.GET_ARRAY_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.GET_ARRAY_SUFFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP; +import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING; +import static jdk.nashorn.internal.codegen.CompilerConstants.INIT; +import static jdk.nashorn.internal.codegen.CompilerConstants.SET_MAP; +import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; +import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE; +import static jdk.nashorn.internal.codegen.CompilerConstants.className; +import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; +import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; +import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; + +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; +import jdk.internal.org.objectweb.asm.ClassReader; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.internal.org.objectweb.asm.util.TraceClassVisitor; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.Source; + +/** + * The interface responsible for speaking to ASM, emitting classes, + * fields and methods. + * <p> + * This file contains the ClassEmitter, which is the master object + * responsible for writing byte codes. It utilizes a MethodEmitter + * for method generation, which also the NodeVisitors own, to keep + * track of the current code generator and what it is doing. + * <p> + * There is, however, nothing stopping you from using this in a + * completely self contained environment, for example in ObjectGenerator + * where there are no visitors or external hooks. + * <p> + * MethodEmitter makes it simple to generate code for methods without + * having to do arduous type checking. It maintains a type stack + * and will pick the appropriate operation for all operations sent to it + * We also allow chained called to a MethodEmitter for brevity, e.g. + * it is legal to write _new(className).dup() or + * load(slot).load(slot2).xor().store(slot3); + * <p> + * If running with assertions enabled, any type conflict, such as different + * bytecode stack sizes or operating on the wrong type will be detected + * and an error thrown. + * <p> + * There is also a very nice debug interface that can emit formatted + * bytecodes that have been written. This is enabled by setting the + * environment "nashorn.codegen.debug" to true, or --log=codegen:<level> + * <p> + * A ClassEmitter implements an Emitter - i.e. it needs to have + * well defined start and end calls for whatever it is generating. Assertions + * detect if this is not true + * + * @see Compiler + * @see CodeGenerator + */ +public class ClassEmitter implements Emitter { + + /** Sanity check flag - have we started on a class? */ + private boolean classStarted; + + /** Sanity check flag - have we ended this emission? */ + private boolean classEnded; + + /** + * Sanity checks - which methods have we currently + * started for generation in this class? + */ + private final HashSet<MethodEmitter> methodsStarted; + + /** The ASM classwriter that we use for all bytecode operations */ + protected final ClassWriter cw; + + /** The context */ + protected final Context context; + + /** Default flags for class generation - oublic class */ + private static final EnumSet<Flag> DEFAULT_METHOD_FLAGS = EnumSet.of(Flag.PUBLIC); + + /** Compile unit class name. */ + private String unitClassName; + + /** Set of constants access methods required. */ + private Set<Class<?>> constantMethodNeeded; + + /** + * Constructor - only used internally in this class as it breaks + * abstraction towards ASM or other code generator below + * + * @param context context + * @param cw ASM classwriter + */ + private ClassEmitter(final Context context, final ClassWriter cw) { + assert context != null; + + this.context = context; + this.cw = cw; + this.methodsStarted = new HashSet<>(); + } + + /** + * Constructor + * + * @param context context + * @param className name of class to weave + * @param superClassName super class name for class + * @param interfaceNames names of interfaces implemented by this class, or null if none + */ + public ClassEmitter(final Context context, final String className, final String superClassName, final String... interfaceNames) { + this(context, new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS)); + cw.visit(V1_7, ACC_PUBLIC | ACC_SUPER, className, null, superClassName, interfaceNames); + } + + /** + * Constructor from the compiler + * + * @param compiler Compiler + * @param unitClassName Compile unit class name. + * @param strictMode Should we generate this method in strict mode + */ + ClassEmitter(final Compiler compiler, final String unitClassName, final boolean strictMode) { + this(compiler.getContext(), + new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) { + private static final String OBJECT_CLASS = "java/lang/Object"; + + @Override + protected String getCommonSuperClass(final String type1, final String type2) { + try { + return super.getCommonSuperClass(type1, type2); + } catch (final RuntimeException e) { + if (isScriptObject(Compiler.SCRIPTS_PACKAGE, type1) && isScriptObject(Compiler.SCRIPTS_PACKAGE, type2)) { + return className(ScriptObject.class); + } + return OBJECT_CLASS; + } + } + }); + + this.unitClassName = unitClassName; + this.constantMethodNeeded = new HashSet<>(); + + cw.visit(V1_7, ACC_PUBLIC | ACC_SUPER, unitClassName, null, Compiler.pathName(jdk.nashorn.internal.scripts.JS$.class.getName()), null); + cw.visitSource(compiler.getSource().getName(), null); + + defineCommonStatics(strictMode); + } + + /** + * Define the static fields common in all scripts. + * @param strictMode Should we generate this method in strict mode + */ + private void defineCommonStatics(final boolean strictMode) { + // source - used to store the source data (text) for this script. Shared across + // compile units. Set externally by the compiler. + field(EnumSet.of(Flag.PUBLIC, Flag.STATIC), SOURCE.tag(), Source.class); + + // constants - used to the constants array for this script. Shared across + // compile units. Set externally by the compiler. + field(EnumSet.of(Flag.PUBLIC, Flag.STATIC), CONSTANTS.tag(), Object[].class); + + // strictMode - was this script compiled in strict mode. Set externally by the compiler. + field(EnumSet.of(Flag.PUBLIC, Flag.STATIC, Flag.FINAL), STRICT_MODE.tag(), boolean.class, strictMode); + } + + /** + * Define static utilities common needed in scripts. These are per compile unit + * and therefore have to be defined here and not in code gen. + */ + private void defineCommonUtilities() { + assert unitClassName != null; + + if (constantMethodNeeded.contains(String.class)) { + // $getString - get the ith entry from the constants table and cast to String. + final MethodEmitter getStringMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), GET_STRING.tag(), String.class, int.class); + getStringMethod.begin(); + getStringMethod.getStatic(unitClassName, CONSTANTS.tag(), CONSTANTS.descriptor()) + .load(Type.INT, 0) + .arrayload() + .checkcast(String.class) + ._return(); + getStringMethod.end(); + } + + if (constantMethodNeeded.contains(PropertyMap.class)) { + // $getMap - get the ith entry from the constants table and cast to PropertyMap. + final MethodEmitter getMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), GET_MAP.tag(), PropertyMap.class, int.class); + getMapMethod.begin(); + getMapMethod.loadConstants(unitClassName) + .load(Type.INT, 0) + .arrayload() + .checkcast(PropertyMap.class) + ._return(); + getMapMethod.end(); + + // $setMap - overwrite an existing map. + final MethodEmitter setMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), SET_MAP.tag(), void.class, int.class, PropertyMap.class); + setMapMethod.begin(); + setMapMethod.loadConstants(unitClassName) + .load(Type.INT, 0) + .load(Type.OBJECT, 1) + .arraystore(); + setMapMethod.returnVoid(); + setMapMethod.end(); + } + + // $getXXXX$array - get the ith entry from the constants table and cast to XXXX[]. + for (final Class<?> cls : constantMethodNeeded) { + if (cls.isArray()) { + defineGetArrayMethod(cls); + } + } + } + + /** + * Constructs a primitive specific method for getting the ith entry from the constants table and cast. + * @param cls Array class. + */ + private void defineGetArrayMethod(final Class<?> cls) { + assert unitClassName != null; + + final String methodName = getArrayMethodName(cls); + final MethodEmitter getArrayMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), methodName, cls, int.class); + + getArrayMethod.begin(); + getArrayMethod.getStatic(unitClassName, CONSTANTS.tag(), CONSTANTS.descriptor()) + .load(Type.INT, 0) + .arrayload() + .checkcast(cls) + .dup() + .arraylength() + .invoke(staticCallNoLookup(Arrays.class, "copyOf", cls, cls, int.class)) + ._return(); + getArrayMethod.end(); + } + + /** + * Generate the name of a get array from constant pool method. + * @param cls Name of array class. + * @return Method name. + */ + static String getArrayMethodName(final Class<?> cls) { + assert cls.isArray(); + return GET_ARRAY_PREFIX.tag() + cls.getComponentType().getSimpleName() + GET_ARRAY_SUFFIX.tag(); + } + + /** + * Ensure a get constant method is issued for the class. + * @param cls Class of constant. + */ + public void needGetConstantMethod(final Class<?> cls) { + constantMethodNeeded.add(cls); + } + + /** + * Inspect class name and decide whether we are generating a ScriptObject class + * + * @param scriptPrefix the script class prefix for the current script + * @param type the type to check + * + * @return true if type is ScriptObject + */ + private static boolean isScriptObject(final String scriptPrefix, final String type) { + if (type.startsWith(scriptPrefix)) { + return true; + } else if (type.equals(CompilerConstants.className(ScriptObject.class))) { + return true; + } else if (type.startsWith(Compiler.OBJECTS_PACKAGE)) { + return true; + } + + return false; + } + + /** + * Call at beginning of class emission + * @see Emitter + */ + @Override + public void begin() { + classStarted = true; + } + + /** + * Call at end of class emission + * @see Emitter + */ + @Override + public void end() { + assert classStarted; + + if (unitClassName != null) { + defineCommonUtilities(); + } + + cw.visitEnd(); + classStarted = false; + classEnded = true; + assert methodsStarted.isEmpty() : "methodsStarted not empty " + methodsStarted; + } + + /** + * Disassemble an array of byte code. + * + * @param context the context + * @param bytecode byte array representing bytecode + */ + public static void disassemble(final Context context, final byte[] bytecode) { + new ClassReader(bytecode).accept(new TraceClassVisitor(context.getErr()), 0); + } + + /** + * Verify an array of byte code as a valid Java class + * + * @param context the context + * @param bytecode the bytecode array + */ + public static void verify(final Context context, final byte[] bytecode) { + context.verify(bytecode); + } + + /** + * @return context used for class emission + */ + Context getContext() { + return context; + } + + /** + * Call back from MethodEmitter for method start + * + * @see MethodEmitter + * + * @param method method emitter. + */ + void beginMethod(final MethodEmitter method) { + assert !methodsStarted.contains(method); + methodsStarted.add(method); + } + + /** + * Call back from MethodEmitter for method end + * + * @see MethodEmitter + * + * @param method + */ + void endMethod(final MethodEmitter method) { + assert methodsStarted.contains(method); + methodsStarted.remove(method); + } + + /** + * Add a new method to the class - defaults to public method + * + * @param methodName name of method + * @param rtype return type of the method + * @param ptypes parameter types the method + * + * @return method emitter to use for weaving this method + */ + public MethodEmitter method(final String methodName, final Class<?> rtype, final Class<?>... ptypes) { + return method(DEFAULT_METHOD_FLAGS, methodName, rtype, ptypes); //TODO why public default ? + } + + /** + * Add a new method to the class - defaults to public method + * + * @param methodFlags access flags for the method + * @param methodName name of method + * @param rtype return type of the method + * @param ptypes parameter types the method + * + * @return method emitter to use for weaving this method + */ + public MethodEmitter method(final EnumSet<Flag> methodFlags, final String methodName, final Class<?> rtype, final Class<?>... ptypes) { + return new MethodEmitter(this, cw.visitMethod(Flag.getValue(methodFlags), methodName, methodDescriptor(rtype, ptypes), null, null)); + } + + /** + * Add a new method to the class - defaults to public method + * + * @param methodName name of method + * @param descriptor descriptor of method + * + * @return method emitter to use for weaving this method + */ + public MethodEmitter method(final String methodName, final String descriptor) { + return method(DEFAULT_METHOD_FLAGS, methodName, descriptor); + } + + /** + * Add a new method to the class - defaults to public method + * + * @param methodFlags access flags for the method + * @param methodName name of method + * @param descriptor descriptor of method + * + * @return method emitter to use for weaving this method + */ + public MethodEmitter method(final EnumSet<Flag> methodFlags, final String methodName, final String descriptor) { + return new MethodEmitter(this, cw.visitMethod(Flag.getValue(methodFlags), methodName, descriptor, null, null)); + } + + /** + * Add a new method to the class, representing a function node + * + * @param functionNode the function node to generate a method for + * @return method emitter to use for weaving this method + */ + public MethodEmitter method(final FunctionNode functionNode) { + final MethodVisitor mv = cw.visitMethod( + ACC_PUBLIC | ACC_STATIC | (functionNode.isVarArg() ? ACC_VARARGS : 0), + functionNode.getName(), + FunctionSignature.functionSignature(functionNode), + null, + null); + + return new MethodEmitter(this, mv, functionNode); + } + + /** + * Start generating the <clinit> method in the class + * + * @return method emitter to use for weaving <clinit> + */ + public MethodEmitter clinit() { + return method(EnumSet.of(Flag.STATIC), CLINIT.tag(), void.class); + } + + /** + * Start generating an <init>()V method in the class + * + * @return method emitter to use for weaving <init>()V + */ + public MethodEmitter init() { + return method(INIT.tag(), void.class); + } + + /** + * Start generating an <init>()V method in the class + * + * @param ptypes parameter types for constructor + * @return method emitter to use for weaving <init>()V + */ + public MethodEmitter init(final Class<?>... ptypes) { + return method(INIT.tag(), void.class, ptypes); + } + + /** + * Start generating an <init>(...)V method in the class + * + * @param flags access flags for the constructor + * @param ptypes parameter types for the constructor + * + * @return method emitter to use for weaving <init>(...)V + */ + public MethodEmitter init(final EnumSet<Flag> flags, final Class<?>... ptypes) { + return method(flags, INIT.tag(), void.class, ptypes); + } + + /** + * Add a field to the class, initialized to a value + * + * @param fieldFlags flags, e.g. should it be static or public etc + * @param fieldName name of field + * @param fieldType the type of the field + * @param value the value + * + * @see ClassEmitter.Flag + */ + public final void field(final EnumSet<Flag> fieldFlags, final String fieldName, final Class<?> fieldType, final Object value) { + cw.visitField(Flag.getValue(fieldFlags), fieldName, typeDescriptor(fieldType), null, value).visitEnd(); + } + + /** + * Add a field to the class + * + * @param fieldFlags access flags for the field + * @param fieldName name of field + * @param fieldType type of the field + * + * @see ClassEmitter.Flag + */ + public final void field(final EnumSet<Flag> fieldFlags, final String fieldName, final Class<?> fieldType) { + field(fieldFlags, fieldName, fieldType, null); + } + + /** + * Add a field to the class - defaults to public + * + * @param fieldName name of field + * @param fieldType type of field + */ + public final void field(final String fieldName, final Class<?> fieldType) { + field(EnumSet.of(Flag.PUBLIC), fieldName, fieldType, null); + } + + /** + * Return a bytecode array from this ClassEmitter. The ClassEmitter must + * have been ended (having its end function called) for this to work. + * + * @return byte code array for generated class, null if class generation hasn't been ended with {@link ClassEmitter#end()} + */ + public byte[] toByteArray() { + assert classEnded; + if (!classEnded) { + return null; + } + + return cw.toByteArray(); + } + + /** + * Abstraction for flags used in class emission + * + * We provide abstraction separating these from the underlying bytecode + * emitter. + * + * Flags are provided for method handles, protection levels, static/virtual + * fields/methods. + */ + public static enum Flag { + /** method handle with static access */ + HANDLE_STATIC(H_INVOKESTATIC), + /** method handle with new invoke special access */ + HANDLE_NEWSPECIAL(H_NEWINVOKESPECIAL), + /** method handle with invoke special access */ + HANDLE_SPECIAL(H_INVOKESPECIAL), + /** method handle with invoke virtual access */ + HANDLE_VIRTUAL(H_INVOKEVIRTUAL), + /** method handle with invoke interface access */ + HANDLE_INTERFACE(H_INVOKEINTERFACE), + + /** final access */ + FINAL(ACC_FINAL), + /** static access */ + STATIC(ACC_STATIC), + /** public access */ + PUBLIC(ACC_PUBLIC), + /** private access */ + PRIVATE(ACC_PRIVATE); + + private int value; + + private Flag(final int value) { + this.value = value; + } + + /** + * Get the value of this flag + * @return the int value + */ + public int getValue() { + return value; + } + + /** + * Return the corresponding ASM flag value for an enum set of flags + * + * @param flags enum set of flags + * @return an integer value representing the flags intrinsic values or:ed together + */ + public static int getValue(final EnumSet<Flag> flags) { + int v = 0; + for (final Flag flag : flags) { + v |= flag.getValue(); + } + return v; + } + } + +} diff --git a/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/src/jdk/nashorn/internal/codegen/CodeGenerator.java new file mode 100644 index 00000000..71dc3a7b --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -0,0 +1,3266 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.PRIVATE; +import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.STATIC; +import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP; +import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING; +import static jdk.nashorn.internal.codegen.CompilerConstants.LEAF; +import static jdk.nashorn.internal.codegen.CompilerConstants.QUICK_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.REGEX_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; +import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_ARRAY_ARG; +import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup; +import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; +import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; +import static jdk.nashorn.internal.codegen.CompilerConstants.staticField; +import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; +import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; +import static jdk.nashorn.internal.ir.Symbol.IS_TEMP; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_FAST_SCOPE; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_FUNCTION_DECLARATION; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_SCOPE; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import jdk.nashorn.internal.codegen.ClassEmitter.Flag; +import jdk.nashorn.internal.codegen.CompilerConstants.Call; +import jdk.nashorn.internal.codegen.MethodEmitter.Condition; +import jdk.nashorn.internal.codegen.MethodEmitter.Label; +import jdk.nashorn.internal.codegen.RuntimeCallSite.SpecializedRuntimeNode; +import jdk.nashorn.internal.codegen.objects.FieldObjectCreator; +import jdk.nashorn.internal.codegen.objects.FunctionObjectCreator; +import jdk.nashorn.internal.codegen.objects.MapCreator; +import jdk.nashorn.internal.codegen.objects.ObjectMapCreator; +import jdk.nashorn.internal.codegen.types.ArrayType; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.AccessNode; +import jdk.nashorn.internal.ir.BaseNode; +import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.BreakNode; +import jdk.nashorn.internal.ir.CallNode; +import jdk.nashorn.internal.ir.CaseNode; +import jdk.nashorn.internal.ir.CatchNode; +import jdk.nashorn.internal.ir.ContinueNode; +import jdk.nashorn.internal.ir.DoWhileNode; +import jdk.nashorn.internal.ir.EmptyNode; +import jdk.nashorn.internal.ir.ExecuteNode; +import jdk.nashorn.internal.ir.ForNode; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.IfNode; +import jdk.nashorn.internal.ir.IndexNode; +import jdk.nashorn.internal.ir.LineNumberNode; +import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.ObjectNode; +import jdk.nashorn.internal.ir.PropertyNode; +import jdk.nashorn.internal.ir.ReferenceNode; +import jdk.nashorn.internal.ir.ReturnNode; +import jdk.nashorn.internal.ir.RuntimeNode; +import jdk.nashorn.internal.ir.RuntimeNode.Request; +import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.SwitchNode; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.ir.TernaryNode; +import jdk.nashorn.internal.ir.ThrowNode; +import jdk.nashorn.internal.ir.TryNode; +import jdk.nashorn.internal.ir.UnaryNode; +import jdk.nashorn.internal.ir.VarNode; +import jdk.nashorn.internal.ir.WhileNode; +import jdk.nashorn.internal.ir.WithNode; +import jdk.nashorn.internal.ir.debug.ASTWriter; +import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.parser.Lexer.RegexToken; +import jdk.nashorn.internal.parser.TokenType; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.ECMAException; +import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.Scope; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.ScriptRuntime; +import jdk.nashorn.internal.runtime.Source; +import jdk.nashorn.internal.runtime.Undefined; +import jdk.nashorn.internal.runtime.linker.LinkerCallSite; + +/** + * This is the lowest tier of the code generator. It takes lowered ASTs emitted + * from Lower and emits Java byte code. The byte code emission logic is broken + * out into MethodEmitter. MethodEmitter works internally with a type stack, and + * keeps track of the contents of the byte code stack. This way we avoid a large + * number of special cases on the form + * <pre> + * {@code + * if (type == INT) { + * visitInsn(ILOAD, slot); + * } else if (type == DOUBLE) { + * visitInsn(DOUBLE, slot); + * } + * } + * </pre> + * This quickly became apparent when the code generator was generalized to work + * with all types, and not just numbers or objects. + * <p> + * The CodeGenerator visits nodes only once, tags them as resolved and emits + * bytecode for them. + */ +public final class CodeGenerator extends NodeOperatorVisitor { + + /** Current compiler */ + private final Compiler compiler; + + /** Compiler context */ + private final Context context; + + /** Call site flags given to the code generator to be used for all generated call sites */ + private final int callSiteFlags; + + /** How many regexp fields have been emitted */ + private int regexFieldCount; + + /** Map of shared scope call sites */ + private final Map<SharedScopeCall, SharedScopeCall> scopeCalls = new HashMap<>(); + + /** When should we stop caching regexp expressions in fields to limit bytecode size? */ + private static final int MAX_REGEX_FIELDS = 2 * 1024; + + /** + * Constructor. + * + * @param compiler + */ + CodeGenerator(final Compiler compiler) { + this.compiler = compiler; + this.context = compiler.getContext(); + this.callSiteFlags = context._callsite_flags; + } + + /** + * Get the compiler + * + * @return the compiler used + */ + public Compiler getCompiler() { + return compiler; + } + + /** + * Gets the call site flags, adding the strict flag if the current function + * being generated is in strict mode + * + * @return the correct flags for a call site in the current function + */ + public int getCallSiteFlags() { + return getCurrentFunctionNode().isStrictMode() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags; + } + + /** + * Load an identity node + * + * @param identNode an identity node to load + * @return the method generator used + */ + private MethodEmitter loadIdent(final IdentNode identNode) { + final Symbol symbol = identNode.getSymbol(); + + if (!symbol.isScope()) { + assert symbol.hasSlot() && symbol.getSlot() != 0 || symbol.isThis(); + return method.load(symbol); + } + + final String name = symbol.getName(); + + if (CompilerConstants.__FILE__.name().equals(name)) { + return method.load(identNode.getSource().getName()); + } else if (CompilerConstants.__DIR__.name().equals(name)) { + return method.load(identNode.getSource().getBase()); + } else if (CompilerConstants.__LINE__.name().equals(name)) { + return method.load(identNode.getSource().getLine(identNode.position())).convert(Type.OBJECT); + } else { + assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; + + final int flags = CALLSITE_SCOPE | getCallSiteFlags(); + method.loadScope(); + + if (symbol.isFastScope(getCurrentFunctionNode())) { + // Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope. + if (symbol.getUseCount() > SharedScopeCall.FAST_SCOPE_GET_THRESHOLD) { + return loadSharedScopeVar(identNode.getType(), symbol, flags); + } + return loadFastScopeVar(identNode.getType(), symbol, flags, identNode.isFunction()); + } + return method.dynamicGet(identNode.getType(), identNode.getName(), flags, identNode.isFunction()); + } + } + + private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) { + method.load(symbol.isFastScope(getCurrentFunctionNode()) ? getScopeProtoDepth(getCurrentBlock(), symbol) : -1); + final SharedScopeCall scopeCall = getScopeGet(valueType, symbol, flags | CALLSITE_FAST_SCOPE); + scopeCall.generateInvoke(method); + return method; + } + + private MethodEmitter loadFastScopeVar(final Type valueType, final Symbol symbol, final int flags, final boolean isMethod) { + loadFastScopeProto(symbol, false); + method.dynamicGet(valueType, symbol.getName(), flags | CALLSITE_FAST_SCOPE, isMethod); + return method; + } + + private MethodEmitter storeFastScopeVar(final Type valueType, final Symbol symbol, final int flags) { + loadFastScopeProto(symbol, true); + method.dynamicSet(valueType, symbol.getName(), flags | CALLSITE_FAST_SCOPE); + return method; + } + + private static int getScopeProtoDepth(final Block currentBlock, final Symbol symbol) { + if (currentBlock == symbol.getBlock()) { + return 0; + } + + final int delta = currentBlock.needsScope() ? 1 : 0; + final Block parentBlock = currentBlock.getParent(); + + if (parentBlock != null) { + final int result = getScopeProtoDepth(parentBlock, symbol); + if (result != -1) { + return delta + result; + } + } + + if (currentBlock instanceof FunctionNode) { + for (final Block lookupBlock : ((FunctionNode)currentBlock).getReferencingParentBlocks()) { + final int result = getScopeProtoDepth(lookupBlock, symbol); + if (result != -1) { + return delta + result; + } + } + } + + return -1; + } + + private void loadFastScopeProto(final Symbol symbol, final boolean swap) { + final int depth = getScopeProtoDepth(getCurrentBlock(), symbol); + assert depth != -1; + if(depth > 0) { + if (swap) { + method.swap(); + } + for (int i = 0; i < depth; i++) { + method.invoke(ScriptObject.GET_PROTO); + } + if (swap) { + method.swap(); + } + } + } + + /** + * Generate code that loads this node to the stack. This method is only + * public to be accessible from the maps sub package. Do not call externally + * + * @param node node to load + * + * @return the method emitter used + */ + public MethodEmitter load(final Node node) { + return load(node, false); + } + + private MethodEmitter load(final Node node, final boolean baseAlreadyOnStack) { + final Symbol symbol = node.getSymbol(); + + // If we lack symbols, we just generate what we see. + if (symbol == null) { + node.accept(this); + return method; + } + + /* + * The load may be of type IdentNode, e.g. "x", AccessNode, e.g. "x.y" + * or IndexNode e.g. "x[y]". Both AccessNodes and IndexNodes are + * BaseNodes and the logic for loading the base object is reused + */ + final CodeGenerator codegen = this; + + node.accept(new NodeVisitor(compileUnit, method) { + @Override + public Node enter(final IdentNode identNode) { + loadIdent(identNode); + return null; + } + + @Override + public Node enter(final AccessNode accessNode) { + if (!baseAlreadyOnStack) { + load(accessNode.getBase()).convert(Type.OBJECT); + } + assert method.peekType().isObject(); + method.dynamicGet(node.getType(), accessNode.getProperty().getName(), getCallSiteFlags(), accessNode.isFunction()); + return null; + } + + @Override + public Node enter(final IndexNode indexNode) { + if (!baseAlreadyOnStack) { + load(indexNode.getBase()); + load(indexNode.getIndex()); + } + method.dynamicGetIndex(node.getType(), getCallSiteFlags(), indexNode.isFunction()); + return null; + } + + @Override + public Node enterDefault(final Node otherNode) { + otherNode.accept(codegen); // generate code for whatever we are looking at. + method.load(symbol); // load the final symbol to the stack (or nop if no slot, then result is already there) + return null; + } + }); + + return method; + } + + @Override + public Node enter(final AccessNode accessNode) { + if (accessNode.testResolved()) { + return null; + } + + load(accessNode); + + return null; + } + + /** + * Initialize a specific set of vars to undefined. This has to be done at + * the start of each method for local variables that aren't passed as + * parameters. + * + * @param symbols list of symbols. + */ + private void initSymbols(final Iterable<Symbol> symbols) { + final LinkedList<Symbol> numbers = new LinkedList<>(); + final LinkedList<Symbol> objects = new LinkedList<>(); + + for (final Symbol symbol : symbols) { + /* + * The following symbols are guaranteed to be defined and thus safe + * from having unsigned written to them: parameters internals this + * + * Otherwise we must, unless we perform control/escape analysis, + * assign them undefined. + */ + final boolean isInternal = symbol.isParam() || symbol.isInternal() || symbol.isThis() || !symbol.canBeUndefined(); + + if (symbol.hasSlot() && !isInternal) { + assert symbol.getSymbolType().isNumber() || symbol.getSymbolType().isObject() : "no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + getCurrentFunctionNode(); + if (symbol.getSymbolType().isNumber()) { + numbers.add(symbol); + } else if (symbol.getSymbolType().isObject()) { + objects.add(symbol); + } + } + } + + initSymbols(numbers, Type.NUMBER); + initSymbols(objects, Type.OBJECT); + } + + private void initSymbols(final LinkedList<Symbol> symbols, final Type type) { + if (symbols.isEmpty()) { + return; + } + + method.loadUndefined(type); + while (!symbols.isEmpty()) { + final Symbol symbol = symbols.removeFirst(); + if (!symbols.isEmpty()) { + method.dup(); + } + method.store(symbol); + } + } + + /** + * Create symbol debug information. + * + * @param block block containing symbols. + */ + private void symbolInfo(final Block block) { + for (final Symbol symbol : block.getFrame().getSymbols()) { + method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel()); + } + } + + @Override + public Node enter(final Block block) { + if (block.testResolved()) { + return null; + } + + method.label(block.getEntryLabel()); + initLocals(block); + + return block; + } + + @Override + public Node leave(final Block block) { + method.label(block.getBreakLabel()); + symbolInfo(block); + + if (block.needsScope()) { + popBlockScope(block); + } + + return block; + } + + private void popBlockScope(final Block block) { + final Label exitLabel = new Label("block_exit"); + final Label recoveryLabel = new Label("block_catch"); + final Label skipLabel = new Label("skip_catch"); + + /* pop scope a la try-finally */ + method.loadScope(); + method.invoke(ScriptObject.GET_PROTO); + method.storeScope(); + method._goto(skipLabel); + method.label(exitLabel); + + method._catch(recoveryLabel); + method.loadScope(); + method.invoke(ScriptObject.GET_PROTO); + method.storeScope(); + method.athrow(); + method.label(skipLabel); + method._try(block.getEntryLabel(), exitLabel, recoveryLabel, Throwable.class); + } + + @Override + public Node enter(final BreakNode breakNode) { + if (breakNode.testResolved()) { + return null; + } + + for (int i = 0; i < breakNode.getScopeNestingLevel(); i++) { + closeWith(); + } + + method.splitAwareGoto(breakNode.getTargetLabel()); + + return null; + } + + private MethodEmitter loadArgs(final List<Node> args) { + return loadArgs(args, null, false, args.size()); + } + + private MethodEmitter loadArgs(final List<Node> args, final String signature, final boolean isVarArg, final int argCount) { + // arg have already been converted to objects here. + if (isVarArg || argCount > LinkerCallSite.ARGLIMIT) { + loadArgsArray(args); + return method; + } + + // pad with undefined if size is too short. argCount is the real number of args + int n = 0; + final Type[] params = signature == null ? null : Type.getMethodArguments(signature); + for (final Node arg : args) { + assert arg != null; + load(arg); + if (params != null) { + method.convert(params[n]); + } + n++; + if (n >= argCount) { + break; + } + } + + while (n < argCount) { + method.loadUndefined(Type.OBJECT); + n++; + } + + return method; + } + + /** + * Create a new function object, including generating its stub code. + * + * @param functionNode FunctionNode to utilize. + */ + private void newFunctionObject(final FunctionNode functionNode) { + // Turn thisProperties into keys and symbols for the FunctionAnalyzer + final Map<String, Node> thisProperties = functionNode.getThisProperties(); + + final List<String> keys = new ArrayList<>(); + final List<Symbol> symbols = new ArrayList<>(); + + for (final Entry<String, Node> entry : thisProperties.entrySet()) { + keys.add(entry.getKey()); + symbols.add(entry.getValue().getSymbol()); + } + + new FunctionObjectCreator(this, functionNode, keys, symbols).makeObject(method); + } + + @Override + public Node enter(final CallNode callNode) { + if (callNode.testResolved()) { + return null; + } + + final List<Node> args = callNode.getArgs(); + final Node function = callNode.getFunction(); + final FunctionNode currentFunction = getCurrentFunctionNode(); + final Block currentBlock = getCurrentBlock(); + + function.accept(new NodeVisitor(compileUnit, method) { + + private void sharedScopeCall(final IdentNode identNode, final int flags) { + final Symbol symbol = identNode.getSymbol(); + int scopeCallFlags = flags; + method.loadScope(); + if (symbol.isFastScope(currentFunction)) { + method.load(getScopeProtoDepth(currentBlock, symbol)); + scopeCallFlags |= CALLSITE_FAST_SCOPE; + } else { + method.load(-1); // Bypass fast-scope code in shared callsite + } + loadArgs(args); + final Type[] paramTypes = method.getTypesFromStack(args.size()); + final SharedScopeCall scopeCall = getScopeCall(symbol, identNode.getType(), callNode.getType(), paramTypes, scopeCallFlags); + scopeCall.generateInvoke(method); + } + + private void scopeCall(final IdentNode node, final int flags) { + load(node); + method.convert(Type.OBJECT); // foo() makes no sense if foo == 3 + // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. + method.loadNull(); + loadArgs(args); + method.dynamicCall(callNode.getType(), args.size(), flags); + } + + private void evalCall(final IdentNode node, final int flags) { + load(node); + method.convert(Type.OBJECT); // foo() makes no sense if foo == 3 + + final Label not_eval = new Label("not_eval"); + final Label eval_done = new Label("eval_done"); + + // check if this is the real built-in eval + method.dup(); + globalIsEval(); + + method.ifeq(not_eval); + // We don't need ScriptFunction object for 'eval' + method.pop(); + + method.loadScope(); // Load up self (scope). + + final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); + // load evaluated code + load(evalArgs.code); + method.convert(Type.OBJECT); + // special/extra 'eval' arguments + load(evalArgs.evalThis); + method.load(evalArgs.location); + method.load(evalArgs.strictMode); + method.convert(Type.OBJECT); + + // direct call to Global.directEval + globalDirectEval(); + method.convert(callNode.getType()); + method._goto(eval_done); + + method.label(not_eval); + // This is some scope 'eval' or global eval replaced by user + // but not the built-in ECMAScript 'eval' function call + method.loadNull(); + loadArgs(args); + method.dynamicCall(callNode.getType(), args.size(), flags); + + method.label(eval_done); + } + + @Override + public Node enter(final IdentNode node) { + final Symbol symbol = node.getSymbol(); + + if (symbol.isScope()) { + final int flags = getCallSiteFlags() | CALLSITE_SCOPE; + final int useCount = symbol.getUseCount(); + + // Threshold for generating shared scope callsite is lower for fast scope symbols because we know + // we can dial in the correct scope. However, we als need to enable it for non-fast scopes to + // support huge scripts like mandreel.js. + if (callNode.isEval()) { + evalCall(node, flags); + } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD + || (!symbol.isFastScope(currentFunction) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD) + || callNode.inWithBlock()) { + scopeCall(node, flags); + } else { + sharedScopeCall(node, flags); + } + assert method.peekType().equals(callNode.getType()); + } else { + enterDefault(node); + } + + return null; + } + + @Override + public Node enter(final AccessNode node) { + load(node.getBase()); + method.convert(Type.OBJECT); + method.dup(); + method.dynamicGet(node.getType(), node.getProperty().getName(), getCallSiteFlags(), true); + method.swap(); + loadArgs(args); + method.dynamicCall(callNode.getType(), args.size(), getCallSiteFlags()); + assert method.peekType().equals(callNode.getType()); + + return null; + } + + @Override + public Node enter(final ReferenceNode node) { + final FunctionNode callee = node.getReference(); + final boolean isVarArg = callee.isVarArg(); + final int argCount = isVarArg ? -1 : callee.getParameters().size(); + + final String signature = new FunctionSignature(true, callee.needsCallee(), callee.getReturnType(), isVarArg ? null : callee.getParameters()).toString(); + + if (callee.isStrictMode()) { // self is undefined + method.loadUndefined(Type.OBJECT); + } else { // get global from scope (which is the self) + globalInstance(); + } + + if (callee.needsCallee()) { // TODO: always true + newFunctionObject(callee); // TODO: if callee not needed, function object is used only to pass scope (could be optimized). if neither the scope nor the function object is needed by the callee, we can pass null instead. + } + + loadArgs(args, signature, isVarArg, argCount); + method.invokeStatic(callee.getCompileUnit().getUnitClassName(), callee.getName(), signature); + assert method.peekType().equals(callee.getReturnType()) : method.peekType() + " != " + callee.getReturnType(); + + return null; + } + + @Override + public Node enter(final IndexNode node) { + load(node.getBase()); + method.convert(Type.OBJECT); + method.dup(); + load(node.getIndex()); + final Type indexType = node.getIndex().getType(); + if (indexType.isObject() || indexType.isBoolean()) { + method.convert(Type.OBJECT); //TODO + } + method.dynamicGetIndex(node.getType(), getCallSiteFlags(), true); + method.swap(); + loadArgs(args); + method.dynamicCall(callNode.getType(), args.size(), getCallSiteFlags()); + assert method.peekType().equals(callNode.getType()); + + return null; + } + + @Override + protected Node enterDefault(final Node node) { + // Load up function. + load(function); + method.convert(Type.OBJECT); //TODO, e.g. booleans can be used as functions + method.loadNull(); // ScriptFunction will figure out the correct this when it sees CALLSITE_SCOPE + + loadArgs(args); + method.dynamicCall(callNode.getType(), args.size(), getCallSiteFlags() | CALLSITE_SCOPE); + assert method.peekType().equals(callNode.getType()); + + return null; + } + }); + + method.store(callNode.getSymbol()); + + return null; + } + + @Override + public Node enter(final ContinueNode continueNode) { + if (continueNode.testResolved()) { + return null; + } + + for (int i = 0; i < continueNode.getScopeNestingLevel(); i++) { + closeWith(); + } + + method.splitAwareGoto(continueNode.getTargetLabel()); + + return null; + } + + @Override + public Node enter(final DoWhileNode doWhileNode) { + return enter((WhileNode)doWhileNode); + } + + @Override + public Node enter(final EmptyNode emptyNode) { + return null; + } + + @Override + public Node enter(final ExecuteNode executeNode) { + if (executeNode.testResolved()) { + return null; + } + + final Node expression = executeNode.getExpression(); + expression.accept(this); + + return null; + } + + @Override + public Node enter(final ForNode forNode) { + if (forNode.testResolved()) { + return null; + } + + final Node test = forNode.getTest(); + final Block body = forNode.getBody(); + final Node modify = forNode.getModify(); + + final Label breakLabel = forNode.getBreakLabel(); + final Label continueLabel = forNode.getContinueLabel(); + final Label loopLabel = new Label("loop"); + + Node init = forNode.getInit(); + + if (forNode.isForIn()) { + final Symbol iter = forNode.getIterator(); + + // We have to evaluate the optional initializer expression + // of the iterator variable of the for-in statement. + if (init instanceof VarNode) { + init.accept(this); + init = ((VarNode)init).getName(); + } + + load(modify); + assert modify.getType().isObject(); + method.invoke(forNode.isForEach() ? ScriptRuntime.TO_VALUE_ITERATOR : ScriptRuntime.TO_PROPERTY_ITERATOR); + method.store(iter); + method._goto(continueLabel); + method.label(loopLabel); + + new Store<Node>(init) { + @Override + protected void evaluate() { + method.load(iter); + method.invoke(interfaceCallNoLookup(Iterator.class, "next", Object.class)); + } + }.store(); + + body.accept(this); + + method.label(continueLabel); + method.load(iter); + method.invoke(interfaceCallNoLookup(Iterator.class, "hasNext", boolean.class)); + method.ifne(loopLabel); + method.label(breakLabel); + } else { + if (init != null) { + init.accept(this); + } + + final Label testLabel = new Label("test"); + + method._goto(testLabel); + method.label(loopLabel); + body.accept(this); + method.label(continueLabel); + + if (!body.isTerminal() && modify != null) { + load(modify); + } + + method.label(testLabel); + if (test != null) { + new BranchOptimizer(this, method).execute(test, loopLabel, true); + } else { + method._goto(loopLabel); + } + + method.label(breakLabel); + } + + return null; + } + + private void initLocals(final Block block) { + initLocals(block, null); + } + + /** + * Initialize the slots in a frame to undefined. + * + * @param block block with local vars. + */ + private void initLocals(final Block block, final AccessSpecializer accessSpecializer) { + final FunctionNode function = block.getFunction(); + final boolean isFunctionNode = block == function; + + /* + * Get the symbols from the frame and realign the frame so that all + * slots get correct numbers. The slot numbering is not fixed until + * after initLocals has been run + */ + final Frame frame = block.getFrame(); + final List<Symbol> symbols = frame.getSymbols(); + + /* Fix the predefined slots so they have numbers >= 0, like varargs. */ + frame.realign(); + + /* + * Determine if block needs scope, if not, just do initSymbols for this block. + */ + if (block.needsScope()) { + /* + * Determine if function is varargs and consequently variables have to + * be in the scope. + */ + final boolean isVarArg = function.isVarArg(); + final boolean varsInScope = function.varsInScope(); + + // TODO for LET we can do better: if *block* does not contain any eval/with, we don't need its vars in scope. + + final List<String> nameList = new ArrayList<>(); + final List<Symbol> locals = new ArrayList<>(); + + // note that in the case of a varargs vector the arguments in the vector should NOT have local slots + // this was a bug: see NASHORN-21 + if (isVarArg) { + for (final IdentNode param : function.getParameters()) { + param.getSymbol().setNeedsSlot(false); + } + } + + // If there are variable arguments, we need to load them (functions only). + if (isFunctionNode && isVarArg) { + method.loadVarArgs(); + method.loadCallee(); + method.load(function.getParameters().size()); + globalAllocateArguments(); + method.storeArguments(); + } + + // Initalize symbols and values + final List<Symbol> newSymbols = new ArrayList<>(); + final List<Symbol> values = new ArrayList<>(); + + for (final Symbol symbol : symbols) { + if (symbol.isInternal() || symbol.isThis()) { + continue; + } + + if (symbol.isVar() && (varsInScope || symbol.isScope())) { + nameList.add(symbol.getName()); + newSymbols.add(symbol); + values.add(null); + symbol.setIsScope(); + symbol.setNeedsSlot(false); + } else if (symbol.isVar()) { + assert symbol.hasSlot() : symbol + " should have a slot only, no scope"; + locals.add(symbol); + } else if (symbol.isParam() && (varsInScope || isVarArg || symbol.isScope())) { + nameList.add(symbol.getName()); + newSymbols.add(symbol); + values.add(isVarArg ? null : symbol); + symbol.setIsScope(); + if (isVarArg) { + symbol.setNeedsSlot(false); + } + } + } + + /* Correct slot numbering again */ + frame.realign(); + + // we may have locals that need to be initialized + initSymbols(locals); + + if (isFunctionNode) { + initScope(); + } + + if (accessSpecializer != null) { + ((FunctionNode)block).accept(accessSpecializer); + } + + /* + * Create a new object based on the symbols and values, generate + * bootstrap code for object + */ + final FieldObjectCreator<Symbol> foc = new FieldObjectCreator<Symbol>(this, nameList, newSymbols, values, true, isVarArg) { + @Override + protected Type getValueType(final Symbol value) { + return value.getSymbolType(); + } + + @Override + protected void loadValue(final Symbol value) { + method.load(value); + } + }; + foc.makeObject(method); + + // runScript(): merge scope into global + if (isFunctionNode && function.isScript()) { + method.invoke(ScriptRuntime.MERGE_SCOPE); + } + + method.storeScope(); + } else { + initSymbols(symbols); + + if (isFunctionNode) { + initScope(); + } + } + + // Debugging: print symbols? @see --print-symbols flag + printSymbols(block, (isFunctionNode ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName())); + } + + private void initScope() { + method.loadCallee(); + method.invoke(ScriptFunction.GET_SCOPE); + method.storeScope(); + } + + @Override + public Node enter(final FunctionNode functionNode) { + if (functionNode.testResolved()) { + return null; + } + + compileUnit = functionNode.getCompileUnit(); + assert compileUnit != null; + + method = compileUnit.getClassEmitter().method(functionNode); + functionNode.setMethodEmitter(method); + // Mark end for variable tables. + method.begin(); + method.label(functionNode.getEntryLabel()); + + initLocals(functionNode, new AccessSpecializer()); + + return functionNode; + } + + @Override + public Node leave(final FunctionNode functionNode) { + // Mark end for variable tables. + method.label(functionNode.getBreakLabel()); + + if (!functionNode.needsScope()) { + method.markerVariable(LEAF.tag(), functionNode.getEntryLabel(), functionNode.getBreakLabel()); + } + + symbolInfo(functionNode); + try { + method.end(); // wrap up this method + } catch (final Throwable t) { + Context.printStackTrace(t); + final VerifyError e = new VerifyError("Code generation bug in \"" + functionNode.getName() + "\": likely stack misaligned: " + t + " " + functionNode.getSource().getName()); + e.initCause(t); + throw e; + } + + return functionNode; + } + + @Override + public Node enter(final IdentNode identNode) { + return null; + } + + @Override + public Node enter(final IfNode ifNode) { + if (ifNode.testResolved()) { + return null; + } + + final Node test = ifNode.getTest(); + final Block pass = ifNode.getPass(); + final Block fail = ifNode.getFail(); + + final Label failLabel = new Label("if_fail"); + final Label afterLabel = fail == null ? failLabel : new Label("if_done"); + + new BranchOptimizer(this, method).execute(test, failLabel, false); + + boolean passTerminal = false; + boolean failTerminal = false; + + pass.accept(this); + if (!pass.hasTerminalFlags()) { + method._goto(afterLabel); //don't fallthru to fail block + } else { + passTerminal = pass.isTerminal(); + } + + if (fail != null) { + method.label(failLabel); + fail.accept(this); + failTerminal = fail.isTerminal(); + } + + //if if terminates, put the after label there + if (!passTerminal || !failTerminal) { + method.label(afterLabel); + } + + return null; + } + + @Override + public Node enter(final IndexNode indexNode) { + if (indexNode.testResolved()) { + return null; + } + + load(indexNode); + + return null; + } + + @Override + public Node enter(final LineNumberNode lineNumberNode) { + if (lineNumberNode.testResolved()) { + return null; + } + + final Label label = new Label("line:" + lineNumberNode.getLineNumber() + " (" + getCurrentFunctionNode().getName() + ")"); + method.label(label); + method.lineNumber(lineNumberNode.getLineNumber(), label); + + return null; + } + + /** + * Load a list of nodes as an array of a specific type + * The array will contain the visited nodes. + * + * @param arrayLiteralNode the array of contents + * @param arrayType the type of the array, e.g. ARRAY_NUMBER or ARRAY_OBJECT + * + * @return the method generator that was used + */ + private MethodEmitter loadArray(final ArrayLiteralNode arrayLiteralNode, final ArrayType arrayType) { + assert arrayType == Type.INT_ARRAY || arrayType == Type.NUMBER_ARRAY || arrayType == Type.OBJECT_ARRAY; + + final Node[] nodes = arrayLiteralNode.getValue(); + final Object presets = arrayLiteralNode.getPresets(); + final int[] postsets = arrayLiteralNode.getPostsets(); + final Class<?> type = arrayType.getTypeClass(); + final List<ArrayUnit> units = arrayLiteralNode.getUnits(); + + loadConstant(presets); + + final Type elementType = arrayType.getElementType(); + + if (units != null) { + final CompileUnit savedCompileUnit = compileUnit; + final MethodEmitter savedMethod = method; + + try { + for (final ArrayUnit unit : units) { + compileUnit = unit.getCompileUnit(); + + final String className = compileUnit.getUnitClassName(); + final String name = compiler.uniqueName(SPLIT_PREFIX.tag()); + final String signature = methodDescriptor(type, Object.class, ScriptFunction.class, ScriptObject.class, type); + + method = compileUnit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature); + method.setFunctionNode(getCurrentFunctionNode()); + method.begin(); + + fixScopeSlot(); + + method.load(arrayType, SPLIT_ARRAY_ARG.slot()); + + for (int i = unit.getLo(); i < unit.getHi(); i++) { + storeElement(nodes, elementType, postsets[i]); + } + + method._return(); + method.end(); + + savedMethod.loadThis(); + savedMethod.swap(); + savedMethod.loadCallee(); + savedMethod.swap(); + savedMethod.loadScope(); + savedMethod.swap(); + savedMethod.invokeStatic(className, name, signature); + } + } finally { + compileUnit = savedCompileUnit; + method = savedMethod; + } + + return method; + } + + for (final int postset : postsets) { + storeElement(nodes, elementType, postset); + } + + return method; + } + + private void storeElement(final Node[] nodes, final Type elementType, final int index) { + method.dup(); + method.load(index); + + final Node element = nodes[index]; + + if (element == null) { + method.loadEmpty(elementType); + } else { + assert elementType.isEquivalentTo(element.getType()) : "array element type doesn't match array type"; + load(element); + } + + method.arraystore(); + } + + private MethodEmitter loadArgsArray(final List<Node> args) { + final Object[] array = new Object[args.size()]; + loadConstant(array); + + for (int i = 0; i < args.size(); i++) { + method.dup(); + method.load(i); + load(args.get(i)).convert(Type.OBJECT); //has to be upcast to object or we fail + method.arraystore(); + } + + return method; + } + + /** + * Load a constant from the constant array. This is only public to be callable from the objects + * subpackage. Do not call directly. + * + * @param string string to load + */ + public void loadConstant(final String string) { + final String unitClassName = compileUnit.getUnitClassName(); + final ClassEmitter classEmitter = compileUnit.getClassEmitter(); + final int index = compiler.getConstantData().add(string); + + method.load(index); + method.invokeStatic(unitClassName, GET_STRING.tag(), methodDescriptor(String.class, int.class)); + classEmitter.needGetConstantMethod(String.class); + } + + /** + * Load a constant from the constant array. This is only public to be callable from the objects + * subpackage. Do not call directly. + * + * @param object object to load + */ + public void loadConstant(final Object object) { + final String unitClassName = compileUnit.getUnitClassName(); + final ClassEmitter classEmitter = compileUnit.getClassEmitter(); + final int index = compiler.getConstantData().add(object); + final Class<?> cls = object.getClass(); + + if (cls == PropertyMap.class) { + method.load(index); + method.invokeStatic(unitClassName, GET_MAP.tag(), methodDescriptor(PropertyMap.class, int.class)); + classEmitter.needGetConstantMethod(PropertyMap.class); + } else if (cls.isArray()) { + method.load(index); + final String methodName = ClassEmitter.getArrayMethodName(cls); + method.invokeStatic(unitClassName, methodName, methodDescriptor(cls, int.class)); + classEmitter.needGetConstantMethod(cls); + } else { + method.loadConstants(unitClassName).load(index).arrayload(); + if (cls != Object.class) { + method.checkcast(cls); + } + } + } + + // literal values + private MethodEmitter load(final LiteralNode<?> node) { + final Object value = node.getValue(); + + if (value == null) { + method.loadNull(); + } else if (value instanceof Undefined) { + method.loadUndefined(Type.OBJECT); + } else if (value instanceof String) { + final String string = (String)value; + + if (string.length() > (MethodEmitter.LARGE_STRING_THRESHOLD / 3)) { // 3 == max bytes per encoded char + loadConstant(string); + } else { + method.load(string); + } + } else if (value instanceof RegexToken) { + loadRegex((RegexToken)value); + } else if (value instanceof Boolean) { + method.load((Boolean)value); + } else if (value instanceof Integer) { + method.load((Integer)value); + } else if (value instanceof Long) { + method.load((Long)value); + } else if (value instanceof Double) { + method.load((Double)value); + } else if (node instanceof ArrayLiteralNode) { + final ArrayType type = (ArrayType)node.getType(); + loadArray((ArrayLiteralNode)node, type); + globalAllocateArray(type); + } else { + assert false : "Unknown literal for " + node.getClass() + " " + value.getClass() + " " + value; + } + + return method; + } + + private MethodEmitter loadRegexToken(final RegexToken value) { + method.load(value.getExpression()); + method.load(value.getOptions()); + return globalNewRegExp(); + } + + private MethodEmitter loadRegex(final RegexToken regexToken) { + if (regexFieldCount > MAX_REGEX_FIELDS) { + return loadRegexToken(regexToken); + } + // emit field + final String regexName = compiler.uniqueName(REGEX_PREFIX.tag()); + final ClassEmitter classEmitter = compileUnit.getClassEmitter(); + + classEmitter.field(EnumSet.of(PRIVATE, STATIC), regexName, Object.class); + regexFieldCount++; + + // get field, if null create new regex, finally clone regex object + method.getStatic(compileUnit.getUnitClassName(), regexName, typeDescriptor(Object.class)); + method.dup(); + final Label cachedLabel = new Label("cached"); + method.ifnonnull(cachedLabel); + + method.pop(); + loadRegexToken(regexToken); + method.dup(); + method.putStatic(compileUnit.getUnitClassName(), regexName, typeDescriptor(Object.class)); + + method.label(cachedLabel); + globalRegExpCopy(); + + return method; + } + + @SuppressWarnings("rawtypes") + @Override + public Node enter(final LiteralNode literalNode) { + load(literalNode).store(literalNode.getSymbol()); + return null; + } + + @Override + public Node enter(final ObjectNode objectNode) { + if (objectNode.testResolved()) { + return null; + } + + final List<Node> elements = objectNode.getElements(); + final int size = elements.size(); + + final List<String> keys = new ArrayList<>(); + final List<Symbol> symbols = new ArrayList<>(); + final List<Node> values = new ArrayList<>(); + + boolean hasGettersSetters = false; + + for (int i = 0; i < size; i++) { + final PropertyNode propertyNode = (PropertyNode)elements.get(i); + final Node value = propertyNode.getValue(); + final String key = propertyNode.getKeyName(); + final Symbol symbol = value == null ? null : propertyNode.getSymbol(); + + if (value == null) { + hasGettersSetters = true; + } + + keys.add(key); + symbols.add(symbol); + values.add(value); + } + + new FieldObjectCreator<Node>(this, keys, symbols, values) { + @Override + protected Type getValueType(final Node node) { + return node.getType(); + } + + @Override + protected void loadValue(final Node node) { + load(node); + } + + /** + * Ensure that the properties start out as object types so that + * we can do putfield initializations instead of dynamicSetIndex + * which would be the case to determine initial property type + * otherwise. + * + * Use case, it's very expensive to do a million var x = {a:obj, b:obj} + * just to have to invalidate them immediately on initialization + * + * see NASHORN-594 + */ + @Override + protected MapCreator newMapCreator(final Class<?> fieldObjectClass) { + return new ObjectMapCreator(fieldObjectClass, keys, symbols); + } + + }.makeObject(method); + + method.dup(); + globalObjectPrototype(); + method.invoke(ScriptObject.SET_PROTO); + + if (!hasGettersSetters) { + method.store(objectNode.getSymbol()); + return null; + } + + for (final Node element : elements) { + final PropertyNode propertyNode = (PropertyNode)element; + final Object key = propertyNode.getKey(); + final ReferenceNode getter = (ReferenceNode)propertyNode.getGetter(); + final ReferenceNode setter = (ReferenceNode)propertyNode.getSetter(); + + if (getter == null && setter == null) { + continue; + } + + method.dup().loadKey(key); + + if (getter == null) { + method.loadNull(); + } else { + getter.accept(this); + } + + if (setter == null) { + method.loadNull(); + } else { + setter.accept(this); + } + + method.invoke(ScriptObject.SET_USER_ACCESSORS); + } + + method.store(objectNode.getSymbol()); + + return null; + } + + @Override + public Node enter(final ReferenceNode referenceNode) { + if (referenceNode.testResolved()) { + return null; + } + + newFunctionObject(referenceNode.getReference()); + + return null; + } + + @Override + public Node enter(final ReturnNode returnNode) { + if (returnNode.testResolved()) { + return null; + } + + // Set the split return flag in the scope if this is a split method fragment. + if (method.getSplitNode() != null) { + assert method.getSplitNode().hasReturn() : "unexpected return in split node"; + + method.loadScope(); + method.checkcast(Scope.class); + method.load(0); + method.invoke(Scope.SET_SPLIT_STATE); + } + + final Node expression = returnNode.getExpression(); + if (expression != null) { + load(expression); + } else { + method.loadUndefined(getCurrentFunctionNode().getReturnType()); + } + + method._return(getCurrentFunctionNode().getReturnType()); + + return null; + } + + private static boolean isNullLiteral(final Node node) { + return node instanceof LiteralNode<?> && ((LiteralNode<?>) node).isNull(); + } + + private boolean nullCheck(final RuntimeNode runtimeNode, final List<Node> args, final String signature) { + final Request request = runtimeNode.getRequest(); + + if (!Request.isEQ(request) && !Request.isNE(request)) { + return false; + } + + assert args.size() == 2 : "EQ or NE or TYPEOF need two args"; + + Node lhs = args.get(0); + Node rhs = args.get(1); + + if (isNullLiteral(lhs)) { + final Node tmp = lhs; + lhs = rhs; + rhs = tmp; + } + + if (isNullLiteral(rhs)) { + final Label trueLabel = new Label("trueLabel"); + final Label falseLabel = new Label("falseLabel"); + final Label endLabel = new Label("end"); + + load(lhs); + method.dup(); + if (Request.isEQ(request)) { + method.ifnull(trueLabel); + } else if (Request.isNE(request)) { + method.ifnonnull(trueLabel); + } else { + assert false : "Invalid request " + request; + } + + method.label(falseLabel); + load(rhs); + method.invokeStatic(CompilerConstants.className(ScriptRuntime.class), request.toString(), signature); + method._goto(endLabel); + + method.label(trueLabel); + // if NE (not strict) this can be "undefined != null" which is supposed to be false + if (request == Request.NE) { + method.loadUndefined(Type.OBJECT); + final Label isUndefined = new Label("isUndefined"); + final Label afterUndefinedCheck = new Label("afterUndefinedCheck"); + method.if_acmpeq(isUndefined); + // not undefined + method.load(true); + method._goto(afterUndefinedCheck); + method.label(isUndefined); + method.load(false); + method.label(afterUndefinedCheck); + } else { + method.pop(); + method.load(true); + } + method.label(endLabel); + method.convert(runtimeNode.getType()); + method.store(runtimeNode.getSymbol()); + + return true; + } + + return false; + } + + private boolean specializationCheck(final RuntimeNode.Request request, final Node node, final List<Node> args) { + if (!request.canSpecialize()) { + return false; + } + + assert args.size() == 2; + final Node lhs = args.get(0); + final Node rhs = args.get(1); + + final Type returnType = node.getType(); + load(lhs); + load(rhs); + + Request finalRequest = request; + + final Request reverse = Request.reverse(request); + if (method.peekType().isObject() && reverse != null) { + if (!method.peekType(1).isObject()) { + method.swap(); + finalRequest = reverse; + } + } + + method.dynamicRuntimeCall( + new SpecializedRuntimeNode( + finalRequest, + new Type[] { + method.peekType(1), + method.peekType() + }, + returnType).getInitialName(), + returnType, + finalRequest); + + method.convert(node.getType()); + method.store(node.getSymbol()); + + return true; + } + + @Override + public Node enter(final RuntimeNode runtimeNode) { + if (runtimeNode.testResolved()) { + return null; + } + + /* + * First check if this should be something other than a runtime node + * AccessSpecializer might have changed the type + */ + if (runtimeNode.isPrimitive()) { + + final Node lhs = runtimeNode.getArgs().get(0); + Node rhs = null; + + if (runtimeNode.getArgs().size() > 1) { + rhs = runtimeNode.getArgs().get(1); + } + + final Type type = runtimeNode.getType(); + final Symbol symbol = runtimeNode.getSymbol(); + + switch (runtimeNode.getRequest()) { + case EQ: + case EQ_STRICT: + return enterCmp(lhs, rhs, Condition.EQ, type, symbol); + case NE: + case NE_STRICT: + return enterCmp(lhs, rhs, Condition.NE, type, symbol); + case LE: + return enterCmp(lhs, rhs, Condition.LE, type, symbol); + case LT: + return enterCmp(lhs, rhs, Condition.LT, type, symbol); + case GE: + return enterCmp(lhs, rhs, Condition.GE, type, symbol); + case GT: + return enterCmp(lhs, rhs, Condition.GT, type, symbol); + case ADD: + return enterNumericAdd(lhs, rhs, type, symbol); + default: + // it's ok to send this one on with only primitive arguments, maybe INSTANCEOF(true, true) or similar + // assert false : runtimeNode + " has all primitive arguments. This is an inconsistent state"; + break; + } + } + + // Get the request arguments. + final List<Node> args = runtimeNode.getArgs(); + + if (nullCheck(runtimeNode, args, new FunctionSignature(false, runtimeNode.getType(), args).toString())) { + return null; + } + + if (specializationCheck(runtimeNode.getRequest(), runtimeNode, args)) { + return null; + } + + for (final Node arg : runtimeNode.getArgs()) { + load(arg).convert(Type.OBJECT); //TODO this should not be necessary below Lower + } + + method.invokeStatic( + CompilerConstants.className(ScriptRuntime.class), + runtimeNode.getRequest().toString(), + new FunctionSignature( + false, + runtimeNode.getType(), + runtimeNode.getArgs().size()).toString()); + method.convert(runtimeNode.getType()); + method.store(runtimeNode.getSymbol()); + + return null; + } + + @Override + public Node enter(final SplitNode splitNode) { + if (splitNode.testResolved()) { + return null; + } + + final CompileUnit splitCompileUnit = splitNode.getCompileUnit(); + + final FunctionNode fn = getCurrentFunctionNode(); + final String className = splitCompileUnit.getUnitClassName(); + final String name = splitNode.getName(); + + final Class<?> rtype = fn.getReturnType().getTypeClass(); + final Class<?>[] ptypes = fn.isVarArg() ? + new Class<?>[] {Object.class, ScriptFunction.class, ScriptObject.class, Object.class} : + new Class<?>[] {Object.class, ScriptFunction.class, ScriptObject.class}; + + setCurrentCompileUnit(splitCompileUnit); + splitNode.setCompileUnit(splitCompileUnit); + + final Call splitCall = staticCallNoLookup( + className, + name, + methodDescriptor(rtype, ptypes)); + + setCurrentMethodEmitter( + splitCompileUnit.getClassEmitter().method( + EnumSet.of(Flag.PUBLIC, Flag.STATIC), + name, + rtype, + ptypes)); + + method.setFunctionNode(fn); + method.setSplitNode(splitNode); + splitNode.setMethodEmitter(method); + + final MethodEmitter caller = splitNode.getCaller(); + caller.loadThis(); + caller.loadCallee(); + caller.loadScope(); + if (fn.isVarArg()) { + caller.loadArguments(); + } + caller.invoke(splitCall); + caller.storeResult(); + + method.begin(); + + method.loadUndefined(fn.getReturnType()); + method.storeResult(); + + fixScopeSlot(); + + return splitNode; + } + + private void fixScopeSlot() { + if (getCurrentFunctionNode().getScopeNode().getSymbol().getSlot() != SCOPE.slot()) { + // TODO hack to move the scope to the expected slot (that's needed because split methods reuse the same slots as the root method) + method.load(Type.typeFor(ScriptObject.class), SCOPE.slot()); + method.storeScope(); + } + } + + @Override + public Node leave(final SplitNode splitNode) { + try { + // Wrap up this method. + method.loadResult(); + method._return(getCurrentFunctionNode().getReturnType()); + method.end(); + } catch (final Throwable t) { + Context.printStackTrace(t); + final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + compiler.getSource().getName()); + e.initCause(t); + throw e; + } + + // Handle return from split method if there was one. + final MethodEmitter caller = splitNode.getCaller(); + final List<Label> targets = splitNode.getExternalTargets(); + final int targetCount = targets.size(); + + if (splitNode.hasReturn() || targetCount > 0) { + + caller.loadScope(); + caller.checkcast(Scope.class); + caller.invoke(Scope.GET_SPLIT_STATE); + + // Split state is -1 for no split state, 0 for return, 1..n+1 for break/continue + final Label breakLabel = new Label("no_split_state"); + final int low = splitNode.hasReturn() ? 0 : 1; + final int labelCount = targetCount + 1 - low; + final Label[] labels = new Label[labelCount]; + + for (int i = 0; i < labelCount; i++) { + labels[i] = new Label("split_state_" + i); + } + + caller.tableSwitch(low, targetCount, breakLabel, labels); + for (int i = low; i <= targetCount; i++) { + caller.label(labels[i - low]); + if (i == 0) { + caller.loadResult(); + caller._return(getCurrentFunctionNode().getReturnType()); + } else { + // Clear split state. + caller.loadScope(); + caller.checkcast(Scope.class); + caller.load(-1); + caller.invoke(Scope.SET_SPLIT_STATE); + caller.splitAwareGoto(targets.get(i - 1)); + } + } + + caller.label(breakLabel); + } + + return splitNode; + } + + @Override + public Node enter(final SwitchNode switchNode) { + if (switchNode.testResolved()) { + return null; + } + + final Node expression = switchNode.getExpression(); + final Symbol tag = switchNode.getTag(); + final boolean allInteger = tag.getSymbolType().isInteger(); + final List<CaseNode> cases = switchNode.getCases(); + final CaseNode defaultCase = switchNode.getDefaultCase(); + final Label breakLabel = switchNode.getBreakLabel(); + + Label defaultLabel = breakLabel; + boolean hasDefault = false; + + if (defaultCase != null) { + defaultLabel = defaultCase.getEntry(); + hasDefault = true; + } + + if (cases.isEmpty()) { + method.label(breakLabel); + return null; + } + + if (allInteger) { + // Tree for sorting values. + final TreeMap<Integer, Label> tree = new TreeMap<>(); + + // Build up sorted tree. + for (final CaseNode caseNode : cases) { + final Node test = caseNode.getTest(); + + if (test != null) { + final Integer value = (Integer)((LiteralNode<?>) test).getValue(); + final Label entry = caseNode.getEntry(); + + // Take first duplicate. + if (!(tree.containsKey(value))) { + tree.put(value, entry); + } + } + } + + // Copy values and labels to arrays. + final int size = tree.size(); + final Integer[] values = tree.keySet().toArray(new Integer[size]); + final Label[] labels = tree.values().toArray(new Label[size]); + + // Discern low, high and range. + final int lo = values[0]; + final int hi = values[size - 1]; + final int range = hi - lo + 1; + + // Find an unused value for default. + int deflt = Integer.MIN_VALUE; + for (final int value : values) { + if (deflt == value) { + deflt++; + } else if (deflt < value) { + break; + } + } + + // Load switch expression. + load(expression); + final Type type = expression.getType(); + + // If expression not int see if we can convert, if not use deflt to trigger default. + if (!type.isInteger()) { + method.load(deflt); + method.invoke(staticCallNoLookup(ScriptRuntime.class, "switchTagAsInt", int.class, type.getTypeClass(), int.class)); + } + + // If reasonable size and not too sparse (80%), use table otherwise use lookup. + if (range > 0 && range < 4096 && range < (size * 5 / 4)) { + final Label[] table = new Label[range]; + Arrays.fill(table, defaultLabel); + + for (int i = 0; i < size; i++) { + final int value = values[i]; + table[value - lo] = labels[i]; + } + + method.tableSwitch(lo, hi, defaultLabel, table); + } else { + final int[] ints = new int[size]; + + for (int i = 0; i < size; i++) { + ints[i] = values[i]; + } + + method.lookupSwitch(defaultLabel, ints, labels); + } + } else { + load(expression); + + if (expression.getType().isInteger()) { + method.convert(Type.NUMBER).dup(); + method.store(tag); + method.conditionalJump(MethodEmitter.Condition.NE, true, defaultLabel); + } else { + method.store(tag); + } + + for (final CaseNode caseNode : cases) { + final Node test = caseNode.getTest(); + + if (test != null) { + method.load(tag); + load(test); + method.invoke(ScriptRuntime.EQ_STRICT); + method.ifne(caseNode.getEntry()); + } + } + + method._goto(hasDefault ? defaultLabel : breakLabel); + } + + for (final CaseNode caseNode : cases) { + method.label(caseNode.getEntry()); + caseNode.getBody().accept(this); + } + + if (!switchNode.isTerminal()) { + method.label(breakLabel); + } + + return null; + } + + @Override + public Node enter(final ThrowNode throwNode) { + if (throwNode.testResolved()) { + return null; + } + + method._new(ECMAException.class).dup(); + + final Node expression = throwNode.getExpression(); + final Source source = compiler.getSource(); + final int position = throwNode.position(); + final int line = source.getLine(position); + final int column = source.getColumn(position); + + load(expression); + assert expression.getType().isObject(); + + method.load(source.getName()); + method.load(line); + method.load(column); + method.invoke(ECMAException.THROW_INIT); + + method.athrow(); + + return null; + } + + @Override + public Node enter(final TryNode tryNode) { + if (tryNode.testResolved()) { + return null; + } + + final Block body = tryNode.getBody(); + final List<Block> catchBlocks = tryNode.getCatchBlocks(); + final Symbol symbol = tryNode.getException(); + final Label entry = new Label("try"); + final Label recovery = new Label("catch"); + final Label exit = tryNode.getExit(); + final Label skip = new Label("skip"); + + method.label(entry); + + body.accept(this); + + if (!body.hasTerminalFlags()) { + method._goto(skip); + } + + method.label(exit); + + method._catch(recovery); + method.store(symbol); + + for (int i = 0; i < catchBlocks.size(); i++) { + final Block saveBlock = getCurrentBlock(); + final Block catchBlock = catchBlocks.get(i); + + setCurrentBlock(catchBlock); + + try { + enter(catchBlock); + + final CatchNode catchNode = (CatchNode)catchBlocks.get(i).getStatements().get(0); + final IdentNode exception = catchNode.getException(); + final Node exceptionCondition = catchNode.getExceptionCondition(); + final Block catchBody = catchNode.getBody(); + + if (catchNode.isSyntheticRethrow()) { + // Generate catch body (inlined finally) and rethrow exception + catchBody.accept(this); + method.load(symbol).athrow(); + continue; + } + + new Store<IdentNode>(exception) { + @Override + protected void evaluate() { + /* + * If caught object is an instance of ECMAException, then + * bind obj.thrown to the script catch var. Or else bind the + * caught object itself to the script catch var. + */ + final Label notEcmaException = new Label("no_ecma_exception"); + + method.load(symbol).dup()._instanceof(ECMAException.class).ifeq(notEcmaException); + method.checkcast(ECMAException.class); //TODO is this necessary? + method.getField(ECMAException.THROWN); + method.label(notEcmaException); + } + }.store(); + + final Label next; + + if (exceptionCondition != null) { + next = new Label("next"); + load(exceptionCondition).convert(Type.BOOLEAN).ifeq(next); + } else { + next = null; + } + + catchBody.accept(this); + + if (i + 1 != catchBlocks.size() && !catchBody.hasTerminalFlags()) { + method._goto(skip); + } + + if (next != null) { + if (i + 1 == catchBlocks.size()) { + // no next catch block - rethrow if condition failed + method._goto(skip); + method.label(next); + method.load(symbol).athrow(); + } else { + method.label(next); + } + } + + leave(catchBlock); + } finally { + setCurrentBlock(saveBlock); + } + } + + method.label(skip); + method._try(entry, exit, recovery, Throwable.class); + + // Finally body is always inlined elsewhere so it doesn't need to be emitted + + return null; + } + + @Override + public Node enter(final VarNode varNode) { + final Node init = varNode.getInit(); + + if (varNode.testResolved() || init == null) { + return null; + } + + final Symbol varSymbol = varNode.getSymbol(); + + assert varSymbol != null : "variable node " + varNode + " requires a symbol"; + + assert method != null; + + final boolean needsScope = varSymbol.isScope(); + if (needsScope) { + method.loadScope(); + } + + load(init); + + if (needsScope) { + int flags = CALLSITE_SCOPE | getCallSiteFlags(); + if (varNode.isFunctionVarNode()) { + flags |= CALLSITE_FUNCTION_DECLARATION; + } + final IdentNode identNode = varNode.getName(); + final Type type = identNode.getType(); + if(varSymbol.isFastScope(getCurrentFunctionNode())) { + storeFastScopeVar(type, varSymbol, flags); + } else { + method.dynamicSet(type, identNode.getName(), flags); + } + } else { + assert varNode.getType() == varNode.getName().getType() : "varNode type=" + varNode.getType() + " nametype=" + varNode.getName().getType() + " inittype=" + init.getType(); + + method.convert(varNode.getType()); // aw: convert moved here + method.store(varSymbol); + } + + return null; + } + + @Override + public Node enter(final WhileNode whileNode) { + if (whileNode.testResolved()) { + return null; + } + + final Node test = whileNode.getTest(); + final Block body = whileNode.getBody(); + final Label breakLabel = whileNode.getBreakLabel(); + final Label continueLabel = whileNode.getContinueLabel(); + final Label loopLabel = new Label("loop"); + + if (!(whileNode instanceof DoWhileNode)) { + method._goto(continueLabel); + } + + method.label(loopLabel); + body.accept(this); + if (!whileNode.isTerminal()) { + method.label(continueLabel); + new BranchOptimizer(this, method).execute(test, loopLabel, true); + method.label(breakLabel); + } + + return null; + } + + private void closeWith() { + method.loadScope(); + method.invoke(ScriptRuntime.CLOSE_WITH); + method.storeScope(); + } + + @Override + public Node enter(final WithNode withNode) { + if (withNode.testResolved()) { + return null; + } + + final Node expression = withNode.getExpression(); + final Node body = withNode.getBody(); + + final Label tryLabel = new Label("with_try"); + final Label endLabel = new Label("with_end"); + final Label catchLabel = new Label("with_catch"); + final Label exitLabel = new Label("with_exit"); + + method.label(tryLabel); + + method.loadScope(); + load(expression); + + assert expression.getType().isObject() : "with expression needs to be object: " + expression; + + method.invoke(ScriptRuntime.OPEN_WITH); + method.storeScope(); + + body.accept(this); + + if (!body.isTerminal()) { + closeWith(); + method._goto(exitLabel); + } + + method.label(endLabel); + + method._catch(catchLabel); + closeWith(); + method.athrow(); + + method.label(exitLabel); + + method._try(tryLabel, endLabel, catchLabel); + + return null; + } + + @Override + public Node enterADD(final UnaryNode unaryNode) { + if (unaryNode.testResolved()) { + return null; + } + + load(unaryNode.rhs()); + assert unaryNode.rhs().getType().isNumber(); + method.store(unaryNode.getSymbol()); + + return null; + } + + @Override + public Node enterBIT_NOT(final UnaryNode unaryNode) { + if (unaryNode.testResolved()) { + return null; + } + + load(unaryNode.rhs()).convert(Type.INT).load(-1).xor().store(unaryNode.getSymbol()); + + return null; + } + + // do this better with convert calls to method. TODO + @Override + public Node enterCONVERT(final UnaryNode unaryNode) { + if (unaryNode.testResolved()) { + return null; + } + + final Node rhs = unaryNode.rhs(); + final Type to = unaryNode.getType(); + + if (to.isObject() && rhs instanceof LiteralNode) { + final LiteralNode<?> literalNode = (LiteralNode<?>)rhs; + final Object value = literalNode.getValue(); + + if (value instanceof Number) { + assert !to.isArray() : "type hygiene - cannot convert number to array: (" + to.getTypeClass().getSimpleName() + ')' + value; + if (value instanceof Integer) { + method.load((Integer)value); + } else if (value instanceof Long) { + method.load((Long)value); + } else if (value instanceof Double) { + method.load((Double)value); + } else { + assert false; + } + method.convert(Type.OBJECT); + } else if (value instanceof Boolean) { + method.getField(staticField(Boolean.class, value.toString().toUpperCase(), Boolean.class)); + } else { + load(rhs); + method.convert(unaryNode.getType()); + } + } else { + load(rhs); + method.convert(unaryNode.getType()); + } + + method.store(unaryNode.getSymbol()); + + return null; + } + + @Override + public Node enterDECINC(final UnaryNode unaryNode) { + if (unaryNode.testResolved()) { + return null; + } + + final Node rhs = unaryNode.rhs(); + final Type type = unaryNode.getType(); + final TokenType tokenType = unaryNode.tokenType(); + final boolean isPostfix = tokenType == TokenType.DECPOSTFIX || tokenType == TokenType.INCPOSTFIX; + final boolean isIncrement = tokenType == TokenType.INCPREFIX || tokenType == TokenType.INCPOSTFIX; + + assert !type.isObject(); + + new SelfModifyingStore<UnaryNode>(unaryNode, rhs) { + + @Override + protected void evaluate() { + load(rhs, true); + + method.convert(type); + if (!isPostfix) { + if (type.isInteger()) { + method.load(isIncrement ? 1 : -1); + } else if (type.isLong()) { + method.load(isIncrement ? 1L : -1L); + } else { + method.load(isIncrement ? 1.0 : -1.0); + } + method.add(); + } + } + + @Override + protected void storeNonDiscard() { + super.storeNonDiscard(); + if (isPostfix) { + if (type.isInteger()) { + method.load(isIncrement ? 1 : -1); + } else if (type.isLong()) { + method.load(isIncrement ? 1L : 1L); + } else { + method.load(isIncrement ? 1.0 : -1.0); + } + method.add(); + } + } + }.store(); + + return null; + } + + @Override + public Node enterDISCARD(final UnaryNode unaryNode) { + if (unaryNode.testResolved()) { + return null; + } + + final Node rhs = unaryNode.rhs(); + + load(rhs); + + if (rhs.shouldDiscard()) { + method.pop(); + } + + return null; + } + + @Override + public Node enterNEW(final UnaryNode unaryNode) { + if (unaryNode.testResolved()) { + return null; + } + + final CallNode callNode = (CallNode)unaryNode.rhs(); + final List<Node> args = callNode.getArgs(); + + // Load function reference. + load(callNode.getFunction()).convert(Type.OBJECT); // must detect type error + + // Load arguments. + loadArgs(args); + + method.dynamicNew(args.size(), getCallSiteFlags()); + method.store(unaryNode.getSymbol()); + + return null; + } + + @Override + public Node enterNOT(final UnaryNode unaryNode) { + if (unaryNode.testResolved()) { + return null; + } + + final Node rhs = unaryNode.rhs(); + + load(rhs); + + final Label trueLabel = new Label("true"); + final Label afterLabel = new Label("after"); + + method.convert(Type.BOOLEAN); + method.ifne(trueLabel); + method.load(true); + method._goto(afterLabel); + method.label(trueLabel); + method.load(false); + method.label(afterLabel); + method.store(unaryNode.getSymbol()); + + return null; + } + + @Override + public Node enterSUB(final UnaryNode unaryNode) { + if (unaryNode.testResolved()) { + return null; + } + + load(unaryNode.rhs()).neg().store(unaryNode.getSymbol()); + + return null; + } + + private Node enterNumericAdd(final Node lhs, final Node rhs, final Type type, final Symbol symbol) { + assert lhs.getType().equals(rhs.getType()) && lhs.getType().equals(type); + load(lhs); + load(rhs); + method.add(); + method.store(symbol); + + return null; + } + + @Override + public Node enterADD(final BinaryNode binaryNode) { + if (binaryNode.testResolved()) { + return null; + } + + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + final Type type = binaryNode.getType(); + if (type.isNumeric()) { + enterNumericAdd(lhs, rhs, type, binaryNode.getSymbol()); + } else { + load(lhs).convert(Type.OBJECT); + load(rhs).convert(Type.OBJECT); + method.add(); + method.store(binaryNode.getSymbol()); + } + + return null; + } + + private Node enterAND_OR(final BinaryNode binaryNode) { + if (binaryNode.testResolved()) { + return null; + } + + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + final Label skip = new Label("skip"); + + load(lhs).convert(Type.OBJECT).dup().convert(Type.BOOLEAN); + + if (binaryNode.tokenType() == TokenType.AND) { + method.ifeq(skip); + } else { + method.ifne(skip); + } + + method.pop(); + load(rhs).convert(Type.OBJECT); + method.label(skip); + method.store(binaryNode.getSymbol()); + + return null; + } + + @Override + public Node enterAND(final BinaryNode binaryNode) { + return enterAND_OR(binaryNode); + } + + @Override + public Node enterASSIGN(final BinaryNode binaryNode) { + if (binaryNode.testResolved()) { + return null; + } + + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + final Type lhsType = lhs.getType(); + final Type rhsType = rhs.getType(); + + if (!lhsType.isEquivalentTo(rhsType)) { + //this is OK if scoped, only locals are wrong + assert !(lhs instanceof IdentNode) || lhs.getSymbol().isScope() : new ASTWriter(binaryNode); + } + + new Store<BinaryNode>(binaryNode, lhs) { + @Override + protected void evaluate() { + load(rhs); + } + }.store(); + + return null; + } + + /** + * Helper class for assignment ops, e.g. *=, += and so on.. + */ + private abstract class AssignOp extends SelfModifyingStore<BinaryNode> { + + /** The type of the resulting operation */ + private final Type opType; + + /** + * Constructor + * + * @param node the assign op node + */ + AssignOp(final BinaryNode node) { + this(node.getType(), node); + } + + /** + * Constructor + * + * @param opType type of the computation - overriding the type of the node + * @param node the assign op node + */ + AssignOp(final Type opType, final BinaryNode node) { + super(node, node.lhs()); + this.opType = opType; + } + + @Override + public void store() { + if (assignNode.testResolved()) { + return; + } + super.store(); + } + + protected abstract void op(); + + @Override + protected void evaluate() { + load(assignNode.lhs(), true).convert(opType); + load(assignNode.rhs()).convert(opType); + op(); + method.convert(assignNode.getType()); + } + } + + @Override + public Node enterASSIGN_ADD(final BinaryNode binaryNode) { + assert RuntimeNode.Request.ADD.canSpecialize(); + final boolean specialize = binaryNode.getType() == Type.OBJECT; + + new AssignOp(binaryNode) { + @Override + protected boolean isSelfModifying() { + return !specialize; + } + + @Override + protected void op() { + method.add(); + } + + @Override + protected void evaluate() { + if (specialize && specializationCheck(Request.ADD, assignNode, Arrays.asList(assignNode.lhs(), assignNode.rhs()))) { + return; + } + super.evaluate(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_BIT_AND(final BinaryNode binaryNode) { + new AssignOp(Type.INT, binaryNode) { + @Override + protected void op() { + method.and(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_BIT_OR(final BinaryNode binaryNode) { + new AssignOp(Type.INT, binaryNode) { + @Override + protected void op() { + method.or(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { + new AssignOp(Type.INT, binaryNode) { + @Override + protected void op() { + method.xor(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_DIV(final BinaryNode binaryNode) { + new AssignOp(binaryNode) { + @Override + protected void op() { + method.div(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_MOD(final BinaryNode binaryNode) { + new AssignOp(binaryNode) { + @Override + protected void op() { + method.rem(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_MUL(final BinaryNode binaryNode) { + new AssignOp(binaryNode) { + @Override + protected void op() { + method.mul(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_SAR(final BinaryNode binaryNode) { + new AssignOp(Type.INT, binaryNode) { + @Override + protected void op() { + method.sar(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_SHL(final BinaryNode binaryNode) { + new AssignOp(Type.INT, binaryNode) { + @Override + protected void op() { + method.shl(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_SHR(final BinaryNode binaryNode) { + new AssignOp(Type.INT, binaryNode) { + @Override + protected void op() { + method.shr(); + method.convert(Type.LONG).load(0xffff_ffffL).and(); + } + }.store(); + + return null; + } + + @Override + public Node enterASSIGN_SUB(final BinaryNode binaryNode) { + new AssignOp(binaryNode) { + @Override + protected void op() { + method.sub(); + } + }.store(); + + return null; + } + + /** + * Helper class for binary arithmetic ops + */ + private abstract class BinaryArith { + + protected abstract void op(); + + protected void evaluate(final BinaryNode node) { + if (node.testResolved()) { + return; + } + load(node.lhs()); + load(node.rhs()); + op(); + method.store(node.getSymbol()); + } + } + + @Override + public Node enterBIT_AND(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.and(); + } + }.evaluate(binaryNode); + + return null; + } + + @Override + public Node enterBIT_OR(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.or(); + } + }.evaluate(binaryNode); + + return null; + } + + @Override + public Node enterBIT_XOR(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.xor(); + } + }.evaluate(binaryNode); + + return null; + } + + private Node enterComma(final BinaryNode binaryNode) { + if (binaryNode.testResolved()) { + return null; + } + + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + load(lhs); + load(rhs); + method.store(binaryNode.getSymbol()); + + return null; + } + + @Override + public Node enterCOMMARIGHT(final BinaryNode binaryNode) { + return enterComma(binaryNode); + } + + @Override + public Node enterCOMMALEFT(final BinaryNode binaryNode) { + return enterComma(binaryNode); + } + + @Override + public Node enterDIV(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.div(); + } + }.evaluate(binaryNode); + + return null; + } + + private Node enterCmp(final Node lhs, final Node rhs, final Condition cond, final Type type, final Symbol symbol) { + final Type lhsType = lhs.getType(); + final Type rhsType = rhs.getType(); + + final Type widest = Type.widest(lhsType, rhsType); + assert widest.isNumeric() || widest.isBoolean() : widest; + + load(lhs); + method.convert(widest); + load(rhs); + method.convert(widest); + + final Label trueLabel = new Label("trueLabel"); + final Label afterLabel = new Label("skip"); + + method.conditionalJump(cond, trueLabel); + + method.load(Boolean.FALSE); + method._goto(afterLabel); + method.label(trueLabel); + method.load(Boolean.TRUE); + method.label(afterLabel); + + method.convert(type); + method.store(symbol); + + return null; + } + + private Node enterCmp(final BinaryNode binaryNode, final Condition cond) { + if (binaryNode.testResolved()) { + return null; + } + return enterCmp(binaryNode.lhs(), binaryNode.rhs(), cond, binaryNode.getType(), binaryNode.getSymbol()); + } + + @Override + public Node enterEQ(final BinaryNode binaryNode) { + return enterCmp(binaryNode, Condition.EQ); + } + + @Override + public Node enterEQ_STRICT(final BinaryNode binaryNode) { + return enterCmp(binaryNode, Condition.EQ); + } + + @Override + public Node enterGE(final BinaryNode binaryNode) { + return enterCmp(binaryNode, Condition.GE); + } + + @Override + public Node enterGT(final BinaryNode binaryNode) { + return enterCmp(binaryNode, Condition.GT); + } + + @Override + public Node enterLE(final BinaryNode binaryNode) { + return enterCmp(binaryNode, Condition.LE); + } + + @Override + public Node enterLT(final BinaryNode binaryNode) { + return enterCmp(binaryNode, Condition.LT); + } + + @Override + public Node enterMOD(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.rem(); + } + }.evaluate(binaryNode); + + return null; + } + + @Override + public Node enterMUL(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.mul(); + } + }.evaluate(binaryNode); + + return null; + } + + @Override + public Node enterNE(final BinaryNode binaryNode) { + return enterCmp(binaryNode, Condition.NE); + } + + @Override + public Node enterNE_STRICT(final BinaryNode binaryNode) { + return enterCmp(binaryNode, Condition.NE); + } + + @Override + public Node enterOR(final BinaryNode binaryNode) { + return enterAND_OR(binaryNode); + } + + @Override + public Node enterSAR(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.sar(); + } + }.evaluate(binaryNode); + + return null; + } + + @Override + public Node enterSHL(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.shl(); + } + }.evaluate(binaryNode); + + return null; + } + + @Override + public Node enterSHR(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.shr(); + method.convert(Type.LONG).load(0xffff_ffffL).and(); + } + }.evaluate(binaryNode); + + return null; + } + + @Override + public Node enterSUB(final BinaryNode binaryNode) { + new BinaryArith() { + @Override + protected void op() { + method.sub(); + } + }.evaluate(binaryNode); + + return null; + } + + /* + * Ternary visits. + */ + @Override + public Node enter(final TernaryNode ternaryNode) { + if (ternaryNode.testResolved()) { + return null; + } + + final Node lhs = ternaryNode.lhs(); + final Node rhs = ternaryNode.rhs(); + final Node third = ternaryNode.third(); + + final Symbol symbol = ternaryNode.getSymbol(); + final Label falseLabel = new Label("ternary_false"); + final Label exitLabel = new Label("ternary_exit"); + + final Type widest = Type.widest(rhs.getType(), third.getType()); + + load(lhs); + assert lhs.getType().isBoolean() : "lhs in ternary must be boolean"; + + // we still keep the conversion here as the AccessSpecializer can have separated the types, e.g. var y = x ? x=55 : 17 + // will left as (Object)x=55 : (Object)17 by Lower. Then the first term can be {I}x=55 of type int, which breaks the + // symmetry for the temporary slot for this TernaryNode. This is evidence that we assign types and explicit conversions + // to early, or Apply the AccessSpecializer too late. We are mostly probably looking for a separate type pass to + // do this property. Then we never need any conversions in CodeGenerator + method.ifeq(falseLabel); + load(rhs); + method.convert(widest); + method._goto(exitLabel); + method.label(falseLabel); + load(third); + method.convert(widest); + method.label(exitLabel); + method.store(symbol); + + return null; + } + + /** + * Generate all shared scope calls generated during codegen. + */ + protected void generateScopeCalls() { + for (final SharedScopeCall scopeAccess : scopeCalls.values()) { + scopeAccess.generateScopeCall(); + } + } + + /** + * Get a shared static method representing a dynamic scope callsite. + * + * @param symbol the symbol + * @param valueType the value type of the symbol + * @param returnType the return type + * @param paramTypes the parameter types + * @param flags the callsite flags + * @return an object representing a shared scope call + */ + private SharedScopeCall getScopeCall(final Symbol symbol, final Type valueType, final Type returnType, + final Type[] paramTypes, final int flags) { + + final SharedScopeCall scopeCall = new SharedScopeCall(symbol, valueType, returnType, paramTypes, flags); + if (scopeCalls.containsKey(scopeCall)) { + return scopeCalls.get(scopeCall); + } + scopeCall.setClassAndName(compileUnit, compiler); + scopeCalls.put(scopeCall, scopeCall); + return scopeCall; + } + + /** + * Get a shared static method representing a dynamic scope get access. + * + * @param type the type of the variable + * @param symbol the symbol + * @param flags the callsite flags + * @return an object representing a shared scope call + */ + private SharedScopeCall getScopeGet(final Type type, final Symbol symbol, final int flags) { + + final SharedScopeCall scopeCall = new SharedScopeCall(symbol, type, type, null, flags); + if (scopeCalls.containsKey(scopeCall)) { + return scopeCalls.get(scopeCall); + } + scopeCall.setClassAndName(compileUnit, compiler); + scopeCalls.put(scopeCall, scopeCall); + return scopeCall; + } + + /** + * Debug code used to print symbols + * + * @param block the block we are in + * @param ident identifier for block or function where applicable + */ + private void printSymbols(final Block block, final String ident) { + if (!context._print_symbols) { + return; + } + + @SuppressWarnings("resource") + final PrintWriter out = context.getErr(); + out.println("[BLOCK in '" + ident + "']"); + if (!block.printSymbols(out)) { + out.println("<no symbols>"); + } + out.println(); + } + + + /** + * The difference between a store and a self modifying store is that + * the latter may load part of the target on the stack, e.g. the base + * of an AccessNode or the base and index of an IndexNode. These are used + * both as target and as an extra source. Previously it was problematic + * for self modifying stores if the target/lhs didn't belong to one + * of three trivial categories: IdentNode, AcessNodes, IndexNodes. In that + * case it was evaluated and tagged as "resolved", which meant at the second + * time the lhs of this store was read (e.g. in a = a (second) + b for a += b, + * it would be evaluated to a nop in the scope and cause stack underflow + * + * see NASHORN-703 + * + * @param <T> + */ + private abstract class SelfModifyingStore<T extends Node> extends Store<T> { + protected SelfModifyingStore(final T assignNode, final Node target) { + super(assignNode, target); + } + + @Override + protected boolean isSelfModifying() { + return true; + } + } + + /** + * Helper class to generate stores + */ + private abstract class Store<T extends Node> { + + /** An assignment node, e.g. x += y */ + protected final T assignNode; + + /** The target node to store to, e.g. x */ + private final Node target; + + /** Should the result always be discarded, no matter what? */ + private final boolean alwaysDiscard; + + /** How deep on the stack do the arguments go if this generates an indy call */ + private int depth; + + /** If we have too many arguments, we need temporary storage, this is stored in 'quick' */ + private Symbol quick; + + /** + * Constructor + * + * @param assignNode the node representing the whole assignment + * @param target the target node of the assignment (destination) + */ + protected Store(final T assignNode, final Node target) { + this.assignNode = assignNode; + this.target = target; + this.alwaysDiscard = assignNode == target; + } + + /** + * Constructor + * + * @param assignNode the node representing the whole assignment + */ + protected Store(final T assignNode) { + this(assignNode, assignNode); + } + + /** + * Is this a self modifying store operation, e.g. *= or ++ + * @return true if self modifying store + */ + protected boolean isSelfModifying() { + return false; + } + + private void prologue() { + final Symbol targetSymbol = target.getSymbol(); + final Symbol scopeSymbol = getCurrentFunctionNode().getScopeNode().getSymbol(); + + /** + * This loads the parts of the target, e.g base and index. they are kept + * on the stack throughout the store and used at the end to execute it + */ + + target.accept(new NodeVisitor(compileUnit, method) { + @Override + public Node enter(final IdentNode node) { + if (targetSymbol.isScope()) { + method.load(scopeSymbol); + depth++; + } + return null; + } + + private void enterBaseNode() { + assert target instanceof BaseNode : "error - base node " + target + " must be instanceof BaseNode"; + final BaseNode baseNode = (BaseNode)target; + final Node base = baseNode.getBase(); + + load(base); + method.convert(Type.OBJECT); + depth += Type.OBJECT.getSlots(); + + if (isSelfModifying()) { + method.dup(); + } + } + + @Override + public Node enter(final AccessNode node) { + enterBaseNode(); + return null; + } + + @Override + public Node enter(final IndexNode node) { + enterBaseNode(); + + final Node index = node.getIndex(); + // could be boolean here as well + load(index); + if (!index.getType().isNumeric()) { + method.convert(Type.OBJECT); + } + depth += index.getType().getSlots(); + + if (isSelfModifying()) { + //convert "base base index" to "base index base index" + method.dup(1); + } + + return null; + } + + }); + } + + private Symbol quickSymbol(final Type type) { + return quickSymbol(type, QUICK_PREFIX.tag()); + } + + /** + * Quick symbol generates an extra local variable, always using the same + * slot, one that is available after the end of the frame. + * + * @param type the type of the symbol + * @param prefix the prefix for the variable name for the symbol + * + * @return the quick symbol + */ + private Symbol quickSymbol(final Type type, final String prefix) { + final String name = compiler.uniqueName(prefix); + final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL, null, null); + + symbol.setType(type); + symbol.setSlot(getCurrentBlock().getFrame().getSlotCount()); + + return symbol; + } + + // store the result that "lives on" after the op, e.g. "i" in i++ postfix. + protected void storeNonDiscard() { + if (assignNode.shouldDiscard() || alwaysDiscard) { + assignNode.setDiscard(false); + return; + } + + final Symbol symbol = assignNode.getSymbol(); + if (symbol.hasSlot()) { + method.dup().store(symbol); + return; + } + + if (method.dup(depth) == null) { + method.dup(); + this.quick = quickSymbol(method.peekType()); + method.store(quick); + } + } + + private void epilogue() { + final Symbol targetSymbol = target.getSymbol(); + final FunctionNode currentFunction = getCurrentFunctionNode(); + + /** + * Take the original target args from the stack and use them + * together with the value to be stored to emit the store code + */ + if (targetSymbol.hasSlot()) { + method.convert(target.getType()); + } + + target.accept(new NodeVisitor(compileUnit, method) { + @Override + public Node enter(final IdentNode node) { + final Symbol symbol = target.getSymbol(); + if (symbol.isScope()) { + if(symbol.isFastScope(currentFunction)) { + storeFastScopeVar(target.getType(), symbol, CALLSITE_SCOPE | getCallSiteFlags()); + } else { + method.dynamicSet(target.getType(), node.getName(), CALLSITE_SCOPE | getCallSiteFlags()); + } + } else { + assert targetSymbol != null; + method.store(targetSymbol); + } + return null; + + } + + @Override + public Node enter(final AccessNode node) { + method.dynamicSet(node.getProperty().getType(), node.getProperty().getName(), getCallSiteFlags()); + return null; + } + + @Override + public Node enter(final IndexNode node) { + method.dynamicSetIndex(getCallSiteFlags()); + return null; + } + }); + + + // whatever is on the stack now is the final answer + } + + protected abstract void evaluate(); + + void store() { + prologue(); + evaluate(); // leaves an operation of whatever the operationType was on the stack + storeNonDiscard(); + epilogue(); + if (quick != null) { + method.load(quick); + } + } + + } + + /* + * Globals are special. We cannot refer to any Global (or NativeObject) class by .class, as they are different + * for different contexts. As far as I can tell, the only NativeObject that we need to deal with like this + * is from the code pipeline is Global + */ + private MethodEmitter globalInstance() { + return method.invokeStatic(Compiler.GLOBAL_OBJECT, "instance", "()L" + Compiler.GLOBAL_OBJECT + ';'); + } + + private MethodEmitter globalObjectPrototype() { + return method.invokeStatic(Compiler.GLOBAL_OBJECT, "objectPrototype", methodDescriptor(ScriptObject.class)); + } + + private MethodEmitter globalAllocateArguments() { + return method.invokeStatic(Compiler.GLOBAL_OBJECT, "allocateArguments", methodDescriptor(Object.class, Object[].class, Object.class, int.class)); + } + + private MethodEmitter globalNewRegExp() { + return method.invokeStatic(Compiler.GLOBAL_OBJECT, "newRegExp", methodDescriptor(Object.class, String.class, String.class)); + } + + private MethodEmitter globalRegExpCopy() { + return method.invokeStatic(Compiler.GLOBAL_OBJECT, "regExpCopy", methodDescriptor(Object.class, Object.class)); + } + + private MethodEmitter globalAllocateArray(final ArrayType type) { + //make sure the native array is treated as an array type + return method.invokeStatic(Compiler.GLOBAL_OBJECT, "allocate", methodDescriptor(Object.class, type.getTypeClass()), type); + } + + private MethodEmitter globalIsEval() { + return method.invokeStatic(Compiler.GLOBAL_OBJECT, "isEval", methodDescriptor(boolean.class, Object.class)); + } + + private MethodEmitter globalDirectEval() { + return method.invokeStatic(Compiler.GLOBAL_OBJECT, "directEval", + methodDescriptor(Object.class, Object.class, Object.class, Object.class, Object.class, Object.class)); + } +} diff --git a/src/jdk/nashorn/internal/codegen/CompileUnit.java b/src/jdk/nashorn/internal/codegen/CompileUnit.java new file mode 100644 index 00000000..676348e1 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/CompileUnit.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +/** + * Used to track split class compilation. + */ +public class CompileUnit { + /** Current class name */ + private final String className; + + /** Current class generator */ + private final ClassEmitter classEmitter; + + private long weight; + + CompileUnit(final String className, final ClassEmitter classEmitter) { + this(className, classEmitter, 0L); + } + + CompileUnit(final String className, final ClassEmitter classEmitter, final long initialWeight) { + this.className = className; + this.classEmitter = classEmitter; + this.weight = initialWeight; + } + + /** + * Add weight to this compile unit + * @param w weight to add + */ + public void addWeight(final long w) { + this.weight += w; + } + + /** + * Get the current weight of the compile unit. + * @return the unit's weight + */ + public long getWeight() { + return weight; + } + + /** + * Check if this compile unit can hold {@code weight} more units of weight + * @param w weight to check if can be added + * @return true if weight fits in this compile unit + */ + public boolean canHold(final long w) { + return (this.weight + w) < Splitter.SPLIT_THRESHOLD; + } + + /** + * Get the class emitter for this compile unit + * @return class emitter + */ + public ClassEmitter getClassEmitter() { + return classEmitter; + } + + /** + * Get the class name for this compile unit + * @return the class name + */ + public String getUnitClassName() { + return className; + } + + @Override + public String toString() { + return "[classname=" + className + " weight=" + weight + '/' + Splitter.SPLIT_THRESHOLD + ']'; + } +} diff --git a/src/jdk/nashorn/internal/codegen/Compiler.java b/src/jdk/nashorn/internal/codegen/Compiler.java new file mode 100644 index 00000000..f7ddfd32 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/Compiler.java @@ -0,0 +1,685 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.DEFAULT_SCRIPT_NAME; +import static jdk.nashorn.internal.codegen.CompilerConstants.RUN_SCRIPT; +import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; +import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; +import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.TreeMap; +import jdk.nashorn.internal.codegen.ClassEmitter.Flag; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.debug.ASTWriter; +import jdk.nashorn.internal.ir.debug.PrintVisitor; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.parser.Parser; +import jdk.nashorn.internal.runtime.CodeInstaller; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.DebugLogger; +import jdk.nashorn.internal.runtime.ECMAErrors; +import jdk.nashorn.internal.runtime.ErrorManager; +import jdk.nashorn.internal.runtime.Source; +import jdk.nashorn.internal.runtime.linker.Mangler; +import jdk.nashorn.internal.runtime.options.Options; + +/** + * Responsible for converting JavaScripts to java byte code. Main entry + * point for code generator + */ +public final class Compiler { + + /** Compiler states available */ + public enum State { + /** compiler is ready */ + INITIALIZED, + /** method has been parsed */ + PARSED, + /** method has been lowered */ + LOWERED, + /** method has been emitted to bytecode */ + EMITTED + } + + /** Current context */ + private final Context context; + + /** Currently compiled source */ + private final Source source; + + /** Current error manager */ + private final ErrorManager errors; + + /** Names uniqueName for this compile. */ + private final Namespace namespace; + + /** Current function node, or null if compiling from source until parsed */ + private FunctionNode functionNode; + + /** Current compiler state */ + private final EnumSet<State> state; + + /** Name of the scripts package */ + public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts"; + + /** Name of the objects package */ + public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects"; + + /** Name of the Global object, cannot be referred to as .class, @see CodeGenerator */ + public static final String GLOBAL_OBJECT = OBJECTS_PACKAGE + '/' + "Global"; + + /** Name of the ScriptObjectImpl, cannot be referred to as .class @see FunctionObjectCreator */ + public static final String SCRIPTOBJECT_IMPL_OBJECT = OBJECTS_PACKAGE + '/' + "ScriptFunctionImpl"; + + /** Name of the Trampoline, cannot be referred to as .class @see FunctionObjectCreator */ + public static final String TRAMPOLINE_OBJECT = OBJECTS_PACKAGE + '/' + "Trampoline"; + + /** Compile unit (class) table. */ + private final Set<CompileUnit> compileUnits; + + /** All the "complex" constants used in the code. */ + private final ConstantData constantData; + + static final DebugLogger LOG = new DebugLogger("compiler"); + + /** Script name */ + private String scriptName; + + /** Should we dump classes to disk and compile only? */ + private final boolean dumpClass; + + /** Code map class name -> byte code for all classes generated from this Source or FunctionNode */ + private Map<String, byte[]> code; + + /** Are we compiling in strict mode? */ + private boolean strict; + + /** Is this a lazy compilation - i.e. not from source, but jitting a previously parsed FunctionNode? */ + private boolean isLazy; + + /** Lazy jitting is disabled by default */ + private static final boolean LAZY_JIT = false; + + /** + * Should we use integers for literals and all operations + * that are based in constant and parameter assignment as + * long as they can be proven not to overflow? With this enabled + * var x = 17 would tag x as an integer, rather than a double, + * but as soon as it is used in an operation that may potentially + * overflow, such as an add, we conservatively widen it to double + * (number type). This is because overflow checks in the code + * are likely much more expensive that method specialization + * + * @return true if numbers should start as ints, false if they should + * start as doubles + */ + static boolean shouldUseIntegers() { + return USE_INTS; + } + + private static final boolean USE_INTS; + + /** + * Should we use integers for arithmetic operations as well? + * TODO: We currently generate no overflow checks so this is + * disabled + * + * @see #shouldUseIntegers() + * + * @return true if arithmetic operations should not widen integer + * operands by default. + */ + static boolean shouldUseIntegerArithmetic() { + return Compiler.shouldUseIntegers() && USE_INT_ARITH; + } + + private static final boolean USE_INT_ARITH; + + static { + USE_INTS = !Options.getBooleanProperty("nashorn.compiler.ints.disable"); + USE_INT_ARITH = Options.getBooleanProperty("nashorn.compiler.intarithmetic"); + + assert !USE_INT_ARITH : "Integer arithmetic is not enabled"; + } + + /** + * Factory method for compiler that should compile from source to bytecode + * + * @param source the source + * @param context context + * + * @return compiler instance + */ + public static Compiler compiler(final Source source, final Context context) { + return Compiler.compiler(source, context, context.getErrors(), context._strict); + } + + /** + * Factory method to get a compiler that goes from from source to bytecode + * + * @param source source code + * @param context context + * @param errors error manager + * @param strict compilation in strict mode? + * + * @return compiler instance + */ + public static Compiler compiler(final Source source, final Context context, final ErrorManager errors, final boolean strict) { + return new Compiler(source, context, errors, strict); + } + + /** + * Factory method to get a compiler that goes from FunctionNode (parsed) to bytecode + * Requires previous compiler for state + * + * @param compiler primordial compiler + * @param functionNode functionNode to compile + * + * @return compiler + */ + public static Compiler compiler(final Compiler compiler, final FunctionNode functionNode) { + assert false : "lazy jit - not implemented"; + final Compiler newCompiler = new Compiler(compiler); + newCompiler.state.add(State.PARSED); + newCompiler.functionNode = functionNode; + newCompiler.isLazy = true; + return compiler; + } + + private Compiler(final Compiler compiler) { + this(compiler.source, compiler.context, compiler.errors, compiler.strict); + } + + /** + * Constructor + * + * @param source the source to compile + * @param context context + * @param errors error manager + * @param strict compile in strict mode + */ + private Compiler(final Source source, final Context context, final ErrorManager errors, final boolean strict) { + this.source = source; + this.context = context; + this.errors = errors; + this.strict = strict; + this.namespace = new Namespace(context.getNamespace()); + this.compileUnits = new HashSet<>(); + this.constantData = new ConstantData(); + this.state = EnumSet.of(State.INITIALIZED); + this.dumpClass = context._compile_only && context._dest_dir != null; + } + + private String scriptsPackageName() { + return dumpClass ? "" : (SCRIPTS_PACKAGE + '/'); + } + + private int nextCompileUnitIndex() { + return compileUnits.size() + 1; + } + + private String firstCompileUnitName() { + return scriptsPackageName() + scriptName; + } + + private String nextCompileUnitName() { + return firstCompileUnitName() + '$' + nextCompileUnitIndex(); + } + + private CompileUnit addCompileUnit(final long initialWeight) { + return addCompileUnit(nextCompileUnitName(), initialWeight); + } + + private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) { + final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight); + compileUnits.add(compileUnit); + LOG.info("Added compile unit " + compileUnit); + return compileUnit; + } + + private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) { + final ClassEmitter classEmitter = new ClassEmitter(this, unitClassName, strict); + final CompileUnit compileUnit = new CompileUnit(unitClassName, classEmitter, initialWeight); + + classEmitter.begin(); + + final MethodEmitter initMethod = classEmitter.init(EnumSet.of(Flag.PRIVATE)); + initMethod.begin(); + initMethod.load(Type.OBJECT, 0); + initMethod.newInstance(jdk.nashorn.internal.scripts.JS$.class); + initMethod.returnVoid(); + initMethod.end(); + + return compileUnit; + } + + /** + * Perform compilation + * + * @return true if successful, false otherwise - if false check the error manager + */ + public boolean compile() { + assert state.contains(State.INITIALIZED); + + /** do we need to parse source? */ + if (!state.contains(State.PARSED)) { + assert this.functionNode == null; + this.functionNode = new Parser(this, strict).parse(RUN_SCRIPT.tag()); + + state.add(State.PARSED); + debugPrintParse(); + + if (errors.hasErrors() || context._parse_only) { + return false; + } + + assert !isLazy; + //tag lazy nodes for later code generation and trampolines + functionNode.accept(new NodeVisitor() { + @Override + public Node enter(final FunctionNode node) { + if (LAZY_JIT) { + node.setIsLazy(!node.isScript()); + } + return node; + } + }); + } else { + assert isLazy; + functionNode.accept(new NodeVisitor() { + @Override + public Node enter(final FunctionNode node) { + node.setIsLazy(false); + return null; //TODO do we want to do this recursively? then return "node" instead + } + }); + } + + assert functionNode != null; + final boolean oldStrict = strict; + + try { + strict |= functionNode.isStrictMode(); + + if (!state.contains(State.LOWERED)) { + debugPrintAST(); + functionNode.accept(new Lower(this)); + state.add(State.LOWERED); + + if (errors.hasErrors()) { + return false; + } + } + + scriptName = computeNames(); + + // Main script code always goes to this compile unit. Note that since we start this with zero weight + // and add script code last this class may end up slightly larger than others, but reserving one class + // just for the main script seems wasteful. + final CompileUnit scriptCompileUnit = addCompileUnit(firstCompileUnitName(), 0l); + new Splitter(this, functionNode, scriptCompileUnit).split(); + assert functionNode.getCompileUnit() == scriptCompileUnit; + + /** Compute compile units */ + assert strict == functionNode.isStrictMode() : "strict == " + strict + " but functionNode == " + functionNode.isStrictMode(); + if (functionNode.isStrictMode()) { + strict = true; + } + + try { + final CodeGenerator codegen = new CodeGenerator(this); + functionNode.accept(codegen); + codegen.generateScopeCalls(); + debugPrintAST(); + debugPrintParse(); + } catch (final VerifyError e) { + if (context._verify_code || context._print_code) { + context.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage()); + if (context._dump_on_error) { + e.printStackTrace(context.getErr()); + } + } else { + throw e; + } + } + + state.add(State.EMITTED); + + code = new TreeMap<>(); + for (final CompileUnit compileUnit : compileUnits) { + final ClassEmitter classEmitter = compileUnit.getClassEmitter(); + classEmitter.end(); + + if (!errors.hasErrors()) { + final byte[] bytecode = classEmitter.toByteArray(); + if (bytecode != null) { + code.put(compileUnit.getUnitClassName(), bytecode); + debugDisassemble(); + debugVerify(); + } + } + } + + if (code.isEmpty()) { + return false; + } + + try { + dumpClassFiles(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + + return true; + } finally { + strict = oldStrict; + } + } + + /** + * Install compiled classes into a given loader + * @param installer that takes the generated classes and puts them in the system + * @return root script class - if there are several compile units they will also be installed + */ + public Class<?> install(final CodeInstaller installer) { + assert state.contains(State.EMITTED); + assert scriptName != null; + + Class<?> rootClass = null; + + for (final Entry<String, byte[]> entry : code.entrySet()) { + final String className = entry.getKey(); + LOG.info("Installing class " + className); + + final byte[] bytecode = entry.getValue(); + final Class<?> clazz = installer.install(Compiler.binaryName(className), bytecode); + + if (rootClass == null && firstCompileUnitName().equals(className)) { + rootClass = clazz; + } + + try { + //use reflection to write source and constants table to installed classes + clazz.getField(SOURCE.tag()).set(null, source); + clazz.getField(CONSTANTS.tag()).set(null, constantData.toArray()); + } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + LOG.info("Root class: " + rootClass); + + return rootClass; + } + + /** + * Find a unit that will hold a node of the specified weight. + * + * @param weight Weight of a node + * @return Unit to hold node. + */ + CompileUnit findUnit(final long weight) { + for (final CompileUnit unit : compileUnits) { + if (unit.canHold(weight)) { + unit.addWeight(weight); + return unit; + } + } + + return addCompileUnit(weight); + } + + /** + * Generate a uniqueName name. Public as {@link Parser} is using this to + * create symbols in a different package + * + * @param name to base unique name on + * @return unique name + */ + public String uniqueName(final String name) { + return namespace.uniqueName(name); + } + + /** + * Internal function to compute reserved names and base names for class to + * be generated + * + * @return scriptName + */ + private String computeNames() { + // Reserve internally used names. + addReservedNames(); + + if (dumpClass) { + // get source file name and remove ".js" + final String baseName = getSource().getName(); + final int index = baseName.lastIndexOf(".js"); + if (index != -1) { + return baseName.substring(0, index); + } + return baseName; + } + + return namespace.getParent().uniqueName( + DEFAULT_SCRIPT_NAME.tag() + + '$' + + safeSourceName(source) + + (isLazy ? CompilerConstants.LAZY.tag() : "") + ); + } + + private static String safeSourceName(final Source source) { + String baseName = new File(source.getName()).getName(); + final int index = baseName.lastIndexOf(".js"); + if (index != -1) { + baseName = baseName.substring(0, index); + } + + baseName = baseName.replace('.', '_').replace('-', '_'); + final String mangled = Mangler.mangle(baseName); + + baseName = mangled != null ? mangled : baseName; + return baseName; + } + + static void verify(final Context context, final byte[] code) { + context.verify(code); + } + + /** + * Fill in the namespace with internally reserved names. + */ + private void addReservedNames() { + namespace.uniqueName(SCOPE.tag()); + namespace.uniqueName(THIS.tag()); + } + + /** + * Get the constant data for this Compiler + * + * @return the constant data + */ + public ConstantData getConstantData() { + return constantData; + } + + /** + * Get the Context used for Compilation + * @see Context + * @return the context + */ + public Context getContext() { + return context; + } + + /** + * Get the Source being compiled + * @see Source + * @return the source + */ + public Source getSource() { + return source; + } + + /** + * Get the error manager used for this compiler + * @return the error manager + */ + public ErrorManager getErrors() { + return errors; + } + + /** + * Get the namespace used for this Compiler + * @see Namespace + * @return the namespace + */ + public Namespace getNamespace() { + return namespace; + } + + /* + * Debugging + */ + + /** + * Print the AST before or after lowering, see --print-ast, --print-lower-ast + */ + private void debugPrintAST() { + assert functionNode != null; + if (context._print_lower_ast && state.contains(State.LOWERED) || + context._print_ast && !state.contains(State.LOWERED)) { + context.getErr().println(new ASTWriter(functionNode)); + } + } + + /** + * Print the parsed code before or after lowering, see --print-parse, --print-lower-parse + */ + private boolean debugPrintParse() { + if (errors.hasErrors()) { + return false; + } + + assert functionNode != null; + + if (context._print_lower_parse && state.contains(State.LOWERED) || + context._print_parse && !state.contains(State.LOWERED)) { + final PrintVisitor pv = new PrintVisitor(); + functionNode.accept(pv); + context.getErr().print(pv); + context.getErr().flush(); + } + + return true; + } + + private void debugDisassemble() { + assert code != null; + if (context._print_code) { + for (final Map.Entry<String, byte[]> entry : code.entrySet()) { + context.getErr().println("CLASS: " + entry.getKey()); + context.getErr().println(); + ClassEmitter.disassemble(context, entry.getValue()); + context.getErr().println("======"); + } + } + } + + private void debugVerify() { + if (context._verify_code) { + for (final Map.Entry<String, byte[]> entry : code.entrySet()) { + Compiler.verify(context, entry.getValue()); + } + } + } + + /** + * Implements the "-d" option - dump class files from script to specified output directory + * + * @throws IOException if classes cannot be written + */ + private void dumpClassFiles() throws IOException { + if (context._dest_dir == null) { + return; + } + + assert code != null; + + for (final Entry<String, byte[]> entry : code.entrySet()) { + final String className = entry.getKey(); + final String fileName = className.replace('.', File.separatorChar) + ".class"; + final int index = fileName.lastIndexOf(File.separatorChar); + + if (index != -1) { + final File dir = new File(fileName.substring(0, index)); + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException(ECMAErrors.getMessage("io.error.cant.write", dir.toString())); + } + } + + final byte[] bytecode = entry.getValue(); + final File outFile = new File(context._dest_dir, fileName); + try (final FileOutputStream fos = new FileOutputStream(outFile)) { + fos.write(bytecode); + } + } + } + + /** + * Convert a package/class name to a binary name. + * + * @param name Package/class name. + * @return Binary name. + */ + public static String binaryName(final String name) { + return name.replace('/', '.'); + } + + /** + * Convert a binary name to a package/class name. + * + * @param name Binary name. + * @return Package/class name. + */ + public static String pathName(final String name) { + return name.replace('.', '/'); + } + + +} diff --git a/src/jdk/nashorn/internal/codegen/CompilerConstants.java b/src/jdk/nashorn/internal/codegen/CompilerConstants.java new file mode 100644 index 00000000..7da72cdc --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/CompilerConstants.java @@ -0,0 +1,687 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.nashorn.internal.runtime.linker.Lookup.MH; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.Source; + +/** + * This class represents constant names of variables, methods and fields in + * the compiler + */ + +public enum CompilerConstants { + /** the __FILE__ variable */ + __FILE__, + + /** the __DIR__ variable */ + __DIR__, + + /** the __LINE__ variable */ + __LINE__, + + /** lazy prefix for classes of jitted methods */ + LAZY("Lazy"), + + /** leaf tag used for functions that require no scope */ + LEAF("__leaf__"), + + /** constructor name */ + INIT("<init>"), + + /** static initializer name */ + CLINIT("<clinit>"), + + /** eval name */ + EVAL("eval"), + + /** source name and class */ + SOURCE("source", Source.class), + + /** constants name and class */ + CONSTANTS("constants", Object[].class), + + /** strict mode field name and type */ + STRICT_MODE("strictMode", boolean.class), + + /** default script name */ + DEFAULT_SCRIPT_NAME("Script"), + + /** function prefix for anonymous functions */ + FUNCTION_PREFIX("function$"), + + /** method name for Java method that is script entry point */ + RUN_SCRIPT("runScript"), + + /** this name and slot */ + THIS("this", 0), + + /** this debugger symbol */ + THIS_DEBUGGER("__this__"), + + /** scope name, type and slot */ + SCOPE("__scope__", ScriptObject.class, 2), + + /** the return value variable name were intermediate results are stored for scripts */ + SCRIPT_RETURN("__return__"), + + /** the callee value variable when necessary */ + CALLEE("__callee__", ScriptFunction.class, 1), + + /** the varargs variable when necessary */ + VARARGS("__varargs__"), + + /** the arguments vector when necessary and the slot */ + ARGUMENTS("arguments", Object.class, 2), + + /** prefix for iterators for for (x in ...) */ + ITERATOR_PREFIX("$iter"), + + /** prefix for tag variable used for switch evaluation */ + SWITCH_TAG_PREFIX("$tag"), + + /** prefix for all exceptions */ + EXCEPTION_PREFIX("$exception"), + + /** prefix for quick slots generated in Store */ + QUICK_PREFIX("$quick"), + + /** prefix for temporary variables */ + TEMP_PREFIX("$temp"), + + /** prefix for literals */ + LITERAL_PREFIX("$lit"), + + /** prefix for map */ + MAP("$map", 1), + + /** prefix for regexps */ + REGEX_PREFIX("$regex"), + + /** init scope */ + INIT_SCOPE("$scope", 2), + + /** init arguments */ + INIT_ARGUMENTS("$arguments", 3), + + /** prefix for all ScriptObject subclasses with fields, @see ObjectGenerator */ + JS_OBJECT_PREFIX("JO$"), + + /** name for allocate method in JO$ objects */ + ALLOCATE("allocate"), + + /** prefix for split methods, @see Splitter */ + SPLIT_PREFIX("$split"), + + /** prefix for split array method and slot */ + SPLIT_ARRAY_ARG("split_array", 3), + + /** get string from constant pool */ + GET_STRING("$getString"), + + /** get map */ + GET_MAP("$getMap"), + + /** get map */ + SET_MAP("$setMap"), + + /** get array prefix */ + GET_ARRAY_PREFIX("$get"), + + /** get array suffix */ + GET_ARRAY_SUFFIX("$array"); + + private final String tag; + private final Class<?> type; + private final int slot; + + private CompilerConstants() { + this.tag = name(); + this.type = null; + this.slot = -1; + } + + private CompilerConstants(final String tag) { + this(tag, -1); + } + + private CompilerConstants(final String tag, final int slot) { + this(tag, null, slot); + } + + private CompilerConstants(final String tag, final Class<?> type) { + this(tag, type, -1); + } + + private CompilerConstants(final String tag, final Class<?> type, final int slot) { + this.tag = tag; + this.type = type; + this.slot = slot; + } + + /** + * Return the tag for this compile constant. Deliberately avoiding "name" here + * not to conflate with enum implementation. This is the master string for the + * constant - every constant has one. + * + * @return the tag + */ + public final String tag() { + return tag; + } + + /** + * Return the type for this compile constant + * + * @return type for this constant's instances, or null if N/A + */ + public final Class<?> type() { + return type; + } + + /** + * Return the slot for this compile constant + * + * @return byte code slot where constant is stored or -1 if N/A + */ + public final int slot() { + return slot; + } + + /** + * Return a descriptor for this compile constant. Only relevant if it has + * a type + * + * @return descriptor the descriptor + */ + public final String descriptor() { + assert type != null : " asking for descriptor of typeless constant"; + return typeDescriptor(type); + } + + /** + * Get the internal class name for a type + * + * @param type a type + * @return the internal name for this type + */ + public static String className(final Class<?> type) { + return Type.getInternalName(type); + } + + /** + * Get the method descriptor for a given method type collection + * + * @param rtype return type + * @param ptypes parameter types + * + * @return internal descriptor for this method + */ + public static String methodDescriptor(final Class<?> rtype, final Class<?>... ptypes) { + return Type.getMethodDescriptor(rtype, ptypes); + } + + /** + * Get the type descriptor for a type + * + * @param clazz a type + * + * @return the internal descriptor for this type + */ + public static String typeDescriptor(final Class<?> clazz) { + return Type.getDescriptor(clazz); + } + + /** + * Create a call representing a void constructor for a given type. Don't + * attempt to look this up at compile time + * + * @param clazz the class + * + * @return Call representing void constructor for type + */ + public static Call constructorNoLookup(final Class<?> clazz) { + return specialCallNoLookup(clazz, INIT.tag(), void.class); + } + + /** + * Create a call representing a constructor for a given type. Don't + * attempt to look this up at compile time + * + * @param className the type class name + * @param ptypes the parameter types for the constructor + * + * @return Call representing constructor for type + */ + public static Call constructorNoLookup(final String className, final Class<?>... ptypes) { + return specialCallNoLookup(className, INIT.tag(), methodDescriptor(void.class, ptypes)); + } + + /** + * Create a call representing a constructor for a given type. Don't + * attempt to look this up at compile time + * + * @param clazz the class name + * @param ptypes the parameter types for the constructor + * + * @return Call representing constructor for type + */ + public static Call constructorNoLookup(final Class<?> clazz, final Class<?>... ptypes) { + return specialCallNoLookup(clazz, INIT.tag(), void.class, ptypes); + } + + /** + * Create a call representing an invokespecial to a given method. Don't + * attempt to look this up at compile time + * + * @param className the class name + * @param name the method name + * @param desc the descriptor + * + * @return Call representing specified invokespecial call + */ + public static Call specialCallNoLookup(final String className, final String name, final String desc) { + return new Call(null, className, name, desc) { + @Override + public MethodEmitter invoke(final MethodEmitter method) { + return method.invokeSpecial(className, name, descriptor); + } + }; + } + + /** + * Create a call representing an invokespecial to a given method. Don't + * attempt to look this up at compile time + * + * @param clazz the class + * @param name the method name + * @param rtype the return type + * @param ptypes the parameter types + * + * @return Call representing specified invokespecial call + */ + public static Call specialCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) { + return specialCallNoLookup(className(clazz), name, methodDescriptor(rtype, ptypes)); + } + + /** + * Create a call representing an invokestatic to a given method. Don't + * attempt to look this up at compile time + * + * @param className the class name + * @param name the method name + * @param desc the descriptor + * + * @return Call representing specified invokestatic call + */ + public static Call staticCallNoLookup(final String className, final String name, final String desc) { + return new Call(null, className, name, desc) { + @Override + public MethodEmitter invoke(final MethodEmitter method) { + return method.invokeStatic(className, name, descriptor); + } + }; + } + + /** + * Create a call representing an invokestatic to a given method. Don't + * attempt to look this up at compile time + * + * @param clazz the class + * @param name the method name + * @param rtype the return type + * @param ptypes the parameter types + * + * @return Call representing specified invokestatic call + */ + public static Call staticCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) { + return staticCallNoLookup(className(clazz), name, methodDescriptor(rtype, ptypes)); + } + + /** + * Create a call representing an invokevirtual to a given method. Don't + * attempt to look this up at compile time + * + * @param clazz the class + * @param name the method name + * @param rtype the return type + * @param ptypes the parameter types + * + * @return Call representing specified invokevirtual call + */ + public static Call virtualCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) { + return new Call(null, className(clazz), name, methodDescriptor(rtype, ptypes)) { + @Override + public MethodEmitter invoke(final MethodEmitter method) { + return method.invokeVirtual(className, name, descriptor); + } + }; + } + + /** + * Create a call representing an invokeinterface to a given method. Don't + * attempt to look this up at compile time + * + * @param clazz the class + * @param name the method name + * @param rtype the return type + * @param ptypes the parameter types + * + * @return Call representing specified invokeinterface call + */ + public static Call interfaceCallNoLookup(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) { + return new Call(null, className(clazz), name, methodDescriptor(rtype, ptypes)) { + @Override + public MethodEmitter invoke(final MethodEmitter method) { + return method.invokeInterface(className, name, descriptor); + } + }; + } + + /** + * Create a FieldAccess representing a virtual field, that can be subject to put + * or get operations + * + * @param className name of the class where the field is a member + * @param name name of the field + * @param desc type descriptor of the field + * + * @return a field access object giving access code generation method for the virtual field + */ + public static FieldAccess virtualField(final String className, final String name, final String desc) { + return new FieldAccess(className, name, desc) { + @Override + public MethodEmitter get(final MethodEmitter method) { + return method.getField(className, name, descriptor); + } + + @Override + public void put(final MethodEmitter method) { + method.putField(className, name, descriptor); + } + }; + } + + /** + * Create a FieldAccess representing a virtual field, that can be subject to put + * or get operations + * + * @param clazz class where the field is a member + * @param name name of the field + * @param type type of the field + * + * @return a field access object giving access code generation method for the virtual field + */ + public static FieldAccess virtualField(final Class<?> clazz, final String name, final Class<?> type) { + return virtualField(className(clazz), name, typeDescriptor(type)); + } + + /** + * Create a FieldAccess representing a static field, that can be subject to put + * or get operations + * + * @param className name of the class where the field is a member + * @param name name of the field + * @param desc type descriptor of the field + * + * @return a field access object giving access code generation method for the static field + */ + public static FieldAccess staticField(final String className, final String name, final String desc) { + return new FieldAccess(className, name, desc) { + @Override + public MethodEmitter get(final MethodEmitter method) { + return method.getStatic(className, name, descriptor); + } + + @Override + public void put(final MethodEmitter method) { + method.putStatic(className, name, descriptor); + } + }; + } + + /** + * Create a FieldAccess representing a static field, that can be subject to put + * or get operations + * + * @param clazz class where the field is a member + * @param name name of the field + * @param type type of the field + * + * @return a field access object giving access code generation method for the virtual field + */ + public static FieldAccess staticField(final Class<?> clazz, final String name, final Class<?> type) { + return staticField(className(clazz), name, typeDescriptor(type)); + } + + /** + * Create a static call, looking up the method handle for it at the same time + * + * @param clazz the class + * @param name the name of the method + * @param rtype the return type of the method + * @param ptypes the parameter types of the method + * + * @return the call object representing the static call + */ + public static Call staticCall(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) { + return staticCall(MethodHandles.publicLookup(), clazz, name, rtype, ptypes); + } + + /** + * Create a static call, given an explicit lookup, looking up the method handle for it at the same time + * + * @param lookup the lookup + * @param clazz the class + * @param name the name of the method + * @param rtype the return type + * @param ptypes the parameter types + * + * @return the call object representing the static call + */ + public static Call staticCall(final MethodHandles.Lookup lookup, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) { + return new Call(MH.findStatic(lookup, clazz, name, MH.type(rtype, ptypes)), className(clazz), name, methodDescriptor(rtype, ptypes)) { + @Override + public MethodEmitter invoke(final MethodEmitter method) { + return method.invokeStatic(className, name, descriptor); + } + }; + } + + /** + * Create a virtual call, looking up the method handle for it at the same time + * + * @param clazz the class + * @param name the name of the method + * @param rtype the return type of the method + * @param ptypes the parameter types of the method + * + * @return the call object representing the virtual call + */ + public static Call virtualCall(final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) { + return virtualCall(MethodHandles.publicLookup(), clazz, name, rtype, ptypes); + } + + /** + * Create a virtual call, given an explicit lookup, looking up the method handle for it at the same time + * + * @param lookup the lookup + * @param clazz the class + * @param name the name of the method + * @param rtype the return type + * @param ptypes the parameter types + * + * @return the call object representing the virtual call + */ + public static Call virtualCall(final MethodHandles.Lookup lookup, final Class<?> clazz, final String name, final Class<?> rtype, final Class<?>... ptypes) { + return new Call(MH.findVirtual(lookup, clazz, name, MH.type(rtype, ptypes)), className(clazz), name, methodDescriptor(rtype, ptypes)) { + @Override + public MethodEmitter invoke(final MethodEmitter method) { + return method.invokeVirtual(className, name, descriptor); + } + }; + } + + /** + * Private class representing an access. This can generate code into a method code or + * a field access. + */ + private abstract static class Access { + protected final MethodHandle methodHandle; + protected final String className; + protected final String name; + protected final String descriptor; + + /** + * Constructor + * + * @param methodHandle methodHandle or null if none + * @param className class name for access + * @param name field or method name for access + * @param descriptor descriptor for access field or method + */ + protected Access(final MethodHandle methodHandle, final String className, final String name, final String descriptor) { + this.methodHandle = methodHandle; + this.className = className; + this.name = name; + this.descriptor = descriptor; + } + + /** + * Get the method handle, or null if access hasn't been looked up + * + * @return method handle + */ + public MethodHandle methodHandle() { + return methodHandle; + } + + /** + * Get the class name of the access + * + * @return the class name + */ + public String className() { + return className; + } + + /** + * Get the field name or method name of the access + * + * @return the name + */ + public String name() { + return name; + } + + /** + * Get the descriptor of the method or field of the access + * + * @return the descriptor + */ + public String descriptor() { + return descriptor; + } + } + + /** + * Field access - this can be used for generating code for static or + * virtual field accesses + */ + public abstract static class FieldAccess extends Access { + /** + * Constructor + * + * @param className name of the class where the field is + * @param name name of the field + * @param descriptor descriptor of the field + */ + protected FieldAccess(final String className, final String name, final String descriptor) { + super(null, className, name, descriptor); + } + + /** + * Generate get code for the field + * + * @param emitter a method emitter + * + * @return the method emitter + */ + protected abstract MethodEmitter get(final MethodEmitter emitter); + + /** + * Generate put code for the field + * + * @param emitter a method emitter + */ + protected abstract void put(final MethodEmitter emitter); + } + + /** + * Call - this can be used for generating code for different types of calls + */ + public abstract static class Call extends Access { + + /** + * Constructor + * + * @param className class name for the method of the call + * @param name method name + * @param descriptor method descriptor + */ + protected Call(final String className, final String name, final String descriptor) { + super(null, className, name, descriptor); + } + + /** + * Constructor + * + * @param methodHandle method handle for the call if resolved + * @param className class name for the method of the call + * @param name method name + * @param descriptor method descriptor + */ + protected Call(final MethodHandle methodHandle, final String className, final String name, final String descriptor) { + super(methodHandle, className, name, descriptor); + } + + /** + * Generate invocation code for the method + * + * @param emitter a method emitter + * + * @return the method emitter + */ + protected abstract MethodEmitter invoke(final MethodEmitter emitter); + } + +} diff --git a/src/jdk/nashorn/internal/codegen/ConstantData.java b/src/jdk/nashorn/internal/codegen/ConstantData.java new file mode 100644 index 00000000..f02144a6 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/ConstantData.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Manages constants needed by code generation. Objects are maintained in an + * interning maps to remove duplicates. + */ +public class ConstantData { + /** Constant table. */ + final List<Object> constants; + + /** Constant table string interning map. */ + final Map<String, Integer> stringMap; + + /** Constant table object interning map. */ + final Map<Object, Integer> objectMap; + + private static class ArrayWrapper { + private final Object array; + private final int hashCode; + + public ArrayWrapper(final Object array) { + this.array = array; + this.hashCode = calcHashCode(); + } + + /** + * Calculate a shallow hashcode for the array. + * @return Hashcode with elements factored in. + */ + private int calcHashCode() { + final Class<?> cls = array.getClass(); + + if (cls == Object[].class) { + return Arrays.hashCode((Object[])array); + } else if (cls == double[].class) { + return Arrays.hashCode((double[])array); + } if (cls == long[].class) { + return Arrays.hashCode((long[])array); + } if (cls == int[].class) { + return Arrays.hashCode((int[])array); + } + + throw new AssertionError("ConstantData doesn't support " + cls); + } + + @Override + public boolean equals(final Object other) { + if (!(other instanceof ArrayWrapper)) { + return false; + } + + final Object otherArray = ((ArrayWrapper)other).array; + + if (array == otherArray) { + return true; + } + + final Class<?> cls = array.getClass(); + + if (cls == otherArray.getClass()) { + if (cls == Object[].class) { + return Arrays.equals((Object[])array, (Object[])otherArray); + } else if (cls == double[].class) { + return Arrays.equals((double[])array, (double[])otherArray); + } else if (cls == long[].class) { + return Arrays.equals((long[])array, (long[])otherArray); + } else if (cls == int[].class) { + return Arrays.equals((int[])array, (int[])otherArray); + } + } + + return false; + } + + @Override + public int hashCode() { + return hashCode; + } + } + + /** + * Constructor + */ + ConstantData() { + this.constants = new ArrayList<>(); + this.stringMap = new HashMap<>(); + this.objectMap = new HashMap<>(); + } + + /** + * Add a string to the constant data + * + * @param string the string to add + * @return the index in the constant pool that the string was given + */ + public int add(final String string) { + final Integer value = stringMap.get(string); + + if (value != null) { + return value.intValue(); + } + + constants.add(string); + final int index = constants.size() - 1; + stringMap.put(string, index); + + return index; + } + + /** + * Add an object to the constant data + * + * @param object the string to add + * @return the index in the constant pool that the object was given + */ + public int add(final Object object) { + final Object entry = object.getClass().isArray() ? new ArrayWrapper(object) : object; + final Integer value = objectMap.get(entry); + + if (value != null) { + return value.intValue(); + } + + constants.add(object); + final int index = constants.size() - 1; + objectMap.put(entry, index); + + return index; + } + + Object[] toArray() { + return constants.toArray(); + } +} diff --git a/src/jdk/nashorn/internal/codegen/Emitter.java b/src/jdk/nashorn/internal/codegen/Emitter.java new file mode 100644 index 00000000..a17b38bb --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/Emitter.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +/** + * Interface for anything that interacts with a low level bytecode + * generation module, for example ASM. + * <p> + * This is pretty generic, i.e. it can be a ClassEmitter, MethodEmitter + * or potentially even more fine grained stuff. + * + */ +public interface Emitter { + + /** + * Register the start of emission for this CodeEmitter + */ + public void begin(); + + /** + * Register the end of emission for this CodeEmitter. + * This is typically required before generated code can + * be requested from it + */ + public void end(); +} diff --git a/src/jdk/nashorn/internal/codegen/Frame.java b/src/jdk/nashorn/internal/codegen/Frame.java new file mode 100644 index 00000000..4ec6f895 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/Frame.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import jdk.nashorn.internal.ir.Symbol; + +/** + * Tracks the variable area state. + * + */ +public final class Frame { + /** Previous frame. */ + private Frame previous; + + /** Current variables. */ + private final ArrayList<Symbol> symbols; + + /** Number of slots in previous frame. */ + private int baseCount; + + /** Number of slots in this frame. */ + private int count; + + /** + * Constructor. + * + * @param previous frame, the parent variable frame + */ + public Frame(final Frame previous) { + this.previous = previous; + this.symbols = new ArrayList<>(); + this.baseCount = getBaseCount(); + this.count = 0; + } + + /** + * Copy constructor + * @param frame + * @param symbols + */ + private Frame(final Frame frame, final List<Symbol> symbols) { + this.previous = frame.getPrevious() == null ? null : new Frame(frame.getPrevious(), frame.getPrevious().getSymbols()); + this.symbols = new ArrayList<>(frame.getSymbols()); + this.baseCount = frame.getBaseCount(); + this.count = frame.getCount(); + } + + /** + * Copy the frame + * + * @return a new frame with the identical contents + */ + public Frame copy() { + return new Frame(this, getSymbols()); + } + + /** + * Add a new variable to the frame. + * @param symbol Symbol representing variable. + */ + public void addSymbol(final Symbol symbol) { + final int slot = symbol.getSlot(); + if (slot < 0) { + symbols.add(symbol); + count += symbol.slotCount(); + } + } + + /** + * Realign slot numbering prior to code generation. + * @return Number of slots in frame. + */ + public int realign() { + baseCount = getBaseCount(); + count = 0; + + for (final Symbol symbol : symbols) { + if (symbol.hasSlot()) { + symbol.setSlot(baseCount + count); + count += symbol.slotCount(); + } + } + + return count; + } + + /** + * Return the slot count of previous frames. + * @return Number of slots in previous frames. + */ + private int getBaseCount() { + return previous != null ? previous.getSlotCount() : 0; + } + + /** + * Determine the number of slots to top of frame. + * @return Number of slots in total. + */ + public int getSlotCount() { + return baseCount + count; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + Frame f = this; + boolean hasPrev = false; + int pos = 0; + + do { + if (hasPrev) { + sb.append("\n"); + } + + sb.append("#"). + append(pos++). + append(" {baseCount:"). + append(baseCount). + append(", "). + append("count:"). + append(count). + append("} "); + + for (final Symbol var : f.getSymbols()) { + sb.append('['). + append(var.toString()). + append(' '). + append(var.hashCode()). + append("] "); + } + + f = f.getPrevious(); + hasPrev = true; + } while (f != null); + + return sb.toString(); + } + + /** + * Get variable count for this frame + * @return variable count + */ + public int getCount() { + return count; + } + + /** + * Get previous frame + * @return previous frame + */ + public Frame getPrevious() { + return previous; + } + + /** + * Set previous frame + * @param previous previous frame + */ + public void setPrevious(final Frame previous) { + this.previous = previous; + } + + /** + * Get symbols in frame + * @return a list of symbols in this frame + */ + public List<Symbol> getSymbols() { + return Collections.unmodifiableList(symbols); + } + } diff --git a/src/jdk/nashorn/internal/codegen/FunctionSignature.java b/src/jdk/nashorn/internal/codegen/FunctionSignature.java new file mode 100644 index 00000000..df587441 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/FunctionSignature.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; +import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; + +import java.util.List; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.linker.LinkerCallSite; + +/** + * Class that generates function signatures for dynamic calls + */ +public final class FunctionSignature { + + /** parameter types that ASM can understand */ + private final Type[] paramTypes; + + /** return type that ASM can understand */ + private final Type returnType; + + /** valid Java descriptor string for function */ + private final String descriptor; + + /** + * Constructor + * + * Create a FunctionSignature given arguments as AST Nodes + * + * @param hasSelf does the function have a self slot? + * @param retType what is the return type + * @param args argument list of AST Nodes + */ + public FunctionSignature(final boolean hasSelf, final Type retType, final List<? extends Node> args) { + this(hasSelf, false, retType, FunctionSignature.typeArray(args)); + } + + /** + * Constructor + * + * Create a FunctionSignature given arguments as AST Nodes + * + * @param hasSelf does the function have a self slot? + * @param hasCallee does the function need a callee variable + * @param retType what is the return type + * @param args argument list of AST Nodes + */ + public FunctionSignature(final boolean hasSelf, final boolean hasCallee, final Type retType, final List<? extends Node> args) { + this(hasSelf, hasCallee, retType, FunctionSignature.typeArray(args)); + } + + /** + * Constructor + * + * Create a FunctionSignature given arguments as AST Nodes + * + * @param hasSelf does the function have a self slot? + * @param retType what is the return type + * @param nArgs number of arguments + */ + public FunctionSignature(final boolean hasSelf, final Type retType, final int nArgs) { + this(hasSelf, false, retType, FunctionSignature.objectArgs(nArgs)); + } + + /** + * Constructor + * + * Create a FunctionSignature given argument types only + * + * @param hasSelf does the function have a self slot? + * @param hasCallee does the function have a callee slot? + * @param retType what is the return type + * @param argTypes argument list of AST Nodes + */ + public FunctionSignature(final boolean hasSelf, final boolean hasCallee, final Type retType, final Type... argTypes) { + final boolean isVarArg; + + int count = 1; + + if (argTypes == null) { + isVarArg = true; + } else { + isVarArg = argTypes.length > LinkerCallSite.ARGLIMIT; + count = isVarArg ? 1 : argTypes.length; + } + + int first = 0; + + if (hasSelf) { + count++; + first++; + } + if (hasCallee) { + count++; + first++; + } + + paramTypes = new Type[count]; + + if (hasSelf) { + paramTypes[THIS.slot()] = Type.OBJECT; + } + if (hasCallee) { + paramTypes[CALLEE.slot()] = Type.typeFor(ScriptFunction.class); + } + + if (isVarArg) { + paramTypes[first] = Type.OBJECT_ARRAY; + } else if (argTypes != null) { + for (int i = first, j = 0; i < count; i++, j++) { + paramTypes[i] = argTypes[j]; + if (paramTypes[i].isObject()) { + paramTypes[i] = Type.OBJECT; //TODO: for now, turn java/lang/String into java/lang/Object as we aren't as specific. + } + } + } else { + assert false : "isVarArgs cannot be false when argTypes are null"; + } + + returnType = retType; + descriptor = Type.getMethodDescriptor(returnType, paramTypes); + } + + /** + * Internal function that converts an array of nodes to their Types + * + * @param args node arg list + * + * @return the array of types + */ + private static Type[] typeArray(final List<? extends Node> args) { + if (args == null) { + return null; + } + + final Type[] typeArray = new Type[args.size()]; + + int pos = 0; + for (final Node arg : args) { + typeArray[pos++] = arg.getType(); + } + + return typeArray; + } + + @Override + public String toString() { + return descriptor; + } + + /** + * @return the number of param types + */ + public int size() { + return paramTypes.length; + } + + /** + * Returns the generic signature of the function being compiled. + * + * @param functionNode function being compiled. + * @return function signature. + */ + public static String functionSignature(final FunctionNode functionNode) { + return new FunctionSignature( + true, + functionNode.needsCallee(), + functionNode.getReturnType(), + (functionNode.isVarArg() && !functionNode.isScript()) ? + null : + functionNode.getParameters()).toString(); + } + + private static Type[] objectArgs(final int nArgs) { + final Type[] array = new Type[nArgs]; + for (int i = 0; i < nArgs; i++) { + array[i] = Type.OBJECT; + } + return array; + } + +} diff --git a/src/jdk/nashorn/internal/codegen/Lower.java b/src/jdk/nashorn/internal/codegen/Lower.java new file mode 100644 index 00000000..20ed637b --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/Lower.java @@ -0,0 +1,3057 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; +import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL; +import static jdk.nashorn.internal.codegen.CompilerConstants.EXCEPTION_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.ITERATOR_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; +import static jdk.nashorn.internal.codegen.CompilerConstants.SCRIPT_RETURN; +import static jdk.nashorn.internal.codegen.CompilerConstants.SWITCH_TAG_PREFIX; +import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; +import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.ADD; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.DELETE; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.EQ; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.EQ_STRICT; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.FAIL_DELETE; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.GE; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.GT; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.IN; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.INSTANCEOF; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.LE; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.LT; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.NE; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.NE_STRICT; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.TYPEOF; +import static jdk.nashorn.internal.ir.RuntimeNode.Request.VOID; +import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL; +import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; +import static jdk.nashorn.internal.ir.Symbol.IS_LET; +import static jdk.nashorn.internal.ir.Symbol.IS_PARAM; +import static jdk.nashorn.internal.ir.Symbol.IS_THIS; +import static jdk.nashorn.internal.ir.Symbol.IS_VAR; +import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.AccessNode; +import jdk.nashorn.internal.ir.Assignment; +import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.BreakNode; +import jdk.nashorn.internal.ir.CallNode; +import jdk.nashorn.internal.ir.CaseNode; +import jdk.nashorn.internal.ir.CatchNode; +import jdk.nashorn.internal.ir.ContinueNode; +import jdk.nashorn.internal.ir.DoWhileNode; +import jdk.nashorn.internal.ir.EmptyNode; +import jdk.nashorn.internal.ir.ExecuteNode; +import jdk.nashorn.internal.ir.ForNode; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.IfNode; +import jdk.nashorn.internal.ir.IndexNode; +import jdk.nashorn.internal.ir.LabelNode; +import jdk.nashorn.internal.ir.LineNumberNode; +import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.ObjectNode; +import jdk.nashorn.internal.ir.PropertyNode; +import jdk.nashorn.internal.ir.ReferenceNode; +import jdk.nashorn.internal.ir.ReturnNode; +import jdk.nashorn.internal.ir.RuntimeNode; +import jdk.nashorn.internal.ir.RuntimeNode.Request; +import jdk.nashorn.internal.ir.SwitchNode; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.ir.TernaryNode; +import jdk.nashorn.internal.ir.ThrowNode; +import jdk.nashorn.internal.ir.TryNode; +import jdk.nashorn.internal.ir.UnaryNode; +import jdk.nashorn.internal.ir.VarNode; +import jdk.nashorn.internal.ir.WhileNode; +import jdk.nashorn.internal.ir.WithNode; +import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.parser.Token; +import jdk.nashorn.internal.parser.TokenType; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.DebugLogger; +import jdk.nashorn.internal.runtime.ECMAException; +import jdk.nashorn.internal.runtime.JSType; +import jdk.nashorn.internal.runtime.Property; +import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.ScriptRuntime; +import jdk.nashorn.internal.runtime.Source; +import jdk.nashorn.internal.runtime.Undefined; + +/** + * Lower to more primitive operations. After lowering, an AST has symbols and + * types. Lowering may also add specialized versions of methods to the script if + * the optimizer is turned on. + * + * Any expression that requires temporary storage as part of computation will + * also be detected here and give a temporary symbol + */ + +final class Lower extends NodeOperatorVisitor { + /** Current compiler. */ + private final Compiler compiler; + + /** Current source. */ + private final Source source; + + /** List of lowered statements */ + private List<Node> statements; + + /** All symbols that are declared locally in a function node */ + private List<Symbol> declaredSymbolsLocal; + + /** + * Local definitions in current block (to discriminate from function + * declarations always defined in the function scope. This is for + * "can be undefined" analysis. + */ + private Set<String> localDefs; + + /** + * Local definitions in current block to guard against cases like + * NASHORN-467 when things can be undefined as they are used before + * their local var definition. *sigh* JavaScript... + */ + private Set<String> localUses; + + /** + * Nesting level stack. Currently just used for loops to avoid the problem + * with terminal bodies that end with throw/return but still do continues to + * outer loops or same loop. + */ + private final Deque<Node> nesting; + + private static final DebugLogger LOG = new DebugLogger("lower"); + private static final boolean DEBUG = LOG.isEnabled(); + + /** + * Constructor. + * + * @param compiler the compiler + */ + Lower(final Compiler compiler) { + this.compiler = compiler; + this.source = compiler.getSource(); + this.statements = new ArrayList<>(); + this.nesting = new ArrayDeque<>(); + } + + private void nest(final Node node) { + nesting.push(node); + } + + private void unnest() { + nesting.pop(); + } + + static void debug(final String str) { + if (DEBUG) { + LOG.info(str); + } + } + + @Override + public Node leave(final AccessNode accessNode) { + //accessNode.setBase(convert(accessNode.getBase(), Type.OBJECT)); + getCurrentFunctionNode().newTemporary(Type.OBJECT, accessNode); //This is not always an object per se, but currently the narrowing logic resides in AccessSpecializer. @see AccessSpecializer! + + return accessNode; + } + + @Override + public Node enter(final Block block) { + /* + * Save the statement list from the outer construct we are currently + * generating and push frame + */ + final List<Node> savedStatements = statements; + final Set<String> savedDefs = localDefs; + final Set<String> savedUses = localUses; + + block.setFrame(getCurrentFunctionNode().pushFrame()); + + try { + /* + * Reset the statement instance var, new block + */ + statements = new ArrayList<>(); + localDefs = new HashSet<>(savedDefs); + localUses = new HashSet<>(savedUses); + + for (final Node statement : block.getStatements()) { + statement.accept(this); + /* + * This is slightly unsound, for example if we have a loop with + * a guarded statement like if (x) continue in the body and the + * body ends with TERMINAL, e.g. return; we removed the continue + * before we had the loop stack, as all we cared about was a + * return last in the loop. + * + * @see NASHORN-285 + */ + final Node lastStatement = Node.lastStatement(statements); + if (lastStatement != null && lastStatement.isTerminal()) { + block.copyTerminalFlags(lastStatement); + break; + } + } + + block.setStatements(statements); + } finally { + /* + * Restore the saved statements after block ends and pop frame + */ + statements = savedStatements; + localDefs = savedDefs; + localUses = savedUses; + + getCurrentFunctionNode().popFrame(); + } + + return null; + } + + /** + * Determine if Try block is inside target block. + * + * @param tryNode Try node to test. + * @param target Target block. + * + * @return true if try block is inside the target, false otherwise. + */ + private boolean isNestedTry(final TryNode tryNode, final Block target) { + for (Block current = getCurrentBlock(); current != target; current = current.getParent()) { + if (tryNode.getBody() == current) { + return true; + } + + for (final Block catchBlock : tryNode.getCatchBlocks()) { + if (catchBlock == current) { + return true; + } + } + } + + return false; + } + + /** + * Clones the body of the try finallys up to the target block. + * + * @param node first try node in the chain. + * @param targetNode target block of the break/continue statement or null for return + * + * @return true if terminates. + */ + private boolean copyFinally(final TryNode node, final Node targetNode) { + Block target = null; + + if (targetNode instanceof Block) { + target = (Block)targetNode; + } + + for (TryNode tryNode = node; tryNode != null; tryNode = tryNode.getNext()) { + if (target != null && !isNestedTry(tryNode, target)) { + return false; + } + + Block finallyBody = tryNode.getFinallyBody(); + if (finallyBody == null) { + continue; + } + + finallyBody = (Block)finallyBody.clone(); + final boolean hasTerminalFlags = finallyBody.hasTerminalFlags(); + + new ExecuteNode(source, finallyBody.getToken(), finallyBody.getFinish(), finallyBody).accept(this); + + if (hasTerminalFlags) { + getCurrentBlock().copyTerminalFlags(finallyBody); + return true; + } + } + + return false; + } + + @Override + public Node enter(final BreakNode breakNode) { + final TryNode tryNode = breakNode.getTryChain(); + + if (tryNode != null && copyFinally(tryNode, breakNode.getTargetNode())) { + return null; + } + + statements.add(breakNode); + + return null; + } + + /** + * Blesses nodes with an expectant type, converting if necessary. + * + * @param node Node to be blest. + * @param type Type class expected. + * + * @return Converted node or original node if no conversion is needed. + */ + private Node convert(final Node node, final Type type) { + + final Symbol symbol = node.getSymbol(); + final FunctionNode functionNode = getCurrentFunctionNode(); + + assert !type.isUnknown() : "unknown"; + + /* + * Conversions are now mandatory, have to be placed at code time and + * cannot be removed as there might be cases like: + * + * var x = 17; if (x + 1) { ... } + * + * x = Number(4711); if (x + 1) { ... } + * + * In the old world this behaved as follows: + * + * Here, x is originally inferred type to a number, then the if does a + * double add without a cast. However, the next statement turns x into + * an object, and this messes up the original. If we would now suddenly + * need an explicit cast for the first addition, given that we don't do + * fancy stuff like splitting live range. + * + * Even worse is the case where we have an eval that modifies local + * variables in the middle of a function and may widen a type suspected + * to be at most e.g. NUMBER to e.g. OBJECT. + * + * There are a few local optimizations we can do. If we want to do an + * OBJECT to OBJECT cast, for example, we can skip it as already is + * maximally wide, except if we are in a method with an eval where + * everything is possible... + * + * @see test/test262/test/suite/ch07/7.2/S7.2_A1.4_T2.js for an example + * of this. + */ + + assert symbol != null : "no symbol for " + node; + + /* check object to object cast */ + if (!functionNode.hasEval() && node.getType().isEquivalentTo(Type.OBJECT) && type.isEquivalentTo(Type.OBJECT)) { + return node; + } + + Node resultNode = node; + + // Literal nodes may be converted directly + + if (node instanceof LiteralNode) { + final LiteralNode<?> convertedLiteral = new LiteralNodeConstantEvaluator((LiteralNode<?>)node, type).eval(); + if (convertedLiteral != null) { + resultNode = newLiteral(convertedLiteral); + } + // object literals still need the cast + if (type.isObject()) { + resultNode = new UnaryNode(source, Token.recast(node.getToken(), TokenType.CONVERT), node); + } + } else { + if (resultNode.getSymbol().isParam()) { + resultNode.getSymbol().setType(type); + } + resultNode = new UnaryNode(source, Token.recast(node.getToken(), TokenType.CONVERT), resultNode); + } + + functionNode.newTemporary(type, resultNode); + resultNode.copyTerminalFlags(node); + + return resultNode; + } + + /** + * Accept and convert all arguments to type Object. If we have a + * specialization profile for this function, we instead try to specialize + * the arguments before the casts based on their current types and values. + * + * @param callNode function call + * @return return type for call + */ + private Type acceptArgs(final CallNode callNode) { + final List<Node> oldArgs = callNode.getArgs(); + final List<Node> acceptedArgs = new ArrayList<>(oldArgs.size()); + + for (final Node arg : oldArgs) { + //acceptedArgs.add(convert(arg.accept(this), OBJECT)); + acceptedArgs.add(arg.accept(this)); + } + callNode.setArgs(acceptedArgs); + + return Type.OBJECT; + } + + private static String evalLocation(final IdentNode node) { + final StringBuilder sb = new StringBuilder(node.getSource().getName()); + + sb.append('#'); + sb.append(node.getSource().getLine(node.position())); + sb.append("<eval>"); + + return sb.toString(); + } + + private void checkEval(final CallNode callNode) { + if (callNode.getFunction() instanceof IdentNode) { + + final List<Node> args = callNode.getArgs(); + final IdentNode callee = (IdentNode)callNode.getFunction(); + + // 'eval' call with atleast one argument + if (args.size() >= 1 && EVAL.tag().equals(callee.getName())) { + final CallNode.EvalArgs evalArgs = new CallNode.EvalArgs(); + // code that is evaluated + evalArgs.code = args.get(0).clone(); + evalArgs.code.accept(this); + // 'this' to be passed to evaluated code + evalArgs.evalThis = new IdentNode(getCurrentFunctionNode().getThisNode()); + // location string of the eval call + evalArgs.location = evalLocation(callee); + // strict mode context or not? + evalArgs.strictMode = getCurrentFunctionNode().isStrictMode(); + callNode.setEvalArgs(evalArgs); + } + } + } + + private static Node markerFunction(final Node function) { + if (function instanceof IdentNode) { + return new IdentNode((IdentNode)function) { + @Override + public boolean isFunction() { + return true; + } + }; + } else if (function instanceof AccessNode) { + return new AccessNode((AccessNode)function) { + @Override + public boolean isFunction() { + return true; + } + }; + } else if (function instanceof IndexNode) { + return new IndexNode((IndexNode)function) { + @Override + public boolean isFunction() { + return true; + } + }; + } + + return function; + } + + @Override + public Node enter(final CallNode callNode) { + final Node function = callNode.getFunction(); + final Node markedFunction = markerFunction(function); + + callNode.setFunction(markedFunction.accept(this)); + + checkEval(callNode); + + final Type returnType = acceptArgs(callNode); + getCurrentFunctionNode().newTemporary(returnType, callNode); + callNode.getFunction().getSymbol().setType(returnType); + + return null; + } + + @Override + public Node leave(final CaseNode caseNode) { + caseNode.copyTerminalFlags(caseNode.getBody()); + + return caseNode; + } + + @Override + public Node enter(final CatchNode catchNode) { + final IdentNode ident = catchNode.getException(); + final Block block = getCurrentBlock(); + + // define block-local exception variable + block.defineSymbol(ident.getName(), IS_VAR | IS_LET, ident).setType(Type.OBJECT); + localDefs.add(ident.getName()); + + return catchNode; + } + + @Override + public Node leave(final CatchNode catchNode) { + final Node exceptionCondition = catchNode.getExceptionCondition(); + if (exceptionCondition != null) { + catchNode.setExceptionCondition(convert(exceptionCondition, Type.BOOLEAN)); + } + + catchNode.copyTerminalFlags(catchNode.getBody()); + + statements.add(catchNode); + + return catchNode; + } + + @Override + public Node enter(final ContinueNode continueNode) { + final TryNode tryNode = continueNode.getTryChain(); + final Node target = continueNode.getTargetNode(); + + if (tryNode != null && copyFinally(tryNode, target)) { + return null; + } + + statements.add(continueNode); + + return null; + } + + @Override + public Node enter(final DoWhileNode whileNode) { + return enter((WhileNode)whileNode); + } + + @Override + public Node leave(final DoWhileNode whileNode) { + return leave((WhileNode)whileNode); + } + + @Override + public Node enter(final EmptyNode emptyNode) { + return null; + } + + /** + * Is this an assignment to the special variable that hosts scripting eval + * results? + * + * @param expression expression to check whether it is $evalresult = X + * + * @return true if an assignment to eval result, false otherwise + */ + private boolean isEvalResultAssignment(final Node expression) { + Node e = expression; + if (e.tokenType() == TokenType.DISCARD) { + e = ((UnaryNode)expression).rhs(); + } + final Node resultNode = getCurrentFunctionNode().getResultNode(); + return e instanceof BinaryNode && ((BinaryNode)e).lhs().equals(resultNode); + } + + /** + * An internal expression has a symbol that is tagged internal. Check if + * this is such a node + * + * @param expression expression to check for internal symbol + * @return true if internal, false otherwise + */ + private static boolean isInternalExpression(final Node expression) { + final Symbol symbol = expression.getSymbol(); + return symbol != null && symbol.isInternal(); + } + + /** + * Discard the result of the expression. + * + * @param expression Expression to discard. + * + * @return Discard node. + */ + private Node discard(final Node expression) { + expression.setDiscard(true); + + if (expression.getSymbol() != null) { + final Node discard = new UnaryNode(source, Token.recast(expression.getToken(), TokenType.DISCARD), expression); + discard.copyTerminalFlags(expression); + + return discard; + } + + return expression; + } + + /** + * ExecuteNodes are needed to actually generate code, with a few exceptions + * such as ReturnNodes and ThrowNodes and various control flow that can + * standalone. Every other kind of statement needs to be wrapped in an + * ExecuteNode in order to become code + * + * @param executeNode the execute node to visit + */ + @Override + public Node leave(final ExecuteNode executeNode) { + + Node expression = executeNode.getExpression(); + + /* + * Handle the eval result for scripts. Every statement has to write its + * return value to a special variable that is the result of the script. + */ + if (getCurrentFunctionNode().isScript() && !(expression instanceof Block) && !isEvalResultAssignment(expression) && !isInternalExpression(expression)) { + final Node resultNode = getCurrentFunctionNode().getResultNode(); + expression = new BinaryNode(source, Token.recast(executeNode.getToken(), TokenType.ASSIGN), resultNode, convert(expression, resultNode.getType())); + + getCurrentFunctionNode().newTemporary(Type.OBJECT, expression); + } + + expression = discard(expression); + executeNode.setExpression(expression); + executeNode.copyTerminalFlags(expression); + + statements.add(executeNode); + + return executeNode; + } + + /** + * Helper that given a loop body makes sure that it is not terminal if it + * has a continue that leads to the loop header or to outer loops' loop + * headers. This means that, even if the body ends with a terminal + * statement, we cannot tag it as terminal + * + * @param loopBody the loop body to check + */ + private boolean controlFlowEscapes(final Node loopBody) { + final List<Node> escapes = new ArrayList<>(); + + loopBody.accept(new NodeVisitor() { + @Override + public Node leave(final BreakNode node) { + escapes.add(node); + return node; + } + + @Override + public Node leave(final ContinueNode node) { + // all inner loops have been popped. + if (nesting.contains(node.getTargetNode())) { + escapes.add(node); + } + return node; + } + }); + + return !escapes.isEmpty(); + } + + @Override + public Node enter(final ForNode forNode) { + // push the loop to the loop context + nest(forNode); + return forNode; + } + + private static boolean conservativeAlwaysTrue(final Node node) { + return node == null || ((node instanceof LiteralNode) && Boolean.TRUE.equals(((LiteralNode<?>)node).getValue())); + } + + @Override + public Node leave(final ForNode forNode) { + final Node init = forNode.getInit(); + final Node test = forNode.getTest(); + final Node modify = forNode.getModify(); + + if (forNode.isForIn()) { + final String name = compiler.uniqueName(ITERATOR_PREFIX.tag()); + // DEFINE SYMBOL: may be any type, not local var + final Symbol iter = getCurrentFunctionNode().defineSymbol(name, IS_VAR | IS_INTERNAL, null); + iter.setType(Type.OBJECT); // NASHORN-73 + forNode.setIterator(iter); + forNode.setModify(convert(forNode.getModify(), Type.OBJECT)); // NASHORN-400 + + /* + * Iterators return objects, so we need to widen the scope of the + * init variable if it, for example, has been assigned double type + * see NASHORN-50 + */ + forNode.getInit().getSymbol().setType(Type.OBJECT); + } else { + /* Normal for node, not for in */ + + if (init != null) { + forNode.setInit(discard(init)); + } + + if (test != null) { + forNode.setTest(convert(test, Type.BOOLEAN)); + } else { + forNode.setHasGoto(); + } + + if (modify != null) { + forNode.setModify(discard(modify)); + } + } + + final Block body = forNode.getBody(); + + final boolean escapes = controlFlowEscapes(body); + if (escapes) { + body.setIsTerminal(false); + } + + // pop the loop from the loop context + unnest(); + + if (!forNode.isForIn() && conservativeAlwaysTrue(test)) { + forNode.setTest(null); + forNode.setIsTerminal(!escapes); + } + + statements.add(forNode); + + return forNode; + } + + /** + * Initialize this symbol and variable for function node + * + * @param functionNode the function node + */ + private void initThis(final FunctionNode functionNode) { + final long token = functionNode.getToken(); + final int finish = functionNode.getFinish(); + + final Symbol thisSymbol = functionNode.defineSymbol(THIS.tag(), IS_PARAM | IS_THIS, null); + final IdentNode thisNode = new IdentNode(source, token, finish, thisSymbol.getName()); + + thisSymbol.setType(Type.OBJECT); + thisSymbol.setNeedsSlot(true); + + thisNode.setSymbol(thisSymbol); + + functionNode.setThisNode(thisNode); + } + + /** + * Initialize scope symbol and variable for function node + * + * @param functionNode the function node + */ + private void initScope(final FunctionNode functionNode) { + final long token = functionNode.getToken(); + final int finish = functionNode.getFinish(); + + final Symbol scopeSymbol = functionNode.defineSymbol(SCOPE.tag(), IS_VAR | IS_INTERNAL, null); + final IdentNode scopeNode = new IdentNode(source, token, finish, scopeSymbol.getName()); + + scopeSymbol.setType(ScriptObject.class); + scopeSymbol.setNeedsSlot(true); + + scopeNode.setSymbol(scopeSymbol); + + functionNode.setScopeNode(scopeNode); + } + + /** + * Initialize return symbol and variable for function node + * + * @param functionNode the function node + */ + private void initReturn(final FunctionNode functionNode) { + final long token = functionNode.getToken(); + final int finish = functionNode.getFinish(); + + final Symbol returnSymbol = functionNode.defineSymbol(SCRIPT_RETURN.tag(), IS_VAR | IS_INTERNAL, null); + final IdentNode returnNode = new IdentNode(source, token, finish, returnSymbol.getName()); + + returnSymbol.setType(Object.class); + returnSymbol.setNeedsSlot(true); + + returnNode.setSymbol(returnSymbol); + + functionNode.setResultNode(returnNode); + } + + /** + * Initialize varargs for function node, if applicable + * + * @param functionNode + */ + private void initVarArg(final FunctionNode functionNode) { + final long token = functionNode.getToken(); + final int finish = functionNode.getFinish(); + + assert functionNode.getCalleeNode() != null; + + final Symbol varArgsSymbol = functionNode.defineSymbol(VARARGS.tag(), IS_PARAM | IS_INTERNAL, null); + final IdentNode varArgsNode = new IdentNode(source, token, finish, varArgsSymbol.getName()); + + varArgsSymbol.setType(Type.OBJECT_ARRAY); + varArgsSymbol.setNeedsSlot(true); + + varArgsNode.setSymbol(varArgsSymbol); + + functionNode.setVarArgsNode(varArgsNode); + + final String argumentsName = ARGUMENTS.tag(); + final String name = functionNode.hideArguments() ? functionNode.uniqueName("$" + argumentsName) : argumentsName; + final Symbol argumentsSymbol = functionNode.defineSymbol(name, IS_PARAM | IS_INTERNAL, null); + final IdentNode argumentsNode = new IdentNode(source, token, finish, argumentsSymbol.getName()); + + argumentsSymbol.setType(Type.OBJECT); + argumentsSymbol.setNeedsSlot(true); + + argumentsNode.setSymbol(argumentsSymbol); + + functionNode.setArgumentsNode(argumentsNode); + } + + private void initCallee(final FunctionNode functionNode) { + if (functionNode.getCalleeNode() != null) { + return; + } + + final long token = functionNode.getToken(); + final int finish = functionNode.getFinish(); + + final Symbol calleeSymbol = functionNode.defineSymbol(CALLEE.tag(), IS_PARAM | IS_INTERNAL, null); + final IdentNode calleeNode = new IdentNode(source, token, finish, calleeSymbol.getName()); + + calleeSymbol.setType(ScriptFunction.class); + calleeSymbol.setNeedsSlot(true); + + calleeNode.setSymbol(calleeSymbol); + + functionNode.setCalleeNode(calleeNode); + } + + /** + * Initialize parameters for function node. This may require specializing + * types if a specialization profile is known + * + * @param functionNode the function node + */ + private void initParameters(final FunctionNode functionNode) { + /* + * If a function is specialized, we don't need to tag either it return + * type or its parameters with the widest (OBJECT) type for safety. + */ + functionNode.setReturnType(Type.UNKNOWN); + + for (final IdentNode ident : functionNode.getParameters()) { + localDefs.add(ident.getName()); + final Symbol paramSymbol = functionNode.defineSymbol(ident.getName(), IS_PARAM, ident); + if (paramSymbol != null) { + paramSymbol.setType(Type.UNKNOWN); + } + } + } + + /** + * This has to run before fix assignment types, store any type specializations for + * paramters, then turn then to objects for the generic version of this method + * + * @param functionNode functionNode + */ + private static void fixParameters(final FunctionNode functionNode) { + boolean nonObjectParams = false; + List<Type> paramSpecializations = new ArrayList<>(); + + for (final IdentNode ident : functionNode.getParameters()) { + final Symbol paramSymbol = ident.getSymbol(); + if (paramSymbol != null) { + Type type = paramSymbol.getSymbolType(); + if (type.isUnknown()) { + type = Type.OBJECT; + } + paramSpecializations.add(type); + if (!type.isObject()) { + nonObjectParams = true; + } + paramSymbol.setType(Type.OBJECT); + } + } + + if (!nonObjectParams) { + paramSpecializations = null; + /* + * Later, when resolving a call to this method, the linker can say "I have a double, an int and an object" as parameters + * here. If the callee has parameter specializations, we can regenerate it with those particular types for speed. + */ + } else { + LOG.info("parameter specialization possible: " + functionNode.getName() + " " + paramSpecializations); + } + } + + private LiteralNode<Undefined> undefined() { + return LiteralNode.newInstance(source, 0, 0, UNDEFINED); + } + + private void guaranteeReturn(final FunctionNode functionNode) { + Node resultNode; + + if (functionNode.isScript()) { + resultNode = functionNode.getResultNode(); + } else { + final Node lastStatement = Node.lastStatement(functionNode.getStatements()); + if (lastStatement != null && (lastStatement.isTerminal() || lastStatement instanceof ReturnNode)) { + return; // return already in place, as it should be for a non-undefined returning function + } + resultNode = undefined().accept(this); + } + + new ReturnNode(source, functionNode.getLastToken(), functionNode.getFinish(), resultNode, null).accept(this); + + functionNode.setReturnType(Type.OBJECT); + } + + /** + * Fix return types for a node. The return type is the widest of all known + * return expressions in the function, and has to be the same for all return + * nodes, thus we need to traverse all of them in case some of them must be + * upcast + * + * @param node function node to scan return types for + */ + private void fixReturnTypes(final FunctionNode node) { + + if (node.getReturnType().isUnknown()) { + node.setReturnType(Type.OBJECT); // for example, infinite loops + } + + node.accept(new NodeVisitor() { + @Override + public Node enter(final FunctionNode subFunction) { + //leave subfunctions alone. their return values are already specialized and should not + //be touched. + return null; + } + @Override + public Node leave(final ReturnNode returnNode) { + if (returnNode.hasExpression()) { + returnNode.setExpression(convert(returnNode.getExpression(), node.getReturnType())); + } + return returnNode; + } + }); + } + + /** + * Augment assignments with casts after traversing a function node. + * Assignment nodes are special, as they require entire scope for type + * inference. + * + * Consider e.g. + * + * var x = 18.5; //double + * for (...) { + * var y = x; //initially y is inferred to be double y = func(x) //y changes to object, but above assignment is unaware that it needs to be var y = (object)x instaed + */ + + private void fixAssignmentTypes(final FunctionNode node, final List<Symbol> declaredSymbols) { + + // Make sure all unknown symbols are OBJECT types now. No UNKNOWN may survive lower. + for (final Symbol symbol : declaredSymbols) { + if (symbol.getSymbolType().isUnknown()) { + LOG.finest("fixAssignmentTypes: widening " + symbol + " to object " + symbol + " in " + getCurrentFunctionNode()); + symbol.setType(Type.OBJECT); + symbol.setCanBeUndefined(); + } + + if (symbol.canBeUndefined()) { + /* + * Ideally we'd like to only widen to the narrowest + * possible type that supports local variables, i.e. doubles for + * numerics, but + * + * var x; + * + * if (x !== undefined) do something + * + * x++; + * + * Screws this up, as x is determined to be a double even though + * the use is undefined This can be fixed with better use + * analysis in the IdentNode visitor. + * + * This actually seems to be superseded with calls to ensureTypeNotUnknown + */ + if (!Type.areEquivalent(symbol.getSymbolType(), Type.OBJECT)) { + debug(symbol + " in " + getCurrentFunctionNode() + " can be undefined. Widening to object"); + symbol.setType(Type.OBJECT); + } + } + } + + node.accept(new NodeVisitor() { + + private void fix(final Assignment<? extends Node> assignment) { + final Node src = assignment.getAssignmentSource(); + final Node dest = assignment.getAssignmentDest(); + + if (src == null) { + return; + } + + //we don't know about scope yet so this is too conservative. AccessSpecialized will remove casts that are not required + + final Type srcType = src.getType(); + Type destType = dest.getType(); + + //we can only narrow an operation type if the variable doesn't have a slot + if (!dest.getSymbol().hasSlot() && !node.hasDeepWithOrEval()) { + destType = Type.narrowest(((Node)assignment).getWidestOperationType(), destType); + } + + if (!Type.areEquivalent(destType, srcType)) { + LOG.finest("fixAssignment " + assignment + " type = " + src.getType() + "=>" + dest.getType()); + assignment.setAssignmentSource(convert(src, destType)); + } + } + + private Node checkAssignment(final Node assignNode) { + if (!assignNode.isAssignment()) { + return assignNode; + } + fix((Assignment<?>)assignNode); + return assignNode; + } + + + @Override + public Node leave(final UnaryNode unaryNode) { + return checkAssignment(unaryNode); + } + + @Override + public Node leave(final BinaryNode binaryNode) { + return checkAssignment(binaryNode); + } + + @Override + public Node leave(final VarNode varNode) { + return checkAssignment(varNode); + } + }); + } + + /* + * For a script, add scope symbols as defined in the property map + */ + private static void initFromPropertyMap(final FunctionNode functionNode) { + assert functionNode.isScript(); + + final PropertyMap map = Context.getGlobal().getMap(); + + for (final Property property : map.getProperties()) { + final String key = property.getKey(); + functionNode.defineSymbol(key, IS_GLOBAL, null).setType(Type.OBJECT); + } + } + + @Override + public Node enter(final FunctionNode functionNode) { + + Node initialEvalResult = undefined(); + + nest(functionNode); + + localDefs = new HashSet<>(); + localUses = new HashSet<>(); + + functionNode.setFrame(functionNode.pushFrame()); + + initThis(functionNode); + initCallee(functionNode); + if (functionNode.isVarArg()) { + initVarArg(functionNode); + } + + initParameters(functionNode); + initScope(functionNode); + initReturn(functionNode); + + /* + * Add all nested functions as symbols in this function + */ + for (final FunctionNode nestedFunction : functionNode.getFunctions()) { + final IdentNode ident = nestedFunction.getIdent(); + + // for initial eval result is the last declared function + if (ident != null && nestedFunction.isStatement()) { + final Symbol functionSymbol = functionNode.defineSymbol(ident.getName(), IS_VAR, nestedFunction); + + functionSymbol.setType(ScriptFunction.class); + initialEvalResult = new IdentNode(ident); + } + } + + if (functionNode.isScript()) { + initFromPropertyMap(functionNode); + } + + // Add function name as local symbol + if (!functionNode.isStatement() && !functionNode.isAnonymous() && !functionNode.isScript()) { + final Symbol selfSymbol = functionNode.defineSymbol(functionNode.getIdent().getName(), IS_VAR, functionNode); + selfSymbol.setType(Type.OBJECT); + selfSymbol.setNode(functionNode); + } + + /* + * As we are evaluating a nested structure, we need to store the + * statement list for the surrounding block and restore it when the + * function is done + */ + final List<Node> savedStatements = statements; + statements = new ArrayList<>(); + + /* + * This pushes all declarations (except for non-statements, i.e. for + * node temporaries) to the top of the function scope. This way we can + * get around problems like + * + * while (true) { + * break; + * if (true) { + * var s; + * } + * } + * + * to an arbitrary nesting depth. + * + * @see NASHORN-73 + */ + + final List<Symbol> declaredSymbols = new ArrayList<>(); + for (final VarNode decl : functionNode.getDeclarations()) { + final IdentNode ident = decl.getName(); + // any declared symbols that aren't visited need to be typed as + // well, hence the list + declaredSymbols.add(functionNode.defineSymbol(ident.getName(), IS_VAR, new IdentNode(ident))); + } + + declaredSymbolsLocal = new ArrayList<>(); + + /* + * Main function code lowering + */ + try { + /* + * Every nested function needs a definition in the outer function + * with its name. Add these. + */ + for (final FunctionNode nestedFunction : functionNode.getFunctions()) { + final VarNode varNode = nestedFunction.getFunctionVarNode(); + if (varNode != null) { + final LineNumberNode lineNumberNode = nestedFunction.getFunctionVarLineNumberNode(); + if (lineNumberNode != null) { + lineNumberNode.accept(this); + } + varNode.accept(this); + varNode.setIsFunctionVarNode(); + } + } + + if (functionNode.isScript()) { + new ExecuteNode(source, functionNode.getFirstToken(), functionNode.getFinish(), initialEvalResult).accept(this); + } + + /* + * Now we do the statements. This fills the block with code + */ + for (final Node statement : functionNode.getStatements()) { + statement.accept(this); + + final Node lastStatement = Node.lastStatement(statements); + if (lastStatement != null && lastStatement.hasTerminalFlags()) { + functionNode.copyTerminalFlags(lastStatement); + break; + } + } + + functionNode.setStatements(statements); + + /* + * If there are unusedterminated enpoints in the function, we need + * to add a "return undefined" in those places for correct semantics + */ + if (!functionNode.isTerminal()) { + guaranteeReturn(functionNode); + } + + /* + * Emit all nested functions + */ + for (final FunctionNode nestedFunction : functionNode.getFunctions()) { + nestedFunction.accept(this); + } + + fixReturnTypes(functionNode); + + if (functionNode.needsSelfSymbol()) { + final IdentNode selfSlotNode = new IdentNode(functionNode.getIdent()); + selfSlotNode.setSymbol(functionNode.findSymbol(functionNode.getIdent().getName())); + final Node functionRef = new IdentNode(functionNode.getCalleeNode()); + statements.add(0, new VarNode(source, functionNode.getToken(), functionNode.getFinish(), selfSlotNode, functionRef, false).accept(this)); + } + } finally { + /* + * Restore statement for outer block that we were working on + */ + statements = savedStatements; + } + + unnest(); + + fixParameters(functionNode); + + /* + * Fix assignment issues. assignments are troublesome as they can be on + * the form "dest = dest op source" where the type is different for the + * two dest:s: right now this is a post pass that handles putting casts + * in the correct places, but it can be implemented as a bottom up AST + * traversal on the fly. TODO. + */ + fixAssignmentTypes(functionNode, declaredSymbols); + + /* + * Set state of function to lowered and pop the frame + */ + functionNode.setIsLowered(true); + functionNode.popFrame(); + + return null; + } + + @Override + public Node enter(final IdentNode identNode) { + final String name = identNode.getName(); + + if (identNode.isPropertyName()) { + // assign a pseudo symbol to property name + identNode.setSymbol(new Symbol(name, 0, Type.OBJECT)); + return null; + } + + final Block block = getCurrentBlock(); + final Symbol oldSymbol = identNode.getSymbol(); + Symbol symbol = block.findSymbol(name); + + /* + * If an existing symbol with the name is found, use that otherwise, + * declare a new one + */ + + if (symbol != null) { + if (isFunctionExpressionSelfReference(symbol)) { + ((FunctionNode) symbol.getNode()).setNeedsSelfSymbol(); + } + + if (!identNode.isInitializedHere()) { // NASHORN-448 + // here is a use outside the local def scope + if (!localDefs.contains(name)) { + symbol.setType(Type.OBJECT); + symbol.setCanBeUndefined(); + } + } + + identNode.setSymbol(symbol); + if (!getCurrentFunctionNode().isLocal(symbol)) { + // non-local: we need to put symbol in scope (if it isn't already) + if (!symbol.isScope()) { + final List<Block> lookupBlocks = findLookupBlocksHelper(getCurrentFunctionNode(), symbol.findFunction()); + for (final Block lookupBlock : lookupBlocks) { + final Symbol refSymbol = lookupBlock.findSymbol(name); + refSymbol.setIsScope(); + } + } + } + } else { + symbol = block.useSymbol(name, identNode); + // we have never seen this before, it can be undefined + symbol.setType(Type.OBJECT); // TODO unknown -we have explicit casts anyway? + symbol.setCanBeUndefined(); + } + + if (symbol != oldSymbol && !identNode.isInitializedHere()) { + symbol.increaseUseCount(); + } + localUses.add(identNode.getName()); + + return null; + } + + private static List<Block> findLookupBlocksHelper(final FunctionNode currentFunction, final FunctionNode topFunction) { + if (currentFunction.findParentFunction() == topFunction) { + final List<Block> blocks = new LinkedList<>(); + + blocks.add(currentFunction.getParent()); + blocks.addAll(currentFunction.getReferencingParentBlocks()); + return blocks; + } + /** + * assumption: all parent blocks of an inner function will always be in the same outer function; + * therefore we can simply skip through intermediate functions. + * @see FunctionNode#addReferencingParentBlock(Block) + */ + return findLookupBlocksHelper(currentFunction.findParentFunction(), topFunction); + } + + private static boolean isFunctionExpressionSelfReference(final Symbol symbol) { + if (symbol.isVar() && symbol.getNode() == symbol.getBlock() && symbol.getNode() instanceof FunctionNode) { + return ((FunctionNode) symbol.getNode()).getIdent().getName().equals(symbol.getName()); + } + return false; + } + + @Override + public Node enter(final IfNode ifNode) { + nest(ifNode); + return ifNode; + } + + @Override + public Node leave(final IfNode ifNode) { + + final Node test = convert(ifNode.getTest(), Type.BOOLEAN); + + /* + * constant if checks should short circuit into just one of the + * pass/fail blocks + */ + if (test.getSymbol().isConstant()) { + assert test instanceof LiteralNode<?>; + final Block shortCut = ((LiteralNode<?>)test).isTrue() ? ifNode.getPass() : ifNode.getFail(); + if (shortCut != null) { + for (final Node statement : shortCut.getStatements()) { + statements.add(statement); + } + } + return null; + } + + final Node pass = ifNode.getPass(); + final Node fail = ifNode.getFail(); + + if (pass.isTerminal() && fail != null && fail.isTerminal()) { + ifNode.setIsTerminal(true); + } + + ifNode.setTest(test); + statements.add(ifNode); + + unnest(); + + return ifNode; + } + + @Override + public Node leave(final IndexNode indexNode) { + getCurrentFunctionNode().newTemporary(Type.OBJECT, indexNode); + + return indexNode; + } + + @Override + public Node enter(final LabelNode labeledNode) { + // Don't want a symbol for label. + final Block body = labeledNode.getBody(); + body.accept(this); + labeledNode.copyTerminalFlags(body); + statements.add(labeledNode); + + return null; + } + + @Override + public Node enter(final LineNumberNode lineNumberNode) { + statements.add(lineNumberNode); + return null; + } + + /** + * Generate a new literal symbol. + * + * @param literalNode LiteralNode needing symbol. + * @return The literal node augmented with the symbol + */ + private LiteralNode<?> newLiteral(final LiteralNode<?> literalNode) { + return newLiteral(getCurrentFunctionNode(), literalNode); + } + + private static LiteralNode<?> newLiteral(final FunctionNode functionNode, final LiteralNode<?> literalNode) { + functionNode.newLiteral(literalNode); + return literalNode; + } + + @SuppressWarnings("rawtypes") + @Override + public Node enter(final LiteralNode literalNode) { + if (literalNode.isTokenType(TokenType.THIS)) { + literalNode.setSymbol(getCurrentFunctionNode().getThisNode().getSymbol()); + return null; + } + + if (literalNode instanceof ArrayLiteralNode) { + final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode)literalNode; + final Node[] array = arrayLiteralNode.getValue(); + + for (int i = 0; i < array.length; i++) { + final Node element = array[i]; + if (element != null) { + array[i] = array[i].accept(this); + } + } + + arrayLiteralNode.analyze(); + final Type elementType = arrayLiteralNode.getElementType(); + + // we only have types after all elements are accepted + for (int i = 0; i < array.length; i++) { + final Node element = array[i]; + if (element != null) { + array[i] = convert(element, elementType); + } + } + } else { + final Object value = literalNode.getValue(); + + if (value instanceof Node) { + final Node node = ((Node)value).accept(this); + if (node != null) { + return newLiteral(LiteralNode.newInstance(source, node.getToken(), node.getFinish(), node)); + } + } + } + + newLiteral(literalNode); + + return null; + } + + @Override + public Node leave(final ObjectNode objectNode) { + getCurrentFunctionNode().newTemporary(Type.OBJECT, objectNode); + return objectNode; + } + + @Override + public Node enter(final PropertyNode propertyNode) { + // assign a pseudo symbol to property name, see NASHORN-710 + propertyNode.setSymbol(new Symbol(propertyNode.getKeyName(), 0, Type.OBJECT)); + return propertyNode; + } + + @Override + public Node enter(final ReferenceNode referenceNode) { + final FunctionNode fn = referenceNode.getReference(); + if (fn != null) { + fn.addReferencingParentBlock(getCurrentBlock()); + } + return referenceNode; + } + + @Override + public Node leave(final ReferenceNode referenceNode) { + getCurrentFunctionNode().newTemporary(Type.OBJECT, referenceNode); + return referenceNode; + } + + /** + * For each return node we need to set a return expression of the correct + * type. In the non-specializing world, we can always upcast to object and + * ignore the rest, but in the specializing world we have to keep track of + * the widest return type so far, which is the common one for this method. + * + * @param returnNode the return node we are generating + * @param expression the expression to return from this code + */ + private void setReturnExpression(final ReturnNode returnNode, final Node expression) { + final FunctionNode functionNode = getCurrentFunctionNode(); + + returnNode.setExpression(expression); + + final Symbol symbol = expression.getSymbol(); + if (expression.getType().isUnknown() && symbol.isParam()) { + symbol.setType(Type.OBJECT); + } + functionNode.setReturnType(Type.widest(expression.getType(), functionNode.getReturnType())); + } + + @Override + public Node enter(final ReturnNode returnNode) { + final TryNode tryNode = returnNode.getTryChain(); + final Node expression = returnNode.getExpression(); + + if (tryNode != null) { + if (expression != null) { + final long token = returnNode.getToken(); + final Node resultNode = getCurrentFunctionNode().getResultNode(); + new ExecuteNode(source, token, Token.descPosition(token), new BinaryNode(source, Token.recast(token, TokenType.ASSIGN), resultNode, expression)).accept(this); + + if (copyFinally(tryNode, null)) { + return null; + } + + setReturnExpression(returnNode, resultNode); + } else if (copyFinally(tryNode, null)) { + return null; + } + } else if (expression != null) { + setReturnExpression(returnNode, expression.accept(this)); + } + + statements.add(returnNode); + + return null; + } + + @Override + public Node leave(final RuntimeNode runtimeNode) { + for (final Node arg : runtimeNode.getArgs()) { + ensureTypeNotUnknown(arg); + } + + getCurrentFunctionNode().newTemporary(runtimeNode.getRequest().getReturnType(), runtimeNode); + + return runtimeNode; + } + + @Override + public Node enter(final SwitchNode switchNode) { + nest(switchNode); + return switchNode; + } + + @Override + public Node leave(final SwitchNode switchNode) { + unnest(); + + final Node expression = switchNode.getExpression(); + final List<CaseNode> cases = switchNode.getCases(); + final CaseNode defaultCase = switchNode.getDefaultCase(); + final boolean hasDefault = defaultCase != null; + final int n = cases.size() + (hasDefault ? -1 : 0); + + boolean allTerminal = !cases.isEmpty(); + boolean allInteger = n > 1; + + for (final CaseNode caseNode : cases) { + allTerminal &= caseNode.isTerminal(); + final Node test = caseNode.getTest(); + + // Skip default. + if (test == null) { + continue; + } + + // If not a number. + if (!(test instanceof LiteralNode) || !test.getType().isNumeric()) { + allInteger = false; + continue; + } + + final LiteralNode<?> testLiteral = (LiteralNode<?>)test; + + // If a potential integer. + if (!(testLiteral.getValue() instanceof Integer)) { + // If not an integer value. + if (!JSType.isRepresentableAsInt(testLiteral.getNumber())) { + allInteger = false; + continue; + } + + // Guarantee all case literals are Integers (simplifies sorting in code gen.) + caseNode.setTest(newLiteral(LiteralNode.newInstance(source, testLiteral.getToken(), testLiteral.getFinish(), testLiteral.getInt32()))); + } + } + + if (allTerminal && defaultCase != null && defaultCase.isTerminal()) { + switchNode.setIsTerminal(true); + } + + if (!allInteger) { + switchNode.setExpression(convert(expression, Type.OBJECT)); + + for (final CaseNode caseNode : cases) { + final Node test = caseNode.getTest(); + + if (test != null) { + caseNode.setTest(convert(test, Type.OBJECT)); + } + } + } + + final String name = compiler.uniqueName(SWITCH_TAG_PREFIX.tag()); + final Symbol tag = getCurrentFunctionNode().defineSymbol(name, IS_VAR | IS_INTERNAL, null); + + tag.setType(allInteger ? Type.INT : Type.OBJECT); + switchNode.setTag(tag); + + statements.add(switchNode); + + return switchNode; + } + + @Override + public Node leave(final ThrowNode throwNode) { + throwNode.setExpression(convert(throwNode.getExpression(), Type.OBJECT)); + statements.add(throwNode); + + return throwNode; + } + + @Override + public Node enter(final TryNode tryNode) { + final Block finallyBody = tryNode.getFinallyBody(); + final Source tryNodeSource = tryNode.getSource(); + final long token = tryNode.getToken(); + final int finish = tryNode.getFinish(); + + nest(tryNode); + + if (finallyBody == null) { + return tryNode; + } + + /** + * We have a finally clause. + * + * Transform to do finally tail duplication as follows: + * + * <pre> + * try { + * try_body + * } catch e1 { + * catchbody_1 + * } + * ... + * } catch en { + * catchbody_n + * } finally { + * finally_body + * } + * + * (where e1 ... en are optional) + * + * turns into + * + * try { + * try { + * try_body + * } catch e1 { + * catchbody1 + * //nothing inlined explicitly here, return, break other + * //terminals may inline the finally body + * ... + * } catch en { + * catchbody2 + * //nothing inlined explicitly here, return, break other + * //terminals may inline the finally body + * } + * } catch all ex { + * finally_body_inlined + * rethrow ex + * } + * finally_body_inlined + * </pre> + * + * If tries are catches are terminal, visitors for return, break & + * continue will handle the tail duplications. Throw needs to be + * treated specially with the catchall as described in the above + * ASCII art. + * + * If the try isn't terminal we do the finally_body_inlined at the + * end. If the try is terminated with continue/break/return the + * existing visitor logic will inline the finally before that + * operation. if the try is terminated with a throw, the catches e1 + * ... en will have a chance to process the exception. If the + * appropriate catch e1..en is non terminal we fall through to the + * last finally_body_inlined. if the catch e1...en IS terminal with + * continue/break/return existing visitor logic will fix it. If they + * are terminal with another throw it goes to the catchall and the + * finally_body_inlined marked (*) will fix it before rethrowing + * whatever problem there was for identical semantic. + */ + + // if try node does not contain a catch we can skip creation of a new + // try node and just append our synthetic catch to the existing try node. + if (!tryNode.getCatchBlocks().isEmpty()) { + // insert an intermediate try-catch* node, where we move the body and all catch blocks. + // the original try node become a try-finally container for the new try-catch* node. + // because we don't clone (to avoid deep copy), we have to fix the block chain in the end. + final TryNode innerTryNode = new TryNode(tryNodeSource, token, finish, tryNode.getNext()); + innerTryNode.setBody(tryNode.getBody()); + innerTryNode.setCatchBlocks(tryNode.getCatchBlocks()); + + // set outer tryNode's body to innerTryNode + final Block outerBody = new Block(tryNodeSource, token, finish, tryNode.getBody().getParent(), getCurrentFunctionNode()); + outerBody.setStatements(new ArrayList<Node>(Arrays.asList(innerTryNode))); + tryNode.setBody(outerBody); + tryNode.setCatchBlocks(null); + + // now before we go on, we have to fix the block parents + // (we repair the block tree after the insertion so that all references are intact) + innerTryNode.getBody().setParent(tryNode.getBody()); + for (final Block block : innerTryNode.getCatchBlocks()) { + block.setParent(tryNode.getBody()); + } + } + + // create a catch-all that inlines finally and rethrows + final String name = compiler.uniqueName(EXCEPTION_PREFIX.tag()); + + final IdentNode exception = new IdentNode(tryNodeSource, token, finish, name); + exception.accept(this); + + final Block catchBlock = new Block(tryNodeSource, token, finish, getCurrentBlock(), getCurrentFunctionNode()); + final Block catchBody = new Block(tryNodeSource, token, finish, catchBlock, getCurrentFunctionNode()); + final Node catchAllFinally = finallyBody.clone(); + + catchBody.addStatement(new ExecuteNode(tryNodeSource, finallyBody.getToken(), finallyBody.getFinish(), catchAllFinally)); + catchBody.setIsTerminal(true); + + final CatchNode catchNode = new CatchNode(tryNodeSource, token, finish, new IdentNode(exception), null, catchBody); + catchNode.setIsSyntheticRethrow(); + getCurrentFunctionNode().setHasThrows(true); + catchBlock.addStatement(catchNode); + + // replace all catches of outer tryNode with the catch-all + tryNode.setCatchBlocks(new ArrayList<>(Arrays.asList(catchBlock))); + + /* + * We leave the finally block for the original try in place for now + * so that children visitations will work. It is removed and placed + * afterwards in the else case below, after all children are visited + */ + + return tryNode; + } + + @Override + public Node leave(final TryNode tryNode) { + final Block finallyBody = tryNode.getFinallyBody(); + + boolean allTerminal = tryNode.getBody().isTerminal() && (finallyBody == null || finallyBody.isTerminal()); + + for (final Block catchBlock : tryNode.getCatchBlocks()) { + allTerminal &= catchBlock.isTerminal(); + } + + tryNode.setIsTerminal(allTerminal); + + final String name = compiler.uniqueName(EXCEPTION_PREFIX.tag()); + final Symbol exception = getCurrentFunctionNode().defineSymbol(name, IS_VAR | IS_INTERNAL, null); + + exception.setType(ECMAException.class); + tryNode.setException(exception); + + statements.add(tryNode); + + unnest(); + + // if finally body is present, place it after the tryNode + if (finallyBody != null) { + tryNode.setFinallyBody(null); + statements.add(finallyBody); + } + + return tryNode; + } + + @Override + public Node enter(final VarNode varNode) { + final IdentNode ident = varNode.getName(); + final String name = ident.getName(); + + final Symbol symbol = getCurrentBlock().defineSymbol(name, IS_VAR, ident); + assert symbol != null; + + varNode.setSymbol(symbol); + declaredSymbolsLocal.add(symbol); + + // NASHORN-467 - use before definition of vars - conservative + if (localUses.contains(ident.getName())) { + symbol.setType(Type.OBJECT); + symbol.setCanBeUndefined(); + } + + return varNode; + } + + private static boolean isScopeOrGlobal(final Symbol symbol) { + return symbol.getBlock().getFunction().isScript(); //we can't do explicit isScope checks as early as lower, which is why AccessSpecializer is afterwards as well. + } + + @Override + public Node leave(final VarNode varNode) { + final Node init = varNode.getInit(); + final IdentNode ident = varNode.getName(); + final String name = ident.getName(); + + if (init != null) { + localDefs.add(name); + } + + if (varNode.shouldAppend()) { + statements.add(varNode); + } + + if (init == null) { + // var x; with no init will be treated like a use of x by + // visit(IdentNode) unless we remove the name + // from the localdef list. + localDefs.remove(name); + return varNode; + } + + final Symbol symbol = varNode.getSymbol(); + if ((init.getType().isNumeric() || init.getType().isBoolean()) && !isScopeOrGlobal(symbol)) { //see NASHORN-56 + // Forbid integers as local vars for now as we have no way to treat them as undefined + final Type type = Compiler.shouldUseIntegers() ? init.getType() : Type.NUMBER; + varNode.setInit(convert(init, type)); + symbol.setType(type); + } else { + symbol.setType(Type.OBJECT); // var x = obj;, x is an "object" //TODO too conservative now? + } + + return varNode; + } + + @Override + public Node enter(final WhileNode whileNode) { + // push the loop to the loop context + nest(whileNode); + + return whileNode; + } + + @Override + public Node leave(final WhileNode whileNode) { + final Node test = whileNode.getTest(); + + if (test != null) { + whileNode.setTest(convert(test, Type.BOOLEAN)); + } else { + whileNode.setHasGoto(); + } + + Node returnNode = whileNode; + + final Block body = whileNode.getBody(); + final boolean escapes = controlFlowEscapes(body); + if (escapes) { + body.setIsTerminal(false); + } + + if (body.isTerminal()) { + if (whileNode instanceof DoWhileNode) { + whileNode.setIsTerminal(true); + } else if (conservativeAlwaysTrue(test)) { + returnNode = new ForNode(source, whileNode.getToken(), whileNode.getFinish()); + ((ForNode)returnNode).setBody(body); + returnNode.setIsTerminal(!escapes); + } + } + + // pop the loop from the loop context + unnest(); + statements.add(returnNode); + + return returnNode; + } + + @Override + public Node leave(final WithNode withNode) { + withNode.setExpression(convert(withNode.getExpression(), Type.OBJECT)); + + if (withNode.getBody().isTerminal()) { + withNode.setIsTerminal(true); + } + + statements.add(withNode); + + return withNode; + } + + /* + * Unary visits. + */ + + /** + * Visit unary node and set symbols and inferred type + * + * @param unaryNode unary node to visit + * @param type The narrowest allowed type for this node + * + * @return the final node, or replacement thereof + */ + public Node leaveUnary(final UnaryNode unaryNode, final Type type) { + /* Try to eliminate the unary node if the expression is constant */ + final LiteralNode<?> literalNode = new UnaryNodeConstantEvaluator(unaryNode).eval(); + if (literalNode != null) { + return newLiteral(literalNode); + } + + /* Add explicit conversion */ + unaryNode.setRHS(convert(unaryNode.rhs(), type)); + getCurrentFunctionNode().newTemporary(type, unaryNode); + + return unaryNode; + } + + @Override + public Node leaveADD(final UnaryNode unaryNode) { + return leaveUnary(unaryNode, Type.NUMBER); + } + + @Override + public Node leaveBIT_NOT(final UnaryNode unaryNode) { + return leaveUnary(unaryNode, Type.INT); + } + + @Override + public Node leaveCONVERT(final UnaryNode unaryNode) { + final Node rhs = unaryNode.rhs(); + + if (rhs.isTokenType(TokenType.CONVERT)) { + rhs.setSymbol(unaryNode.getSymbol()); + return rhs; + } + + return unaryNode; + } + + /** + * In an assignment, recursively make sure that there are slots for + * everything that has to be laid out as temporary storage, which is the + * case if we are assign-op:ing a BaseNode subclass. This has to be + * recursive to handle things like multi dimensional arrays as lhs + * + * see NASHORN-258 + * + * @param functionNode the current function node (has to be passed as it changes in the visitor below) + * @param assignmentDest the destination node of the assignment, e.g. lhs for binary nodes + */ + private static void ensureAssignmentSlots(final FunctionNode functionNode, final Node assignmentDest) { + + assignmentDest.accept(new NodeVisitor() { + + @Override + public Node leave(final AccessNode accessNode) { + final Node base = accessNode.getBase(); + if (base.getSymbol().isThis()) { + functionNode.addThisProperty(accessNode.getProperty().getName(), accessNode); + } + + return accessNode; + } + + @Override + public Node leave(final IndexNode indexNode) { + final Node index = indexNode.getIndex(); + index.getSymbol().setNeedsSlot(!index.getSymbol().isConstant()); + + return indexNode; + } + + }); + } + + @Override + public Node leaveDECINC(final UnaryNode unaryNode) { + // @see assignOffset + ensureAssignmentSlots(getCurrentFunctionNode(), unaryNode.rhs()); + final Type type = Compiler.shouldUseIntegerArithmetic() ? Type.INT : Type.NUMBER; + + unaryNode.rhs().getSymbol().setType(type); + getCurrentFunctionNode().newTemporary(type, unaryNode); + + return unaryNode; + } + + /** + * Helper class for wrapping a result + */ + private abstract static class ResultNodeVisitor extends NodeVisitor { + + private Node resultNode; + + ResultNodeVisitor(final Node resultNode) { + this.resultNode = resultNode; + } + + Node getResultNode() { + return resultNode; + } + + void setResultNode(final Node resultNode) { + this.resultNode = resultNode; + } + } + + @Override + public Node leaveDELETE(final UnaryNode unaryNode) { + final boolean strictMode = getCurrentFunctionNode().isStrictMode(); + final Node rhs = unaryNode.rhs(); + final long token = unaryNode.getToken(); + final int finish = unaryNode.getFinish(); + final LiteralNode<Boolean> trueNode = LiteralNode.newInstance(source, token, finish, true); + final LiteralNode<Boolean> strictFlagNode = LiteralNode.newInstance(source, token, finish, strictMode); + + newLiteral(trueNode); + newLiteral(strictFlagNode); + + final NodeVisitor lower = this; + final FunctionNode currentFunctionNode = getCurrentFunctionNode(); + + final ResultNodeVisitor visitor = new ResultNodeVisitor(trueNode) { + + private void initRuntimeNode(final RuntimeNode node) { + node.accept(lower); + currentFunctionNode.newTemporary(Type.BOOLEAN, unaryNode); + node.setSymbol(unaryNode.getSymbol()); + + setResultNode(node); + } + + @Override + public Node enter(final IdentNode node) { + // If this is a declared variable or a function parameter, delete always fails (except for globals). + final boolean failDelete = + strictMode || + node.getSymbol().isParam() || + (node.getSymbol().isVar() && !node.getSymbol().isTopLevel()); + + final Node literalNode = + newLiteral( + currentFunctionNode, + LiteralNode.newInstance( + source, + node.getToken(), + node.getFinish(), + node.getName())); + + if (failDelete) { + if (THIS.tag().equals(node.getName())) { + statements.add(new ExecuteNode(source, token, finish, discard(node))); + currentFunctionNode.newTemporary(Type.BOOLEAN, trueNode); + return null; //trueNode it is + } + } + + final List<Node> args = new ArrayList<>(); + args.add(convert(currentFunctionNode.getScopeNode(), Type.OBJECT)); + args.add(convert(literalNode, Type.OBJECT)); + args.add(strictFlagNode); + + initRuntimeNode( + new RuntimeNode( + source, + token, + finish, + failDelete ? FAIL_DELETE : DELETE, + args)); + + return null; + } + + @Override + public Node enter(final AccessNode node) { + final IdentNode property = node.getProperty(); + + initRuntimeNode( + new RuntimeNode( + source, + token, + finish, + DELETE, + convert(node.getBase(), Type.OBJECT), + convert( + newLiteral( + currentFunctionNode, + LiteralNode.newInstance( + source, + property.getToken(), + property.getFinish(), + property.getName())), + Type.OBJECT), + strictFlagNode)); + + return null; + } + + @Override + public Node enter(final IndexNode node) { + initRuntimeNode( + new RuntimeNode( + source, + token, + finish, + DELETE, + convert(node.getBase(), Type.OBJECT), + convert(node.getIndex(), Type.OBJECT), + strictFlagNode)); + + return null; + } + + @Override + public Node enterDefault(final Node node) { + statements.add(new ExecuteNode(source, token, finish, discard(node))); + currentFunctionNode.newTemporary(Type.BOOLEAN, trueNode); + //just return trueNode which is the default + return null; + } + }; + + rhs.accept(visitor); + + return visitor.getResultNode(); + } + + @Override + public Node enterNEW(final UnaryNode unaryNode) { + final CallNode callNode = (CallNode)unaryNode.rhs(); + + callNode.setIsNew(); + + final Node function = callNode.getFunction(); + final Node markedFunction = markerFunction(function); + + callNode.setFunction(markedFunction.accept(this)); + + acceptArgs(callNode); + + getCurrentFunctionNode().newTemporary(Type.OBJECT, unaryNode); + + return null; + } + + @Override + public Node leaveNOT(final UnaryNode unaryNode) { + return leaveUnary(unaryNode, Type.BOOLEAN); + } + + /** + * Create a null literal + * + * @param parent node to inherit token from. + * @return the new null literal + */ + private LiteralNode<?> newNullLiteral(final Node parent) { + return newLiteral(LiteralNode.newInstance(parent.getSource(), parent.getToken(), parent.getFinish())); + } + + @Override + public Node leaveTYPEOF(final UnaryNode unaryNode) { + final Node rhs = unaryNode.rhs(); + final long token = unaryNode.getToken(); + final int finish = unaryNode.getFinish(); + + RuntimeNode runtimeNode; + + if (rhs instanceof IdentNode) { + final IdentNode ident = (IdentNode)rhs; + + if (ident.getSymbol().isParam() || ident.getSymbol().isVar()) { + runtimeNode = new RuntimeNode( + source, + token, + finish, + TYPEOF, + convert( + rhs, + Type.OBJECT), + newNullLiteral(unaryNode)); + } else { + runtimeNode = new RuntimeNode( + source, + token, + finish, + TYPEOF, + convert( + getCurrentFunctionNode().getScopeNode(), + Type.OBJECT), + convert( + newLiteral( + LiteralNode.newInstance( + source, + ident.getToken(), + ident.getFinish(), + ident.getName())), + Type.OBJECT)); + } + } else { + runtimeNode = new RuntimeNode( + source, + token, + finish, + TYPEOF, + convert( + rhs, + Type.OBJECT), + newNullLiteral(unaryNode)); + } + + runtimeNode.accept(this); + + getCurrentFunctionNode().newTemporary(Type.OBJECT, unaryNode); + runtimeNode.setSymbol(unaryNode.getSymbol()); + + return runtimeNode; + } + + @Override + public Node leaveSUB(final UnaryNode unaryNode) { + return leaveUnary(unaryNode, Type.NUMBER); + } + + @Override + public Node leaveVOID(final UnaryNode unaryNode) { + final Node rhs = unaryNode.rhs(); + getCurrentFunctionNode().newTemporary(Type.OBJECT, unaryNode); + + // Set up request. + final RuntimeNode runtimeNode = new RuntimeNode(source, unaryNode.getToken(), unaryNode.getFinish(), VOID, convert(rhs, Type.OBJECT)); + + // Use same symbol as unary node. + runtimeNode.setSymbol(unaryNode.getSymbol()); + + return runtimeNode; + } + + /** + * Compute the narrowest possible type for a binaryNode, based on its + * contents. + * + * @param binaryNode the binary node + * @return the narrowest possible type + */ + private static Type binaryType(final BinaryNode binaryNode) { + final Node lhs = binaryNode.lhs(); + assert lhs.hasType(); + + final Node rhs = binaryNode.rhs(); + assert rhs.hasType(); + + // actually bitwise assignments are ok for ints. TODO + switch (binaryNode.tokenType()) { + case ASSIGN_BIT_AND: + case ASSIGN_BIT_OR: + case ASSIGN_BIT_XOR: + case ASSIGN_SHL: + case ASSIGN_SAR: + return Compiler.shouldUseIntegers() ? Type.widest(lhs.getType(), rhs.getType(), Type.INT) : Type.INT; + case ASSIGN_SHR: + return Type.LONG; + case ASSIGN: + return Compiler.shouldUseIntegers() ? Type.widest(lhs.getType(), rhs.getType(), Type.NUMBER) : Type.NUMBER; + default: + return binaryArithType(lhs.getType(), rhs.getType()); + } + } + + private static Type binaryArithType(final Type lhsType, final Type rhsType) { + if (!Compiler.shouldUseIntegerArithmetic()) { + return Type.NUMBER; + } + return Type.widest(lhsType, rhsType, Type.NUMBER); + } + + /** + * Visit binary node and set symbols and inferred type + * + * @param binaryNode unary node to visit + * @param type The narrowest allowed type for this node + * + * @return the final node, or replacement thereof + */ + private Node leaveBinary(final BinaryNode binaryNode, final Type type) { + return leaveBinary(binaryNode, type, type, type); + } + + /** + * Visit a binary node, specifying types for rhs, rhs and destType. + * + * @param binaryNode the binary node + * @param destType destination type + * @param lhsType type for left hand side + * @param rhsType type for right hand side + * + * @return resulting binary node + */ + private Node leaveBinary(final BinaryNode binaryNode, final Type destType, final Type lhsType, final Type rhsType) { + /* Attempt to turn this binary expression into a constant */ + final LiteralNode<?> literalNode = new BinaryNodeConstantEvaluator(binaryNode).eval(); + if (literalNode != null) { + return newLiteral(literalNode); + } + + if (lhsType != null) { + binaryNode.setLHS(convert(binaryNode.lhs(), lhsType)); + } + + if (rhsType != null) { + binaryNode.setRHS(convert(binaryNode.rhs(), rhsType)); + } + + getCurrentFunctionNode().newTemporary(destType, binaryNode); + + return binaryNode; + } + + /** + * Determine if the outcome of + operator is a string. + * + * @param node + * Node to test. + * @return true if a string result. + */ + private boolean isAddString(final Node node) { + if (node instanceof BinaryNode && node.isTokenType(TokenType.ADD)) { + final BinaryNode binaryNode = (BinaryNode)node; + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + return isAddString(lhs) || isAddString(rhs); + } + + return node instanceof LiteralNode<?> && ((LiteralNode<?>)node).getObject() instanceof String; + } + + /** + * Helper for creating a new runtime node from a parent node, inheriting its + * types and tokens + * + * @param parent Parent node. + * @param args Runtime request arguments. + * @param request Runtime request type. + * @return New {@link RuntimeNode}. + */ + + private RuntimeNode newRuntime(final Node parent, final List<Node> args, final Request request) { + final RuntimeNode runtimeNode = new RuntimeNode(source, parent.getToken(), parent.getFinish(), request, args); + runtimeNode.setStart(parent.getStart()); + runtimeNode.setFinish(parent.getFinish()); + + // Use same symbol as parent node. + runtimeNode.accept(this); + runtimeNode.setSymbol(parent.getSymbol()); + + return runtimeNode; + } + + /** + * Helper for creating a new runtime node from a binary node + * + * @param binaryNode {@link RuntimeNode} expression. + * @param request Runtime request type. + * @return New {@link RuntimeNode}. + */ + private RuntimeNode newRuntime(final BinaryNode binaryNode, final Request request) { + return newRuntime(binaryNode, Arrays.asList(new Node[] { binaryNode.lhs(), binaryNode.rhs() }), request); + } + + /** + * Add is a special binary, as it works not only on arithmetic, but for + * strings etc as well. + */ + @Override + public Node leaveADD(final BinaryNode binaryNode) { + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + //parameters must be blown up to objects + ensureTypeNotUnknown(lhs); + ensureTypeNotUnknown(rhs); + + if ((lhs.getType().isNumeric() || lhs.getType().isBoolean()) && (rhs.getType().isNumeric() || rhs.getType().isBoolean())) { + return leaveBinary(binaryNode, binaryType(binaryNode)); + } else if (isAddString(binaryNode)) { + binaryNode.setLHS(convert(lhs, Type.OBJECT)); + binaryNode.setRHS(convert(rhs, Type.OBJECT)); + getCurrentFunctionNode().newTemporary(Type.OBJECT, binaryNode); + } else { + getCurrentFunctionNode().newTemporary(Type.OBJECT, binaryNode); + return newRuntime(binaryNode, ADD); + } + + return binaryNode; + } + + @Override + public Node leaveAND(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, Type.OBJECT, null, null); + } + + /** + * This is a helper called before an assignment. + * @param binaryNode assignment node + */ + private Node enterAssign(final BinaryNode binaryNode) { + final Node lhs = binaryNode.lhs(); + + if (!(lhs instanceof IdentNode)) { + return binaryNode; + } + + final Block block = getCurrentBlock(); + final IdentNode ident = (IdentNode)lhs; + final String name = ident.getName(); + + Symbol symbol = getCurrentBlock().findSymbol(name); + + if (symbol == null) { + symbol = block.defineSymbol(name, IS_GLOBAL, ident); + binaryNode.setSymbol(symbol); + } else if (!getCurrentFunctionNode().isLocal(symbol)) { + symbol.setIsScope(); + } + + localDefs.add(name); + + return binaryNode; + } + + /** + * This assign helper is called after an assignment, when all children of + * the assign has been processed. It fixes the types and recursively makes + * sure that everyhing has slots that should have them in the chain. + * + * @param binaryNode assignment node + * @param destType destination type of assignment + */ + private Node leaveAssign(final BinaryNode binaryNode, final Type destType) { + binaryNode.lhs().getSymbol().setType(destType); // lhs inherits dest type + getCurrentFunctionNode().newTemporary(destType, binaryNode); // as does destination + + ensureAssignmentSlots(getCurrentFunctionNode(), binaryNode.lhs()); + return binaryNode; + } + + @Override + public Node enterASSIGN(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN(final BinaryNode binaryNode) { + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + if (rhs.getType().isNumeric()) { + final Symbol lhsSymbol = lhs.getSymbol(); + final Type lhsType = Type.widest(lhs.getType(), binaryType(binaryNode)); + + // for index nodes, we can set anything + lhsSymbol.setType(lhsType); + getCurrentFunctionNode().newTemporary(lhs.getType(), binaryNode); + if (!(lhs instanceof IndexNode)) { + binaryNode.setRHS(convert(rhs, lhsType)); + } + } else { + // Force symbol to be object if not numeric assignment. + binaryNode.setRHS(convert(rhs, Type.OBJECT)); + getCurrentFunctionNode().newTemporary(Type.OBJECT, binaryNode); + + if (lhs instanceof IdentNode) { + lhs.getSymbol().setType(Type.OBJECT); + } + } + + if (lhs instanceof AccessNode) { + final Node baseNode = ((AccessNode)lhs).getBase(); + + if (baseNode.getSymbol().isThis()) { + final IdentNode property = ((AccessNode)lhs).getProperty(); + getCurrentFunctionNode().addThisProperty(property.getName(), property); + } + } + + return binaryNode; + } + + @Override + public Node enterASSIGN_ADD(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_ADD(final BinaryNode binaryNode) { + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + final boolean bothNumeric = lhs.getType().isNumeric() && rhs.getType().isNumeric(); + + /* + * In the case of bothNumeric, + * compute type for lhs += rhs. Assign type to lhs. Assign type to + * temporary for this node. Convert rhs from whatever it was to this + * type. Legacy wise dest has always been narrowed. It should + * actually only be the lhs that is the double, or other narrower + * type than OBJECT + */ + return leaveAssign(binaryNode, bothNumeric ? binaryType(binaryNode) : Type.OBJECT); + } + + @Override + public Node enterASSIGN_BIT_AND(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_BIT_AND(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_BIT_OR(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_BIT_OR(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_BIT_XOR(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_DIV(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_DIV(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_MOD(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_MOD(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_MUL(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_MUL(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_SAR(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_SAR(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_SHL(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_SHL(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_SHR(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_SHR(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node enterASSIGN_SUB(final BinaryNode binaryNode) { + return enterAssign(binaryNode); + } + + @Override + public Node leaveASSIGN_SUB(final BinaryNode binaryNode) { + return leaveAssign(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node leaveBIT_AND(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, Type.INT); + } + + @Override + public Node leaveBIT_OR(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, Type.INT); + } + + @Override + public Node leaveBIT_XOR(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, Type.INT); + } + + @Override + public Node leaveCOMMARIGHT(final BinaryNode binaryNode) { + binaryNode.setLHS(discard(binaryNode.lhs())); + getCurrentFunctionNode().newTemporary(binaryNode.rhs().getType(), binaryNode); + + return binaryNode; + } + + @Override + public Node leaveCOMMALEFT(final BinaryNode binaryNode) { + binaryNode.setRHS(discard(binaryNode.rhs())); + getCurrentFunctionNode().newTemporary(binaryNode.lhs().getType(), binaryNode); + + return binaryNode; + } + + @Override + public Node leaveDIV(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node leaveEQ(final BinaryNode binaryNode) { + return leaveCmp(binaryNode, EQ); + } + + @Override + public Node leaveEQ_STRICT(final BinaryNode binaryNode) { + return leaveCmp(binaryNode, EQ_STRICT); + } + + private Node leaveCmp(final BinaryNode binaryNode, final RuntimeNode.Request request) { + final Node lhs = binaryNode.lhs(); + final Node rhs = binaryNode.rhs(); + + /* Attempt to turn this comparison into a constant and collapse it */ + final LiteralNode<?> literalNode = new BinaryNodeConstantEvaluator(binaryNode).eval(); + if (literalNode != null) { + return newLiteral(literalNode); + } + + // another case where dest != source operand types always a boolean + getCurrentFunctionNode().newTemporary(request.getReturnType(), binaryNode); + + ensureTypeNotUnknown(lhs); + ensureTypeNotUnknown(rhs); + final Type type = Type.widest(lhs.getType(), rhs.getType()); + + if (type.isObject()) { + return newRuntime(binaryNode, request); + } + + if ((request.equals(EQ_STRICT) || request.equals(NE_STRICT)) && lhs.getType().isBoolean() != rhs.getType().isBoolean()) { + // special case: number compared against boolean => never equal. must not convert! + final boolean result = request.equals(NE_STRICT); + final LiteralNode<Boolean> resultNode = LiteralNode.newInstance(source, 0, 0, result); + final boolean canSkipLHS = (lhs instanceof LiteralNode); + final boolean canSkipRHS = (rhs instanceof LiteralNode); + + if (canSkipLHS && canSkipRHS) { + return resultNode.accept(this); + } + + final Node argNode; + + if (!canSkipLHS && !canSkipRHS) { + argNode = new BinaryNode(source, Token.recast(binaryNode.getToken(), TokenType.COMMARIGHT), lhs, rhs); + } else { + argNode = !canSkipLHS ? lhs : rhs; + } + + return new BinaryNode(source, Token.recast(binaryNode.getToken(), TokenType.COMMARIGHT), argNode, resultNode).accept(this); + } + + binaryNode.setLHS(convert(lhs, type)); + binaryNode.setRHS(convert(rhs, type)); + + return binaryNode; + } + + @Override + public Node leaveGE(final BinaryNode binaryNode) { + return leaveCmp(binaryNode, GE); + } + + @Override + public Node leaveGT(final BinaryNode binaryNode) { + return leaveCmp(binaryNode, GT); + } + + private Node exitIN_INSTANCEOF(final BinaryNode binaryNode, final Request request) { + getCurrentFunctionNode().newTemporary(request.getReturnType(), binaryNode); + return newRuntime(binaryNode, request); + } + + @Override + public Node leaveIN(final BinaryNode binaryNode) { + return exitIN_INSTANCEOF(binaryNode, IN); + } + + @Override + public Node leaveINSTANCEOF(final BinaryNode binaryNode) { + return exitIN_INSTANCEOF(binaryNode, INSTANCEOF); + } + + @Override + public Node leaveLE(final BinaryNode binaryNode) { + return leaveCmp(binaryNode, LE); + } + + @Override + public Node leaveLT(final BinaryNode binaryNode) { + return leaveCmp(binaryNode, LT); + } + + @Override + public Node leaveMOD(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node leaveMUL(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node leaveNE(final BinaryNode binaryNode) { + return leaveCmp(binaryNode, NE); + } + + @Override + public Node leaveNE_STRICT(final BinaryNode binaryNode) { + return leaveCmp(binaryNode, NE_STRICT); + } + + @Override + public Node leaveOR(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, Type.OBJECT, null, null); + } + + @Override + public Node leaveSAR(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, Type.INT); + } + + @Override + public Node leaveSHL(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, Type.INT); + } + + @Override + public Node leaveSHR(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, Type.LONG, Type.INT, Type.INT); + } + + @Override + public Node leaveSUB(final BinaryNode binaryNode) { + return leaveBinary(binaryNode, binaryType(binaryNode)); + } + + @Override + public Node leave(final TernaryNode ternaryNode) { + final Node test = ternaryNode.lhs(); + final Node lhs = ternaryNode.rhs(); + final Node rhs = ternaryNode.third(); + + ensureTypeNotUnknown(lhs); + ensureTypeNotUnknown(rhs); + final Type type = Type.widest(lhs.getType(), rhs.getType()); + + ternaryNode.setRHS(convert(lhs, type)); + ternaryNode.setThird(convert(rhs, type)); + + // optimize away the ternary if the test is constant. + if (test.getSymbol().isConstant()) { + return ((LiteralNode<?>)test).isTrue() ? lhs : rhs; + } + + ternaryNode.setLHS(convert(test, Type.BOOLEAN)); + + getCurrentFunctionNode().newTemporary(type, ternaryNode); + + return ternaryNode; + } + + private static void ensureTypeNotUnknown(final Node node) { + final Symbol symbol = node.getSymbol(); + + /* + * Note that not just unknowns, but params need to be blown + * up to objects, because we can have something like + * + * function f(a) { + * var b = ~a; //b and a are inferred to be int + * return b; + * } + * + * In this case, it would be correct to say that "if you have + * an int at the callsite, just pass it". + * + * However + * + * function f(a) { + * var b = ~a; //b and a are inferred to be int + * return b == 17; //b is still inferred to be int. + * } + * + * can be called with f("17") and if we assume that b is an + * int and don't blow it up to an object in the comparison, we + * are screwed. I hate JavaScript. + * + * This check has to be done for any operation that might take + * objects as parameters, for example +, but not *, which is known + * to coerce types into doubles + */ + if (node.getType().isUnknown() || symbol.isParam()) { + symbol.setType(Type.OBJECT); + symbol.setCanBeUndefined(); + } + } + + /** + * Helper class to evaluate constant expressions at compile time This is + * also a simplifier used by BinaryNode visits, UnaryNode visits and + * conversions. + */ + private abstract static class ConstantEvaluator<T extends Node> { + protected T parent; + protected final Source source; + protected final long token; + protected final int finish; + + protected ConstantEvaluator(final T parent) { + this.parent = parent; + this.source = parent.getSource(); + this.token = parent.getToken(); + this.finish = parent.getFinish(); + } + + /** + * Returns a literal node that replaces the given parent node, or null if replacement + * is impossible + * @return the literal node + */ + protected abstract LiteralNode<?> eval(); + } + + static class LiteralNodeConstantEvaluator extends ConstantEvaluator<LiteralNode<?>> { + private final Type type; + + LiteralNodeConstantEvaluator(final LiteralNode<?> parent, final Type type) { + super(parent); + this.type = type; + } + + @Override + protected LiteralNode<?> eval() { + final Object value = ((LiteralNode<?>)parent).getValue(); + + LiteralNode<?> literalNode = null; + + if (type.isString()) { + literalNode = LiteralNode.newInstance(source, token, finish, JSType.toString(value)); + } else if (type.isBoolean()) { + literalNode = LiteralNode.newInstance(source, token, finish, JSType.toBoolean(value)); + } else if (type.isInteger()) { + literalNode = LiteralNode.newInstance(source, token, finish, JSType.toInt32(value)); + } else if (type.isLong()) { + literalNode = LiteralNode.newInstance(source, token, finish, JSType.toLong(value)); + } else if (type.isNumber() || parent.getType().isNumeric() && !parent.getType().isNumber()) { + literalNode = LiteralNode.newInstance(source, token, finish, JSType.toNumber(value)); + } + + return literalNode; + } + } + + private static class UnaryNodeConstantEvaluator extends ConstantEvaluator<UnaryNode> { + UnaryNodeConstantEvaluator(final UnaryNode parent) { + super(parent); + } + + @Override + protected LiteralNode<?> eval() { + final Node rhsNode = parent.rhs(); + + if (!rhsNode.getSymbol().isConstant()) { + return null; + } + + final LiteralNode<?> rhs = (LiteralNode<?>)rhsNode; + final boolean rhsInteger = rhs.getType().isInteger(); + + LiteralNode<?> literalNode; + + switch (parent.tokenType()) { + case ADD: + if (rhsInteger) { + literalNode = LiteralNode.newInstance(source, token, finish, rhs.getInt32()); + } else { + literalNode = LiteralNode.newInstance(source, token, finish, rhs.getNumber()); + } + break; + case SUB: + if (rhsInteger && rhs.getInt32() != 0) { // @see test/script/basic/minuszero.js + literalNode = LiteralNode.newInstance(source, token, finish, -rhs.getInt32()); + } else { + literalNode = LiteralNode.newInstance(source, token, finish, -rhs.getNumber()); + } + break; + case NOT: + literalNode = LiteralNode.newInstance(source, token, finish, !rhs.getBoolean()); + break; + case BIT_NOT: + literalNode = LiteralNode.newInstance(source, token, finish, ~rhs.getInt32()); + break; + default: + return null; + } + + return literalNode; + } + } + + private static class BinaryNodeConstantEvaluator extends ConstantEvaluator<BinaryNode> { + BinaryNodeConstantEvaluator(final BinaryNode parent) { + super(parent); + } + + @Override + protected LiteralNode<?> eval() { + + if (!parent.lhs().getSymbol().isConstant() || !parent.rhs().getSymbol().isConstant()) { + return null; + } + + final LiteralNode<?> lhs = (LiteralNode<?>)parent.lhs(); + final LiteralNode<?> rhs = (LiteralNode<?>)parent.rhs(); + + final Type widest = Type.widest(lhs.getType(), rhs.getType()); + + boolean isInteger = widest.isInteger(); + boolean isLong = widest.isLong(); + + double value; + + switch (parent.tokenType()) { + case AND: + return JSType.toBoolean(lhs.getObject()) ? rhs : lhs; + case OR: + return JSType.toBoolean(lhs.getObject()) ? lhs : rhs; + case DIV: + value = lhs.getNumber() / rhs.getNumber(); + break; + case ADD: + value = lhs.getNumber() + rhs.getNumber(); + break; + case MUL: + value = lhs.getNumber() * rhs.getNumber(); + break; + case MOD: + value = lhs.getNumber() % rhs.getNumber(); + break; + case SUB: + value = lhs.getNumber() - rhs.getNumber(); + break; + case SHR: + return LiteralNode.newInstance(source, token, finish, (lhs.getInt32() >>> rhs.getInt32()) & 0xffff_ffffL); + case SAR: + return LiteralNode.newInstance(source, token, finish, lhs.getInt32() >> rhs.getInt32()); + case SHL: + return LiteralNode.newInstance(source, token, finish, lhs.getInt32() << rhs.getInt32()); + case BIT_XOR: + return LiteralNode.newInstance(source, token, finish, lhs.getInt32() ^ rhs.getInt32()); + case BIT_AND: + return LiteralNode.newInstance(source, token, finish, lhs.getInt32() & rhs.getInt32()); + case BIT_OR: + return LiteralNode.newInstance(source, token, finish, lhs.getInt32() | rhs.getInt32()); + case GE: + return LiteralNode.newInstance(source, token, finish, ScriptRuntime.GE(lhs.getObject(), rhs.getObject())); + case LE: + return LiteralNode.newInstance(source, token, finish, ScriptRuntime.LE(lhs.getObject(), rhs.getObject())); + case GT: + return LiteralNode.newInstance(source, token, finish, ScriptRuntime.GT(lhs.getObject(), rhs.getObject())); + case LT: + return LiteralNode.newInstance(source, token, finish, ScriptRuntime.LT(lhs.getObject(), rhs.getObject())); + case NE: + return LiteralNode.newInstance(source, token, finish, ScriptRuntime.NE(lhs.getObject(), rhs.getObject())); + case NE_STRICT: + return LiteralNode.newInstance(source, token, finish, ScriptRuntime.NE_STRICT(lhs.getObject(), rhs.getObject())); + case EQ: + return LiteralNode.newInstance(source, token, finish, ScriptRuntime.EQ(lhs.getObject(), rhs.getObject())); + case EQ_STRICT: + return LiteralNode.newInstance(source, token, finish, ScriptRuntime.EQ_STRICT(lhs.getObject(), rhs.getObject())); + default: + return null; + } + + isInteger &= value != 0.0 && JSType.isRepresentableAsInt(value); + isLong &= value != 0.0 && JSType.isRepresentableAsLong(value); + + if (isInteger) { + return LiteralNode.newInstance(source, token, finish, JSType.toInt32(value)); + } else if (isLong) { + return LiteralNode.newInstance(source, token, finish, JSType.toLong(value)); + } + + return LiteralNode.newInstance(source, token, finish, value); + } + } +} diff --git a/src/jdk/nashorn/internal/codegen/MethodEmitter.java b/src/jdk/nashorn/internal/codegen/MethodEmitter.java new file mode 100644 index 00000000..3f7d3558 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/MethodEmitter.java @@ -0,0 +1,2349 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.internal.org.objectweb.asm.Opcodes.ATHROW; +import static jdk.internal.org.objectweb.asm.Opcodes.CHECKCAST; +import static jdk.internal.org.objectweb.asm.Opcodes.DUP2; +import static jdk.internal.org.objectweb.asm.Opcodes.GETFIELD; +import static jdk.internal.org.objectweb.asm.Opcodes.GETSTATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.GOTO; +import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.IFEQ; +import static jdk.internal.org.objectweb.asm.Opcodes.IFGE; +import static jdk.internal.org.objectweb.asm.Opcodes.IFGT; +import static jdk.internal.org.objectweb.asm.Opcodes.IFLE; +import static jdk.internal.org.objectweb.asm.Opcodes.IFLT; +import static jdk.internal.org.objectweb.asm.Opcodes.IFNE; +import static jdk.internal.org.objectweb.asm.Opcodes.IFNONNULL; +import static jdk.internal.org.objectweb.asm.Opcodes.IFNULL; +import static jdk.internal.org.objectweb.asm.Opcodes.IF_ACMPEQ; +import static jdk.internal.org.objectweb.asm.Opcodes.IF_ACMPNE; +import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPEQ; +import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPGE; +import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPGT; +import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPLE; +import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPLT; +import static jdk.internal.org.objectweb.asm.Opcodes.IF_ICMPNE; +import static jdk.internal.org.objectweb.asm.Opcodes.INSTANCEOF; +import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEINTERFACE; +import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static jdk.internal.org.objectweb.asm.Opcodes.NEW; +import static jdk.internal.org.objectweb.asm.Opcodes.PUTFIELD; +import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.RETURN; +import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; +import static jdk.nashorn.internal.codegen.CompilerConstants.THIS_DEBUGGER; +import static jdk.nashorn.internal.codegen.CompilerConstants.className; +import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; +import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; +import static jdk.nashorn.internal.codegen.CompilerConstants.staticField; +import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; + +import java.io.PrintStream; +import java.lang.reflect.Array; +import java.util.ArrayDeque; +import java.util.EnumSet; +import java.util.Iterator; +import jdk.internal.org.objectweb.asm.Handle; +import jdk.internal.org.objectweb.asm.MethodVisitor; +import jdk.nashorn.internal.codegen.ClassEmitter.Flag; +import jdk.nashorn.internal.codegen.CompilerConstants.Call; +import jdk.nashorn.internal.codegen.CompilerConstants.FieldAccess; +import jdk.nashorn.internal.codegen.types.ArrayType; +import jdk.nashorn.internal.codegen.types.BitwiseType; +import jdk.nashorn.internal.codegen.types.NumericType; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.RuntimeNode; +import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.DebugLogger; +import jdk.nashorn.internal.runtime.JSType; +import jdk.nashorn.internal.runtime.Scope; +import jdk.nashorn.internal.runtime.linker.Bootstrap; +import jdk.nashorn.internal.runtime.options.Options; +import org.dynalang.dynalink.support.NameCodec; + +/** + * This is the main function responsible for emitting method code + * in a class. It maintains a type stack and keeps track of control + * flow to make sure that the registered instructions don't violate + * byte code verification. + * + * Running Nashorn with -ea will assert as soon as a type stack + * becomes corrupt, for easier debugging + * + * Running Nashorn with -Dnashorn.codegen.debug=true will print + * all generated bytecode and labels to stderr, for easier debugging, + * including bytecode stack contents + */ +public class MethodEmitter implements Emitter { + /** The ASM MethodVisitor we are plugged into */ + private final MethodVisitor method; + + /** Current type stack for current evaluation */ + protected ArrayDeque<Type> stack; + + /** Parent classEmitter representing the class of this method */ + private final ClassEmitter classEmitter; + + /** FunctionNode representing this method, or null if none exists */ + private FunctionNode functionNode; + + /** SplitNode representing the current split, or null if none exists */ + private SplitNode splitNode; + + /** The context */ + private final Context context; + + /** Threshold in chars for when string constants should be split */ + static final int LARGE_STRING_THRESHOLD = 32 * 1024; + + /** Debug flag, should we dump all generated bytecode along with stacks? */ + private static final DebugLogger LOG = new DebugLogger("codegen", "nashorn.codegen.debug"); + private static final boolean DEBUG = LOG.isEnabled(); + + /** dump stack on a particular line, or -1 if disabled */ + private static final int DEBUG_TRACE_LINE; + + static { + final String tl = Options.getStringProperty("nashorn.codegen.debug.trace", "-1"); + int line = -1; + try { + line = Integer.parseInt(tl); + } catch (final NumberFormatException e) { + //fallthru + } + DEBUG_TRACE_LINE = line; + } + + /** Bootstrap for normal indy:s */ + private static final Handle LINKERBOOTSTRAP = new Handle(H_INVOKESTATIC, Bootstrap.BOOTSTRAP.className(), Bootstrap.BOOTSTRAP.name(), Bootstrap.BOOTSTRAP.descriptor()); + + /** Bootstrap for runtime node indy:s */ + private static final Handle RUNTIMEBOOTSTRAP = new Handle(H_INVOKESTATIC, RuntimeCallSite.BOOTSTRAP.className(), RuntimeCallSite.BOOTSTRAP.name(), RuntimeCallSite.BOOTSTRAP.descriptor()); + + /** + * Constructor - internal use from ClassEmitter only + * @see ClassEmitter.method + * + * @param classEmitter the class emitter weaving the class this method is in + * @param method a method visitor + */ + MethodEmitter(final ClassEmitter classEmitter, final MethodVisitor method) { + this(classEmitter, method, null); + } + + /** + * Constructor - internal use from ClassEmitter only + * @see ClassEmitter.method + * + * @param classEmitter the class emitter weaving the class this method is in + * @param method a method visitor + * @param functionNode a function node representing this method + */ + MethodEmitter(final ClassEmitter classEmitter, final MethodVisitor method, final FunctionNode functionNode) { + this.context = classEmitter.getContext(); + this.classEmitter = classEmitter; + this.method = method; + this.functionNode = functionNode; + this.stack = null; + } + + /** + * Begin a method + * @see Emitter + */ + @Override + public void begin() { + classEmitter.beginMethod(this); + stack = new ArrayDeque<>(); + method.visitCode(); + } + + /** + * End a method + * @see Emitter + */ + @Override + public void end() { + method.visitMaxs(0, 0); + method.visitEnd(); + + classEmitter.endMethod(this); + } + + @Override + public String toString() { + return "methodEmitter: " + (functionNode == null ? method : functionNode.getName()).toString(); + } + + /** + * Push a type to the existing stack + * @param type the type + */ + private void pushType(final Type type) { + if (type != null) { + stack.push(type); + } + } + + /** + * Pop a type from the existing stack + * + * @param expected expected type - will assert if wrong + * + * @return the type that was retrieved + */ + private Type popType(final Type expected) { + final Type type = stack.pop(); + assert type.isObject() && expected.isObject() || + type.isEquivalentTo(expected) : type + " is not compatible with " + expected; + return type; + } + + /** + * Pop a type from the existing stack, no matter what it is. + * + * @return the type + */ + private Type popType() { + return stack.pop(); + } + + /** + * Pop a type from the existing stack, ensuring that it is numeric, + * assert if not + * + * @return the type + */ + private NumericType popNumeric() { + final Type type = stack.pop(); + assert type.isNumeric() : type + " is not numeric"; + return (NumericType)type; + } + + /** + * Pop a type from the existing stack, ensuring that it is an integer type + * (integer or long), assert if not + * + * @return the type + */ + private BitwiseType popInteger() { + final Type type = stack.pop(); + assert type.isInteger() || type.isLong() : type + " is not an integer or long"; + return (BitwiseType)type; + } + + /** + * Pop a type from the existing stack, ensuring that it is an array type, + * assert if not + * + * @return the type + */ + private ArrayType popArray() { + final Type type = stack.pop(); + assert type.isArray(); + return (ArrayType)type; + } + + /** + * Peek a given number of slots from the top of the stack and return the + * type in that slot + * + * @param pos the number of positions from the top, 0 is the top element + * + * @return the type at position "pos" on the stack + */ + public final Type peekType(final int pos) { + final Iterator<Type> iter = stack.iterator(); + for (int i = 0; i < pos; i++) { + iter.next(); + } + return iter.next(); + } + + /** + * Peek at the type at the top of the stack + * + * @return the type at the top of the stack + */ + public final Type peekType() { + return stack.peek(); + } + + /** + * Generate code a for instantiating a new object and push the + * object type on the stack + * + * @param classDescriptor class descriptor for the object type + * + * @return the method emitter + */ + public MethodEmitter _new(final String classDescriptor) { + method.visitTypeInsn(NEW, classDescriptor); + pushType(Type.OBJECT); + return this; + } + + /** + * Generate code a for instantiating a new object and push the + * object type on the stack + * + * @param clazz class type to instatiate + * + * @return the method emitter + */ + public MethodEmitter _new(final Class<?> clazz) { + return _new(className(clazz)); + } + + /** + * Generate code to call the empty constructor for a class + * + * @param clazz class type to instatiate + * + * @return the method emitter + */ + public MethodEmitter newInstance(final Class<?> clazz) { + return invoke(constructorNoLookup(clazz)); + } + + /** + * Perform a dup, that is, duplicate the top element and + * push the duplicate down a given number of positions + * on the stack. This is totally type agnostic. + * + * @param depth the depth on which to put the copy + * + * @return the method emitter, or null if depth is illegal and + * has no instruction equivalent. + */ + public MethodEmitter dup(final int depth) { + if (peekType().dup(method, depth) == null) { + return null; + } + + debug("dup", depth); + + switch (depth) { + case 0: + pushType(peekType()); + break; + case 1: { + final Type p0 = popType(); + final Type p1 = popType(); + pushType(p0); + pushType(p1); + pushType(p0); + break; + } + case 2: { + final Type p0 = popType(); + final Type p1 = popType(); + final Type p2 = popType(); + pushType(p0); + pushType(p2); + pushType(p1); + pushType(p0); + break; + } + default: + assert false : "illegal dup depth = " + depth; + return null; + } + + return this; + } + + /** + * Perform a dup2, that is, duplicate the top element if it + * is a category 2 type, or two top elements if they are category + * 1 types, and push them on top of the stack + * + * @return the method emitter + */ + public MethodEmitter dup2() { + debug("dup2"); + + if (peekType().isCategory2()) { + pushType(peekType()); + } else { + final Type type = get2(); + pushType(type); + pushType(type); + pushType(type); + pushType(type); + } + method.visitInsn(DUP2); + return this; + } + + /** + * Duplicate the top element on the stack and push it + * + * @return the method emitter + */ + public MethodEmitter dup() { + return dup(0); + } + + /** + * Pop the top element of the stack and throw it away + * + * @return the method emitter + */ + public MethodEmitter pop() { + debug("pop", peekType()); + popType().pop(method); + return this; + } + + /** + * Pop the top element of the stack if category 2 type, or the two + * top elements of the stack if category 1 types + * + * @return the method emitter + */ + public MethodEmitter pop2() { + if (peekType().isCategory2()) { + popType(); + } else { + get2n(); + } + return this; + } + + /** + * Swap the top two elements of the stack. This is totally + * type agnostic and works for all types + * + * @return the method emitter + */ + public MethodEmitter swap() { + debug("swap"); + + final Type p0 = popType(); + final Type p1 = popType(); + p0.swap(method, p1); + + pushType(p0); + pushType(p1); + debug("after ", p0, p1); + return this; + } + + /** + * Add a local variable. This is a nop if the symbol has no slot + * + * @param symbol symbol for the local variable + * @param start start of scope + * @param end end of scope + */ + public void localVariable(final Symbol symbol, final Label start, final Label end) { + if (!symbol.hasSlot()) { + return; + } + + String name = symbol.getName(); + + if (name.equals(THIS.tag())) { + name = THIS_DEBUGGER.tag(); + } + + method.visitLocalVariable(name, symbol.getSymbolType().getDescriptor(), null, start, end, symbol.getSlot()); + } + + /** + * Create a new string builder, call the constructor and push the instance to the stack. + * + * @return the method emitter + */ + public MethodEmitter newStringBuilder() { + return invoke(constructorNoLookup(StringBuilder.class)).dup(); + } + + /** + * Pop a string and a StringBuilder from the top of the stack and call the append + * function of the StringBuilder, appending the string. Pushes the StringBuilder to + * the stack when finished. + * + * @return the method emitter + */ + public MethodEmitter stringBuilderAppend() { + convert(Type.STRING); + return invoke(virtualCallNoLookup(StringBuilder.class, "append", StringBuilder.class, String.class)); + } + + /** + * Associate a variable with a given range + * + * @param name name of the variable + * @param start start + * @param end end + */ + public void markerVariable(final String name, final Label start, final Label end) { + method.visitLocalVariable(name, Type.OBJECT.getDescriptor(), null, start, end, 0); + } + + /** + * Pops two integer types from the stack, performs a bitwise and and pushes + * the result + * + * @return the method emitter + */ + public MethodEmitter and() { + debug("and"); + pushType(get2i().and(method)); + return this; + } + + /** + * Pops two integer types from the stack, performs a bitwise or and pushes + * the result + * + * @return the method emitter + */ + public MethodEmitter or() { + debug("or"); + pushType(get2i().or(method)); + return this; + } + + /** + * Pops two integer types from the stack, performs a bitwise xor and pushes + * the result + * + * @return the method emitter + */ + public MethodEmitter xor() { + debug("xor"); + pushType(get2i().xor(method)); + return this; + } + + /** + * Pops two integer types from the stack, performs a bitwise logic shift right and pushes + * the result. The shift count, the first element, must be INT. + * + * @return the method emitter + */ + public MethodEmitter shr() { + debug("shr"); + popType(Type.INT); + pushType(popInteger().shr(method)); + return this; + } + + /** + * Pops two integer types from the stack, performs a bitwise shift left and and pushes + * the result. The shift count, the first element, must be INT. + * + * @return the method emitter + */ + public MethodEmitter shl() { + debug("shl"); + popType(Type.INT); + pushType(popInteger().shl(method)); + return this; + } + + /** + * Pops two integer types from the stack, performs a bitwise arithetic shift right and pushes + * the result. The shift count, the first element, must be INT. + * + * @return the method emitter + */ + public MethodEmitter sar() { + debug("sar"); + popType(Type.INT); + pushType(popInteger().sar(method)); + return this; + } + + /** + * Pops a numeric type from the stack, negates it and pushes the result + * + * @return the method emitter + */ + public MethodEmitter neg() { + debug("neg"); + pushType(popNumeric().neg(method)); + return this; + } + + /** + * Add label for the start of a catch block and push the exception to the + * stack + * + * @param recovery label pointing to start of catch block + */ + public void _catch(final Label recovery) { + stack.clear(); + stack.push(Type.OBJECT); + label(recovery); + } + + /** + * Start a try/catch block. + * + * @param entry start label for try + * @param exit end label for try + * @param recovery start label for catch + * @param typeDescriptor type descriptor for exception + */ + public void _try(final Label entry, final Label exit, final Label recovery, final String typeDescriptor) { + method.visitTryCatchBlock(entry, exit, recovery, typeDescriptor); + } + + /** + * Start a try/catch block. + * + * @param entry start label for try + * @param exit end label for try + * @param recovery start label for catch + * @param clazz exception class + */ + public void _try(final Label entry, final Label exit, final Label recovery, final Class<?> clazz) { + method.visitTryCatchBlock(entry, exit, recovery, CompilerConstants.className(clazz)); + } + + /** + * Start a try/catch block. The catch is "Throwable" - i.e. catch-all + * + * @param entry start label for try + * @param exit end label for try + * @param recovery start label for catch + */ + public void _try(final Label entry, final Label exit, final Label recovery) { + _try(entry, exit, recovery, (String)null); + } + + + /** + * Load the constants array + * @param unitClassName name of the compile unit from which to load constants + * @return this method emitter + */ + public MethodEmitter loadConstants(final String unitClassName) { + getStatic(unitClassName, CONSTANTS.tag(), CONSTANTS.descriptor()); + assert peekType().isArray() : peekType(); + return this; + } + + /** + * Push the undefined value for the given type, i.e. + * UNDEFINED or UNDEFINEDNUMBER. Currently we have no way of + * representing UNDEFINED for INTs and LONGs, so they are not + * allowed to be local variables (yet) + * + * @param type the type for which to push UNDEFINED + * @return the method emitter + */ + public MethodEmitter loadUndefined(final Type type) { + debug("load undefined " + type); + pushType(type.loadUndefined(method)); + return this; + } + + /** + * Push the empty value for the given type, i.e. EMPTY. + * + * @param type the type + * @return the method emitter + */ + public MethodEmitter loadEmpty(final Type type) { + debug("load empty " + type); + pushType(type.loadEmpty(method)); + return this; + } + + /** + * Push null to stack + * + * @return the method emitter + */ + public MethodEmitter loadNull() { + debug("aconst_null"); + pushType(Type.OBJECT.ldc(method, null)); + return this; + } + + /** + * Push a handle representing this class top stack + * + * @param className name of the class + * + * @return the method emitter + */ + public MethodEmitter loadType(final String className) { + debug("load type", className); + method.visitLdcInsn(jdk.internal.org.objectweb.asm.Type.getObjectType(className)); + pushType(Type.OBJECT); + return this; + } + + /** + * Push a boolean constant to the stack. + * + * @param b value of boolean + * + * @return the method emitter + */ + public MethodEmitter load(final boolean b) { + debug("load boolean", b); + pushType(Type.BOOLEAN.ldc(method, b)); + return this; + } + + /** + * Push an int constant to the stack + * + * @param i value of the int + * + * @return the method emitter + */ + public MethodEmitter load(final int i) { + debug("load int", i); + pushType(Type.INT.ldc(method, i)); + return this; + } + + /** + * Push a double constant to the stack + * + * @param d value of the double + * + * @return the method emitter + */ + public MethodEmitter load(final double d) { + debug("load double", d); + pushType(Type.NUMBER.ldc(method, d)); + return this; + } + + /** + * Push an long constant to the stack + * + * @param l value of the long + * + * @return the method emitter + */ + public MethodEmitter load(final long l) { + debug("load long", l); + pushType(Type.LONG.ldc(method, l)); + return this; + } + + /** + * Fetch the length of an array. + * @return Array length. + */ + public MethodEmitter arraylength() { + debug("arraylength"); + popType(Type.OBJECT); + pushType(Type.OBJECT_ARRAY.arraylength(method)); + return this; + } + + /** + * Push a String constant to the stack + * + * @param s value of the String + * + * @return the method emitter + */ + public MethodEmitter load(final String s) { + debug("load string", s); + + if (s == null) { + loadNull(); + return this; + } + + //NASHORN-142 - split too large string + final int length = s.length(); + if (length > LARGE_STRING_THRESHOLD) { + + _new(StringBuilder.class); + dup(); + load(length); + invoke(constructorNoLookup(StringBuilder.class, int.class)); + + for (int n = 0; n < length; n += LARGE_STRING_THRESHOLD) { + final String part = s.substring(n, Math.min(n + LARGE_STRING_THRESHOLD, length)); + load(part); + stringBuilderAppend(); + } + + invoke(virtualCallNoLookup(StringBuilder.class, "toString", String.class)); + + return this; + } + + pushType(Type.OBJECT.ldc(method, s)); + return this; + } + + /** + * Push an local variable to the stack. If the symbol representing + * the local variable doesn't have a slot, this is a NOP + * + * @param symbol the symbol representing the local variable. + * + * @return the method emitter + */ + public MethodEmitter load(final Symbol symbol) { + assert symbol != null; + if (symbol.hasSlot()) { + debug("load symbol", symbol.getName() + " slot=" + symbol.getSlot()); + pushType(symbol.getSymbolType().load(method, symbol.getSlot())); + } + return this; + } + + /** + * Push a local variable to the stack, given an explicit bytecode slot + * This is used e.g. for stub generation where we know where items like + * "this" and "scope" reside. + * + * @param type the type of the variable + * @param slot the slot the variable is in + * + * @return the method emitter + */ + public MethodEmitter load(final Type type, final int slot) { + debug("explicit load", type, slot); + pushType(type.load(method, slot)); + return this; + } + + /** + * Push the this object to the stack. + * + * @return the method emitter + */ + public MethodEmitter loadThis() { + load(functionNode.getThisNode().getSymbol()); + return this; + } + + /** + * Push the scope object to the stack. + * + * @return the method emitter + */ + public MethodEmitter loadScope() { + if (peekType() == Type.SCOPE) { + dup(); + return this; + } + load(functionNode.getScopeNode().getSymbol()); + return this; + } + + /** + * Push the return object to the stack. + * + * @return the method emitter + */ + public MethodEmitter loadResult() { + load(functionNode.getResultNode().getSymbol()); + return this; + } + + + /** + * Push a method handle to the stack + * + * @param className class name + * @param methodName method name + * @param descName descriptor + * @param flags flags that describe this handle, e.g. invokespecial new, or invoke virtual + * + * @see ClassEmitter.Flag + * + * @return the method emitter + */ + public MethodEmitter loadHandle(final String className, final String methodName, final String descName, final EnumSet<Flag> flags) { + debug("load handle "); + pushType(Type.OBJECT.ldc(method, new Handle(Flag.getValue(flags), className, methodName, descName))); + return this; + } + + /** + * Push the varargs object to the stack + * + * @return the method emitter + */ + public MethodEmitter loadVarArgs() { + debug("load var args " + functionNode.getVarArgsNode().getSymbol()); + return load(functionNode.getVarArgsNode().getSymbol()); + } + + /** + * Push the arguments array to the stack + * + * @return the method emitter + */ + public MethodEmitter loadArguments() { + debug("load arguments " + functionNode.getVarArgsNode().getSymbol()); + assert functionNode.getArgumentsNode().getSymbol().getSlot() != 0; + return load(functionNode.getArgumentsNode().getSymbol()); + } + + /** + * Push the callee object to the stack + * + * @return the method emitter + */ + public MethodEmitter loadCallee() { + debug("load callee " + functionNode.getCalleeNode().getSymbol()); + assert functionNode.getCalleeNode().getSymbol().getSlot() != 0; + + return load(functionNode.getCalleeNode().getSymbol()); + } + + /** + * Pop the scope from the stack and store it in its predefined slot + */ + public void storeScope() { + debug("store scope"); + store(functionNode.getScopeNode().getSymbol()); + } + + /** + * Pop the return from the stack and store it in its predefined slot + */ + public void storeResult() { + debug("store result"); + store(functionNode.getResultNode().getSymbol()); + } + + /** + * Pop the arguments array from the stack and store it in its predefined slot + */ + public void storeArguments() { + debug("store arguments"); + store(functionNode.getArgumentsNode().getSymbol()); + } + + /** + * Load an element from an array, determining type automatically + * @return the method emitter + */ + public MethodEmitter arrayload() { + debug("Xaload"); + popType(Type.INT); + pushType(popArray().aload(method)); + return this; + } + + /** + * Pop a value, an index and an array from the stack and store + * the value at the given index in the array. + */ + public void arraystore() { + debug("Xastore"); + final Type value = popType(); + final Type index = popType(Type.INT); + assert index.isInteger() : "array index is not integer, but " + index; + final ArrayType array = popArray(); + + assert value.isEquivalentTo(array.getElementType()) : "Storing "+value+" into "+array; + assert array.isObject(); + array.astore(method); + } + + /** + * Pop a value from the stack and store it in a local variable represented + * by the given symbol. If the symbol has no slot, this is a NOP + * + * @param symbol symbol to store stack to + */ + public void store(final Symbol symbol) { + assert symbol != null; + if (symbol.hasSlot()) { + debug("store", symbol); + popType(symbol.getSymbolType()).store(method, symbol.getSlot()); + } + } + + /** + * Pop a value from the stack and store it in a given local variable + * slot. + * + * @param type the type to pop + * @param slot the slot + */ + public void store(final Type type, final int slot) { + popType(type); + type.store(method, slot); + } + + /** + * Increment/Decrement a local integer by the given value. + * + * @param slot the int slot + * @param increment the amount to increment + */ + public void iinc(final int slot, final int increment) { + debug("iinc"); + method.visitIincInsn(slot, increment); + } + + /** + * Pop an exception object from the stack and generate code + * for throwing it + */ + public void athrow() { + debug("athrow"); + final Type receiver = popType(Type.OBJECT); + assert receiver.isObject(); + method.visitInsn(ATHROW); + stack = null; + } + + /** + * Pop an object from the stack and perform an instanceof + * operation, given a classDescriptor to compare it to. + * Push the boolean result 1/0 as an int to the stack + * + * @param classDescriptor descriptor of the class to type check against + * + * @return the method emitter + */ + public MethodEmitter _instanceof(final String classDescriptor) { + debug("instanceof", classDescriptor); + popType(Type.OBJECT); + method.visitTypeInsn(INSTANCEOF, classDescriptor); + pushType(Type.INT); + return this; + } + + /** + * Pop an object from the stack and perform an instanceof + * operation, given a classDescriptor to compare it to. + * Push the boolean result 1/0 as an int to the stack + * + * @param clazz the type to check instanceof against + * + * @return the method emitter + */ + public MethodEmitter _instanceof(final Class<?> clazz) { + return _instanceof(CompilerConstants.className(clazz)); + } + + /** + * Perform a checkcast operation on the object at the top of the + * stack. + * + * @param classDescriptor descriptor of the class to type check against + * + * @return the method emitter + */ + public MethodEmitter checkcast(final String classDescriptor) { + debug("checkcast", classDescriptor); + assert peekType().isObject(); + method.visitTypeInsn(CHECKCAST, classDescriptor); + return this; + } + + /** + * Perform a checkcast operation on the object at the top of the + * stack. + * + * @param clazz class to checkcast against + * + * @return the method emitter + */ + public MethodEmitter checkcast(final Class<?> clazz) { + return checkcast(CompilerConstants.className(clazz)); + } + + /** + * Instantiate a new array given a length that is popped + * from the stack and the array type + * + * @param arrayType the type of the array + * + * @return the method emitter + */ + public MethodEmitter newarray(final ArrayType arrayType) { + debug("newarray ", "arrayType=" + arrayType); + popType(Type.INT); //LENGTH + pushType(arrayType.newarray(method)); + return this; + } + + /** + * Instantiate a multidimensional array with a given number of dimensions. + * On the stack are dim lengths of the sub arrays. + * + * @param arrayType type of the array + * @param dims number of dimensions + * + * @return the method emitter + */ + public MethodEmitter multinewarray(final ArrayType arrayType, final int dims) { + debug("multianewarray ", arrayType, dims); + for (int i = 0; i < dims; i++) { + popType(Type.INT); //LENGTH + } + pushType(arrayType.newarray(method, dims)); + return this; + } + + /** + * Helper function to pop and type check the appropriate arguments + * from the stack given a method signature + * + * @param signature method signature + * + * @return return type of method + */ + private Type fixParamStack(final String signature) { + final Type[] params = Type.getMethodArguments(signature); + for (int i = params.length - 1; i >= 0; i--) { + popType(params[i]); + } + final Type returnType = Type.getMethodReturnType(signature); + return returnType; + } + + /** + * Generate an invocation to a Call structure + * @see CompilerConstants + * + * @param call the call object + * + * @return the method emitter + */ + public MethodEmitter invoke(final Call call) { + return call.invoke(this); + } + + private MethodEmitter invoke(final int opcode, final String className, final String methodName, final String methodDescriptor, final boolean hasReceiver) { + final Type returnType = fixParamStack(methodDescriptor); + + if (hasReceiver) { + popType(Type.OBJECT); + } + + method.visitMethodInsn(opcode, className, methodName, methodDescriptor); + + if (returnType != null) { + pushType(returnType); + } + + return this; + } + + /** + * Pop receiver from stack, perform an invoke special + * + * @param className class name + * @param methodName method name + * @param methodDescriptor descriptor + * + * @return the method emitter + */ + public MethodEmitter invokeSpecial(final String className, final String methodName, final String methodDescriptor) { + debug("invokespecial", className + "." + methodName + methodDescriptor); + return invoke(INVOKESPECIAL, className, methodName, methodDescriptor, true); + } + + /** + * Pop receiver from stack, perform an invoke virtual, push return value if any + * + * @param className class name + * @param methodName method name + * @param methodDescriptor descriptor + * + * @return the method emitter + */ + public MethodEmitter invokeVirtual(final String className, final String methodName, final String methodDescriptor) { + debug("invokevirtual", className + "." + methodName + methodDescriptor + " " + stack); + return invoke(INVOKEVIRTUAL, className, methodName, methodDescriptor, true); + } + + /** + * Perform an invoke static and push the return value if any + * + * @param className class name + * @param methodName method name + * @param methodDescriptor descriptor + * + * @return the method emitter + */ + public MethodEmitter invokeStatic(final String className, final String methodName, final String methodDescriptor) { + debug("invokestatic", className + "." + methodName + methodDescriptor); + invoke(INVOKESTATIC, className, methodName, methodDescriptor, false); + return this; + } + + /** + * Perform an invoke static and replace the return type if we know better, e.g. Global.allocate + * that allocates an array should return an ObjectArray type as a NativeArray counts as that + * + * @param className class name + * @param methodName method name + * @param methodDescriptor descriptor + * @param returnType return type override + * + * @return the method emitter + */ + public MethodEmitter invokeStatic(final String className, final String methodName, final String methodDescriptor, final Type returnType) { + invokeStatic(className, methodName, methodDescriptor); + popType(); + pushType(returnType); + return this; + } + + /** + * Pop receiver from stack, perform an invoke interface and push return value if any + * + * @param className class name + * @param methodName method name + * @param methodDescriptor descriptor + * + * @return the method emitter + */ + public MethodEmitter invokeInterface(final String className, final String methodName, final String methodDescriptor) { + debug("invokeinterface", className + "." + methodName + methodDescriptor); + return invoke(INVOKEINTERFACE, className, methodName, methodDescriptor, true); + } + + /** + * Generate a lookup switch, popping the switch value from the stack + * + * @param defaultLabel default label + * @param values case values for the table + * @param table default label + */ + public void lookupSwitch(final Label defaultLabel, final int[] values, final Label[] table) { + debug("lookupswitch", peekType()); + popType(Type.INT); + method.visitLookupSwitchInsn(defaultLabel, values, table); + } + + /** + * Generate a table switch + * @param lo low value + * @param hi high value + * @param defaultLabel default label + * @param table label table + */ + public void tableSwitch(final int lo, final int hi, final Label defaultLabel, final Label[] table) { + debug("tableswitch", peekType()); + popType(Type.INT); + method.visitTableSwitchInsn(lo, hi, defaultLabel, table); + } + + /** + * Abstraction for performing a conditional jump of any type + * + * @see MethodEmitter.Condition + * + * @param cond the condition to test + * @param trueLabel the destination label is condition is true + */ + void conditionalJump(final Condition cond, final Label trueLabel) { + conditionalJump(cond, cond != Condition.GT && cond != Condition.GE, trueLabel); + } + + /** + * Abstraction for performing a conditional jump of any type, + * including a dcmpg/dcmpl semantic for doubles. + * + * @param cond the condition to test + * @param isCmpG is this a dcmpg for numbers, false if it's a dcmpl + * @param trueLabel the destination label if condition is true + */ + void conditionalJump(final Condition cond, final boolean isCmpG, final Label trueLabel) { + if (peekType().isCategory2()) { + debug("[ld]cmp isCmpG=" + isCmpG); + pushType(get2n().cmp(method, isCmpG)); + jump(Condition.toUnary(cond), trueLabel, 1); + } else { + debug("if" + cond); + jump(Condition.toBinary(cond, peekType().isObject()), trueLabel, 2); + } + } + + /** + * Perform a non void return, popping the type from the stack + * + * @param type the type for the return + */ + public void _return(final Type type) { + debug("return", type); + assert stack.size() == 1 : "Only return value on stack allowed at return point - depth=" + stack.size() + " stack = " + stack; + final Type stackType = peekType(); + if (!Type.areEquivalent(type, stackType)) { + convert(type); + } + popType(type)._return(method); + stack = null; + } + + /** + * Perform a return using the stack top value as the guide for the type + */ + public void _return() { + _return(peekType()); + } + + /** + * Perform a void return. + */ + public void returnVoid() { + debug("return [void]"); + assert stack.isEmpty() : stack; + method.visitInsn(RETURN); + stack = null; + } + + /** + * Goto, possibly when splitting is taking place. If + * a splitNode exists, we need to handle the case that the + * jump target is another method + * + * @param label destination label + */ + public void splitAwareGoto(final Label label) { + + if (splitNode != null) { + final int index = splitNode.getExternalTargets().indexOf(label); + + if (index > -1) { + loadScope(); + checkcast(Scope.class); + load(index + 1); + invoke(Scope.SET_SPLIT_STATE); + loadUndefined(Type.OBJECT); + _return(functionNode.getReturnType()); + return; + } + } + + _goto(label); + } + + /** + * Perform a comparison of two number types that are popped from the stack + * + * @param isCmpG is this a dcmpg semantic, false if it's a dcmpl semantic + * + * @return the method emitter + */ + public MethodEmitter cmp(final boolean isCmpG) { + pushType(get2n().cmp(method, isCmpG)); + return this; + } + + /** + * Helper function for jumps, conditional or not + * @param opcode opcode for jump + * @param label destination + * @param n elements on stack to compare, 0-2 + */ + private void jump(final int opcode, final Label label, final int n) { + for (int i = 0; i < n; i++) { + assert peekType().isInteger() || peekType().isBoolean() || peekType().isObject() : "expecting integer type or object for jump, but found " + peekType(); + popType(); + } + mergeStackTo(label); + method.visitJumpInsn(opcode, label); + } + + /** + * Generate an if_acmpeq + * + * @param label label to true case + */ + public void if_acmpeq(final Label label) { + debug("if_acmpeq", label); + jump(IF_ACMPEQ, label, 2); + } + + /** + * Generate an if_acmpne + * + * @param label label to true case + */ + public void if_acmpne(final Label label) { + debug("if_acmpne", label); + jump(IF_ACMPNE, label, 2); + } + + /** + * Generate an ifnull + * + * @param label label to true case + */ + public void ifnull(final Label label) { + debug("ifnull", label); + jump(IFNULL, label, 1); + } + + /** + * Generate an ifnonnull + * + * @param label label to true case + */ + public void ifnonnull(final Label label) { + debug("ifnonnull", label); + jump(IFNONNULL, label, 1); + } + + /** + * Generate an ifeq + * + * @param label label to true case + */ + public void ifeq(final Label label) { + debug("ifeq ", label); + jump(IFEQ, label, 1); + } + + /** + * Generate an if_icmpeq + * + * @param label label to true case + */ + public void if_icmpeq(final Label label) { + debug("if_icmpeq", label); + jump(IF_ICMPEQ, label, 2); + } + + /** + * Generate an if_ne + * + * @param label label to true case + */ + + public void ifne(final Label label) { + debug("ifne", label); + jump(IFNE, label, 1); + } + + /** + * Generate an if_icmpne + * + * @param label label to true case + */ + public void if_icmpne(final Label label) { + debug("if_icmpne", label); + jump(IF_ICMPNE, label, 2); + } + + /** + * Generate an iflt + * + * @param label label to true case + */ + public void iflt(final Label label) { + debug("iflt", label); + jump(IFLT, label, 1); + } + + /** + * Generate an ifle + * + * @param label label to true case + */ + public void ifle(final Label label) { + debug("ifle", label); + jump(IFLE, label, 1); + } + + /** + * Generate an ifgt + * + * @param label label to true case + */ + public void ifgt(final Label label) { + debug("ifgt", label); + jump(IFGT, label, 1); + } + + /** + * Generate an ifge + * + * @param label label to true case + */ + public void ifge(final Label label) { + debug("ifge", label); + jump(IFGE, label, 1); + } + + /** + * Unconditional jump to a label + * + * @param label destination label + */ + public void _goto(final Label label) { + debug("goto", label); + jump(GOTO, label, 0); + stack = null; + } + + /** + * Examine two stacks and make sure they are of the same size and their + * contents are equivalent to each other + * @param s0 first stack + * @param s1 second stack + * + * @return true if stacks are equivalent, false otherwise + */ + private boolean stacksEquivalent(final ArrayDeque<Type> s0, final ArrayDeque<Type> s1) { + if (s0.size() != s1.size()) { + debug("different stack sizes", s0, s1); + return false; + } + + final Type[] s0a = s0.toArray(new Type[s0.size()]); + final Type[] s1a = s1.toArray(new Type[s1.size()]); + for (int i = 0; i < s0.size(); i++) { + if (!s0a[i].isEquivalentTo(s1a[i])) { + debug("different stack element", s0a[i], s1a[i]); + return false; + } + } + + return true; + } + + /** + * A join in control flow - helper function that makes sure all entry stacks + * discovered for the join point so far are equivalent + * @param label + */ + private void mergeStackTo(final Label label) { + final ArrayDeque<Type> labelStack = label.getStack(); + //debug(labelStack == null ? " >> Control flow - first visit " + label : " >> Control flow - JOIN with " + labelStack + " at " + label); + if (labelStack == null) { + assert stack != null; + label.setStack(stack.clone()); + return; + } + assert stacksEquivalent(stack, labelStack); + } + + /** + * Register a new label, enter it here. + * + * @param label the label + */ + public void label(final Label label) { + /* + * If stack == null, this means that we came here not through a fallthrough. + * E.g. a label after an athrow. Then we create a new stack if one doesn't exist + * for this location already. + */ + if (stack == null) { + stack = label.getStack(); + if (stack == null) { + stack = new ArrayDeque<>(); //we don't have a stack at this point. + } + } + debug_label(label); + + mergeStackTo(label); //we have to merge our stack to whatever is in the label + + method.visitLabel(label); + } + + /** + * Pop element from stack, convert to given type + * + * @param to type to convert to + * + * @return the method emitter + */ + public MethodEmitter convert(final Type to) { + final Type type = peekType().convert(method, to); + if (type != null) { + if (peekType() != to) { + debug("convert", peekType(), "->", to); + } + popType(); + pushType(type); + } + return this; + } + + /** + * Helper function - expect two types that are equivalent + * + * @return common type + */ + private Type get2() { + final Type p0 = popType(); + final Type p1 = popType(); + assert p0.isEquivalentTo(p1) : "expecting equivalent types on stack but got " + p0 + " and " + p1; + return p0; + } + + /** + * Helper function - expect two types that are integer types and equivalent + * + * @return common type + */ + private BitwiseType get2i() { + final BitwiseType p0 = popInteger(); + final BitwiseType p1 = popInteger(); + assert p0.isEquivalentTo(p1) : "expecting equivalent types on stack but got " + p0 + " and " + p1; + return p0; + } + + /** + * Helper function - expect two types that are numbers and equivalent + * + * @return common type + */ + private NumericType get2n() { + final NumericType p0 = popNumeric(); + final NumericType p1 = popNumeric(); + assert p0.isEquivalentTo(p1) : "expecting equivalent types on stack but got " + p0 + " and " + p1; + return p0; + } + + /** + * Pop two numbers, perform addition and push result + * + * @return the method emitter + */ + public MethodEmitter add() { + debug("add"); + pushType(get2().add(method)); + return this; + } + + /** + * Pop two numbers, perform subtraction and push result + * + * @return the method emitter + */ + public MethodEmitter sub() { + debug("sub"); + pushType(get2n().sub(method)); + return this; + } + + /** + * Pop two numbers, perform multiplication and push result + * + * @return the method emitter + */ + public MethodEmitter mul() { + debug("mul "); + pushType(get2n().mul(method)); + return this; + } + + /** + * Pop two numbers, perform division and push result + * + * @return the method emitter + */ + public MethodEmitter div() { + debug("div"); + pushType(get2n().div(method)); + return this; + } + + /** + * Pop two numbers, calculate remainder and push result + * + * @return the method emitter + */ + public MethodEmitter rem() { + debug("rem"); + pushType(get2n().rem(method)); + return this; + } + + /** + * Retrieve the top <tt>count</tt> types on the stack without modifying it. + * + * @param count number of types to return + * @return array of Types + */ + protected Type[] getTypesFromStack(final int count) { + final Iterator<Type> iter = stack.iterator(); + final Type[] types = new Type[count]; + + for (int i = count - 1; i >= 0; i--) { + types[i] = iter.next(); + } + + return types; + } + + /** + * Helper function to generate a function signature based on stack contents + * and argument count and return type + * + * @param returnType return type + * @param argCount argument count + * + * @return function signature for stack contents + */ + private String getDynamicSignature(final Type returnType, final int argCount, final boolean hasSelf) { + final Iterator<Type> iter = stack.iterator(); + final Type[] argTypes = new Type[argCount]; + + for (int i = argCount - 1; i >= 0; i--) { + argTypes[i] = iter.next(); + } + + final FunctionSignature sig = new FunctionSignature(hasSelf, false, returnType, argTypes); + for (int i = 0; i < argCount; i++) { + popType(argTypes[argCount - i - 1]); + } + for (int i = 0 ; i < sig.size() - argTypes.length; i++) { + popType(Type.OBJECT); + } + + return sig.toString(); + } + + /** + * Generate a dynamic new + * + * @param argCount number of arguments + * @param flags callsite flags + * + * @return the method emitter + */ + public MethodEmitter dynamicNew(final int argCount, final int flags) { + debug("dynamic_new", "argcount=" + argCount); + final String signature = getDynamicSignature(Type.OBJECT, argCount, true); + method.visitInvokeDynamicInsn("dyn:new", signature, LINKERBOOTSTRAP, flags); + pushType(Type.OBJECT); //TODO fix result type + return this; + } + + /** + * Generate a dynamic call + * + * @param returnType return type + * @param argCount number of arguments + * @param flags callsite flags + * + * @return the method emitter + */ + public MethodEmitter dynamicCall(final Type returnType, final int argCount, final int flags) { + debug("dynamic_call", "args=" + argCount, "returnType=" + returnType); + final String signature = getDynamicSignature(returnType, argCount + 1, true); + debug(" signature", signature); + method.visitInvokeDynamicInsn("dyn:call", signature, LINKERBOOTSTRAP, flags); + pushType(returnType); + + return this; + } + + /** + * Generate a dynamic call for a runtime node + * + * @param name tag for the invoke dynamic for this runtime node + * @param returnType return type + * @param request RuntimeNode request + * + * @return the method emitter + */ + public MethodEmitter dynamicRuntimeCall(final String name, final Type returnType, final RuntimeNode.Request request) { + debug("dynamic_runtime_call", name, "args=" + request.getArity(), "returnType=" + returnType); + final String signature = getDynamicSignature(returnType, request.getArity(), false); + debug(" signature", signature); + method.visitInvokeDynamicInsn(name, signature, RUNTIMEBOOTSTRAP); + pushType(returnType); + + return this; + } + + /** + * Generate dynamic getter. Pop scope from stack. Push result + * + * @param valueType type of the value to set + * @param name name of property + * @param flags call site flags + * @param isMethod should it prefer retrieving methods + * + * @return the method emitter + */ + public MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod) { + debug("dynamic_get", name, valueType); + + Type type = valueType; + if (type.isObject() || type.isBoolean()) { + type = Type.OBJECT; //promote e.g strings to object generic setter + } + + popType(Type.SCOPE); + method.visitInvokeDynamicInsn((isMethod ? "dyn:getMethod|getProp|getElem:" : "dyn:getProp|getElem|getMethod:") + + NameCodec.encode(name), Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags); + + pushType(type); + + convert(valueType); //most probably a nop + + return this; + } + + /** + * Generate dynamic setter. Pop receiver and property from stack. + * + * @param valueType the type of the value to set + * @param name name of property + * @param flags call site flags + */ + public void dynamicSet(final Type valueType, final String name, final int flags) { + debug("dynamic_set", name, peekType()); + + Type type = valueType; + if (type.isObject() || type.isBoolean()) { //promote strings to objects etc + type = Type.OBJECT; + convert(Type.OBJECT); //TODO bad- until we specialize boolean setters, + } + + popType(type); + popType(Type.SCOPE); + + method.visitInvokeDynamicInsn("dyn:setProp|setElem:" + NameCodec.encode(name), methodDescriptor(void.class, Object.class, type.getTypeClass()), LINKERBOOTSTRAP, flags); + } + + /** + * Dynamic getter for indexed structures. Pop index and receiver from stack, + * generate appropriate signatures based on types + * + * @param result result type for getter + * @param flags call site flags for getter + * @param isMethod should it prefer retrieving methods + * + * @return the method emitter + */ + public MethodEmitter dynamicGetIndex(final Type result, final int flags, final boolean isMethod) { + debug("dynamic_get_index", peekType(1) + "[" + peekType() + "]"); + + Type resultType = result; + if (result.isBoolean()) { + resultType = Type.OBJECT; // INT->OBJECT to avoid another dimension of cross products in the getters. TODO + } + + Type index = peekType(); + if (index.isObject() || index.isBoolean()) { + index = Type.OBJECT; //e.g. string->object + convert(Type.OBJECT); + } + popType(); + + popType(Type.OBJECT); + + final String signature = Type.getMethodDescriptor(resultType, Type.OBJECT /*e.g STRING->OBJECT*/, index); + + method.visitInvokeDynamicInsn(isMethod ? "dyn:getMethod|getElem|getProp" : "dyn:getElem|getProp|getMethod", + signature, LINKERBOOTSTRAP, flags); + pushType(resultType); + + if (result.isBoolean()) { + convert(Type.BOOLEAN); + } + + return this; + } + + /** + * Dynamic setter for indexed structures. Pop value, index and receiver from + * stack, generate appropriate signature based on types + * + * @param flags call site flags for setter + */ + public void dynamicSetIndex(final int flags) { + debug("dynamic_set_index", peekType(2) + "[" + peekType(1) + "] =", peekType()); + + Type value = peekType(); + if (value.isObject() || value.isBoolean()) { + value = Type.OBJECT; //e.g. STRING->OBJECT - one descriptor for all object types + convert(Type.OBJECT); + } + popType(); + + Type index = peekType(); + if (index.isObject() || index.isBoolean()) { + index = Type.OBJECT; //e.g. string->object + convert(Type.OBJECT); + } + popType(index); + + final Type receiver = popType(Type.OBJECT); + assert receiver.isObject(); + + method.visitInvokeDynamicInsn("dyn:setElem|setProp", methodDescriptor(void.class, receiver.getTypeClass(), index.getTypeClass(), value.getTypeClass()), LINKERBOOTSTRAP, flags); + } + + /** + * Load a key value in the proper form. + * + * @param key + */ + //TODO move this and break it apart + MethodEmitter loadKey(final Object key) { + if (key instanceof IdentNode) { + method.visitLdcInsn(((IdentNode) key).getName()); + } else if (key instanceof LiteralNode) { + method.visitLdcInsn(((LiteralNode<?>)key).getString()); + } else { + method.visitLdcInsn(JSType.toString(key)); + } + pushType(Type.OBJECT); //STRING + return this; + } + + @SuppressWarnings("fallthrough") + private static Type fieldType(final String desc) { + switch (desc) { + case "Z": + case "B": + case "C": + case "S": + case "I": + return Type.INT; + case "F": + assert false; + case "D": + return Type.NUMBER; + case "J": + return Type.LONG; + default: + assert desc.startsWith("[") || desc.startsWith("L") : desc + " is not an object type"; + switch (desc.charAt(0)) { + case 'L': + return Type.OBJECT; + case '[': + return Type.typeFor(Array.newInstance(fieldType(desc.substring(1)).getTypeClass(), 0).getClass()); + default: + assert false; + } + return Type.OBJECT; + } + } + + /** + * Generate get for a field access + * + * @param fa the field access + * + * @return the method emitter + */ + public MethodEmitter getField(final FieldAccess fa) { + return fa.get(this); + } + + /** + * Generate set for a field access + * + * @param fa the field access + */ + public void putField(final FieldAccess fa) { + fa.put(this); + } + + /** + * Get the value of a non-static field, pop the receiver from the stack, + * push value to the stack + * + * @param className class + * @param fieldName field name + * @param fieldDescriptor field descriptor + * + * @return the method emitter + */ + public MethodEmitter getField(final String className, final String fieldName, final String fieldDescriptor) { + debug("getfield", "receiver=" + peekType(), className + "." + fieldName + fieldDescriptor); + final Type receiver = popType(); + assert receiver.isObject(); + method.visitFieldInsn(GETFIELD, className, fieldName, fieldDescriptor); + pushType(fieldType(fieldDescriptor)); + return this; + } + + /** + * Get the value of a static field, push it to the stack + * + * @param className class + * @param fieldName field name + * @param fieldDescriptor field descriptor + * + * @return the method emitter + */ + public MethodEmitter getStatic(final String className, final String fieldName, final String fieldDescriptor) { + debug("getstatic", className + "." + fieldName + "." + fieldDescriptor); + method.visitFieldInsn(GETSTATIC, className, fieldName, fieldDescriptor); + pushType(fieldType(fieldDescriptor)); + return this; + } + + /** + * Pop value and field from stack and write to a non-static field + * + * @param className class + * @param fieldName field name + * @param fieldDescriptor field descriptor + */ + public void putField(final String className, final String fieldName, final String fieldDescriptor) { + debug("putfield", "receiver=" + peekType(1), "value=" + peekType()); + popType(fieldType(fieldDescriptor)); + popType(Type.OBJECT); + method.visitFieldInsn(PUTFIELD, className, fieldName, fieldDescriptor); + } + + /** + * Pop value from stack and write to a static field + * + * @param className class + * @param fieldName field name + * @param fieldDescriptor field descriptor + */ + public void putStatic(final String className, final String fieldName, final String fieldDescriptor) { + debug("putfield", "value=" + peekType()); + popType(fieldType(fieldDescriptor)); + method.visitFieldInsn(PUTSTATIC, className, fieldName, fieldDescriptor); + } + + /** + * Register line number at a label + * + * @param line line number + * @param label label + */ + public void lineNumber(final int line, final Label label) { + method.visitLineNumber(line, label); + } + + /* + * Debugging below + */ + + private final FieldAccess ERR_STREAM = staticField(System.class, "err", PrintStream.class); + private final Call PRINT = virtualCallNoLookup(PrintStream.class, "print", void.class, Object.class); + private final Call PRINTLN = virtualCallNoLookup(PrintStream.class, "println", void.class, Object.class); + private final Call PRINT_STACKTRACE = virtualCallNoLookup(Throwable.class, "printStackTrace", void.class); + + /** + * Emit a System.err.print statement of whatever is on top of the bytecode stack + */ + public void print() { + getField(ERR_STREAM); + swap(); + convert(Type.OBJECT); + invoke(PRINT); + } + + /** + * Emit a System.err.println statement of whatever is on top of the bytecode stack + */ + public void println() { + getField(ERR_STREAM); + swap(); + convert(Type.OBJECT); + invoke(PRINTLN); + } + + /** + * Emit a System.err.print statement + * @param string string to print + */ + public void print(final String string) { + getField(ERR_STREAM); + load(string); + invoke(PRINT); + } + + /** + * Emit a System.err.println statement + * @param string string to print + */ + public void println(final String string) { + getField(ERR_STREAM); + load(string); + invoke(PRINTLN); + } + + /** + * Print a stacktrace to S + */ + public void stacktrace() { + _new(Throwable.class); + dup(); + invoke(constructorNoLookup(Throwable.class)); + invoke(PRINT_STACKTRACE); + } + + private static int linePrefix = 0; + + /** + * Debug function that outputs generated bytecode and stack contents + * + * @param args debug information to print + */ + private void debug(final Object... args) { + debug(30, args); + } + + /** + * Debug function that outputs generated bytecode and stack contents + * for a label - indentation is currently the only thing that differs + * + * @param args debug information to print + */ + private void debug_label(final Object... args) { + debug(26, args); + } + + private void debug(final int padConstant, final Object... args) { + if (DEBUG) { + final StringBuilder sb = new StringBuilder(); + int pad; + + sb.append('#'); + sb.append(++linePrefix); + + pad = 5 - sb.length(); + while (pad > 0) { + sb.append(' '); + pad--; + } + + if (!stack.isEmpty()) { + sb.append("{"); + sb.append(stack.size()); + sb.append(":"); + for (final Iterator<Type> iter = stack.iterator(); iter.hasNext();) { + final Type t = iter.next(); + + if (t == Type.SCOPE) { + sb.append("scope"); + } else if (t == Type.THIS) { + sb.append("this"); + } else if (t.isObject()) { + String desc = t.getDescriptor(); + final int slash = desc.lastIndexOf('/'); + if (slash != -1) { + desc = desc.substring(slash + 1, desc.length() - 1); + } + if ("Object".equals(desc)) { + sb.append('O'); + } else { + sb.append(desc); + } + } else { + sb.append(t.getDescriptor()); + } + + if (iter.hasNext()) { + sb.append(' '); + } + } + sb.append('}'); + sb.append(' '); + } + + pad = padConstant - sb.length(); + while (pad > 0) { + sb.append(' '); + pad--; + } + + for (final Object arg : args) { + sb.append(arg); + sb.append(' '); + } + + if (context != null) { //early bootstrap code doesn't have inited context yet + LOG.info(sb.toString()); + if (DEBUG_TRACE_LINE == linePrefix) { + new Throwable().printStackTrace(LOG.getOutputStream()); + } + } + + } + } + + + /** + * Abstraction for labels, separating a label from the underlying + * byte code emitter. Also augmenting label with e.g. a name + * for easier debugging and reading code + * + * see -Dnashorn.codegen.debug, --log=codegen + */ + public static class Label extends jdk.internal.org.objectweb.asm.Label { + /** Name of this label */ + private final String name; + + /** Type stack at this label */ + private ArrayDeque<Type> stack; + + /** + * Constructor + * + * @param name name of this label + */ + public Label(final String name) { + super(); + this.name = name; + } + + /** + * Copy constructor + * + * @param label a label to clone + */ + public Label(final Label label) { + super(); + name = label.name; + } + + ArrayDeque<Type> getStack() { + return stack; + } + + void setStack(final ArrayDeque<Type> stack) { + this.stack = stack; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + String s = super.toString(); + s = s.substring(1, s.length()); + sb.append(name).append('_').append(Long.toHexString(Long.parseLong(s))); + + return sb.toString(); + } + } + + /** + * Condition enum used for all kinds of jumps, regardless of type + */ + static enum Condition { + EQ, + NE, + LE, + LT, + GE, + GT; + + public static Condition forRuntimeRequest(final RuntimeNode.Request request) { + try { + final String reqString = request.toString().replace("_STRICT", ""); + return Condition.valueOf(reqString); + } catch (final IllegalArgumentException e) { + return null; + } + } + + public static int toUnary(final Condition c) { + switch (c) { + case EQ: + return IFEQ; + case NE: + return IFNE; + case LE: + return IFLE; + case LT: + return IFLT; + case GE: + return IFGE; + case GT: + return IFGT; + default: + assert false; + return -1; + } + } + + public static int toBinary(final Condition c) { + return toBinary(c, false); + } + + public static int toBinary(final Condition c, final boolean isObject) { + switch (c) { + case EQ: + return isObject ? IF_ACMPEQ : IF_ICMPEQ; + case NE: + return isObject ? IF_ACMPNE : IF_ICMPNE; + case LE: + return IF_ICMPLE; + case LT: + return IF_ICMPLT; + case GE: + return IF_ICMPGE; + case GT: + return IF_ICMPGT; + default: + assert false; + return -1; + } + } + } + + /** + * Set the current function node being emitted + * @param functionNode the function node + */ + public void setFunctionNode(final FunctionNode functionNode) { + this.functionNode = functionNode; + } + + /** + * Get the split node for this method emitter, if this is code + * generation due to splitting large methods + * + * @return split node + */ + public SplitNode getSplitNode() { + return splitNode; + } + + /** + * Set the split node for this method emitter + * @param splitNode split node + */ + public void setSplitNode(final SplitNode splitNode) { + this.splitNode = splitNode; + } +} diff --git a/src/jdk/nashorn/internal/codegen/Namespace.java b/src/jdk/nashorn/internal/codegen/Namespace.java new file mode 100644 index 00000000..639dc7f3 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/Namespace.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import java.util.HashMap; + +/** + * A name space hierarchy, where each level holds a name directory with + * names that may be unique for each level. + */ + +public class Namespace { + /** Parent namespace. */ + private final Namespace parent; + + /** Name directory - version count for each name */ + private final HashMap<String, Integer> directory; + + /** + * Constructor + */ + public Namespace() { + this(null); + } + + /** + * Constructor + * + * @param parent parent name space + */ + public Namespace(final Namespace parent) { + this.parent = parent; + directory = new HashMap<>(); + } + + /** + * Return the parent Namespace of this space. + * + * @return parent name space + */ + public Namespace getParent() { + return parent; + } + + private HashMap<String, Integer> getDirectory() { + return directory; + } + + /** + * Create a uniqueName name in the namespace in the form base$n where n varies + * . + * @param base Base of name. Base will be returned if uniqueName. + * + * @return Generated uniqueName name. + */ + public String uniqueName(final String base) { + for (Namespace namespace = this; namespace != null; namespace = namespace.getParent()) { + final HashMap<String, Integer> namespaceDirectory = namespace.getDirectory(); + final Integer counter = namespaceDirectory.get(base); + + if (counter != null) { + final int count = counter + 1; + namespaceDirectory.put(base, count); + + return base + "$" + count; + } + } + + directory.put(base, 0); + + return base; + } + + @Override + public String toString() { + return directory.toString(); + } +} diff --git a/src/jdk/nashorn/internal/codegen/RuntimeCallSite.java b/src/jdk/nashorn/internal/codegen/RuntimeCallSite.java new file mode 100644 index 00000000..921c1f66 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/RuntimeCallSite.java @@ -0,0 +1,701 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; +import static jdk.nashorn.internal.codegen.types.Type.BOOLEAN; +import static jdk.nashorn.internal.codegen.types.Type.INT; +import static jdk.nashorn.internal.runtime.linker.Lookup.MH; + +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.MutableCallSite; +import java.util.HashMap; +import java.util.Map; +import jdk.nashorn.internal.codegen.CompilerConstants.Call; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.RuntimeNode; +import jdk.nashorn.internal.ir.RuntimeNode.Request; +import jdk.nashorn.internal.runtime.ScriptRuntime; +import jdk.nashorn.internal.runtime.linker.Lookup; +import jdk.nashorn.internal.runtime.linker.MethodHandleFactory; + +/** + * Optimistic call site that assumes its Object arguments to be of a boxed type. + * Gradually reverts to wider boxed types if the assumption for the RuntimeNode + * is proven wrong. Finally reverts to the generic ScriptRuntime method. + * + * This is used from the CodeGenerator when we have a runtime node, but 1 or more + * primitive arguments. This class generated appropriate specializations, for example + * {@code Object a === int b} is a good idea to specialize to {@code ((Integer)a).intValue() == b} + * surrounded by catch blocks that will try less narrow specializations + */ +public class RuntimeCallSite extends MutableCallSite { + static final Call BOOTSTRAP = staticCallNoLookup(RuntimeCallSite.class, "bootstrap", CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class); + + private static final MethodHandle NEXT = findOwnMH("next", MethodHandle.class); + + private final RuntimeNode.Request request; + + private String name; + + /** + * A specialized runtime node, i.e. on where we know at least one more specific type than object + */ + static final class SpecializedRuntimeNode { + private static final char REQUEST_SEPARATOR = ':'; + + private final RuntimeNode.Request request; + + private final Type[] parameterTypes; + + private final Type returnType; + + /** + * Constructor. + * + * @param request runtime node request to specialize + * @param parameterTypes parameter types of the call site + * @param returnType return type of the call site + */ + SpecializedRuntimeNode(final RuntimeNode.Request request, final Type[] parameterTypes, final Type returnType) { + this.request = request; + this.parameterTypes = parameterTypes; + this.returnType = returnType; + } + + /** + * The first type to try to use for this genrated runtime node + * + * @return a type + */ + public Type firstTypeGuess() { + Type widest = Type.UNKNOWN; + for (final Type type : parameterTypes) { + if (type.isObject()) { + continue; + } + widest = Type.widest(type, widest); + } + widest = Type.widest(widest, firstTypeGuessForObject(request)); + + return widest; + } + + private static Type firstTypeGuessForObject(final Request request) { + switch (request) { + case ADD: + return INT; + default: + return BOOLEAN; + } + } + + Request getRequest() { + return request; + } + + Type[] getParameterTypes() { + return parameterTypes; + } + + Type getReturnType() { + return returnType; + } + + private static char descFor(final Type type) { + if (type.isObject()) { + return 'O'; + } + return type.getDescriptor().charAt(0); + } + + @Override + public boolean equals(final Object other) { + if (other instanceof SpecializedRuntimeNode) { + final SpecializedRuntimeNode otherNode = (SpecializedRuntimeNode)other; + + if (!otherNode.getReturnType().equals(getReturnType())) { + return false; + } + + if (getParameterTypes().length != otherNode.getParameterTypes().length) { + return false; + } + + for (int i = 0; i < getParameterTypes().length; i++) { + if (!Type.areEquivalent(getParameterTypes()[i], otherNode.getParameterTypes()[i])) { + return false; + } + } + + return otherNode.getRequest().equals(getRequest()); + } + + return false; + } + + @Override + public int hashCode() { + int hashCode = getRequest().toString().hashCode(); + hashCode ^= getReturnType().hashCode(); + for (final Type type : getParameterTypes()) { + hashCode ^= type.hashCode(); + } + return hashCode; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append(getRequest().toString()); + sb.append(REQUEST_SEPARATOR); + sb.append(descFor(getReturnType())); + + for (final Type type : getParameterTypes()) { + sb.append(descFor(type)); + } + + return sb.toString(); + } + + String getName(final Type extraType) { + return toString() + "_" + descFor(extraType); + } + + String getInitialName() { + return getName(firstTypeGuess()); + } + } + + + /** + * Constructor + * + * @param type method type for call site + * @param name name of runtime call + */ + RuntimeCallSite(final MethodType type, final String name) { + super(type); + this.name = name; + this.request = Request.valueOf(name.substring(0, name.indexOf(SpecializedRuntimeNode.REQUEST_SEPARATOR))); + setTarget(makeMethod(name)); + } + + /** + * Bootstrapper for a specialized Runtime call + * + * @param lookup lookup + * @param initialName initial name for callsite + * @param type method type for call site + * + * @return callsite for a runtime node + */ + public static CallSite bootstrap(final MethodHandles.Lookup lookup, final String initialName, final MethodType type) { + return new RuntimeCallSite(type, initialName); + } + + private String nextName(final String requestName) { + if (requestName.equals(request.toString())) { + return null; + } + + final char[] c = requestName.toCharArray(); + final int last = c.length - 1; + + if (c[last - 1] != '_') { + return null; + } + + switch (c[last]) { + case 'Z': + c[last] = 'I'; + break; + case 'I': + c[last] = 'J'; + break; + case 'J': + c[last] = 'D'; + break; + case 'D': + default: + return request.toString(); + } + + return new String(c); + } + + private boolean isSpecialized(final String requestName) { + return nextName(requestName) != null; + } + + private MethodHandle makeMethod(final String requestName) { + MethodHandle mh; + + if (isSpecialized(requestName)) { + final Class<?> boxedType; + final Class<?> primitiveType; + + switch (requestName.charAt(requestName.length() - 1)) { + case 'Z': + boxedType = Boolean.class; + primitiveType = int.class; + break; + case 'I': + boxedType = Integer.class; + primitiveType = int.class; + break; + case 'J': + boxedType = Long.class; + primitiveType = long.class; + break; + case 'D': + boxedType = Number.class; + primitiveType = double.class; + break; + default: + throw new RuntimeException("should not reach here"); + } + + final boolean isStrictCmp = (request == Request.EQ_STRICT || request == Request.NE_STRICT); + + if (isStrictCmp && + (boxedType != Boolean.class && + (type().parameterType(0) == boolean.class || + type().parameterType(1) == boolean.class))) { + // number and boolean are never strictly equal, e.g. 0 !== false + mh = MH.dropArguments(MH.constant(boolean.class, request == Request.NE_STRICT), 0, type().parameterArray()); + } else { + mh = METHODS.get(request.name().replace("_STRICT", "") + primitiveType.getSimpleName()); + // unbox objects + + for (int i = 0; i < type().parameterCount(); i++) { + if (!type().parameterType(i).isPrimitive()) { + mh = MH.filterArguments(mh, i, UNBOX.get(boxedType)); + } + } + + mh = Lookup.filterReturnType(mh, type().returnType()); + mh = MH.explicitCastArguments(mh, type()); + } + + final MethodHandle fallback = MH.foldArguments(MethodHandles.exactInvoker(type()), MH.bindTo(NEXT, this)); + + MethodHandle guard; + if (type().parameterType(0).isPrimitive()) { + guard = MH.insertArguments( + MH.dropArguments(CHECKCAST, 1, type().parameterType(0)), 0, boxedType); + } else if (type().parameterType(1).isPrimitive()) { + guard = MH.insertArguments( + MH.dropArguments(CHECKCAST, 2, type().parameterType(1)), 0, boxedType); + } else { + assert !type().parameterType(0).isPrimitive() && !type().parameterType(1).isPrimitive(); + guard = MH.insertArguments(CHECKCAST2, 0, boxedType); + } + + if (request == Request.ADD && boxedType == Integer.class) { + // int add needs additional overflow check + MethodHandle addcheck = ADDCHECK; + for (int i = 0; i < type().parameterCount(); i++) { + if (!type().parameterType(i).isPrimitive()) { + addcheck = MH.filterArguments(addcheck, i, UNBOX.get(boxedType)); + } + } + addcheck = MH.explicitCastArguments(addcheck, type().changeReturnType(boolean.class)); + guard = MH.guardWithTest(guard, addcheck, + MH.dropArguments(MH.constant(boolean.class, false), 0, type().parameterArray())); + } + + return MH.guardWithTest(guard, mh, fallback); + } + + // generic fallback + return MH.explicitCastArguments(Lookup.filterReturnType(GENERIC_METHODS.get(request.name()), type().returnType()), type()); + } + + /** + * This is public just so that the generated specialization code can + * use it to get the next wider typed method + * + * Do not call directly + * + * @return next wider specialization method for this RuntimeCallSite + */ + public MethodHandle next() { + this.name = nextName(name); + final MethodHandle next = makeMethod(name); + setTarget(next); + return next; + } + + @Override + public String toString() { + return super.toString() + " " + name; + } + + /** Method cache */ + private static final Map<String, MethodHandle> METHODS; + + /** Generic method cache */ + private static final Map<String, MethodHandle> GENERIC_METHODS; + + /** Unbox cache */ + private static final Map<Class<?>, MethodHandle> UNBOX; + + private static final MethodHandle CHECKCAST = findOwnMH("checkcast", boolean.class, Class.class, Object.class); + private static final MethodHandle CHECKCAST2 = findOwnMH("checkcast", boolean.class, Class.class, Object.class, Object.class); + private static final MethodHandle ADDCHECK = findOwnMH("ADDcheck", boolean.class, int.class, int.class); + + /** + * Build maps of correct boxing operations + */ + static { + UNBOX = new HashMap<>(); + UNBOX.put(Boolean.class, findOwnMH("unboxZ", int.class, Object.class)); + UNBOX.put(Integer.class, findOwnMH("unboxI", int.class, Object.class)); + UNBOX.put(Long.class, findOwnMH("unboxJ", long.class, Object.class)); + UNBOX.put(Number.class, findOwnMH("unboxD", double.class, Object.class)); + + METHODS = new HashMap<>(); + + for (final Request req : Request.values()) { + if (req.canSpecialize()) { + if (req.name().endsWith("_STRICT")) { + continue; + } + + final boolean isCmp = Request.isComparison(req); + + METHODS.put(req.name() + "int", findOwnMH(req.name(), (isCmp ? boolean.class : int.class), int.class, int.class)); + METHODS.put(req.name() + "long", findOwnMH(req.name(), (isCmp ? boolean.class : long.class), long.class, long.class)); + METHODS.put(req.name() + "double", findOwnMH(req.name(), (isCmp ? boolean.class : double.class), double.class, double.class)); + } + } + + GENERIC_METHODS = new HashMap<>(); + for (final Request req : Request.values()) { + if (req.canSpecialize()) { + GENERIC_METHODS.put(req.name(), MH.findStatic(MethodHandles.lookup(), ScriptRuntime.class, req.name(), + MH.type(req.getReturnType().getTypeClass(), Object.class, Object.class))); + } + } + } + + /** + * Specialized version of != operator for two int arguments. Do not call directly. + * @param a int + * @param b int + * @return a != b + */ + public static boolean NE(final int a, final int b) { + return a != b; + } + + /** + * Specialized version of != operator for two double arguments. Do not call directly. + * @param a double + * @param b double + * @return a != b + */ + public static boolean NE(final double a, final double b) { + return a != b; + } + + /** + * Specialized version of != operator for two long arguments. Do not call directly. + * @param a long + * @param b long + * @return a != b + */ + public static boolean NE(final long a, final long b) { + return a != b; + } + + /** + * Specialized version of == operator for two int arguments. Do not call directly. + * @param a int + * @param b int + * @return a == b + */ + public static boolean EQ(final int a, final int b) { + return a == b; + } + + /** + * Specialized version of == operator for two double arguments. Do not call directly. + * @param a double + * @param b double + * @return a == b + */ + public static boolean EQ(final double a, final double b) { + return a == b; + } + + /** + * Specialized version of == operator for two long arguments. Do not call directly. + * @param a long + * @param b long + * @return a == b + */ + public static boolean EQ(final long a, final long b) { + return a == b; + } + + /** + * Specialized version of < operator for two int arguments. Do not call directly. + * @param a int + * @param b int + * @return a < b + */ + public static boolean LT(final int a, final int b) { + return a < b; + } + + /** + * Specialized version of < operator for two double arguments. Do not call directly. + * @param a double + * @param b double + * @return a < b + */ + public static boolean LT(final double a, final double b) { + return a < b; + } + + /** + * Specialized version of < operator for two long arguments. Do not call directly. + * @param a long + * @param b long + * @return a < b + */ + public static boolean LT(final long a, final long b) { + return a < b; + } + + /** + * Specialized version of <= operator for two int arguments. Do not call directly. + * @param a int + * @param b int + * @return a <= b + */ + public static boolean LE(final int a, final int b) { + return a <= b; + } + + /** + * Specialized version of <= operator for two double arguments. Do not call directly. + * @param a double + * @param b double + * @return a <= b + */ + public static boolean LE(final double a, final double b) { + return a <= b; + } + + /** + * Specialized version of <= operator for two long arguments. Do not call directly. + * @param a long + * @param b long + * @return a <= b + */ + public static boolean LE(final long a, final long b) { + return a <= b; + } + + /** + * Specialized version of > operator for two int arguments. Do not call directly. + * @param a int + * @param b int + * @return a > b + */ + public static boolean GT(final int a, final int b) { + return a > b; + } + + /** + * Specialized version of > operator for two double arguments. Do not call directly. + * @param a double + * @param b double + * @return a > b + */ + public static boolean GT(final double a, final double b) { + return a > b; + } + + /** + * Specialized version of > operator for two long arguments. Do not call directly. + * @param a long + * @param b long + * @return a > b + */ + public static boolean GT(final long a, final long b) { + return a > b; + } + + /** + * Specialized version of >= operator for two int arguments. Do not call directly. + * @param a int + * @param b int + * @return a >= b + */ + public static boolean GE(final int a, final int b) { + return a >= b; + } + + /** + * Specialized version of >= operator for two double arguments. Do not call directly. + * @param a double + * @param b double + * @return a >= b + */ + public static boolean GE(final double a, final double b) { + return a >= b; + } + + /** + * Specialized version of >= operator for two long arguments. Do not call directly. + * @param a long + * @param b long + * @return a >= b + */ + public static boolean GE(final long a, final long b) { + return a >= b; + } + + /** + * Specialized version of + operator for two int arguments. Do not call directly. + * @param a int + * @param b int + * @return a + b + */ + public static int ADD(final int a, final int b) { + return a + b; + } + + /** + * Specialized version of + operator for two long arguments. Do not call directly. + * @param a long + * @param b long + * @return a + b + */ + public static long ADD(final long a, final long b) { + return a + b; + } + + /** + * Specialized version of + operator for two double arguments. Do not call directly. + * @param a double + * @param b double + * @return a + b + */ + public static double ADD(final double a, final double b) { + return a + b; + } + + /** + * Check that ints are addition compatible, i.e. their sum is equal to the sum + * of them cast to long. Otherwise the addition will overflow. Do not call directly. + * + * @param a int + * @param b int + * + * @return true if addition does not overflow + */ + public static boolean ADDcheck(final int a, final int b) { + return (a + b == (long)a + (long)b); + } + + /** + * Checkcast used for specialized ops. Do not call directly + * + * @param type to to check against + * @param obj object to check for type + * + * @return true if type check holds + */ + public static boolean checkcast(final Class<?> type, final Object obj) { + return type.isInstance(obj); + } + + /** + * Checkcast used for specialized ops. Do not call directly + * + * @param type type to check against + * @param objA first object to check against type + * @param objB second object to check against type + * + * @return true if type check holds for both objects + */ + public static boolean checkcast(final Class<?> type, final Object objA, final Object objB) { + return type.isInstance(objA) && type.isInstance(objB); + } + + /** + * Unbox a java.lang.Boolean. Do not call directly + * @param obj object to cast to int and unbox + * @return an int value for the boolean, 1 is true, 0 is false + */ + public static int unboxZ(final Object obj) { + return (boolean)obj ? 1 : 0; + } + + /** + * Unbox a java.lang.Integer. Do not call directly + * @param obj object to cast to int and unbox + * @return an int + */ + public static int unboxI(final Object obj) { + return (int)obj; + } + + /** + * Unbox a java.lang.Long. Do not call directly + * @param obj object to cast to long and unbox + * @return a long + */ + public static long unboxJ(final Object obj) { + return (long)obj; + } + + /** + * Unbox a java.lang.Number. Do not call directly + * @param obj object to cast to Number and unbox + * @return a double + */ + public static double unboxD(final Object obj) { + return ((Number)obj).doubleValue(); + } + + private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) { + try { + return MH.findStatic(MethodHandles.lookup(), RuntimeCallSite.class, name, MH.type(rtype, types)); + } catch (final MethodHandleFactory.LookupException e) { + return MH.findVirtual(MethodHandles.lookup(), RuntimeCallSite.class, name, MH.type(rtype, types)); + } + } + +} diff --git a/src/jdk/nashorn/internal/codegen/SharedScopeCall.java b/src/jdk/nashorn/internal/codegen/SharedScopeCall.java new file mode 100644 index 00000000..376416de --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/SharedScopeCall.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import java.util.Arrays; +import java.util.EnumSet; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.runtime.ScriptObject; + +/** + * A scope call or get operation that can be shared by several callsites. This generates a static + * method that wraps the invokedynamic instructions to get or call scope variables. + * The rationale for this is that initial linking of invokedynamic callsites is expensive, + * so by sharing them we can reduce startup overhead and allow very large scripts to run that otherwise wouldn't. + * + * <p>Static methods generated by this class expect two parameters in addition to the parameters of the + * function call: The current scope object and the depth of the target scope relative to the scope argument + * for when this is known at compile-time (fast-scope access).</p> + * + * <p>The second argument may be -1 for non-fast-scope symbols, in which case the scope chain is checked + * for each call. This may cause callsite invalidation when the shared method is used from different + * scopes, but such sharing of non-fast scope calls may still be necessary for very large scripts.</p> + * + * <p>Scope calls must not be shared between normal callsites and callsites contained in a <tt>with</tt> + * statement as this condition is not handled by current guards and will cause a runtime error.</p> + */ +public class SharedScopeCall { + + /** Threshold for using shared scope calls with fast scope access. */ + public static final int FAST_SCOPE_CALL_THRESHOLD = 4; + /** Threshold for using shared scope calls with slow scope access. */ + public static final int SLOW_SCOPE_CALL_THRESHOLD = 500; + /** Threshold for using shared scope gets with fast scope access. */ + public static final int FAST_SCOPE_GET_THRESHOLD = 200; + + final Type valueType; + final Symbol symbol; + final Type returnType; + final Type[] paramTypes; + final int flags; + final boolean isCall; + private CompileUnit compileUnit; + private String methodName; + private String staticSignature; + + /** + * Constructor. + * + * @param symbol the symbol + * @param valueType the type of the value + * @param returnType the return type + * @param paramTypes the function parameter types + * @param flags the callsite flags + */ + SharedScopeCall(final Symbol symbol, final Type valueType, final Type returnType, final Type[] paramTypes, final int flags) { + this.symbol = symbol; + this.valueType = valueType; + this.returnType = returnType; + this.paramTypes = paramTypes; + this.flags = flags; + // If paramTypes is not null this is a call, otherwise it's just a get. + this.isCall = paramTypes != null; + } + + @Override + public int hashCode() { + return symbol.hashCode() ^ returnType.hashCode() ^ Arrays.hashCode(paramTypes) ^ flags; + } + + @Override + public boolean equals(final Object obj) { + if (obj instanceof SharedScopeCall) { + final SharedScopeCall c = (SharedScopeCall) obj; + return symbol.equals(c.symbol) + && flags == c.flags + && returnType.equals(c.returnType) + && Arrays.equals(paramTypes, c.paramTypes); + } + return false; + } + + /** + * Set the compile unit and method name. + * @param compileUnit the compile unit + * @param compiler the compiler to generate a unique method name + */ + protected void setClassAndName(final CompileUnit compileUnit, final Compiler compiler) { + this.compileUnit = compileUnit; + this.methodName = compiler.uniqueName("scopeCall"); + } + + /** + * Generate the invoke instruction for this shared scope call. + * @param method the method emitter + */ + public void generateInvoke(final MethodEmitter method) { + method.invokeStatic(compileUnit.getUnitClassName(), methodName, getStaticSignature()); + } + + /** + * Generate the method that implements the scope get or call. + */ + protected void generateScopeCall() { + final ClassEmitter classEmitter = compileUnit.getClassEmitter(); + final EnumSet<ClassEmitter.Flag> methodFlags = EnumSet.of(ClassEmitter.Flag.STATIC); + + // This method expects two fixed parameters in addition to any parameters that may be + // passed on to the function: A ScriptObject representing the caller's current scope object, + // and an int specifying the distance to the target scope containing the symbol we want to + // access, or -1 if this is not known at compile time (e.g. because of a "with" or "eval"). + + final MethodEmitter method = classEmitter.method(methodFlags, methodName, getStaticSignature()); + method.begin(); + + // Load correct scope by calling getProto() on the scope argument as often as specified + // by the second argument. + final MethodEmitter.Label parentLoopStart = new MethodEmitter.Label("parent_loop_start"); + final MethodEmitter.Label parentLoopDone = new MethodEmitter.Label("parent_loop_done"); + method.load(Type.OBJECT, 0); + method.label(parentLoopStart); + method.load(Type.INT, 1); + method.iinc(1, -1); + method.ifle(parentLoopDone); + method.invoke(ScriptObject.GET_PROTO); + method._goto(parentLoopStart); + method.label(parentLoopDone); + + method.dynamicGet(valueType, symbol.getName(), flags, isCall); + + // If this is a get we're done, otherwise call the value as function. + if (isCall) { + method.convert(Type.OBJECT); + // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. + method.loadNull(); + int slot = 2; + for (final Type type : paramTypes) { + method.load(type, slot++); + if (type == Type.NUMBER || type == Type.LONG) slot++; + } + method.dynamicCall(returnType, paramTypes.length, flags); + } + + method._return(returnType); + method.end(); + } + + private String getStaticSignature() { + if (staticSignature == null) { + if (paramTypes == null) { + staticSignature = Type.getMethodDescriptor(returnType, Type.typeFor(ScriptObject.class), Type.INT); + } else { + final Type[] params = new Type[paramTypes.length + 2]; + params[0] = Type.typeFor(ScriptObject.class); + params[1] = Type.INT; + int i = 2; + for (Type type : paramTypes) { + if (type.isObject()) { + type = Type.OBJECT; + } + params[i++] = type; + } + staticSignature = Type.getMethodDescriptor(returnType, params); + } + } + return staticSignature; + } + +} diff --git a/src/jdk/nashorn/internal/codegen/Splitter.java b/src/jdk/nashorn/internal/codegen/Splitter.java new file mode 100644 index 00000000..f6053a70 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/Splitter.java @@ -0,0 +1,403 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.BreakNode; +import jdk.nashorn.internal.ir.ContinueNode; +import jdk.nashorn.internal.ir.DoWhileNode; +import jdk.nashorn.internal.ir.ForNode; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.LabelNode; +import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.ReturnNode; +import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.SwitchNode; +import jdk.nashorn.internal.ir.WhileNode; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.runtime.Source; + +/** + * Split the IR into smaller compile units. + */ +public class Splitter extends NodeVisitor { + /** Current compiler. */ + private final Compiler compiler; + + /** IR to be broken down. */ + private final FunctionNode functionNode; + + /** Compile unit for the main script. */ + private final CompileUnit scriptCompileUnit; + + /** Cache for calculated block weights. */ + private final Map<Node, Long> weightCache = new HashMap<>(); + + /** Weight threshold for when to start a split. */ + public static final long SPLIT_THRESHOLD = 32 * 1024; + + /** + * Constructor. + * + * @param compiler the compiler + * @param functionNode function node to split + */ + public Splitter(final Compiler compiler, final FunctionNode functionNode, final CompileUnit compileUnit) { + this.compiler = compiler; + this.functionNode = functionNode; + this.scriptCompileUnit = compileUnit; + } + + /** + * Execute the split + */ + void split() { + long weight = WeighNodes.weigh(functionNode); + + if (weight >= SPLIT_THRESHOLD) { + + functionNode.accept(this); + + if (functionNode.isSplit()) { + // Weight has changed so weigh again, this time using block weight cache + weight = WeighNodes.weigh(functionNode, weightCache); + } + + if (weight >= SPLIT_THRESHOLD) { + weight = splitBlock(functionNode); + } + + if (functionNode.isSplit()) { + functionNode.accept(new SplitFlowAnalyzer()); + } + } + + assert functionNode.getCompileUnit() == null : "compile unit already set"; + + if (functionNode.isScript()) { + assert scriptCompileUnit != null : "script compile unit is null"; + + functionNode.setCompileUnit(scriptCompileUnit); + scriptCompileUnit.addWeight(weight + WeighNodes.FUNCTION_WEIGHT); + } else { + functionNode.setCompileUnit(findUnit(weight)); + } + + // Recursively split nested functions + final List<FunctionNode> functions = functionNode.getFunctions(); + + for (final FunctionNode function : functions) { + new Splitter(compiler, function, scriptCompileUnit).split(); + } + } + + /** + * Override this logic to look up compile units in a different way + * @param weight weight needed + * @return compile unit + */ + protected CompileUnit findUnit(final long weight) { + return compiler.findUnit(weight); + } + + /** + * Split a block into sub methods. + * + * @param block Block or function to split. + * + * @return new weight for the resulting block. + */ + private long splitBlock(final Block block) { + functionNode.setIsSplit(); + + final List<Node> splits = new ArrayList<>(); + List<Node> statements = new ArrayList<>(); + long statementsWeight = 0; + + for (final Node statement : block.getStatements()) { + final long weight = WeighNodes.weigh(statement, weightCache); + + if (statementsWeight + weight >= SPLIT_THRESHOLD || statement.isTerminal()) { + if (!statements.isEmpty()) { + splits.add(createBlockSplitNode(block, statements, statementsWeight)); + statements = new ArrayList<>(); + statementsWeight = 0; + } + + } + + if (statement.isTerminal()) { + splits.add(statement); + } else { + statements.add(statement); + statementsWeight += weight; + } + } + + if (!statements.isEmpty()) { + splits.add(createBlockSplitNode(block, statements, statementsWeight)); + } + + block.setStatements(splits); + + return WeighNodes.weigh(block, weightCache); + } + + /** + * Create a new split node from statements contained in a parent block. + * + * @param parent Parent block. + * @param statements Statements to include. + * + * @return New split node. + */ + private SplitNode createBlockSplitNode(final Block parent, final List<Node> statements, final long weight) { + final Source source = parent.getSource(); + final long token = parent.getToken(); + final int finish = parent.getFinish(); + final String name = compiler.uniqueName(SPLIT_PREFIX.tag()); + + final Block newBlock = new Block(source, token, finish, parent, functionNode); + newBlock.setFrame(new Frame(parent.getFrame())); + newBlock.setStatements(statements); + + final SplitNode splitNode = new SplitNode(name, functionNode, newBlock); + + splitNode.setCompileUnit(compiler.findUnit(weight + WeighNodes.FUNCTION_WEIGHT)); + + return splitNode; + } + + @Override + public Node enter(final Block block) { + if (block.isCatchBlock()) { + return null; + } + + final long weight = WeighNodes.weigh(block, weightCache); + + if (weight < SPLIT_THRESHOLD) { + weightCache.put(block, weight); + return null; + } + + return block; + } + + @Override + public Node leave(final Block block) { + assert !block.isCatchBlock(); + + // Block was heavier than SLIT_THRESHOLD in enter, but a sub-block may have + // been split already, so weigh again before splitting. + long weight = WeighNodes.weigh(block, weightCache); + if (weight >= SPLIT_THRESHOLD) { + weight = splitBlock(block); + } + weightCache.put(block, weight); + + return block; + } + + @SuppressWarnings("rawtypes") + @Override + public Node leave(final LiteralNode literal) { + long weight = WeighNodes.weigh(literal); + + if (weight < SPLIT_THRESHOLD) { + return literal; + } + + functionNode.setIsSplit(); + + if (literal instanceof ArrayLiteralNode) { + final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode) literal; + final Node[] value = arrayLiteralNode.getValue(); + final int[] postsets = arrayLiteralNode.getPostsets(); + final List<ArrayUnit> units = new ArrayList<>(); + + long totalWeight = 0; + int lo = 0; + + for (int i = 0; i < postsets.length; i++) { + final int postset = postsets[i]; + final Node element = value[postset]; + + weight = WeighNodes.weigh(element); + totalWeight += weight; + + if (totalWeight >= SPLIT_THRESHOLD) { + final CompileUnit unit = compiler.findUnit(totalWeight - weight); + units.add(new ArrayUnit(unit, lo, i)); + lo = i; + totalWeight = weight; + } + } + + if (lo != postsets.length) { + final CompileUnit unit = compiler.findUnit(totalWeight); + units.add(new ArrayUnit(unit, lo, postsets.length)); + } + + arrayLiteralNode.setUnits(units); + } + + return literal; + } + + @Override + public Node enter(final FunctionNode node) { + final List<Node> statements = node.getStatements(); + + for (final Node statement : statements) { + statement.accept(this); + } + + return null; + } + + static class SplitFlowAnalyzer extends NodeVisitor { + + /** Stack of visited Split nodes, deepest node first. */ + private final Deque<SplitNode> splitStack; + + /** Map of possible jump targets to containing split node */ + private final Map<Node,SplitNode> targetNodes = new HashMap<>(); + + SplitFlowAnalyzer() { + this.splitStack = new LinkedList<>(); + } + + @Override + public Node enter(final LabelNode labelNode) { + registerJumpTarget(labelNode.getBreakNode()); + registerJumpTarget(labelNode.getContinueNode()); + return labelNode; + } + + @Override + public Node enter(final WhileNode whileNode) { + registerJumpTarget(whileNode); + return whileNode; + } + + @Override + public Node enter(final DoWhileNode doWhileNode) { + registerJumpTarget(doWhileNode); + return doWhileNode; + } + + @Override + public Node enter(final ForNode forNode) { + registerJumpTarget(forNode); + return forNode; + } + + @Override + public Node enter(final SwitchNode switchNode) { + registerJumpTarget(switchNode); + return switchNode; + } + + @Override + public Node enter(final ReturnNode returnNode) { + for (final SplitNode split : splitStack) { + split.setHasReturn(true); + } + return returnNode; + } + + @Override + public Node enter(final ContinueNode continueNode) { + searchJumpTarget(continueNode.getTargetNode(), continueNode.getTargetLabel()); + return continueNode; + } + + @Override + public Node enter(final BreakNode breakNode) { + searchJumpTarget(breakNode.getTargetNode(), breakNode.getTargetLabel()); + return breakNode; + } + + @Override + public Node enter(final SplitNode splitNode) { + splitStack.addFirst(splitNode); + return splitNode; + } + + @Override + public Node leave(final SplitNode splitNode) { + assert splitNode == splitStack.peekFirst(); + splitStack.removeFirst(); + return splitNode; + } + + /** + * Register the split node containing a potential jump target. + * @param targetNode a potential target node. + */ + private void registerJumpTarget(final Node targetNode) { + final SplitNode splitNode = splitStack.peekFirst(); + if (splitNode != null) { + targetNodes.put(targetNode, splitNode); + } + } + + /** + * Check if a jump target is outside the current split node and its parent split nodes. + * @param targetNode the jump target node. + * @param targetLabel the jump target label. + */ + private void searchJumpTarget(final Node targetNode, final MethodEmitter.Label targetLabel) { + + final SplitNode targetSplit = targetNodes.get(targetNode); + // Note that targetSplit may be null, indicating that targetNode is in top level method. + // In this case we have to add the external jump target to all split nodes. + + for (final SplitNode split : splitStack) { + if (split == targetSplit) { + break; + } + final List<MethodEmitter.Label> externalTargets = split.getExternalTargets(); + if (!externalTargets.contains(targetLabel)) { + split.addExternalTarget(targetLabel); + } + } + } + } +} + diff --git a/src/jdk/nashorn/internal/codegen/Transform.java b/src/jdk/nashorn/internal/codegen/Transform.java new file mode 100644 index 00000000..d250102c --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/Transform.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +/** + * Code generation transform + */ +public interface Transform { + //empty +} diff --git a/src/jdk/nashorn/internal/codegen/WeighNodes.java b/src/jdk/nashorn/internal/codegen/WeighNodes.java new file mode 100644 index 00000000..040907dc --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/WeighNodes.java @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen; + +import java.util.List; +import java.util.Map; +import jdk.nashorn.internal.ir.AccessNode; +import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.BreakNode; +import jdk.nashorn.internal.ir.CallNode; +import jdk.nashorn.internal.ir.CatchNode; +import jdk.nashorn.internal.ir.ContinueNode; +import jdk.nashorn.internal.ir.DoWhileNode; +import jdk.nashorn.internal.ir.ExecuteNode; +import jdk.nashorn.internal.ir.ForNode; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.IfNode; +import jdk.nashorn.internal.ir.IndexNode; +import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; +import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.PropertyNode; +import jdk.nashorn.internal.ir.ReferenceNode; +import jdk.nashorn.internal.ir.ReturnNode; +import jdk.nashorn.internal.ir.RuntimeNode; +import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.SwitchNode; +import jdk.nashorn.internal.ir.ThrowNode; +import jdk.nashorn.internal.ir.TryNode; +import jdk.nashorn.internal.ir.VarNode; +import jdk.nashorn.internal.ir.WhileNode; +import jdk.nashorn.internal.ir.WithNode; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.parser.TokenType; + +/** + * Computes the "byte code" weight of an AST segment. This is used + * for Splitting too large class files + */ +public class WeighNodes extends NodeVisitor { + /* + * Weight constants. + */ + static final long FUNCTION_WEIGHT = 40; + private static final long ACCESS_WEIGHT = 4; + private static final long ADD_WEIGHT = 10; + private static final long BREAK_WEIGHT = 1; + private static final long CALL_WEIGHT = 10; + private static final long CATCH_WEIGHT = 10; + private static final long CONTINUE_WEIGHT = 1; + private static final long IF_WEIGHT = 2; + private static final long LITERAL_WEIGHT = 10; + private static final long LOOP_WEIGHT = 4; + private static final long REFERENCE_WEIGHT = 20; + private static final long RETURN_WEIGHT = 2; + private static final long SPLIT_WEIGHT = 40; + private static final long SWITCH_WEIGHT = 8; + private static final long THROW_WEIGHT = 2; + private static final long VAR_WEIGHT = 40; + private static final long WITH_WEIGHT = 8; + + /** Accumulated weight. */ + private long weight; + + /** Optional cache for weight of block nodes. */ + private final Map<Node, Long> weightCache; + + /* + * Constructor + * + * @param weightCache cache of already calculated block weights + */ + private WeighNodes(final Map<Node, Long> weightCache) { + super(null, null); + this.weightCache = weightCache; + } + + static long weigh(final Node node) { + final WeighNodes weighNodes = new WeighNodes(null); + node.accept(weighNodes); + return weighNodes.weight; + } + + static long weigh(final Node node, final Map<Node, Long> weightCache) { + final WeighNodes weighNodes = new WeighNodes(weightCache); + node.accept(weighNodes); + return weighNodes.weight; + } + + @Override + public Node leave(final AccessNode accessNode) { + weight += ACCESS_WEIGHT; + return accessNode; + } + + @Override + public Node leave(final BinaryNode binaryNode) { + final TokenType tokenType = binaryNode.tokenType(); + + if (tokenType == TokenType.ADD || tokenType == TokenType.ASSIGN_ADD) { + weight += ADD_WEIGHT; + } else { + weight += 1; + } + + return binaryNode; + } + + @Override + public Node enter(final Block block) { + + if (weightCache != null && weightCache.containsKey(block)) { + weight += weightCache.get(block); + return null; + } + + return block; + } + + @Override + public Node leave(final BreakNode breakNode) { + weight += BREAK_WEIGHT; + return breakNode; + } + + @Override + public Node leave(final CallNode callNode) { + weight += CALL_WEIGHT; + return callNode; + } + + @Override + public Node leave(final CatchNode catchNode) { + weight += CATCH_WEIGHT; + return catchNode; + } + + @Override + public Node leave(final ContinueNode continueNode) { + weight += CONTINUE_WEIGHT; + return continueNode; + } + + @Override + public Node leave(final DoWhileNode doWhileNode) { + weight += LOOP_WEIGHT; + return doWhileNode; + } + + @Override + public Node leave(final ExecuteNode executeNode) { + return executeNode; + } + + @Override + public Node leave(final ForNode forNode) { + weight += LOOP_WEIGHT; + return forNode; + } + + @Override + public Node enter(final FunctionNode functionNode) { + final List<Node> statements = functionNode.getStatements(); + + for (final Node statement : statements) { + statement.accept(this); + } + + return null; + } + + @Override + public Node leave(final IdentNode identNode) { + weight += ACCESS_WEIGHT + identNode.getName().length() * 2; + return identNode; + } + + @Override + public Node leave(final IfNode ifNode) { + weight += IF_WEIGHT; + return ifNode; + } + + @Override + public Node leave(final IndexNode indexNode) { + weight += ACCESS_WEIGHT; + return indexNode; + } + + @SuppressWarnings("rawtypes") + @Override + public Node enter(final LiteralNode literalNode) { + weight += LITERAL_WEIGHT; + + if (literalNode instanceof ArrayLiteralNode) { + final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode)literalNode; + final Node[] value = arrayLiteralNode.getValue(); + final int[] postsets = arrayLiteralNode.getPostsets(); + final List<ArrayUnit> units = arrayLiteralNode.getUnits(); + + if (units == null) { + for (final int postset : postsets) { + final Node element = value[postset]; + + if (element != null) { + element.accept(this); + } + } + } + + return null; + } + + return literalNode; + } + + @Override + public Node leave(final PropertyNode propertyNode) { + weight += LITERAL_WEIGHT; + return propertyNode; + } + + @Override + public Node leave(final ReferenceNode referenceNode) { + weight += REFERENCE_WEIGHT; + return referenceNode; + } + + @Override + public Node leave(final ReturnNode returnNode) { + weight += RETURN_WEIGHT; + return returnNode; + } + + @Override + public Node leave(final RuntimeNode runtimeNode) { + weight += CALL_WEIGHT; + return runtimeNode; + } + + @Override + public Node enter(final SplitNode splitNode) { + weight += SPLIT_WEIGHT; + return null; + } + + @Override + public Node leave(final SwitchNode switchNode) { + weight += SWITCH_WEIGHT; + return switchNode; + } + + @Override + public Node leave(final ThrowNode throwNode) { + weight += THROW_WEIGHT; + return throwNode; + } + + @Override + public Node leave(final TryNode tryNode) { + weight += THROW_WEIGHT; + return tryNode; + } + + @Override + public Node leave(final VarNode varNode) { + weight += VAR_WEIGHT; + return varNode; + } + + @Override + public Node leave(final WhileNode whileNode) { + weight += LOOP_WEIGHT; + return whileNode; + } + + @Override + public Node leave(final WithNode withNode) { + weight += WITH_WEIGHT; + return withNode; + } +} diff --git a/src/jdk/nashorn/internal/codegen/objects/FieldObjectCreator.java b/src/jdk/nashorn/internal/codegen/objects/FieldObjectCreator.java new file mode 100644 index 00000000..9d04ca1b --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/objects/FieldObjectCreator.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen.objects; + +import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; +import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; +import static jdk.nashorn.internal.codegen.types.Type.OBJECT; + +import java.util.Iterator; +import java.util.List; +import jdk.nashorn.internal.codegen.CodeGenerator; +import jdk.nashorn.internal.codegen.MethodEmitter; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.arrays.ArrayIndex; + +/** + * Analyze an object's characteristics for appropriate code generation. This + * is used for functions and for objects. A field object take a set of values which + * to assign to the various fields in the object. This is done by the generated code + * + * @param <T> the value type for the fields being written on object creation, e.g. Node + * @see jdk.nashorn.internal.ir.Node + */ +public abstract class FieldObjectCreator<T> extends ObjectCreator { + /** array of corresponding values to symbols (null for no values) */ + private final List<T> values; + + /** call site flags to be used for invocations */ + private final int callSiteFlags; + + /** + * Constructor + * + * @param codegen code generator + * @param keys keys for fields in object + * @param symbols symbols for fields in object + * @param values list of values corresponding to keys + */ + public FieldObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final List<T> values) { + this(codegen, keys, symbols, values, false, false); + } + + /** + * Constructor + * + * @param codegen code generator + * @param keys keys for fields in object + * @param symbols symbols for fields in object + * @param values values (or null where no value) to be written to the fields + * @param isScope is this a scope object + * @param isVarArg is this a vararg object + */ + public FieldObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final List<T> values, final boolean isScope, final boolean isVarArg) { + super(codegen, keys, symbols, isScope, isVarArg); + this.values = values; + this.callSiteFlags = codegen.getCallSiteFlags(); + } + + /** + * Construct an object. + * + * @param method the method emitter + */ + @Override + public void makeObject(final MethodEmitter method) { + makeMap(); + + method._new(getClassName()).dup(); // create instance + loadMap(method); //load the map + + if (isScope()) { + method.loadScope(); + + if (isVarArg()) { + method.loadArguments(); + method.invoke(constructorNoLookup(getClassName(), PropertyMap.class, ScriptObject.class, ARGUMENTS.type())); + } else { + method.invoke(constructorNoLookup(getClassName(), PropertyMap.class, ScriptObject.class)); + } + } else { + method.invoke(constructorNoLookup(getClassName(), PropertyMap.class)); + } + + // Set values. + final Iterator<Symbol> symbolIter = symbols.iterator(); + final Iterator<String> keyIter = keys.iterator(); + final Iterator<T> valueIter = values.iterator(); + + while (symbolIter.hasNext()) { + final Symbol symbol = symbolIter.next(); + final String key = keyIter.next(); + final T value = valueIter.next(); + + if (symbol != null && value != null) { + final int index = ArrayIndex.getArrayIndexNoThrow(key); + + if (index < 0) { + putField(method, key, symbol.getFieldIndex(), value); + } else { + putSlot(method, index, value); + } + } + } + } + + /** + * Technique for loading an initial value. Defined by anonymous subclasses in code gen. + * + * @param value Value to load. + */ + protected abstract void loadValue(T value); + + /** + * Determine the type of a value. Defined by anonymous subclasses in code gen. + * + * @param value Value to inspect. + * + * @return Value type. + */ + protected abstract Type getValueType(T value); + + /** + * Store a value in a field of the generated class object. + * + * @param method Script method. + * @param key Property key. + * @param fieldIndex Field number. + * @param value Value to store. + */ + private void putField(final MethodEmitter method, final String key, final int fieldIndex, final T value) { + method.dup(); + + loadValue(value); + + final Type valueType = getValueType(value); + // for example when we have a with scope + if (valueType.isObject() || valueType.isBoolean()) { + method.convert(OBJECT); + } + + method.convert(OBJECT); + method.putField(getClassName(), ObjectClassGenerator.getFieldName(fieldIndex, Type.OBJECT), typeDescriptor(Object.class)); + } + + /** + * Store a value in an indexed slot of a generated class object. + * + * @param method Script method. + * @param index Slot index. + * @param value Value to store. + */ + private void putSlot(final MethodEmitter method, final int index, final T value) { + method.dup(); + method.load(index); + loadValue(value); + method.dynamicSetIndex(callSiteFlags); + } + +} diff --git a/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java b/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java new file mode 100644 index 00000000..31635f31 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen.objects; + +import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.HANDLE_STATIC; +import static jdk.nashorn.internal.codegen.Compiler.SCRIPTOBJECT_IMPL_OBJECT; +import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE; +import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; +import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; +import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; + +import java.lang.invoke.MethodHandle; +import java.util.EnumSet; +import java.util.List; +import jdk.nashorn.internal.codegen.CodeGenerator; +import jdk.nashorn.internal.codegen.FunctionSignature; +import jdk.nashorn.internal.codegen.MethodEmitter; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.parser.Token; +import jdk.nashorn.internal.parser.TokenType; +import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.ScriptFunction; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.Source; + +/** + * Analyze a function object's characteristics for appropriate code + * generation. This generates code for the instantiation of ScriptFunction:s + */ +public class FunctionObjectCreator extends ObjectCreator { + + private final FunctionNode functionNode; + + /** + * Constructor + * + * @param codegen the code generator + * @param functionNode the function node to turn into a ScriptFunction implementation + * @param keys initial keys for the object map + * @param symbols corresponding initial symbols for object map + */ + public FunctionObjectCreator(final CodeGenerator codegen, final FunctionNode functionNode, final List<String> keys, final List<Symbol> symbols) { + super(codegen, keys, symbols, false, false); + this.functionNode = functionNode; + } + + private void loadHandle(final MethodEmitter method, final String signature) { + method.loadHandle(functionNode.getCompileUnit().getUnitClassName(), functionNode.getName(), signature, EnumSet.of(HANDLE_STATIC)); // function + } + + /** + * Emit code for creating the object + * + * @param method the method emitter + */ + @Override + public void makeObject(final MethodEmitter method) { + makeMap(); + + final IdentNode identNode = functionNode.getIdent(); + final String signature = new FunctionSignature(true, functionNode.needsCallee(), functionNode.getReturnType(), functionNode.isVarArg() ? null : functionNode.getParameters()).toString(); + final long firstToken = functionNode.getFirstToken(); + final long lastToken = functionNode.getLastToken(); + final int position = Token.descPosition(firstToken); + final int length = Token.descPosition(lastToken) - position + Token.descLength(lastToken); + final long token = Token.toDesc(TokenType.FUNCTION, position, length); + + /* + * Instantiate the function object, must be referred to by name as + * class is not available at compile time + */ + method._new(SCRIPTOBJECT_IMPL_OBJECT).dup(); + method.load(functionNode.isAnonymous() ? "" : identNode.getName()); + loadHandle(method, signature); + method.loadScope(); + method.getStatic(compileUnit.getUnitClassName(), SOURCE.tag(), SOURCE.descriptor()); + method.load(token); + method.loadHandle(getClassName(), ALLOCATE.tag(), methodDescriptor(ScriptObject.class, PropertyMap.class), EnumSet.of(HANDLE_STATIC)); + + /* + * Emit code for the correct property map for the object + */ + loadMap(method); + + /* + * Invoke the constructor + */ + method.load(functionNode.needsCallee()); + method.load(functionNode.isStrictMode()); + method.invoke(constructorNoLookup(SCRIPTOBJECT_IMPL_OBJECT, + String.class, + MethodHandle.class, + ScriptObject.class, + Source.class, + long.class, + MethodHandle.class, + PropertyMap.class, + boolean.class, + boolean.class)); + + + if (functionNode.isVarArg()) { + method.dup(); + method.load(functionNode.getParameters().size()); + method.invoke(ScriptFunction.SET_ARITY); + } + } +} diff --git a/src/jdk/nashorn/internal/codegen/objects/MapCreator.java b/src/jdk/nashorn/internal/codegen/objects/MapCreator.java new file mode 100644 index 00000000..9483666b --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/objects/MapCreator.java @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2010, 2012, 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.codegen.objects; + +import static jdk.nashorn.internal.codegen.objects.ObjectClassGenerator.OBJECT_FIELDS_ONLY; +import static jdk.nashorn.internal.codegen.objects.ObjectClassGenerator.PRIMITIVE_TYPE; +import static jdk.nashorn.internal.runtime.linker.Lookup.MH; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.ArrayList; +import java.util.List; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.runtime.AccessorProperty; +import jdk.nashorn.internal.runtime.Property; +import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.ScriptObject; +import jdk.nashorn.internal.runtime.arrays.ArrayIndex; + +/** + * Class that creates PropertyMap sent to script object constructors. + */ +public class MapCreator { + /** Object structure for objects associated with this map */ + private final Class<?> structure; + + /** key set for object map */ + private final String[] keys; + + /** corresponding symbol set for object map */ + private final Symbol[] symbols; + + /** + * Constructor + * + * @param structure structure to generate map for (a JO$ subclass) + * @param keys list of keys for map + * @param symbols list of symbols for map + */ + public MapCreator(final Class<?> structure, final List<String> keys, final List<Symbol> symbols) { + final int size = keys.size(); + + this.structure = structure; + this.keys = keys.toArray(new String[size]); + this.symbols = symbols.toArray(new Symbol[size]); + } + + /** + * Constructs a property map based on a set of fields. + * + * @param isVarArg is this a vararg object map + * + * @return New map populated with accessor properties. + */ + public PropertyMap makeMap(final boolean isVarArg) { + final List<Property> properties = new ArrayList<>(); + + assert keys != null; + + for (int i = 0; i < keys.length; i++) { + final String key = keys[i]; + final Symbol symbol = symbols[i]; + + if (symbol != null && !ArrayIndex.isIndexKey(key)) { + final Property property = initHandle(key, symbol.getFieldIndex(), symbol, isVarArg); + properties.add(property); + } + } + + return PropertyMap.newMap(structure, properties); + } + + private Property initHandle(final String key, final int fieldIndex, final Symbol symbol, final boolean isVarArg) { + assert symbol != null; + final boolean isParam = symbol.isParam(); + + final String fieldNameObject = ObjectClassGenerator.getFieldName(fieldIndex, Type.OBJECT); + final String fieldNamePrimitive = ObjectClassGenerator.getFieldName(fieldIndex, ObjectClassGenerator.PRIMITIVE_TYPE); + + MethodHandle primitiveGetter = null; + MethodHandle primitiveSetter = null; + MethodHandle objectGetter; + MethodHandle objectSetter; + + final MethodHandles.Lookup lookup = MethodHandles.lookup(); + + if (isParam && isVarArg) { + final MethodHandle arguments = MH.getter(MethodHandles.lookup(), structure, "arguments", Object.class); + final MethodHandle argumentsSO = MH.asType(arguments, arguments.type().changeReturnType(ScriptObject.class)); + objectGetter = MH.insertArguments(MH.filterArguments(ScriptObject.GET_ARGUMENT.methodHandle(), 0, argumentsSO), 1, fieldIndex); + objectSetter = MH.insertArguments(MH.filterArguments(ScriptObject.SET_ARGUMENT.methodHandle(), 0, argumentsSO), 1, fieldIndex); + } else { + objectGetter = MH.getter(lookup, structure, fieldNameObject, Type.OBJECT.getTypeClass()); + objectSetter = MH.setter(lookup, structure, fieldNameObject, Type.OBJECT.getTypeClass()); + if (!OBJECT_FIELDS_ONLY) { + primitiveGetter = MH.getter(lookup, structure, fieldNamePrimitive, PRIMITIVE_TYPE.getTypeClass()); + primitiveSetter = MH.setter(lookup, structure, fieldNamePrimitive, PRIMITIVE_TYPE.getTypeClass()); + } + } + + return new AccessorProperty(key, getPropertyFlags(symbol, isVarArg), objectGetter, objectSetter, primitiveGetter, primitiveSetter); + } + + /** + * Compute property flags given local state of a field. Maybe be overridden and extended, + * as is the case in {@link ObjectMapCreator} + * + * @param symbol symbol to check + * @param isVarArg is this a vararg + * + * @return flags to use for fields + */ + protected int getPropertyFlags(final Symbol symbol, final boolean isVarArg) { + final boolean isParam = symb |