diff options
584 files changed, 40692 insertions, 14794 deletions
diff --git a/bin/checkintest.sh b/bin/checkintest.sh deleted file mode 100644 index c4a9e96c..00000000 --- a/bin/checkintest.sh +++ /dev/null @@ -1,266 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License version 2 only, as -# published by the Free Software Foundation. -# -# 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. -# - -#best pass rate at test 262 known -TEST262_PASS_AT_LEAST=435 - -RUN_TEST="true" -RUN_TEST262="true" -RUN_NODE="true" -KEEP_OUTPUT="true" -CLEAN_AND_BUILD_NASHORN="true" - -#the stable node version to sync against -NODE_LAST_STABLE=v0.6.18 - -#parse args -for arg in $* -do - if [ $arg = "--no-test" ]; then - RUN_TEST="false" - echo "**** WARNING - you have disabled 'ant test', which is a minimum checkin requirement..." - elif [ $arg = "--no-262" ]; then - RUN_TEST262="false" - elif [ $arg = "--no-node" ]; then - RUN_NODE="false" - elif [ $arg = "--no-build" ]; then - CLEAN_AND_BUILD_NASHORN="false" - elif [ $arg = "--no-logs" ]; then - KEEP_OUTPUT="false" - fi -done - -function lastpart() { - arr=$(echo $1 | tr "/" "\n") - for x in $arr - do - _last=$x - done - echo $_last -} - -function check_installed() { - which $1 >/dev/null - if [ $? -ne 0 ]; then - echo "Error $1 not installed: $?" - exit 2 - fi -} - -check_installed hg -check_installed git -check_installed mv -check_installed git - -PWD=$(pwd); - -while [ -z $NASHORN_ROOT ] -do - if [ -e $PWD/.hg ]; then - NASHORN_ROOT=${PWD} - break - fi - PWD=$(dirname ${PWD}) -done - -echo "Nashorn root detected at ${NASHORN_ROOT}" - -COMMON_ROOT=$(dirname $NASHORN_ROOT) -echo "Common root is ${COMMON_ROOT}" - -echo "Running checkintest..." - -ABSOLUTE_NASHORN_HOME=$COMMON_ROOT/$(lastpart $NASHORN_ROOT) - -if [ $CLEAN_AND_BUILD_NASHORN != "false" ]; then - echo "Cleaning and building nashorn at $ABSOLUTE_NASHORN_HOME/nashorn..." - $(cd $ABSOLUTE_NASHORN_HOME/nashorn; ant clean >/dev/null 2>/dev/null) - $(cd $ABSOLUTE_NASHORN_HOME/nashorn; ant jar >/dev/null 2>/dev/null) - echo "Done." -fi - -function failure_check() { - while read line - do - LINE=$(echo $line | grep "Tests run") - if [ "${LINE}" != "" ]; then - RESULT=$(echo $line | grep "Failures: 0" | grep "Errors: 0") - if [ "${RESULT}" == "" ]; then - TESTNAME=$2 - echo "There were errors in ${TESTNAME} : ${LINE}" - exit 1 - fi - fi - done < $1 -} - -function test() { - TEST_OUTPUT=$ABSOLUTE_NASHORN_HOME/$(mktemp tmp.XXXXX) - echo "Running 'ant test' on nashorn from ${ABSOLUTE_NASHORN_HOME}/nashorn..." - $(cd $ABSOLUTE_NASHORN_HOME/nashorn; ant test >$TEST_OUTPUT) - echo "Done." - - failure_check $TEST_OUTPUT - - echo "**** SUCCESS: 'ant test' successful" - - if [ $KEEP_OUTPUT == "true" ]; then - cp $TEST_OUTPUT ./checkintest.test.log - rm -fr $TEST_OUTPUT - fi -} - -if [ $RUN_TEST != "false" ]; then - test; -fi - -function test262() { - - echo "Running 'ant test262parallel' on nashorn from ${ABSOLUTE_NASHORN_HOME}/nashorn..." - TEST262_OUTPUT=$ABSOLUTE_NASHORN_HOME/$(mktemp tmp.XXXXX) - - echo "Looking for ${ABSOLUTE_NASHORN_HOME}/test/test262..." - - if [ ! -e $ABSOLUTE_NASHORN_HOME/nashorn/test/test262 ]; then - echo "test262 is missing... looking in $COMMON_ROOT..." - if [ ! -e $COMMON_ROOT/test262 ]; then - echo "... not there either... cloning from repo..." - hg clone http://hg.ecmascript.org/tests/test262 $COMMON_ROOT/test262 >/dev/null 2>/dev/null - echo "Done." - fi - echo "Adding soft link ${COMMON_ROOT}/test262 -> ${ABSOLUTE_NASHORN_HOME}/test/test262..." - ln -s $COMMON_ROOT/test262 $ABSOLUTE_NASHORN_HOME/nashorn/test/test262 - echo "Done." - fi - - echo "Ensuring test262 is up to date..." - $(cd $ABSOLUTE_NASHORN_HOME/nashorn/test/test262; hg pull -u >/dev/null 2>/dev/null) - echo "Done." - - echo "Running test262..." - $(cd $ABSOLUTE_NASHORN_HOME/nashorn; ant test262parallel > $TEST262_OUTPUT) - - FAILED=$(cat $TEST262_OUTPUT|grep "Tests run:"| cut -d ' ' -f 15 |tr -cd '"[[:digit:]]') - if [ $FAILED -gt $TEST262_PASS_AT_LEAST ]; then - echo "FAILURE: There are ${FAILED} failures in test262 and can be no more than ${TEST262_PASS_AT_LEAST}" - cp $TEST262_OUTPUT ./checkintest.test262.log - echo "See ./checkintest.test262.log" - echo "Terminating due to error" - exit 1 - elif [ $FAILED -lt $TEST262_PASS_AT_LEAST ]; then - echo "There seem to have been fixes to 262. ${FAILED} < ${TEST262_PASS_AT_LEAST}. Please update limit in bin/checkintest.sh" - fi - - echo "**** SUCCESS: Test262 passed with no more than ${TEST262_PASS_AT_LEAST} failures." - - if [ $KEEP_OUTPUT == "true" ]; then - cp $TEST262_OUTPUT ./checkintest.test262.log - rm -fr $TEST262_OUTPUT - fi -} - -if [ $RUN_TEST262 != "false" ]; then - test262; -fi; - -function testnode() { - TESTNODEJAR_OUTPUT=$ABSOLUTE_NASHORN_HOME/$(mktemp tmp.XXXXX) - - echo "Running node tests..." -#replace node jar properties nashorn with this nashorn - - NODEJAR_PROPERTIES=~/nodejar.properties - - NODE_HOME=$(cat $NODEJAR_PROPERTIES | grep ^node.home | cut -f2 -d=) - NASHORN_HOME=$(cat $NODEJAR_PROPERTIES | grep ^nashorn.home | cut -f2 -d=) - - ABSOLUTE_NODE_HOME=$COMMON_ROOT/$(lastpart $NODE_HOME) - - echo "Writing nodejar.properties..." - - cat > $NODEJAR_PROPERTIES << EOF -node.home=../node -nashorn.home=../$(lastpart $NASHORN_ROOT) -EOF - echo "Done." - echo "Checking node home ${ABSOLUTE_NODE_HOME}..." - - if [ ! -e $ABSOLUTE_NODE_HOME ]; then - echo "Node base dir not found. Cloning node..." - $(cd $COMMON_ROOT; git clone https://github.com/joyent/node.git $(lastpart $NODE_HOME) >/dev/null 2>/dev/null) - echo "Done." - echo "Updating to last stable version ${NODE_LAST_STABLE}..." - $(cd $ABSOLUTE_NODE_HOME; git checkout $NODE_LAST_STABLE >/dev/null 2>/dev/null) - echo "Done." - echo "Running configure..." - $(cd $ABSOLUTE_NODE_HOME; ./configure >/dev/null 2>/dev/null) - echo "Done." - fi - - echo "Ensuring node is built..." -#make sure node is built - $(cd $ABSOLUTE_NODE_HOME; make >/dev/null 2>/dev/null) - echo "Done." - - NODEJAR_HOME=$COMMON_ROOT/nodejar - - if [ ! -e $NODEJAR_HOME ]; then - echo "No node jar home found. cloning from depot..." - $(cd $COMMON_ROOT; hg clone https://hg.kenai.com/hg/nodejs~source nodejar >/dev/null 2>/dev/null) - $(cd $COMMON_ROOT/nodejar; ant >/dev/null) - echo "Done." - echo "Copying node files..." - $(cd $COMMON_ROOT/nodejar; ant copy-node-files >/dev/null 2>/dev/null) - echo "Patching node files..." - $(cd $COMMON_ROOT/nodejar; ant patch-node-files >/dev/null 2>/dev/null) - echo "Done." - fi - - echo "Ensuring node.jar is up to date from source depot..." - $(cd $COMMON_ROOT/nodejar; hg pull -u >/dev/null 2>/dev/null) - echo "Done." - - echo "Installing nashorn..." - $(cd $COMMON_ROOT/nodejar; ant >/dev/null) - echo "Done." - - echo "Running node.jar test..." - $(cd $COMMON_ROOT/nodejar; mvn clean verify >$TESTNODEJAR_OUTPUT) - echo "Done." - - failure_check $TESTNODEJAR_OUTPUT - - echo "**** SUCCESS: Node test successful." - - if [ $KEEP_OUTPUT == "true" ]; then - rm -fr $TESTNODEJAR_OUTPUT - cp $TESTNODEJAR_OUTPUT ./checkintest.nodejar.log - fi -} - -if [ $RUN_NODE != "false" ]; then - testnode; -fi; - -echo "Finished" diff --git a/bin/dump_octane_code.sh b/bin/dump_octane_code.sh deleted file mode 100644 index d24ab2ce..00000000 --- a/bin/dump_octane_code.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License version 2 only, as -# published by the Free Software Foundation. -# -# 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. -# - -# -# The purpose of this script is to provide a large amount of IR/bytecode from a known -# application to be diffed against the same output with a different Nashorn version. -# That way we can quickly detect if a seemingly minute change modifies a lot of code, -# which it most likely shouldn't. One example of this was when AccessSpecializer was -# moved into Lower the first time, it worked fine, but as a lot of Scope information -# at the time was finalized further down the code pipeline it did a lot fewer callsite -# specializations. This would have been immediately detected with a before and after -# diff using the output from this script. -# - -ITERS=$1 -if [ -z $ITERS ]; then - ITERS=7 -fi -NASHORN_JAR=dist/nashorn.jar -JVM_FLAGS="-ea -esa -server -jar ${NASHORN_JAR}" - -BENCHMARKS=( "box2d.js" "code-load.js" "crypto.js" "deltablue.js" "earley-boyer.js" "gbemu.js" "mandreel.js" "navier-stokes.js" "pdfjs.js" "raytrace.js" "regexp.js" "richards.js" "splay.js" ) - -for BENCHMARK in "${BENCHMARKS[@]}" -do - echo "START: ${BENCHMARK}" - CMD="${JAVA_HOME}/bin/java ${JVM_FLAGS} -co --print-lower-parse test/script/external/octane/${BENCHMARK}" - $CMD - echo "END: ${BENCHMARK}" - echo "" -done - -echo "Done" diff --git a/bin/jjs.bat b/bin/jjs.bat deleted file mode 100644 index 3c1c1595..00000000 --- a/bin/jjs.bat +++ /dev/null @@ -1,27 +0,0 @@ -rem -rem Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -rem DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -rem -rem This code is free software; you can redistribute it and/or modify it -rem under the terms of the GNU General Public License version 2 only, as -rem published by the Free Software Foundation. Oracle designates this -rem particular file as subject to the "Classpath" exception as provided -rem by Oracle in the LICENSE file that accompanied this code. -rem -rem This code is distributed in the hope that it will be useful, but WITHOUT -rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -rem version 2 for more details (a copy is included in the LICENSE file that -rem accompanied this code). -rem -rem You should have received a copy of the GNU General Public License version -rem 2 along with this work; if not, write to the Free Software Foundation, -rem Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -rem -rem Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -rem or visit www.oracle.com if you need additional information or have any -rem questions. -rem -@echo off - -java -Xms2G -Xmx2G -XX:-TieredCompilation -server -esa -ea -Djava.ext.dirs=%~dp0\..\dist -XX:+HeapDumpOnOutOfMemoryError -Dnashorn.debug=true -Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false jdk.nashorn.tools.Shell diff --git a/bin/jjs b/bin/jjsdebug.sh index f89a07c2..509700c3 100644 --- a/bin/jjs +++ b/bin/jjsdebug.sh @@ -1,29 +1,25 @@ -#!/bin/bash +#!/bin/sh # -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 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. -# +# 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. # -[ -z "$JAVA_HOME" ] && echo "Please set JAVA_HOME" && exit 1; - -$JAVA_HOME/bin/java -server -XX:+TieredCompilation -Xms2G -Xmx2G -esa -ea -Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -XX:+HeapDumpOnOutOfMemoryError -Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -Dnashorn.debug=true jdk.nashorn.tools.Shell $* +$JAVA_HOME/bin/jjs -J-Djava.ext.dirs=`dirname $0`/../dist -J-agentlib:jdwp=transport=dt_socket,address=localhost:9009,server=y,suspend=y $* diff --git a/bin/jjssecure b/bin/jjssecure deleted file mode 100644 index db6bdfc4..00000000 --- a/bin/jjssecure +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License version 2 only, as -# published by the Free Software Foundation. Oracle designates this -# particular file as subject to the "Classpath" exception as provided -# by Oracle in the LICENSE file that accompanied this code. -# -# This code is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# version 2 for more details (a copy is included in the LICENSE file that -# accompanied this code). -# -# You should have received a copy of the GNU General Public License version -# 2 along with this work; if not, write to the Free Software Foundation, -# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -# -# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -# or visit www.oracle.com if you need additional information or have any -# questions. -# - -[ -z "$JAVA_HOME" ] && echo "Please set JAVA_HOME" && exit 1; - -$JAVA_HOME/bin/java -Xms2G -Xmx2G -XX:-TieredCompilation -server -esa -ea -Djava.security.properties=`dirname $0`/../make/java.security.override -Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -XX:+HeapDumpOnOutOfMemoryError -Dnashorn.debug=true -Djava.lang.invoke.MethodHandle.DEBUG_NAMES=true -Dnashorn.home=`dirname $0`/.. -Djava.security.manager jdk.nashorn.tools.Shell $* diff --git a/bin/jjssecure.bat b/bin/jjssecure.bat deleted file mode 100644 index f8da10aa..00000000 --- a/bin/jjssecure.bat +++ /dev/null @@ -1,27 +0,0 @@ -rem -rem Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -rem DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -rem -rem This code is free software; you can redistribute it and/or modify it -rem under the terms of the GNU General Public License version 2 only, as -rem published by the Free Software Foundation. Oracle designates this -rem particular file as subject to the "Classpath" exception as provided -rem by Oracle in the LICENSE file that accompanied this code. -rem -rem This code is distributed in the hope that it will be useful, but WITHOUT -rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -rem version 2 for more details (a copy is included in the LICENSE file that -rem accompanied this code). -rem -rem You should have received a copy of the GNU General Public License version -rem 2 along with this work; if not, write to the Free Software Foundation, -rem Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -rem -rem Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -rem or visit www.oracle.com if you need additional information or have any -rem questions. -rem -@echo off - -java -Xms2G -Xmx2G -XX:-TieredCompilation -server -esa -ea -Djava.security.properties=%~dp0\..\make\java.security.override -Djava.ext.dirs=%~dp0\..\dist -XX:+HeapDumpOnOutOfMemoryError -Dnashorn.debug=true -Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -Dnashorn.home=%~dp0\.. -Djava.security.manager jdk.nashorn.tools.Shell diff --git a/bin/nashorn.bat b/bin/nashorn.bat deleted file mode 100644 index 2961ac69..00000000 --- a/bin/nashorn.bat +++ /dev/null @@ -1,27 +0,0 @@ -rem -rem Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -rem DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -rem -rem This code is free software; you can redistribute it and/or modify it -rem under the terms of the GNU General Public License version 2 only, as -rem published by the Free Software Foundation. Oracle designates this -rem particular file as subject to the "Classpath" exception as provided -rem by Oracle in the LICENSE file that accompanied this code. -rem -rem This code is distributed in the hope that it will be useful, but WITHOUT -rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -rem version 2 for more details (a copy is included in the LICENSE file that -rem accompanied this code). -rem -rem You should have received a copy of the GNU General Public License version -rem 2 along with this work; if not, write to the Free Software Foundation, -rem Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -rem -rem Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -rem or visit www.oracle.com if you need additional information or have any -rem questions. -rem -@echo off - -jrunscript -J-Xms2G -J-Xmx2G -J-XX:-TieredCompilation -J-server -J-esa -J-ea -J-Djava.ext.dirs=%~dp0\..\dist -J-XX:+HeapDumpOnOutOfMemoryError -J-Dnashorn.debug=true -J-Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -l nashorn diff --git a/bin/nashornsecure b/bin/nashornsecure deleted file mode 100644 index 77c7c529..00000000 --- a/bin/nashornsecure +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash -# -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License version 2 only, as -# published by the Free Software Foundation. Oracle designates this -# particular file as subject to the "Classpath" exception as provided -# by Oracle in the LICENSE file that accompanied this code. -# -# This code is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -# version 2 for more details (a copy is included in the LICENSE file that -# accompanied this code). -# -# You should have received a copy of the GNU General Public License version -# 2 along with this work; if not, write to the Free Software Foundation, -# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -# -# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -# or visit www.oracle.com if you need additional information or have any -# questions. -# - -[ -z "$JAVA_HOME" ] && echo "Please set JAVA_HOME" && exit 1; - -$JAVA_HOME/bin/jrunscript -J-Djava.security.properties=`dirname $0`/../make/java.security.override -J-Djava.security.manager -J-Xms2G -J-Xmx2G -J-XX:-TieredCompilation -J-server -J-esa -J-ea -J-Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -J-XX:+HeapDumpOnOutOfMemoryError -J-Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -J-Dnashorn.debug=true -l nashorn $* diff --git a/bin/nashornsecure.bat b/bin/nashornsecure.bat deleted file mode 100644 index 5a4eca63..00000000 --- a/bin/nashornsecure.bat +++ /dev/null @@ -1,27 +0,0 @@ -rem -rem Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -rem DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -rem -rem This code is free software; you can redistribute it and/or modify it -rem under the terms of the GNU General Public License version 2 only, as -rem published by the Free Software Foundation. Oracle designates this -rem particular file as subject to the "Classpath" exception as provided -rem by Oracle in the LICENSE file that accompanied this code. -rem -rem This code is distributed in the hope that it will be useful, but WITHOUT -rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -rem version 2 for more details (a copy is included in the LICENSE file that -rem accompanied this code). -rem -rem You should have received a copy of the GNU General Public License version -rem 2 along with this work; if not, write to the Free Software Foundation, -rem Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -rem -rem Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -rem or visit www.oracle.com if you need additional information or have any -rem questions. -rem -@echo off - -jrunscript -J-Djava.security.properties=%~dp0\..\make\java.security.override -J-Djava.security.manager -J-Xms2G -J-Xmx2G -J-XX:-TieredCompilation -J-server -J-esa -J-ea -J-Djava.ext.dirs=%~dp0\..\dist -J-XX:+HeapDumpOnOutOfMemoryError -J-Dnashorn.debug=true -J-Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -l nashorn diff --git a/bin/nashorn b/bin/run_octane.sh index da22be1f..a50137f0 100644 --- a/bin/nashorn +++ b/bin/run_octane.sh @@ -1,29 +1,51 @@ #!/bin/bash # -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2010, 2014, 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. -# +# 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. # -[ -z "$JAVA_HOME" ] && echo "Please set JAVA_HOME" && exit 1; +LOG="./octane_$(date|sed "s/ /_/g"|sed "s/:/_/g").log" + +run_one() { + sh ../bin/runopt.sh -scripting ../test/script/basic/run-octane.js -- $1 --verbose --iterations 25 | tee -a $LOG +} + +if [ -z $1 ]; then + + run_one "box2d" + run_one "code-load" + run_one "crypto" + run_one "deltablue" + run_one "earley-boyer" + run_one "gbemu" + run_one "mandreel" + run_one "navier-stokes" + run_one "pdfjs" + run_one "raytrace" + run_one "regexp" + run_one "richards" + run_one "splay" + run_one "typescript" + run_one "zlib" -$JAVA_HOME/bin/jrunscript -J-Xms2G -J-Xmx2G -J-XX:-TieredCompilation -J-server -J-esa -J-ea -J-Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -J-XX:+HeapDumpOnOutOfMemoryError -J-Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -J-Dnashorn.debug=true -l nashorn $* +else + run_one $1 +fi diff --git a/bin/runopt.sh b/bin/runopt.sh new file mode 100644 index 00000000..6b26e287 --- /dev/null +++ b/bin/runopt.sh @@ -0,0 +1,107 @@ +#!/bin/sh +# +# Copyright (c) 2010, 2014, 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 is a helper script to evaluate nashorn with optimistic types +# it produces a flight recording for every run, and uses the best +# known flags for performance for the current configration +########################################################################################### + +# Flags to instrument lambdaform computation, caching, interpretation and compilation +# Default compile threshold for lambdaforms is 30 +#FLAGS="-Djava.lang.invoke.MethodHandle.COMPILE_THRESHOLD=3 -Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true -Djava.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE=true -Djava.lang.invoke.MethodHandle.TRACE_INTERPRETER=true" + + +# Flags to run trusted tests from the Nashorn test suite +#FLAGS="-Djava.security.manager -Djava.security.policy=../build/nashorn.policy -Dnashorn.debug" + + +# Unique timestamped file name for JFR recordings. For JFR, we also have to +# crank up the stack cutoff depth to 1024, because of ridiculously long lambda form +# stack traces. +# +# It is also recommended that you go into $JAVA_HOME/jre/lib/jfr/default.jfc and +# set the "method-sampling-interval" Normal and Maximum sample time as low as you +# can go (10 ms on most platforms). The default is normally higher. The increased +# sampling overhead is usually negligible for Nashorn runs, but the data is better + +JFR_FILENAME="./nashorn_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" + + +# Directory where to look for nashorn.jar in a dist folder. The default is "..", assuming +# that we run the script from the make dir +DIR=.. +NASHORN_JAR=$DIR/dist/nashorn.jar + + +# The built Nashorn jar is placed first in the bootclasspath to override the JDK +# nashorn.jar in $JAVA_HOME/jre/lib/ext. Thus, we also need -esa, as assertions in +# nashorn count as system assertions in this configuration + +$JAVA_HOME/bin/java \ +$FLAGS \ +-ea \ +-esa \ +-Xbootclasspath/p:$NASHORN_JAR \ +-Xms2G -Xmx2G \ +-cp $CLASSPATH:../build/test/classes/ \ +jdk.nashorn.tools.Shell ${@} + +# Below are flags that may come in handy, but aren't used for default runs + + +# Type profiling default level is 111, 222 adds some compile time, but produces better code. +# -XX:TypeProfileLevel=222 \ + + +# Testing out new code optimizations using the generic hotspot "new code" parameter +#-XX:+UnlockDiagnosticVMOptions \ +#-XX:+UseNewCode \ + + +# Type specialization and math intrinsic replacement should be enabled by default in 8u20 and nine, +# keeping this flag around for experimental reasons. Replace + with - to switch it off +#-XX:+UseTypeSpeculation \ + + +# Same with math intrinsics. They should be enabled by default in 8u20 and 9 +#-XX:+UseMathExactIntrinsics \ + + +# Add -Dnashorn.time to time the compilation phases. +#-Dnashorn.time \ + + +# Add ShowHiddenFrames to get lambda form internals on the stack traces +#-XX:+ShowHiddenFrames \ + + +# Add print optoassembly to get an asm dump. This requires 1) a debug build, not product, +# That tired compilation is switched off, for C2 only output and that the number of +# compiler threads is set to 1 for determinsm. +#-XX:+PrintOptoAssembly -XX:-TieredCompilation -XX:CICompilerCount=1 \ + +# Tier compile threasholds. Default value is 10. (1-100 is useful for experiments) +# -XX:IncreaseFirstTierCompileThresholdAt=XX + diff --git a/bin/verbose_octane.bat b/bin/verbose_octane.bat deleted file mode 100644 index ab9e4846..00000000 --- a/bin/verbose_octane.bat +++ /dev/null @@ -1,59 +0,0 @@ -rem -rem Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -rem DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -rem -rem This code is free software; you can redistribute it and/or modify it -rem under the terms of the GNU General Public License version 2 only, as -rem published by the Free Software Foundation. -rem -rem This code is distributed in the hope that it will be useful, but WITHOUT -rem ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or -rem FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -rem version 2 for more details (a copy is included in the LICENSE file that -rem accompanied this code). -rem -rem You should have received a copy of the GNU General Public License version -rem 2 along with this work; if not, write to the Free Software Foundation, -rem Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. -rem -rem Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA -rem or visit www.oracle.com if you need additional information or have any -rem questions. -rem -@echo off - -if "%JAVA_HOME%" neq "" ( - call :run "%JAVA_HOME%/bin/java" -) else ( - call :run java -) -goto :EOF - -:run -setlocal -set NASHORN_JAR=dist/nashorn.jar -set JVM_FLAGS=-Xms2G -Xmx2G -XX:-TieredCompilation -server -esa -ea -jar %NASHORN_JAR% -set JVM_FLAGS7=-Xbootclasspath/p:%NASHORN_JAR% %JVM_FLAGS% -set OCTANE_ARGS=--verbose --iterations 7 - -%1 -fullversion 2>&1 | findstr /L /C:"version ""1.7" -if %errorlevel% equ 0 ( - set CMD=%1 %JVM_FLAGS7% -) else ( - %1 -fullversion - set CMD=%1 %JVM_FLAGS% -) - -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/box2d.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/code-load.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/crypto.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/deltablue.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/gbemu.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/navier-stokes.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/pdfjs.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/raytrace.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/regexp.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/richards.js %OCTANE_ARGS% -%CMD% test/script/basic/run-octane.js -- test/script/external/octane/splay.js %OCTANE_ARGS% -endlocal -goto :EOF diff --git a/bin/verbose_octane.sh b/bin/verbose_octane.sh deleted file mode 100644 index 1895afed..00000000 --- a/bin/verbose_octane.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. -# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. -# -# This code is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License version 2 only, as -# published by the Free Software Foundation. -# -# 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. -# - -ITERS=$1 -if [ -z $ITERS ]; then - ITERS=7 -fi -NASHORN_JAR=dist/nashorn.jar -JVM_FLAGS="-Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -XX:+UnlockDiagnosticVMOptions -Dnashorn.unstable.relink.threshold=8 -Xms2G -Xmx2G -XX:+TieredCompilation -server -jar ${NASHORN_JAR}" -JVM_FLAGS7="-Xbootclasspath/p:${NASHORN_JAR} ${JVM_FLAGS}" -OCTANE_ARGS="--verbose --iterations ${ITERS}" - -BENCHMARKS=( "box2d.js" "code-load.js" "crypto.js" "deltablue.js" "earley-boyer.js" "gbemu.js" "navier-stokes.js" "pdfjs.js" "raytrace.js" "regexp.js" "richards.js" "splay.js" ) -# TODO mandreel.js has metaspace issues - -if [ ! -z $JAVA7_HOME ]; then - echo "running ${ITERS} iterations with java7 using JAVA_HOME=${JAVA7_HOME}..." - for BENCHMARK in "${BENCHMARKS[@]}" - do - CMD="${JAVA7_HOME}/bin/java ${JVM_FLAGS} test/script/basic/run-octane.js -- test/script/external/octane/${BENCHMARK} ${OCTANE_ARGS}" - $CMD - done -else - echo "no JAVA7_HOME set. skipping java7" -fi - -if [ ! -z $JAVA8_HOME ]; then - echo "running ${ITERS} iterations with java8 using JAVA_HOME=${JAVA8_HOME}..." - for BENCHMARK in "${BENCHMARKS[@]}" - do - CMD="${JAVA8_HOME}/bin/java ${JVM_FLAGS} test/script/basic/run-octane.js -- test/script/external/octane/${BENCHMARK} ${OCTANE_ARGS}" - $CMD - done -else - echo "no JAVA8_HOME set." -fi - -echo "Done" diff --git a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MemberInfo.java b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MemberInfo.java index e47f8b16..c8a929dc 100644 --- a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MemberInfo.java +++ b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MemberInfo.java @@ -319,7 +319,7 @@ public final class MemberInfo implements Cloneable { break; case FUNCTION: { final Type returnType = Type.getReturnType(javaDesc); - if (!isValidJSType(returnType)) { + if (!(isValidJSType(returnType) || Type.VOID_TYPE == returnType)) { error("return value of a @Function method should be a valid JS type, found " + returnType); } final Type[] argTypes = Type.getArgumentTypes(javaDesc); @@ -351,7 +351,7 @@ public final class MemberInfo implements Cloneable { break; case SPECIALIZED_FUNCTION: { final Type returnType = Type.getReturnType(javaDesc); - if (!isValidJSType(returnType)) { + if (!(isValidJSType(returnType) || Type.VOID_TYPE == returnType)) { error("return value of a @SpecializedFunction method should be a valid JS type, found " + returnType); } final Type[] argTypes = Type.getArgumentTypes(javaDesc); @@ -371,9 +371,8 @@ public final class MemberInfo implements Cloneable { error("first argument of a @Getter method should be of Object type, found: " + argTypes[0]); } - final Type returnType = Type.getReturnType(javaDesc); - if (!isJavaLangObject(returnType)) { - error("return type of a @Getter method should be Object, found: " + javaDesc); + if (Type.getReturnType(javaDesc).equals(Type.VOID_TYPE)) { + error("return type of getter should not be void"); } } break; @@ -413,6 +412,10 @@ public final class MemberInfo implements Cloneable { } } } + break; + + default: + break; } } @@ -451,7 +454,7 @@ public final class MemberInfo implements Cloneable { if (type.getSort() == Type.OBJECT) { try { - final Class clazz = Class.forName(type.getClassName(), false, myLoader); + final Class<?> clazz = Class.forName(type.getClassName(), false, myLoader); return ScriptObject.class.isAssignableFrom(clazz); } catch (final ClassNotFoundException cnfe) { return false; diff --git a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MethodGenerator.java b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MethodGenerator.java index 479d1d31..a8d6ae2b 100644 --- a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MethodGenerator.java +++ b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MethodGenerator.java @@ -413,7 +413,8 @@ public class MethodGenerator extends MethodVisitor { super.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", - "(Ljava/lang/String;)V", false); + "(Ljava/lang/String;)V", + false); } // print the object on the top of the stack @@ -426,6 +427,7 @@ public class MethodGenerator extends MethodVisitor { super.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", - "(Ljava/lang/Object;)V", false); + "(Ljava/lang/Object;)V", + false); } } diff --git a/docs/DEVELOPER_README b/docs/DEVELOPER_README index 9a2fffdf..fe140954 100644 --- a/docs/DEVELOPER_README +++ b/docs/DEVELOPER_README @@ -737,26 +737,6 @@ an implementation based on Joni, the regular expression engine used by the JRuby project. The default value for this flag is "joni" -SYSTEM PROPERTY: -Dnashorn.time - -This enables timers for various phases of script compilation. The timers -will be dumped when the Nashorn process exits. We see a percentage value -of how much time was spent not executing bytecode (i.e. compilation and -internal tasks) at the end of the report. - -Here is an example: - -[JavaScript Parsing] 61 ms -[Constant Folding] 11 ms -[Control Flow Lowering] 26 ms -[Type Attribution] 81 ms -[Range Analysis] 0 ms -[Code Splitting] 29 ms -[Type Finalization] 19 ms -[Bytecode Generation] 189 ms -[Code Installation] 7 ms -Total runtime: 508 ms (Non-runtime: 423 ms [83%]) - =============== 2. The loggers. =============== @@ -887,6 +867,34 @@ etc. It will also show the internal representation of respective field (Object in the normal case, unless running with the dual field representation) +* time + +This enables timers for various phases of script compilation. The timers +will be dumped when the Nashorn process exits. We see a percentage value +of how much time was spent not executing bytecode (i.e. compilation and +internal tasks) at the end of the report. + +A finer level than "info" will show individual compilation timings as they +happen. + +Here is an example: + +[time] Accumulated complation phase Timings: +[time] +[time] 'JavaScript Parsing' 1076 ms +[time] 'Constant Folding' 159 ms +[time] 'Control Flow Lowering' 303 ms +[time] 'Program Point Calculation' 282 ms +[time] 'Builtin Replacement' 71 ms +[time] 'Code Splitting' 670 ms +[time] 'Symbol Assignment' 474 ms +[time] 'Scope Depth Computation' 249 ms +[time] 'Optimistic Type Assignment' 186 ms +[time] 'Local Variable Type Calculation' 526 ms +[time] 'Bytecode Generation' 5177 ms +[time] 'Class Installation' 1854 ms +[time] +[time] Total runtime: 11994 ms (Non-runtime: 11027 ms [91%]) ======================= 3. Undocumented options @@ -914,11 +922,10 @@ A short summary follows: -cp, -classpath (-cp path. Specify where to find user class files.) - -co, --compile-only (Compile script without running. Exit after compilation) + -co, --compile-only (Compile without running.) param: [true|false] default: false - -d, --dump-debug-dir (specify a destination directory to dump class files. - This must be combined with the --compile-only option to work) + -d, --dump-debug-dir (specify a destination directory to dump class files.) param: <path> --debug-lines (Generate line number table in .class files.) @@ -954,10 +961,6 @@ A short summary follows: -h, -help (Print help for command line flags.) param: [true|false] default: false - --lazy-compilation (EXPERIMENTAL: Use lazy code generation strategies - do not compile - the entire script at once.) - param: [true|false] default: false - --loader-per-compile (Create a new class loader per compile.) param: [true|false] default: true @@ -965,16 +968,16 @@ A short summary follows: param: <locale> default: en-US --log (Enable logging of a given level for a given number of sub systems. - [for example: --log=fields:finest,codegen:info]) + [for example: --log=fields:finest,codegen:info].) param: <module:level>,* - -nj, --no-java (No Java support) + -nj, --no-java (Disable Java support.) param: [true|false] default: false - -nse, --no-syntax-extensions (No non-standard syntax extensions) + -nse, --no-syntax-extensions (Disallow non-standard syntax extensions.) param: [true|false] default: false - -nta, --no-typed-arrays (No Typed arrays support) + -nta, --no-typed-arrays (Disable typed arrays support.) param: [true|false] default: false --parse-only (Parse without compiling.) @@ -983,13 +986,15 @@ A short summary follows: --print-ast (Print abstract syntax tree.) param: [true|false] default: false - --print-code (Print bytecode.) - param: [true|false] default: false + -pc, --print-code (Print generated bytecode. If a directory is specified, nothing will + be dumped to stderr. Also, in that case, .dot files will be generated + for all functions or for the function with the specified name only.) + param: [dir:<output-dir>,function:<name>] --print-lower-ast (Print lowered abstract syntax tree.) param: [true|false] default: false - --print-lower-parse (Print the parse tree after lowering.) + -plp, --print-lower-parse (Print the parse tree after lowering.) param: [true|false] default: false --print-mem-usage (Print memory usage of IR after each compile stage.) @@ -998,7 +1003,7 @@ A short summary follows: --print-no-newline (Print function will not print new line char.) param: [true|false] default: false - --print-parse (Print the parse tree.) + -pp, --print-parse (Print the parse tree.) param: [true|false] default: false --print-symbols (Print the symbol table.) @@ -1007,21 +1012,13 @@ A short summary follows: -pcs, --profile-callsites (Dump callsite profile data.) param: [true|false] default: false - --range-analysis (EXPERIMENTAL: Do range analysis using known compile time types, - and try to narrow number types) - param: [true|false] default: false - -scripting (Enable scripting features.) param: [true|false] default: false - --specialize-calls (EXPERIMENTAL: Specialize all or a set of method according - to callsite parameter types) - param: [=function_1,...,function_n] - - --stderr (Redirect stderr to a filename or to another tty, e.g. stdout) + --stderr (Redirect stderr to a filename or to another tty, e.g. stdout.) param: <output console> - --stdout (Redirect stdout to a filename or to another tty, e.g. stderr) + --stdout (Redirect stdout to a filename or to another tty, e.g. stderr.) param: <output console> -strict (Run scripts in strict mode.) @@ -1031,7 +1028,7 @@ A short summary follows: param: <timezone> default: Europe/Stockholm -tcs, --trace-callsites (Enable callsite trace mode. Options are: miss [trace callsite misses] - enterexit [trace callsite enter/exit], objects [print object properties]) + enterexit [trace callsite enter/exit], objects [print object properties].) param: [=[option,]*] --verify-code (Verify byte code before running.) diff --git a/make/build-benchmark.xml b/make/build-benchmark.xml index ac87fc12..ae76718f 100644 --- a/make/build-benchmark.xml +++ b/make/build-benchmark.xml @@ -1,381 +1,333 @@ <?xml version="1.0" encoding="UTF-8"?> + <!-- - Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - - This code is free software; you can redistribute it and/or modify it - under the terms of the GNU General Public License version 2 only, as - published by the Free Software Foundation. - - 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. + Copyright (c) 2010, 2014, 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. --> -<project name="nashorn-benchmarks" default="all" basedir=".."> - <target name="octane-init" depends="jar"> - <property name="octane-tests" value="box2d code-load crypto deltablue earley-boyer gbemu navier-stokes pdfjs raytrace regexp richards splay"/> - </target> - - <!-- ignore benchmarks where rhino crashes --> - <target name="octane-init-rhino" depends="jar"> - <property name="octane-tests" value="box2d code-load crypto deltablue earley-boyer gbemu navier-stokes raytrace regexp richards splay"/> - </target> + +<project + name="nashorn-benchmarks" + default="all" + basedir=".." + xmlns:if="ant:if"> + + <!-- + Below are the octane benchmarks that should be run. + The ones that are excluded, as Nashorn currently has + some issues with them (functionality or performance) + are commented out + --> <!-- box2d --> - <target name="octane-box2d" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="box2d"/> - </antcall> + <target name="octane-box2d" depends="octane-box2d-nashorn"/> + <target name="octane-box2d-nashorn" depends="jar"> + <run-one cond="octane.benchmark.box2d" runtime="nashorn"/> </target> - <target name="octane-box2d-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="box2d"/> - </antcall> + <run-one cond="octane.benchmark.box2d" runtime="v8"/> </target> - <target name="octane-box2d-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="box2d"/> - </antcall> + <run-one cond="octane.benchmark.box2d" runtime="rhino"/> </target> - - <!-- code-load --> - <target name="octane-code-load" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="code-load"/> - </antcall> + <!-- code-load --> + <target name="octane-code-load" depends="octane-code-load-nashorn"/> + <target name="octane-code-load-nashorn" depends="jar"> + <run-one cond="octane.benchmark.code-load" runtime="nashorn"/> </target> - <target name="octane-code-load-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="code-load"/> - </antcall> + <run-one cond="octane.benchmark.code-load" runtime="v8"/> </target> - <target name="octane-code-load-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="code-load"/> - </antcall> + <run-one cond="octane.benchmark.code-load" runtime="rhino"/> </target> - <!-- crypto --> - <target name="octane-crypto" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="crypto"/> - </antcall> + <target name="octane-crypto" depends="octane-crypto-nashorn"/> + <target name="octane-crypto-nashorn" depends="jar"> + <run-one cond="octane.benchmark.crypto" runtime="nashorn"/> </target> - <target name="octane-crypto-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="crypto"/> - </antcall> + <run-one cond="octane.benchmark.crypto" runtime="v8"/> </target> - <target name="octane-crypto-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="crypto"/> - </antcall> + <run-one cond="octane.benchmark.crypto" runtime="rhino"/> </target> - <!-- deltablue --> - <target name="octane-deltablue" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="deltablue"/> - </antcall> + <target name="octane-deltablue" depends="octane-deltablue-nashorn"/> + <target name="octane-deltablue-nashorn" depends="jar"> + <run-one cond="octane.benchmark.deltablue" runtime="nashorn"/> </target> - <target name="octane-deltablue-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="deltablue"/> - </antcall> + <run-one cond="octane.benchmark.deltablue" runtime="v8"/> </target> - <target name="octane-deltablue-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="deltablue"/> - </antcall> + <run-one cond="octane.benchmark.deltablue" runtime="rhino"/> </target> - <!-- earley-boyer --> - <target name="octane-earley-boyer" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="earley-boyer"/> - </antcall> + <target name="octane-earley-boyer" depends="octane-earley-boyer-nashorn"/> + <target name="octane-earley-boyer-nashorn" depends="jar"> + <run-one cond="octane.benchmark.earley-boyer" runtime="nashorn"/> </target> - <target name="octane-earley-boyer-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="earley-boyer"/> - </antcall> + <run-one cond="octane.benchmark.earley-boyer" runtime="v8"/> </target> - <target name="octane-earley-boyer-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="earley-boyer"/> - </antcall> + <run-one cond="octane.benchmark.earley-boyer" runtime="rhino"/> </target> - - - <!-- gbemu --> - <target name="octane-gbemu" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="gbemu"/> - </antcall> + + <!-- gbemu --> + <target name="octane-gbemu" depends="octane-gbemu-nashorn"/> + <target name="octane-gbemu-nashorn" depends="jar"> + <run-one cond="octane.benchmark.gbemu" runtime="nashorn"/> </target> - <target name="octane-gbemu-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="gbemu"/> - </antcall> + <run-one cond="octane.benchmark.gbemu" runtime="v8"/> </target> - <target name="octane-gbemu-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="gbemu"/> - </antcall> + <run-one cond="octane.benchmark.gbemu" runtime="rhino"/> </target> - - <!-- mandreel --> - <target name="octane-mandreel" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="mandreel"/> - </antcall> + <!-- mandreel --> + <target name="octane-mandreel" depends="octane-mandreel-nashorn"/> + <target name="octane-mandreel-nashorn" depends="jar"> + <run-one cond="octane.benchmark.mandreel" runtime="nashorn"/> </target> - <target name="octane-mandreel-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="mandreel"/> - </antcall> + <run-one cond="octane.benchmark.mandreel" runtime="v8"/> </target> - <target name="octane-mandreel-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="mandreel"/> - </antcall> + <run-one cond="octane.benchmark.mandreel" runtime="rhino"/> </target> - <!-- navier-stokes --> - <target name="octane-navier-stokes" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="navier-stokes"/> - </antcall> + <target name="octane-navier-stokes" depends="octane-navier-stokes-nashorn"/> + <target name="octane-navier-stokes-nashorn" depends="jar"> + <run-one cond="octane.benchmark.navier-stokes" runtime="nashorn"/> </target> - <target name="octane-navier-stokes-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="navier-stokes"/> - </antcall> + <run-one cond="octane.benchmark.navier-stokes" runtime="v8"/> </target> - <target name="octane-navier-stokes-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="navier-stokes"/> - </antcall> + <run-one cond="octane.benchmark.navier-stokes" runtime="rhino"/> </target> - - <!-- pdfjs --> - <target name="octane-pdfjs" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="pdfjs"/> - </antcall> + <!-- pdfjs --> + <target name="octane-pdfjs" depends="octane-pdfjs-nashorn"/> + <target name="octane-pdfjs-nashorn" depends="jar"> + <run-one cond="octane.benchmark.pdfjs" runtime="nashorn"/> </target> - <target name="octane-pdfjs-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="pdfjs"/> - </antcall> + <run-one cond="octane.benchmark.pdfjs" runtime="v8"/> </target> - <target name="octane-pdfjs-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="pdfjs"/> - </antcall> + <run-one cond="octane.benchmark.pdfjs" runtime="rhino"/> </target> - <!-- raytrace --> - <target name="octane-raytrace" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="raytrace"/> - </antcall> + <target name="octane-raytrace" depends="octane-raytrace-nashorn"/> + <target name="octane-raytrace-nashorn" depends="jar"> + <run-one cond="octane.benchmark.raytrace" runtime="nashorn"/> </target> - <target name="octane-raytrace-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="raytrace"/> - </antcall> + <run-one cond="octane.benchmark.raytrace" runtime="v8"/> </target> - <target name="octane-raytrace-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="raytrace"/> - </antcall> + <run-one cond="octane.benchmark.raytrace" runtime="rhino"/> </target> - <!-- regexp --> - <target name="octane-regexp" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="regexp"/> - </antcall> + <target name="octane-regexp" depends="octane-regexp-nashorn"/> + <target name="octane-regexp-nashorn" depends="jar"> + <run-one cond="octane.benchmark.regexp" runtime="nashorn"/> </target> - <target name="octane-regexp-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="regexp"/> - </antcall> + <run-one cond="octane.benchmark.regexp" runtime="v8"/> </target> - <target name="octane-regexp-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="regexp"/> - </antcall> + <run-one cond="octane.benchmark.regexp" runtime="rhino"/> </target> - <!-- richards --> - <target name="octane-richards" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="richards"/> - </antcall> + <target name="octane-richards" depends="octane-richards-nashorn"/> + <target name="octane-richards-nashorn" depends="jar"> + <run-one cond="octane.benchmark.richards" runtime="nashorn"/> </target> - <target name="octane-richards-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="richards"/> - </antcall> + <run-one cond="octane.benchmark.richards" runtime="v8"/> </target> - <target name="octane-richards-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="richards"/> - </antcall> + <run-one cond="octane.benchmark.richards" runtime="rhino"/> </target> - <!-- splay --> - <target name="octane-splay" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="splay"/> - </antcall> + <target name="octane-splay" depends="octane-splay-nashorn"/> + <target name="octane-splay-nashorn" depends="jar"> + <run-one cond="octane.benchmark.splay" runtime="nashorn"/> </target> - <target name="octane-splay-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-tests" value="splay"/> - </antcall> + <run-one cond="octane.benchmark.splay" runtime="v8"/> </target> - <target name="octane-splay-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="splay"/> - </antcall> + <run-one cond="octane.benchmark.splay" runtime="rhino"/> </target> - <!-- splay --> - <target name="octane-typescript" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="typescript"/> - </antcall> + <!-- typescript --> + <target name="octane-typescript" depends="octane-typescript-nashorn"/> + <target name="octane-typescript-nashorn" depends="jar"> + <run-one cond="octane.benchmark.typescript" runtime="nashorn"/> </target> - <target name="octane-typescript-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-typescript" value="typescript"/> - </antcall> + <run-one cond="octane.benchmark.typescript" runtime="v8"/> </target> - <target name="octane-typescript-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="typescript"/> - </antcall> + <run-one cond="octane.benchmark.typescript" runtime="rhino"/> </target> <!-- zlib --> - <target name="octane-zlib" depends="jar"> - <antcall target="run-octane"> - <param name="octane-tests" value="zlib"/> - </antcall> + <target name="octane-zlib" depends="octane-zlib-nashorn"/> + <target name="octane-zlib-nashorn" depends="jar"> + <run-one cond="octane.benchmark.zlib" runtime="nashorn"/> </target> - <target name="octane-zlib-v8" depends="jar"> - <antcall target="run-octane-v8"> - <param name="octane-typescript" value="zlib"/> - </antcall> + <run-one cond="octane.benchmark.zlib" runtime="v8"/> </target> - <target name="octane-zlib-rhino" depends="jar"> - <antcall target="run-octane-rhino"> - <param name="octane-tests" value="zlib"/> - </antcall> - </target> - - <!-- run octane benchmarks in a single process --> - <target name="octane-single-process" depends="octane-init"> - <antcall target="run-octane"/> - </target> - - <!-- zlib excluded due to missing implementation of 'read' --> - <target name="octane-separate-process" depends= - "octane-box2d, octane-code-load, octane-crypto, - octane-deltablue, octane-earley-boyer, octane-gbemu, - octane-mandreel, octane-navier-stokes, octane-pdfjs, - octane-raytrace, octane-regexp, octane-richards, - octane-splay, octane-typescript"/> - - <target name="--single-process" unless="${octane-test-sys-prop.separate.process}"> - <antcall target="octane-single-process"/> - </target> - <target name="--separate-process" if="${octane-test-sys-prop.separate.process}"> - <antcall target="octane-separate-process"/> - </target> - - <!-- run 'octane' in single or separate processes based on config --> - <target name="octane" depends="init, --single-process, --separate-process"/> + <run-one cond="octane.benchmark.zlib" runtime="rhino"/> + </target> + + <!-- + Benchmark runners for one or more benchmarks, single + or multiple process + --> + + <target name="octane-process-separate" if="${octane-test-sys-prop.separate.process}"> + <echo message="Running each benchmark in separate processes, starting new JVMs for each."/> + <script language="javascript"><![CDATA[ + var props = []; + + for (var prop in project.getProperties()) { + if (prop.startsWith("octane.benchmark.")) { + props.push(prop); + } + } + + //sort benchmark props in alphabetical order by name + props.sort(function(a, b) { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } + }); + + var runtime = project.getProperty("runtime"); + + for (var i in props) { + var task = project.createTask("run-one"); + // workaround for https://issues.apache.org/bugzilla/show_bug.cgi?id=53831, still not fixed + if (task.getOwningTarget() == null) { + task.setOwningTarget(self.getOwningTarget()); + } + var prop = props[i]; + task.setDynamicAttribute("cond", prop); + task.setDynamicAttribute("runtime", runtime); + task.perform(); + } + ]]></script> + </target> + + <target name="octane-process-single" unless="${octane-test-sys-prop.separate.process}"> + <echo message="Running all benchmarks in the same process."/> + <pathconvert property="octane.benchmarks" pathsep=" "> + <propertyset> + <propertyref prefix="octane.benchmark."/> + </propertyset> + </pathconvert> + <antcall target="run-octane${runtime}"> + <param name="octane-tests" value="${octane.benchmarks}"/> + </antcall> + </target> + + <!-- + run 'octane' in single or separate processes based on config + This uses nashorn as the default runtime + --> + <target name="octane-nashorn" depends="jar"> + <property name="runtime" value="nashorn"/> + <antcall target="octane-process-separate"/> + <antcall target="octane-process-single"/> + </target> + + <!-- alias for 'octane' --> + <target name="octane" depends="octane-nashorn"/> <!-- run octane benchmarks using octane as runtime --> - <target name="octane-v8" depends="octane-init"> - <antcall target="run-octane-v8"/> + <target name="octane-v8" depends="jar"> + <property name="runtime" value="v8"/> + <antcall target="octane-process-separate"/> + <antcall target="octane-process-single"/> </target> <!-- run octane benchmarks using Rhino as runtime --> - <target name="octane-rhino" depends="octane-init-rhino"> - <antcall target="run-octane-rhino"/> - </target> - - <target name="run-octane"> + <target name="octane-rhino" depends="jar"> + <property name="runtime" value="rhino"/> + <antcall target="octane-process-separate"/> + <antcall target="octane-process-single"/> + </target> + + <macrodef name="run-one"> + <attribute name="cond"/> + <attribute name="runtime" default=""/> + <sequential> + <antcall target="run-octane-@{runtime}" if:set="@{cond}"> + <param name="octane-tests" value="${@{cond}}"/> + </antcall> + </sequential> + </macrodef> + + <target name="run-octane-nashorn"> <java classname="${nashorn.shell.tool}" classpath="${run.test.classpath}" fork="true" dir="."> <jvmarg line="${ext.class.path}"/> <jvmarg line="${run.test.jvmargs.octane} -Xms${run.test.xms} -Xmx${run.test.xmx}"/> + <!-- pass on all properties prefixed with 'nashorn' to the runtime --> + <syspropertyset> + <propertyref prefix="nashorn."/> + </syspropertyset> <arg value="${octane-test-sys-prop.test.js.framework}"/> + <arg value="-scripting"/> <arg value="--"/> <arg value="${octane-tests}"/> <arg value="--runtime"/> - <arg value="Nashorn"/> + <arg value="nashorn"/> <arg value="--verbose"/> - <arg value="--iterations 8"/> + <arg value="--iterations ${octane.iterations}"/> </java> </target> @@ -383,11 +335,11 @@ <exec executable="${v8.shell}"> <arg value="${octane-test-sys-prop.test.js.framework}"/> <arg value="--"/> - <arg value="${octane-tests}"/> + <arg value="${octane-tests}"/> <arg value="--runtime"/> <arg value="v8"/> <arg value="--verbose"/> - <arg value="--iterations 8"/> + <arg value="--iterations ${octane.iterations}"/> </exec> </target> @@ -397,12 +349,14 @@ fork="true" dir="."> <jvmarg line="${run.test.jvmargs.octane} -Xms${run.test.xms} -Xmx${run.test.xmx}"/> + <arg value="-opt"/> + <arg value="9"/> <arg value="${octane-test-sys-prop.test.js.framework}"/> <arg value="${octane-tests}"/> <arg value="--runtime"/> - <arg value="Rhino"/> + <arg value="rhino"/> <arg value="--verbose"/> - <arg value="--iterations 8"/> + <arg value="--iterations ${octane.iterations}"/> </java> </target> @@ -413,18 +367,22 @@ <arg value="${octane-tests}/"/> </exec> </target> - + <target name="sunspider-init" depends="jar"> <fileset id="sunspider-set" - dir="${sunspider-test-sys-prop.test.js.roots}" - excludes="${sunspider-test-sys-prop.test.js.exclude.list}"> + dir="${sunspider-test-sys-prop.test.js.roots}" + excludes="${sunspider-test-sys-prop.test.js.exclude.list}"> <include name="**/*.js"/> </fileset> <pathconvert pathsep=" " property="sunspider-tests" refid="sunspider-set"/> </target> + <!--- SUNSPIDER JOB BELOW --> + <!-- run sunspider with Nashorn --> - <target name="sunspider" depends="sunspider-init"> + <target name="sunspider" depends="sunspider-nashorn"/> + + <target name="sunspider-nashorn" depends="sunspider-init"> <java classname="${nashorn.shell.tool}" classpath="${run.test.classpath}" fork="true" @@ -436,6 +394,9 @@ <arg value="${sunspider-test-sys-prop.test.js.framework}"/> <arg value="--"/> <arg value="${sunspider-tests}/"/> + <arg value="--verbose"/> + <arg value="--times"/> + <arg value="${sunspider.iterations}"/> </java> </target> @@ -445,6 +406,9 @@ <arg value="${sunspider-test-sys-prop.test.js.framework}"/> <arg value="--"/> <arg value="${sunspider-tests}/"/> + <arg value="--verbose"/> + <arg value="--times"/> + <arg value="${sunspider.iterations}"/> </exec> </target> @@ -455,8 +419,13 @@ fork="true" dir="."> <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx}"/> + <arg value="-opt"/> + <arg value="9"/> <arg value="${sunspider-test-sys-prop.test.js.framework}"/> <arg value="${sunspider-tests}/"/> + <arg value="--verbose"/> + <arg value="--times"/> + <arg value="${sunspider.iterations}"/> </java> </target> diff --git a/make/build-nasgen.xml b/make/build-nasgen.xml index 9dca5505..16094cc9 100644 --- a/make/build-nasgen.xml +++ b/make/build-nasgen.xml @@ -36,11 +36,13 @@ <pathelement location="${basedir}/jcov2/lib/jcov_j2se_rt.jar"/> <pathelement location="${basedir}/buildtools/nasgen/dist/nasgen.jar"/> <pathelement path="${basedir}/build/classes"/> + <pathelement location="${dist.dir}/nasgen.jar"/> + <pathelement path="${build.dir}/classes"/> </classpath> <jvmarg value="-Djava.ext.dirs="/> - <arg value="${basedir}/build/classes"/> + <arg value="${build.dir}/classes"/> <arg value="jdk.nashorn.internal.objects"/> - <arg value="${basedir}/build/classes"/> + <arg value="${build.dir}/classes"/> </java> </target> diff --git a/make/build.xml b/make/build.xml index 3b30a089..92d6f5dd 100644 --- a/make/build.xml +++ b/make/build.xml @@ -1,4 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> + <!-- Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -21,12 +22,11 @@ or visit www.oracle.com if you need additional information or have any questions. --> + <project name="nashorn" default="test" basedir=".."> <import file="build-nasgen.xml"/> - <import file="build-benchmark.xml"/> <import file="code_coverage.xml"/> - <target name="init-conditions"> <!-- loading locally defined resources and properties. NB they owerwrite default ones defined later --> <property file="${user.home}/.nashorn.project.local.properties"/> @@ -51,7 +51,7 @@ <available property="testng.available" file="${file.reference.testng.jar}"/> <!-- check if Jemmy ang testng.jar are avaiable --> <condition property="jemmy.jfx.testng.available" value="true"> - <and> + <and> <available file="${file.reference.jemmyfx.jar}"/> <available file="${file.reference.jemmycore.jar}"/> <available file="${file.reference.jemmyawtinput.jar}"/> @@ -69,13 +69,16 @@ <condition property="exclude.list" value="./exclude/exclude_list_cc.txt" else="./exclude/exclude_list.txt"> <istrue value="${make.code.coverage}" /> </condition> + + <condition property="jfr.options" value="${run.test.jvmargs.jfr}" else=""> + <istrue value="${jfr}"/> + </condition> </target> <target name="init" depends="init-conditions, init-cc"> - <!-- extends jvm args --> - <property name="run.test.jvmargs" value="${run.test.jvmargs.main} ${run.test.cc.jvmargs}"/> - <property name="run.test.jvmargs.octane" value="${run.test.jvmargs.octane.main} ${run.test.cc.jvmargs}" /> + <property name="run.test.jvmargs" value="${run.test.jvmargs.main} ${run.test.cc.jvmargs} ${jfr.options}"/> + <property name="run.test.jvmargs.octane" value="${run.test.jvmargs.octane.main} ${run.test.cc.jvmargs} ${jfr.options}"/> <echo message="run.test.jvmargs=${run.test.jvmargs}"/> <echo message="run.test.jvmargs.octane=${run.test.jvmargs.octane}"/> @@ -122,8 +125,7 @@ encoding="${javac.encoding}" includeantruntime="false" fork="true"> <compilerarg value="-J-Djava.ext.dirs="/> - <compilerarg value="-Xlint:unchecked"/> - <compilerarg value="-Xlint:deprecation"/> + <compilerarg value="-Xlint:all"/> <compilerarg value="-XDignore.symbol.file"/> <compilerarg value="-Xdiags:verbose"/> </javac> @@ -291,6 +293,10 @@ <target name="generate-policy-file" depends="prepare"> <echo file="${build.dir}/nashorn.policy"> +grant codeBase "file:/${toString:nashorn.ext.path}/nashorn.jar" { + permission java.security.AllPermission; +}; + grant codeBase "file:/${basedir}/${nashorn.internal.tests.jar}" { permission java.security.AllPermission; }; @@ -298,6 +304,14 @@ grant codeBase "file:/${basedir}/${nashorn.internal.tests.jar}" { grant codeBase "file:/${basedir}/${file.reference.testng.jar}" { permission java.security.AllPermission; }; +//// in case of absolute path: +grant codeBase "file:/${nashorn.internal.tests.jar}" { + permission java.security.AllPermission; +}; + +grant codeBase "file:/${file.reference.testng.jar}" { + permission java.security.AllPermission; +}; grant codeBase "file:/${basedir}/test/script/trusted/*" { permission java.security.AllPermission; @@ -330,6 +344,10 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { permission java.lang.RuntimePermission "nashorn.JavaReflection"; }; +grant codeBase "file:/${basedir}/test/script/markdown.js" { + permission java.io.FilePermission "${basedir}/test/script/external/showdown/-", "read"; +}; + </echo> <replace file="${build.dir}/nashorn.policy"><replacetoken>\</replacetoken><replacevalue>/</replacevalue></replace> <!--hack for Windows - to make URLs with normal path separators --> @@ -345,6 +363,7 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <available file="${test.external.dir}/yui" property="test-sys-prop.external.yui"/> <available file="${test.external.dir}/jquery" property="test-sys-prop.external.jquery"/> <available file="${test.external.dir}/test262" property="test-sys-prop.external.test262"/> + <available file="${test.external.dir}/showdown" property="test-sys-prop.external.markdown"/> </target> <target name="check-testng" unless="testng.available"> @@ -380,7 +399,8 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <testng outputdir="${build.nosecurity.test.results.dir}" classfilesetref="test.nosecurity.classes" verbose="${testng.verbose}" haltonfailure="true" useDefaultListeners="false" listeners="${testng.listeners}" workingDir="${basedir}"> <jvmarg line="${ext.class.path}"/> - <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx}"/> + <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} -Dbuild.dir=${build.dir}"/> + <sysproperty key="nashorn.jar" value="${dist.dir}/nashorn.jar"/> <propertyset> <propertyref prefix="nashorn."/> </propertyset> @@ -401,9 +421,12 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <testng outputdir="${build.test.results.dir}" classfilesetref="test.classes" verbose="${testng.verbose}" haltonfailure="true" useDefaultListeners="false" listeners="${testng.listeners}" workingDir="${basedir}"> <jvmarg line="${ext.class.path}"/> - <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs}"/> + <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs} -Dbuild.dir=${build.dir}"/> <jvmarg line="${debug.test.jvmargs}"/> <propertyset> + <propertyref prefix="nashorn."/> + </propertyset> + <propertyset> <propertyref prefix="test-sys-prop."/> <mapper from="test-sys-prop.*" to="*" type="glob"/> </propertyset> @@ -416,18 +439,6 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <target name="test" depends="jar, -test-classes-all,-test-classes-single, check-testng, check-external-tests, compile-test, generate-policy-file, -test-security, -test-nosecurity" if="testng.available"/> - <target name="test-basicparallel" depends="jar, check-testng, check-external-tests, compile-test, generate-policy-file"> - <!-- use just build.test.classes.dir to avoid referring to TestNG --> - <java classname="${parallel.test.runner}" dir="${basedir}" classpath="${build.test.classes.dir}" failonerror="true" fork="true"> - <jvmarg line="${ext.class.path}"/> - <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs}"/> - <syspropertyset> - <propertyref prefix="test-sys-prop."/> - <mapper type="glob" from="test-sys-prop.*" to="*"/> - </syspropertyset> - </java> - </target> - <target name="check-jemmy.jfx.testng" unless="jemmy.jfx.testng.available"> <echo message="WARNING: Jemmy or JavaFX or TestNG not available, will not run tests. Please copy testng.jar, JemmyCore.jar, JemmyFX.jar, JemmyAWTInput.jar under test${file.separator}lib directory. And make sure you have jfxrt.jar in ${java.home}${file.separator}lib${file.separator}ext dir."/> </target> @@ -436,19 +447,19 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <fileset id="test.classes" dir="${build.test.classes.dir}"> <include name="**/framework/*Test.class"/> </fileset> - + <copy file="${file.reference.jfxrt.jar}" todir="dist"/> - + <condition property="jfx.prism.order" value="-Dprism.order=j2d" else=" "> - <not> + <not> <os family="mac"/> </not> - </condition> - + </condition> + <testng outputdir="${build.test.results.dir}" classfilesetref="test.classes" verbose="${testng.verbose}" haltonfailure="true" useDefaultListeners="false" listeners="${testng.listeners}" workingDir="${basedir}"> <jvmarg line="${ext.class.path}"/> - <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx}"/> + <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} -Dbuild.dir=${build.dir}"/> <propertyset> <propertyref prefix="testjfx-test-sys-prop."/> <mapper from="testjfx-test-sys-prop.*" to="*" type="glob"/> @@ -459,7 +470,26 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { </classpath> </testng> </target> - + + <target name="testmarkdown" depends="jar, check-testng, check-external-tests, compile-test, generate-policy-file" if="testng.available"> + <fileset id="test.classes" dir="${build.test.classes.dir}"> + <include name="**/framework/*Test.class"/> + </fileset> + + <testng outputdir="${build.test.results.dir}" classfilesetref="test.classes" + verbose="${testng.verbose}" haltonfailure="true" useDefaultListeners="false" listeners="${testng.listeners}" workingDir="${basedir}"> + <jvmarg line="${ext.class.path}"/> + <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs} -Dbuild.dir=${build.dir}"/> + <propertyset> + <propertyref prefix="testmarkdown-test-sys-prop."/> + <mapper from="testmarkdown-test-sys-prop.*" to="*" type="glob"/> + </propertyset> + <classpath> + <pathelement path="${run.test.classpath}"/> + </classpath> + </testng> + </target> + <target name="test262" depends="jar, check-testng, check-external-tests, compile-test, generate-policy-file" if="testng.available"> <fileset id="test.classes" dir="${build.test.classes.dir}"> <include name="**/framework/*Test.class"/> @@ -468,7 +498,10 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <testng outputdir="${build.test.results.dir}" classfilesetref="test.classes" verbose="${testng.verbose}" haltonfailure="true" useDefaultListeners="false" listeners="${testng.listeners}" workingDir="${basedir}"> <jvmarg line="${ext.class.path}"/> - <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs}"/> + <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs} -Dbuild.dir=${build.dir}"/> + <propertyset> + <propertyref prefix="nashorn."/> + </propertyset> <propertyset> <propertyref prefix="test262-test-sys-prop."/> <mapper from="test262-test-sys-prop.*" to="*" type="glob"/> @@ -485,7 +518,9 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <!-- use just build.test.classes.dir to avoid referring to TestNG --> <java classname="${parallel.test.runner}" dir="${basedir}" fork="true"> <jvmarg line="${ext.class.path}"/> - <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs}"/> + <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs} -Dbuild.dir=${build.dir}"/> + <!-- avoid too many typeinfo cache files. Each script is run only once anyway --> + <jvmarg line="-Dnashorn.typeInfo.disabled=true"/> <classpath> <pathelement path="${run.test.classpath}"/> </classpath> @@ -496,6 +531,26 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { </java> </target> + <target name="testparallel" depends="test-parallel"/> + + <target name="test-parallel" depends="jar, check-testng, check-external-tests, compile-test, generate-policy-file" if="testng.available"> + <!-- use just build.test.classes.dir to avoid referring to TestNG --> + <java classname="${parallel.test.runner}" dir="${basedir}" + failonerror="true" + fork="true"> + <jvmarg line="${ext.class.path}"/> + <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs}"/> + <classpath> + <pathelement path="${run.test.classpath}"/> + <pathelement path="${build.test.classes.dir}"/> + </classpath> + <syspropertyset> + <propertyref prefix="test-sys-prop."/> + <mapper type="glob" from="test-sys-prop.*" to="*"/> + </syspropertyset> + </java> + </target> + <target name="all" depends="test, docs" description="Build, test and generate docs for nashorn"/> @@ -529,6 +584,8 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <!-- clone test262 git repo --> <exec executable="${git.executable}"> <arg value="clone"/> + <arg value="--branch"/> + <arg value="es5-tests"/> <arg value="https://github.com/tc39/test262"/> <arg value="${test.external.dir}/test262"/> </exec> @@ -604,6 +661,11 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <get src="http://yui.yahooapis.com/3.5.1/build/yui/yui.js" dest="${test.external.dir}/yui" skipexisting="true" ignoreerrors="true"/> <get src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js" dest="${test.external.dir}/yui" skipexisting="true" ignoreerrors="true"/> + <!-- showdown --> + <mkdir dir="${test.external.dir}/showdown"/> + <get src="https://raw.github.com/coreyti/showdown/master/src/showdown.js" dest="${test.external.dir}/showdown" skipexisting="true" ignoreerrors="true"/> + <get src="https://raw.github.com/coreyti/showdown/master/src/extensions/table.js" dest="${test.external.dir}/showdown" skipexisting="true" ignoreerrors="true"/> + </target> <!-- update external test suites that are pulled from source control systems --> @@ -619,4 +681,6 @@ grant codeBase "file:/${basedir}/test/script/basic/classloader.js" { <target name="alltests" depends="exit-if-no-testng, externals, update-externals, test, test262parallel, perf"/> + <import file="build-benchmark.xml"/> + </project> diff --git a/make/nbproject/ide-targets.xml b/make/nbproject/ide-targets.xml index 70b3e68f..d1e8135f 100644 --- a/make/nbproject/ide-targets.xml +++ b/make/nbproject/ide-targets.xml @@ -31,9 +31,10 @@ <classpath path="${run.test.classpath}"/> </nbjpdastart> <java classname="jdk.nashorn.tools.Shell" classpath="${run.test.classpath}" dir="samples" fork="true"> + <jvmarg line="-Dnashorn.optimistic"/> <jvmarg line="${ext.class.path}"/> <jvmarg line="${run.test.jvmargs}"/> - <arg value="test.js"/> + <arg value="../samples/test.js"/> <jvmarg value="-Xdebug"/> <jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/> </java> diff --git a/make/project.properties b/make/project.properties index 2abccdeb..800a6295 100644 --- a/make/project.properties +++ b/make/project.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2010, 2014, 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 @@ -53,6 +53,7 @@ parallel.test.runner=jdk.nashorn.internal.test.framework.ParallelTestRunner # test classes directory build.test.classes.dir=${build.dir}/test/classes + # nashorn test jar - internal tests jar and api tests jar nashorn.internal.tests.jar=${build.dir}/nashorn-internal-tests.jar nashorn.api.tests.jar=${build.dir}/nashorn-api-tests.jar @@ -60,6 +61,7 @@ nashorn.api.tests.jar=${build.dir}/nashorn-api-tests.jar # test results directory build.test.results.dir=${build.dir}/test/reports build.nosecurity.test.results.dir=${build.dir}/test/nosecurity/reports +build.nooptimistic.test.results.dir=${build.dir}/test/nooptimistic/reports # This directory is removed when the project is cleaned: dist.dir=dist @@ -72,6 +74,9 @@ fxshell.classes.dir = ${build.dir}/fxshell/classes fxshell.dir = tools/fxshell fxshell.jar = ${dist.dir}/nashornfx.jar +# configuration for java flight recorder +run.test.jvmargs.jfr=-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=${build.dir},stackdepth=128 + # jars refererred file.reference.testng.jar=test/lib/testng.jar @@ -122,6 +127,7 @@ test.external.dir=test/script/external test262.dir=${test.external.dir}/test262 test262.suite.dir=${test262.dir}/test/suite testjfx.dir=${test.script.dir}/jfx +testmarkdown.dir=${test.script.dir}/markdown test-sys-prop.test.dir=${test.dir} test-sys-prop.test.js.roots=${test.basic.dir} ${test.maptests.dir} ${test.error.dir} ${test.sandbox.dir} ${test.trusted.dir} @@ -163,21 +169,14 @@ test-sys-prop.test.js.unchecked.dir=${test262.dir} # test root for octane octane-test-sys-prop.test.js.roots=${test.external.dir}/octane/ -# run octane benchmars in separate processes? +# run octane benchmars in separate processes? (recommended) octane-test-sys-prop.separate.process=true # framework root for octane octane-test-sys-prop.test.js.framework=${test.basic.dir}/run-octane.js -# list of tests to be excluded -# mandreel excluded due to OOM -octane-test-sys-prop.test.js.exclude.list=\ - base.js \ - run.js \ - mandreel.js - # test root for sunspider -sunspider-test-sys-prop.test.js.roots=${test.external.dir}/sunspider/tests/sunspider-1.0/ +sunspider-test-sys-prop.test.js.roots=${test.external.dir}/sunspider/tests/sunspider-1.0.2/ # framework root for sunspider sunspider-test-sys-prop.test.js.framework=${test.basic.dir}/runsunspider.js @@ -193,6 +192,7 @@ test262-test-sys-prop.test.js.shared.context=true # test262 test root test262-test-sys-prop.test.js.roots=${test262.suite.dir} + # test262 enable/disable strict mode tests test262-test-sys-prop.test.js.enable.strict.mode=true @@ -202,7 +202,7 @@ test262-test-sys-prop.test.js.enable.strict.mode=true # list of test262 test dirs to be excluded test262-test-sys-prop.test.js.exclude.dir=\ ${test262.suite.dir}/intl402/ \ - ${test262.suite.dir}/bestPractice/ + ${test262.suite.dir}/bestPractice/ test262-test-sys-prop.test.failed.list.file=${build.dir}/test/failedTests @@ -216,8 +216,18 @@ test262-test-sys-prop.test.js.framework=\ ${test262.dir}/test/harness/framework.js \ ${test262.dir}/test/harness/sta.js +# testmarkdown test root +testmarkdown-test-sys-prop.test.js.roots=${testmarkdown.dir} + +# execute testmarkdown tests in shared nashorn context or not? +testmarkdown-test-sys-prop.test.js.shared.context=false + +# framework root for markdown script tests +testmarkdown-test-sys-prop.test.js.framework=\ + ${test.script.dir}${file.separator}markdown.js + # testjfx test root -testjfx-test-sys-prop.test.js.roots=${testjfx.dir} +testjfx-test-sys-prop.test.js.roots=${testjfx.dir} # execute testjfx tests in shared nashorn context or not? testjfx-test-sys-prop.test.js.shared.context=false @@ -254,48 +264,114 @@ test.src.dir=test/src run.test.xmx=2G run.test.xms=2G +# uncomment this jfr.args to enable light recordings. the stack needs to be cranked up to 1024 frames, +# or everything will as of the now drown in lambda forms and be cut off. +# +#jfr.args=-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath="test_suite.jfr",stackdepth=1024 \ + +jfr.args= + run.test.user.language=tr run.test.user.country=TR -run.test.jvmargs.common=-server -XX:+TieredCompilation -Dfile.encoding=UTF-8 -Duser.language=${run.test.user.language} -Duser.country=${run.test.user.country} -XX:+HeapDumpOnOutOfMemoryError - -#-XX:-UseCompressedKlassPointers -XX:+PrintHeapAtGC -XX:ClassMetaspaceSize=300M -# -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMethods +run.test.jvmargs.common=\ + -server \ + -Dfile.encoding=UTF-8 \ + -Duser.language=${run.test.user.language} \ + -Duser.country=${run.test.user.country} \ + -Dnashorn.typeInfo.cacheDir=${build.dir}${file.separator}test${file.separator}type_info_cache \ + ${jfr.args} \ + -XX:+HeapDumpOnOutOfMemoryError # turn on assertions for tests run.test.jvmargs.main=${run.test.jvmargs.common} -ea -#-XX:-UseCompressedKlassPointers -XX:+PrintHeapAtGC -XX:ClassMetaspaceSize=300M -run.test.jvmargs.octane.main=${run.test.jvmargs.common} +# extra jvmargs that might be useful for debugging +# +# -XX:+UnlockDiagnosticVMOptions +# +# turn off compressed class pointers in metaspace +# -XX:-UseCompressedKlassPointers +# +# dump the heap after every GC +# -XX:+PrintHeapAtGC +# +# manually set a metaspace size for class data +# -XX:ClassMetaspaceSize=300M +# +# print out methods compiled +# -XX:+PrintCompilation +# +# print all compiled nmethods with oopmaps and lots of other info +# -XX:+PrintNMethods -run.test.jvmsecurityargs=-Xverify:all -Djava.security.manager -Djava.security.policy=${basedir}/build/nashorn.policy +# Use best known performance options for octane +run.test.jvmargs.octane.main=${run.test.jvmargs.common} -XX:+UnlockDiagnosticVMOptions -XX:+UseNewCode -XX:TypeProfileLevel=222 + +# Security manager args - make sure that we run with the nashorn.policy that the build creates +run.test.jvmsecurityargs=-Xverify:all -Djava.security.manager -Djava.security.policy=${build.dir}/nashorn.policy # VM options for script tests with @fork option test-sys-prop.test.fork.jvm.options=${run.test.jvmargs.main} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs} -cp ${run.test.classpath} # path of rhino.jar for benchmarks -rhino.jar= +rhino.dir= +rhino.jar=${rhino.dir}/js.jar v8.shell=d8 +# How many iterations should 'ant octane' run for each +# benchmark +octane.iterations=25 + +# List of octane tests to run, as properties prefixed with +# "octane.benchmark." mapping to the benchmark name in +# the test harness +# +# Octane tests that are disabled should have their entire line +# commented out Tests may be disabled for functionality reasons when +# they have bugs or when the runtime doesn't handle them (yet) +octane.benchmark.box2d=box2d +#octane.benchmark.code-load=code-load +octane.benchmark.crypto=crypto +octane.benchmark.deltablue=deltablue +octane.benchmark.earley-boyer=earley-boyer +octane.benchmark.gbemu=gbemu +octane.benchmark.navier-stokes=navier-stokes +octane.benchmark.mandreel=mandreel +octane.benchmark.pdfjs=pdfjs +octane.benchmark.raytrace=raytrace +octane.benchmark.regexp=regexp +octane.benchmark.richards=richards +octane.benchmark.splay=splay +#octane.benchmark.typescript=typescript +#octane.benchmark.zlib=zlib + #path to rhino jar file octaneperf-sys-prop.rhino.jar=${rhino.jar} #timeout for performance tests in minutes octaneperf-sys-prop.timeout.value=10 -################ -# codecoverage # -################ - #enable/disable code coverage; please redifine in the ${user.home}/.nashorn.project.local.properties +#how many iterations to run sunspider after warmup +sunspider.iterations=3000 + +################# +# code coverage # +################# + +#enable/disable code coverage; please redifine in the ${user.home}/.nashorn.project.local.properties make.code.coverage=false - #type of codecoverage; one of static or dynamic. Now only dynamic is supported + +#type of codecoverage; one of static or dynamic. Now only dynamic is supported jcov=dynamic - #naming of CC results - #NB directory specified in the cc.dir will be cleaned up!!! + +#naming of CC results +#NB directory specified in the cc.dir will be cleaned up!!! cc.dir=${basedir}/../Codecoverage_Nashorn cc.result.file.name=CC_${jcov}_nashorn.xml - #dynamic CC parameters; please redefine in the ${user.home}/.nashorn.project.local.properties + +#dynamic CC parameters; please redefine in the ${user.home}/.nashorn.project.local.properties jcov2.lib.dir=${basedir}/../jcov2/lib jcov.jar=${jcov2.lib.dir}/jcov.jar cc.include=jdk\.nashorn\.* diff --git a/samples/javafoovars.js b/samples/javafoovars.js new file mode 100644 index 00000000..037c2bf5 --- /dev/null +++ b/samples/javafoovars.js @@ -0,0 +1,103 @@ +#// Usage: jjs javafoovars.js -- <directory> + +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// This example demonstrates Java subclassing by Java.extend +// and javac Compiler and Tree API. This example counts number +// of variables called "foo" in the given java source files! +if (arguments.length == 0) { + print("Usage: jjs javafoovars.js -- <directory>"); + exit(1); +} + +// Java types used +var File = Java.type("java.io.File"); +var Files = Java.type("java.nio.file.Files"); +var FileVisitOption = Java.type("java.nio.file.FileVisitOption"); +var StringArray = Java.type("java.lang.String[]"); +var ToolProvider = Java.type("javax.tools.ToolProvider"); +var Tree = Java.type("com.sun.source.tree.Tree"); +var TreeScanner = Java.type("com.sun.source.util.TreeScanner"); +var VariableTree = Java.type("com.sun.source.tree.VariableTree"); + +// count "foo"-s in the given .java files +function countFoo() { + // get the system compiler tool + var compiler = ToolProvider.systemJavaCompiler; + // get standard file manager + var fileMgr = compiler.getStandardFileManager(null, null, null); + // Using Java.to convert script array (arguments) to a Java String[] + var compUnits = fileMgr.getJavaFileObjects( + Java.to(arguments, StringArray)); + // create a new compilation task + var task = compiler.getTask(null, fileMgr, null, null, null, compUnits); + // subclass SimpleTreeVisitor - to count variables called "foo" + var FooCounterVisitor = Java.extend(TreeScanner); + var fooCount = 0; + + var visitor = new FooCounterVisitor() { + visitVariable: function (node, p) { + if (node.name.toString() == "foo") { + fooCount++; + } + } + } + + for each (var cu in task.parse()) { + cu.accept(visitor, null); + } + return fooCount; +} + +// for each ".java" file in directory (recursively) count "foo". +function main(dir) { + var totalCount = 0; + Files.walk(dir.toPath(), FileVisitOption.FOLLOW_LINKS). + forEach(function(p) { + var name = p.toFile().absolutePath; + if (name.endsWith(".java")) { + var count = 0; + try { + count = countFoo(p.toFile().getAbsolutePath()); + } catch (e) { + print(e); + } + if (count != 0) { + print(name + ": " + count); + } + totalCount += count; + } + }); + print("Total foo count: " + totalCount); +} + +main(new File(arguments[0])); diff --git a/samples/jsobj_example.js b/samples/jsobj_example.js new file mode 100644 index 00000000..d608876e --- /dev/null +++ b/samples/jsobj_example.js @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Flexible script object using AbstractJSObject subclass + +var AbstractJSObject = Java.type("jdk.nashorn.api.scripting.AbstractJSObject"); + +// JSObject example that uses a map for properties and +// falls back to with methods on a java object (for missing +// properties + +function makeJSObj(map, fallback) { + return new AbstractJSObject() { + getMember: function(name) { + if (map.containsKey(name)) { + return map.get(name); + } + + var val = fallback[name]; + if (typeof val == 'function') { + return function() { + var a = arguments; + switch (a.length) { + case 0: return fallback[name](); + case 1: return fallback[name](a[0]); + case 2: return fallback[name](a[0], a[1]); + case 3: return fallback[name](a[0], a[1], a[2]); + case 4: return fallback[name](a[0], a[1], a[2], a[3]); + } + } + } + } + } +} + +var m = new java.util.HashMap(); +m.put("foo", 42); +m.put("bar", 'hello'); + +var obj = makeJSObj(m, new java.io.File(".")); + +print(obj.foo); +print(obj.bar); +print(obj.getAbsolutePath()); +print(obj.isDirectory()); diff --git a/samples/zipfs.js b/samples/zipfs.js new file mode 100644 index 00000000..ecb6f61a --- /dev/null +++ b/samples/zipfs.js @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +if (arguments.length == 0) { + print("Usage: jjs zipfs.js -- <.zip/.jar file>") + exit(1) +} + +var Files = Java.type("java.nio.file.Files") +var FileSystems = Java.type("java.nio.file.FileSystems") +var FileVisitOption = Java.type("java.nio.file.FileVisitOption") +var Paths = Java.type("java.nio.file.Paths") + +var zipfile = Paths.get(arguments[0]) +var fs = FileSystems.newFileSystem(zipfile, null) +var root = fs.rootDirectories[0] +Files.walk(root, FileVisitOption.FOLLOW_LINKS).forEach( + function(p) (print(p), print(Files.readAttributes(p, "zip:*"))) +) +fs.close() diff --git a/samples/ziplist.js b/samples/ziplist.js new file mode 100644 index 00000000..214dd351 --- /dev/null +++ b/samples/ziplist.js @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Oracle nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +if (arguments.length == 0) { + print("Usage: jjs ziplist -- <zip-file>"); + exit(1); +} + +// list the content details of a .zip or .jar file +var file = arguments[0]; + +// java classes used +var Attributes = Java.type("java.util.jar.Attributes"); +var FileTime = Java.type("java.nio.file.attribute.FileTime"); +var JarFile = Java.type("java.util.jar.JarFile"); +var ZipEntry = Java.type("java.util.zip.ZipEntry"); +var ZipFile = Java.type("java.util.zip.ZipFile"); + +var zf = file.endsWith(".jar")? new JarFile(file) : new ZipFile(file); + +var entries = zf.entries(); +// make overall output a valid JSON +var zfObj = { + name: zf.name, + comment: zf.comment, + size: zf.size(), + entries: [] +}; + +while (entries.hasMoreElements()) { + zfObj.entries.push(entries.nextElement()); +} + +print(JSON.stringify(zfObj, function (key, value) { + if (value instanceof ZipEntry) { + return Object.bindProperties({}, value); + } else if (value instanceof FileTime) { + return value.toString(); + } else if (value instanceof Attributes) { + var attrs = {}; + var itr = value.entrySet().iterator(); + while (itr.hasNext()) { + var n = itr.next(); + attrs[n.key] = String(n.value); + } + return attrs; + } + + return value; +}, ' ')); + +zf.close(); diff --git a/src/jdk/internal/dynalink/ChainedCallSite.java b/src/jdk/internal/dynalink/ChainedCallSite.java index 31346e6d..45f191a0 100644 --- a/src/jdk/internal/dynalink/ChainedCallSite.java +++ b/src/jdk/internal/dynalink/ChainedCallSite.java @@ -103,8 +103,27 @@ import jdk.internal.dynalink.support.Lookup; * handle is always at the start of the chain. */ public class ChainedCallSite extends AbstractRelinkableCallSite { - private static final MethodHandle PRUNE = Lookup.findOwnSpecial(MethodHandles.lookup(), "prune", MethodHandle.class, - MethodHandle.class); + private static final MethodHandle PRUNE_CATCHES = + MethodHandles.insertArguments( + Lookup.findOwnSpecial( + MethodHandles.lookup(), + "prune", + MethodHandle.class, + MethodHandle.class, + boolean.class), + 2, + true); + + private static final MethodHandle PRUNE_SWITCHPOINTS = + MethodHandles.insertArguments( + Lookup.findOwnSpecial( + MethodHandles.lookup(), + "prune", + MethodHandle.class, + MethodHandle.class, + boolean.class), + 2, + false); private final AtomicReference<LinkedList<GuardedInvocation>> invocations = new AtomicReference<>(); @@ -127,23 +146,25 @@ public class ChainedCallSite extends AbstractRelinkableCallSite { @Override public void relink(final GuardedInvocation guardedInvocation, final MethodHandle fallback) { - relinkInternal(guardedInvocation, fallback, false); + relinkInternal(guardedInvocation, fallback, false, false); } @Override public void resetAndRelink(final GuardedInvocation guardedInvocation, final MethodHandle fallback) { - relinkInternal(guardedInvocation, fallback, true); + relinkInternal(guardedInvocation, fallback, true, false); } - private MethodHandle relinkInternal(final GuardedInvocation invocation, final MethodHandle relink, final boolean reset) { + private MethodHandle relinkInternal(final GuardedInvocation invocation, final MethodHandle relink, final boolean reset, final boolean removeCatches) { final LinkedList<GuardedInvocation> currentInvocations = invocations.get(); @SuppressWarnings({ "unchecked", "rawtypes" }) final LinkedList<GuardedInvocation> newInvocations = currentInvocations == null || reset ? new LinkedList<>() : (LinkedList)currentInvocations.clone(); - // First, prune the chain of invalidated switchpoints. + // First, prune the chain of invalidated switchpoints, we always do this + // We also remove any catches if the remove catches flag is set for(final Iterator<GuardedInvocation> it = newInvocations.iterator(); it.hasNext();) { - if(it.next().hasBeenInvalidated()) { + final GuardedInvocation inv = it.next(); + if(inv.hasBeenInvalidated() || (removeCatches && inv.getException() != null)) { it.remove(); } } @@ -160,12 +181,13 @@ public class ChainedCallSite extends AbstractRelinkableCallSite { // prune-and-invoke is used as the fallback for invalidated switchpoints. If a switchpoint gets invalidated, we // rebuild the chain and get rid of all invalidated switchpoints instead of letting them linger. - final MethodHandle pruneAndInvoke = makePruneAndInvokeMethod(relink); + final MethodHandle pruneAndInvokeSwitchPoints = makePruneAndInvokeMethod(relink, getPruneSwitchpoints()); + final MethodHandle pruneAndInvokeCatches = makePruneAndInvokeMethod(relink, getPruneCatches()); // Fold the new chain MethodHandle target = relink; for(final GuardedInvocation inv: newInvocations) { - target = inv.compose(pruneAndInvoke, target); + target = inv.compose(target, pruneAndInvokeSwitchPoints, pruneAndInvokeCatches); } // If nobody else updated the call site while we were rebuilding the chain, set the target to our chain. In case @@ -178,14 +200,30 @@ public class ChainedCallSite extends AbstractRelinkableCallSite { } /** + * Get the switchpoint pruning function for a chained call site + * @return function that removes invalidated switchpoints tied to callsite guard chain and relinks + */ + protected MethodHandle getPruneSwitchpoints() { + return PRUNE_SWITCHPOINTS; + } + + /** + * Get the catch pruning function for a chained call site + * @return function that removes all catches tied to callsite guard chain and relinks + */ + protected MethodHandle getPruneCatches() { + return PRUNE_CATCHES; + } + + /** * Creates a method that rebuilds our call chain, pruning it of any invalidated switchpoints, and then invokes that * chain. * @param relink the ultimate fallback for the chain (the {@code DynamicLinker}'s relink). * @return a method handle for prune-and-invoke */ - private MethodHandle makePruneAndInvokeMethod(final MethodHandle relink) { + private MethodHandle makePruneAndInvokeMethod(final MethodHandle relink, final MethodHandle prune) { // Bind prune to (this, relink) - final MethodHandle boundPrune = MethodHandles.insertArguments(PRUNE, 0, this, relink); + final MethodHandle boundPrune = MethodHandles.insertArguments(prune, 0, this, relink); // Make it ignore all incoming arguments final MethodHandle ignoreArgsPrune = MethodHandles.dropArguments(boundPrune, 0, type().parameterList()); // Invoke prune, then invoke the call site target with original arguments @@ -193,7 +231,7 @@ public class ChainedCallSite extends AbstractRelinkableCallSite { } @SuppressWarnings("unused") - private MethodHandle prune(final MethodHandle relink) { - return relinkInternal(null, relink, false); + private MethodHandle prune(final MethodHandle relink, final boolean catches) { + return relinkInternal(null, relink, false, catches); } } diff --git a/src/jdk/internal/dynalink/DynamicLinker.java b/src/jdk/internal/dynalink/DynamicLinker.java index 489272c6..e7e58d2a 100644 --- a/src/jdk/internal/dynalink/DynamicLinker.java +++ b/src/jdk/internal/dynalink/DynamicLinker.java @@ -140,7 +140,6 @@ import jdk.internal.dynalink.support.RuntimeContextLinkRequestImpl; * @author Attila Szegedi */ public class DynamicLinker { - private static final String CLASS_NAME = DynamicLinker.class.getName(); private static final String RELINK_METHOD_NAME = "relink"; @@ -148,6 +147,7 @@ public class DynamicLinker { private static final String INITIAL_LINK_METHOD_NAME = "linkCallSite"; private final LinkerServices linkerServices; + private final GuardedInvocationFilter prelinkFilter; private final int runtimeContextArgCount; private final boolean syncOnRelink; private final int unstableRelinkThreshold; @@ -156,18 +156,20 @@ public class DynamicLinker { * Creates a new dynamic linker. * * @param linkerServices the linkerServices used by the linker, created by the factory. + * @param prelinkFilter see {@link DynamicLinkerFactory#setPrelinkFilter(GuardedInvocationFilter)} * @param runtimeContextArgCount see {@link DynamicLinkerFactory#setRuntimeContextArgCount(int)} */ - DynamicLinker(final LinkerServices linkerServices, final int runtimeContextArgCount, final boolean syncOnRelink, - final int unstableRelinkThreshold) { + DynamicLinker(final LinkerServices linkerServices, final GuardedInvocationFilter prelinkFilter, final int runtimeContextArgCount, + final boolean syncOnRelink, final int unstableRelinkThreshold) { if(runtimeContextArgCount < 0) { throw new IllegalArgumentException("runtimeContextArgCount < 0"); } if(unstableRelinkThreshold < 0) { throw new IllegalArgumentException("unstableRelinkThreshold < 0"); } - this.runtimeContextArgCount = runtimeContextArgCount; this.linkerServices = linkerServices; + this.prelinkFilter = prelinkFilter; + this.runtimeContextArgCount = runtimeContextArgCount; this.syncOnRelink = syncOnRelink; this.unstableRelinkThreshold = unstableRelinkThreshold; } @@ -224,11 +226,10 @@ public class DynamicLinker { final boolean unstableDetectionEnabled = unstableRelinkThreshold > 0; final boolean callSiteUnstable = unstableDetectionEnabled && relinkCount >= unstableRelinkThreshold; final LinkRequest linkRequest = - runtimeContextArgCount == 0 ? new LinkRequestImpl(callSiteDescriptor, callSiteUnstable, arguments) - : new RuntimeContextLinkRequestImpl(callSiteDescriptor, callSiteUnstable, arguments, - runtimeContextArgCount); + runtimeContextArgCount == 0 ? + new LinkRequestImpl(callSiteDescriptor, callSite, relinkCount, callSiteUnstable, arguments) : + new RuntimeContextLinkRequestImpl(callSiteDescriptor, callSite, relinkCount, callSiteUnstable, arguments, runtimeContextArgCount); - // Find a suitable method handle with a guard GuardedInvocation guardedInvocation = linkerServices.getGuardedInvocation(linkRequest); // None found - throw an exception @@ -248,6 +249,11 @@ public class DynamicLinker { } } + // Make sure we filter the invocation before linking it into the call site. This is typically used to match the + // return type of the invocation to the call site. + guardedInvocation = prelinkFilter.filter(guardedInvocation, linkRequest, linkerServices); + guardedInvocation.getClass(); // null pointer check + int newRelinkCount = relinkCount; // Note that the short-circuited "&&" evaluation below ensures we'll increment the relinkCount until // threshold + 1 but not beyond that. Threshold + 1 is treated as a special value to signal that resetAndRelink diff --git a/src/jdk/internal/dynalink/DynamicLinkerFactory.java b/src/jdk/internal/dynalink/DynamicLinkerFactory.java index 0d4c23d5..af5eb119 100644 --- a/src/jdk/internal/dynalink/DynamicLinkerFactory.java +++ b/src/jdk/internal/dynalink/DynamicLinkerFactory.java @@ -102,14 +102,15 @@ import jdk.internal.dynalink.support.BottomGuardingDynamicLinker; import jdk.internal.dynalink.support.ClassLoaderGetterContextProvider; import jdk.internal.dynalink.support.CompositeGuardingDynamicLinker; import jdk.internal.dynalink.support.CompositeTypeBasedGuardingDynamicLinker; +import jdk.internal.dynalink.support.DefaultPrelinkFilter; import jdk.internal.dynalink.support.LinkerServicesImpl; import jdk.internal.dynalink.support.TypeConverterFactory; /** * A factory class for creating {@link DynamicLinker}s. The most usual dynamic linker is a linker that is a composition * of all {@link GuardingDynamicLinker}s known and pre-created by the caller as well as any - * {@link AutoDiscovery automatically discovered} guarding linkers and the standard fallback {@link BeansLinker}. See - * {@link DynamicLinker} documentation for tips on how to use this class. + * {@link AutoDiscovery automatically discovered} guarding linkers and the standard fallback {@link BeansLinker} and a + * {@link DefaultPrelinkFilter}. See {@link DynamicLinker} documentation for tips on how to use this class. * * @author Attila Szegedi */ @@ -128,6 +129,7 @@ public class DynamicLinkerFactory { private int runtimeContextArgCount = 0; private boolean syncOnRelink = false; private int unstableRelinkThreshold = DEFAULT_UNSTABLE_RELINK_THRESHOLD; + private GuardedInvocationFilter prelinkFilter; /** * Sets the class loader for automatic discovery of available linkers. If not set explicitly, then the thread @@ -246,7 +248,19 @@ public class DynamicLinkerFactory { } /** - * Creates a new dynamic linker consisting of all the prioritized, autodiscovered, and fallback linkers. + * Set the pre-link filter. This is a {@link GuardedInvocationFilter} that will get the final chance to modify the + * guarded invocation after it has been created by a component linker and before the dynamic linker links it into + * the call site. It is normally used to adapt the return value type of the invocation to the type of the call site. + * When not set explicitly, {@link DefaultPrelinkFilter} will be used. + * @param prelinkFilter the pre-link filter for the dynamic linker. + */ + public void setPrelinkFilter(final GuardedInvocationFilter prelinkFilter) { + this.prelinkFilter = prelinkFilter; + } + + /** + * Creates a new dynamic linker consisting of all the prioritized, autodiscovered, and fallback linkers as well as + * the pre-link filter. * * @return the new dynamic Linker */ @@ -306,8 +320,12 @@ public class DynamicLinkerFactory { } } + if(prelinkFilter == null) { + prelinkFilter = new DefaultPrelinkFilter(); + } + return new DynamicLinker(new LinkerServicesImpl(new TypeConverterFactory(typeConverters), composite), - runtimeContextArgCount, syncOnRelink, unstableRelinkThreshold); + prelinkFilter, runtimeContextArgCount, syncOnRelink, unstableRelinkThreshold); } private static ClassLoader getThreadContextClassLoader() { diff --git a/src/jdk/internal/dynalink/GuardedInvocationFilter.java b/src/jdk/internal/dynalink/GuardedInvocationFilter.java new file mode 100644 index 00000000..8560d82f --- /dev/null +++ b/src/jdk/internal/dynalink/GuardedInvocationFilter.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file, and Oracle licenses the original version of this file under the BSD + * license: + */ +/* + Copyright 2009-2013 Attila Szegedi + + Licensed under both the Apache License, Version 2.0 (the "Apache License") + and the BSD License (the "BSD License"), with licensee being free to + choose either of the two at their discretion. + + You may not use this file except in compliance with either the Apache + License or the BSD License. + + If you choose to use this file in compliance with the Apache License, the + following notice applies to you: + + You may obtain a copy of the Apache License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing + permissions and limitations under the License. + + If you choose to use this file in compliance with the BSD License, the + following notice applies to you: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package jdk.internal.dynalink; + +import jdk.internal.dynalink.linker.GuardedInvocation; +import jdk.internal.dynalink.linker.LinkRequest; +import jdk.internal.dynalink.linker.LinkerServices; + +/** + * Interface for objects that are used to transform one guarded invocation into another one. Typical usage is for + * implementing {@link DynamicLinkerFactory#setPrelinkFilter(GuardedInvocationFilter) pre-link filters}. + */ +public interface GuardedInvocationFilter { + /** + * Given a guarded invocation, return a potentially different guarded invocation. + * @param inv the original guarded invocation. Null is never passed. + * @param linkRequest the link request for which the invocation was generated (usually by some linker). + * @param linkerServices the linker services that can be used during creation of a new invocation. + * @return either the passed guarded invocation or a different one, with the difference usually determined based on + * information in the link request and the differing invocation created with the assistance of the linker services. + * Whether or not {@code null} is an accepted return value is dependent on the user of the filter. + */ + public GuardedInvocation filter(GuardedInvocation inv, LinkRequest linkRequest, LinkerServices linkerServices); +} diff --git a/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java b/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java index a2567f95..dab6cbf5 100644 --- a/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java +++ b/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java @@ -106,6 +106,7 @@ import jdk.internal.dynalink.linker.LinkerServices; import jdk.internal.dynalink.support.CallSiteDescriptorFactory; import jdk.internal.dynalink.support.Guards; import jdk.internal.dynalink.support.Lookup; +import jdk.internal.dynalink.support.TypeUtilities; /** * A base class for both {@link StaticClassLinker} and {@link BeanLinker}. Deals with common aspects of property @@ -289,7 +290,7 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { return new CallerSensitiveDynamicMethod(m); } final Member member = (Member)m; - return new SimpleDynamicMethod(unreflectSafely(m), member.getDeclaringClass(), member.getName()); + return new SimpleDynamicMethod(unreflectSafely(m), member.getDeclaringClass(), member.getName(), m instanceof Constructor); } /** @@ -389,6 +390,10 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { return new GuardedInvocationComponent(invocation, getClassGuard(type), clazz, ValidationType.EXACT_CLASS); } + SingleDynamicMethod getConstructorMethod(final String signature) { + return null; + } + private MethodHandle getAssignableGuard(final MethodType type) { return Guards.asType(assignableGuard, type); } @@ -411,18 +416,18 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { return inv == null ? null : new GuardedInvocation(inv, getClassGuard(callSiteDescriptor.getMethodType())); } - private static MethodHandle getDynamicMethodInvocation(final CallSiteDescriptor callSiteDescriptor, + private MethodHandle getDynamicMethodInvocation(final CallSiteDescriptor callSiteDescriptor, final LinkerServices linkerServices, final String methodName, final Map<String, DynamicMethod> methodMap) { final DynamicMethod dynaMethod = getDynamicMethod(methodName, methodMap); return dynaMethod != null ? dynaMethod.getInvocation(callSiteDescriptor, linkerServices) : null; } - private static DynamicMethod getDynamicMethod(final String methodName, final Map<String, DynamicMethod> methodMap) { + private DynamicMethod getDynamicMethod(final String methodName, final Map<String, DynamicMethod> methodMap) { final DynamicMethod dynaMethod = methodMap.get(methodName); return dynaMethod != null ? dynaMethod : getExplicitSignatureDynamicMethod(methodName, methodMap); } - private static SingleDynamicMethod getExplicitSignatureDynamicMethod(final String methodName, + private SingleDynamicMethod getExplicitSignatureDynamicMethod(final String fullName, final Map<String, DynamicMethod> methodsMap) { // What's below is meant to support the "name(type, type, ...)" syntax that programmers can use in a method name // to manually pin down an exact overloaded variant. This is not usually required, as the overloaded method @@ -432,23 +437,33 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { // for performance reasons. // Is the method name lexically of the form "name(types)"? - final int lastChar = methodName.length() - 1; - if(methodName.charAt(lastChar) != ')') { + final int lastChar = fullName.length() - 1; + if(fullName.charAt(lastChar) != ')') { return null; } - final int openBrace = methodName.indexOf('('); + final int openBrace = fullName.indexOf('('); if(openBrace == -1) { return null; } + final String name = fullName.substring(0, openBrace); + final String signature = fullName.substring(openBrace + 1, lastChar); + // Find an existing method for the "name" part - final DynamicMethod simpleNamedMethod = methodsMap.get(methodName.substring(0, openBrace)); + final DynamicMethod simpleNamedMethod = methodsMap.get(name); if(simpleNamedMethod == null) { + // explicit signature constructor access + // Java.type("java.awt.Color")["(int,int,int)"] + // will get Color(int,int,int) constructor of Color class. + if (name.isEmpty()) { + return getConstructorMethod(signature); + } + return null; } // Try to get a narrowed dynamic method for the explicit parameter types. - return simpleNamedMethod.getMethodForExactParamTypes(methodName.substring(openBrace + 1, lastChar)); + return simpleNamedMethod.getMethodForExactParamTypes(signature); } private static final MethodHandle IS_METHOD_HANDLE_NOT_NULL = Guards.isNotNull().asType(MethodType.methodType( @@ -458,12 +473,16 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { private GuardedInvocationComponent getPropertySetter(final CallSiteDescriptor callSiteDescriptor, final LinkerServices linkerServices, final List<String> operations) throws Exception { - final MethodType type = callSiteDescriptor.getMethodType(); switch(callSiteDescriptor.getNameTokenCount()) { case 2: { // Must have three arguments: target object, property name, and property value. assertParameterCount(callSiteDescriptor, 3); + // We want setters that conform to "Object(O, V)". Note, we aren't doing "R(O, V)" as it might not be + // valid for us to convert return values proactively. Also, since we don't know what setters will be + // invoked, we'll conservatively presume Object return type. + final MethodType type = callSiteDescriptor.getMethodType().changeReturnType(Object.class); + // What's below is basically: // foldArguments(guardWithTest(isNotNull, invoke, null|nextComponent.invocation), // get_setter_handle(type, linkerServices)) @@ -472,8 +491,8 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { // component's invocation. // Call site type is "ret_type(object_type,property_name_type,property_value_type)", which we'll - // abbreviate to R(O, N, V) going forward. - // We want setters that conform to "R(O, V)" + // abbreviate to R(O, N, V) going forward, although we don't really use R here (see above about using + // Object return type). final MethodType setterType = type.dropParameterTypes(1, 2); // Bind property setter handle to the expected setter type and linker services. Type is // MethodHandle(Object, String, Object) @@ -494,11 +513,11 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { final MethodHandle fallbackFolded; if(nextComponent == null) { - // Object(MethodHandle)->R(MethodHandle, O, N, V); returns constant null + // Object(MethodHandle)->Object(MethodHandle, O, N, V); returns constant null fallbackFolded = MethodHandles.dropArguments(CONSTANT_NULL_DROP_METHOD_HANDLE, 1, type.parameterList()).asType(type.insertParameterTypes(0, MethodHandle.class)); } else { - // R(O, N, V)->R(MethodHandle, O, N, V); adapts the next component's invocation to drop the + // Object(O, N, V)->Object(MethodHandle, O, N, V); adapts the next component's invocation to drop the // extra argument resulting from fold fallbackFolded = MethodHandles.dropArguments(nextComponent.getGuardedInvocation().getInvocation(), 0, MethodHandle.class); @@ -544,9 +563,12 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { private GuardedInvocationComponent getPropertyGetter(final CallSiteDescriptor callSiteDescriptor, final LinkerServices linkerServices, final List<String> ops) throws Exception { - final MethodType type = callSiteDescriptor.getMethodType(); switch(callSiteDescriptor.getNameTokenCount()) { case 2: { + // Since we can't know what kind of a getter we'll get back on different invocations, we'll just + // conservatively presume Object. Note we can't just coerce to a narrower call site type as the linking + // runtime might not allow coercing at that call site. + final MethodType type = callSiteDescriptor.getMethodType().changeReturnType(Object.class); // Must have exactly two arguments: receiver and name assertParameterCount(callSiteDescriptor, 2); @@ -562,11 +584,11 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { GET_ANNOTATED_METHOD, 1, callSiteDescriptor.getLookup()); final MethodHandle callSiteBoundInvoker = MethodHandles.filterArguments(GETTER_INVOKER, 0, callSiteBoundMethodGetter); - // Object(AnnotatedDynamicMethod, Object)->R(AnnotatedDynamicMethod, T0) + // Object(AnnotatedDynamicMethod, Object)->Object(AnnotatedDynamicMethod, T0) final MethodHandle invokeHandleTyped = linkerServices.asType(callSiteBoundInvoker, MethodType.methodType(type.returnType(), AnnotatedDynamicMethod.class, type.parameterType(0))); // Since it's in the target of a fold, drop the unnecessary second argument - // R(AnnotatedDynamicMethod, T0)->R(AnnotatedDynamicMethod, T0, T1) + // Object(AnnotatedDynamicMethod, T0)->Object(AnnotatedDynamicMethod, T0, T1) final MethodHandle invokeHandleFolded = MethodHandles.dropArguments(invokeHandleTyped, 2, type.parameterType(1)); final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor, @@ -574,17 +596,19 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { final MethodHandle fallbackFolded; if(nextComponent == null) { - // Object(AnnotatedDynamicMethod)->R(AnnotatedDynamicMethod, T0, T1); returns constant null + // Object(AnnotatedDynamicMethod)->Object(AnnotatedDynamicMethod, T0, T1); returns constant null fallbackFolded = MethodHandles.dropArguments(CONSTANT_NULL_DROP_ANNOTATED_METHOD, 1, type.parameterList()).asType(type.insertParameterTypes(0, AnnotatedDynamicMethod.class)); } else { - // R(T0, T1)->R(AnnotatedDynamicMethod, T0, T1); adapts the next component's invocation to drop the - // extra argument resulting from fold - fallbackFolded = MethodHandles.dropArguments(nextComponent.getGuardedInvocation().getInvocation(), - 0, AnnotatedDynamicMethod.class); + // Object(T0, T1)->Object(AnnotatedDynamicMethod, T0, T1); adapts the next component's invocation to + // drop the extra argument resulting from fold and to change its return type to Object. + final MethodHandle nextInvocation = nextComponent.getGuardedInvocation().getInvocation(); + final MethodType nextType = nextInvocation.type(); + fallbackFolded = MethodHandles.dropArguments(nextInvocation.asType( + nextType.changeReturnType(Object.class)), 0, AnnotatedDynamicMethod.class); } - // fold(R(AnnotatedDynamicMethod, T0, T1), AnnotatedDynamicMethod(T0, T1)) + // fold(Object(AnnotatedDynamicMethod, T0, T1), AnnotatedDynamicMethod(T0, T1)) final MethodHandle compositeGetter = MethodHandles.foldArguments(MethodHandles.guardWithTest( IS_ANNOTATED_METHOD_NOT_NULL, invokeHandleFolded, fallbackFolded), typedGetter); if(nextComponent == null) { @@ -611,8 +635,8 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { // value is null. final ValidationType validationType = annGetter.validationType; // TODO: we aren't using the type that declares the most generic getter here! - return new GuardedInvocationComponent(linkerServices.asType(getter, type), getGuard(validationType, - type), clazz, validationType); + return new GuardedInvocationComponent(getter, getGuard(validationType, + callSiteDescriptor.getMethodType()), clazz, validationType); } default: { // Can't do anything with more than 3 name components @@ -641,21 +665,25 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { } } - private static final MethodHandle IS_DYNAMIC_METHOD_NOT_NULL = Guards.asType(Guards.isNotNull(), - MethodType.methodType(boolean.class, DynamicMethod.class)); - private static final MethodHandle DYNAMIC_METHOD_IDENTITY = MethodHandles.identity(DynamicMethod.class); + private static final MethodHandle IS_DYNAMIC_METHOD = Guards.isInstance(DynamicMethod.class, + MethodType.methodType(boolean.class, Object.class)); + private static final MethodHandle OBJECT_IDENTITY = MethodHandles.identity(Object.class); private GuardedInvocationComponent getMethodGetter(final CallSiteDescriptor callSiteDescriptor, final LinkerServices linkerServices, final List<String> ops) throws Exception { - final MethodType type = callSiteDescriptor.getMethodType(); + // The created method handle will always return a DynamicMethod (or null), but since we don't want that type to + // be visible outside of this linker, declare it to return Object. + final MethodType type = callSiteDescriptor.getMethodType().changeReturnType(Object.class); switch(callSiteDescriptor.getNameTokenCount()) { case 2: { // Must have exactly two arguments: receiver and name assertParameterCount(callSiteDescriptor, 2); final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor, linkerServices, ops); - if(nextComponent == null) { - // No next component operation; just return a component for this operation. + if(nextComponent == null || !TypeUtilities.areAssignable(DynamicMethod.class, + nextComponent.getGuardedInvocation().getInvocation().type().returnType())) { + // No next component operation, or it can never produce a dynamic method; just return a component + // for this operation. return getClassGuardedInvocationComponent(linkerServices.asType(getDynamicMethod, type), type); } @@ -664,21 +692,20 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { // bunch of method signature adjustments. Basically, execute method getter; if it returns a non-null // DynamicMethod, use identity to return it, otherwise delegate to nextComponent's invocation. - final MethodHandle typedGetter = linkerServices.asType(getDynamicMethod, type.changeReturnType( - DynamicMethod.class)); + final MethodHandle typedGetter = linkerServices.asType(getDynamicMethod, type); // Since it is part of the foldArgument() target, it will have extra args that we need to drop. final MethodHandle returnMethodHandle = linkerServices.asType(MethodHandles.dropArguments( - DYNAMIC_METHOD_IDENTITY, 1, type.parameterList()), type.insertParameterTypes(0, - DynamicMethod.class)); + OBJECT_IDENTITY, 1, type.parameterList()), type.insertParameterTypes(0, Object.class)); final MethodHandle nextComponentInvocation = nextComponent.getGuardedInvocation().getInvocation(); - // The assumption is that getGuardedInvocationComponent() already asType()'d it correctly - assert nextComponentInvocation.type().equals(type); + // The assumption is that getGuardedInvocationComponent() already asType()'d it correctly modulo the + // return type. + assert nextComponentInvocation.type().changeReturnType(type.returnType()).equals(type); // Since it is part of the foldArgument() target, we have to drop an extra arg it receives. final MethodHandle nextCombinedInvocation = MethodHandles.dropArguments(nextComponentInvocation, 0, - DynamicMethod.class); + Object.class); // Assemble it all into a fold(guard(isNotNull, identity, nextInvocation), get) final MethodHandle compositeGetter = MethodHandles.foldArguments(MethodHandles.guardWithTest( - IS_DYNAMIC_METHOD_NOT_NULL, returnMethodHandle, nextCombinedInvocation), typedGetter); + IS_DYNAMIC_METHOD, returnMethodHandle, nextCombinedInvocation), typedGetter); return nextComponent.compose(compositeGetter, getClassGuard(type), clazz, ValidationType.EXACT_CLASS); } @@ -694,7 +721,7 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { // No delegation to the next component of the composite operation; if we have a method with that name, // we'll always return it at this point. return getClassGuardedInvocationComponent(linkerServices.asType(MethodHandles.dropArguments( - MethodHandles.constant(DynamicMethod.class, method), 0, type.parameterType(0)), type), type); + MethodHandles.constant(Object.class, method), 0, type.parameterType(0)), type), type); } default: { // Can't do anything with more than 3 name components @@ -703,6 +730,30 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { } } + static class MethodPair { + final MethodHandle method1; + final MethodHandle method2; + + MethodPair(final MethodHandle method1, final MethodHandle method2) { + this.method1 = method1; + this.method2 = method2; + } + + MethodHandle guardWithTest(final MethodHandle test) { + return MethodHandles.guardWithTest(test, method1, method2); + } + } + + static MethodPair matchReturnTypes(final MethodHandle m1, final MethodHandle m2) { + final MethodType type1 = m1.type(); + final MethodType type2 = m2.type(); + final Class<?> commonRetType = TypeUtilities.getCommonLosslessConversionType(type1.returnType(), + type2.returnType()); + return new MethodPair( + m1.asType(type1.changeReturnType(commonRetType)), + m2.asType(type2.changeReturnType(commonRetType))); + } + private static void assertParameterCount(final CallSiteDescriptor descriptor, final int paramCount) { if(descriptor.getMethodType().parameterCount() != paramCount) { throw new BootstrapMethodError(descriptor.getName() + " must have exactly " + paramCount + " parameters."); @@ -738,11 +789,14 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker { } private static MethodHandle GET_DYNAMIC_METHOD = MethodHandles.dropArguments(privateLookup.findOwnSpecial( - "getDynamicMethod", DynamicMethod.class, Object.class), 1, Object.class); + "getDynamicMethod", Object.class, Object.class), 1, Object.class); private final MethodHandle getDynamicMethod = GET_DYNAMIC_METHOD.bindTo(this); @SuppressWarnings("unused") - private DynamicMethod getDynamicMethod(final Object name) { + // This method is marked to return Object instead of DynamicMethod as it's used as a linking component and we don't + // want to make the DynamicMethod type observable externally (e.g. as the return type of a MethodHandle returned for + // "dyn:getMethod" linking). + private Object getDynamicMethod(final Object name) { return getDynamicMethod(String.valueOf(name), methods); } diff --git a/src/jdk/internal/dynalink/beans/BeanLinker.java b/src/jdk/internal/dynalink/beans/BeanLinker.java index 5bc0e9b3..f7f0c94a 100644 --- a/src/jdk/internal/dynalink/beans/BeanLinker.java +++ b/src/jdk/internal/dynalink/beans/BeanLinker.java @@ -237,8 +237,9 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL } else { checkGuard = convertArgToInt(RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor); } - return nextComponent.compose(MethodHandles.guardWithTest(binder.bindTest(checkGuard), - binder.bind(invocation), nextComponent.getGuardedInvocation().getInvocation()), gi.getGuard(), + final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation), + nextComponent.getGuardedInvocation().getInvocation()); + return nextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(), gic.getValidatorClass(), gic.getValidationType()); } @@ -308,7 +309,7 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL } /*private*/ MethodHandle bind(final MethodHandle handle) { - return bindToFixedKey(linkerServices.asType(handle, methodType)); + return bindToFixedKey(linkerServices.asTypeLosslessReturn(handle, methodType)); } /*private*/ MethodHandle bindTest(final MethodHandle handle) { @@ -440,8 +441,9 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL final MethodHandle checkGuard = convertArgToInt(invocation == SET_LIST_ELEMENT ? RANGE_CHECK_LIST : RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor); - return nextComponent.compose(MethodHandles.guardWithTest(binder.bindTest(checkGuard), - binder.bind(invocation), nextComponent.getGuardedInvocation().getInvocation()), gi.getGuard(), + final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation), + nextComponent.getGuardedInvocation().getInvocation()); + return nextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(), gic.getValidatorClass(), gic.getValidationType()); } diff --git a/src/jdk/internal/dynalink/beans/BeansLinker.java b/src/jdk/internal/dynalink/beans/BeansLinker.java index b1862de0..439c5a65 100644 --- a/src/jdk/internal/dynalink/beans/BeansLinker.java +++ b/src/jdk/internal/dynalink/beans/BeansLinker.java @@ -169,6 +169,26 @@ public class BeansLinker implements GuardingDynamicLinker { } /** + * Returns true if the object is a Dynalink Java constructor. + * + * @param obj the object we want to test for being a constructor + * @return true if it is a constructor, false otherwise. + */ + public static boolean isDynamicConstructor(final Object obj) { + return obj instanceof DynamicMethod && ((DynamicMethod)obj).isConstructor(); + } + + /** + * Return the dynamic method of constructor of the given class and the given signature. + * @param clazz the class + * @param signature full signature of the constructor + * @return DynamicMethod for the constructor + */ + public static Object getConstructorMethod(final Class<?> clazz, final String signature) { + return StaticClassLinker.getConstructorMethod(clazz, signature); + } + + /** * Returns a collection of names of all readable instance properties of a class. * @param clazz the class * @return a collection of names of all readable instance properties of a class. diff --git a/src/jdk/internal/dynalink/beans/CallerSensitiveDynamicMethod.java b/src/jdk/internal/dynalink/beans/CallerSensitiveDynamicMethod.java index 5fceb1a7..2896732e 100644 --- a/src/jdk/internal/dynalink/beans/CallerSensitiveDynamicMethod.java +++ b/src/jdk/internal/dynalink/beans/CallerSensitiveDynamicMethod.java @@ -155,4 +155,9 @@ class CallerSensitiveDynamicMethod extends SingleDynamicMethod { return StaticClassIntrospector.editConstructorMethodHandle(Lookup.unreflectConstructor(lookup, (Constructor<?>)target)); } + + @Override + boolean isConstructor() { + return target instanceof Constructor; + } } diff --git a/src/jdk/internal/dynalink/beans/DynamicMethod.java b/src/jdk/internal/dynalink/beans/DynamicMethod.java index e72ca6dc..7b7a4d80 100644 --- a/src/jdk/internal/dynalink/beans/DynamicMethod.java +++ b/src/jdk/internal/dynalink/beans/DynamicMethod.java @@ -147,4 +147,13 @@ abstract class DynamicMethod { public String toString() { return "[" + getClass().getName() + " " + getName() + "]"; } + + /** + * True if this method happens to be a constructor method. + * + * @return true if this represents a constructor. + */ + boolean isConstructor() { + return false; + } } diff --git a/src/jdk/internal/dynalink/beans/DynamicMethodLinker.java b/src/jdk/internal/dynalink/beans/DynamicMethodLinker.java index 08f2a258..7067c2ce 100644 --- a/src/jdk/internal/dynalink/beans/DynamicMethodLinker.java +++ b/src/jdk/internal/dynalink/beans/DynamicMethodLinker.java @@ -114,15 +114,30 @@ class DynamicMethodLinker implements TypeBasedGuardingDynamicLinker { return null; } final String operator = desc.getNameToken(CallSiteDescriptor.OPERATOR); - if(operator == "call") { - final MethodHandle invocation = ((DynamicMethod)receiver).getInvocation( + final DynamicMethod dynMethod = (DynamicMethod)receiver; + final boolean constructor = dynMethod.isConstructor(); + final MethodHandle invocation; + + if (operator == "call" && !constructor) { + invocation = dynMethod.getInvocation( CallSiteDescriptorFactory.dropParameterTypes(desc, 0, 1), linkerServices); - if(invocation == null) { + } else if (operator == "new" && constructor) { + final MethodHandle ctorInvocation = dynMethod.getInvocation(desc, linkerServices); + if(ctorInvocation == null) { return null; } + + // Insert null for StaticClass parameter + invocation = MethodHandles.insertArguments(ctorInvocation, 0, (Object)null); + } else { + return null; + } + + if (invocation != null) { return new GuardedInvocation(MethodHandles.dropArguments(invocation, 0, - desc.getMethodType().parameterType(0)), Guards.getIdentityGuard(receiver)); + desc.getMethodType().parameterType(0)), Guards.getIdentityGuard(receiver)); } + return null; } } diff --git a/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java b/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java index 618f71a6..83156e3b 100644 --- a/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java +++ b/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java @@ -148,7 +148,6 @@ class OverloadedDynamicMethod extends DynamicMethod { } } - @SuppressWarnings("fallthrough") @Override public MethodHandle getInvocation(final CallSiteDescriptor callSiteDescriptor, final LinkerServices linkerServices) { final MethodType callSiteType = callSiteDescriptor.getMethodType(); @@ -207,7 +206,7 @@ class OverloadedDynamicMethod extends DynamicMethod { case 1: { // Very lucky, we ended up with a single candidate method handle based on the call site signature; we // can link it very simply by delegating to the SingleDynamicMethod. - invokables.iterator().next().getInvocation(callSiteDescriptor, linkerServices); + return invokables.iterator().next().getInvocation(callSiteDescriptor, linkerServices); } default: { // We have more than one candidate. We have no choice but to link to a method that resolves overloads on @@ -237,6 +236,12 @@ class OverloadedDynamicMethod extends DynamicMethod { return false; } + @Override + public boolean isConstructor() { + assert !methods.isEmpty(); + return methods.getFirst().isConstructor(); + } + ClassLoader getClassLoader() { return classLoader; } @@ -304,6 +309,11 @@ class OverloadedDynamicMethod extends DynamicMethod { * @param method a method to add */ public void addMethod(final SingleDynamicMethod method) { + assert constructorFlagConsistent(method); methods.add(method); } + + private boolean constructorFlagConsistent(final SingleDynamicMethod method) { + return methods.isEmpty()? true : (methods.getFirst().isConstructor() == method.isConstructor()); + } } diff --git a/src/jdk/internal/dynalink/beans/OverloadedMethod.java b/src/jdk/internal/dynalink/beans/OverloadedMethod.java index 95d91a92..70ec495a 100644 --- a/src/jdk/internal/dynalink/beans/OverloadedMethod.java +++ b/src/jdk/internal/dynalink/beans/OverloadedMethod.java @@ -93,6 +93,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import jdk.internal.dynalink.linker.LinkerServices; import jdk.internal.dynalink.support.Lookup; +import jdk.internal.dynalink.support.TypeUtilities; /** * Represents a subset of overloaded methods for a certain method name on a certain class. It can be either a fixarg or @@ -114,13 +115,15 @@ class OverloadedMethod { OverloadedMethod(final List<MethodHandle> methodHandles, final OverloadedDynamicMethod parent, final MethodType callSiteType, final LinkerServices linkerServices) { this.parent = parent; - this.callSiteType = callSiteType; + final Class<?> commonRetType = getCommonReturnType(methodHandles); + this.callSiteType = callSiteType.changeReturnType(commonRetType); this.linkerServices = linkerServices; fixArgMethods = new ArrayList<>(methodHandles.size()); varArgMethods = new ArrayList<>(methodHandles.size()); final int argNum = callSiteType.parameterCount(); - for(final MethodHandle mh: methodHandles) { + for(MethodHandle mh: methodHandles) { + mh = mh.asType(mh.type().changeReturnType(commonRetType)); if(mh.isVarargsCollector()) { final MethodHandle asFixed = mh.asFixedArity(); if(argNum == asFixed.type().parameterCount()) { @@ -137,7 +140,7 @@ class OverloadedMethod { final MethodHandle bound = SELECT_METHOD.bindTo(this); final MethodHandle collecting = SingleDynamicMethod.collectArguments(bound, argNum).asType( callSiteType.changeReturnType(MethodHandle.class)); - invoker = MethodHandles.foldArguments(MethodHandles.exactInvoker(callSiteType), collecting); + invoker = MethodHandles.foldArguments(MethodHandles.exactInvoker(this.callSiteType), collecting); } MethodHandle getInvoker() { @@ -149,7 +152,7 @@ class OverloadedMethod { @SuppressWarnings("unused") private MethodHandle selectMethod(final Object[] args) throws NoSuchMethodException { - final Class<?>[] argTypes = new Class[args.length]; + final Class<?>[] argTypes = new Class<?>[args.length]; for(int i = 0; i < argTypes.length; ++i) { final Object arg = args[i]; argTypes[i] = arg == null ? ClassString.NULL_CLASS : arg.getClass(); @@ -262,4 +265,13 @@ class OverloadedMethod { b.append(classes[l - 1].getComponentType().getCanonicalName()).append("..."); } } + + private static Class<?> getCommonReturnType(final List<MethodHandle> methodHandles) { + final Iterator<MethodHandle> it = methodHandles.iterator(); + Class<?> retType = it.next().type().returnType(); + while(it.hasNext()) { + retType = TypeUtilities.getCommonLosslessConversionType(retType, it.next().type().returnType()); + } + return retType; + } } diff --git a/src/jdk/internal/dynalink/beans/SimpleDynamicMethod.java b/src/jdk/internal/dynalink/beans/SimpleDynamicMethod.java index 2965cb4e..f4a8b0a0 100644 --- a/src/jdk/internal/dynalink/beans/SimpleDynamicMethod.java +++ b/src/jdk/internal/dynalink/beans/SimpleDynamicMethod.java @@ -98,6 +98,7 @@ import java.lang.invoke.MethodType; */ class SimpleDynamicMethod extends SingleDynamicMethod { private final MethodHandle target; + private final boolean constructor; /** * Creates a new simple dynamic method, with a name constructed from the class name, method name, and handle @@ -108,8 +109,22 @@ class SimpleDynamicMethod extends SingleDynamicMethod { * @param name the simple name of the method */ SimpleDynamicMethod(final MethodHandle target, final Class<?> clazz, final String name) { + this(target, clazz, name, false); + } + + /** + * Creates a new simple dynamic method, with a name constructed from the class name, method name, and handle + * signature. + * + * @param target the target method handle + * @param clazz the class declaring the method + * @param name the simple name of the method + * @param constructor does this represent a constructor? + */ + SimpleDynamicMethod(final MethodHandle target, final Class<?> clazz, final String name, final boolean constructor) { super(getName(target, clazz, name)); this.target = target; + this.constructor = constructor; } private static String getName(final MethodHandle target, final Class<?> clazz, final String name) { @@ -130,4 +145,9 @@ class SimpleDynamicMethod extends SingleDynamicMethod { MethodHandle getTarget(final Lookup lookup) { return target; } + + @Override + boolean isConstructor() { + return constructor; + } } diff --git a/src/jdk/internal/dynalink/beans/SingleDynamicMethod.java b/src/jdk/internal/dynalink/beans/SingleDynamicMethod.java index 98edcfba..79dca9a3 100644 --- a/src/jdk/internal/dynalink/beans/SingleDynamicMethod.java +++ b/src/jdk/internal/dynalink/beans/SingleDynamicMethod.java @@ -156,7 +156,9 @@ abstract class SingleDynamicMethod extends DynamicMethod { /** * Given a method handle and a call site type, adapts the method handle to the call site type. Performs type * conversions as needed using the specified linker services, and in case that the method handle is a vararg - * collector, matches it to the arity of the call site. + * collector, matches it to the arity of the call site. The type of the return value is only changed if it can be + * converted using a conversion that loses neither precision nor magnitude, see + * {@link LinkerServices#asTypeLosslessReturn(MethodHandle, MethodType)}. * @param target the method handle to adapt * @param callSiteType the type of the call site * @param linkerServices the linker services used for type conversions @@ -286,7 +288,7 @@ abstract class SingleDynamicMethod extends DynamicMethod { private static MethodHandle createConvertingInvocation(final MethodHandle sizedMethod, final LinkerServices linkerServices, final MethodType callSiteType) { - return linkerServices.asType(sizedMethod, callSiteType); + return linkerServices.asTypeLosslessReturn(sizedMethod, callSiteType); } private static boolean typeMatchesDescription(final String paramTypes, final MethodType type) { diff --git a/src/jdk/internal/dynalink/beans/StaticClassLinker.java b/src/jdk/internal/dynalink/beans/StaticClassLinker.java index 41955bb9..ab9c884a 100644 --- a/src/jdk/internal/dynalink/beans/StaticClassLinker.java +++ b/src/jdk/internal/dynalink/beans/StaticClassLinker.java @@ -160,6 +160,15 @@ class StaticClassLinker implements TypeBasedGuardingDynamicLinker { } return null; } + + @Override + SingleDynamicMethod getConstructorMethod(final String signature) { + return constructor != null? constructor.getMethodForExactParamTypes(signature) : null; + } + } + + static Object getConstructorMethod(final Class<?> clazz, final String signature) { + return linkers.get(clazz).getConstructorMethod(signature); } static Collection<String> getReadableStaticPropertyNames(final Class<?> clazz) { diff --git a/src/jdk/internal/dynalink/linker/GuardedInvocation.java b/src/jdk/internal/dynalink/linker/GuardedInvocation.java index 6cf62d74..00ee9dc7 100644 --- a/src/jdk/internal/dynalink/linker/GuardedInvocation.java +++ b/src/jdk/internal/dynalink/linker/GuardedInvocation.java @@ -83,6 +83,8 @@ package jdk.internal.dynalink.linker; +import static jdk.nashorn.internal.lookup.Lookup.MH; + import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; @@ -104,7 +106,18 @@ import jdk.internal.dynalink.support.Guards; public class GuardedInvocation { private final MethodHandle invocation; private final MethodHandle guard; - private final SwitchPoint switchPoint; + private final Class<? extends Throwable> exception; + private final SwitchPoint[] switchPoints; + + /** + * Creates a new guarded invocation. This invocation is unconditional as it has no invalidations. + * + * @param invocation the method handle representing the invocation. Must not be null. + * @throws NullPointerException if invocation is null. + */ + public GuardedInvocation(final MethodHandle invocation) { + this(invocation, null, (SwitchPoint)null, null); + } /** * Creates a new guarded invocation. @@ -116,7 +129,18 @@ public class GuardedInvocation { * @throws NullPointerException if invocation is null. */ public GuardedInvocation(final MethodHandle invocation, final MethodHandle guard) { - this(invocation, guard, null); + this(invocation, guard, (SwitchPoint)null, null); + } + + /** + * Creates a new guarded invocation. + * + * @param invocation the method handle representing the invocation. Must not be null. + * @param switchPoint the optional switch point that can be used to invalidate this linkage. + * @throws NullPointerException if invocation is null. + */ + public GuardedInvocation(final MethodHandle invocation, final SwitchPoint switchPoint) { + this(invocation, null, switchPoint, null); } /** @@ -130,25 +154,49 @@ public class GuardedInvocation { * @throws NullPointerException if invocation is null. */ public GuardedInvocation(final MethodHandle invocation, final MethodHandle guard, final SwitchPoint switchPoint) { + this(invocation, guard, switchPoint, null); + } + + /** + * Creates a new guarded invocation. + * + * @param invocation the method handle representing the invocation. Must not be null. + * @param guard the method handle representing the guard. Must have the same method type as the invocation, except + * it must return boolean. For some useful guards, check out the {@link Guards} class. It can be null. If both it + * and the switch point are null, this represents an unconditional invocation, which is legal but unusual. + * @param switchPoint the optional switch point that can be used to invalidate this linkage. + * @param exception the optional exception type that is expected to be thrown by the invocation and that also + * invalidates the linkage. + * @throws NullPointerException if invocation is null. + */ + public GuardedInvocation(final MethodHandle invocation, final MethodHandle guard, final SwitchPoint switchPoint, final Class<? extends Throwable> exception) { invocation.getClass(); // NPE check this.invocation = invocation; this.guard = guard; - this.switchPoint = switchPoint; + this.switchPoints = switchPoint == null ? null : new SwitchPoint[] { switchPoint }; + this.exception = exception; } /** - * Creates a new guarded invocation. + * Creates a new guarded invocation * * @param invocation the method handle representing the invocation. Must not be null. - * @param switchPoint the optional switch point that can be used to invalidate this linkage. * @param guard the method handle representing the guard. Must have the same method type as the invocation, except * it must return boolean. For some useful guards, check out the {@link Guards} class. It can be null. If both it * and the switch point are null, this represents an unconditional invocation, which is legal but unusual. + * @param switchPoints the optional switch points that can be used to invalidate this linkage. + * @param exception the optional exception type that is expected to be thrown by the invocation and that also + * invalidates the linkage. * @throws NullPointerException if invocation is null. */ - public GuardedInvocation(final MethodHandle invocation, final SwitchPoint switchPoint, final MethodHandle guard) { - this(invocation, guard, switchPoint); + public GuardedInvocation(final MethodHandle invocation, final MethodHandle guard, final SwitchPoint[] switchPoints, final Class<? extends Throwable> exception) { + invocation.getClass(); // NPE check + this.invocation = invocation; + this.guard = guard; + this.switchPoints = switchPoints == null ? null : switchPoints.clone(); + this.exception = exception; } + /** * Returns the invocation method handle. * @@ -172,8 +220,17 @@ public class GuardedInvocation { * * @return the switch point that can be used to invalidate the invocation handle. Can be null. */ - public SwitchPoint getSwitchPoint() { - return switchPoint; + public SwitchPoint[] getSwitchPoints() { + return switchPoints == null ? null : switchPoints.clone(); + } + + /** + * Returns the exception type that if thrown should be used to invalidate the linkage. + * + * @return the exception type that if thrown should be used to invalidate the linkage. Can be null. + */ + public Class<? extends Throwable> getException() { + return exception; } /** @@ -181,7 +238,15 @@ public class GuardedInvocation { * @return true if and only if this guarded invocation has a switchpoint, and that switchpoint has been invalidated. */ public boolean hasBeenInvalidated() { - return switchPoint != null && switchPoint.hasBeenInvalidated(); + if (switchPoints == null) { + return false; + } + for (final SwitchPoint sp : switchPoints) { + if (sp.hasBeenInvalidated()) { + return true; + } + } + return false; } /** @@ -193,7 +258,7 @@ public class GuardedInvocation { */ public void assertType(final MethodType type) { assertType(invocation, type); - if(guard != null) { + if (guard != null) { assertType(guard, type.changeReturnType(Boolean.TYPE)); } } @@ -206,11 +271,33 @@ public class GuardedInvocation { * @return a new guarded invocation with the replaced methods and the same switch point as this invocation. */ public GuardedInvocation replaceMethods(final MethodHandle newInvocation, final MethodHandle newGuard) { - return new GuardedInvocation(newInvocation, newGuard, switchPoint); + return new GuardedInvocation(newInvocation, newGuard, switchPoints, exception); + } + + /** + * Add a switchpoint to this guarded invocation + * @param newSwitchPoint new switchpoint, or null for nop + * @return new guarded invocation with the extra switchpoint + */ + public GuardedInvocation addSwitchPoint(final SwitchPoint newSwitchPoint) { + if (newSwitchPoint == null) { + return this; + } + + final SwitchPoint[] newSwitchPoints; + if (switchPoints != null) { + newSwitchPoints = new SwitchPoint[switchPoints.length + 1]; + System.arraycopy(switchPoints, 0, newSwitchPoints, 0, switchPoints.length); + newSwitchPoints[switchPoints.length] = newSwitchPoint; + } else { + newSwitchPoints = new SwitchPoint[] { newSwitchPoint }; + } + + return new GuardedInvocation(invocation, guard, newSwitchPoints, exception); } private GuardedInvocation replaceMethodsOrThis(final MethodHandle newInvocation, final MethodHandle newGuard) { - if(newInvocation == invocation && newGuard == guard) { + if (newInvocation == invocation && newGuard == guard) { return this; } return replaceMethods(newInvocation, newGuard); @@ -241,6 +328,20 @@ public class GuardedInvocation { } /** + * Changes the type of the invocation, as if {@link LinkerServices#asTypeLosslessReturn(MethodHandle, MethodType)} was + * applied to its invocation and {@link LinkerServices#asType(MethodHandle, MethodType)} applied to its guard, if it + * has one (with return type changed to boolean, and parameter count potentially truncated for the guard). If the + * invocation doesn't change its type, returns this object. + * @param linkerServices the linker services to use for the conversion + * @param newType the new type of the invocation. + * @return a guarded invocation with the new type applied to it. + */ + public GuardedInvocation asTypeSafeReturn(final LinkerServices linkerServices, final MethodType newType) { + return replaceMethodsOrThis(linkerServices.asTypeLosslessReturn(invocation, newType), guard == null ? null : + Guards.asType(linkerServices, guard, newType)); + } + + /** * Changes the type of the invocation, as if {@link MethodHandle#asType(MethodType)} was applied to its invocation * and its guard, if it has one (with return type changed to boolean for guard). If the invocation already is of the * required type, returns this object. @@ -291,19 +392,46 @@ public class GuardedInvocation { * @return a composite method handle. */ public MethodHandle compose(final MethodHandle fallback) { - return compose(fallback, fallback); + return compose(fallback, fallback, fallback); } /** * Composes the invocation, switchpoint, and the guard into a composite method handle that knows how to fall back. * @param switchpointFallback the fallback method handle in case switchpoint is invalidated. * @param guardFallback the fallback method handle in case guard returns false. + * @param catchFallback the fallback method in case the exception handler triggers * @return a composite method handle. */ - public MethodHandle compose(final MethodHandle switchpointFallback, final MethodHandle guardFallback) { + public MethodHandle compose(final MethodHandle guardFallback, final MethodHandle switchpointFallback, final MethodHandle catchFallback) { final MethodHandle guarded = - guard == null ? invocation : MethodHandles.guardWithTest(guard, invocation, guardFallback); - return switchPoint == null ? guarded : switchPoint.guardWithTest(guarded, switchpointFallback); + guard == null ? + invocation : + MethodHandles.guardWithTest( + guard, + invocation, + guardFallback); + + final MethodHandle catchGuarded = + exception == null ? + guarded : + MH.catchException( + guarded, + exception, + MethodHandles.dropArguments( + catchFallback, + 0, + exception)); + + if (switchPoints == null) { + return catchGuarded; + } + + MethodHandle spGuarded = catchGuarded; + for (final SwitchPoint sp : switchPoints) { + spGuarded = sp.guardWithTest(spGuarded, switchpointFallback); + } + + return spGuarded; } private static void assertType(final MethodHandle mh, final MethodType type) { diff --git a/src/jdk/internal/dynalink/linker/GuardedTypeConversion.java b/src/jdk/internal/dynalink/linker/GuardedTypeConversion.java index 9baed7c0..88357ff0 100644 --- a/src/jdk/internal/dynalink/linker/GuardedTypeConversion.java +++ b/src/jdk/internal/dynalink/linker/GuardedTypeConversion.java @@ -83,19 +83,35 @@ package jdk.internal.dynalink.linker; +/** + * Guarded type conversion + */ public class GuardedTypeConversion { private final GuardedInvocation conversionInvocation; private final boolean cacheable; + /** + * Constructor + * @param conversionInvocation guarded invocation for this type conversion + * @param cacheable is this invocation cacheable + */ public GuardedTypeConversion(final GuardedInvocation conversionInvocation, final boolean cacheable) { this.conversionInvocation = conversionInvocation; this.cacheable = cacheable; } + /** + * Get the invocation + * @return invocation + */ public GuardedInvocation getConversionInvocation() { return conversionInvocation; } + /** + * Check if invocation is cacheable + * @return true if cachable, false otherwise + */ public boolean isCacheable() { return cacheable; } diff --git a/src/jdk/internal/dynalink/linker/GuardingDynamicLinker.java b/src/jdk/internal/dynalink/linker/GuardingDynamicLinker.java index 2cd0d0f0..82a36197 100644 --- a/src/jdk/internal/dynalink/linker/GuardingDynamicLinker.java +++ b/src/jdk/internal/dynalink/linker/GuardingDynamicLinker.java @@ -101,10 +101,16 @@ public interface GuardingDynamicLinker { * @return a guarded invocation with a method handle suitable for the arguments, as well as a guard condition that * if fails should trigger relinking. Must return null if it can't resolve the invocation. If the returned * invocation is unconditional (which is actually quite rare), the guard in the return value can be null. The - * invocation can also have a switch point for asynchronous invalidation of the linkage. If the linker does not - * recognize any native language runtime contexts in arguments, or does recognize its own, but receives a call site - * descriptor without its recognized context in the arguments, it should invoke - * {@link LinkRequest#withoutRuntimeContext()} and link for that. + * invocation can also have a switch point for asynchronous invalidation of the linkage, as well as a + * {@link Throwable} subclass that describes an expected exception condition that also triggers relinking (often it + * is faster to rely on an infrequent but expected {@link ClassCastException} than on an always evaluated + * {@code instanceof} guard). If the linker does not recognize any native language runtime contexts in arguments, or + * does recognize its own, but receives a call site descriptor without its recognized context in the arguments, it + * should invoke {@link LinkRequest#withoutRuntimeContext()} and link for that. While the linker must produce an + * invocation with parameter types matching those in the call site descriptor of the link request, it should not try + * to match the return type expected at the call site except when it can do it with only the conversions that lose + * neither precision nor magnitude, see {@link LinkerServices#asTypeLosslessReturn(java.lang.invoke.MethodHandle, + * java.lang.invoke.MethodType)}. * @throws Exception if the operation fails for whatever reason */ public GuardedInvocation getGuardedInvocation(LinkRequest linkRequest, LinkerServices linkerServices) diff --git a/src/jdk/internal/dynalink/linker/LinkRequest.java b/src/jdk/internal/dynalink/linker/LinkRequest.java index 1a82a4fa..63501cdc 100644 --- a/src/jdk/internal/dynalink/linker/LinkRequest.java +++ b/src/jdk/internal/dynalink/linker/LinkRequest.java @@ -101,6 +101,17 @@ public interface LinkRequest { public CallSiteDescriptor getCallSiteDescriptor(); /** + * Returns the call site token for the call site being linked. This token is an opaque object that is guaranteed to + * have different identity for different call sites, and is also guaranteed to not become weakly reachable before + * the call site does and to become weakly reachable some time after the call site does. This makes it ideal as a + * candidate for a key in a weak hash map in which a linker might want to keep per-call site linking state (usually + * profiling information). + * + * @return the call site token for the call site being linked. + */ + public Object getCallSiteToken(); + + /** * Returns the arguments for the invocation being linked. The returned array is a clone; modifications to it won't * affect the arguments in this request. * @@ -116,6 +127,17 @@ public interface LinkRequest { public Object getReceiver(); /** + * Returns the number of times this callsite has been linked/relinked. This can be useful if you want to + * change e.g. exception based relinking to guard based relinking. It's probably not a good idea to keep, + * for example, expensive exception throwing relinkage based on failed type checks/ClassCastException in + * a nested callsite tree where the exception is thrown repeatedly for the common case. There it would be + * much more performant to use exact type guards instead. + * + * @return link count for call site + */ + public int getLinkCount(); + + /** * Returns true if the call site is considered unstable, that is, it has been relinked more times than was * specified in {@link DynamicLinkerFactory#setUnstableRelinkThreshold(int)}. Linkers should use this as a * hint to prefer producing linkage that is more stable (its guard fails less frequently), even if that assumption diff --git a/src/jdk/internal/dynalink/linker/LinkerServices.java b/src/jdk/internal/dynalink/linker/LinkerServices.java index deaf820f..9e35d7f6 100644 --- a/src/jdk/internal/dynalink/linker/LinkerServices.java +++ b/src/jdk/internal/dynalink/linker/LinkerServices.java @@ -87,7 +87,9 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import jdk.internal.dynalink.DynamicLinker; +import jdk.internal.dynalink.DynamicLinkerFactory; import jdk.internal.dynalink.linker.ConversionComparator.Comparison; +import jdk.internal.dynalink.support.TypeUtilities; /** * Interface for services provided to {@link GuardingDynamicLinker} instances by the {@link DynamicLinker} that owns @@ -103,18 +105,34 @@ public interface LinkerServices { * parameters. It will apply {@link MethodHandle#asType(MethodType)} for all primitive-to-primitive, * wrapper-to-primitive, primitive-to-wrapper conversions as well as for all upcasts. For all other conversions, * it'll insert {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with composite filters - * provided by {@link GuardingTypeConverterFactory} implementations. It doesn't use language-specific conversions on - * the return type. + * provided by {@link GuardingTypeConverterFactory} implementations. * * @param handle target method handle * @param fromType the types of source arguments - * @return a method handle that is a suitable combination of {@link MethodHandle#asType(MethodType)} and - * {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with - * {@link GuardingTypeConverterFactory} produced type converters as filters. + * @return a method handle that is a suitable combination of {@link MethodHandle#asType(MethodType)}, + * {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)}, and + * {@link MethodHandles#filterReturnValue(MethodHandle, MethodHandle)} with + * {@link GuardingTypeConverterFactory}-produced type converters as filters. */ public MethodHandle asType(MethodHandle handle, MethodType fromType); /** + * Similar to {@link #asType(MethodHandle, MethodType)} except it only converts the return type of the method handle + * when it can be done using a conversion that loses neither precision nor magnitude, otherwise it leaves it + * unchanged. The idea is that other conversions should not be performed by individual linkers, but instead the + * {@link DynamicLinkerFactory#setPrelinkFilter(jdk.internal.dynalink.GuardedInvocationFilter) pre-link filter of + * the dynamic linker} should implement the strategy of dealing with potentially lossy return type conversions in a + * manner specific to the language runtime. + * + * @param handle target method handle + * @param fromType the types of source arguments + * @return a method handle that is a suitable combination of {@link MethodHandle#asType(MethodType)}, and + * {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with + * {@link GuardingTypeConverterFactory}-produced type converters as filters. + */ + public MethodHandle asTypeLosslessReturn(MethodHandle handle, MethodType fromType); + + /** * Given a source and target type, returns a method handle that converts between them. Never returns null; in worst * case it will return an identity conversion (that might fail for some values at runtime). You rarely need to use * this method directly; you should mostly rely on {@link #asType(MethodHandle, MethodType)} instead. You really @@ -161,4 +179,23 @@ public interface LinkerServices { * conversion. */ public Comparison compareConversion(Class<?> sourceType, Class<?> targetType1, Class<?> targetType2); + + /** + * If we could just use Java 8 constructs, then {@code asTypeSafeReturn} would be a method with default + * implementation. Since we can't do that, we extract common default implementations into this static class. + */ + public static class Implementation { + /** + * Default implementation for {@link LinkerServices#asTypeLosslessReturn(MethodHandle, MethodType)}. + * @param linkerServices the linker services that delegates to this implementation + * @param handle the passed handle + * @param fromType the passed type + * @return the converted method handle, as per the {@code asTypeSafeReturn} semantics. + */ + public static MethodHandle asTypeLosslessReturn(final LinkerServices linkerServices, final MethodHandle handle, final MethodType fromType) { + final Class<?> handleReturnType = handle.type().returnType(); + return linkerServices.asType(handle, TypeUtilities.isConvertibleWithoutLoss(handleReturnType, fromType.returnType()) ? + fromType : fromType.changeReturnType(handleReturnType)); + } + } } diff --git a/src/jdk/internal/dynalink/support/CallSiteDescriptorFactory.java b/src/jdk/internal/dynalink/support/CallSiteDescriptorFactory.java index 0e7af404..9cbd5f04 100644 --- a/src/jdk/internal/dynalink/support/CallSiteDescriptorFactory.java +++ b/src/jdk/internal/dynalink/support/CallSiteDescriptorFactory.java @@ -86,6 +86,7 @@ package jdk.internal.dynalink.support; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.invoke.MethodType; +import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.Arrays; import java.util.Collections; @@ -103,7 +104,7 @@ import jdk.internal.dynalink.CallSiteDescriptor; * @author Attila Szegedi */ public class CallSiteDescriptorFactory { - private static final WeakHashMap<CallSiteDescriptor, WeakReference<CallSiteDescriptor>> publicDescs = + private static final WeakHashMap<CallSiteDescriptor, Reference<CallSiteDescriptor>> publicDescs = new WeakHashMap<>(); @@ -134,18 +135,27 @@ public class CallSiteDescriptorFactory { static CallSiteDescriptor getCanonicalPublicDescriptor(final CallSiteDescriptor desc) { synchronized(publicDescs) { - final WeakReference<CallSiteDescriptor> ref = publicDescs.get(desc); + final Reference<CallSiteDescriptor> ref = publicDescs.get(desc); if(ref != null) { final CallSiteDescriptor canonical = ref.get(); if(canonical != null) { return canonical; } } - publicDescs.put(desc, new WeakReference<>(desc)); + publicDescs.put(desc, createReference(desc)); } return desc; } + /** + * Override this to use a different kind of references for the cache + * @param desc desc + * @return reference + */ + protected static Reference<CallSiteDescriptor> createReference(final CallSiteDescriptor desc) { + return new WeakReference<>(desc); + } + private static CallSiteDescriptor createPublicCallSiteDescriptor(final String[] tokenizedName, final MethodType methodType) { final int l = tokenizedName.length; if(l > 0 && tokenizedName[0] == "dyn") { diff --git a/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java b/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java index 814fc693..d1ff0280 100644 --- a/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java +++ b/src/jdk/internal/dynalink/support/CompositeTypeBasedGuardingDynamicLinker.java @@ -111,7 +111,7 @@ public class CompositeTypeBasedGuardingDynamicLinker implements TypeBasedGuardin private final TypeBasedGuardingDynamicLinker[] linkers; private final List<TypeBasedGuardingDynamicLinker>[] singletonLinkers; - @SuppressWarnings("unchecked") + @SuppressWarnings({"unchecked", "rawtypes"}) ClassToLinker(final TypeBasedGuardingDynamicLinker[] linkers) { this.linkers = linkers; singletonLinkers = new List[linkers.length]; @@ -120,6 +120,7 @@ public class CompositeTypeBasedGuardingDynamicLinker implements TypeBasedGuardin } } + @SuppressWarnings("fallthrough") @Override protected List<TypeBasedGuardingDynamicLinker> computeValue(final Class<?> clazz) { List<TypeBasedGuardingDynamicLinker> list = NO_LINKER; @@ -134,7 +135,6 @@ public class CompositeTypeBasedGuardingDynamicLinker implements TypeBasedGuardin case 1: { list = new LinkedList<>(list); } - //$FALL-THROUGH$ default: { list.add(linker); } diff --git a/src/jdk/internal/dynalink/support/DefaultPrelinkFilter.java b/src/jdk/internal/dynalink/support/DefaultPrelinkFilter.java new file mode 100644 index 00000000..ad9679f6 --- /dev/null +++ b/src/jdk/internal/dynalink/support/DefaultPrelinkFilter.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file, and Oracle licenses the original version of this file under the BSD + * license: + */ +/* + Copyright 2009-2013 Attila Szegedi + + Licensed under both the Apache License, Version 2.0 (the "Apache License") + and the BSD License (the "BSD License"), with licensee being free to + choose either of the two at their discretion. + + You may not use this file except in compliance with either the Apache + License or the BSD License. + + If you choose to use this file in compliance with the Apache License, the + following notice applies to you: + + You may obtain a copy of the Apache License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing + permissions and limitations under the License. + + If you choose to use this file in compliance with the BSD License, the + following notice applies to you: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package jdk.internal.dynalink.support; + +import jdk.internal.dynalink.GuardedInvocationFilter; +import jdk.internal.dynalink.linker.GuardedInvocation; +import jdk.internal.dynalink.linker.LinkRequest; +import jdk.internal.dynalink.linker.LinkerServices; + +/** + * Default filter for guarded invocation pre link filtering + */ +public class DefaultPrelinkFilter implements GuardedInvocationFilter { + @Override + public GuardedInvocation filter(final GuardedInvocation inv, final LinkRequest request, final LinkerServices linkerServices) { + return inv.asType(linkerServices, request.getCallSiteDescriptor().getMethodType()); + } +} diff --git a/src/jdk/internal/dynalink/support/LinkRequestImpl.java b/src/jdk/internal/dynalink/support/LinkRequestImpl.java index 0f967ed3..2d4c0b18 100644 --- a/src/jdk/internal/dynalink/support/LinkRequestImpl.java +++ b/src/jdk/internal/dynalink/support/LinkRequestImpl.java @@ -95,18 +95,24 @@ import jdk.internal.dynalink.linker.LinkRequest; public class LinkRequestImpl implements LinkRequest { private final CallSiteDescriptor callSiteDescriptor; + private final Object callSiteToken; private final Object[] arguments; private final boolean callSiteUnstable; + private final int linkCount; /** * Creates a new link request. * * @param callSiteDescriptor the descriptor for the call site being linked + * @param callSiteToken the opaque token for the call site being linked. + * @param linkCount how many times this callsite has been linked/relinked * @param callSiteUnstable true if the call site being linked is considered unstable * @param arguments the arguments for the invocation */ - public LinkRequestImpl(final CallSiteDescriptor callSiteDescriptor, final boolean callSiteUnstable, final Object... arguments) { + public LinkRequestImpl(final CallSiteDescriptor callSiteDescriptor, final Object callSiteToken, final int linkCount, final boolean callSiteUnstable, final Object... arguments) { this.callSiteDescriptor = callSiteDescriptor; + this.callSiteToken = callSiteToken; + this.linkCount = linkCount; this.callSiteUnstable = callSiteUnstable; this.arguments = arguments; } @@ -127,17 +133,27 @@ public class LinkRequestImpl implements LinkRequest { } @Override + public Object getCallSiteToken() { + return callSiteToken; + } + + @Override public boolean isCallSiteUnstable() { return callSiteUnstable; } @Override + public int getLinkCount() { + return linkCount; + } + + @Override public LinkRequest withoutRuntimeContext() { return this; } @Override public LinkRequest replaceArguments(final CallSiteDescriptor newCallSiteDescriptor, final Object[] newArguments) { - return new LinkRequestImpl(newCallSiteDescriptor, callSiteUnstable, newArguments); + return new LinkRequestImpl(newCallSiteDescriptor, callSiteToken, linkCount, callSiteUnstable, newArguments); } } diff --git a/src/jdk/internal/dynalink/support/LinkerServicesImpl.java b/src/jdk/internal/dynalink/support/LinkerServicesImpl.java index b8be7e2c..38d93c84 100644 --- a/src/jdk/internal/dynalink/support/LinkerServicesImpl.java +++ b/src/jdk/internal/dynalink/support/LinkerServicesImpl.java @@ -127,6 +127,11 @@ public class LinkerServicesImpl implements LinkerServices { } @Override + public MethodHandle asTypeLosslessReturn(final MethodHandle handle, final MethodType fromType) { + return Implementation.asTypeLosslessReturn(this, handle, fromType); + } + + @Override public MethodHandle getTypeConverter(final Class<?> sourceType, final Class<?> targetType) { return typeConverterFactory.getTypeConverter(sourceType, targetType); } diff --git a/src/jdk/internal/dynalink/support/RuntimeContextLinkRequestImpl.java b/src/jdk/internal/dynalink/support/RuntimeContextLinkRequestImpl.java index cccbaf39..3f43621a 100644 --- a/src/jdk/internal/dynalink/support/RuntimeContextLinkRequestImpl.java +++ b/src/jdk/internal/dynalink/support/RuntimeContextLinkRequestImpl.java @@ -101,15 +101,17 @@ public class RuntimeContextLinkRequestImpl extends LinkRequestImpl { * Creates a new link request. * * @param callSiteDescriptor the descriptor for the call site being linked + * @param callSiteToken the opaque token for the call site being linked. * @param arguments the arguments for the invocation + * @param linkCount number of times callsite has been linked/relinked * @param callSiteUnstable true if the call site being linked is considered unstable * @param runtimeContextArgCount the number of the leading arguments on the stack that represent the language * runtime specific context arguments. * @throws IllegalArgumentException if runtimeContextArgCount is less than 1. */ - public RuntimeContextLinkRequestImpl(final CallSiteDescriptor callSiteDescriptor, final boolean callSiteUnstable, - final Object[] arguments, final int runtimeContextArgCount) { - super(callSiteDescriptor, callSiteUnstable, arguments); + public RuntimeContextLinkRequestImpl(final CallSiteDescriptor callSiteDescriptor, final Object callSiteToken, + final int linkCount, final boolean callSiteUnstable, final Object[] arguments, final int runtimeContextArgCount) { + super(callSiteDescriptor, callSiteToken, linkCount, callSiteUnstable, arguments); if(runtimeContextArgCount < 1) { throw new IllegalArgumentException("runtimeContextArgCount < 1"); } @@ -121,14 +123,14 @@ public class RuntimeContextLinkRequestImpl extends LinkRequestImpl { if(contextStrippedRequest == null) { contextStrippedRequest = new LinkRequestImpl(CallSiteDescriptorFactory.dropParameterTypes(getCallSiteDescriptor(), 1, - runtimeContextArgCount + 1), isCallSiteUnstable(), getTruncatedArguments()); + runtimeContextArgCount + 1), getCallSiteToken(), getLinkCount(), isCallSiteUnstable(), getTruncatedArguments()); } return contextStrippedRequest; } @Override public LinkRequest replaceArguments(final CallSiteDescriptor callSiteDescriptor, final Object[] arguments) { - return new RuntimeContextLinkRequestImpl(callSiteDescriptor, isCallSiteUnstable(), arguments, + return new RuntimeContextLinkRequestImpl(callSiteDescriptor, getCallSiteToken(), getLinkCount(), isCallSiteUnstable(), arguments, runtimeContextArgCount); } diff --git a/src/jdk/internal/dynalink/support/TypeConverterFactory.java b/src/jdk/internal/dynalink/support/TypeConverterFactory.java index 5b3d1a1e..79f6549b 100644 --- a/src/jdk/internal/dynalink/support/TypeConverterFactory.java +++ b/src/jdk/internal/dynalink/support/TypeConverterFactory.java @@ -372,6 +372,7 @@ public class TypeConverterFactory { /*private*/ static final MethodHandle IDENTITY_CONVERSION = MethodHandles.identity(Object.class); + @SuppressWarnings("serial") private static class NotCacheableConverter extends RuntimeException { final MethodHandle converter; diff --git a/src/jdk/internal/dynalink/support/TypeUtilities.java b/src/jdk/internal/dynalink/support/TypeUtilities.java index d84c93e9..6403f3d5 100644 --- a/src/jdk/internal/dynalink/support/TypeUtilities.java +++ b/src/jdk/internal/dynalink/support/TypeUtilities.java @@ -106,38 +106,49 @@ public class TypeUtilities { } /** - * Given two types represented by c1 and c2, returns a type that is their most specific common superclass or - * superinterface. + * Given two types represented by c1 and c2, returns a type that is their most specific common supertype for + * purposes of lossless conversions. * * @param c1 one type * @param c2 another type - * @return their most common superclass or superinterface. If they have several unrelated superinterfaces as their - * most specific common type, or the types themselves are completely unrelated interfaces, {@link java.lang.Object} - * is returned. + * @return their most common superclass or superinterface for purposes of lossless conversions. If they have several + * unrelated superinterfaces as their most specific common type, or the types themselves are completely + * unrelated interfaces, {@link java.lang.Object} is returned. */ - public static Class<?> getMostSpecificCommonType(final Class<?> c1, final Class<?> c2) { + public static Class<?> getCommonLosslessConversionType(final Class<?> c1, final Class<?> c2) { if(c1 == c2) { return c1; + } else if(isConvertibleWithoutLoss(c2, c1)) { + return c1; + } else if(isConvertibleWithoutLoss(c1, c2)) { + return c2; + } + if(c1 == void.class) { + return c2; + } else if(c2 == void.class) { + return c1; } - Class<?> c3 = c2; - if(c3.isPrimitive()) { - if(c3 == Byte.TYPE) - c3 = Byte.class; - else if(c3 == Short.TYPE) - c3 = Short.class; - else if(c3 == Character.TYPE) - c3 = Character.class; - else if(c3 == Integer.TYPE) - c3 = Integer.class; - else if(c3 == Float.TYPE) - c3 = Float.class; - else if(c3 == Long.TYPE) - c3 = Long.class; - else if(c3 == Double.TYPE) - c3 = Double.class; - } - final Set<Class<?>> a1 = getAssignables(c1, c3); - final Set<Class<?>> a2 = getAssignables(c3, c1); + if(c1.isPrimitive() && c2.isPrimitive()) { + if((c1 == byte.class && c2 == char.class) || (c1 == char.class && c2 == byte.class)) { + // byte + char = int + return int.class; + } else if((c1 == short.class && c2 == char.class) || (c1 == char.class && c2 == short.class)) { + // short + char = int + return int.class; + } else if((c1 == int.class && c2 == float.class) || (c1 == float.class && c2 == int.class)) { + // int + float = double + return double.class; + } + } + // For all other cases. This will handle long + (float|double) = Number case as well as boolean + anything = Object case too. + return getMostSpecificCommonTypeUnequalNonprimitives(c1, c2); + } + + private static Class<?> getMostSpecificCommonTypeUnequalNonprimitives(final Class<?> c1, final Class<?> c2) { + final Class<?> npc1 = c1.isPrimitive() ? getWrapperType(c1) : c1; + final Class<?> npc2 = c2.isPrimitive() ? getWrapperType(c2) : c2; + final Set<Class<?>> a1 = getAssignables(npc1, npc2); + final Set<Class<?>> a2 = getAssignables(npc2, npc1); a1.retainAll(a2); if(a1.isEmpty()) { // Can happen when at least one of the arguments is an interface, @@ -168,7 +179,7 @@ public class TypeUtilities { max.add(clazz); } if(max.size() > 1) { - return OBJECT_CLASS; + return Object.class; } return max.get(0); } @@ -232,30 +243,59 @@ public class TypeUtilities { * {@link #isSubtype(Class, Class)}) as well as boxing conversion (JLS 5.1.7) optionally followed by widening * reference conversion and unboxing conversion (JLS 5.1.8) optionally followed by widening primitive conversion. * - * @param callSiteType the parameter type at the call site - * @param methodType the parameter type in the method declaration - * @return true if callSiteType is method invocation convertible to the methodType. + * @param sourceType the type being converted from (call site type for parameter types, method type for return types) + * @param targetType the parameter type being converted to (method type for parameter types, call site type for return types) + * @return true if source type is method invocation convertible to target type. */ - public static boolean isMethodInvocationConvertible(final Class<?> callSiteType, final Class<?> methodType) { - if(methodType.isAssignableFrom(callSiteType)) { + public static boolean isMethodInvocationConvertible(final Class<?> sourceType, final Class<?> targetType) { + if(targetType.isAssignableFrom(sourceType)) { return true; } - if(callSiteType.isPrimitive()) { - if(methodType.isPrimitive()) { - return isProperPrimitiveSubtype(callSiteType, methodType); + if(sourceType.isPrimitive()) { + if(targetType.isPrimitive()) { + return isProperPrimitiveSubtype(sourceType, targetType); } // Boxing + widening reference conversion - return methodType.isAssignableFrom(WRAPPER_TYPES.get(callSiteType)); + assert WRAPPER_TYPES.get(sourceType) != null : sourceType.getName(); + return targetType.isAssignableFrom(WRAPPER_TYPES.get(sourceType)); } - if(methodType.isPrimitive()) { - final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(callSiteType); + if(targetType.isPrimitive()) { + final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(sourceType); return unboxedCallSiteType != null - && (unboxedCallSiteType == methodType || isProperPrimitiveSubtype(unboxedCallSiteType, methodType)); + && (unboxedCallSiteType == targetType || isProperPrimitiveSubtype(unboxedCallSiteType, targetType)); } return false; } /** + * Determines whether a type can be converted to another without losing any + * precision. + * + * @param sourceType the source type + * @param targetType the target type + * @return true if lossless conversion is possible + */ + public static boolean isConvertibleWithoutLoss(final Class<?> sourceType, final Class<?> targetType) { + if(targetType.isAssignableFrom(sourceType)) { + return true; + } + if(sourceType.isPrimitive()) { + if(sourceType == void.class) { + return false; // Void can't be losslessly represented by any type + } + if(targetType.isPrimitive()) { + return isProperPrimitiveLosslessSubtype(sourceType, targetType); + } + // Boxing + widening reference conversion + assert WRAPPER_TYPES.get(sourceType) != null : sourceType.getName(); + return targetType.isAssignableFrom(WRAPPER_TYPES.get(sourceType)); + } + // Can't convert from any non-primitive type to any primitive type without data loss because of null. + // Also, can't convert non-assignable reference types. + return false; + } + + /** * Determines whether one type can be potentially converted to another type at runtime. Allows a conversion between * any subtype and supertype in either direction, and also allows a conversion between any two primitive types, as * well as between any primitive type and any reference type that can hold a boxed primitive. @@ -266,7 +306,7 @@ public class TypeUtilities { */ public static boolean isPotentiallyConvertible(final Class<?> callSiteType, final Class<?> methodType) { // Widening or narrowing reference conversion - if(methodType.isAssignableFrom(callSiteType) || callSiteType.isAssignableFrom(methodType)) { + if(areAssignable(callSiteType, methodType)) { return true; } if(callSiteType.isPrimitive()) { @@ -287,6 +327,16 @@ public class TypeUtilities { } /** + * Returns true if either of the types is assignable from the other. + * @param c1 one of the types + * @param c2 another one of the types + * @return true if either c1 is assignable from c2 or c2 is assignable from c1. + */ + public static boolean areAssignable(final Class<?> c1, final Class<?> c2) { + return c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1); + } + + /** * Determines whether one type is a subtype of another type, as per JLS 4.10 "Subtyping". Note: this is not strict * or proper subtype, therefore true is also returned for identical types; to be completely precise, it allows * identity conversion (JLS 5.1.1), widening primitive conversion (JLS 5.1.2) and widening reference conversion (JLS @@ -353,6 +403,37 @@ public class TypeUtilities { return false; } + /** + * Similar to {@link #isProperPrimitiveSubtype(Class, Class)}, except it disallows conversions from int and long to + * float, and from long to double, as those can lose precision. It also disallows conversion from and to char and + * anything else (similar to boolean) as char is not meant to be an arithmetic type. + * @param subType the supposed subtype + * @param superType the supposed supertype + * @return true if subType is a proper (not identical to) primitive subtype of the superType that can be represented + * by the supertype without no precision loss. + */ + private static boolean isProperPrimitiveLosslessSubtype(final Class<?> subType, final Class<?> superType) { + if(superType == boolean.class || subType == boolean.class) { + return false; + } + if(superType == char.class || subType == char.class) { + return false; + } + if(subType == byte.class) { + return true; + } + if(subType == short.class) { + return superType != byte.class; + } + if(subType == int.class) { + return superType == long.class || superType == double.class; + } + if(subType == float.class) { + return superType == double.class; + } + return false; + } + private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPES = createWrapperToPrimitiveTypes(); private static Map<Class<?>, Class<?>> createWrapperToPrimitiveTypes() { diff --git a/src/jdk/internal/dynalink/support/messages.properties b/src/jdk/internal/dynalink/support/messages.properties index 88d59908..ed26299e 100644 --- a/src/jdk/internal/dynalink/support/messages.properties +++ b/src/jdk/internal/dynalink/support/messages.properties @@ -83,4 +83,4 @@ isOfClassGuardAlwaysTrue=isOfClass guard for {0} in position {1} in method type isOfClassGuardAlwaysFalse=isOfClass guard for {0} in position {1} in method type {2} at {3} will always return false isArrayGuardAlwaysTrue=isArray guard in position {0} in method type {1} at {2} will always return true -isArrayGuardAlwaysFalse=isArray guard in position {0} in method type {1} at {2} will always return false
\ No newline at end of file +isArrayGuardAlwaysFalse=isArray guard in position {0} in method type {1} at {2} will always return false diff --git a/src/jdk/nashorn/api/scripting/NashornException.java b/src/jdk/nashorn/api/scripting/NashornException.java index a5f8c24a..098f8508 100644 --- a/src/jdk/nashorn/api/scripting/NashornException.java +++ b/src/jdk/nashorn/api/scripting/NashornException.java @@ -53,9 +53,6 @@ public abstract class NashornException extends RuntimeException { // underlying ECMA error object - lazily initialized private Object ecmaError; - /** script source name used for "engine.js" */ - public static final String ENGINE_SCRIPT_SOURCE_NAME = "nashorn:engine/resources/engine.js"; - /** * Constructor * @@ -182,7 +179,7 @@ public abstract class NashornException extends RuntimeException { if (ECMAErrors.isScriptFrame(st)) { final String className = "<" + st.getFileName() + ">"; String methodName = st.getMethodName(); - if (methodName.equals(CompilerConstants.RUN_SCRIPT.symbolName())) { + if (methodName.equals(CompilerConstants.PROGRAM.symbolName())) { methodName = "<program>"; } @@ -224,10 +221,22 @@ public abstract class NashornException extends RuntimeException { return buf.toString(); } + /** + * Get the thrown object. Subclass responsibility + * @return thrown object + */ protected Object getThrown() { return null; } + /** + * Initialization function for ECMA errors. Stores the error + * in the ecmaError field of this class. It is only initialized + * once, and then reused + * + * @param global the global + * @return initialized exception + */ protected NashornException initEcmaError(final ScriptObject global) { if (ecmaError != null) { return this; // initialized already! diff --git a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java index 6f09e963..ce2b83ae 100644 --- a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java +++ b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java @@ -25,8 +25,6 @@ package jdk.nashorn.api.scripting; -import static jdk.nashorn.internal.runtime.ECMAErrors.referenceError; -import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED; import static jdk.nashorn.internal.runtime.Source.sourceFor; import java.io.IOException; @@ -34,13 +32,10 @@ import java.io.Reader; import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.net.URL; import java.security.AccessControlContext; import java.security.AccessController; import java.security.Permissions; import java.security.PrivilegedAction; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; import java.security.ProtectionDomain; import java.text.MessageFormat; import java.util.Locale; @@ -58,7 +53,6 @@ import javax.script.SimpleBindings; import jdk.nashorn.internal.objects.Global; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.ErrorManager; -import jdk.nashorn.internal.runtime.Property; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; @@ -98,9 +92,6 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C // This is the initial default Nashorn global object. // This is used as "shared" global if above option is true. private final Global global; - // initialized bit late to be made 'final'. - // Property object for "context" property of global object. - private volatile Property contextProperty; // default options passed to Nashorn Options object private static final String[] DEFAULT_OPTIONS = new String[] { "-doe" }; @@ -122,31 +113,6 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C } } - // load engine.js - @SuppressWarnings("resource") - private static Source loadEngineJSSource() { - final String script = "resources/engine.js"; - try { - return AccessController.doPrivileged( - new PrivilegedExceptionAction<Source>() { - @Override - public Source run() throws IOException { - final URL url = NashornScriptEngine.class.getResource(script); - return sourceFor(NashornException.ENGINE_SCRIPT_SOURCE_NAME, url); - } - } - ); - } catch (final PrivilegedActionException e) { - if (Context.DEBUG) { - e.printStackTrace(); - } - throw new RuntimeException(e); - } - } - - // Source object for engine.js - private static final Source ENGINE_SCRIPT_SRC = loadEngineJSSource(); - NashornScriptEngine(final NashornScriptEngineFactory factory, final ClassLoader appLoader) { this(factory, DEFAULT_OPTIONS, appLoader); } @@ -249,33 +215,6 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C return getInterfaceInner(thiz, 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) { - if (ctxt != null) { - final int scope = ctxt.getAttributesScope(name); - final Global ctxtGlobal = getNashornGlobalFrom(ctxt); - if (scope != -1) { - return ScriptObjectMirror.unwrap(ctxt.getAttribute(name, scope), ctxtGlobal); - } - - if (self == UNDEFINED) { - // scope access and so throw ReferenceError - throw referenceError(ctxtGlobal, "not.defined", name); - } - } - - return UNDEFINED; - } - // Implementation only below this point private static Source makeSource(final Reader reader, final ScriptContext ctxt) throws ScriptException { @@ -427,45 +366,10 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C } }, CREATE_GLOBAL_ACC_CTXT); - nashornContext.initGlobal(newGlobal); - - final int NON_ENUMERABLE_CONSTANT = Property.NOT_ENUMERABLE | Property.NOT_CONFIGURABLE | Property.NOT_WRITABLE; - // current ScriptContext exposed as "context" - // "context" is non-writable from script - but script engine still - // needs to set it and so save the context Property object - contextProperty = newGlobal.addOwnProperty("context", NON_ENUMERABLE_CONSTANT, ctxt); - // 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. - newGlobal.addOwnProperty("engine", NON_ENUMERABLE_CONSTANT, this); - // global script arguments with undefined value - newGlobal.addOwnProperty("arguments", Property.NOT_ENUMERABLE, UNDEFINED); - // file name default is null - newGlobal.addOwnProperty(ScriptEngine.FILENAME, Property.NOT_ENUMERABLE, null); - // evaluate engine.js initialization script this new global object - try { - evalImpl(compileImpl(ENGINE_SCRIPT_SRC, newGlobal), ctxt, newGlobal); - } catch (final ScriptException exp) { - throw new RuntimeException(exp); - } - return newGlobal; - } + nashornContext.initGlobal(newGlobal, this); + newGlobal.setScriptContext(ctxt); - // scripts should see "context" and "engine" as variables in the given global object - private void setContextVariables(final Global ctxtGlobal, final ScriptContext ctxt) { - // set "context" global variable via contextProperty - because this - // property is non-writable - contextProperty.setObjectValue(ctxtGlobal, ctxtGlobal, ctxt, false); - Object args = ScriptObjectMirror.unwrap(ctxt.getAttribute("arguments"), ctxtGlobal); - if (args == null || args == UNDEFINED) { - args = ScriptRuntime.EMPTY_ARRAY; - } - // if no arguments passed, expose it - if (! (args instanceof ScriptObject)) { - args = ctxtGlobal.wrapAsObject(args); - ctxtGlobal.set("arguments", args, false); - } + return newGlobal; } private Object invokeImpl(final Object selfObject, final String name, final Object... args) throws ScriptException, NoSuchMethodException { @@ -525,7 +429,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C return evalImpl(script, ctxt, getNashornGlobalFrom(ctxt)); } - private Object evalImpl(final Context.MultiGlobalCompiledScript mgcs, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { + private static Object evalImpl(final Context.MultiGlobalCompiledScript mgcs, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { final Global oldGlobal = Context.getGlobal(); final boolean globalChanged = (oldGlobal != ctxtGlobal); try { @@ -534,11 +438,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C } final ScriptFunction script = mgcs.getFunction(ctxtGlobal); - - // set ScriptContext variables if ctxt is non-null - if (ctxt != null) { - setContextVariables(ctxtGlobal, ctxt); - } + ctxtGlobal.setScriptContext(ctxt); return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); } catch (final Exception e) { throwAsScriptException(e, ctxtGlobal); @@ -550,7 +450,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C } } - private Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { + private static Object evalImpl(final ScriptFunction script, final ScriptContext ctxt, final Global ctxtGlobal) throws ScriptException { if (script == null) { return null; } @@ -561,10 +461,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C Context.setGlobal(ctxtGlobal); } - // set ScriptContext variables if ctxt is non-null - if (ctxt != null) { - setContextVariables(ctxtGlobal, ctxt); - } + ctxtGlobal.setScriptContext(ctxt); return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal)); } catch (final Exception e) { throwAsScriptException(e, ctxtGlobal); diff --git a/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java b/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java index 470502d4..777edc9d 100644 --- a/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java +++ b/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java @@ -164,7 +164,7 @@ public final class NashornScriptEngineFactory implements ScriptEngineFactory { * @param args arguments array passed to script engine. * @return newly created script engine. */ - public ScriptEngine getScriptEngine(final String[] args) { + public ScriptEngine getScriptEngine(final String... args) { checkConfigPermission(); return new NashornScriptEngine(this, args, getAppClassLoader()); } diff --git a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java index db80a441..b261a1b6 100644 --- a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java +++ b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java @@ -169,6 +169,12 @@ public final class ScriptObjectMirror extends AbstractJSObject implements Bindin }); } + /** + * Call member function + * @param functionName function name + * @param args arguments + * @return return value of function + */ public Object callMember(final String functionName, final Object... args) { functionName.getClass(); // null check final Global oldGlobal = Context.getGlobal(); @@ -709,6 +715,23 @@ public final class ScriptObjectMirror extends AbstractJSObject implements Bindin return newArgs; } + /** + * Are the given objects mirrors to same underlying object? + * + * @param obj1 first object + * @param obj2 second object + * @return true if obj1 and obj2 are identical script objects or mirrors of it. + */ + public static boolean identical(final Object obj1, final Object obj2) { + final Object o1 = (obj1 instanceof ScriptObjectMirror)? + ((ScriptObjectMirror)obj1).sobj : obj1; + + final Object o2 = (obj2 instanceof ScriptObjectMirror)? + ((ScriptObjectMirror)obj2).sobj : obj2; + + return o1 == o2; + } + // package-privates below this. ScriptObjectMirror(final ScriptObject sobj, final Global global) { diff --git a/src/jdk/nashorn/api/scripting/ScriptUtils.java b/src/jdk/nashorn/api/scripting/ScriptUtils.java index da5b9bdb..4de2cbf5 100644 --- a/src/jdk/nashorn/api/scripting/ScriptUtils.java +++ b/src/jdk/nashorn/api/scripting/ScriptUtils.java @@ -25,6 +25,8 @@ package jdk.nashorn.api.scripting; +import static jdk.nashorn.internal.runtime.ECMAErrors.typeError; + import java.lang.invoke.MethodHandle; import jdk.internal.dynalink.beans.StaticClass; import jdk.internal.dynalink.linker.LinkerServices; @@ -69,12 +71,15 @@ public final class ScriptUtils { * Create a wrapper function that calls {@code func} synchronized on {@code sync} or, if that is undefined, * {@code self}. Used to implement "sync" function in resources/mozilla_compat.js. * - * @param func the function to invoke + * @param func the function to wrap * @param sync the object to synchronize on * @return a synchronizing wrapper function */ - public static Object makeSynchronizedFunction(final ScriptFunction func, final Object sync) { - return func.makeSynchronizedFunction(sync); + public static Object makeSynchronizedFunction(final Object func, final Object sync) { + if (func instanceof ScriptFunction) { + return ((ScriptFunction)func).makeSynchronizedFunction(sync); + } + throw typeError("not.a.function", ScriptRuntime.safeToString(func)); } /** diff --git a/src/jdk/nashorn/api/scripting/resources/engine.js b/src/jdk/nashorn/api/scripting/resources/engine.js deleted file mode 100644 index 5aed7e6e..00000000 --- a/src/jdk/nashorn/api/scripting/resources/engine.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * 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 every new Global object. 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); - } -}); - -function print() { - var writer = context != null? context.writer : engine.context.writer; - if (! (writer instanceof java.io.PrintWriter)) { - writer = new java.io.PrintWriter(writer); - } - - var buf = new java.lang.StringBuilder(); - for (var i = 0; i < arguments.length; i++) { - if (i != 0) { - buf.append(' '); - } - buf.append(String(arguments[i])); - } - writer.println(buf.toString()); -} - -/** - * This is C-like printf - * - * @param format string to format the rest of the print items - * @param args variadic argument list - */ -Object.defineProperty(this, "printf", { - configurable: true, - enumerable: false, - writable: true, - value: function (format, args/*, more args*/) { - print(sprintf.apply(this, arguments)); - } -}); - -/** - * This is C-like sprintf - * - * @param format string to format the rest of the print items - * @param args variadic argument list - */ -Object.defineProperty(this, "sprintf", { - configurable: true, - enumerable: false, - writable: true, - value: function (format, args/*, more args*/) { - var len = arguments.length - 1; - var array = []; - - if (len < 0) { - return ""; - } - - for (var i = 0; i < len; i++) { - if (arguments[i+1] instanceof Date) { - array[i] = arguments[i+1].getTime(); - } else { - array[i] = arguments[i+1]; - } - } - - array = Java.to(array); - return Packages.jdk.nashorn.api.scripting.ScriptUtils.format(format, array); - } -}); diff --git a/src/jdk/nashorn/internal/IntDeque.java b/src/jdk/nashorn/internal/IntDeque.java new file mode 100644 index 00000000..477afcf9 --- /dev/null +++ b/src/jdk/nashorn/internal/IntDeque.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.nashorn.internal; + +/** + * Small helper class for fast int deques + */ +public class IntDeque { + private int[] deque = new int[16]; + private int nextFree = 0; + + /** + * Push an int value + * @param value value + */ + public void push(final int value) { + if (nextFree == deque.length) { + final int[] newDeque = new int[nextFree * 2]; + System.arraycopy(deque, 0, newDeque, 0, nextFree); + deque = newDeque; + } + deque[nextFree++] = value; + } + + /** + * Pop an int value + * @return value + */ + public int pop() { + return deque[--nextFree]; + } + + /** + * Peek + * @return top value + */ + public int peek() { + return deque[nextFree - 1]; + } + + /** + * Get the value of the top element and increment it. + * @return top value + */ + public int getAndIncrement() { + return deque[nextFree - 1]++; + } + + /** + * Decrement the value of the top element and return it. + * @return decremented top value + */ + public int decrementAndGet() { + return --deque[nextFree - 1]; + } + + /** + * Check if deque is empty + * @return true if empty + */ + public boolean isEmpty() { + return nextFree == 0; + } +} diff --git a/src/jdk/nashorn/internal/codegen/ApplySpecialization.java b/src/jdk/nashorn/internal/codegen/ApplySpecialization.java new file mode 100644 index 00000000..a5fbfdf9 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/ApplySpecialization.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2010, 2014, 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_VAR; +import static jdk.nashorn.internal.codegen.CompilerConstants.EXPLODED_ARGUMENT_PREFIX; + +import java.lang.invoke.MethodType; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import jdk.nashorn.internal.ir.AccessNode; +import jdk.nashorn.internal.ir.CallNode; +import jdk.nashorn.internal.ir.Expression; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.LexicalContext; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.objects.Global; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.logging.DebugLogger; +import jdk.nashorn.internal.runtime.logging.Loggable; +import jdk.nashorn.internal.runtime.logging.Logger; +import jdk.nashorn.internal.runtime.options.Options; + +/** + * An optimization that attempts to turn applies into calls. This pattern + * is very common for fake class instance creation, and apply + * introduces expensive args collection and boxing + * + * <pre> + * var Class = { + * create: function() { + * return function() { //vararg + * this.initialize.apply(this, arguments); + * } + * } + * }; + * + * Color = Class.create(); + * + * Color.prototype = { + * red: 0, green: 0, blue: 0, + * initialize: function(r,g,b) { + * this.red = r; + * this.green = g; + * this.blue = b; + * } + * } + * + * new Color(17, 47, 11); + * </pre> + */ + +@Logger(name="apply2call") +public final class ApplySpecialization extends NodeVisitor<LexicalContext> implements Loggable { + + private static final boolean USE_APPLY2CALL = Options.getBooleanProperty("nashorn.apply2call", true); + + private final DebugLogger log; + + private final Compiler compiler; + + private final Set<Integer> changed = new HashSet<>(); + + private final Deque<List<IdentNode>> explodedArguments = new ArrayDeque<>(); + + private static final String ARGUMENTS = ARGUMENTS_VAR.symbolName(); + + /** + * Apply specialization optimization. Try to explode arguments and call + * applies as calls if they just pass on the "arguments" array and + * "arguments" doesn't escape. + * + * @param compiler compiler + */ + public ApplySpecialization(final Compiler compiler) { + super(new LexicalContext()); + this.compiler = compiler; + this.log = initLogger(compiler.getContext()); + } + + @Override + public DebugLogger getLogger() { + return log; + } + + @Override + public DebugLogger initLogger(final Context context) { + return context.getLogger(this.getClass()); + } + + /** + * Arguments may only be used as args to the apply. Everything else is disqualified + * We cannot control arguments if they escape from the method and go into an unknown + * scope, thus we are conservative and treat any access to arguments outside the + * apply call as a case of "we cannot apply the optimization". + * + * @return true if arguments escape + */ + private boolean argumentsEscape(final FunctionNode functionNode) { + + final Deque<Set<Expression>> stack = new ArrayDeque<>(); + //ensure that arguments is only passed as arg to apply + try { + functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { + private boolean isCurrentArg(final Expression expr) { + return !stack.isEmpty() && stack.peek().contains(expr); //args to current apply call + } + + private boolean isArguments(final Expression expr) { + return expr instanceof IdentNode && ARGUMENTS.equals(((IdentNode)expr).getName()); + } + + private boolean isParam(final String name) { + for (final IdentNode param : functionNode.getParameters()) { + if (param.getName().equals(name)) { + return true; + } + } + return false; + } + + @Override + public Node leaveIdentNode(final IdentNode identNode) { + if (isParam(identNode.getName()) || ARGUMENTS.equals(identNode.getName()) && !isCurrentArg(identNode)) { + throw new UnsupportedOperationException(); + } + return identNode; + } + + @Override + public boolean enterCallNode(final CallNode callNode) { + final Set<Expression> callArgs = new HashSet<>(); + if (isApply(callNode)) { + final List<Expression> argList = callNode.getArgs(); + if (argList.size() != 2 || !isArguments(argList.get(argList.size() - 1))) { + throw new UnsupportedOperationException(); + } + callArgs.addAll(callNode.getArgs()); + } + stack.push(callArgs); + return true; + } + + @Override + public Node leaveCallNode(final CallNode callNode) { + stack.pop(); + return callNode; + } + }); + } catch (final UnsupportedOperationException e) { + log.fine("'arguments' escapes, is not used in standard call dispatch, or is reassigned in '" + functionNode.getName() + "'. Aborting"); + return true; //bad + } + + return false; + } + + @Override + public boolean enterCallNode(final CallNode callNode) { + return !explodedArguments.isEmpty(); + } + + @Override + public Node leaveCallNode(final CallNode callNode) { + //apply needs to be a global symbol or we don't allow it + + final List<IdentNode> newParams = explodedArguments.peek(); + if (isApply(callNode)) { + final List<Expression> newArgs = new ArrayList<>(); + for (final Expression arg : callNode.getArgs()) { + if (arg instanceof IdentNode && ARGUMENTS.equals(((IdentNode)arg).getName())) { + newArgs.addAll(newParams); + } else { + newArgs.add(arg); + } + } + + changed.add(lc.getCurrentFunction().getId()); + + final CallNode newCallNode = callNode.setArgs(newArgs).setIsApplyToCall(); + + log.fine("Transformed ", + callNode, + " from apply to call => ", + newCallNode, + " in ", + DebugLogger.quote(lc.getCurrentFunction().getName())); + + return newCallNode; + } + + return callNode; + } + + private boolean pushExplodedArgs(final FunctionNode functionNode) { + int start = 0; + + final MethodType actualCallSiteType = compiler.getCallSiteType(functionNode); + if (actualCallSiteType == null) { + return false; + } + assert actualCallSiteType.parameterType(actualCallSiteType.parameterCount() - 1) != Object[].class : "error vararg callsite passed to apply2call " + functionNode.getName() + " " + actualCallSiteType; + + final TypeMap ptm = compiler.getTypeMap(); + if (ptm.needsCallee()) { + start++; + } + + start++; //we always uses this + + final List<IdentNode> params = functionNode.getParameters(); + final List<IdentNode> newParams = new ArrayList<>(); + final long to = Math.max(params.size(), actualCallSiteType.parameterCount() - start); + for (int i = 0; i < to; i++) { + if (i >= params.size()) { + newParams.add(new IdentNode(functionNode.getToken(), functionNode.getFinish(), EXPLODED_ARGUMENT_PREFIX.symbolName() + (i))); + } else { + newParams.add(params.get(i)); + } + } + + explodedArguments.push(newParams); + return true; + } + + @Override + public boolean enterFunctionNode(final FunctionNode functionNode) { + if (!USE_APPLY2CALL) { + return false; + } + + if (!Global.instance().isSpecialNameValid("apply")) { + log.fine("Apply transform disabled: apply/call overridden"); + assert !Global.instance().isSpecialNameValid("call") : "call and apply should have the same SwitchPoint"; + return false; + } + + if (!compiler.isOnDemandCompilation()) { + return false; + } + + if (functionNode.hasEval()) { + return false; + } + + if (argumentsEscape(functionNode)) { + return false; + } + + return pushExplodedArgs(functionNode); + } + + /** + * Try to do the apply to call transformation + * @return true if successful, false otherwise + */ + @Override + public Node leaveFunctionNode(final FunctionNode functionNode0) { + FunctionNode newFunctionNode = functionNode0; + final String functionName = newFunctionNode.getName(); + + if (changed.contains(newFunctionNode.getId())) { + newFunctionNode = newFunctionNode.clearFlag(lc, FunctionNode.USES_ARGUMENTS). + setFlag(lc, FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION). + setParameters(lc, explodedArguments.peek()); + + if (log.isEnabled()) { + log.info("Successfully specialized apply to call in '", + functionName, + " params=", + explodedArguments.peek(), + "' id=", + newFunctionNode.getId(), + " source=", + newFunctionNode.getSource().getURL()); + } + } + + explodedArguments.pop(); + + return newFunctionNode; + } + + private static boolean isApply(final CallNode callNode) { + final Expression f = callNode.getFunction(); + return f instanceof AccessNode && "apply".equals(((AccessNode)f).getProperty()); + } + +} diff --git a/src/jdk/nashorn/internal/codegen/AssignSymbols.java b/src/jdk/nashorn/internal/codegen/AssignSymbols.java new file mode 100644 index 00000000..91a022d9 --- /dev/null +++ b/src/jdk/nashorn/internal/codegen/AssignSymbols.java @@ -0,0 +1,958 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.nashorn.internal.codegen; + +import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; +import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR; +import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; +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.RETURN; +import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; +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.Symbol.HAS_OBJECT_VALUE; +import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF; +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_PROGRAM_LEVEL; +import static jdk.nashorn.internal.ir.Symbol.IS_SCOPE; +import static jdk.nashorn.internal.ir.Symbol.IS_THIS; +import static jdk.nashorn.internal.ir.Symbol.IS_VAR; +import static jdk.nashorn.internal.ir.Symbol.KINDMASK; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Set; +import jdk.nashorn.internal.ir.AccessNode; +import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.CatchNode; +import jdk.nashorn.internal.ir.Expression; +import jdk.nashorn.internal.ir.ForNode; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.FunctionNode.CompilationState; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.IndexNode; +import jdk.nashorn.internal.ir.LexicalContext; +import jdk.nashorn.internal.ir.LexicalContextNode; +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.RuntimeNode; +import jdk.nashorn.internal.ir.RuntimeNode.Request; +import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.Statement; +import jdk.nashorn.internal.ir.SwitchNode; +import jdk.nashorn.internal.ir.Symbol; +import jdk.nashorn.internal.ir.TryNode; +import jdk.nashorn.internal.ir.UnaryNode; +import jdk.nashorn.internal.ir.VarNode; +import jdk.nashorn.internal.ir.WithNode; +import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.runtime.Context; +import jdk.nashorn.internal.runtime.Property; +import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.logging.DebugLogger; +import jdk.nashorn.internal.runtime.logging.Loggable; +import jdk.nashorn.internal.runtime.logging.Logger; + +/** + * This visitor assigns symbols to identifiers denoting variables. It does few more minor calculations that are only + * possible after symbols have been assigned; such is the transformation of "delete" and "typeof" operators into runtime + * nodes and counting of number of properties assigned to "this" in constructor functions. This visitor is also notable + * for what it doesn't do, most significantly it does no type calculations as in JavaScript variables can change types + * during runtime and as such symbols don't have types. Calculation of expression types is performed by a separate + * visitor. + */ +@Logger(name="symbols") +final class AssignSymbols extends NodeOperatorVisitor<LexicalContext> implements Loggable { + private final DebugLogger log; + private final boolean debug; + + private static boolean isParamOrVar(final IdentNode identNode) { + final Symbol symbol = identNode.getSymbol(); + return symbol.isParam() || symbol.isVar(); + } + + private static String name(final Node node) { + final String cn = node.getClass().getName(); + final int lastDot = cn.lastIndexOf('.'); + if (lastDot == -1) { + return cn; + } + return cn.substring(lastDot + 1); + } + + /** + * Checks if various symbols that were provisionally marked as needing a slot ended up unused, and marks them as not + * needing a slot after all. + * @param functionNode the function node + * @return the passed in node, for easy chaining + */ + private static FunctionNode removeUnusedSlots(final FunctionNode functionNode) { + if (!functionNode.needsCallee()) { + functionNode.compilerConstant(CALLEE).setNeedsSlot(false); + } + if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) { + functionNode.compilerConstant(SCOPE).setNeedsSlot(false); + } + if (!functionNode.usesReturnSymbol()) { + functionNode.compilerConstant(RETURN).setNeedsSlot(false); + } + // Named function expressions that end up not referencing themselves won't need a local slot for the self symbol. + if(!functionNode.isDeclared() && !functionNode.usesSelfSymbol() && !functionNode.isAnonymous()) { + final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName()); + if(selfSymbol != null) { + if(selfSymbol.isFunctionSelf()) { + selfSymbol.setNeedsSlot(false); + selfSymbol.clearFlag(Symbol.IS_VAR); + } + } else { + assert functionNode.isProgram(); + } + } + return functionNode; + } + + private final Deque<Set<String>> thisProperties = new ArrayDeque<>(); + private final Map<String, Symbol> globalSymbols = new HashMap<>(); //reuse the same global symbol + private final Compiler compiler; + + public AssignSymbols(final Compiler compiler) { + super(new LexicalContext()); + this.compiler = compiler; + this.log = initLogger(compiler.getContext()); + this.debug = log.isEnabled(); + } + + @Override + public DebugLogger getLogger() { + return log; + } + + @Override + public DebugLogger initLogger(final Context context) { + return context.getLogger(this.getClass()); + } + + /** + * Define symbols for all variable declarations at 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 + * + * @param functionNode the FunctionNode we are entering + * @param body the body of the FunctionNode we are entering + */ + private void acceptDeclarations(final FunctionNode functionNode, final Block body) { + // This visitor will assign symbol to all declared variables, except function declarations (which are taken care + // in a separate step above) and "var" declarations in for loop initializers. + // + body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { + @Override + public boolean enterFunctionNode(final FunctionNode nestedFn) { + // Don't descend into nested functions + return false; + } + + @Override + public Node leaveVarNode(final VarNode varNode) { + if (varNode.isStatement()) { + final IdentNode ident = varNode.getName(); + final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR); + functionNode.addDeclaredSymbol(symbol); + if (varNode.isFunctionDeclaration()) { + symbol.setIsFunctionDeclaration(); + } + return varNode.setName(ident.setSymbol(symbol)); + } + return varNode; + } + }); + } + + private IdentNode compilerConstantIdentifier(final CompilerConstants cc) { + return createImplicitIdentifier(cc.symbolName()).setSymbol(lc.getCurrentFunction().compilerConstant(cc)); + } + + /** + * Creates an ident node for an implicit identifier within the function (one not declared in the script source + * code). These identifiers are defined with function's token and finish. + * @param name the name of the identifier + * @return an ident node representing the implicit identifier. + */ + private IdentNode createImplicitIdentifier(final String name) { + final FunctionNode fn = lc.getCurrentFunction(); + return new IdentNode(fn.getToken(), fn.getFinish(), name); + } + + private Symbol createSymbol(final String name, final int flags) { + if ((flags & Symbol.KINDMASK) == IS_GLOBAL) { + //reuse global symbols so they can be hashed + Symbol global = globalSymbols.get(name); + if (global == null) { + global = new Symbol(name, flags); + globalSymbols.put(name, global); + } + return global; + } + return new Symbol(name, flags); + } + + /** + * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically + * used to create assignmnent of {@code :callee} to the function name symbol in self-referential function + * expressions as well as for assignment of {@code :arguments} to {@code arguments}. + * + * @param name the ident node identifying the variable to initialize + * @param initConstant the compiler constant it is initialized to + * @param fn the function node the assignment is for + * @return a var node with the appropriate assignment + */ + private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) { + final IdentNode init = compilerConstantIdentifier(initConstant); + assert init.getSymbol() != null && init.getSymbol().isBytecodeLocal(); + + final VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init); + + final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName()); + assert nameSymbol != null; + + return (VarNode)synthVar.setName(name.setSymbol(nameSymbol)).accept(this); + } + + private FunctionNode createSyntheticInitializers(final FunctionNode functionNode) { + final List<VarNode> syntheticInitializers = new ArrayList<>(2); + + // Must visit the new var nodes in the context of the body. We could also just set the new statements into the + // block and then revisit the entire block, but that seems to be too much double work. + final Block body = functionNode.getBody(); + lc.push(body); + try { + if (functionNode.usesSelfSymbol()) { + // "var fn = :callee" + syntheticInitializers.add(createSyntheticInitializer(functionNode.getIdent(), CALLEE, functionNode)); + } + + if (functionNode.needsArguments()) { + // "var arguments = :arguments" + syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()), + ARGUMENTS, functionNode)); + } + + if (syntheticInitializers.isEmpty()) { + return functionNode; + } + + for(final ListIterator<VarNode> it = syntheticInitializers.listIterator(); it.hasNext();) { + it.set((VarNode)it.next().accept(this)); + } + } finally { + lc.pop(body); + } + + final List<Statement> stmts = body.getStatements(); + final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size()); + newStatements.addAll(syntheticInitializers); + newStatements.addAll(stmts); + return functionNode.setBody(lc, body.setStatements(lc, newStatements)); + } + + private Symbol defineGlobalSymbol(final Block block, final String name) { + return defineSymbol(block, name, IS_GLOBAL); + } + + /** + * Defines a new symbol in the given block. + * + * @param block the block in which to define the symbol + * @param name name of symbol. + * @param symbolFlags Symbol flags. + * + * @return Symbol for given name or null for redefinition. + */ + private Symbol defineSymbol(final Block block, final String name, final int symbolFlags) { + int flags = symbolFlags; + Symbol symbol = findSymbol(block, name); // Locate symbol. + final boolean isGlobal = (flags & KINDMASK) == IS_GLOBAL; + + // Global variables are implicitly always scope variables too. + if (isGlobal) { + flags |= IS_SCOPE; + } + + if (lc.getCurrentFunction().isProgram()) { + flags |= IS_PROGRAM_LEVEL; + } + + final boolean isParam = (flags & KINDMASK) == IS_PARAM; + final boolean isVar = (flags & KINDMASK) == IS_VAR; + + final FunctionNode function = lc.getFunction(block); + if (symbol != null) { + // Symbol was already defined. Check if it needs to be redefined. + if (isParam) { + if (!isLocal(function, symbol)) { + // Not defined in this function. Create a new definition. + symbol = null; + } else if (symbol.isParam()) { + // Duplicate parameter. Null return will force an error. + throw new AssertionError("duplicate parameter"); + } + } else if (isVar) { + if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) { + // Always create a new definition. + symbol = null; + } else { + // Not defined in this function. Create a new definition. + if (!isLocal(function, symbol) || symbol.less(IS_VAR)) { + symbol = null; + } + } + } + } + + if (symbol == null) { + // If not found, then create a new one. + Block symbolBlock; + + // Determine where to create it. + if (isVar && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) { + symbolBlock = block; //internal vars are always defined in the block closest to them + } else if (isGlobal) { + symbolBlock = lc.getOutermostFunction().getBody(); + } else { + symbolBlock = lc.getFunctionBody(function); + } + + // Create and add to appropriate block. + symbol = createSymbol(name, flags); + symbolBlock.putSymbol(lc, symbol); + + if ((flags & IS_SCOPE) == 0) { + // Initial assumption; symbol can lose its slot later + symbol.setNeedsSlot(true); + } + } else if (symbol.less(flags)) { + symbol.setFlags(flags); + } + + return symbol; + } + + private <T extends Node> T end(final T node) { + return end(node, true); + } + + private <T extends Node> T end(final T node, final boolean printNode) { + if (debug) { + final StringBuilder sb = new StringBuilder(); + + sb.append("[LEAVE "). + append(name(node)). + append("] "). + append(printNode ? node.toString() : ""). + append(" in '"). + append(lc.getCurrentFunction().getName()). + append('\''); + + if (node instanceof IdentNode) { + final Symbol symbol = ((IdentNode)node).getSymbol(); + if (symbol == null) { + sb.append(" <NO SYMBOL>"); + } else { + sb.append(" <symbol=").append(symbol).append('>'); + } + } + + log.unindent(); + log.info(sb); + } + + return node; + } + + @Override + public boolean enterBlock(final Block block) { + start(block); + block.clearSymbols(); + + if (lc.isFunctionBody()) { + enterFunctionBody(); + } + + return true; + } + + @Override + public boolean enterCatchNode(final CatchNode catchNode) { + final IdentNode exception = catchNode.getException(); + final Block block = lc.getCurrentBlock(); + + start(catchNode); + + // define block-local exception variable + final String exname = exception.getName(); + // If the name of the exception starts with ":e", this is a synthetic catch block, likely a catch-all. Its + // symbol is naturally internal, and should be treated as such. + final boolean isInternal = exname.startsWith(EXCEPTION_PREFIX.symbolName()); + defineSymbol(block, exname, IS_VAR | IS_LET | (isInternal ? IS_INTERNAL : 0) | HAS_OBJECT_VALUE); + + return true; + } + + private void enterFunctionBody() { + final FunctionNode functionNode = lc.getCurrentFunction(); + final Block body = lc.getCurrentBlock(); + + initFunctionWideVariables(functionNode, body); + + if (functionNode.isProgram()) { + initGlobalSymbols(body); + } else if (!functionNode.isDeclared() && !functionNode.isAnonymous()) { + // It's neither declared nor program - it's a function expression then; assign it a self-symbol unless it's + // anonymous. + final String name = functionNode.getIdent().getName(); + assert name != null; + assert body.getExistingSymbol(name) == null; + defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF | HAS_OBJECT_VALUE); + if(functionNode.allVarsInScope()) { // basically, has deep eval + lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL); + } + } + + acceptDeclarations(functionNode, body); + } + + @Override + public boolean enterFunctionNode(final FunctionNode functionNode) { + // TODO: once we have information on symbols used by nested functions, we can stop descending into nested + // functions with on-demand compilation, e.g. add + // if(!thisProperties.isEmpty() && env.isOnDemandCompilation()) { + // return false; + // } + start(functionNode, false); + + thisProperties.push(new HashSet<String>()); + + //an outermost function in our lexical context that is not a program + //is possible - it is a function being compiled lazily + if (functionNode.isDeclared()) { + final Iterator<Block> blocks = lc.getBlocks(); + if (blocks.hasNext()) { + defineSymbol(blocks.next(), functionNode.getIdent().getName(), IS_VAR); + } + } + + return true; + } + + @Override + public boolean enterVarNode(final VarNode varNode) { + start(varNode); + defineSymbol(lc.getCurrentBlock(), varNode.getName().getName(), IS_VAR | (lc.getCurrentFunction().isProgram() ? IS_SCOPE : 0)); + return true; + } + + private Symbol exceptionSymbol() { + return newObjectInternal(EXCEPTION_PREFIX); + } + + /** + * 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 FunctionNode finalizeParameters(final FunctionNode functionNode) { + final List<IdentNode> newParams = new ArrayList<>(); + final boolean isVarArg = functionNode.isVarArg(); + + final Block body = functionNode.getBody(); + for (final IdentNode param : functionNode.getParameters()) { + final Symbol paramSymbol = body.getExistingSymbol(param.getName()); + assert paramSymbol != null; + assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags(); + newParams.add(param.setSymbol(paramSymbol)); + + // parameters should not be slots for a function that uses variable arity signature + if (isVarArg) { + paramSymbol.setNeedsSlot(false); + } + } + + return functionNode.setParameters(lc, newParams); + } + + /** + * Search for symbol in the lexical context starting from the given block. + * @param name Symbol name. + * @return Found symbol or null if not found. + */ + private Symbol findSymbol(final Block block, final String name) { + for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) { + final Symbol symbol = blocks.next().getExistingSymbol(name); + if (symbol != null) { + return symbol; + } + } + return null; + } + + /** + * Marks the current function as one using any global symbol. The function and all its parent functions will all be + * marked as needing parent scope. + * @see FunctionNode#needsParentScope() + */ + private void functionUsesGlobalSymbol() { + for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) { + lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE); + } + } + + /** + * Marks the current function as one using a scoped symbol. The block defining the symbol will be marked as needing + * its own scope to hold the variable. If the symbol is defined outside of the current function, it and all + * functions up to (but not including) the function containing the defining block will be marked as needing parent + * function scope. + * @see FunctionNode#needsParentScope() + */ + private void functionUsesScopeSymbol(final Symbol symbol) { + final String name = symbol.getName(); + for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) { + final LexicalContextNode node = contextNodeIter.next(); + if (node instanceof Block) { + final Block block = (Block)node; + if (block.getExistingSymbol(name) != null) { + assert lc.contains(block); + lc.setBlockNeedsScope(block); + break; + } + } else if (node instanceof FunctionNode) { + lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE); + } + } + } + + /** + * Declares that the current function is using the symbol. + * @param symbol the symbol used by the current function. + */ + private void functionUsesSymbol(final Symbol symbol) { + assert symbol != null; + if (symbol.isScope()) { + if (symbol.isGlobal()) { + functionUsesGlobalSymbol(); + } else { + functionUsesScopeSymbol(symbol); + } + } else { + assert !symbol.isGlobal(); // Every global is also scope + } + } + + private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) { + defineSymbol(block, cc.symbolName(), flags).setNeedsSlot(true); + } + + private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) { + initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE); + initCompileConstant(THIS, body, IS_PARAM | IS_THIS | HAS_OBJECT_VALUE); + + if (functionNode.isVarArg()) { + initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL | HAS_OBJECT_VALUE); + if (functionNode.needsArguments()) { + initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE); + defineSymbol(body, ARGUMENTS_VAR.symbolName(), IS_VAR | HAS_OBJECT_VALUE); + } + } + + initParameters(functionNode, body); + initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | HAS_OBJECT_VALUE); + initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL); + } + + + /** + * Move any properties from the global map into the scope of this function (which must be a program function). + * @param block the function node body for which to init scope vars + */ + private void initGlobalSymbols(final Block block) { + final PropertyMap map = Context.getGlobalMap(); + + for (final Property property : map.getProperties()) { + final Symbol symbol = defineGlobalSymbol(block, property.getKey()); + log.info("Added global symbol from property map ", symbol); + } + } + + /** + * Initialize parameters for function node. + * @param functionNode the function node + */ + private void initParameters(final FunctionNode functionNode, final Block body) { + final boolean isVarArg = functionNode.isVarArg(); + final boolean scopeParams = functionNode.allVarsInScope() || isVarArg; + for (final IdentNode param : functionNode.getParameters()) { + final Symbol symbol = defineSymbol(body, param.getName(), IS_PARAM); + if(scopeParams) { + // NOTE: this "set is scope" is a poor substitute for clear expression of where the symbol is stored. + // It will force creation of scopes where they would otherwise not necessarily be needed (functions + // using arguments object and other variable arity functions). Tracked by JDK-8038942. + symbol.setIsScope(); + assert symbol.hasSlot(); + if(isVarArg) { + symbol.setNeedsSlot(false); + } + } + } + } + + /** + * Is the symbol local to (that is, defined in) the specified function? + * @param function the function + * @param symbol the symbol + * @return true if the symbol is defined in the specified function + */ + private boolean isLocal(final FunctionNode function, final Symbol symbol) { + final FunctionNode definingFn = lc.getDefiningFunction(symbol); + assert definingFn != null; + return definingFn == function; + } + + @Override + public Node leaveASSIGN(final BinaryNode binaryNode) { + // If we're assigning a property of the this object ("this.foo = ..."), record it. + + final Expression lhs = binaryNode.lhs(); + if (lhs instanceof AccessNode) { + final AccessNode accessNode = (AccessNode) lhs; + final Expression base = accessNode.getBase(); + if (base instanceof IdentNode) { + final Symbol symbol = ((IdentNode)base).getSymbol(); + if(symbol.isThis()) { + thisProperties.peek().add(accessNode.getProperty()); + } + } + } + return binaryNode; + } + + @Override + public Node leaveBlock(final Block block) { + // It's not necessary to guard the marking of symbols as locals with this "if"condition for correctness, it's + // just an optimization -- runtime type calculation is not used when the compilation is not an on-demand + // optimistic compilation, so we can skip locals marking then. + if (compiler.useOptimisticTypes() && compiler.isOnDemandCompilation()) { + for (final Symbol symbol: block.getSymbols()) { + if (!symbol.isScope()) { + assert symbol.isVar() || symbol.isParam(); + compiler.declareLocalSymbol(symbol.getName()); + } + } + } + return block; + } + + @Override + public Node leaveDELETE(final UnaryNode unaryNode) { + final FunctionNode currentFunctionNode = lc.getCurrentFunction(); + final boolean strictMode = currentFunctionNode.isStrict(); + final Expression rhs = unaryNode.getExpression(); + final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this); + + Request request = Request.DELETE; + final List<Expression> args = new ArrayList<>(); + + if (rhs instanceof IdentNode) { + final IdentNode ident = (IdentNode)rhs; + // If this is a declared variable or a function parameter, delete always fails (except for globals). + final String name = ident.getName(); + final Symbol symbol = ident.getSymbol(); + final boolean failDelete = strictMode || (!symbol.isScope() && (symbol.isParam() || (symbol.isVar() && !symbol.isProgramLevel()))); + + if (failDelete && symbol.isThis()) { + return LiteralNode.newInstance(unaryNode, true).accept(this); + } + final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this); + + if (!failDelete) { + args.add(compilerConstantIdentifier(SCOPE)); + } + args.add(literalNode); + args.add(strictFlagNode); + + if (failDelete) { + request = Request.FAIL_DELETE; + } + } else if (rhs instanceof AccessNode) { + final Expression base = ((AccessNode)rhs).getBase(); + final String property = ((AccessNode)rhs).getProperty(); + + args.add(base); + args.add((Expression)LiteralNode.newInstance(unaryNode, property).accept(this)); + args.add(strictFlagNode); + + } else if (rhs instanceof IndexNode) { + final IndexNode indexNode = (IndexNode)rhs; + final Expression base = indexNode.getBase(); + final Expression index = indexNode.getIndex(); + + args.add(base); + args.add(index); + args.add(strictFlagNode); + + } else { + return LiteralNode.newInstance(unaryNode, true).accept(this); + } + return new RuntimeNode(unaryNode, request, args).accept(this); + } + + @Override + public Node leaveForNode(final ForNode forNode) { + if (forNode.isForIn()) { + forNode.setIterator(newObjectInternal(ITERATOR_PREFIX)); //NASHORN-73 + } + + return end(forNode); + } + + @Override + public Node leaveFunctionNode(final FunctionNode functionNode) { + + return markProgramBlock( + removeUnusedSlots( + createSyntheticInitializers( + finalizeParameters( + lc.applyTopFlags(functionNode)))) + .setThisProperties(lc, thisProperties.pop().size()) + .setState(lc, CompilationState.SYMBOLS_ASSIGNED)); + } + + @Override + public Node leaveIdentNode(final IdentNode identNode) { + final String name = identNode.getName(); + + if (identNode.isPropertyName()) { + return identNode; + } + + final Block block = lc.getCurrentBlock(); + + Symbol symbol = findSymbol(block, name); + + //If an existing symbol with the name is found, use that otherwise, declare a new one + if (symbol != null) { + log.info("Existing symbol = ", symbol); + if (symbol.isFunctionSelf()) { + final FunctionNode functionNode = lc.getDefiningFunction(symbol); + assert functionNode != null; + assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null; + lc.setFlag(functionNode, FunctionNode.USES_SELF_SYMBOL); + } + + // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already) + maybeForceScope(symbol); + } else { + log.info("No symbol exists. Declare as global: ", symbol); + symbol = defineGlobalSymbol(block, name); + Symbol.setSymbolIsScope(lc, symbol); + } + + functionUsesSymbol(symbol); + + if (!identNode.isInitializedHere()) { + symbol.increaseUseCount(); + } + + return end(identNode.setSymbol(symbol)); + } + + @Override + public Node leaveSwitchNode(final SwitchNode switchNode) { + // We only need a symbol for the tag if it's not an integer switch node + if(!switchNode.isInteger()) { + switchNode.setTag(newObjectInternal(SWITCH_TAG_PREFIX)); + } + return switchNode; + } + + @Override + public Node leaveTryNode(final TryNode tryNode) { + tryNode.setException(exceptionSymbol()); + if (tryNode.getFinallyBody() != null) { + tryNode.setFinallyCatchAll(exceptionSymbol()); + } + + end(tryNode); + + return tryNode; + } + + @Override + public Node leaveTYPEOF(final UnaryNode unaryNode) { + final Expression rhs = unaryNode.getExpression(); + + final List<Expression> args = new ArrayList<>(); + if (rhs instanceof IdentNode && !isParamOrVar((IdentNode)rhs)) { + args.add(compilerConstantIdentifier(SCOPE)); + args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null + } else { + args.add(rhs); + args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this' + } + + final Node runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args).accept(this); + + end(unaryNode); + + return runtimeNode; + } + + private FunctionNode markProgramBlock(final FunctionNode functionNode) { + if (compiler.isOnDemandCompilation() || !functionNode.isProgram()) { + return functionNode; + } + + assert functionNode.getId() == 1; + return functionNode.setBody(lc, functionNode.getBody().setFlag(lc, Block.IS_GLOBAL_SCOPE)); + } + + /** + * If the symbol isn't already a scope symbol, but it needs to be (see {@link #symbolNeedsToBeScope(Symbol)}, it is + * promoted to a scope symbol and its block marked as needing a scope. + * @param symbol the symbol that might be scoped + */ + private void maybeForceScope(final Symbol symbol) { + if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) { + Symbol.setSymbolIsScope(lc, symbol); + } + } + + private Symbol newInternal(final CompilerConstants cc, final int flags) { + return defineSymbol(lc.getCurrentBlock(), lc.getCurrentFunction().uniqueName(cc.symbolName()), IS_VAR | IS_INTERNAL | flags); //NASHORN-73 + } + + private Symbol newObjectInternal(final CompilerConstants cc) { + return newInternal(cc, HAS_OBJECT_VALUE); + } + + private boolean start(final Node node) { + return start(node, true); + } + + private boolean start(final Node node, final boolean printNode) { + if (debug) { + final StringBuilder sb = new StringBuilder(); + + sb.append("[ENTER "). + append(name(node)). + append("] "). + append(printNode ? node.toString() : ""). + append(" in '"). + append(lc.getCurrentFunction().getName()). + append("'"); + log.info(sb); + log.indent(); + } + + return true; + } + + /** + * Determines if the symbol has to be a scope symbol. In general terms, it has to be a scope symbol if it can only + * be reached from the current block by traversing a function node, a split node, or a with node. + * @param symbol the symbol checked for needing to be a scope symbol + * @return true if the symbol has to be a scope symbol. + */ + private boolean symbolNeedsToBeScope(final Symbol symbol) { + if (symbol.isThis() || symbol.isInternal()) { + return false; + } + + if (lc.getCurrentFunction().allVarsInScope()) { + return true; + } + + boolean previousWasBlock = false; + for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { + final LexicalContextNode node = it.next(); + if (node instanceof FunctionNode || node instanceof SplitNode || isSplitArray(node)) { + // We reached the function boundary or a splitting boundary without seeing a definition for the symbol. + // It needs to be in scope. + return true; + } else if (node instanceof WithNode) { + if (previousWasBlock) { + // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately + // preceded by a block, this means we're currently processing its expression, not its body, + // therefore it doesn't count. + return true; + } + previousWasBlock = false; + } else if (node instanceof Block) { + if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) { + // We reached the block that defines the symbol without reaching either the function boundary, or a + // WithNode. The symbol need not be scoped. + return false; + } + previousWasBlock = true; + } else { + previousWasBlock = false; + } + } + throw new AssertionError(); + } + + private static boolean isSplitArray(final LexicalContextNode expr) { + if(!(expr instanceof ArrayLiteralNode)) { + return false; + } + final List<ArrayUnit> units = ((ArrayLiteralNode)expr).getUnits(); + return !(units == null || units.isEmpty()); + } +} diff --git a/src/jdk/nashorn/internal/codegen/Attr.java b/src/jdk/nashorn/internal/codegen/Attr.java deleted file mode 100644 index f92e0649..00000000 --- a/src/jdk/nashorn/internal/codegen/Attr.java +++ /dev/null @@ -1,1947 +0,0 @@ -/* - * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package jdk.nashorn.internal.codegen; - -import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; -import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS_VAR; -import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; -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.LITERAL_PREFIX; -import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; -import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; -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.Symbol.IS_ALWAYS_DEFINED; -import static jdk.nashorn.internal.ir.Symbol.IS_CONSTANT; -import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF; -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_SCOPE; -import static jdk.nashorn.internal.ir.Symbol.IS_THIS; -import static jdk.nashorn.internal.ir.Symbol.IS_VAR; -import static jdk.nashorn.internal.ir.Symbol.KINDMASK; - -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.HashSet; -import java.util.Iterator; -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.BinaryNode; -import jdk.nashorn.internal.ir.Block; -import jdk.nashorn.internal.ir.CallNode; -import jdk.nashorn.internal.ir.CaseNode; -import jdk.nashorn.internal.ir.CatchNode; -import jdk.nashorn.internal.ir.Expression; -import jdk.nashorn.internal.ir.ForNode; -import jdk.nashorn.internal.ir.FunctionNode; -import jdk.nashorn.internal.ir.FunctionNode.CompilationState; -import jdk.nashorn.internal.ir.IdentNode; -import jdk.nashorn.internal.ir.IndexNode; -import jdk.nashorn.internal.ir.LexicalContext; -import jdk.nashorn.internal.ir.LexicalContextNode; -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.ReturnNode; -import jdk.nashorn.internal.ir.RuntimeNode; -import jdk.nashorn.internal.ir.RuntimeNode.Request; -import jdk.nashorn.internal.ir.Statement; -import jdk.nashorn.internal.ir.SwitchNode; -import jdk.nashorn.internal.ir.Symbol; -import jdk.nashorn.internal.ir.TemporarySymbols; -import jdk.nashorn.internal.ir.TernaryNode; -import jdk.nashorn.internal.ir.TryNode; -import jdk.nashorn.internal.ir.UnaryNode; -import jdk.nashorn.internal.ir.VarNode; -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.TokenType; -import jdk.nashorn.internal.runtime.Context; -import jdk.nashorn.internal.runtime.Debug; -import jdk.nashorn.internal.runtime.DebugLogger; -import jdk.nashorn.internal.runtime.JSType; -import jdk.nashorn.internal.runtime.Property; -import jdk.nashorn.internal.runtime.PropertyMap; - -/** - * This is the attribution pass of the code generator. Attr takes Lowered IR, - * that is, IR where control flow has been computed and high level to low level - * substitions for operations have been performed. - * - * After Attr, every symbol will have a conservative correct type. - * - * Any expression that requires temporary storage as part of computation will - * also be detected here and give a temporary symbol - * - * Types can be narrowed after Attr by Access Specialization in FinalizeTypes, - * but in general, this is where the main symbol type information is - * computed. - */ - -final class Attr extends NodeOperatorVisitor<LexicalContext> { - - /** - * Local definitions in current block (to discriminate from function - * declarations always defined in the function scope. This is for - * "can be undefined" analysis. - */ - private final Deque<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 final Deque<Set<String>> localUses; - - private final Deque<Type> returnTypes; - - private int catchNestingLevel; - - private static final DebugLogger LOG = new DebugLogger("attr"); - private static final boolean DEBUG = LOG.isEnabled(); - - private final TemporarySymbols temporarySymbols; - - /** - * Constructor. - */ - Attr(final TemporarySymbols temporarySymbols) { - super(new LexicalContext()); - this.temporarySymbols = temporarySymbols; - this.localDefs = new ArrayDeque<>(); - this.localUses = new ArrayDeque<>(); - this.returnTypes = new ArrayDeque<>(); - } - - @Override - protected boolean enterDefault(final Node node) { - return start(node); - } - - @Override - protected Node leaveDefault(final Node node) { - return end(node); - } - - @Override - public Node leaveAccessNode(final AccessNode accessNode) { - //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this, that - //is why we can't set the access node base to be an object here, that will ruin access specialization - //for example for a.x | 17. - return end(ensureSymbol(Type.OBJECT, accessNode)); - } - - private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) { - initCompileConstant(CALLEE, body, IS_PARAM | IS_INTERNAL); - initCompileConstant(THIS, body, IS_PARAM | IS_THIS, Type.OBJECT); - - if (functionNode.isVarArg()) { - initCompileConstant(VARARGS, body, IS_PARAM | IS_INTERNAL); - if (functionNode.needsArguments()) { - initCompileConstant(ARGUMENTS, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED); - final String argumentsName = ARGUMENTS_VAR.symbolName(); - newType(defineSymbol(body, argumentsName, IS_VAR | IS_ALWAYS_DEFINED), Type.typeFor(ARGUMENTS_VAR.type())); - addLocalDef(argumentsName); - } - } - - initParameters(functionNode, body); - initCompileConstant(SCOPE, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED); - initCompileConstant(RETURN, body, IS_VAR | IS_INTERNAL | IS_ALWAYS_DEFINED, Type.OBJECT); - } - - - /** - * 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 - * - * @param functionNode the FunctionNode we are entering - * @param body the body of the FunctionNode we are entering - */ - private void acceptDeclarations(final FunctionNode functionNode, final Block body) { - // This visitor will assign symbol to all declared variables, except function declarations (which are taken care - // in a separate step above) and "var" declarations in for loop initializers. - // - // It also handles the case that a variable can be undefined, e.g - // if (cond) { - // x = x.y; - // } - // var x = 17; - // - // by making sure that no identifier has been found earlier in the body than the - // declaration - if such is the case the identifier is flagged as caBeUndefined to - // be safe if it turns into a local var. Otherwise corrupt bytecode results - - body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { - private final Set<String> uses = new HashSet<>(); - private final Set<String> canBeUndefined = new HashSet<>(); - - @Override - public boolean enterFunctionNode(final FunctionNode nestedFn) { - return false; - } - - @Override - public Node leaveIdentNode(final IdentNode identNode) { - uses.add(identNode.getName()); - return identNode; - } - - @Override - public boolean enterVarNode(final VarNode varNode) { - final String name = varNode.getName().getName(); - //if this is used before the var node, the var node symbol needs to be tagged as can be undefined - if (uses.contains(name)) { - canBeUndefined.add(name); - } - - // all uses of the declared varnode inside the var node are potentially undefined - // however this is a bit conservative as e.g. var x = 17; var x = 1 + x; does work - if (!varNode.isFunctionDeclaration() && varNode.getInit() != null) { - varNode.getInit().accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { - @Override - public boolean enterIdentNode(final IdentNode identNode) { - if (name.equals(identNode.getName())) { - canBeUndefined.add(name); - } - return false; - } - }); - } - - return true; - } - - @Override - public Node leaveVarNode(final VarNode varNode) { - // any declared symbols that aren't visited need to be typed as well, hence the list - if (varNode.isStatement()) { - final IdentNode ident = varNode.getName(); - final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR); - if (canBeUndefined.contains(ident.getName())) { - symbol.setType(Type.OBJECT); - symbol.setCanBeUndefined(); - } - functionNode.addDeclaredSymbol(symbol); - if (varNode.isFunctionDeclaration()) { - newType(symbol, FunctionNode.FUNCTION_TYPE); - symbol.setIsFunctionDeclaration(); - } - return varNode.setName((IdentNode)ident.setSymbol(lc, symbol)); - } - - return varNode; - } - }); - } - - private void enterFunctionBody() { - - final FunctionNode functionNode = lc.getCurrentFunction(); - final Block body = lc.getCurrentBlock(); - - initFunctionWideVariables(functionNode, body); - - if (functionNode.isProgram()) { - initFromPropertyMap(body); - } else if (!functionNode.isDeclared()) { - // It's neither declared nor program - it's a function expression then; assign it a self-symbol. - assert functionNode.getSymbol() == null; - - final boolean anonymous = functionNode.isAnonymous(); - final String name = anonymous ? null : functionNode.getIdent().getName(); - if (!(anonymous || body.getExistingSymbol(name) != null)) { - assert !anonymous && name != null; - newType(defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF), Type.OBJECT); - } - } - - acceptDeclarations(functionNode, body); - } - - @Override - public boolean enterBlock(final Block block) { - start(block); - //ensure that we don't use information from a previous compile. This is very ugly TODO - //the symbols in the block should really be stateless - block.clearSymbols(); - - if (lc.isFunctionBody()) { - enterFunctionBody(); - } - pushLocalsBlock(); - - return true; - } - - @Override - public Node leaveBlock(final Block block) { - popLocals(); - return end(block); - } - - @Override - public boolean enterCallNode(final CallNode callNode) { - return start(callNode); - } - - @Override - public Node leaveCallNode(final CallNode callNode) { - return end(ensureSymbol(callNode.getType(), callNode)); - } - - @Override - public boolean enterCatchNode(final CatchNode catchNode) { - final IdentNode exception = catchNode.getException(); - final Block block = lc.getCurrentBlock(); - - start(catchNode); - catchNestingLevel++; - - // define block-local exception variable - final String exname = exception.getName(); - final Symbol def = defineSymbol(block, exname, IS_VAR | IS_LET | IS_ALWAYS_DEFINED); - newType(def, Type.OBJECT); //we can catch anything, not just ecma exceptions - - addLocalDef(exname); - - return true; - } - - @Override - public Node leaveCatchNode(final CatchNode catchNode) { - final IdentNode exception = catchNode.getException(); - final Block block = lc.getCurrentBlock(); - final Symbol symbol = findSymbol(block, exception.getName()); - - catchNestingLevel--; - - assert symbol != null; - return end(catchNode.setException((IdentNode)exception.setSymbol(lc, symbol))); - } - - /** - * Declare the definition of a new symbol. - * - * @param name Name of symbol. - * @param symbolFlags Symbol flags. - * - * @return Symbol for given name or null for redefinition. - */ - private Symbol defineSymbol(final Block block, final String name, final int symbolFlags) { - int flags = symbolFlags; - Symbol symbol = findSymbol(block, name); // Locate symbol. - final boolean isGlobal = (flags & KINDMASK) == IS_GLOBAL; - - if (isGlobal) { - flags |= IS_SCOPE; - } - - final FunctionNode function = lc.getFunction(block); - if (symbol != null) { - // Symbol was already defined. Check if it needs to be redefined. - if ((flags & KINDMASK) == IS_PARAM) { - if (!isLocal(function, symbol)) { - // Not defined in this function. Create a new definition. - symbol = null; - } else if (symbol.isParam()) { - // Duplicate parameter. Null return will force an error. - assert false : "duplicate parameter"; - return null; - } - } else if ((flags & KINDMASK) == IS_VAR) { - if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) { - // Always create a new definition. - symbol = null; - } else { - // Not defined in this function. Create a new definition. - if (!isLocal(function, symbol) || symbol.less(IS_VAR)) { - symbol = null; - } - } - } - } - - if (symbol == null) { - // If not found, then create a new one. - Block symbolBlock; - - // Determine where to create it. - if ((flags & Symbol.KINDMASK) == IS_VAR && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) { - symbolBlock = block; //internal vars are always defined in the block closest to them - } else if (isGlobal) { - symbolBlock = lc.getOutermostFunction().getBody(); - } else { - symbolBlock = lc.getFunctionBody(function); - } - - // Create and add to appropriate block. - symbol = new Symbol(name, flags); - symbolBlock.putSymbol(lc, symbol); - - if ((flags & Symbol.KINDMASK) != IS_GLOBAL) { - symbol.setNeedsSlot(true); - } - } else if (symbol.less(flags)) { - symbol.setFlags(flags); - } - - return symbol; - } - - @Override - public boolean enterFunctionNode(final FunctionNode functionNode) { - start(functionNode, false); - - if (functionNode.isLazy()) { - return false; - } - - //an outermost function in our lexical context that is not a program (runScript) - //is possible - it is a function being compiled lazily - if (functionNode.isDeclared()) { - final Iterator<Block> blocks = lc.getBlocks(); - if (blocks.hasNext()) { - defineSymbol(blocks.next(), functionNode.getIdent().getName(), IS_VAR); - } - } - - returnTypes.push(functionNode.getReturnType()); - pushLocalsFunction(); - - return true; - } - - @Override - public Node leaveFunctionNode(final FunctionNode functionNode) { - FunctionNode newFunctionNode = functionNode; - - final Block body = newFunctionNode.getBody(); - - //look for this function in the parent block - if (functionNode.isDeclared()) { - final Iterator<Block> blocks = lc.getBlocks(); - if (blocks.hasNext()) { - newFunctionNode = (FunctionNode)newFunctionNode.setSymbol(lc, findSymbol(blocks.next(), functionNode.getIdent().getName())); - } - } else if (!functionNode.isProgram()) { - final boolean anonymous = functionNode.isAnonymous(); - final String name = anonymous ? null : functionNode.getIdent().getName(); - if (anonymous || body.getExistingSymbol(name) != null) { - newFunctionNode = (FunctionNode)ensureSymbol(FunctionNode.FUNCTION_TYPE, newFunctionNode); - } else { - assert name != null; - final Symbol self = body.getExistingSymbol(name); - assert self != null && self.isFunctionSelf(); - newFunctionNode = (FunctionNode)newFunctionNode.setSymbol(lc, body.getExistingSymbol(name)); - } - } - - //unknown parameters are promoted to object type. - if (newFunctionNode.hasLazyChildren()) { - //the final body has already been assigned as we have left the function node block body by now - objectifySymbols(body); - } - newFunctionNode = finalizeParameters(newFunctionNode); - newFunctionNode = finalizeTypes(newFunctionNode); - for (final Symbol symbol : newFunctionNode.getDeclaredSymbols()) { - if (symbol.getSymbolType().isUnknown()) { - symbol.setType(Type.OBJECT); - symbol.setCanBeUndefined(); - } - } - - List<VarNode> syntheticInitializers = null; - - if (body.getFlag(Block.NEEDS_SELF_SYMBOL)) { - syntheticInitializers = new ArrayList<>(2); - LOG.info("Accepting self symbol init for ", newFunctionNode.getName()); - // "var fn = :callee" - syntheticInitializers.add(createSyntheticInitializer(newFunctionNode.getIdent(), CALLEE, newFunctionNode)); - } - - if (newFunctionNode.needsArguments()) { - if (syntheticInitializers == null) { - syntheticInitializers = new ArrayList<>(1); - } - // "var arguments = :arguments" - syntheticInitializers.add(createSyntheticInitializer(createImplicitIdentifier(ARGUMENTS_VAR.symbolName()), - ARGUMENTS, newFunctionNode)); - } - - if (syntheticInitializers != null) { - final List<Statement> stmts = newFunctionNode.getBody().getStatements(); - final List<Statement> newStatements = new ArrayList<>(stmts.size() + syntheticInitializers.size()); - newStatements.addAll(syntheticInitializers); - newStatements.addAll(stmts); - newFunctionNode = newFunctionNode.setBody(lc, newFunctionNode.getBody().setStatements(lc, newStatements)); - } - - if (returnTypes.peek().isUnknown()) { - LOG.info("Unknown return type promoted to object"); - newFunctionNode = newFunctionNode.setReturnType(lc, Type.OBJECT); - } - final Type returnType = returnTypes.pop(); - newFunctionNode = newFunctionNode.setReturnType(lc, returnType.isUnknown() ? Type.OBJECT : returnType); - newFunctionNode = newFunctionNode.setState(lc, CompilationState.ATTR); - - popLocals(); - - end(newFunctionNode, false); - - return newFunctionNode; - } - - /** - * Creates a synthetic initializer for a variable (a var statement that doesn't occur in the source code). Typically - * used to create assignmnent of {@code :callee} to the function name symbol in self-referential function - * expressions as well as for assignment of {@code :arguments} to {@code arguments}. - * - * @param name the ident node identifying the variable to initialize - * @param initConstant the compiler constant it is initialized to - * @param fn the function node the assignment is for - * @return a var node with the appropriate assignment - */ - private VarNode createSyntheticInitializer(final IdentNode name, final CompilerConstants initConstant, final FunctionNode fn) { - final IdentNode init = compilerConstant(initConstant); - assert init.getSymbol() != null && init.getSymbol().hasSlot(); - - final VarNode synthVar = new VarNode(fn.getLineNumber(), fn.getToken(), fn.getFinish(), name, init); - - final Symbol nameSymbol = fn.getBody().getExistingSymbol(name.getName()); - assert nameSymbol != null; - - return synthVar.setName((IdentNode)name.setSymbol(lc, nameSymbol)); - } - - @Override - public Node leaveIdentNode(final IdentNode identNode) { - final String name = identNode.getName(); - - if (identNode.isPropertyName()) { - // assign a pseudo symbol to property name - final Symbol pseudoSymbol = pseudoSymbol(name); - LOG.info("IdentNode is property name -> assigning pseudo symbol ", pseudoSymbol); - LOG.unindent(); - return end(identNode.setSymbol(lc, pseudoSymbol)); - } - - final Block block = lc.getCurrentBlock(); - - Symbol symbol = findSymbol(block, name); - - //If an existing symbol with the name is found, use that otherwise, declare a new one - if (symbol != null) { - LOG.info("Existing symbol = ", symbol); - if (symbol.isFunctionSelf()) { - final FunctionNode functionNode = lc.getDefiningFunction(symbol); - assert functionNode != null; - assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null; - lc.setFlag(functionNode.getBody(), Block.NEEDS_SELF_SYMBOL); - newType(symbol, FunctionNode.FUNCTION_TYPE); - } else if (!identNode.isInitializedHere()) { - /* - * See NASHORN-448, JDK-8016235 - * - * Here is a use outside the local def scope - * the inCatch check is a conservative approach to handle things that might have only been - * defined in the try block, but with variable declarations, which due to JavaScript rules - * have to be lifted up into the function scope outside the try block anyway, but as the - * flow can fault at almost any place in the try block and get us to the catch block, all we - * know is that we have a declaration, not a definition. This can be made better and less - * conservative once we superimpose a CFG onto the AST. - */ - if (!isLocalDef(name) || inCatch()) { - newType(symbol, Type.OBJECT); - symbol.setCanBeUndefined(); - } - } - - // if symbol is non-local or we're in a with block, we need to put symbol in scope (if it isn't already) - maybeForceScope(symbol); - } else { - LOG.info("No symbol exists. Declare undefined: ", symbol); - symbol = defineSymbol(block, name, IS_GLOBAL); - // we have never seen this before, it can be undefined - newType(symbol, Type.OBJECT); // TODO unknown -we have explicit casts anyway? - symbol.setCanBeUndefined(); - Symbol.setSymbolIsScope(lc, symbol); - } - - setBlockScope(name, symbol); - - if (!identNode.isInitializedHere()) { - symbol.increaseUseCount(); - } - addLocalUse(identNode.getName()); - - return end(identNode.setSymbol(lc, symbol)); - } - - private boolean inCatch() { - return catchNestingLevel > 0; - } - - /** - * If the symbol isn't already a scope symbol, and it is either not local to the current function, or it is being - * referenced from within a with block, we force it to be a scope symbol. - * @param symbol the symbol that might be scoped - */ - private void maybeForceScope(final Symbol symbol) { - if (!symbol.isScope() && symbolNeedsToBeScope(symbol)) { - Symbol.setSymbolIsScope(lc, symbol); - } - } - - private boolean symbolNeedsToBeScope(final Symbol symbol) { - if (symbol.isThis() || symbol.isInternal()) { - return false; - } - boolean previousWasBlock = false; - for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { - final LexicalContextNode node = it.next(); - if (node instanceof FunctionNode) { - // We reached the function boundary without seeing a definition for the symbol - it needs to be in - // scope. - return true; - } else if (node instanceof WithNode) { - if (previousWasBlock) { - // We reached a WithNode; the symbol must be scoped. Note that if the WithNode was not immediately - // preceded by a block, this means we're currently processing its expression, not its body, - // therefore it doesn't count. - return true; - } - previousWasBlock = false; - } else if (node instanceof Block) { - if (((Block)node).getExistingSymbol(symbol.getName()) == symbol) { - // We reached the block that defines the symbol without reaching either the function boundary, or a - // WithNode. The symbol need not be scoped. - return false; - } - previousWasBlock = true; - } else { - previousWasBlock = false; - } - } - throw new AssertionError(); - } - - private void setBlockScope(final String name, final Symbol symbol) { - assert symbol != null; - if (symbol.isGlobal()) { - setUsesGlobalSymbol(); - return; - } - - if (symbol.isScope()) { - Block scopeBlock = null; - for (final Iterator<LexicalContextNode> contextNodeIter = lc.getAllNodes(); contextNodeIter.hasNext(); ) { - final LexicalContextNode node = contextNodeIter.next(); - if (node instanceof Block) { - if (((Block)node).getExistingSymbol(name) != null) { - scopeBlock = (Block)node; - break; - } - } else if (node instanceof FunctionNode) { - lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE); - } - } - - if (scopeBlock != null) { - assert lc.contains(scopeBlock); - lc.setBlockNeedsScope(scopeBlock); - } - } - } - - /** - * Marks the current function as one using any global symbol. The function and all its parent functions will all be - * marked as needing parent scope. - * @see #needsParentScope() - */ - private void setUsesGlobalSymbol() { - for (final Iterator<FunctionNode> fns = lc.getFunctions(); fns.hasNext();) { - lc.setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE); - } - } - - /** - * Search for symbol in the lexical context starting from the given block. - * @param name Symbol name. - * @return Found symbol or null if not found. - */ - private Symbol findSymbol(final Block block, final String name) { - // Search up block chain to locate symbol. - - for (final Iterator<Block> blocks = lc.getBlocks(block); blocks.hasNext();) { - // Find name. - final Symbol symbol = blocks.next().getExistingSymbol(name); - // If found then we are good. - if (symbol != null) { - return symbol; - } - } - return null; - } - - @Override - public Node leaveIndexNode(final IndexNode indexNode) { - return end(ensureSymbol(Type.OBJECT, indexNode)); - } - - @SuppressWarnings("rawtypes") - @Override - public Node leaveLiteralNode(final LiteralNode literalNode) { - assert !literalNode.isTokenType(TokenType.THIS) : "tokentype for " + literalNode + " is this"; //guard against old dead code case. literal nodes should never inherit tokens - assert literalNode instanceof ArrayLiteralNode || !(literalNode.getValue() instanceof Node) : "literals with Node values not supported"; - final Symbol symbol = new Symbol(lc.getCurrentFunction().uniqueName(LITERAL_PREFIX.symbolName()), IS_CONSTANT, literalNode.getType()); - if (literalNode instanceof ArrayLiteralNode) { - ((ArrayLiteralNode)literalNode).analyze(); - } - return end(literalNode.setSymbol(lc, symbol)); - } - - @Override - public boolean enterObjectNode(final ObjectNode objectNode) { - return start(objectNode); - } - - @Override - public Node leaveObjectNode(final ObjectNode objectNode) { - return end(ensureSymbol(Type.OBJECT, objectNode)); - } - - @Override - public Node leaveReturnNode(final ReturnNode returnNode) { - final Expression expr = returnNode.getExpression(); - final Type returnType; - - if (expr != null) { - //we can't do parameter specialization if we return something that hasn't been typed yet - final Symbol symbol = expr.getSymbol(); - if (expr.getType().isUnknown() && symbol.isParam()) { - symbol.setType(Type.OBJECT); - } - - returnType = widestReturnType(returnTypes.pop(), symbol.getSymbolType()); - } else { - returnType = Type.OBJECT; //undefined - } - LOG.info("Returntype is now ", returnType); - returnTypes.push(returnType); - - end(returnNode); - - return returnNode; - } - - @Override - public Node leaveSwitchNode(final SwitchNode switchNode) { - Type type = Type.UNKNOWN; - - final List<CaseNode> newCases = new ArrayList<>(); - for (final CaseNode caseNode : switchNode.getCases()) { - final Node test = caseNode.getTest(); - - CaseNode newCaseNode = caseNode; - if (test != null) { - if (test instanceof LiteralNode) { - //go down to integers if we can - final LiteralNode<?> lit = (LiteralNode<?>)test; - if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) { - if (JSType.isRepresentableAsInt(lit.getNumber())) { - newCaseNode = caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this)); - } - } - } else { - // the "all integer" case that CodeGenerator optimizes for currently assumes literals only - type = Type.OBJECT; - } - - final Type newCaseType = newCaseNode.getTest().getType(); - if (newCaseType.isBoolean()) { - type = Type.OBJECT; //booleans and integers aren't assignment compatible - } else { - type = Type.widest(type, newCaseType); - } - } - - newCases.add(newCaseNode); - } - - //only optimize for all integers - if (!type.isInteger()) { - type = Type.OBJECT; - } - - switchNode.setTag(newInternal(lc.getCurrentFunction().uniqueName(SWITCH_TAG_PREFIX.symbolName()), type)); - - end(switchNode); - - return switchNode.setCases(lc, newCases); - } - - @Override - public Node leaveTryNode(final TryNode tryNode) { - tryNode.setException(exceptionSymbol()); - - if (tryNode.getFinallyBody() != null) { - tryNode.setFinallyCatchAll(exceptionSymbol()); - } - - end(tryNode); - - return tryNode; - } - - @Override - public boolean enterVarNode(final VarNode varNode) { - start(varNode); - - final IdentNode ident = varNode.getName(); - final String name = ident.getName(); - - final Symbol symbol = defineSymbol(lc.getCurrentBlock(), name, IS_VAR); - assert symbol != null; - - // NASHORN-467 - use before definition of vars - conservative - if (isLocalUse(ident.getName())) { - newType(symbol, Type.OBJECT); - symbol.setCanBeUndefined(); - } - - return true; - } - - @Override - public Node leaveVarNode(final VarNode varNode) { - final Expression init = varNode.getInit(); - final IdentNode ident = varNode.getName(); - final String name = ident.getName(); - - final Symbol symbol = findSymbol(lc.getCurrentBlock(), name); - assert ident.getSymbol() == symbol; - - if (init == null) { - // var x; with no init will be treated like a use of x by - // leaveIdentNode unless we remove the name from the localdef list. - removeLocalDef(name); - return end(varNode); - } - - addLocalDef(name); - - assert symbol != null; - - final IdentNode newIdent = (IdentNode)ident.setSymbol(lc, symbol); - - final VarNode newVarNode = varNode.setName(newIdent); - - final boolean isScript = lc.getDefiningFunction(symbol).isProgram(); //see NASHORN-56 - if ((init.getType().isNumeric() || init.getType().isBoolean()) && !isScript) { - // Forbid integers as local vars for now as we have no way to treat them as undefined - newType(symbol, init.getType()); - } else { - newType(symbol, Type.OBJECT); - } - - assert newVarNode.getName().hasType() : newVarNode + " has no type"; - - return end(newVarNode); - } - - @Override - public Node leaveADD(final UnaryNode unaryNode) { - return end(ensureSymbol(arithType(), unaryNode)); - } - - @Override - public Node leaveBIT_NOT(final UnaryNode unaryNode) { - return end(ensureSymbol(Type.INT, unaryNode)); - } - - @Override - public Node leaveDECINC(final UnaryNode unaryNode) { - // @see assignOffset - final Type type = arithType(); - newType(unaryNode.rhs().getSymbol(), type); - return end(ensureSymbol(type, unaryNode)); - } - - @Override - public Node leaveDELETE(final UnaryNode unaryNode) { - final FunctionNode currentFunctionNode = lc.getCurrentFunction(); - final boolean strictMode = currentFunctionNode.isStrict(); - final Expression rhs = unaryNode.rhs(); - final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this); - - Request request = Request.DELETE; - final List<Expression> args = new ArrayList<>(); - - if (rhs instanceof IdentNode) { - // If this is a declared variable or a function parameter, delete always fails (except for globals). - final String name = ((IdentNode)rhs).getName(); - - final boolean failDelete = strictMode || rhs.getSymbol().isParam() || (rhs.getSymbol().isVar() && !isProgramLevelSymbol(name)); - - if (failDelete && rhs.getSymbol().isThis()) { - return LiteralNode.newInstance(unaryNode, true).accept(this); - } - final Expression literalNode = (Expression)LiteralNode.newInstance(unaryNode, name).accept(this); - - if (!failDelete) { - args.add(compilerConstant(SCOPE)); - } - args.add(literalNode); - args.add(strictFlagNode); - - if (failDelete) { - request = Request.FAIL_DELETE; - } - } else if (rhs instanceof AccessNode) { - final Expression base = ((AccessNode)rhs).getBase(); - final IdentNode property = ((AccessNode)rhs).getProperty(); - - args.add(base); - args.add((Expression)LiteralNode.newInstance(unaryNode, property.getName()).accept(this)); - args.add(strictFlagNode); - - } else if (rhs instanceof IndexNode) { - final IndexNode indexNode = (IndexNode)rhs; - final Expression base = indexNode.getBase(); - final Expression index = indexNode.getIndex(); - - args.add(base); - args.add(index); - args.add(strictFlagNode); - - } else { - return LiteralNode.newInstance(unaryNode, true).accept(this); - } - - final RuntimeNode runtimeNode = new RuntimeNode(unaryNode, request, args); - assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //unary parent constructor should do this - - return leaveRuntimeNode(runtimeNode); - } - - /** - * Is the symbol denoted by the specified name in the current lexical context defined in the program level - * @param name the name of the symbol - * @return true if the symbol denoted by the specified name in the current lexical context defined in the program level. - */ - private boolean isProgramLevelSymbol(final String name) { - for(final Iterator<Block> it = lc.getBlocks(); it.hasNext();) { - final Block next = it.next(); - if(next.getExistingSymbol(name) != null) { - return next == lc.getFunctionBody(lc.getOutermostFunction()); - } - } - throw new AssertionError("Couldn't find symbol " + name + " in the context"); - } - - @Override - public Node leaveNEW(final UnaryNode unaryNode) { - return end(ensureSymbol(Type.OBJECT, unaryNode.setRHS(((CallNode)unaryNode.rhs()).setIsNew()))); - } - - @Override - public Node leaveNOT(final UnaryNode unaryNode) { - return end(ensureSymbol(Type.BOOLEAN, unaryNode)); - } - - private IdentNode compilerConstant(final CompilerConstants cc) { - return (IdentNode)createImplicitIdentifier(cc.symbolName()).setSymbol(lc, lc.getCurrentFunction().compilerConstant(cc)); - } - - /** - * Creates an ident node for an implicit identifier within the function (one not declared in the script source - * code). These identifiers are defined with function's token and finish. - * @param name the name of the identifier - * @return an ident node representing the implicit identifier. - */ - private IdentNode createImplicitIdentifier(final String name) { - final FunctionNode fn = lc.getCurrentFunction(); - return new IdentNode(fn.getToken(), fn.getFinish(), name); - } - - @Override - public Node leaveTYPEOF(final UnaryNode unaryNode) { - final Expression rhs = unaryNode.rhs(); - - final List<Expression> args = new ArrayList<>(); - if (rhs instanceof IdentNode && !rhs.getSymbol().isParam() && !rhs.getSymbol().isVar()) { - args.add(compilerConstant(SCOPE)); - args.add((Expression)LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null - } else { - args.add(rhs); - args.add((Expression)LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this' - } - - RuntimeNode runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args); - assert runtimeNode.getSymbol() == unaryNode.getSymbol(); - - runtimeNode = (RuntimeNode)leaveRuntimeNode(runtimeNode); - - end(unaryNode); - - return runtimeNode; - } - - @Override - public Node leaveRuntimeNode(final RuntimeNode runtimeNode) { - return end(ensureSymbol(runtimeNode.getRequest().getReturnType(), runtimeNode)); - } - - @Override - public Node leaveSUB(final UnaryNode unaryNode) { - return end(ensureSymbol(arithType(), unaryNode)); - } - - @Override - public Node leaveVOID(final UnaryNode unaryNode) { - return end(ensureSymbol(Type.OBJECT, unaryNode)); - } - - /** - * 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 Expression lhs = binaryNode.lhs(); - final Expression rhs = binaryNode.rhs(); - - ensureTypeNotUnknown(lhs); - ensureTypeNotUnknown(rhs); - //even if we are adding two known types, this can overflow. i.e. - //int and number -> number. - //int and int are also number though. - //something and object is object - return end(ensureSymbol(Type.widest(arithType(), Type.widest(lhs.getType(), rhs.getType())), binaryNode)); - } - - @Override - public Node leaveAND(final BinaryNode binaryNode) { - return end(ensureSymbol(Type.OBJECT, binaryNode)); - } - - /** - * This is a helper called before an assignment. - * @param binaryNode assignment node - */ - private boolean enterAssignmentNode(final BinaryNode binaryNode) { - start(binaryNode); - - return true; - } - - - /** - * 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 - */ - private Node leaveAssignmentNode(final BinaryNode binaryNode) { - final Expression lhs = binaryNode.lhs(); - final Expression rhs = binaryNode.rhs(); - final Type type; - - if (lhs instanceof IdentNode) { - final Block block = lc.getCurrentBlock(); - final IdentNode ident = (IdentNode)lhs; - final String name = ident.getName(); - final Symbol symbol = findSymbol(block, name); - - if (symbol == null) { - defineSymbol(block, name, IS_GLOBAL); - } else { - maybeForceScope(symbol); - } - - addLocalDef(name); - } - - if (rhs.getType().isNumeric()) { - type = Type.widest(lhs.getType(), rhs.getType()); - } else { - type = Type.OBJECT; //force lhs to be an object if not numeric assignment, e.g. strings too. - } - - newType(lhs.getSymbol(), type); - return end(ensureSymbol(type, binaryNode)); - } - - private boolean isLocal(final FunctionNode function, final Symbol symbol) { - final FunctionNode definingFn = lc.getDefiningFunction(symbol); - // Temp symbols are not assigned to a block, so their defining fn is null; those can be assumed local - return definingFn == null || definingFn == function; - } - - @Override - public boolean enterASSIGN(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN(final BinaryNode binaryNode) { - return leaveAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_ADD(final BinaryNode binaryNode) { - final Expression lhs = binaryNode.lhs(); - final Expression rhs = binaryNode.rhs(); - - final Type widest = Type.widest(lhs.getType(), rhs.getType()); - //Type.NUMBER if we can't prove that the add doesn't overflow. todo - return leaveSelfModifyingAssignmentNode(binaryNode, widest.isNumeric() ? Type.NUMBER : Type.OBJECT); - } - - @Override - public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_BIT_AND(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_BIT_OR(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_BIT_XOR(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_DIV(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_MOD(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_MUL(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_SAR(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_SHL(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_SHR(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { - return enterAssignmentNode(binaryNode); - } - - @Override - public Node leaveASSIGN_SUB(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode); - } - - @Override - public Node leaveBIT_AND(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); - } - - @Override - public Node leaveBIT_OR(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); - } - - @Override - public Node leaveBIT_XOR(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); - } - - @Override - public Node leaveCOMMARIGHT(final BinaryNode binaryNode) { - return leaveComma(binaryNode, binaryNode.rhs()); - } - - @Override - public Node leaveCOMMALEFT(final BinaryNode binaryNode) { - return leaveComma(binaryNode, binaryNode.lhs()); - } - - private Node leaveComma(final BinaryNode commaNode, final Expression effectiveExpr) { - ensureTypeNotUnknown(effectiveExpr); - return end(ensureSymbol(effectiveExpr.getType(), commaNode)); - } - - @Override - public Node leaveDIV(final BinaryNode binaryNode) { - return leaveBinaryArithmetic(binaryNode); - } - - private Node leaveCmp(final BinaryNode binaryNode) { - ensureTypeNotUnknown(binaryNode.lhs()); - ensureTypeNotUnknown(binaryNode.rhs()); - final Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); - ensureSymbol(widest, binaryNode.lhs()); - ensureSymbol(widest, binaryNode.rhs()); - return end(ensureSymbol(Type.BOOLEAN, binaryNode)); - } - - private Node coerce(final BinaryNode binaryNode, final Type operandType, final Type destType) { - // TODO we currently don't support changing inferred type based on uses, only on - // definitions. we would need some additional logic. We probably want to do that - // in the future, if e.g. a specialized method gets parameter that is only used - // as, say, an int : function(x) { return x & 4711 }, and x is not defined in - // the function. to make this work, uncomment the following two type inferences - // and debug. - //newType(binaryNode.lhs().getSymbol(), operandType); - //newType(binaryNode.rhs().getSymbol(), operandType); - return ensureSymbol(destType, binaryNode); - } - - private Node coerce(final BinaryNode binaryNode, final Type type) { - return coerce(binaryNode, type, type); - } - - //leave a binary node and inherit the widest type of lhs , rhs - private Node leaveBinaryArithmetic(final BinaryNode binaryNode) { - assert !Compiler.shouldUseIntegerArithmetic(); - return end(coerce(binaryNode, Type.NUMBER)); - } - - @Override - public Node leaveEQ(final BinaryNode binaryNode) { - return leaveCmp(binaryNode); - } - - @Override - public Node leaveEQ_STRICT(final BinaryNode binaryNode) { - return leaveCmp(binaryNode); - } - - @Override - public Node leaveGE(final BinaryNode binaryNode) { - return leaveCmp(binaryNode); - } - - @Override - public Node leaveGT(final BinaryNode binaryNode) { - return leaveCmp(binaryNode); - } - - @Override - public Node leaveIN(final BinaryNode binaryNode) { - return leaveBinaryRuntimeOperator(binaryNode, Request.IN); - } - - @Override - public Node leaveINSTANCEOF(final BinaryNode binaryNode) { - return leaveBinaryRuntimeOperator(binaryNode, Request.INSTANCEOF); - } - - private Node leaveBinaryRuntimeOperator(final BinaryNode binaryNode, final Request request) { - try { - // Don't do a full RuntimeNode.accept, as we don't want to double-visit the binary node operands - return leaveRuntimeNode(new RuntimeNode(binaryNode, request)); - } finally { - end(binaryNode); - } - } - - @Override - public Node leaveLE(final BinaryNode binaryNode) { - return leaveCmp(binaryNode); - } - - @Override - public Node leaveLT(final BinaryNode binaryNode) { - return leaveCmp(binaryNode); - } - - @Override - public Node leaveMOD(final BinaryNode binaryNode) { - return leaveBinaryArithmetic(binaryNode); - } - - @Override - public Node leaveMUL(final BinaryNode binaryNode) { - return leaveBinaryArithmetic(binaryNode); - } - - @Override - public Node leaveNE(final BinaryNode binaryNode) { - return leaveCmp(binaryNode); - } - - @Override - public Node leaveNE_STRICT(final BinaryNode binaryNode) { - return leaveCmp(binaryNode); - } - - @Override - public Node leaveOR(final BinaryNode binaryNode) { - return end(ensureSymbol(Type.OBJECT, binaryNode)); - } - - @Override - public Node leaveSAR(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); - } - - @Override - public Node leaveSHL(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); - } - - @Override - public Node leaveSHR(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.LONG)); - } - - @Override - public Node leaveSUB(final BinaryNode binaryNode) { - return leaveBinaryArithmetic(binaryNode); - } - - @Override - public Node leaveForNode(final ForNode forNode) { - if (forNode.isForIn()) { - forNode.setIterator(newInternal(lc.getCurrentFunction().uniqueName(ITERATOR_PREFIX.symbolName()), Type.typeFor(ITERATOR_PREFIX.type()))); //NASHORN-73 - /* - * 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 - */ - newType(forNode.getInit().getSymbol(), Type.OBJECT); - } - - end(forNode); - - return forNode; - } - - @Override - public Node leaveTernaryNode(final TernaryNode ternaryNode) { - final Expression trueExpr = ternaryNode.getTrueExpression(); - final Expression falseExpr = ternaryNode.getFalseExpression(); - - ensureTypeNotUnknown(trueExpr); - ensureTypeNotUnknown(falseExpr); - - final Type type = widestReturnType(trueExpr.getType(), falseExpr.getType()); - return end(ensureSymbol(type, ternaryNode)); - } - - /** - * When doing widening for return types of a function or a ternary operator, it is not valid to widen a boolean to - * anything other than Object. Also, widening a numeric type to an object type must widen to Object proper and not - * any more specific subclass (e.g. widest of int/long/double and String is Object). - * @param t1 type 1 - * @param t2 type 2 - * @return wider of t1 and t2, except if one is boolean and the other is neither boolean nor unknown, or if one is - * numeric and the other is neither numeric nor unknown in which case {@code Type.OBJECT} is returned. - */ - private static Type widestReturnType(final Type t1, final Type t2) { - if (t1.isUnknown()) { - return t2; - } else if (t2.isUnknown()) { - return t1; - } else if (t1.isBoolean() != t2.isBoolean() || t1.isNumeric() != t2.isNumeric()) { - return Type.OBJECT; - } - return Type.widest(t1, t2); - } - - private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) { - final Class<?> type = cc.type(); - // Must not call this method for constants with no explicit types; use the one with (..., Type) signature instead. - assert type != null; - initCompileConstant(cc, block, flags, Type.typeFor(type)); - } - - private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags, final Type type) { - final Symbol symbol = defineSymbol(block, cc.symbolName(), flags); - symbol.setTypeOverride(type); - symbol.setNeedsSlot(true); - } - - /** - * 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, final Block body) { - int pos = 0; - for (final IdentNode param : functionNode.getParameters()) { - addLocalDef(param.getName()); - - final Type callSiteParamType = functionNode.getHints().getParameterType(pos); - int flags = IS_PARAM; - if (callSiteParamType != null) { - LOG.info("Param ", param, " has a callsite type ", callSiteParamType, ". Using that."); - flags |= Symbol.IS_SPECIALIZED_PARAM; - } - - final Symbol paramSymbol = defineSymbol(body, param.getName(), flags); - assert paramSymbol != null; - - newType(paramSymbol, callSiteParamType == null ? Type.UNKNOWN : callSiteParamType); - - LOG.info("Initialized param ", pos, "=", paramSymbol); - pos++; - } - - } - - /** - * 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 FunctionNode finalizeParameters(final FunctionNode functionNode) { - final List<IdentNode> newParams = new ArrayList<>(); - final boolean isVarArg = functionNode.isVarArg(); - final int nparams = functionNode.getParameters().size(); - - int specialize = 0; - int pos = 0; - for (final IdentNode param : functionNode.getParameters()) { - final Symbol paramSymbol = functionNode.getBody().getExistingSymbol(param.getName()); - assert paramSymbol != null; - assert paramSymbol.isParam(); - newParams.add((IdentNode)param.setSymbol(lc, paramSymbol)); - - assert paramSymbol != null; - Type type = functionNode.getHints().getParameterType(pos); - if (type == null) { - type = Type.OBJECT; - } - - // if we know that a parameter is only used as a certain type throughout - // this function, we can tell the runtime system that no matter what the - // call site is, use this information: - // we also need more than half of the parameters to be specializable - // for the heuristic to be worth it, and we need more than one use of - // the parameter to consider it, i.e. function(x) { call(x); } doens't count - if (paramSymbol.getUseCount() > 1 && !paramSymbol.getSymbolType().isObject()) { - LOG.finest("Parameter ", param, " could profit from specialization to ", paramSymbol.getSymbolType()); - specialize++; - } - - newType(paramSymbol, Type.widest(type, paramSymbol.getSymbolType())); - - // parameters should not be slots for a function that uses variable arity signature - if (isVarArg) { - paramSymbol.setNeedsSlot(false); - } - - pos++; - } - - FunctionNode newFunctionNode = functionNode; - - if (nparams == 0 || (specialize * 2) < nparams) { - newFunctionNode = newFunctionNode.clearSnapshot(lc); - } - - return newFunctionNode.setParameters(lc, newParams); - } - - /** - * Move any properties from a global map into the scope of this method - * @param block the function node body for which to init scope vars - */ - private void initFromPropertyMap(final Block block) { - // For a script, add scope symbols as defined in the property map - - final PropertyMap map = Context.getGlobalMap(); - - for (final Property property : map.getProperties()) { - final String key = property.getKey(); - final Symbol symbol = defineSymbol(block, key, IS_GLOBAL); - newType(symbol, Type.OBJECT); - LOG.info("Added global symbol from property map ", symbol); - } - } - - private static void ensureTypeNotUnknown(final Expression node) { - - final Symbol symbol = node.getSymbol(); - - LOG.info("Ensure type not unknown for: ", symbol); - - /* - * 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.isSpecializedParam())) { - newType(symbol, Type.OBJECT); - symbol.setCanBeUndefined(); - } - } - - private static Symbol pseudoSymbol(final String name) { - return new Symbol(name, 0, Type.OBJECT); - } - - private Symbol exceptionSymbol() { - return newInternal(lc.getCurrentFunction().uniqueName(EXCEPTION_PREFIX.symbolName()), Type.typeFor(EXCEPTION_PREFIX.type())); - } - - /** - * Return the type that arithmetic ops should use. Until we have implemented better type - * analysis (range based) or overflow checks that are fast enough for int arithmetic, - * this is the number type - * @return the arithetic type - */ - private static Type arithType() { - return Compiler.shouldUseIntegerArithmetic() ? Type.INT : Type.NUMBER; - } - - /** - * If types have changed, we can have failed to update vars. For example - * - * var x = 17; //x is int - * x = "apa"; //x is object. This will be converted fine - * - * @param functionNode - */ - private FunctionNode finalizeTypes(final FunctionNode functionNode) { - final Set<Node> changed = new HashSet<>(); - FunctionNode currentFunctionNode = functionNode; - do { - changed.clear(); - final FunctionNode newFunctionNode = (FunctionNode)currentFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { - - private Expression widen(final Expression node, final Type to) { - if (node instanceof LiteralNode) { - return node; - } - final Type from = node.getType(); - if (!Type.areEquivalent(from, to) && Type.widest(from, to) == to) { - LOG.fine("Had to post pass widen '", node, "' ", Debug.id(node), " from ", node.getType(), " to ", to); - Symbol symbol = node.getSymbol(); - if (symbol.isShared() && symbol.wouldChangeType(to)) { - symbol = temporarySymbols.getTypedTemporarySymbol(to); - } - newType(symbol, to); - final Expression newNode = node.setSymbol(lc, symbol); - changed.add(newNode); - return newNode; - } - return node; - } - - @Override - public boolean enterFunctionNode(final FunctionNode node) { - return !node.isLazy(); - } - - // - // Eg. - // - // var d = 17; - // var e; - // e = d; //initially typed as int for node type, should retype as double - // e = object; - // - // var d = 17; - // var e; - // e -= d; //initially type number, should number remain with a final conversion supplied by Store. ugly, but the computation result of the sub is numeric - // e = object; - // - @SuppressWarnings("fallthrough") - @Override - public Node leaveBinaryNode(final BinaryNode binaryNode) { - final Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); - BinaryNode newBinaryNode = binaryNode; - - if (isAdd(binaryNode)) { - newBinaryNode = (BinaryNode)widen(newBinaryNode, widest); - if (newBinaryNode.getType().isObject() && !isAddString(newBinaryNode)) { - return new RuntimeNode(newBinaryNode, Request.ADD); - } - } else if (binaryNode.isComparison()) { - final Expression lhs = newBinaryNode.lhs(); - final Expression rhs = newBinaryNode.rhs(); - - Type cmpWidest = Type.widest(lhs.getType(), rhs.getType()); - - boolean newRuntimeNode = false, finalized = false; - switch (newBinaryNode.tokenType()) { - case EQ_STRICT: - case NE_STRICT: - if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) { - newRuntimeNode = true; - cmpWidest = Type.OBJECT; - finalized = true; - } - //fallthru - default: - if (newRuntimeNode || cmpWidest.isObject()) { - return new RuntimeNode(newBinaryNode, Request.requestFor(binaryNode)).setIsFinal(finalized); - } - break; - } - - return newBinaryNode; - } else { - if (!binaryNode.isAssignment() || binaryNode.isSelfModifying()) { - return newBinaryNode; - } - checkThisAssignment(binaryNode); - newBinaryNode = newBinaryNode.setLHS(widen(newBinaryNode.lhs(), widest)); - newBinaryNode = (BinaryNode)widen(newBinaryNode, widest); - } - - return newBinaryNode; - - } - - private boolean isAdd(final Node node) { - return node.isTokenType(TokenType.ADD); - } - - /** - * 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 && isAdd(node)) { - 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).isString(); - } - - private void checkThisAssignment(final BinaryNode binaryNode) { - if (binaryNode.isAssignment()) { - if (binaryNode.lhs() instanceof AccessNode) { - final AccessNode accessNode = (AccessNode) binaryNode.lhs(); - - if (accessNode.getBase().getSymbol().isThis()) { - lc.getCurrentFunction().addThisProperty(accessNode.getProperty().getName()); - } - } - } - } - }); - lc.replace(currentFunctionNode, newFunctionNode); - currentFunctionNode = newFunctionNode; - } while (!changed.isEmpty()); - - return currentFunctionNode; - } - - private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode) { - return leaveSelfModifyingAssignmentNode(binaryNode, binaryNode.getWidestOperationType()); - } - - private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode, final Type destType) { - //e.g. for -=, Number, no wider, destType (binaryNode.getWidestOperationType()) is the coerce type - final Expression lhs = binaryNode.lhs(); - - newType(lhs.getSymbol(), destType); //may not narrow if dest is already wider than destType - - return end(ensureSymbol(destType, binaryNode)); - } - - private Expression ensureSymbol(final Type type, final Expression expr) { - LOG.info("New TEMPORARY added to ", lc.getCurrentFunction().getName(), " type=", type); - return temporarySymbols.ensureSymbol(lc, type, expr); - } - - private Symbol newInternal(final String name, final Type type) { - final Symbol iter = defineSymbol(lc.getCurrentBlock(), name, IS_VAR | IS_INTERNAL); - iter.setType(type); // NASHORN-73 - return iter; - } - - private static void newType(final Symbol symbol, final Type type) { - final Type oldType = symbol.getSymbolType(); - symbol.setType(type); - - if (symbol.getSymbolType() != oldType) { - LOG.info("New TYPE ", type, " for ", symbol," (was ", oldType, ")"); - } - - if (symbol.isParam()) { - symbol.setType(type); - LOG.info("Param type change ", symbol); - } - } - - private void pushLocalsFunction() { - localDefs.push(new HashSet<String>()); - localUses.push(new HashSet<String>()); - } - - private void pushLocalsBlock() { - localDefs.push(new HashSet<>(localDefs.peek())); - localUses.push(new HashSet<>(localUses.peek())); - } - - private void popLocals() { - localDefs.pop(); - localUses.pop(); - } - - private boolean isLocalDef(final String name) { - return localDefs.peek().contains(name); - } - - private void addLocalDef(final String name) { - LOG.info("Adding local def of symbol: '", name, "'"); - localDefs.peek().add(name); - } - - private void removeLocalDef(final String name) { - LOG.info("Removing local def of symbol: '", name, "'"); - localDefs.peek().remove(name); - } - - private boolean isLocalUse(final String name) { - return localUses.peek().contains(name); - } - - private void addLocalUse(final String name) { - LOG.info("Adding local use of symbol: '", name, "'"); - localUses.peek().add(name); - } - - /** - * Pessimistically promote all symbols in current function node to Object types - * This is done when the function contains unevaluated black boxes such as - * lazy sub-function nodes that have not been compiled. - * - * @param body body for the function node we are leaving - */ - private static void objectifySymbols(final Block body) { - body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { - private void toObject(final Block block) { - for (final Symbol symbol : block.getSymbols()) { - if (!symbol.isTemp()) { - newType(symbol, Type.OBJECT); - } - } - } - - @Override - public boolean enterBlock(final Block block) { - toObject(block); - return true; - } - - @Override - public boolean enterFunctionNode(final FunctionNode node) { - return false; - } - }); - } - - private static String name(final Node node) { - final String cn = node.getClass().getName(); - final int lastDot = cn.lastIndexOf('.'); - if (lastDot == -1) { - return cn; - } - return cn.substring(lastDot + 1); - } - - private boolean start(final Node node) { - return start(node, true); - } - - private boolean start(final Node node, final boolean printNode) { - if (DEBUG) { - final StringBuilder sb = new StringBuilder(); - - sb.append("[ENTER "). - append(name(node)). - append("] "). - append(printNode ? node.toString() : ""). - append(" in '"). - append(lc.getCurrentFunction().getName()). - append("'"); - LOG.info(sb); - LOG.indent(); - } - - return true; - } - - private <T extends Node> T end(final T node) { - return end(node, true); - } - - private <T extends Node> T end(final T node, final boolean printNode) { - if(node instanceof Statement) { - // If we're done with a statement, all temporaries can be reused. - temporarySymbols.reuse(); - } - if (DEBUG) { - final StringBuilder sb = new StringBuilder(); - - sb.append("[LEAVE "). - append(name(node)). - append("] "). - append(printNode ? node.toString() : ""). - append(" in '"). - append(lc.getCurrentFunction().getName()). - append('\''); - - if (node instanceof Expression) { - final Symbol symbol = ((Expression)node).getSymbol(); - if (symbol == null) { - sb.append(" <NO SYMBOL>"); - } else { - sb.append(" <symbol=").append(symbol).append('>'); - } - } - - LOG.unindent(); - LOG.info(sb); - } - - return node; - } -} diff --git a/src/jdk/nashorn/internal/codegen/BranchOptimizer.java b/src/jdk/nashorn/internal/codegen/BranchOptimizer.java index ad9bdb07..5c8f9645 100644 --- a/src/jdk/nashorn/internal/codegen/BranchOptimizer.java +++ b/src/jdk/nashorn/internal/codegen/BranchOptimizer.java @@ -32,10 +32,10 @@ import static jdk.nashorn.internal.codegen.Condition.LE; import static jdk.nashorn.internal.codegen.Condition.LT; import static jdk.nashorn.internal.codegen.Condition.NE; -import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.Expression; -import jdk.nashorn.internal.ir.TernaryNode; +import jdk.nashorn.internal.ir.JoinPredecessorExpression; +import jdk.nashorn.internal.ir.LocalVariableConversion; import jdk.nashorn.internal.ir.UnaryNode; /** @@ -57,7 +57,7 @@ final class BranchOptimizer { } private void branchOptimizer(final UnaryNode unaryNode, final Label label, final boolean state) { - final Expression rhs = unaryNode.rhs(); + final Expression rhs = unaryNode.getExpression(); switch (unaryNode.tokenType()) { case NOT: @@ -71,13 +71,7 @@ final class BranchOptimizer { break; } - // convert to boolean - codegen.load(unaryNode, Type.BOOLEAN); - if (state) { - method.ifne(label); - } else { - method.ifeq(label); - } + loadTestAndJump(unaryNode, label, state); } private void branchOptimizer(final BinaryNode binaryNode, final Label label, final boolean state) { @@ -88,86 +82,97 @@ final class BranchOptimizer { case AND: if (state) { final Label skip = new Label("skip"); - branchOptimizer(lhs, skip, false); - branchOptimizer(rhs, label, true); + optimizeLogicalOperand(lhs, skip, false, false); + optimizeLogicalOperand(rhs, label, true, true); method.label(skip); } else { - branchOptimizer(lhs, label, false); - branchOptimizer(rhs, label, false); + optimizeLogicalOperand(lhs, label, false, false); + optimizeLogicalOperand(rhs, label, false, true); } return; case OR: if (state) { - branchOptimizer(lhs, label, true); - branchOptimizer(rhs, label, true); + optimizeLogicalOperand(lhs, label, true, false); + optimizeLogicalOperand(rhs, label, true, true); } else { final Label skip = new Label("skip"); - branchOptimizer(lhs, skip, true); - branchOptimizer(rhs, label, false); + optimizeLogicalOperand(lhs, skip, true, false); + optimizeLogicalOperand(rhs, label, false, true); method.label(skip); } return; case EQ: case EQ_STRICT: - codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType())); + codegen.loadBinaryOperands(binaryNode); method.conditionalJump(state ? EQ : NE, true, label); return; case NE: case NE_STRICT: - codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType())); + codegen.loadBinaryOperands(binaryNode); method.conditionalJump(state ? NE : EQ, true, label); return; case GE: - codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType())); - method.conditionalJump(state ? GE : LT, !state, label); + codegen.loadBinaryOperands(binaryNode); + method.conditionalJump(state ? GE : LT, false, label); return; case GT: - codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType())); - method.conditionalJump(state ? GT : LE, !state, label); + codegen.loadBinaryOperands(binaryNode); + method.conditionalJump(state ? GT : LE, false, label); return; case LE: - codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType())); - method.conditionalJump(state ? LE : GT, state, label); + codegen.loadBinaryOperands(binaryNode); + method.conditionalJump(state ? LE : GT, true, label); return; case LT: - codegen.loadBinaryOperands(lhs, rhs, Type.widest(lhs.getType(), rhs.getType())); - method.conditionalJump(state ? LT : GE, state, label); + codegen.loadBinaryOperands(binaryNode); + method.conditionalJump(state ? LT : GE, true, label); return; default: break; } - codegen.load(binaryNode, Type.BOOLEAN); - if (state) { - method.ifne(label); + loadTestAndJump(binaryNode, label, state); + } + + private void optimizeLogicalOperand(final Expression expr, final Label label, final boolean state, final boolean isRhs) { + final JoinPredecessorExpression jpexpr = (JoinPredecessorExpression)expr; + if(LocalVariableConversion.hasLiveConversion(jpexpr)) { + final Label after = new Label("after"); + branchOptimizer(jpexpr.getExpression(), after, !state); + method.beforeJoinPoint(jpexpr); + method._goto(label); + method.label(after); + if(isRhs) { + method.beforeJoinPoint(jpexpr); + } } else { - method.ifeq(label); + branchOptimizer(jpexpr.getExpression(), label, state); } } - private void branchOptimizer(final Expression node, final Label label, final boolean state) { - if (!(node instanceof TernaryNode)) { - - if (node instanceof BinaryNode) { - branchOptimizer((BinaryNode)node, label, state); - return; - } + if (node instanceof BinaryNode) { + branchOptimizer((BinaryNode)node, label, state); + return; + } - if (node instanceof UnaryNode) { - branchOptimizer((UnaryNode)node, label, state); - return; - } + if (node instanceof UnaryNode) { + branchOptimizer((UnaryNode)node, label, state); + return; } - codegen.load(node, Type.BOOLEAN); + loadTestAndJump(node, label, state); + } + + private void loadTestAndJump(final Expression node, final Label label, final boolean state) { + codegen.loadExpressionAsBoolean(node); if (state) { method.ifne(label); } else { diff --git a/src/jdk/nashorn/internal/codegen/ClassEmitter.java b/src/jdk/nashorn/internal/codegen/ClassEmitter.java index 717ed356..386effdc 100644 --- a/src/jdk/nashorn/internal/codegen/ClassEmitter.java +++ b/src/jdk/nashorn/internal/codegen/ClassEmitter.java @@ -49,24 +49,27 @@ 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 static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; import java.io.ByteArrayOutputStream; import java.io.PrintWriter; -import java.util.Arrays; +import java.security.AccessController; +import java.security.PrivilegedAction; 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.ir.SplitNode; +import jdk.nashorn.internal.ir.debug.NashornClassReader; +import jdk.nashorn.internal.ir.debug.NashornTextifier; +import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.PropertyMap; -import jdk.nashorn.internal.runtime.ScriptEnvironment; +import jdk.nashorn.internal.runtime.RewriteException; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.Source; @@ -105,6 +108,8 @@ import jdk.nashorn.internal.runtime.Source; * @see Compiler */ public class ClassEmitter implements Emitter { + /** Default flags for class generation - public class */ + private static final EnumSet<Flag> DEFAULT_METHOD_FLAGS = EnumSet.of(Flag.PUBLIC); /** Sanity check flag - have we started on a class? */ private boolean classStarted; @@ -122,10 +127,7 @@ public class ClassEmitter implements Emitter { protected final ClassWriter cw; /** The script environment */ - protected final ScriptEnvironment env; - - /** Default flags for class generation - oublic class */ - private static final EnumSet<Flag> DEFAULT_METHOD_FLAGS = EnumSet.of(Flag.PUBLIC); + protected final Context context; /** Compile unit class name. */ private String unitClassName; @@ -140,10 +142,8 @@ public class ClassEmitter implements Emitter { * @param env script environment * @param cw ASM classwriter */ - private ClassEmitter(final ScriptEnvironment env, final ClassWriter cw) { - assert env != null; - - this.env = env; + private ClassEmitter(final Context context, final ClassWriter cw) { + this.context = context; this.cw = cw; this.methodsStarted = new HashSet<>(); } @@ -156,8 +156,8 @@ public class ClassEmitter implements Emitter { * @param superClassName super class name for class * @param interfaceNames names of interfaces implemented by this class, or null if none */ - ClassEmitter(final ScriptEnvironment env, final String className, final String superClassName, final String... interfaceNames) { - this(env, new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS)); + 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); } @@ -169,8 +169,8 @@ public class ClassEmitter implements Emitter { * @param unitClassName Compile unit class name. * @param strictMode Should we generate this method in strict mode */ - ClassEmitter(final ScriptEnvironment env, final String sourceName, final String unitClassName, final boolean strictMode) { - this(env, + ClassEmitter(final Context context, final String sourceName, final String unitClassName, final boolean strictMode) { + this(context, new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) { private static final String OBJECT_CLASS = "java/lang/Object"; @@ -196,6 +196,10 @@ public class ClassEmitter implements Emitter { defineCommonStatics(strictMode); } + Context getContext() { + return context; + } + /** * Returns the name of the compile unit class name. * @return the name of the compile unit class name. @@ -273,51 +277,51 @@ public class ClassEmitter implements Emitter { } // $getXXXX$array - get the ith entry from the constants table and cast to XXXX[]. - for (final Class<?> cls : constantMethodNeeded) { - if (cls.isArray()) { - defineGetArrayMethod(cls); + for (final Class<?> clazz : constantMethodNeeded) { + if (clazz.isArray()) { + defineGetArrayMethod(clazz); } } } /** - * Constructs a primitive specific method for getting the ith entry from the constants table and cast. - * @param cls Array class. + * Constructs a primitive specific method for getting the ith entry from the constants table as an array. + * @param clazz Array class. */ - private void defineGetArrayMethod(final Class<?> cls) { + private void defineGetArrayMethod(final Class<?> clazz) { assert unitClassName != null; - final String methodName = getArrayMethodName(cls); - final MethodEmitter getArrayMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), methodName, cls, int.class); + final String methodName = getArrayMethodName(clazz); + final MethodEmitter getArrayMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), methodName, clazz, int.class); getArrayMethod.begin(); getArrayMethod.getStatic(unitClassName, CONSTANTS.symbolName(), CONSTANTS.descriptor()) .load(Type.INT, 0) .arrayload() - .checkcast(cls) - .dup() - .arraylength() - .invoke(staticCallNoLookup(Arrays.class, "copyOf", cls, cls, int.class)) + .checkcast(clazz) + .invoke(virtualCallNoLookup(clazz, "clone", Object.class)) + .checkcast(clazz) ._return(); getArrayMethod.end(); } + /** * Generate the name of a get array from constant pool method. - * @param cls Name of array class. + * @param clazz Name of array class. * @return Method name. */ - static String getArrayMethodName(final Class<?> cls) { - assert cls.isArray(); - return GET_ARRAY_PREFIX.symbolName() + cls.getComponentType().getSimpleName() + GET_ARRAY_SUFFIX.symbolName(); + static String getArrayMethodName(final Class<?> clazz) { + assert clazz.isArray(); + return GET_ARRAY_PREFIX.symbolName() + clazz.getComponentType().getSimpleName() + GET_ARRAY_SUFFIX.symbolName(); } /** * Ensure a get constant method is issued for the class. - * @param cls Class of constant. + * @param clazz Class of constant. */ - void needGetConstantMethod(final Class<?> cls) { - constantMethodNeeded.add(cls); + void needGetConstantMethod(final Class<?> clazz) { + constantMethodNeeded.add(clazz); } /** @@ -375,16 +379,19 @@ public class ClassEmitter implements Emitter { static String disassemble(final byte[] bytecode) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (final PrintWriter pw = new PrintWriter(baos)) { - new ClassReader(bytecode).accept(new TraceClassVisitor(pw), 0); + final NashornClassReader cr = new NashornClassReader(bytecode); + final Context ctx = AccessController.doPrivileged(new PrivilegedAction<Context>() { + @Override + public Context run() { + return Context.getContext(); + } + }); + final TraceClassVisitor tcv = new TraceClassVisitor(null, new NashornTextifier(ctx.getEnv(), cr), pw); + cr.accept(tcv, 0); } - return new String(baos.toByteArray()); - } - /** - * @return env used for class emission - */ - ScriptEnvironment getEnv() { - return env; + final String str = new String(baos.toByteArray()); + return str; } /** @@ -474,10 +481,11 @@ public class ClassEmitter implements Emitter { * @return method emitter to use for weaving this method */ MethodEmitter method(final FunctionNode functionNode) { + final FunctionSignature signature = new FunctionSignature(functionNode); final MethodVisitor mv = cw.visitMethod( ACC_PUBLIC | ACC_STATIC | (functionNode.isVarArg() ? ACC_VARARGS : 0), functionNode.getName(), - new FunctionSignature(functionNode).toString(), + signature.toString(), null, null); @@ -485,6 +493,24 @@ public class ClassEmitter implements Emitter { } /** + * Add a new method to the class, representing a rest-of version of the function node + * + * @param functionNode the function node to generate a method for + * @return method emitter to use for weaving this method + */ + MethodEmitter restOfMethod(final FunctionNode functionNode) { + final MethodVisitor mv = cw.visitMethod( + ACC_PUBLIC | ACC_STATIC, + functionNode.getName(), + Type.getMethodDescriptor(functionNode.getReturnType().getTypeClass(), RewriteException.class), + null, + null); + + return new MethodEmitter(this, mv, functionNode); + } + + + /** * Start generating the <clinit> method in the class * * @return method emitter to use for weaving <clinit> diff --git a/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/src/jdk/nashorn/internal/codegen/CodeGenerator.java index d299d0a3..0266e5d1 100644 --- a/src/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -29,6 +29,7 @@ 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.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; +import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION; 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.QUICK_PREFIX; @@ -45,25 +46,38 @@ 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 static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; +import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY; +import static jdk.nashorn.internal.ir.Symbol.HAS_SLOT; import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; -import static jdk.nashorn.internal.ir.Symbol.IS_TEMP; +import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; +import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_APPLY_TO_CALL; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_FAST_SCOPE; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT; 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.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.TreeMap; +import java.util.function.Supplier; +import jdk.nashorn.internal.IntDeque; import jdk.nashorn.internal.codegen.ClassEmitter.Flag; import jdk.nashorn.internal.codegen.CompilerConstants.Call; -import jdk.nashorn.internal.codegen.RuntimeCallSite.SpecializedRuntimeNode; import jdk.nashorn.internal.codegen.types.ArrayType; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.AccessNode; @@ -86,14 +100,20 @@ import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.IfNode; import jdk.nashorn.internal.ir.IndexNode; +import jdk.nashorn.internal.ir.JoinPredecessor; +import jdk.nashorn.internal.ir.JoinPredecessorExpression; +import jdk.nashorn.internal.ir.LabelNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LexicalContextNode; 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.LiteralNode.PrimitiveLiteralNode; +import jdk.nashorn.internal.ir.LocalVariableConversion; import jdk.nashorn.internal.ir.LoopNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.ObjectNode; +import jdk.nashorn.internal.ir.Optimistic; import jdk.nashorn.internal.ir.PropertyNode; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.RuntimeNode; @@ -117,20 +137,26 @@ import jdk.nashorn.internal.parser.Lexer.RegexToken; import jdk.nashorn.internal.parser.TokenType; import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.Debug; -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.OptimisticReturnFilters; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; +import jdk.nashorn.internal.runtime.RewriteException; import jdk.nashorn.internal.runtime.Scope; +import jdk.nashorn.internal.runtime.ScriptEnvironment; 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.UnwarrantedOptimismException; import jdk.nashorn.internal.runtime.arrays.ArrayData; import jdk.nashorn.internal.runtime.linker.LinkerCallSite; +import jdk.nashorn.internal.runtime.logging.DebugLogger; +import jdk.nashorn.internal.runtime.logging.Loggable; +import jdk.nashorn.internal.runtime.logging.Logger; +import jdk.nashorn.internal.runtime.options.Options; /** * This is the lowest tier of the code generator. It takes lowered ASTs emitted @@ -151,16 +177,42 @@ import jdk.nashorn.internal.runtime.linker.LinkerCallSite; * The CodeGenerator visits nodes only once, tags them as resolved and emits * bytecode for them. */ -final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContext> { +@Logger(name="codegen") +final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContext> implements Loggable { + + private static final Type SCOPE_TYPE = Type.typeFor(ScriptObject.class); private static final String GLOBAL_OBJECT = Type.getInternalName(Global.class); - private static final String SCRIPTFUNCTION_IMPL_OBJECT = Type.getInternalName(ScriptFunctionImpl.class); + private static final String SCRIPTFUNCTION_IMPL_NAME = Type.getInternalName(ScriptFunctionImpl.class); + private static final Type SCRIPTFUNCTION_IMPL_TYPE = Type.typeFor(ScriptFunction.class); + + private static final Call CREATE_REWRITE_EXCEPTION = CompilerConstants.staticCallNoLookup(RewriteException.class, + "create", RewriteException.class, UnwarrantedOptimismException.class, Object[].class, String[].class); + private static final Call CREATE_REWRITE_EXCEPTION_REST_OF = CompilerConstants.staticCallNoLookup(RewriteException.class, + "create", RewriteException.class, UnwarrantedOptimismException.class, Object[].class, String[].class, int[].class); + + private static final Call ENSURE_INT = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class, + "ensureInt", int.class, Object.class, int.class); + private static final Call ENSURE_LONG = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class, + "ensureLong", long.class, Object.class, int.class); + private static final Call ENSURE_NUMBER = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class, + "ensureNumber", double.class, Object.class, int.class); + + private static final Class<?> ITERATOR_CLASS = Iterator.class; + static { + assert ITERATOR_CLASS == CompilerConstants.ITERATOR_PREFIX.type(); + } + private static final Type ITERATOR_TYPE = Type.typeFor(ITERATOR_CLASS); + private static final Type EXCEPTION_TYPE = Type.typeFor(CompilerConstants.EXCEPTION_PREFIX.type()); /** Constant data & installation. The only reason the compiler keeps this is because it is assigned * by reflection in class installation */ private final Compiler compiler; + /** Is the current code submitted by 'eval' call? */ + private final boolean evalCode; + /** Call site flags given to the code generator to be used for all generated call sites */ private final int callSiteFlags; @@ -180,22 +232,53 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex /** Current compile unit */ private CompileUnit unit; - private static final DebugLogger LOG = new DebugLogger("codegen", "nashorn.codegen.debug"); + private final DebugLogger log; /** From what size should we use spill instead of fields for JavaScript objects? */ - private static final int OBJECT_SPILL_THRESHOLD = 300; + private static final int OBJECT_SPILL_THRESHOLD = Options.getIntProperty("nashorn.spill.threshold", 256); + + private static boolean assertsEnabled = false; + static { + assert assertsEnabled = true; // Intentional side effect + } private final Set<String> emittedMethods = new HashSet<>(); + // Function Id -> ContinuationInfo. Used by compilation of rest-of function only. + private final Map<Integer, ContinuationInfo> fnIdToContinuationInfo = new HashMap<>(); + + private final Deque<Label> scopeEntryLabels = new ArrayDeque<>(); + + private static final Label METHOD_BOUNDARY = new Label(""); + private final Deque<Label> catchLabels = new ArrayDeque<>(); + // Number of live locals on entry to (and thus also break from) labeled blocks. + private final IntDeque labeledBlockBreakLiveLocals = new IntDeque(); + + //is this a rest of compilation + private final int[] continuationEntryPoints; + /** * Constructor. * * @param compiler */ - CodeGenerator(final Compiler compiler) { + CodeGenerator(final Compiler compiler, final int[] continuationEntryPoints) { super(new CodeGeneratorLexicalContext()); - this.compiler = compiler; - this.callSiteFlags = compiler.getEnv()._callsite_flags; + this.compiler = compiler; + this.evalCode = compiler.getSource().isEvalCode(); + this.continuationEntryPoints = continuationEntryPoints; + this.callSiteFlags = compiler.getScriptEnvironment()._callsite_flags; + this.log = initLogger(compiler.getContext()); + } + + @Override + public DebugLogger getLogger() { + return log; + } + + @Override + public DebugLogger initLogger(final Context context) { + return context.getLogger(this.getClass()); } /** @@ -205,7 +288,15 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex * @return the correct flags for a call site in the current function */ int getCallSiteFlags() { - return lc.getCurrentFunction().isStrict() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags; + return lc.getCurrentFunction().getCallSiteFlags() | callSiteFlags; + } + + /** + * Are we generating code for 'eval' code? + * @return true if currently compiled code is 'eval' code. + */ + boolean isEvalCode() { + return evalCode; } /** @@ -214,38 +305,69 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex * @param identNode an identity node to load * @return the method generator used */ - private MethodEmitter loadIdent(final IdentNode identNode, final Type type) { + private MethodEmitter loadIdent(final IdentNode identNode, final TypeBounds resultBounds) { final Symbol symbol = identNode.getSymbol(); if (!symbol.isScope()) { + final Type type = identNode.getType(); + if(type == Type.UNDEFINED) { + return method.loadUndefined(resultBounds.widest); + } + assert symbol.hasSlot() || symbol.isParam(); - return method.load(symbol).convert(type); + return method.load(identNode); } - final String name = symbol.getName(); - final Source source = lc.getCurrentFunction().getSource(); - - if (CompilerConstants.__FILE__.name().equals(name)) { - return method.load(source.getName()); - } else if (CompilerConstants.__DIR__.name().equals(name)) { - return method.load(source.getBase()); - } else if (CompilerConstants.__LINE__.name().equals(name)) { - return method.load(source.getLine(identNode.position())).convert(Type.OBJECT); + assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; + final int flags = CALLSITE_SCOPE | getCallSiteFlags(); + if (isFastScope(symbol)) { + // 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 && !isOptimisticOrRestOf()) { + method.loadCompilerConstant(SCOPE); + // As shared scope vars are only used in non-optimistic compilation, we switch from using TypeBounds to + // just a single definitive type, resultBounds.widest. + loadSharedScopeVar(resultBounds.widest, symbol, flags); + } else { + new LoadFastScopeVar(identNode, resultBounds, flags).emit(); + } } else { - assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; + //slow scope load, we have no proto depth + new LoadScopeVar(identNode, resultBounds, flags).emit(); + } - final int flags = CALLSITE_SCOPE | getCallSiteFlags(); - method.loadCompilerConstant(SCOPE); + return method; + } - if (isFastScope(symbol)) { - // 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(type, symbol, flags); + private boolean isRestOf() { + return continuationEntryPoints != null; + } + + private boolean isOptimisticOrRestOf() { + return useOptimisticTypes() || isRestOf(); + } + + private boolean isCurrentContinuationEntryPoint(final int programPoint) { + return isRestOf() && getCurrentContinuationEntryPoint() == programPoint; + } + + private int[] getContinuationEntryPoints() { + return isRestOf() ? continuationEntryPoints : null; + } + + private int getCurrentContinuationEntryPoint() { + return isRestOf() ? continuationEntryPoints[0] : INVALID_PROGRAM_POINT; + } + + private boolean isContinuationEntryPoint(final int programPoint) { + if (isRestOf()) { + assert continuationEntryPoints != null; + for (final int cep : continuationEntryPoints) { + if (cep == programPoint) { + return true; } - return loadFastScopeVar(type, symbol, flags, identNode.isFunction()); } - return method.dynamicGet(type, identNode.getName(), flags, identNode.isFunction()); } + return false; } /** @@ -285,7 +407,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex } previousWasBlock = true; } else { - if ((node instanceof WithNode && previousWasBlock) || (node instanceof FunctionNode && CodeGeneratorLexicalContext.isFunctionDynamicScope((FunctionNode)node))) { + if (node instanceof WithNode && previousWasBlock || node instanceof FunctionNode && ((FunctionNode)node).needsDynamicScope()) { // If we hit a scope that can have symbols introduced into it at run time before finding the defining // block, the symbol can't be fast scoped. A WithNode only counts if we've immediately seen a block // before - its block. Otherwise, we are currently processing the WithNode's expression, and that's @@ -300,14 +422,57 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex } private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) { - method.load(isFastScope(symbol) ? getScopeProtoDepth(lc.getCurrentBlock(), symbol) : -1); - final SharedScopeCall scopeCall = lc.getScopeGet(unit, valueType, symbol, flags | CALLSITE_FAST_SCOPE); - return scopeCall.generateInvoke(method); + assert !isOptimisticOrRestOf(); + if (isFastScope(symbol)) { + method.load(getScopeProtoDepth(lc.getCurrentBlock(), symbol)); + } else { + method.load(-1); + } + return lc.getScopeGet(unit, symbol, valueType, flags | CALLSITE_FAST_SCOPE).generateInvoke(method); + } + + private class LoadScopeVar extends OptimisticOperation { + final IdentNode identNode; + private final int flags; + + LoadScopeVar(final IdentNode identNode, final TypeBounds resultBounds, final int flags) { + super(identNode, resultBounds); + this.identNode = identNode; + this.flags = flags; + } + + @Override + void loadStack() { + method.loadCompilerConstant(SCOPE); + getProto(); + } + + void getProto() { + //empty + } + + @Override + void consumeStack() { + // If this is either __FILE__, __DIR__, or __LINE__ then load the property initially as Object as we'd convert + // it anyway for replaceLocationPropertyPlaceholder. + if(identNode.isCompileTimePropertyName()) { + method.dynamicGet(Type.OBJECT, identNode.getSymbol().getName(), flags, identNode.isFunction()); + replaceCompileTimeProperty(); + } else { + dynamicGet(identNode.getSymbol().getName(), flags, identNode.isFunction()); + } + } } - private MethodEmitter loadFastScopeVar(final Type valueType, final Symbol symbol, final int flags, final boolean isMethod) { - loadFastScopeProto(symbol, false); - return method.dynamicGet(valueType, symbol.getName(), flags | CALLSITE_FAST_SCOPE, isMethod); + private class LoadFastScopeVar extends LoadScopeVar { + LoadFastScopeVar(final IdentNode identNode, final TypeBounds resultBounds, final int flags) { + super(identNode, resultBounds, flags | CALLSITE_FAST_SCOPE); + } + + @Override + void getProto() { + loadFastScopeProto(identNode.getSymbol(), false); + } } private MethodEmitter storeFastScopeVar(final Symbol symbol, final int flags) { @@ -317,23 +482,30 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex } private int getScopeProtoDepth(final Block startingBlock, final Symbol symbol) { + //walk up the chain from starting block and when we bump into the current function boundary, add the external + //information. + final FunctionNode fn = lc.getCurrentFunction(); + final int fnId = fn.getId(); + final int externalDepth = compiler.getScriptFunctionData(fnId).getExternalSymbolDepth(symbol.getName()); + + //count the number of scopes from this place to the start of the function + + final int internalDepth = FindScopeDepths.findInternalDepth(lc, fn, startingBlock, symbol); + final int scopesToStart = FindScopeDepths.findScopesToStart(lc, fn, startingBlock); int depth = 0; - final String name = symbol.getName(); - for(final Iterator<Block> blocks = lc.getBlocks(startingBlock); blocks.hasNext();) { - final Block currentBlock = blocks.next(); - if (currentBlock.getExistingSymbol(name) == symbol) { - return depth; - } - if (currentBlock.needsScope()) { - ++depth; - } + if (internalDepth == -1) { + depth = scopesToStart + externalDepth; + } else { + assert internalDepth <= scopesToStart; + depth = internalDepth; } - return -1; + + return depth; } private void loadFastScopeProto(final Symbol symbol, final boolean swap) { final int depth = getScopeProtoDepth(lc.getCurrentBlock(), symbol); - assert depth != -1; + assert depth != -1 : "Couldn't find scope depth for symbol " + symbol.getName() + " in " + lc.getCurrentFunction(); if (depth > 0) { if (swap) { method.swap(); @@ -348,29 +520,36 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex } /** - * 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 + * Generate code that loads this node to the stack, not constraining its type * - * @param node node to load + * @param expr node to load * * @return the method emitter used */ - MethodEmitter load(final Expression node) { - return load(node, node.hasType() ? node.getType() : null, false); + private MethodEmitter loadExpressionUnbounded(final Expression expr) { + return loadExpression(expr, TypeBounds.UNBOUNDED); + } + + private MethodEmitter loadExpressionAsObject(final Expression expr) { + return loadExpression(expr, TypeBounds.OBJECT); + } + + MethodEmitter loadExpressionAsBoolean(final Expression expr) { + return loadExpression(expr, TypeBounds.BOOLEAN); } // Test whether conversion from source to target involves a call of ES 9.1 ToPrimitive // with possible side effects from calling an object's toString or valueOf methods. - private boolean noToPrimitiveConversion(final Type source, final Type target) { + private static boolean noToPrimitiveConversion(final Type source, final Type target) { // Object to boolean conversion does not cause ToPrimitive call return source.isJSPrimitive() || !target.isJSPrimitive() || target.isBoolean(); } - MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final Type type) { - return loadBinaryOperands(lhs, rhs, type, false); + MethodEmitter loadBinaryOperands(final BinaryNode binaryNode) { + return loadBinaryOperands(binaryNode.lhs(), binaryNode.rhs(), TypeBounds.UNBOUNDED.notWiderThan(binaryNode.getWidestOperandType()), false); } - private MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final Type type, final boolean baseAlreadyOnStack) { + private MethodEmitter loadBinaryOperands(final Expression lhs, final Expression rhs, final TypeBounds explicitOperandBounds, final boolean baseAlreadyOnStack) { // ECMAScript 5.1 specification (sections 11.5-11.11 and 11.13) prescribes that when evaluating a binary // expression "LEFT op RIGHT", the order of operations must be: LOAD LEFT, LOAD RIGHT, CONVERT LEFT, CONVERT // RIGHT, EXECUTE OP. Unfortunately, doing it in this order defeats potential optimizations that arise when we @@ -381,38 +560,130 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex // a primitive value, or RIGHT is an expression that loads without side effects, then we can do the // reordering and collapse LOAD/CONVERT into a single operation; otherwise we need to do the more costly // separate operations to preserve specification semantics. - if (noToPrimitiveConversion(lhs.getType(), type) || rhs.isLocal()) { + + // Operands' load type should not be narrower than the narrowest of the individual operand types, nor narrower + // than the lower explicit bound, but it should also not be wider than + final Type narrowestOperandType = Type.narrowest(Type.widest(lhs.getType(), rhs.getType()), explicitOperandBounds.widest); + final TypeBounds operandBounds = explicitOperandBounds.notNarrowerThan(narrowestOperandType); + if (noToPrimitiveConversion(lhs.getType(), explicitOperandBounds.widest) || rhs.isLocal()) { // Can reorder. Combine load and convert into single operations. - load(lhs, type, baseAlreadyOnStack); - load(rhs, type, false); + loadExpression(lhs, operandBounds, baseAlreadyOnStack); + loadExpression(rhs, operandBounds, false); } else { // Can't reorder. Load and convert separately. - load(lhs, lhs.getType(), baseAlreadyOnStack); - load(rhs, rhs.getType(), false); - method.swap().convert(type).swap().convert(type); + final TypeBounds safeConvertBounds = TypeBounds.UNBOUNDED.notNarrowerThan(narrowestOperandType); + loadExpression(lhs, safeConvertBounds, baseAlreadyOnStack); + loadExpression(rhs, safeConvertBounds, false); + method.swap().convert(operandBounds.within(method.peekType())).swap().convert(operandBounds.within(method.peekType())); } + assert Type.generic(method.peekType()) == operandBounds.narrowest; + assert Type.generic(method.peekType(1)) == operandBounds.narrowest; return method; } - MethodEmitter loadBinaryOperands(final BinaryNode node) { - return loadBinaryOperands(node.lhs(), node.rhs(), node.getType(), false); - } + private static final class TypeBounds { + final Type narrowest; + final Type widest; - MethodEmitter load(final Expression node, final Type type) { - return load(node, type, false); - } + static final TypeBounds UNBOUNDED = new TypeBounds(Type.UNKNOWN, Type.OBJECT); + static final TypeBounds INT = exact(Type.INT); + static final TypeBounds NUMBER = exact(Type.NUMBER); + static final TypeBounds OBJECT = exact(Type.OBJECT); + static final TypeBounds BOOLEAN = exact(Type.BOOLEAN); - private MethodEmitter load(final Expression node, final Type type, final boolean baseAlreadyOnStack) { - final Symbol symbol = node.getSymbol(); + static TypeBounds exact(final Type type) { + return new TypeBounds(type, type); + } - // If we lack symbols, we just generate what we see. - if (symbol == null || type == null) { - node.accept(this); - return method; + TypeBounds(final Type narrowest, final Type widest) { + assert widest != null && widest != Type.UNDEFINED && widest != Type.UNKNOWN : widest; + assert narrowest != null && narrowest != Type.UNDEFINED : narrowest; + assert !narrowest.widerThan(widest) : narrowest + " wider than " + widest; + assert !widest.narrowerThan(narrowest); + this.narrowest = Type.generic(narrowest); + this.widest = Type.generic(widest); + } + + TypeBounds notNarrowerThan(final Type type) { + return maybeNew(Type.narrowest(Type.widest(narrowest, type), widest), widest); + } + + TypeBounds notWiderThan(final Type type) { + return maybeNew(Type.narrowest(narrowest, type), Type.narrowest(widest, type)); + } + + boolean canBeNarrowerThan(final Type type) { + return narrowest.narrowerThan(type); + } + + TypeBounds maybeNew(final Type newNarrowest, final Type newWidest) { + if(newNarrowest == narrowest && newWidest == widest) { + return this; + } + return new TypeBounds(newNarrowest, newWidest); + } + + TypeBounds booleanToInt() { + return maybeNew(booleanToInt(narrowest), booleanToInt(widest)); + } + + TypeBounds objectToNumber() { + return maybeNew(objectToNumber(narrowest), objectToNumber(widest)); + } + + private static Type booleanToInt(final Type t) { + return t == Type.BOOLEAN ? Type.INT : t; + } + + private static Type objectToNumber(final Type t) { + return t.isObject() ? Type.NUMBER : t; + } + + Type within(final Type type) { + if(type.narrowerThan(narrowest)) { + return narrowest; + } + if(type.widerThan(widest)) { + return widest; + } + return type; + } + + @Override + public String toString() { + return "[" + narrowest + ", " + widest + "]"; + } + } + + MethodEmitter loadExpressionAsType(final Expression expr, final Type type) { + if(type == Type.BOOLEAN) { + return loadExpressionAsBoolean(expr); + } else if(type == Type.UNDEFINED) { + assert expr.getType() == Type.UNDEFINED; + return loadExpressionAsObject(expr); } + // having no upper bound preserves semantics of optimistic operations in the expression (by not having them + // converted early) and then applies explicit conversion afterwards. + return loadExpression(expr, TypeBounds.UNBOUNDED.notNarrowerThan(type)).convert(type); + } + + private MethodEmitter loadExpression(final Expression expr, final TypeBounds resultBounds) { + return loadExpression(expr, resultBounds, false); + } - assert !type.isUnknown(); + /** + * Emits code for evaluating an expression and leaving its value on top of the stack, narrowing or widening it if + * necessary. + * @param expr the expression to load + * @param resultBounds the incoming type bounds. The value on the top of the stack is guaranteed to not be of narrower + * type than the narrowest bound, or wider type than the widest bound after it is loaded. + * @param baseAlreadyOnStack true if the base of an access or index node is already on the stack. Used to avoid + * double evaluation of bases in self-assignment expressions to access and index nodes. {@code Type.OBJECT} is used + * to indicate the widest possible type. + * @return the method emitter + */ + private MethodEmitter loadExpression(final Expression expr, final TypeBounds resultBounds, final boolean baseAlreadyOnStack) { /* * The load may be of type IdentNode, e.g. "x", AccessNode, e.g. "x.y" @@ -421,30 +692,49 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex */ final CodeGenerator codegen = this; - node.accept(new NodeVisitor<LexicalContext>(lc) { + final Node currentDiscard = codegen.lc.getCurrentDiscard(); + expr.accept(new NodeOperatorVisitor<LexicalContext>(new LexicalContext()) { @Override public boolean enterIdentNode(final IdentNode identNode) { - loadIdent(identNode, type); + loadIdent(identNode, resultBounds); return false; } @Override public boolean enterAccessNode(final AccessNode accessNode) { - if (!baseAlreadyOnStack) { - load(accessNode.getBase(), Type.OBJECT); - } - assert method.peekType().isObject(); - method.dynamicGet(type, accessNode.getProperty().getName(), getCallSiteFlags(), accessNode.isFunction()); + new OptimisticOperation(accessNode, resultBounds) { + @Override + void loadStack() { + if (!baseAlreadyOnStack) { + loadExpressionAsObject(accessNode.getBase()); + } + assert method.peekType().isObject(); + } + @Override + void consumeStack() { + final int flags = getCallSiteFlags(); + dynamicGet(accessNode.getProperty(), flags, accessNode.isFunction()); + } + }.emit(baseAlreadyOnStack ? 1 : 0); return false; } @Override public boolean enterIndexNode(final IndexNode indexNode) { - if (!baseAlreadyOnStack) { - load(indexNode.getBase(), Type.OBJECT); - load(indexNode.getIndex()); - } - method.dynamicGetIndex(type, getCallSiteFlags(), indexNode.isFunction()); + new OptimisticOperation(indexNode, resultBounds) { + @Override + void loadStack() { + if (!baseAlreadyOnStack) { + loadExpressionAsObject(indexNode.getBase()); + loadExpressionUnbounded(indexNode.getIndex()); + } + } + @Override + void consumeStack() { + final int flags = getCallSiteFlags(); + dynamicGetIndex(flags, indexNode.isFunction()); + } + }.emit(baseAlreadyOnStack ? 2 : 0); return false; } @@ -459,204 +749,485 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex // is the last element in the compilation pipeline, the AST it produces is not used externally. So, we // re-push the original functionNode. lc.push(functionNode); - method.convert(type); + return false; + } + + @Override + public boolean enterASSIGN(final BinaryNode binaryNode) { + loadASSIGN(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_ADD(final BinaryNode binaryNode) { + loadASSIGN_ADD(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) { + loadASSIGN_BIT_AND(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) { + loadASSIGN_BIT_OR(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) { + loadASSIGN_BIT_XOR(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_DIV(final BinaryNode binaryNode) { + loadASSIGN_DIV(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_MOD(final BinaryNode binaryNode) { + loadASSIGN_MOD(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_MUL(final BinaryNode binaryNode) { + loadASSIGN_MUL(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_SAR(final BinaryNode binaryNode) { + loadASSIGN_SAR(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_SHL(final BinaryNode binaryNode) { + loadASSIGN_SHL(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_SHR(final BinaryNode binaryNode) { + loadASSIGN_SHR(binaryNode); + return false; + } + + @Override + public boolean enterASSIGN_SUB(final BinaryNode binaryNode) { + loadASSIGN_SUB(binaryNode); return false; } @Override public boolean enterCallNode(final CallNode callNode) { - return codegen.enterCallNode(callNode, type); + return loadCallNode(callNode, resultBounds); } @Override public boolean enterLiteralNode(final LiteralNode<?> literalNode) { - return codegen.enterLiteralNode(literalNode, type); + loadLiteral(literalNode, resultBounds); + return false; } @Override - public boolean enterDefault(final Node otherNode) { - final Node currentDiscard = codegen.lc.getCurrentDiscard(); - otherNode.accept(codegen); // generate code for whatever we are looking at. - if(currentDiscard != otherNode) { - method.load(symbol); // load the final symbol to the stack (or nop if no slot, then result is already there) - assert method.peekType() != null; - method.convert(type); - } + public boolean enterTernaryNode(final TernaryNode ternaryNode) { + loadTernaryNode(ternaryNode, resultBounds); return false; } - }); - return method; - } + @Override + public boolean enterADD(final BinaryNode binaryNode) { + loadADD(binaryNode, resultBounds); + return false; + } - @Override - public boolean enterAccessNode(final AccessNode accessNode) { - load(accessNode); - return false; - } + @Override + public boolean enterSUB(final UnaryNode unaryNode) { + loadSUB(unaryNode, resultBounds); + return false; + } - /** - * 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<>(); + @Override + public boolean enterSUB(final BinaryNode binaryNode) { + loadSUB(binaryNode, resultBounds); + return false; + } - for (final Symbol symbol : symbols) { - /* - * The following symbols are guaranteed to be defined and thus safe - * from having undefined 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 " + lc.getCurrentFunction(); - if (symbol.getSymbolType().isNumber()) { - numbers.add(symbol); - } else if (symbol.getSymbolType().isObject()) { - objects.add(symbol); - } + @Override + public boolean enterMUL(final BinaryNode binaryNode) { + loadMUL(binaryNode, resultBounds); + return false; } - } - initSymbols(numbers, Type.NUMBER); - initSymbols(objects, Type.OBJECT); - } + @Override + public boolean enterDIV(final BinaryNode binaryNode) { + loadDIV(binaryNode, resultBounds); + return false; + } - private void initSymbols(final LinkedList<Symbol> symbols, final Type type) { - final Iterator<Symbol> it = symbols.iterator(); - if(it.hasNext()) { - method.loadUndefined(type); - boolean hasNext; - do { - final Symbol symbol = it.next(); - hasNext = it.hasNext(); - if(hasNext) { - method.dup(); - } - method.store(symbol); - } while(hasNext); + @Override + public boolean enterMOD(final BinaryNode binaryNode) { + loadMOD(binaryNode, resultBounds); + return false; + } + + @Override + public boolean enterSAR(final BinaryNode binaryNode) { + loadSAR(binaryNode); + return false; + } + + @Override + public boolean enterSHL(final BinaryNode binaryNode) { + loadSHL(binaryNode); + return false; + } + + @Override + public boolean enterSHR(final BinaryNode binaryNode) { + loadSHR(binaryNode); + return false; + } + + @Override + public boolean enterCOMMALEFT(final BinaryNode binaryNode) { + loadCOMMALEFT(binaryNode, resultBounds); + return false; + } + + @Override + public boolean enterCOMMARIGHT(final BinaryNode binaryNode) { + loadCOMMARIGHT(binaryNode, resultBounds); + return false; + } + + @Override + public boolean enterAND(final BinaryNode binaryNode) { + loadAND_OR(binaryNode, resultBounds, true); + return false; + } + + @Override + public boolean enterOR(final BinaryNode binaryNode) { + loadAND_OR(binaryNode, resultBounds, false); + return false; + } + + @Override + public boolean enterNOT(final UnaryNode unaryNode) { + loadNOT(unaryNode); + return false; + } + + @Override + public boolean enterADD(final UnaryNode unaryNode) { + loadADD(unaryNode, resultBounds); + return false; + } + + @Override + public boolean enterBIT_NOT(final UnaryNode unaryNode) { + loadBIT_NOT(unaryNode); + return false; + } + + @Override + public boolean enterBIT_AND(final BinaryNode binaryNode) { + loadBIT_AND(binaryNode); + return false; + } + + @Override + public boolean enterBIT_OR(final BinaryNode binaryNode) { + loadBIT_OR(binaryNode); + return false; + } + + @Override + public boolean enterBIT_XOR(final BinaryNode binaryNode) { + loadBIT_XOR(binaryNode); + return false; + } + + @Override + public boolean enterVOID(final UnaryNode unaryNode) { + loadVOID(unaryNode, resultBounds); + return false; + } + + @Override + public boolean enterEQ(final BinaryNode binaryNode) { + loadCmp(binaryNode, Condition.EQ); + return false; + } + + @Override + public boolean enterEQ_STRICT(final BinaryNode binaryNode) { + loadCmp(binaryNode, Condition.EQ); + return false; + } + + @Override + public boolean enterGE(final BinaryNode binaryNode) { + loadCmp(binaryNode, Condition.GE); + return false; + } + + @Override + public boolean enterGT(final BinaryNode binaryNode) { + loadCmp(binaryNode, Condition.GT); + return false; + } + + @Override + public boolean enterLE(final BinaryNode binaryNode) { + loadCmp(binaryNode, Condition.LE); + return false; + } + + @Override + public boolean enterLT(final BinaryNode binaryNode) { + loadCmp(binaryNode, Condition.LT); + return false; + } + + @Override + public boolean enterNE(final BinaryNode binaryNode) { + loadCmp(binaryNode, Condition.NE); + return false; + } + + @Override + public boolean enterNE_STRICT(final BinaryNode binaryNode) { + loadCmp(binaryNode, Condition.NE); + return false; + } + + @Override + public boolean enterObjectNode(final ObjectNode objectNode) { + loadObjectNode(objectNode); + return false; + } + + @Override + public boolean enterRuntimeNode(final RuntimeNode runtimeNode) { + loadRuntimeNode(runtimeNode); + return false; + } + + @Override + public boolean enterNEW(final UnaryNode unaryNode) { + loadNEW(unaryNode); + return false; + } + + @Override + public boolean enterDECINC(final UnaryNode unaryNode) { + loadDECINC(unaryNode); + return false; + } + + @Override + public boolean enterJoinPredecessorExpression(final JoinPredecessorExpression joinExpr) { + loadExpression(joinExpr.getExpression(), resultBounds); + return false; + } + + @Override + public boolean enterDefault(final Node otherNode) { + // Must have handled all expressions that can legally be encountered. + throw new AssertionError(otherNode.getClass().getName()); + } + }); + if(currentDiscard != expr) { + coerceStackTop(resultBounds); } + return method; + } + + private MethodEmitter coerceStackTop(final TypeBounds typeBounds) { + return method.convert(typeBounds.within(method.peekType())); } /** - * Create symbol debug information. + * Closes any still open entries for this block's local variables in the bytecode local variable table. * * @param block block containing symbols. */ - private void symbolInfo(final Block block) { + private void closeBlockVariables(final Block block) { for (final Symbol symbol : block.getSymbols()) { - if (symbol.hasSlot()) { - method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel()); + if (symbol.isBytecodeLocal()) { + method.closeLocalVariable(symbol, block.getBreakLabel()); } } } @Override public boolean enterBlock(final Block block) { + method.label(block.getEntryLabel()); + if(!method.isReachable()) { + return false; + } if(lc.isFunctionBody() && emittedMethods.contains(lc.getCurrentFunction().getName())) { return false; } - method.label(block.getEntryLabel()); initLocals(block); + assert lc.getUsedSlotCount() == method.getFirstTemp(); return true; } + private boolean useOptimisticTypes() { + return !lc.inSplitNode() && compiler.useOptimisticTypes(); + } + @Override public Node leaveBlock(final Block block) { - method.label(block.getBreakLabel()); - symbolInfo(block); + popBlockScope(block); + method.beforeJoinPoint(block); + + closeBlockVariables(block); + lc.releaseSlots(); + assert !method.isReachable() || (lc.isFunctionBody() ? 0 : lc.getUsedSlotCount()) == method.getFirstTemp() : + "reachable="+method.isReachable() + + " isFunctionBody=" + lc.isFunctionBody() + + " usedSlotCount=" + lc.getUsedSlotCount() + + " firstTemp=" + method.getFirstTemp(); - if (block.needsScope() && !block.isTerminal()) { - 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"); + final Label breakLabel = block.getBreakLabel(); - /* pop scope a la try-finally */ - method.loadCompilerConstant(SCOPE); - method.invoke(ScriptObject.GET_PROTO); - method.storeCompilerConstant(SCOPE); - method._goto(skipLabel); - method.label(exitLabel); + if(!block.needsScope() || lc.isFunctionBody()) { + emitBlockBreakLabel(breakLabel); + return; + } + + final Label beginTryLabel = scopeEntryLabels.pop(); + final Label recoveryLabel = new Label("block_popscope_catch"); + emitBlockBreakLabel(breakLabel); + final boolean bodyCanThrow = breakLabel.isAfter(beginTryLabel); + if(bodyCanThrow) { + method._try(beginTryLabel, breakLabel, recoveryLabel); + } + + Label afterCatchLabel = null; + + if(method.isReachable()) { + popScope(); + if(bodyCanThrow) { + afterCatchLabel = new Label("block_after_catch"); + method._goto(afterCatchLabel); + } + } + + if(bodyCanThrow) { + assert !method.isReachable(); + method._catch(recoveryLabel); + popScopeException(); + method.athrow(); + } + if(afterCatchLabel != null) { + method.label(afterCatchLabel); + } + } + + private void emitBlockBreakLabel(final Label breakLabel) { + // TODO: this is totally backwards. Block should not be breakable, LabelNode should be breakable. + final LabelNode labelNode = lc.getCurrentBlockLabelNode(); + if(labelNode != null) { + // Only have conversions if we're reachable + assert labelNode.getLocalVariableConversion() == null || method.isReachable(); + method.beforeJoinPoint(labelNode); + method.breakLabel(breakLabel, labeledBlockBreakLiveLocals.pop()); + } else { + method.label(breakLabel); + } + } + + private void popScope() { + popScopes(1); + } + + /** + * Pop scope as part of an exception handler. Similar to {@code popScope()} but also takes care of adjusting the + * number of scopes that needs to be popped in case a rest-of continuation handler encounters an exception while + * performing a ToPrimitive conversion. + */ + private void popScopeException() { + popScope(); + final ContinuationInfo ci = getContinuationInfo(); + if(ci != null) { + final Label catchLabel = ci.catchLabel; + if(catchLabel != METHOD_BOUNDARY && catchLabel == catchLabels.peek()) { + ++ci.exceptionScopePops; + } + } + } + + private void popScopesUntil(final LexicalContextNode until) { + popScopes(lc.getScopeNestingLevelTo(until)); + } - method._catch(recoveryLabel); + private void popScopes(final int count) { + if(count == 0) { + return; + } + assert count > 0; // together with count == 0 check, asserts nonnegative count + if (!method.hasScope()) { + // We can sometimes invoke this method even if the method has no slot for the scope object. Typical example: + // for(;;) { with({}) { break; } }. WithNode normally creates a scope, but if it uses no identifiers and + // nothing else forces creation of a scope in the method, we just won't have the :scope local variable. + return; + } method.loadCompilerConstant(SCOPE); - method.invoke(ScriptObject.GET_PROTO); + for(int i = 0; i < count; ++i) { + method.invoke(ScriptObject.GET_PROTO); + } method.storeCompilerConstant(SCOPE); - method.athrow(); - method.label(skipLabel); - method._try(block.getEntryLabel(), exitLabel, recoveryLabel, Throwable.class); } @Override public boolean enterBreakNode(final BreakNode breakNode) { - lineNumber(breakNode); - - final BreakableNode breakFrom = lc.getBreakable(breakNode.getLabel()); - for (int i = 0; i < lc.getScopeNestingLevelTo(breakFrom); i++) { - closeWith(); + if(!method.isReachable()) { + return false; } - method.splitAwareGoto(lc, breakFrom.getBreakLabel()); + enterStatement(breakNode); + + method.beforeJoinPoint(breakNode); + final BreakableNode breakFrom = lc.getBreakable(breakNode.getLabelName()); + popScopesUntil(breakFrom); + final Label breakLabel = breakFrom.getBreakLabel(); + breakLabel.markAsBreakTarget(); + method.splitAwareGoto(lc, breakLabel, breakFrom); return false; } private int loadArgs(final List<Expression> args) { - return loadArgs(args, null, false, args.size()); - } - - private int loadArgs(final List<Expression> args, final String signature, final boolean isVarArg, final int argCount) { + final int argCount = args.size(); // arg have already been converted to objects here. - if (isVarArg || argCount > LinkerCallSite.ARGLIMIT) { + if (argCount > LinkerCallSite.ARGLIMIT) { loadArgsArray(args); return 1; } - // 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 Expression arg : args) { assert arg != null; - if (n >= argCount) { - load(arg); - method.pop(); // we had to load the arg for its side effects - } else if (params != null) { - load(arg, params[n]); - } else { - load(arg); - } - n++; + loadExpressionUnbounded(arg); } - - while (n < argCount) { - method.loadUndefined(Type.OBJECT); - n++; - } - return argCount; } - - @Override - public boolean enterCallNode(final CallNode callNode) { - return enterCallNode(callNode, callNode.getType()); - } - - private boolean enterCallNode(final CallNode callNode, final Type callNodeType) { + private boolean loadCallNode(final CallNode callNode, final TypeBounds resultBounds) { lineNumber(callNode.getLineNumber()); final List<Expression> args = callNode.getArgs(); @@ -668,68 +1239,122 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex private MethodEmitter sharedScopeCall(final IdentNode identNode, final int flags) { final Symbol symbol = identNode.getSymbol(); - int scopeCallFlags = flags; - method.loadCompilerConstant(SCOPE); - if (isFastScope(symbol)) { - 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 = codegenLexicalContext.getScopeCall(unit, symbol, identNode.getType(), callNodeType, paramTypes, scopeCallFlags); - return scopeCall.generateInvoke(method); + final boolean isFastScope = isFastScope(symbol); + final int scopeCallFlags = flags | (isFastScope ? CALLSITE_FAST_SCOPE : 0); + new OptimisticOperation(callNode, resultBounds) { + @Override + void loadStack() { + method.loadCompilerConstant(SCOPE); + if (isFastScope) { + method.load(getScopeProtoDepth(currentBlock, symbol)); + } else { + method.load(-1); // Bypass fast-scope code in shared callsite + } + loadArgs(args); + } + @Override + void consumeStack() { + final Type[] paramTypes = method.getTypesFromStack(args.size()); + // We have trouble finding e.g. in Type.typeFor(asm.Type) because it can't see the Context class + // loader, so we need to weaken reference signatures to Object. + for(int i = 0; i < paramTypes.length; ++i) { + paramTypes[i] = Type.generic(paramTypes[i]); + } + // As shared scope calls are only used in non-optimistic compilation, we switch from using + // TypeBounds to just a single definitive type, resultBounds.widest. + final SharedScopeCall scopeCall = codegenLexicalContext.getScopeCall(unit, symbol, + identNode.getType(), resultBounds.widest, paramTypes, scopeCallFlags); + scopeCall.generateInvoke(method); + } + }.emit(); + return method; } - private void scopeCall(final IdentNode node, final int flags) { - load(node, Type.OBJECT); // Type.OBJECT as foo() makes no sense if foo == 3 - // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. - method.loadUndefined(Type.OBJECT); //the 'this' object - method.dynamicCall(callNodeType, 2 + loadArgs(args), flags); + private void scopeCall(final IdentNode ident, final int flags) { + new OptimisticOperation(callNode, resultBounds) { + int argsCount; + @Override + void loadStack() { + loadExpressionAsObject(ident); // foo() makes no sense if foo == 3 + // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. + method.loadUndefined(Type.OBJECT); //the 'this' + argsCount = loadArgs(args); + } + @Override + void consumeStack() { + dynamicCall(2 + argsCount, flags); + } + }.emit(); } - private void evalCall(final IdentNode node, final int flags) { - load(node, Type.OBJECT); // Type.OBJECT as foo() makes no sense if foo == 3 - - final Label not_eval = new Label("not_eval"); + private void evalCall(final IdentNode ident, final int flags) { + final Label invoke_direct_eval = new Label("invoke_direct_eval"); + final Label is_not_eval = new Label("is_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.loadCompilerConstant(SCOPE); // Load up self (scope). - - final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); - // load evaluated code - load(evalArgs.getCode(), Type.OBJECT); - // load second and subsequent args for side-effect - final List<Expression> args = callNode.getArgs(); - final int numArgs = args.size(); - for (int i = 1; i < numArgs; i++) { - load(args.get(i)).pop(); - } - // special/extra 'eval' arguments - load(evalArgs.getThis()); - method.load(evalArgs.getLocation()); - method.load(evalArgs.getStrictMode()); - method.convert(Type.OBJECT); - - // direct call to Global.directEval - globalDirectEval(); - method.convert(callNodeType); - method._goto(eval_done); + new OptimisticOperation(callNode, resultBounds) { + int argsCount; + @Override + void loadStack() { + /** + * We want to load 'eval' to check if it is indeed global builtin eval. + * If this eval call is inside a 'with' statement, dyn:getMethod|getProp|getElem + * would be generated if ident is a "isFunction". But, that would result in a + * bound function from WithObject. We don't want that as bound function as that + * won't be detected as builtin eval. So, we make ident as "not a function" which + * results in "dyn:getProp|getElem|getMethod" being generated and so WithObject + * would return unbounded eval function. + * + * Example: + * + * var global = this; + * function func() { + * with({ eval: global.eval) { eval("var x = 10;") } + * } + */ + loadExpressionAsObject(ident.setIsNotFunction()); // Type.OBJECT as foo() makes no sense if foo == 3 + globalIsEval(); + method.ifeq(is_not_eval); + + // Load up self (scope). + method.loadCompilerConstant(SCOPE); + final List<Expression> evalArgs = callNode.getEvalArgs().getArgs(); + // load evaluated code + loadExpressionAsObject(evalArgs.get(0)); + // load second and subsequent args for side-effect + final int numArgs = evalArgs.size(); + for (int i = 1; i < numArgs; i++) { + loadAndDiscard(evalArgs.get(i)); + } + method._goto(invoke_direct_eval); + + method.label(is_not_eval); + // load this time but with dyn:getMethod|getProp|getElem + loadExpressionAsObject(ident); // Type.OBJECT as foo() makes no sense if foo == 3 + // This is some scope 'eval' or global eval replaced by user + // but not the built-in ECMAScript 'eval' function call + method.loadNull(); + argsCount = loadArgs(callNode.getArgs()); + } - 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(); - method.dynamicCall(callNodeType, 2 + loadArgs(args), flags); + @Override + void consumeStack() { + // Ordinary call + dynamicCall(2 + argsCount, flags); + method._goto(eval_done); + + method.label(invoke_direct_eval); + // Special/extra 'eval' arguments. These can be loaded late (in consumeStack) as we know none of + // them can ever be optimistic. + method.loadCompilerConstant(THIS); + method.load(callNode.getEvalArgs().getLocation()); + method.load(CodeGenerator.this.lc.getCurrentFunction().isStrict()); + // direct call to Global.directEval + globalDirectEval(); + convertOptimisticReturnValue(); + coerceStackTop(resultBounds); + } + }.emit(); method.label(eval_done); } @@ -748,13 +1373,14 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex if (callNode.isEval()) { evalCall(node, flags); } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD - || (!isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD) - || CodeGenerator.this.lc.inDynamicScope()) { + || !isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD + || CodeGenerator.this.lc.inDynamicScope() + || isOptimisticOrRestOf()) { scopeCall(node, flags); } else { sharedScopeCall(node, flags); } - assert method.peekType().equals(callNodeType) : method.peekType() + "!=" + callNode.getType(); + assert method.peekType().equals(resultBounds.within(callNode.getType())) : method.peekType() + " != " + resultBounds + "(" + callNode.getType() + ")"; } else { enterDefault(node); } @@ -764,104 +1390,170 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex @Override public boolean enterAccessNode(final AccessNode node) { - load(node.getBase(), Type.OBJECT); - method.dup(); - method.dynamicGet(node.getType(), node.getProperty().getName(), getCallSiteFlags(), true); - method.swap(); - method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags()); + //check if this is an apply to call node. only real applies, that haven't been + //shadowed from their way to the global scope counts + + //call nodes have program points. + + final int flags = getCallSiteFlags() | (callNode.isApplyToCall() ? CALLSITE_APPLY_TO_CALL : 0); + + new OptimisticOperation(callNode, resultBounds) { + int argCount; + @Override + void loadStack() { + loadExpressionAsObject(node.getBase()); + method.dup(); + // NOTE: not using a nested OptimisticOperation on this dynamicGet, as we expect to get back + // a callable object. Nobody in their right mind would optimistically type this call site. + assert !node.isOptimistic(); + method.dynamicGet(node.getType(), node.getProperty(), flags, true); + method.swap(); + argCount = loadArgs(args); + } + @Override + void consumeStack() { + dynamicCall(2 + argCount, flags); + } + }.emit(); return false; } @Override public boolean enterFunctionNode(final FunctionNode origCallee) { - // NOTE: visiting the callee will leave a constructed ScriptFunction object on the stack if - // callee.needsCallee() == true - final FunctionNode callee = (FunctionNode)origCallee.accept(CodeGenerator.this); - - 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(); + new OptimisticOperation(callNode, resultBounds) { + FunctionNode callee; + int argsCount; + @Override + void loadStack() { + callee = (FunctionNode)origCallee.accept(CodeGenerator.this); + if (callee.isStrict()) { // "this" is undefined + method.loadUndefined(Type.OBJECT); + } else { // get global from scope (which is the self) + globalInstance(); + } + argsCount = loadArgs(args); + } - if (callee.isStrict()) { // self is undefined - method.loadUndefined(Type.OBJECT); - } else { // get global from scope (which is the self) - globalInstance(); - } - loadArgs(args, signature, isVarArg, argCount); - assert callee.getCompileUnit() != null : "no compile unit for " + callee.getName() + " " + Debug.id(callee) + " " + callNode; - method.invokestatic(callee.getCompileUnit().getUnitClassName(), callee.getName(), signature); - assert method.peekType().equals(callee.getReturnType()) : method.peekType() + " != " + callee.getReturnType(); - method.convert(callNodeType); + @Override + void consumeStack() { + final int flags = getCallSiteFlags(); + //assert callNodeType.equals(callee.getReturnType()) : callNodeType + " != " + callee.getReturnType(); + dynamicCall(2 + argsCount, flags); + } + }.emit(); return false; } @Override public boolean enterIndexNode(final IndexNode node) { - load(node.getBase(), Type.OBJECT); - method.dup(); - final Type indexType = node.getIndex().getType(); - if (indexType.isObject() || indexType.isBoolean()) { - load(node.getIndex(), Type.OBJECT); //TODO - } else { - load(node.getIndex()); - } - method.dynamicGetIndex(node.getType(), getCallSiteFlags(), true); - method.swap(); - method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags()); - + new OptimisticOperation(callNode, resultBounds) { + int argsCount; + @Override + void loadStack() { + loadExpressionAsObject(node.getBase()); + method.dup(); + final Type indexType = node.getIndex().getType(); + if (indexType.isObject() || indexType.isBoolean()) { + loadExpressionAsObject(node.getIndex()); //TODO boolean + } else { + loadExpressionUnbounded(node.getIndex()); + } + // NOTE: not using a nested OptimisticOperation on this dynamicGetIndex, as we expect to get + // back a callable object. Nobody in their right mind would optimistically type this call site. + assert !node.isOptimistic(); + method.dynamicGetIndex(node.getType(), getCallSiteFlags(), true); + method.swap(); + argsCount = loadArgs(args); + } + @Override + void consumeStack() { + final int flags = getCallSiteFlags(); + dynamicCall(2 + argsCount, flags); + } + }.emit(); return false; } @Override protected boolean enterDefault(final Node node) { - // Load up function. - load(function, Type.OBJECT); //TODO, e.g. booleans can be used as functions - method.loadUndefined(Type.OBJECT); // ScriptFunction will figure out the correct this when it sees CALLSITE_SCOPE - method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags() | CALLSITE_SCOPE); - + new OptimisticOperation(callNode, resultBounds) { + int argsCount; + @Override + void loadStack() { + // Load up function. + loadExpressionAsObject(function); //TODO, e.g. booleans can be used as functions + method.loadUndefined(Type.OBJECT); // ScriptFunction will figure out the correct this when it sees CALLSITE_SCOPE + argsCount = loadArgs(args); + } + @Override + void consumeStack() { + final int flags = getCallSiteFlags() | CALLSITE_SCOPE; + dynamicCall(2 + argsCount, flags); + } + }.emit(); return false; } }); - method.store(callNode.getSymbol()); - return false; } + /** + * Returns the flags with optimistic flag and program point removed. + * @param flags the flags that need optimism stripped from them. + * @return flags without optimism + */ + static int nonOptimisticFlags(final int flags) { + return flags & ~(CALLSITE_OPTIMISTIC | -1 << CALLSITE_PROGRAM_POINT_SHIFT); + } + @Override public boolean enterContinueNode(final ContinueNode continueNode) { - lineNumber(continueNode); - - final LoopNode continueTo = lc.getContinueTo(continueNode.getLabel()); - for (int i = 0; i < lc.getScopeNestingLevelTo(continueTo); i++) { - closeWith(); + if(!method.isReachable()) { + return false; } - method.splitAwareGoto(lc, continueTo.getContinueLabel()); + enterStatement(continueNode); + method.beforeJoinPoint(continueNode); + + final LoopNode continueTo = lc.getContinueTo(continueNode.getLabelName()); + popScopesUntil(continueTo); + final Label continueLabel = continueTo.getContinueLabel(); + continueLabel.markAsBreakTarget(); + method.splitAwareGoto(lc, continueLabel, continueTo); return false; } @Override public boolean enterEmptyNode(final EmptyNode emptyNode) { - lineNumber(emptyNode); + if(!method.isReachable()) { + return false; + } + enterStatement(emptyNode); return false; } @Override public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { - lineNumber(expressionStatement); + if(!method.isReachable()) { + return false; + } + enterStatement(expressionStatement); - expressionStatement.getExpression().accept(this); + loadAndDiscard(expressionStatement.getExpression()); + assert method.getStackSize() == 0; return false; } @Override public boolean enterBlockStatement(final BlockStatement blockStatement) { - lineNumber(blockStatement); + if(!method.isReachable()) { + return false; + } + enterStatement(blockStatement); blockStatement.getBlock().accept(this); @@ -870,83 +1562,70 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex @Override public boolean enterForNode(final ForNode forNode) { - lineNumber(forNode); - + if(!method.isReachable()) { + return false; + } + enterStatement(forNode); if (forNode.isForIn()) { enterForIn(forNode); } else { - enterFor(forNode); + final Expression init = forNode.getInit(); + if (init != null) { + loadAndDiscard(init); + } + enterForOrWhile(forNode, forNode.getModify()); } return false; } - private void enterFor(final ForNode forNode) { - final Expression init = forNode.getInit(); - final Expression test = forNode.getTest(); - final Block body = forNode.getBody(); - final Expression modify = forNode.getModify(); - - if (init != null) { - init.accept(this); - } - - final Label loopLabel = new Label("loop"); - final Label testLabel = new Label("test"); - - method._goto(testLabel); - method.label(loopLabel); - body.accept(this); - method.label(forNode.getContinueLabel()); - - 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(forNode.getBreakLabel()); - } - private void enterForIn(final ForNode forNode) { - final Block body = forNode.getBody(); - final Expression modify = forNode.getModify(); - - final Symbol iter = forNode.getIterator(); - final Label loopLabel = new Label("loop"); - - final Expression init = forNode.getInit(); - - load(modify, Type.OBJECT); + loadExpression(forNode.getModify(), TypeBounds.OBJECT); method.invoke(forNode.isForEach() ? ScriptRuntime.TO_VALUE_ITERATOR : ScriptRuntime.TO_PROPERTY_ITERATOR); - method.store(iter); - method._goto(forNode.getContinueLabel()); - method.label(loopLabel); + final Symbol iterSymbol = forNode.getIterator(); + final int iterSlot = iterSymbol.getSlot(Type.OBJECT); + method.store(iterSymbol, ITERATOR_TYPE); + + method.beforeJoinPoint(forNode); + + final Label continueLabel = forNode.getContinueLabel(); + final Label breakLabel = forNode.getBreakLabel(); + + method.label(continueLabel); + method.load(ITERATOR_TYPE, iterSlot); + method.invoke(interfaceCallNoLookup(ITERATOR_CLASS, "hasNext", boolean.class)); + final JoinPredecessorExpression test = forNode.getTest(); + final Block body = forNode.getBody(); + if(LocalVariableConversion.hasLiveConversion(test)) { + final Label afterConversion = new Label("for_in_after_test_conv"); + method.ifne(afterConversion); + method.beforeJoinPoint(test); + method._goto(breakLabel); + method.label(afterConversion); + } else { + method.ifeq(breakLabel); + } - new Store<Expression>(init) { + new Store<Expression>(forNode.getInit()) { @Override protected void storeNonDiscard() { - return; + // This expression is neither part of a discard, nor needs to be left on the stack after it was + // stored, so we override storeNonDiscard to be a no-op. } + @Override protected void evaluate() { - method.load(iter); - method.invoke(interfaceCallNoLookup(Iterator.class, "next", Object.class)); + method.load(ITERATOR_TYPE, iterSlot); + // TODO: optimistic for-in iteration + method.invoke(interfaceCallNoLookup(ITERATOR_CLASS, "next", Object.class)); } }.store(); - body.accept(this); - method.label(forNode.getContinueLabel()); - method.load(iter); - method.invoke(interfaceCallNoLookup(Iterator.class, "hasNext", boolean.class)); - method.ifne(loopLabel); - method.label(forNode.getBreakLabel()); + if(method.isReachable()) { + method._goto(continueLabel); + } + method.label(breakLabel); } /** @@ -955,13 +1634,16 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex * @param block block with local vars. */ private void initLocals(final Block block) { - lc.nextFreeSlot(block); + lc.onEnterBlock(block); final boolean isFunctionBody = lc.isFunctionBody(); - final FunctionNode function = lc.getCurrentFunction(); if (isFunctionBody) { - if(method.hasScope()) { + initializeMethodParameters(function); + if(!function.isVarArg()) { + expandParameterSlots(function); + } + if (method.hasScope()) { if (function.needsParentScope()) { method.loadCompilerConstant(CALLEE); method.invoke(ScriptFunction.GET_SCOPE); @@ -988,76 +1670,173 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex // 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<>(); - - // Initalize symbols and values - final List<Symbol> newSymbols = new ArrayList<>(); - final List<Symbol> values = new ArrayList<>(); - final boolean hasArguments = function.needsArguments(); - + final List<MapTuple<Symbol>> tuples = new ArrayList<>(); + final Iterator<IdentNode> paramIter = function.getParameters().iterator(); for (final Symbol symbol : block.getSymbols()) { - - if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) { + if (symbol.isInternal() || symbol.isThis()) { continue; } if (symbol.isVar()) { + assert !varsInScope || symbol.isScope(); if (varsInScope || symbol.isScope()) { - nameList.add(symbol.getName()); - newSymbols.add(symbol); - values.add(null); assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName(); assert !symbol.hasSlot() : "slot for " + symbol + " should have been removed in Lower already" + function.getName(); + + //this tuple will not be put fielded, as it has no value, just a symbol + tuples.add(new MapTuple<Symbol>(symbol.getName(), symbol, null)); } else { - assert symbol.hasSlot() : symbol + " should have a slot only, no scope"; - locals.add(symbol); + assert symbol.hasSlot() || symbol.slotCount() == 0 : symbol + " should have a slot only, no scope"; } } else if (symbol.isParam() && (varsInScope || hasArguments || symbol.isScope())) { - nameList.add(symbol.getName()); - newSymbols.add(symbol); - values.add(hasArguments ? null : symbol); - assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName() + " varsInScope="+varsInScope+" hasArguments="+hasArguments+" symbol.isScope()=" + symbol.isScope(); + assert symbol.isScope() : "scope for " + symbol + " should have been set in AssignSymbols already " + function.getName() + " varsInScope="+varsInScope+" hasArguments="+hasArguments+" symbol.isScope()=" + symbol.isScope(); assert !(hasArguments && symbol.hasSlot()) : "slot for " + symbol + " should have been removed in Lower already " + function.getName(); + + final Type paramType; + final Symbol paramSymbol; + + if (hasArguments) { + assert !symbol.hasSlot() : "slot for " + symbol + " should have been removed in Lower already "; + paramSymbol = null; + paramType = null; + } else { + paramSymbol = symbol; + // NOTE: We're relying on the fact here that Block.symbols is a LinkedHashMap, hence it will + // return symbols in the order they were defined, and parameters are defined in the same order + // they appear in the function. That's why we can have a single pass over the parameter list + // with an iterator, always just scanning forward for the next parameter that matches the symbol + // name. + for(;;) { + final IdentNode nextParam = paramIter.next(); + if(nextParam.getName().equals(symbol.getName())) { + paramType = nextParam.getType(); + break; + } + } + } + + tuples.add(new MapTuple<Symbol>(symbol.getName(), symbol, paramType, paramSymbol) { + //this symbol will be put fielded, we can't initialize it as undefined with a known type + @Override + public Class<?> getValueType() { + if (OBJECT_FIELDS_ONLY || value == null || paramType == null) { + return Object.class; + } + return paramType.isBoolean() ? Object.class : paramType.getTypeClass(); + } + }); } } - // we may have locals that need to be initialized - initSymbols(locals); - /* * Create a new object based on the symbols and values, generate * bootstrap code for object */ - new FieldObjectCreator<Symbol>(this, nameList, newSymbols, values, true, hasArguments) { + new FieldObjectCreator<Symbol>(this, tuples, true, hasArguments) { @Override - protected void loadValue(final Symbol value) { - method.load(value); + protected void loadValue(final Symbol value, final Type type) { + method.load(value, type); } }.makeObject(method); - - // runScript(): merge scope into global + // program function: merge scope into global if (isFunctionBody && function.isProgram()) { method.invoke(ScriptRuntime.MERGE_SCOPE); } method.storeCompilerConstant(SCOPE); - } else { + if(!isFunctionBody) { + // Function body doesn't need a try/catch to restore scope, as it'd be a dead store anyway. Allowing it + // actually causes issues with UnwarrantedOptimismException handlers as ASM will sort this handler to + // the top of the exception handler table, so it'll be triggered instead of the UOE handlers. + final Label scopeEntryLabel = new Label("scope_entry"); + scopeEntryLabels.push(scopeEntryLabel); + method.label(scopeEntryLabel); + } + } else if (isFunctionBody && function.isVarArg()) { // Since we don't have a scope, parameters didn't get assigned array indices by the FieldObjectCreator, so // we need to assign them separately here. int nextParam = 0; - if (isFunctionBody && function.isVarArg()) { - for (final IdentNode param : function.getParameters()) { - param.getSymbol().setFieldIndex(nextParam++); - } + for (final IdentNode param : function.getParameters()) { + param.getSymbol().setFieldIndex(nextParam++); } - - initSymbols(block.getSymbols()); } // Debugging: print symbols? @see --print-symbols flag - printSymbols(block, (isFunctionBody ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName())); + printSymbols(block, function, (isFunctionBody ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName())); + } + + /** + * Incoming method parameters are always declared on method entry; declare them in the local variable table. + * @param function function for which code is being generated. + */ + private void initializeMethodParameters(final FunctionNode function) { + final Label functionStart = new Label("fn_start"); + method.label(functionStart); + int nextSlot = 0; + if(function.needsCallee()) { + initializeInternalFunctionParameter(CALLEE, function, functionStart, nextSlot++); + } + initializeInternalFunctionParameter(THIS, function, functionStart, nextSlot++); + if(function.isVarArg()) { + initializeInternalFunctionParameter(VARARGS, function, functionStart, nextSlot++); + } else { + for(final IdentNode param: function.getParameters()) { + final Symbol symbol = param.getSymbol(); + if(symbol.isBytecodeLocal()) { + method.initializeMethodParameter(symbol, param.getType(), functionStart); + } + } + } + } + + private void initializeInternalFunctionParameter(final CompilerConstants cc, final FunctionNode fn, final Label functionStart, final int slot) { + final Symbol symbol = initializeInternalFunctionOrSplitParameter(cc, fn, functionStart, slot); + // Internal function params (:callee, this, and :varargs) are never expanded to multiple slots + assert symbol.getFirstSlot() == slot; + } + + private Symbol initializeInternalFunctionOrSplitParameter(final CompilerConstants cc, final FunctionNode fn, final Label functionStart, final int slot) { + final Symbol symbol = fn.getBody().getExistingSymbol(cc.symbolName()); + final Type type = Type.typeFor(cc.type()); + method.initializeMethodParameter(symbol, type, functionStart); + method.onLocalStore(type, slot); + return symbol; + } + + /** + * Parameters come into the method packed into local variable slots next to each other. Nashorn on the other hand + * can use 1-6 slots for a local variable depending on all the types it needs to store. When this method is invoked, + * the symbols are already allocated such wider slots, but the values are still in tightly packed incoming slots, + * and we need to spread them into their new locations. + * @param function the function for which parameter-spreading code needs to be emitted + */ + private void expandParameterSlots(final FunctionNode function) { + final List<IdentNode> parameters = function.getParameters(); + // Calculate the total number of incoming parameter slots + int currentIncomingSlot = function.needsCallee() ? 2 : 1; + for(final IdentNode parameter: parameters) { + currentIncomingSlot += parameter.getType().getSlots(); + } + // Starting from last parameter going backwards, move the parameter values into their new slots. + for(int i = parameters.size(); i-- > 0;) { + final IdentNode parameter = parameters.get(i); + final Type parameterType = parameter.getType(); + final int typeWidth = parameterType.getSlots(); + currentIncomingSlot -= typeWidth; + final Symbol symbol = parameter.getSymbol(); + final int slotCount = symbol.slotCount(); + assert slotCount > 0; + // Scoped parameters must not hold more than one value + assert symbol.isBytecodeLocal() || slotCount == typeWidth; + + // Mark it as having its value stored into it by the method invocation. + method.onLocalStore(parameterType, currentIncomingSlot); + if(currentIncomingSlot != symbol.getSlot(parameterType)) { + method.load(parameterType, currentIncomingSlot); + method.store(symbol, parameterType); + } + } } private void initArguments(final FunctionNode function) { @@ -1075,15 +1854,45 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex method.storeCompilerConstant(ARGUMENTS); } + private boolean skipFunction(final FunctionNode functionNode) { + final ScriptEnvironment env = compiler.getScriptEnvironment(); + final boolean lazy = env._lazy_compilation; + final boolean onDemand = compiler.isOnDemandCompilation(); + + // If this is on-demand or lazy compilation, don't compile a nested (not topmost) function. + if((onDemand || lazy) && lc.getOutermostFunction() != functionNode) { + return true; + } + + // If lazy compiling with optimistic types, don't compile the program eagerly either. It will soon be + // invalidated anyway. In presence of a class cache, this further means that an obsoleted program version + // lingers around. Also, currently loading previously persisted optimistic types information only works if + // we're on-demand compiling a function, so with this strategy the :program method can also have the warmup + // benefit of using previously persisted types. + // + // NOTE that this means the first compiled class will effectively just have a :createProgramFunction method, and + // the RecompilableScriptFunctionData (RSFD) object in its constants array. It won't even have the :program + // method. This is by design. It does mean that we're wasting one compiler execution (and we could minimize this + // by just running it up to scope depth calculation, which creates the RSFDs and then this limited codegen). + // We could emit an initial separate compile unit with the initial version of :program in it to better utilize + // the compilation pipeline, but that would need more invasive changes, as currently the assumption that + // :program is emitted into the first compilation unit of the function lives in many places. + return !onDemand && lazy && env._optimistic_types && functionNode.isProgram(); + } + @Override public boolean enterFunctionNode(final FunctionNode functionNode) { - if (functionNode.isLazy()) { - // Must do it now; can't postpone it until leaveFunctionNode() - newFunctionObject(functionNode, functionNode); + final int fnId = functionNode.getId(); + + if (skipFunction(functionNode)) { + // In case we are not generating code for the function, we must create or retrieve the function object and + // load it on the stack here. + newFunctionObject(functionNode, false); return false; } final String fnName = functionNode.getName(); + // NOTE: we only emit the method for a function with the given name once. We can have multiple functions with // the same name as a result of inlining finally blocks. However, in the future -- with type specialization, // notably -- we might need to check for both name *and* signature. Of course, even that might not be @@ -1092,35 +1901,67 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex // to decide to either generate a unique method for each inlined copy of the function, maybe figure out its // exact type closure and deduplicate based on that, or just decide that functions in finally blocks aren't // worth it, and generate one method with most generic type closure. - if(!emittedMethods.contains(fnName)) { - LOG.info("=== BEGIN ", fnName); + if (!emittedMethods.contains(fnName)) { + log.info("=== BEGIN ", fnName); assert functionNode.getCompileUnit() != null : "no compile unit for " + fnName + " " + Debug.id(functionNode); unit = lc.pushCompileUnit(functionNode.getCompileUnit()); assert lc.hasCompileUnits(); - method = lc.pushMethodEmitter(unit.getClassEmitter().method(functionNode)); + final ClassEmitter classEmitter = unit.getClassEmitter(); + pushMethodEmitter(isRestOf() ? classEmitter.restOfMethod(functionNode) : classEmitter.method(functionNode)); + method.setPreventUndefinedLoad(); + if(useOptimisticTypes()) { + lc.pushUnwarrantedOptimismHandlers(); + } + // new method - reset last line number lastLineNumber = -1; - // Mark end for variable tables. + method.begin(); + + if (isRestOf()) { + final ContinuationInfo ci = new ContinuationInfo(); + fnIdToContinuationInfo.put(fnId, ci); + method.gotoLoopStart(ci.getHandlerLabel()); + } } return true; } + private void pushMethodEmitter(final MethodEmitter newMethod) { + method = lc.pushMethodEmitter(newMethod); + catchLabels.push(METHOD_BOUNDARY); + } + + private void popMethodEmitter() { + method = lc.popMethodEmitter(method); + assert catchLabels.peek() == METHOD_BOUNDARY; + catchLabels.pop(); + } + @Override public Node leaveFunctionNode(final FunctionNode functionNode) { try { - if(emittedMethods.add(functionNode.getName())) { + final boolean markOptimistic; + if (emittedMethods.add(functionNode.getName())) { + markOptimistic = generateUnwarrantedOptimismExceptionHandlers(functionNode); + generateContinuationHandler(); method.end(); // wrap up this method unit = lc.popCompileUnit(functionNode.getCompileUnit()); - method = lc.popMethodEmitter(method); - LOG.info("=== END ", functionNode.getName()); + popMethodEmitter(); + log.info("=== END ", functionNode.getName()); + } else { + markOptimistic = false; + } + + FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.BYTECODE_GENERATED); + if (markOptimistic) { + newFunctionNode = newFunctionNode.setFlag(lc, FunctionNode.IS_DEOPTIMIZABLE); } - final FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.EMITTED); - newFunctionObject(newFunctionNode, functionNode); + newFunctionObject(newFunctionNode, true); return newFunctionNode; } catch (final Throwable t) { Context.printStackTrace(t); @@ -1131,51 +1972,47 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex } @Override - public boolean enterIdentNode(final IdentNode identNode) { - return false; - } - - @Override public boolean enterIfNode(final IfNode ifNode) { - lineNumber(ifNode); + if(!method.isReachable()) { + return false; + } + enterStatement(ifNode); final Expression test = ifNode.getTest(); final Block pass = ifNode.getPass(); final Block fail = ifNode.getFail(); + final boolean hasFailConversion = LocalVariableConversion.hasLiveConversion(ifNode); 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); + final Label afterLabel = (fail == null && !hasFailConversion) ? null : new Label("if_done"); - boolean passTerminal = false; - boolean failTerminal = false; + emitBranch(test, failLabel, false); pass.accept(this); - if (!pass.hasTerminalFlags()) { + if(method.isReachable() && afterLabel != null) { method._goto(afterLabel); //don't fallthru to fail block - } else { - passTerminal = pass.isTerminal(); } + method.label(failLabel); if (fail != null) { - method.label(failLabel); fail.accept(this); - failTerminal = fail.isTerminal(); + } else if(hasFailConversion) { + method.beforeJoinPoint(ifNode); } - //if if terminates, put the after label there - if (!passTerminal || !failTerminal) { + if(afterLabel != null) { method.label(afterLabel); } return false; } - @Override - public boolean enterIndexNode(final IndexNode indexNode) { - load(indexNode); - return false; + private void emitBranch(final Expression test, final Label label, final boolean jumpWhenTrue) { + new BranchOptimizer(this, method).execute(test, label, jumpWhenTrue); + } + + private void enterStatement(final Statement statement) { + lineNumber(statement); } private void lineNumber(final Statement statement) { @@ -1189,6 +2026,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex lastLineNumber = lineNumber; } + int getLastLineNumber() { + return lastLineNumber; + } + /** * Load a list of nodes as an array of a specific type * The array will contain the visited nodes. @@ -1219,26 +2060,34 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex unit = lc.pushCompileUnit(arrayUnit.getCompileUnit()); final String className = unit.getUnitClassName(); + assert unit != null; final String name = currentFunction.uniqueName(SPLIT_PREFIX.symbolName()); final String signature = methodDescriptor(type, ScriptFunction.class, Object.class, ScriptObject.class, type); - final MethodEmitter me = unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature); - method = lc.pushMethodEmitter(me); + pushMethodEmitter(unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature)); method.setFunctionNode(currentFunction); method.begin(); + defineCommonSplitMethodParameters(); + defineSplitMethodParameter(3, arrayType); + fixScopeSlot(currentFunction); - method.load(arrayType, SPLIT_ARRAY_ARG.slot()); + lc.enterSplitNode(); + final int arraySlot = SPLIT_ARRAY_ARG.slot(); for (int i = arrayUnit.getLo(); i < arrayUnit.getHi(); i++) { + method.load(arrayType, arraySlot); storeElement(nodes, elementType, postsets[i]); } + method.load(arrayType, arraySlot); method._return(); + lc.exitSplitNode(); method.end(); - method = lc.popMethodEmitter(me); + lc.releaseSlots(); + popMethodEmitter(); assert method == savedMethod; method.loadCompilerConstant(CALLEE); @@ -1255,15 +2104,19 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex return method; } - for (final int postset : postsets) { - storeElement(nodes, elementType, postset); + if(postsets.length > 0) { + final int arraySlot = method.getUsedSlotsWithLiveTemporaries(); + method.storeTemp(arrayType, arraySlot); + for (final int postset : postsets) { + method.load(arrayType, arraySlot); + storeElement(nodes, elementType, postset); + } + method.load(arrayType, arraySlot); } - return method; } private void storeElement(final Expressi |