diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ApplySpecialization.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ApplySpecialization.java index b05475b6e54..cdfe34f2ee5 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ApplySpecialization.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ApplySpecialization.java @@ -27,6 +27,7 @@ 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; @@ -38,6 +39,7 @@ 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.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.Node; @@ -321,7 +323,7 @@ public final class ApplySpecialization extends NodeVisitor imple explodedArguments.pop(); - return newFunctionNode; + return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED); } private static boolean isApply(final CallNode callNode) { diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java index f83dc9ccdd1..88fd89bba18 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AssignSymbols.java @@ -76,7 +76,6 @@ 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; @@ -135,9 +134,6 @@ final class AssignSymbols extends NodeVisitor implements Loggabl 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()); @@ -1014,7 +1010,7 @@ final class AssignSymbols extends NodeVisitor implements Loggabl boolean previousWasBlock = false; for (final Iterator it = lc.getAllNodes(); it.hasNext();) { final LexicalContextNode node = it.next(); - if (node instanceof FunctionNode || node instanceof SplitNode || isSplitArray(node)) { + if (node instanceof FunctionNode || 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; diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AstSerializer.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AstSerializer.java new file mode 100644 index 00000000000..19197a26a0b --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/AstSerializer.java @@ -0,0 +1,71 @@ +/* + * 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 java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.Collections; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.LexicalContext; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.Statement; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.runtime.options.Options; + +/** + * This static utility class performs serialization of FunctionNode ASTs to a byte array. + * The format is a standard Java serialization stream, deflated. + */ +final class AstSerializer { + // Experimentally, we concluded that compression level 4 gives a good tradeoff between serialization speed + // and size. + private static final int COMPRESSION_LEVEL = Options.getIntProperty("nashorn.serialize.compression", 4); + static byte[] serialize(final FunctionNode fn) { + final ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (final ObjectOutputStream oout = new ObjectOutputStream(new DeflaterOutputStream(out, + new Deflater(COMPRESSION_LEVEL)))) { + oout.writeObject(removeInnerFunctionBodies(fn)); + } catch (final IOException e) { + throw new AssertionError("Unexpected exception serializing function", e); + } + return out.toByteArray(); + } + + private static FunctionNode removeInnerFunctionBodies(final FunctionNode fn) { + return (FunctionNode)fn.accept(new NodeVisitor(new LexicalContext()) { + @Override + public Node leaveBlock(final Block block) { + if (lc.isFunctionBody() && lc.getFunction(block) != lc.getOutermostFunction()) { + return block.setStatements(lc, Collections.emptyList()); + } + return super.leaveBlock(block); + } + }); + } +} diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ClassEmitter.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ClassEmitter.java index 7a89e490508..80bb61dd080 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ClassEmitter.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ClassEmitter.java @@ -51,6 +51,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.className; import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor; 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.security.AccessController; @@ -64,7 +65,6 @@ 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; @@ -476,12 +476,6 @@ public class ClassEmitter implements Emitter { methodsStarted.remove(method); } - SplitMethodEmitter method(final SplitNode splitNode, final String methodName, final Class rtype, final Class... ptypes) { - methodCount++; - methodNames.add(methodName); - return new SplitMethodEmitter(this, methodVisitor(EnumSet.of(Flag.PUBLIC, Flag.STATIC), methodName, rtype, ptypes), splitNode); - } - /** * Add a new method to the class - defaults to public method * diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java index ae3ee6947e0..07ca5c0b7c0 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CodeGenerator.java @@ -34,9 +34,7 @@ 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; import static jdk.nashorn.internal.codegen.CompilerConstants.REGEX_PREFIX; -import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; -import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_ARRAY_ARG; import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; @@ -99,10 +97,10 @@ import jdk.nashorn.internal.ir.ExpressionStatement; import jdk.nashorn.internal.ir.ForNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; +import jdk.nashorn.internal.ir.GetSplitState; 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.JumpStatement; import jdk.nashorn.internal.ir.LabelNode; @@ -121,7 +119,8 @@ import jdk.nashorn.internal.ir.PropertyNode; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.RuntimeNode.Request; -import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.SetSplitState; +import jdk.nashorn.internal.ir.SplitReturn; import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; @@ -493,8 +492,7 @@ final class CodeGenerator extends NodeOperatorVisitor rtype = fn.getReturnType().getTypeClass(); - final boolean needsArguments = fn.needsArguments(); - final Class[] ptypes = needsArguments ? - new Class[] {ScriptFunction.class, Object.class, ScriptObject.class, ScriptObject.class} : - new Class[] {ScriptFunction.class, Object.class, ScriptObject.class}; - - final MethodEmitter caller = method; - unit = lc.pushCompileUnit(splitCompileUnit); - - final Call splitCall = staticCallNoLookup( - className, - name, - methodDescriptor(rtype, ptypes)); - - final MethodEmitter splitEmitter = - splitCompileUnit.getClassEmitter().method( - splitNode, - name, - rtype, - ptypes); - - pushMethodEmitter(splitEmitter); - method.setFunctionNode(fn); - - assert fn.needsCallee() : "split function should require callee"; - caller.loadCompilerConstant(CALLEE); - caller.loadCompilerConstant(THIS); - caller.loadCompilerConstant(SCOPE); - if (needsArguments) { - caller.loadCompilerConstant(ARGUMENTS); - } - caller.invoke(splitCall); - caller.storeCompilerConstant(RETURN, returnType); - - method.begin(); - - defineCommonSplitMethodParameters(); - if(needsArguments) { - defineSplitMethodParameter(3, ARGUMENTS); - } - - // Copy scope to its target slot as first thing because the original slot could be used by return symbol. - fixScopeSlot(fn); - - final int returnSlot = fn.compilerConstant(RETURN).getSlot(returnType); - method.defineBlockLocalVariable(returnSlot, returnSlot + returnType.getSlots()); - method.loadUndefined(returnType); - method.storeCompilerConstant(RETURN, returnType); - - lc.enterSplitNode(); - return true; - } - private void defineCommonSplitMethodParameters() { defineSplitMethodParameter(0, CALLEE); defineSplitMethodParameter(1, THIS); @@ -2782,114 +2721,40 @@ final class CodeGenerator extends NodeOperatorVisitor targets = splitMethod.getExternalTargets(); - final boolean hasControlFlow = hasReturn || !targets.isEmpty(); - final List targetNodes = splitMethod.getExternalTargetNodes(); - final Type returnType = lc.getCurrentFunction().getReturnType(); - - try { - // Wrap up this method. - - if(method.isReachable()) { - if (hasControlFlow) { - method.setSplitState(-1); - } - method.loadCompilerConstant(RETURN, returnType); - method._return(returnType); - } - method.end(); - - lc.releaseSlots(); - - unit = lc.popCompileUnit(splitNode.getCompileUnit()); - popMethodEmitter(); - - } catch (final Throwable t) { - Context.printStackTrace(t); - final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + getCurrentSource().getName()); - e.initCause(t); - throw e; + public boolean enterSplitReturn(final SplitReturn splitReturn) { + if (method.isReachable()) { + method.loadUndefined(lc.getCurrentFunction().getReturnType())._return(); } + return false; + } - //no external jump targets or return in switch node - if (!hasControlFlow) { - return splitNode; + @Override + public boolean enterSetSplitState(final SetSplitState setSplitState) { + if (method.isReachable()) { + method.setSplitState(setSplitState.getState()); } - - // Handle return from split method if there was one. - final MethodEmitter caller = method; - final int targetCount = targets.size(); - - caller.loadScope(); - caller.invoke(Scope.GET_SPLIT_STATE); - - final Label breakLabel = new Label("no_split_state"); - // Split state is -1 for no split state, 0 for return, 1..n+1 for break/continue - - //the common case is that we don't need a switch - if (targetCount == 0) { - assert hasReturn; - caller.ifne(breakLabel); - //has to be zero - caller.label(new Label("split_return")); - caller.loadCompilerConstant(RETURN, returnType); - caller._return(returnType); - caller.label(breakLabel); - } else { - assert !targets.isEmpty(); - - final int low = hasReturn ? 0 : 1; - final int labelCount = targetCount + 1 - low; - final Label[] labels = new Label[labelCount]; - - for (int i = 0; i < labelCount; i++) { - labels[i] = new Label(i == 0 ? "split_return" : "split_" + targets.get(i - 1)); - } - caller.tableswitch(low, targetCount, breakLabel, labels); - for (int i = low; i <= targetCount; i++) { - caller.label(labels[i - low]); - if (i == 0) { - caller.loadCompilerConstant(RETURN, returnType); - caller._return(returnType); - } else { - final BreakableNode targetNode = targetNodes.get(i - 1); - final Label label = targets.get(i - 1); - if (!lc.isExternalTarget(splitNode, targetNode)) { - final JoinPredecessor jumpOrigin = splitNode.getJumpOrigin(label); - if(jumpOrigin != null) { - method.beforeJoinPoint(jumpOrigin); - } - popScopesUntil(targetNode); - } - caller.splitAwareGoto(lc, label, targetNode); - } - } - caller.label(breakLabel); - } - - // If split has a return and caller is itself a split method it needs to propagate the return. - if (hasReturn) { - caller.setHasReturn(); - } - - return splitNode; + return false; } @Override @@ -4379,11 +4244,7 @@ final class CodeGenerator extends NodeOperatorVisitor(new LexicalContext()) { - @Override - public Node leaveFunctionNode(final FunctionNode node) { - return node.setState(lc, BUILTINS_TRANSFORMED); - } - }); + return setStates(transformFunction(fn, new ApplySpecialization(compiler)), BUILTINS_TRANSFORMED); } @Override @@ -177,7 +150,7 @@ enum CompilationPhase { FunctionNode newFunctionNode; //ensure elementTypes, postsets and presets exist for splitter and arraynodes - newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor(new LexicalContext()) { + newFunctionNode = transformFunction(fn, new NodeVisitor(new LexicalContext()) { @Override public LiteralNode leaveLiteralNode(final LiteralNode literalNode) { return literalNode.initialize(lc); @@ -185,7 +158,7 @@ enum CompilationPhase { }); newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true); - + newFunctionNode = transformFunction(newFunctionNode, new SplitIntoFunctions(compiler)); assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit; assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName()); @@ -198,6 +171,52 @@ enum CompilationPhase { } }, + PROGRAM_POINT_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT)) { + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + return transformFunction(fn, new ProgramPoints()); + } + + @Override + public String toString() { + return "'Program Point Calculation'"; + } + }, + + SERIALIZE_SPLIT_PHASE( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT)) { + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + return transformFunction(fn, new NodeVisitor(new LexicalContext()) { + @Override + public boolean enterFunctionNode(final FunctionNode functionNode) { + if (functionNode.isSplit()) { + compiler.serializeAst(functionNode); + } + return true; + } + }); + } + + @Override + public String toString() { + return "'Serialize Split Functions'"; + } + }, + SYMBOL_ASSIGNMENT_PHASE( EnumSet.of( INITIALIZED, @@ -208,7 +227,7 @@ enum CompilationPhase { SPLIT)) { @Override FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { - return (FunctionNode)fn.accept(new AssignSymbols(compiler)); + return transformFunction(fn, new AssignSymbols(compiler)); } @Override @@ -228,7 +247,7 @@ enum CompilationPhase { SYMBOLS_ASSIGNED)) { @Override FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { - return (FunctionNode)fn.accept(new FindScopeDepths(compiler)); + return transformFunction(fn, new FindScopeDepths(compiler)); } @Override @@ -250,7 +269,7 @@ enum CompilationPhase { @Override FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { if (compiler.useOptimisticTypes()) { - return (FunctionNode)fn.accept(new OptimisticTypesCalculator(compiler)); + return transformFunction(fn, new OptimisticTypesCalculator(compiler)); } return setStates(fn, OPTIMISTIC_TYPES_ASSIGNED); } @@ -274,8 +293,7 @@ enum CompilationPhase { OPTIMISTIC_TYPES_ASSIGNED)) { @Override FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { - final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new LocalVariableTypesCalculator(compiler)); - + final FunctionNode newFunctionNode = transformFunction(fn, new LocalVariableTypesCalculator(compiler)); final ScriptEnvironment senv = compiler.getScriptEnvironment(); final PrintWriter err = senv.getErr(); @@ -330,13 +348,7 @@ enum CompilationPhase { for (final CompileUnit oldUnit : compiler.getCompileUnits()) { assert map.get(oldUnit) == null; - final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName()); - if (phases.isRestOfCompilation()) { - sb.append("$restOf"); - } - //it's ok to not copy the initCount, methodCount and clinitCount here, as codegen is what - //fills those out anyway. Thus no need for a copy constructor - final CompileUnit newUnit = compiler.createCompileUnit(sb.toString(), oldUnit.getWeight()); + final CompileUnit newUnit = createNewCompileUnit(compiler, phases); log.fine("Creating new compile unit ", oldUnit, " => ", newUnit); map.put(oldUnit, newUnit); assert newUnit != null; @@ -350,47 +362,10 @@ enum CompilationPhase { //replace old compile units in function nodes, if any are assigned, //for example by running the splitter on this function node in a previous //partial code generation - final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor(new LexicalContext()) { + final FunctionNode newFunctionNode = transformFunction(fn, new ReplaceCompileUnits() { @Override - public Node leaveFunctionNode(final FunctionNode node) { - final CompileUnit oldUnit = node.getCompileUnit(); - assert oldUnit != null : "no compile unit in function node"; - - final CompileUnit newUnit = map.get(oldUnit); - assert newUnit != null : "old unit has no mapping to new unit " + oldUnit; - - log.fine("Replacing compile unit: ", oldUnit, " => ", newUnit, " in ", quote(node.getName())); - return node.setCompileUnit(lc, newUnit).setState(lc, CompilationState.COMPILE_UNITS_REUSED); - } - - @Override - public Node leaveSplitNode(final SplitNode node) { - final CompileUnit oldUnit = node.getCompileUnit(); - assert oldUnit != null : "no compile unit in function node"; - - final CompileUnit newUnit = map.get(oldUnit); - assert newUnit != null : "old unit has no mapping to new unit " + oldUnit; - - log.fine("Replacing compile unit: ", oldUnit, " => ", newUnit, " in ", quote(node.getName())); - return node.setCompileUnit(lc, newUnit); - } - - @Override - public Node leaveLiteralNode(final LiteralNode node) { - if (node instanceof ArrayLiteralNode) { - final ArrayLiteralNode aln = (ArrayLiteralNode)node; - if (aln.getUnits() == null) { - return node; - } - final List newArrayUnits = new ArrayList<>(); - for (final ArrayUnit au : aln.getUnits()) { - final CompileUnit newUnit = map.get(au.getCompileUnit()); - assert newUnit != null; - newArrayUnits.add(new ArrayUnit(newUnit, au.getLo(), au.getHi())); - } - return aln.setUnits(lc, newArrayUnits); - } - return node; + CompileUnit getReplacement(CompileUnit original) { + return map.get(original); } @Override @@ -408,7 +383,59 @@ enum CompilationPhase { } }, - /** + REINITIALIZE_SERIALIZED( + EnumSet.of( + INITIALIZED, + PARSED, + CONSTANT_FOLDED, + LOWERED, + BUILTINS_TRANSFORMED, + SPLIT)) { + @Override + FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { + final Set unitSet = CompileUnit.createCompileUnitSet(); + final Map unitMap = new HashMap<>(); + + // Ensure that the FunctionNode's compile unit is the first in the list of new units. Install phase + // will use that as the root class. + createCompileUnit(fn.getCompileUnit(), unitSet, unitMap, compiler, phases); + + final FunctionNode newFn = transformFunction(fn, new ReplaceCompileUnits() { + @Override + CompileUnit getReplacement(final CompileUnit oldUnit) { + final CompileUnit existing = unitMap.get(oldUnit); + if (existing != null) { + return existing; + } + return createCompileUnit(oldUnit, unitSet, unitMap, compiler, phases); + } + + @Override + public Node leaveFunctionNode(final FunctionNode fn2) { + return super.leaveFunctionNode( + // restore flags for deserialized nested function nodes + compiler.getScriptFunctionData(fn2.getId()).restoreFlags(lc, fn2)); + }; + }); + compiler.replaceCompileUnits(unitSet); + return newFn; + } + + private CompileUnit createCompileUnit(final CompileUnit oldUnit, final Set unitSet, + final Map unitMap, final Compiler compiler, final CompilationPhases phases) { + final CompileUnit newUnit = createNewCompileUnit(compiler, phases); + unitMap.put(oldUnit, newUnit); + unitSet.add(newUnit); + return newUnit; + } + + @Override + public String toString() { + return "'Deserialize'"; + } + }, + + /** * Bytecode generation: * * Generate the byte code class(es) resulting from the compiled FunctionNode @@ -443,7 +470,7 @@ enum CompilationPhase { try { // Explicitly set BYTECODE_GENERATED here; it can not be set in case of skipping codegen for :program // in the lazy + optimistic world. See CodeGenerator.skipFunction(). - newFunctionNode = ((FunctionNode)newFunctionNode.accept(codegen)).setState(null, BYTECODE_GENERATED); + newFunctionNode = transformFunction(newFunctionNode, codegen).setState(null, BYTECODE_GENERATED); codegen.generateScopeCalls(); } catch (final VerifyError e) { if (senv._verify_code || senv._print_code) { @@ -615,7 +642,7 @@ enum CompilationPhase { if (!AssertsEnabled.assertsEnabled()) { return functionNode; } - return (FunctionNode)functionNode.accept(new NodeVisitor(new LexicalContext()) { + return transformFunction(functionNode, new NodeVisitor(new LexicalContext()) { @Override public Node leaveFunctionNode(final FunctionNode fn) { return fn.setState(lc, state); @@ -701,4 +728,17 @@ enum CompilationPhase { return end(compiler, transform(compiler, phases, begin(compiler, functionNode))); } + private static FunctionNode transformFunction(final FunctionNode fn, final NodeVisitor visitor) { + return (FunctionNode) fn.accept(visitor); + } + + private static CompileUnit createNewCompileUnit(final Compiler compiler, final CompilationPhases phases) { + final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName()); + if (phases.isRestOfCompilation()) { + sb.append("$restOf"); + } + //it's ok to not copy the initCount, methodCount and clinitCount here, as codegen is what + //fills those out anyway. Thus no need for a copy constructor + return compiler.createCompileUnit(sb.toString(), 0); + } } diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompileUnit.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompileUnit.java index 79747573c2b..13e9d7168cb 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompileUnit.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/CompileUnit.java @@ -28,11 +28,13 @@ package jdk.nashorn.internal.codegen; import java.io.Serializable; import java.util.Set; import java.util.TreeSet; +import jdk.nashorn.internal.ir.CompileUnitHolder; /** * Used to track split class compilation. Note that instances of the class are serializable, but all fields are * transient, making the serialized version of the class only useful for tracking the referential topology of other - * AST nodes referencing the same or different compile units. + * AST nodes referencing the same or different compile units. We do want to preserve this topology though as + * {@link CompileUnitHolder}s in a deserialized AST will undergo reinitialization. */ public final class CompileUnit implements Comparable, Serializable { private static final long serialVersionUID = 1L; @@ -126,14 +128,6 @@ public final class CompileUnit implements Comparable, Serializable this.weight += w; } - /** - * Get the current weight of the compile unit. - * @return the unit's weight - */ - long getWeight() { - return weight; - } - /** * Check if this compile unit can hold {@code weight} more units of weight * @param w weight to check if can be added @@ -160,7 +154,7 @@ public final class CompileUnit implements Comparable, Serializable } private static String shortName(final String name) { - return name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1); + return name == null ? null : name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1); } @Override diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java index 80846e139dd..196862f0699 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java @@ -32,15 +32,16 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.THIS; import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote; + import java.io.File; import java.lang.invoke.MethodType; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; @@ -159,75 +160,142 @@ public final class Compiler implements Loggable { */ private static final int COMPILE_UNIT_NAME_BUFFER_SIZE = 32; + private final Map serializedAsts = new HashMap<>(); + /** * Compilation phases that a compilation goes through */ public static class CompilationPhases implements Iterable { - /** Singleton that describes a standard eager compilation - this includes code installation */ - public final static CompilationPhases COMPILE_ALL = new CompilationPhases( - "Compile all", - new CompilationPhase[] { - CompilationPhase.CONSTANT_FOLDING_PHASE, - CompilationPhase.LOWERING_PHASE, - CompilationPhase.PROGRAM_POINT_PHASE, - CompilationPhase.TRANSFORM_BUILTINS_PHASE, - CompilationPhase.SPLITTING_PHASE, - CompilationPhase.SYMBOL_ASSIGNMENT_PHASE, - CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE, - CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE, - CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE, - CompilationPhase.BYTECODE_GENERATION_PHASE, - CompilationPhase.INSTALL_PHASE - }); + /** + * Singleton that describes compilation up to the phase where a function can be serialized. + */ + private final static CompilationPhases COMPILE_UPTO_SERIALIZABLE = new CompilationPhases( + "Common initial phases", + CompilationPhase.CONSTANT_FOLDING_PHASE, + CompilationPhase.LOWERING_PHASE, + CompilationPhase.TRANSFORM_BUILTINS_PHASE, + CompilationPhase.SPLITTING_PHASE, + CompilationPhase.PROGRAM_POINT_PHASE, + CompilationPhase.SERIALIZE_SPLIT_PHASE + ); - /** Compile all for a rest of method */ - public final static CompilationPhases COMPILE_ALL_RESTOF = - COMPILE_ALL.setDescription("Compile all, rest of").addAfter(CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE, CompilationPhase.REUSE_COMPILE_UNITS_PHASE); + private final static CompilationPhases COMPILE_SERIALIZABLE_UPTO_BYTECODE = new CompilationPhases( + "After common phases, before bytecode generator", + CompilationPhase.SYMBOL_ASSIGNMENT_PHASE, + CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE, + CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE, + CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE + ); - /** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */ - public final static CompilationPhases COMPILE_ALL_NO_INSTALL = - COMPILE_ALL. - removeLast(). - setDescription("Compile without install"); - - /** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */ - public final static CompilationPhases COMPILE_UPTO_BYTECODE = - COMPILE_ALL. - removeLast(). - removeLast(). - setDescription("Compile upto bytecode"); + /** + * Singleton that describes additional steps to be taken after deserializing, all the way up to (but not + * including) generating and installing code. + */ + public final static CompilationPhases RECOMPILE_SERIALIZED_UPTO_BYTECODE = new CompilationPhases( + "Recompile serialized function up to bytecode", + CompilationPhase.REINITIALIZE_SERIALIZED, + COMPILE_SERIALIZABLE_UPTO_BYTECODE + ); /** * Singleton that describes back end of method generation, given that we have generated the normal * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE} */ - public final static CompilationPhases COMPILE_FROM_BYTECODE = new CompilationPhases( + public final static CompilationPhases GENERATE_BYTECODE_AND_INSTALL = new CompilationPhases( "Generate bytecode and install", - new CompilationPhase[] { - CompilationPhase.BYTECODE_GENERATION_PHASE, - CompilationPhase.INSTALL_PHASE - }); + CompilationPhase.BYTECODE_GENERATION_PHASE, + CompilationPhase.INSTALL_PHASE + ); + + /** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */ + public final static CompilationPhases COMPILE_UPTO_BYTECODE = new CompilationPhases( + "Compile upto bytecode", + COMPILE_UPTO_SERIALIZABLE, + COMPILE_SERIALIZABLE_UPTO_BYTECODE); + + /** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */ + public final static CompilationPhases COMPILE_ALL_NO_INSTALL = new CompilationPhases( + "Compile without install", + COMPILE_UPTO_BYTECODE, + CompilationPhase.BYTECODE_GENERATION_PHASE); + + /** Singleton that describes a standard eager compilation - this includes code installation */ + public final static CompilationPhases COMPILE_ALL = new CompilationPhases( + "Full eager compilation", + COMPILE_UPTO_BYTECODE, + GENERATE_BYTECODE_AND_INSTALL); + + /** Singleton that describes a full compilation - this includes code installation - from serialized state*/ + public final static CompilationPhases COMPILE_ALL_SERIALIZED = new CompilationPhases( + "Eager compilation from serializaed state", + RECOMPILE_SERIALIZED_UPTO_BYTECODE, + GENERATE_BYTECODE_AND_INSTALL); /** * Singleton that describes restOf method generation, given that we have generated the normal * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE} */ - public final static CompilationPhases COMPILE_FROM_BYTECODE_RESTOF = - COMPILE_FROM_BYTECODE. - addFirst(CompilationPhase.REUSE_COMPILE_UNITS_PHASE). - setDescription("Generate bytecode and install - RestOf method"); + public final static CompilationPhases GENERATE_BYTECODE_AND_INSTALL_RESTOF = new CompilationPhases( + "Generate bytecode and install - RestOf method", + CompilationPhase.REUSE_COMPILE_UNITS_PHASE, + GENERATE_BYTECODE_AND_INSTALL); + + /** Compile all for a rest of method */ + public final static CompilationPhases COMPILE_ALL_RESTOF = new CompilationPhases( + "Compile all, rest of", + COMPILE_UPTO_BYTECODE, + GENERATE_BYTECODE_AND_INSTALL_RESTOF); + + /** Compile from serialized for a rest of method */ + public final static CompilationPhases COMPILE_SERIALIZED_RESTOF = new CompilationPhases( + "Compile serialized, rest of", + RECOMPILE_SERIALIZED_UPTO_BYTECODE, + GENERATE_BYTECODE_AND_INSTALL_RESTOF); private final List phases; private final String desc; private CompilationPhases(final String desc, final CompilationPhase... phases) { - this.desc = desc; + this(desc, Arrays.asList(phases)); + } - final List newPhases = new LinkedList<>(); - newPhases.addAll(Arrays.asList(phases)); - this.phases = Collections.unmodifiableList(newPhases); + private CompilationPhases(final String desc, final CompilationPhases base, final CompilationPhase... phases) { + this(desc, concat(base.phases, Arrays.asList(phases))); + } + + private CompilationPhases(final String desc, final CompilationPhase first, final CompilationPhases rest) { + this(desc, concat(Collections.singletonList(first), rest.phases)); + } + + private CompilationPhases(final String desc, final CompilationPhases base) { + this(desc, base.phases); + } + + private CompilationPhases(final String desc, final CompilationPhases... bases) { + this(desc, concatPhases(bases)); + } + + private CompilationPhases(final String desc, final List phases) { + this.desc = desc; + this.phases = phases; + } + + private static List concatPhases(final CompilationPhases[] bases) { + final ArrayList l = new ArrayList<>(); + for(final CompilationPhases base: bases) { + l.addAll(base.phases); + } + l.trimToSize(); + return l; + } + + private static List concat(final List l1, final List l2) { + final ArrayList l = new ArrayList<>(l1); + l.addAll(l2); + l.trimToSize(); + return l; } @Override @@ -235,45 +303,6 @@ public final class Compiler implements Loggable { return "'" + desc + "' " + phases.toString(); } - private CompilationPhases setDescription(final String desc) { - return new CompilationPhases(desc, phases.toArray(new CompilationPhase[phases.size()])); - } - - private CompilationPhases removeLast() { - final LinkedList list = new LinkedList<>(phases); - list.removeLast(); - return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()])); - } - - private CompilationPhases addFirst(final CompilationPhase phase) { - if (phases.contains(phase)) { - return this; - } - final LinkedList list = new LinkedList<>(phases); - list.addFirst(phase); - return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()])); - } - - @SuppressWarnings("unused") //TODO I'll use this soon - private CompilationPhases replace(final CompilationPhase phase, final CompilationPhase newPhase) { - final LinkedList list = new LinkedList<>(); - for (final CompilationPhase p : phases) { - list.add(p == phase ? newPhase : p); - } - return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()])); - } - - private CompilationPhases addAfter(final CompilationPhase phase, final CompilationPhase newPhase) { - final LinkedList list = new LinkedList<>(); - for (final CompilationPhase p : phases) { - list.add(p); - if (p == phase) { - list.add(newPhase); - } - } - return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()])); - } - boolean contains(final CompilationPhase phase) { return phases.contains(phase); } @@ -284,7 +313,7 @@ public final class Compiler implements Loggable { } boolean isRestOfCompilation() { - return this == COMPILE_ALL_RESTOF || this == COMPILE_FROM_BYTECODE_RESTOF; + return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_SERIALIZED_RESTOF; } String getDesc() { @@ -749,6 +778,14 @@ public final class Compiler implements Loggable { compileUnits.addAll(newUnits); } + void serializeAst(final FunctionNode fn) { + serializedAsts.put(fn.getId(), AstSerializer.serialize(fn)); + } + + byte[] removeSerializedAst(final int fnId) { + return serializedAsts.remove(fnId); + } + CompileUnit findUnit(final long weight) { for (final CompileUnit unit : compileUnits) { if (unit.canHold(weight)) { @@ -771,7 +808,10 @@ public final class Compiler implements Loggable { } RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { - return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId); + assert compiledFunction != null; + final RecompilableScriptFunctionData fn = compiledFunction.getScriptFunctionData(functionId); + assert fn != null : functionId; + return fn; } boolean isGlobalSymbol(final FunctionNode fn, final String name) { diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java index 4c1db02ba93..431244dc3c6 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/FindScopeDepths.java @@ -187,7 +187,6 @@ final class FindScopeDepths extends NodeVisitor implements Logga if (compiler.isOnDemandCompilation()) { final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(newFunctionNode.getId()); - assert data != null : newFunctionNode.getName() + " lacks data"; if (data.inDynamicContext()) { log.fine("Reviving scriptfunction ", quote(name), " as defined in previous (now lost) dynamic scope."); newFunctionNode = newFunctionNode.setInDynamicContext(lc); @@ -202,7 +201,7 @@ final class FindScopeDepths extends NodeVisitor implements Logga //create recompilable scriptfunctiondata final int fnId = newFunctionNode.getId(); - final Map nestedFunctions = fnIdToNestedFunctions.get(fnId); + final Map nestedFunctions = fnIdToNestedFunctions.remove(fnId); assert nestedFunctions != null; // Generate the object class and property map in case this function is ever used as constructor @@ -212,8 +211,8 @@ final class FindScopeDepths extends NodeVisitor implements Logga new AllocatorDescriptor(newFunctionNode.getThisProperties()), nestedFunctions, externalSymbolDepths.get(fnId), - internalSymbols.get(fnId) - ); + internalSymbols.get(fnId), + compiler.removeSerializedAst(fnId)); if (lc.getOutermostFunction() != newFunctionNode) { final FunctionNode parentFn = lc.getParentFunction(newFunctionNode); diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java index 491ef2caf13..ea073623c9a 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/LocalVariableTypesCalculator.java @@ -72,7 +72,7 @@ import jdk.nashorn.internal.ir.PropertyNode; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.RuntimeNode.Request; -import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.SplitReturn; import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; @@ -361,10 +361,6 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ // Synthetic return node that we must insert at the end of the function if it's end is reachable. private ReturnNode syntheticReturn; - // Topmost current split node (if any) - private SplitNode topSplit; - private boolean split; - private boolean alreadyEnteredTopLevelFunction; // LvarType and conversion information gathered during the top-down pass; applied to nodes in the bottom-up pass. @@ -477,22 +473,7 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ return false; } final BreakableNode target = jump.getTarget(lc); - return splitAwareJumpToLabel(jump, target, jump.getTargetLabel(target)); - } - - private boolean splitAwareJumpToLabel(final JumpStatement jumpStatement, final BreakableNode target, final Label targetLabel) { - final JoinPredecessor jumpOrigin; - if(topSplit != null && lc.isExternalTarget(topSplit, target)) { - // If the jump target is outside the topmost split node, then we'll create a synthetic jump origin in the - // split node. - jumpOrigin = new JoinPredecessorExpression(); - topSplit.addJump(jumpOrigin, targetLabel); - } else { - // Otherwise, the original jump statement is the jump origin - jumpOrigin = jumpStatement; - } - - jumpToLabel(jumpOrigin, targetLabel, getBreakTargetTypes(target)); + jumpToLabel(jump, jump.getTargetLabel(target), getBreakTargetTypes(target)); doesNotContinueSequentially(); return false; } @@ -703,18 +684,9 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ } @Override - public boolean enterSplitNode(final SplitNode splitNode) { - if(!reachable) { - return false; - } - // Need to visit inside of split nodes. While it's true that they don't have local variables, we need to visit - // breaks, continues, and returns in them. - if(topSplit == null) { - topSplit = splitNode; - } - split = true; - setType(getCompilerConstantSymbol(lc.getCurrentFunction(), CompilerConstants.RETURN), LvarType.UNDEFINED); - return true; + public boolean enterSplitReturn(final SplitReturn splitReturn) { + doesNotContinueSequentially(); + return false; } @Override @@ -1116,15 +1088,6 @@ final class LocalVariableTypesCalculator extends NodeVisitor{ if(returnType.isUnknown()) { returnType = Type.OBJECT; } - - if(split) { - // If the function is split, the ":return" symbol is used and needs a slot. Note we can't mark the return - // symbol as used in enterSplitNode, as we don't know the final return type of the function earlier than - // here. - final Symbol retSymbol = getCompilerConstantSymbol(lc.getCurrentFunction(), CompilerConstants.RETURN); - retSymbol.setHasSlotFor(returnType); - retSymbol.setNeedsSlot(true); - } } private void createSyntheticReturn(final Block body) { diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java index dddf6976891..724c6f1e531 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Lower.java @@ -352,8 +352,6 @@ final class Lower extends NodeOperatorVisitor implements Lo private Node spliceFinally(final TryNode tryNode, final List rethrows, final Block finallyBody) { assert tryNode.getFinallyBody() == null; - final LexicalContext lowerLc = lc; - final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor(new LexicalContext()) { final List insideTry = new ArrayList<>(); @@ -406,7 +404,6 @@ final class Lower extends NodeOperatorVisitor implements Lo //still in the try block, store it in a result value and return it afterwards resultNode = new IdentNode(Lower.this.compilerConstant(RETURN)); newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr))); - lowerLc.setFlag(lowerLc.getCurrentFunction(), FunctionNode.USES_RETURN_SYMBOL); } else { resultNode = null; } diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java index 2df1ae90b88..c6b473f3e54 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/MethodEmitter.java @@ -71,6 +71,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT; + import java.io.PrintStream; import java.lang.reflect.Array; import java.util.Collection; @@ -88,11 +89,9 @@ import jdk.nashorn.internal.codegen.types.ArrayType; import jdk.nashorn.internal.codegen.types.BitwiseType; import jdk.nashorn.internal.codegen.types.NumericType; import jdk.nashorn.internal.codegen.types.Type; -import jdk.nashorn.internal.ir.BreakableNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.IdentNode; import jdk.nashorn.internal.ir.JoinPredecessor; -import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LiteralNode; import jdk.nashorn.internal.ir.LocalVariableConversion; import jdk.nashorn.internal.ir.RuntimeNode; @@ -1662,19 +1661,6 @@ public class MethodEmitter implements Emitter { doesNotContinueSequentially(); } - /** - * Goto, possibly when splitting is taking place. If - * a splitNode exists, we need to handle the case that the - * jump target is another method - * - * @param label destination label - * @param targetNode the node to which the destination label belongs (the label is normally a break or continue - * label) - */ - void splitAwareGoto(final LexicalContext lc, final Label label, final BreakableNode targetNode) { - _goto(label); - } - /** * Perform a comparison of two number types that are popped from the stack * diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java index ac402e935bc..c8029070a8a 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/OptimisticTypesCalculator.java @@ -30,7 +30,6 @@ import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; import java.util.ArrayDeque; import java.util.BitSet; import java.util.Deque; -import jdk.nashorn.internal.IntDeque; import jdk.nashorn.internal.ir.AccessNode; import jdk.nashorn.internal.ir.BinaryNode; import jdk.nashorn.internal.ir.CallNode; @@ -49,7 +48,6 @@ import jdk.nashorn.internal.ir.LoopNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.Optimistic; import jdk.nashorn.internal.ir.PropertyNode; -import jdk.nashorn.internal.ir.SplitNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.TernaryNode; import jdk.nashorn.internal.ir.UnaryNode; @@ -70,8 +68,6 @@ final class OptimisticTypesCalculator extends NodeVisitor { // Per-function bit set of program points that must never be optimistic. final Deque neverOptimistic = new ArrayDeque<>(); - // Per-function depth of split nodes - final IntDeque splitDepth = new IntDeque(); OptimisticTypesCalculator(final Compiler compiler) { super(new LexicalContext()); @@ -155,7 +151,6 @@ final class OptimisticTypesCalculator extends NodeVisitor { return false; } neverOptimistic.push(new BitSet()); - splitDepth.push(0); return true; } @@ -189,19 +184,6 @@ final class OptimisticTypesCalculator extends NodeVisitor { return true; } - @Override - public boolean enterSplitNode(final SplitNode splitNode) { - splitDepth.getAndIncrement(); - return true; - } - - @Override - public Node leaveSplitNode(final SplitNode splitNode) { - final int depth = splitDepth.decrementAndGet(); - assert depth >= 0; - return splitNode; - } - @Override public boolean enterVarNode(final VarNode varNode) { tagNeverOptimistic(varNode.getName()); @@ -226,16 +208,11 @@ final class OptimisticTypesCalculator extends NodeVisitor { @Override public Node leaveFunctionNode(final FunctionNode functionNode) { neverOptimistic.pop(); - final int lastSplitDepth = splitDepth.pop(); - assert lastSplitDepth == 0; return functionNode.setState(lc, CompilationState.OPTIMISTIC_TYPES_ASSIGNED); } @Override public Node leaveIdentNode(final IdentNode identNode) { - if(inSplitNode()) { - return identNode; - } final Symbol symbol = identNode.getSymbol(); if(symbol == null) { assert identNode.isPropertyName(); @@ -256,7 +233,7 @@ final class OptimisticTypesCalculator extends NodeVisitor { private Expression leaveOptimistic(final Optimistic opt) { final int pp = opt.getProgramPoint(); - if(isValid(pp) && !inSplitNode() && !neverOptimistic.peek().get(pp)) { + if(isValid(pp) && !neverOptimistic.peek().get(pp)) { return (Expression)opt.setType(compiler.getOptimisticType(opt)); } return (Expression)opt; @@ -277,8 +254,4 @@ final class OptimisticTypesCalculator extends NodeVisitor { tagNeverOptimistic(test.getExpression()); } } - - private boolean inSplitNode() { - return splitDepth.peek() > 0; - } } diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ProgramPoints.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ProgramPoints.java index c04546b25f2..4606989e49c 100644 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ProgramPoints.java +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ProgramPoints.java @@ -85,7 +85,7 @@ class ProgramPoints extends NodeVisitor { @Override public boolean enterVarNode(final VarNode varNode) { - noProgramPoint.add(varNode.getAssignmentDest()); + noProgramPoint.add(varNode.getName()); return true; } diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ReplaceCompileUnits.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ReplaceCompileUnits.java new file mode 100644 index 00000000000..57f9e681dc5 --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/ReplaceCompileUnits.java @@ -0,0 +1,85 @@ +/* + * 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. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.nashorn.internal.codegen; + +import java.util.ArrayList; +import java.util.List; +import jdk.nashorn.internal.ir.CompileUnitHolder; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.FunctionNode.CompilationState; +import jdk.nashorn.internal.ir.LexicalContext; +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.visitor.NodeVisitor; + +/** + * Base class for a node visitor that replaces {@link CompileUnit}s in {@link CompileUnitHolder}s. + */ +abstract class ReplaceCompileUnits extends NodeVisitor { + ReplaceCompileUnits() { + super(new LexicalContext()); + } + + /** + * Override to provide a replacement for an old compile unit. + * @param oldUnit the old compile unit to replace + * @return the compile unit's replacement. + */ + abstract CompileUnit getReplacement(final CompileUnit oldUnit); + + CompileUnit getExistingReplacement(final CompileUnitHolder node) { + final CompileUnit oldUnit = node.getCompileUnit(); + assert oldUnit != null; + + final CompileUnit newUnit = getReplacement(oldUnit); + assert newUnit != null; + + return newUnit; + } + + @Override + public Node leaveFunctionNode(final FunctionNode node) { + return node.setCompileUnit(lc, getExistingReplacement(node)).setState(lc, CompilationState.COMPILE_UNITS_REUSED); + } + + @Override + public Node leaveLiteralNode(final LiteralNode node) { + if (node instanceof ArrayLiteralNode) { + final ArrayLiteralNode aln = (ArrayLiteralNode)node; + if (aln.getUnits() == null) { + return node; + } + final List newArrayUnits = new ArrayList<>(); + for (final ArrayUnit au : aln.getUnits()) { + newArrayUnits.add(new ArrayUnit(getExistingReplacement(au), au.getLo(), au.getHi())); + } + return aln.setUnits(lc, newArrayUnits); + } + return node; + } +} diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java new file mode 100644 index 00000000000..9b980b5d4c9 --- /dev/null +++ b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitIntoFunctions.java @@ -0,0 +1,450 @@ +/* + * 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. + * + * 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.ir.Node.NO_FINISH; +import static jdk.nashorn.internal.ir.Node.NO_LINE_NUMBER; +import static jdk.nashorn.internal.ir.Node.NO_TOKEN; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.Objects; +import jdk.nashorn.internal.ir.AccessNode; +import jdk.nashorn.internal.ir.BinaryNode; +import jdk.nashorn.internal.ir.Block; +import jdk.nashorn.internal.ir.BlockLexicalContext; +import jdk.nashorn.internal.ir.BreakNode; +import jdk.nashorn.internal.ir.CallNode; +import jdk.nashorn.internal.ir.CaseNode; +import jdk.nashorn.internal.ir.ContinueNode; +import jdk.nashorn.internal.ir.Expression; +import jdk.nashorn.internal.ir.ExpressionStatement; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.FunctionNode.CompilationState; +import jdk.nashorn.internal.ir.GetSplitState; +import jdk.nashorn.internal.ir.IdentNode; +import jdk.nashorn.internal.ir.IfNode; +import jdk.nashorn.internal.ir.JumpStatement; +import jdk.nashorn.internal.ir.LiteralNode; +import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.ReturnNode; +import jdk.nashorn.internal.ir.SetSplitState; +import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.SplitReturn; +import jdk.nashorn.internal.ir.Statement; +import jdk.nashorn.internal.ir.SwitchNode; +import jdk.nashorn.internal.ir.VarNode; +import jdk.nashorn.internal.ir.visitor.NodeVisitor; +import jdk.nashorn.internal.parser.Token; +import jdk.nashorn.internal.parser.TokenType; + +/** + * A node visitor that replaces {@link SplitNode}s with anonymous function invocations and some additional constructs + * to support control flow across splits. By using this transformation, split functions are translated into ordinary + * JavaScript functions with nested anonymous functions. The transformations however introduce several AST nodes that + * have no JavaScript source representations ({@link GetSplitState}, {@link SetSplitState}, and {@link SplitReturn}), + * and therefore such function is no longer reparseable from its source. For that reason, split functions and their + * fragments are serialized in-memory and deserialized when they need to be recompiled either for deoptimization or + * for type specialization. + * NOTE: all {@code leave*()} methods for statements are returning their input nodes. That way, they will not mutate + * the original statement list in the block containing the statement, which is fine, as it'll be replaced by the + * lexical context when the block is left. If we returned something else (e.g. null), we'd cause a mutation in the + * enclosing block's statement list that is otherwise overwritten later anyway. + */ +final class SplitIntoFunctions extends NodeVisitor { + private static final int FALLTHROUGH_STATE = -1; + private static final int RETURN_STATE = 0; + private static final int BREAK_STATE = 1; + private static final int FIRST_JUMP_STATE = 2; + + private static final String THIS_NAME = CompilerConstants.THIS.symbolName(); + private static final String RETURN_NAME = CompilerConstants.RETURN.symbolName(); + // Used as the name of the formal parameter for passing the current value of :return symbol into a split fragment. + private static final String RETURN_PARAM_NAME = RETURN_NAME + "-in"; + + private final Deque functionStates = new ArrayDeque<>(); + private final Deque splitStates = new ArrayDeque<>(); + private final Namespace namespace; + + private boolean artificialBlock = false; + + // -1 is program; we need to use negative ones + private int nextFunctionId = -2; + + public SplitIntoFunctions(final Compiler compiler) { + super(new BlockLexicalContext() { + @Override + protected Block afterSetStatements(Block block) { + for(Statement stmt: block.getStatements()) { + assert !(stmt instanceof SplitNode); + } + return block; + } + }); + namespace = new Namespace(compiler.getScriptEnvironment().getNamespace()); + } + + @Override + public boolean enterFunctionNode(final FunctionNode functionNode) { + functionStates.push(new FunctionState(functionNode)); + return true; + } + + @Override + public Node leaveFunctionNode(final FunctionNode functionNode) { + functionStates.pop(); + return functionNode; + } + + @Override + protected Node leaveDefault(final Node node) { + if (node instanceof Statement) { + appendStatement((Statement)node); + } + return node; + } + + @Override + public boolean enterSplitNode(final SplitNode splitNode) { + getCurrentFunctionState().splitDepth++; + splitStates.push(new SplitState(splitNode)); + return true; + } + + @Override + public Node leaveSplitNode(final SplitNode splitNode) { + // Replace the split node with an anonymous function expression call. + + final FunctionState fnState = getCurrentFunctionState(); + + final String name = splitNode.getName(); + Block body = splitNode.getBody(); + final int firstLineNumber = body.getFirstStatementLineNumber(); + final long token = body.getToken(); + final int finish = body.getFinish(); + + final FunctionNode originalFn = fnState.fn; + assert originalFn == lc.getCurrentFunction(); + final boolean isProgram = originalFn.isProgram(); + + // Change SplitNode({...}) into "function () { ... }", or "function (:return-in) () { ... }" (for program) + final long newFnToken = Token.toDesc(TokenType.FUNCTION, nextFunctionId--, 0); + final FunctionNode fn = new FunctionNode( + originalFn.getSource(), + body.getFirstStatementLineNumber(), + newFnToken, + finish, + newFnToken, + NO_TOKEN, + namespace, + createIdent(name), + originalFn.getName() + "$" + name, + isProgram ? Collections.singletonList(createReturnParamIdent()) : Collections.emptyList(), + FunctionNode.Kind.NORMAL, + // We only need IS_SPLIT conservatively, in case it contains any array units so that we force + // the :callee's existence, to force :scope to never be in a slot lower than 2. This is actually + // quite a horrible hack to do with CodeGenerator.fixScopeSlot not trampling other parameters + // and should go away once we no longer have array unit handling in codegen. Note however that + // we still use IS_SPLIT as the criteria in CompilationPhase.SERIALIZE_SPLIT_PHASE. + FunctionNode.IS_ANONYMOUS | FunctionNode.USES_ANCESTOR_SCOPE | FunctionNode.IS_SPLIT, + body, + CompilationState.INITIALIZED, + null + ) + .setCompileUnit(lc, splitNode.getCompileUnit()) + .copyCompilationState(lc, originalFn); + + // Call the function: + // either "(function () { ... }).call(this)" + // or "(function (:return-in) { ... }).call(this, :return)" + // NOTE: Function.call() has optimized linking that basically does a pass-through to the function being invoked. + // NOTE: CompilationPhase.PROGRAM_POINT_PHASE happens after this, so these calls are subject to optimistic + // assumptions on their return value (when they return a value), as they should be. + final IdentNode thisIdent = createIdent(THIS_NAME); + final CallNode callNode = new CallNode(firstLineNumber, token, finish, new AccessNode(NO_TOKEN, NO_FINISH, fn, "call"), + isProgram ? Arrays.asList(thisIdent, createReturnIdent()) + : Collections.singletonList(thisIdent), + false); + + final SplitState splitState = splitStates.pop(); + fnState.splitDepth--; + + final Expression callWithReturn; + final boolean hasReturn = splitState.hasReturn; + if (hasReturn && fnState.splitDepth > 0) { + final SplitState parentSplit = splitStates.peek(); + if (parentSplit != null) { + // Propagate hasReturn to parent split + parentSplit.hasReturn = true; + } + } + if (hasReturn || isProgram) { + // capture return value: ":return = (function () { ... })();" + callWithReturn = new BinaryNode(Token.recast(token, TokenType.ASSIGN), createReturnIdent(), callNode); + } else { + // no return value, just call : "(function () { ... })();" + callWithReturn = callNode; + } + appendStatement(new ExpressionStatement(firstLineNumber, token, finish, callWithReturn)); + + Statement splitStateHandler; + + final List jumpStatements = splitState.jumpStatements; + final int jumpCount = jumpStatements.size(); + // There are jumps (breaks or continues) that need to be propagated outside the split node. We need to + // set up a switch statement for them: + // switch(:scope.getScopeState()) { ... } + if (jumpCount > 0) { + final List cases = new ArrayList<>(jumpCount + (hasReturn ? 1 : 0)); + if (hasReturn) { + // If the split node also contained a return, we'll slip it as a case in the switch statement + addCase(cases, RETURN_STATE, createReturnFromSplit()); + } + int i = FIRST_JUMP_STATE; + for (final JumpStatement jump: jumpStatements) { + addCase(cases, i++, enblockAndVisit(jump)); + } + splitStateHandler = new SwitchNode(NO_LINE_NUMBER, token, finish, GetSplitState.INSTANCE, cases, null); + } else { + splitStateHandler = null; + } + + // As the switch statement itself is breakable, an unlabelled break can't be in the switch statement, + // so we need to test for it separately. + if (splitState.hasBreak) { + // if(:scope.getScopeState() == Scope.BREAK) { break; } + splitStateHandler = makeIfStateEquals(firstLineNumber, token, finish, BREAK_STATE, + enblockAndVisit(new BreakNode(NO_LINE_NUMBER, token, finish, null)), splitStateHandler); + } + + // Finally, if the split node had a return statement, but there were no external jumps, we didn't have + // the switch statement to handle the return, so we need a separate if for it. + if (hasReturn && jumpCount == 0) { + // if (:scope.getScopeState() == Scope.RETURN) { return :return; } + splitStateHandler = makeIfStateEquals(NO_LINE_NUMBER, token, finish, RETURN_STATE, + createReturnFromSplit(), splitStateHandler); + } + + if (splitStateHandler != null) { + appendStatement(splitStateHandler); + } + + return splitNode; + } + + private static void addCase(final List cases, final int i, final Block body) { + cases.add(new CaseNode(NO_TOKEN, NO_FINISH, intLiteral(i), body)); + } + + private static LiteralNode intLiteral(final int i) { + return LiteralNode.newInstance(NO_TOKEN, NO_FINISH, i); + } + + private static Block createReturnFromSplit() { + return new Block(NO_TOKEN, NO_FINISH, createReturnReturn()); + } + + private static ReturnNode createReturnReturn() { + return new ReturnNode(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, createReturnIdent()); + } + + private static IdentNode createReturnIdent() { + return createIdent(RETURN_NAME); + } + + private static IdentNode createReturnParamIdent() { + return createIdent(RETURN_PARAM_NAME); + } + + private static IdentNode createIdent(final String name) { + return new IdentNode(NO_TOKEN, NO_FINISH, name); + } + + private Block enblockAndVisit(final JumpStatement jump) { + artificialBlock = true; + final Block block = (Block)new Block(NO_TOKEN, NO_FINISH, jump).accept(this); + artificialBlock = false; + return block; + } + + private static IfNode makeIfStateEquals(final int lineNumber, final long token, final int finish, + final int value, final Block pass, final Statement fail) { + return new IfNode(lineNumber, token, finish, + new BinaryNode(Token.recast(token, TokenType.EQ_STRICT), + GetSplitState.INSTANCE, intLiteral(value)), + pass, + fail == null ? null : new Block(NO_TOKEN, NO_FINISH, fail)); + } + + @Override + public boolean enterVarNode(VarNode varNode) { + if (!inSplitNode()) { + return super.enterVarNode(varNode); + } + assert !varNode.isBlockScoped(); //TODO: we must handle these too, but we currently don't + + final Expression init = varNode.getInit(); + if (varNode.isAnonymousFunctionDeclaration()) { + // We ain't moving anonymous function declarations. + return super.enterVarNode(varNode); + } + + // Move a declaration-only var statement to the top of the outermost function. + getCurrentFunctionState().varStatements.add(varNode.setInit(null)); + // If it had an initializer, replace it with an assignment expression statement. Note that "var" is a + // statement, so it doesn't contribute to :return of the programs, therefore we are _not_ adding a + // ":return = ..." assignment around the original assignment. + if (init != null) { + final long token = Token.recast(varNode.getToken(), TokenType.ASSIGN); + new ExpressionStatement(varNode.getLineNumber(), token, varNode.getFinish(), + new BinaryNode(token, varNode.getName(), varNode.getInit())).accept(this); + } + + return false; + } + + @Override + public Node leaveBlock(final Block block) { + if (!artificialBlock) { + if (lc.isFunctionBody()) { + // Prepend declaration-only var statements to the top of the statement list. + lc.prependStatements(getCurrentFunctionState().varStatements); + } else if (lc.isSplitBody()) { + appendSplitReturn(FALLTHROUGH_STATE, NO_LINE_NUMBER); + if (getCurrentFunctionState().fn.isProgram()) { + // If we're splitting the program, make sure every shard ends with "return :return" and + // begins with ":return = :return-in;". + lc.prependStatement(new ExpressionStatement(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH, + new BinaryNode(Token.toDesc(TokenType.ASSIGN, 0, 0), createReturnIdent(), createReturnParamIdent()))); + } + } + } + return block; + } + + @Override + public Node leaveBreakNode(final BreakNode breakNode) { + return leaveJumpNode(breakNode); + } + + @Override + public Node leaveContinueNode(final ContinueNode continueNode) { + return leaveJumpNode(continueNode); + } + + private JumpStatement leaveJumpNode(final JumpStatement jump) { + if (inSplitNode()) { + final SplitState splitState = getCurrentSplitState(); + final SplitNode splitNode = splitState.splitNode; + if (lc.isExternalTarget(splitNode, jump.getTarget(lc))) { + appendSplitReturn(splitState.getSplitStateIndex(jump), jump.getLineNumber()); + return jump; + } + } + appendStatement(jump); + return jump; + } + + private void appendSplitReturn(final int splitState, final int lineNumber) { + appendStatement(new SetSplitState(splitState, lineNumber)); + if (getCurrentFunctionState().fn.isProgram()) { + // If we're splitting the program, make sure every fragment passes back :return + appendStatement(createReturnReturn()); + } else { + appendStatement(SplitReturn.INSTANCE); + } + } + + @Override + public Node leaveReturnNode(final ReturnNode returnNode) { + if(inSplitNode()) { + appendStatement(new SetSplitState(RETURN_STATE, returnNode.getLineNumber())); + getCurrentSplitState().hasReturn = true; + } + appendStatement(returnNode); + return returnNode; + } + + private void appendStatement(final Statement statement) { + lc.appendStatement(statement); + } + + private boolean inSplitNode() { + return getCurrentFunctionState().splitDepth > 0; + } + + private FunctionState getCurrentFunctionState() { + return functionStates.peek(); + } + + private SplitState getCurrentSplitState() { + return splitStates.peek(); + } + + private static class FunctionState { + final FunctionNode fn; + final List varStatements = new ArrayList<>(); + int splitDepth; + + FunctionState(final FunctionNode fn) { + this.fn = fn; + } + } + + private static class SplitState { + final SplitNode splitNode; + boolean hasReturn; + boolean hasBreak; + + final List jumpStatements = new ArrayList<>(); + + int getSplitStateIndex(final JumpStatement jump) { + if (jump instanceof BreakNode && jump.getLabelName() == null) { + // Unlabelled break is a special case + hasBreak = true; + return BREAK_STATE; + } + + int i = 0; + for(final JumpStatement exJump: jumpStatements) { + if (jump.getClass() == exJump.getClass() && Objects.equals(jump.getLabelName(), exJump.getLabelName())) { + return i + FIRST_JUMP_STATE; + } + ++i; + } + jumpStatements.add(jump); + return i + FIRST_JUMP_STATE; + } + + SplitState(final SplitNode splitNode) { + this.splitNode = splitNode; + } + } +} diff --git a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitMethodEmitter.java b/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitMethodEmitter.java deleted file mode 100644 index 792a6255859..00000000000 --- a/nashorn/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/SplitMethodEmitter.java +++ /dev/null @@ -1,102 +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 java.util.ArrayList; -import java.util.List; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.nashorn.internal.codegen.types.Type; -import jdk.nashorn.internal.ir.BreakableNode; -import jdk.nashorn.internal.ir.LexicalContext; -import jdk.nashorn.internal.ir.SplitNode; - -/** - * Emitter used for splitting methods. Needs to keep track of if there are jump targets - * outside the current split node. All external jump targets encountered at method - * emission are logged, and {@code CodeGenerator#leaveSplitNode(SplitNode)} creates - * an appropriate jump table when the SplitNode has been iterated through - */ -public class SplitMethodEmitter extends MethodEmitter { - - private final SplitNode splitNode; - - private final List