8059844: Implement optimistic splitter
Reviewed-by: hannesw, lagergren
This commit is contained in:
parent
aeed202b0b
commit
7bb8d39499
@ -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<LexicalContext> imple
|
||||
|
||||
explodedArguments.pop();
|
||||
|
||||
return newFunctionNode;
|
||||
return newFunctionNode.setState(lc, CompilationState.BUILTINS_TRANSFORMED);
|
||||
}
|
||||
|
||||
private static boolean isApply(final CallNode callNode) {
|
||||
|
@ -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<LexicalContext> 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<LexicalContext> implements Loggabl
|
||||
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)) {
|
||||
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;
|
||||
|
@ -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<LexicalContext>(new LexicalContext()) {
|
||||
@Override
|
||||
public Node leaveBlock(final Block block) {
|
||||
if (lc.isFunctionBody() && lc.getFunction(block) != lc.getOutermostFunction()) {
|
||||
return block.setStatements(lc, Collections.<Statement>emptyList());
|
||||
}
|
||||
return super.leaveBlock(block);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@ -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
|
||||
*
|
||||
|
@ -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<CodeGeneratorLexicalContex
|
||||
//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());
|
||||
final int externalDepth = compiler.getScriptFunctionData(fn.getId()).getExternalSymbolDepth(symbol.getName());
|
||||
|
||||
//count the number of scopes from this place to the start of the function
|
||||
|
||||
@ -1047,6 +1045,13 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterGetSplitState(final GetSplitState getSplitState) {
|
||||
method.loadScope();
|
||||
method.invoke(Scope.GET_SPLIT_STATE);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterDefault(final Node otherNode) {
|
||||
// Must have handled all expressions that can legally be encountered.
|
||||
@ -1219,7 +1224,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
popScopesUntil(target);
|
||||
final Label targetLabel = jump.getTargetLabel(target);
|
||||
targetLabel.markAsBreakTarget();
|
||||
method.splitAwareGoto(lc, targetLabel, target);
|
||||
method._goto(targetLabel);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -2029,10 +2034,10 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
}
|
||||
|
||||
private void lineNumber(final int lineNumber) {
|
||||
if (lineNumber != lastLineNumber) {
|
||||
if (lineNumber != lastLineNumber && lineNumber != Node.NO_LINE_NUMBER) {
|
||||
method.lineNumber(lineNumber);
|
||||
lastLineNumber = lineNumber;
|
||||
}
|
||||
lastLineNumber = lineNumber;
|
||||
}
|
||||
|
||||
int getLastLineNumber() {
|
||||
@ -2079,13 +2084,14 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
method.begin();
|
||||
|
||||
defineCommonSplitMethodParameters();
|
||||
defineSplitMethodParameter(3, arrayType);
|
||||
defineSplitMethodParameter(CompilerConstants.SPLIT_ARRAY_ARG.slot(), arrayType);
|
||||
|
||||
fixScopeSlot(currentFunction);
|
||||
// NOTE: when this is no longer needed, SplitIntoFunctions will no longer have to add IS_SPLIT
|
||||
// to synthetic functions, and FunctionNode.needsCallee() will no longer need to test for isSplit().
|
||||
final int arraySlot = fixScopeSlot(currentFunction, 3);
|
||||
|
||||
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]);
|
||||
@ -2700,73 +2706,6 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
|
||||
method.convert(newRuntimeNode.getType());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterSplitNode(final SplitNode splitNode) {
|
||||
if(!method.isReachable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final CompileUnit splitCompileUnit = splitNode.getCompileUnit();
|
||||
|
||||
final FunctionNode fn = lc.getCurrentFunction();
|
||||
final String className = splitCompileUnit.getUnitClassName();
|
||||
final String name = splitNode.getName();
|
||||
|
||||
final Type returnType = fn.getReturnType();
|
||||
|
||||
final Class<?> 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<CodeGeneratorLexicalContex
|
||||
method.onLocalStore(type, slot);
|
||||
}
|
||||
|
||||
private void fixScopeSlot(final FunctionNode functionNode) {
|
||||
private int fixScopeSlot(final FunctionNode functionNode, final int extraSlot) {
|
||||
// TODO hack to move the scope to the expected slot (needed because split methods reuse the same slots as the root method)
|
||||
final int actualScopeSlot = functionNode.compilerConstant(SCOPE).getSlot(SCOPE_TYPE);
|
||||
final int defaultScopeSlot = SCOPE.slot();
|
||||
int newExtraSlot = extraSlot;
|
||||
if (actualScopeSlot != defaultScopeSlot) {
|
||||
method.defineBlockLocalVariable(actualScopeSlot, actualScopeSlot + 1);
|
||||
if (actualScopeSlot == extraSlot) {
|
||||
newExtraSlot = extraSlot + 1;
|
||||
method.defineBlockLocalVariable(newExtraSlot, newExtraSlot + 1);
|
||||
method.load(Type.OBJECT, extraSlot);
|
||||
method.storeHidden(Type.OBJECT, newExtraSlot);
|
||||
} else {
|
||||
method.defineBlockLocalVariable(actualScopeSlot, actualScopeSlot + 1);
|
||||
}
|
||||
method.load(SCOPE_TYPE, defaultScopeSlot);
|
||||
method.storeCompilerConstant(SCOPE);
|
||||
}
|
||||
return newExtraSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveSplitNode(final SplitNode splitNode) {
|
||||
assert method instanceof SplitMethodEmitter;
|
||||
lc.exitSplitNode();
|
||||
final boolean hasReturn = method.hasReturn();
|
||||
final SplitMethodEmitter splitMethod = ((SplitMethodEmitter)method);
|
||||
final List<Label> targets = splitMethod.getExternalTargets();
|
||||
final boolean hasControlFlow = hasReturn || !targets.isEmpty();
|
||||
final List<BreakableNode> 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<CodeGeneratorLexicalContex
|
||||
private void newFunctionObject(final FunctionNode functionNode, final boolean addInitializer) {
|
||||
assert lc.peek() == functionNode;
|
||||
|
||||
final int fnId = functionNode.getId();
|
||||
|
||||
final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(fnId);
|
||||
|
||||
assert data != null : functionNode.getName() + " has no data";
|
||||
final RecompilableScriptFunctionData data = compiler.getScriptFunctionData(functionNode.getId());
|
||||
|
||||
if (functionNode.isProgram() && !compiler.isOnDemandCompilation()) {
|
||||
final CompileUnit fnUnit = functionNode.getCompileUnit();
|
||||
|
@ -38,12 +38,11 @@ import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SCOPE_DEPTHS
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SYMBOLS_ASSIGNED;
|
||||
import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
@ -53,10 +52,7 @@ 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.SplitNode;
|
||||
import jdk.nashorn.internal.ir.debug.ASTWriter;
|
||||
import jdk.nashorn.internal.ir.debug.PrintVisitor;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
@ -81,7 +77,7 @@ enum CompilationPhase {
|
||||
PARSED)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
|
||||
return (FunctionNode)fn.accept(new FoldConstants(compiler));
|
||||
return transformFunction(fn, new FoldConstants(compiler));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -104,7 +100,7 @@ enum CompilationPhase {
|
||||
CONSTANT_FOLDED)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
|
||||
return (FunctionNode)fn.accept(new Lower(compiler));
|
||||
return transformFunction(fn, new Lower(compiler));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,23 +114,6 @@ enum CompilationPhase {
|
||||
* optimistic ops a program point so that an UnwarrantedException knows from where
|
||||
* a guess went wrong when creating the continuation to roll back this execution
|
||||
*/
|
||||
PROGRAM_POINT_PHASE(
|
||||
EnumSet.of(
|
||||
INITIALIZED,
|
||||
PARSED,
|
||||
CONSTANT_FOLDED,
|
||||
LOWERED)) {
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
|
||||
return (FunctionNode)fn.accept(new ProgramPoints());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "'Program Point Calculation'";
|
||||
}
|
||||
},
|
||||
|
||||
TRANSFORM_BUILTINS_PHASE(
|
||||
EnumSet.of(
|
||||
INITIALIZED,
|
||||
@ -144,13 +123,7 @@ enum CompilationPhase {
|
||||
//we only do this if we have a param type map, otherwise this is not a specialized recompile
|
||||
@Override
|
||||
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
|
||||
final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new ApplySpecialization(compiler));
|
||||
return (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(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<LexicalContext>(new LexicalContext()) {
|
||||
newFunctionNode = transformFunction(fn, new NodeVisitor<LexicalContext>(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<LexicalContext>(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<LexicalContext>(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<ArrayUnit> 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<CompileUnit> unitSet = CompileUnit.createCompileUnitSet();
|
||||
final Map<CompileUnit, CompileUnit> 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<CompileUnit> unitSet,
|
||||
final Map<CompileUnit, CompileUnit> 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<LexicalContext>(new LexicalContext()) {
|
||||
return transformFunction(functionNode, new NodeVisitor<LexicalContext>(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);
|
||||
}
|
||||
}
|
||||
|
@ -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<CompileUnit>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
@ -126,14 +128,6 @@ public final class CompileUnit implements Comparable<CompileUnit>, 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<CompileUnit>, 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
|
||||
|
@ -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<Integer, byte[]> serializedAsts = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Compilation phases that a compilation goes through
|
||||
*/
|
||||
public static class CompilationPhases implements Iterable<CompilationPhase> {
|
||||
|
||||
/** 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<CompilationPhase> phases;
|
||||
|
||||
private final String desc;
|
||||
|
||||
private CompilationPhases(final String desc, final CompilationPhase... phases) {
|
||||
this.desc = desc;
|
||||
this(desc, Arrays.asList(phases));
|
||||
}
|
||||
|
||||
final List<CompilationPhase> 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<CompilationPhase> phases) {
|
||||
this.desc = desc;
|
||||
this.phases = phases;
|
||||
}
|
||||
|
||||
private static List<CompilationPhase> concatPhases(final CompilationPhases[] bases) {
|
||||
final ArrayList<CompilationPhase> l = new ArrayList<>();
|
||||
for(final CompilationPhases base: bases) {
|
||||
l.addAll(base.phases);
|
||||
}
|
||||
l.trimToSize();
|
||||
return l;
|
||||
}
|
||||
|
||||
private static <T> List<T> concat(final List<T> l1, final List<T> l2) {
|
||||
final ArrayList<T> 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<CompilationPhase> 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<CompilationPhase> 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<CompilationPhase> 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<CompilationPhase> 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) {
|
||||
|
@ -187,7 +187,6 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> 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<LexicalContext> implements Logga
|
||||
|
||||
//create recompilable scriptfunctiondata
|
||||
final int fnId = newFunctionNode.getId();
|
||||
final Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.get(fnId);
|
||||
final Map<Integer, RecompilableScriptFunctionData> 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<LexicalContext> 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);
|
||||
|
@ -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<LexicalContext>{
|
||||
// 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<LexicalContext>{
|
||||
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<LexicalContext>{
|
||||
}
|
||||
|
||||
@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<LexicalContext>{
|
||||
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) {
|
||||
|
@ -352,8 +352,6 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Lo
|
||||
private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
|
||||
assert tryNode.getFinallyBody() == null;
|
||||
|
||||
final LexicalContext lowerLc = lc;
|
||||
|
||||
final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
|
||||
final List<Node> insideTry = new ArrayList<>();
|
||||
|
||||
@ -406,7 +404,6 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> 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;
|
||||
}
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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<LexicalContext> {
|
||||
|
||||
// Per-function bit set of program points that must never be optimistic.
|
||||
final Deque<BitSet> 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<LexicalContext> {
|
||||
return false;
|
||||
}
|
||||
neverOptimistic.push(new BitSet());
|
||||
splitDepth.push(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -189,19 +184,6 @@ final class OptimisticTypesCalculator extends NodeVisitor<LexicalContext> {
|
||||
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<LexicalContext> {
|
||||
@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<LexicalContext> {
|
||||
|
||||
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<LexicalContext> {
|
||||
tagNeverOptimistic(test.getExpression());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean inSplitNode() {
|
||||
return splitDepth.peek() > 0;
|
||||
}
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class ProgramPoints extends NodeVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public boolean enterVarNode(final VarNode varNode) {
|
||||
noProgramPoint.add(varNode.getAssignmentDest());
|
||||
noProgramPoint.add(varNode.getName());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -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<LexicalContext> {
|
||||
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<ArrayUnit> 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;
|
||||
}
|
||||
}
|
@ -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<BlockLexicalContext> {
|
||||
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<FunctionState> functionStates = new ArrayDeque<>();
|
||||
private final Deque<SplitState> 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.<IdentNode>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.<Expression>asList(thisIdent, createReturnIdent())
|
||||
: Collections.<Expression>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<JumpStatement> 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<CaseNode> 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<CaseNode> cases, final int i, final Block body) {
|
||||
cases.add(new CaseNode(NO_TOKEN, NO_FINISH, intLiteral(i), body));
|
||||
}
|
||||
|
||||
private static LiteralNode<Number> 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<Statement> 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<JumpStatement> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Label> externalTargets = new ArrayList<>();
|
||||
/**
|
||||
* In addition to external target labels, we need to track the target breakables too as the code generator needs to
|
||||
* be able to correctly pop the scopes to the target, see {@link CodeGenerator#leaveSplitNode(SplitNode)}. Note that
|
||||
* this is only used within CodeGenerator, which doesn't mutate the AST, so keeping pointers to other nodes is not
|
||||
* incorrect.
|
||||
*/
|
||||
private final List<BreakableNode> externalTargetNodes = new ArrayList<>();
|
||||
|
||||
SplitMethodEmitter(final ClassEmitter classEmitter, final MethodVisitor mv, final SplitNode splitNode) {
|
||||
super(classEmitter, mv);
|
||||
this.splitNode = splitNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
void splitAwareGoto(final LexicalContext lc, final Label label, final BreakableNode targetNode) {
|
||||
assert splitNode != null;
|
||||
final int index = findExternalTarget(lc, label, targetNode);
|
||||
if (index >= 0) {
|
||||
setSplitState(index + 1); // 0 is ordinary return
|
||||
final Type retType = functionNode.getReturnType();
|
||||
loadUndefined(retType);
|
||||
_return(retType);
|
||||
} else {
|
||||
super.splitAwareGoto(lc, label, targetNode);
|
||||
}
|
||||
}
|
||||
|
||||
private int findExternalTarget(final LexicalContext lc, final Label label, final BreakableNode targetNode) {
|
||||
final int index = externalTargets.indexOf(label);
|
||||
|
||||
if (index >= 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
if (lc.isExternalTarget(splitNode, targetNode)) {
|
||||
externalTargets.add(label);
|
||||
externalTargetNodes.add(targetNode);
|
||||
return externalTargets.size() - 1;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
MethodEmitter registerReturn() {
|
||||
setHasReturn();
|
||||
return setSplitState(0);
|
||||
}
|
||||
|
||||
final List<Label> getExternalTargets() {
|
||||
return externalTargets;
|
||||
}
|
||||
|
||||
final List<BreakableNode> getExternalTargetNodes() {
|
||||
return externalTargetNodes;
|
||||
}
|
||||
}
|
@ -108,6 +108,16 @@ public class BlockLexicalContext extends LexicalContext {
|
||||
return statement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a list of statement to the block being generated
|
||||
* @param statements a list of statements to prepend
|
||||
*/
|
||||
public void prependStatements(final List<Statement> statements) {
|
||||
assert statements != null;
|
||||
sstack.peek().addAll(0, statements);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the last statement that was emitted into a block
|
||||
* @return the last statement emitted
|
||||
|
@ -31,6 +31,7 @@ import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALL
|
||||
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_ENTEREXIT;
|
||||
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_MISSES;
|
||||
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_TRACE_VALUES;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Iterator;
|
||||
@ -109,7 +110,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
|
||||
/** Source of entity. */
|
||||
private final Source source;
|
||||
private transient final Source source;
|
||||
|
||||
/**
|
||||
* Opaque object representing parser state at the end of the function. Used when reparsing outer functions
|
||||
@ -143,7 +144,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
private final long lastToken;
|
||||
|
||||
/** Method's namespace. */
|
||||
private final Namespace namespace;
|
||||
private transient final Namespace namespace;
|
||||
|
||||
/** Current compilation state */
|
||||
@Ignore
|
||||
@ -209,31 +210,23 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
/** Are we vararg, but do we just pass the arguments along to apply or call */
|
||||
public static final int HAS_APPLY_TO_CALL_SPECIALIZATION = 1 << 12;
|
||||
|
||||
/** Does this function explicitly use the {@link CompilerConstants#RETURN} symbol? Some functions are known to
|
||||
* always use the return symbol, namely a function that is a program (as it must track its last executed expression
|
||||
* statement's value) as well as functions that are split (to communicate return values from inner to outer
|
||||
* partitions). Other functions normally don't use the return symbol (so we optimize away its slot), except in some
|
||||
* very special cases, e.g. when containing a return statement in a finally block. These special cases set this
|
||||
* flag. */
|
||||
public static final int USES_RETURN_SYMBOL = 1 << 13;
|
||||
|
||||
/**
|
||||
* Is this function the top-level program?
|
||||
*/
|
||||
public static final int IS_PROGRAM = 1 << 14;
|
||||
public static final int IS_PROGRAM = 1 << 13;
|
||||
|
||||
/**
|
||||
* Flag indicating whether this function uses the local variable symbol for itself. Only named function expressions
|
||||
* can have this flag set if they reference themselves (e.g. "(function f() { return f })". Declared functions will
|
||||
* use the symbol in their parent scope instead when they reference themselves by name.
|
||||
*/
|
||||
public static final int USES_SELF_SYMBOL = 1 << 15;
|
||||
public static final int USES_SELF_SYMBOL = 1 << 14;
|
||||
|
||||
/** Does this function use the "this" keyword? */
|
||||
public static final int USES_THIS = 1 << 16;
|
||||
public static final int USES_THIS = 1 << 15;
|
||||
|
||||
/** Is this declared in a dynamic context */
|
||||
public static final int IN_DYNAMIC_CONTEXT = 1 << 17;
|
||||
public static final int IN_DYNAMIC_CONTEXT = 1 << 16;
|
||||
|
||||
/**
|
||||
* The following flags are derived from directive comments within this function.
|
||||
@ -241,28 +234,28 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
*/
|
||||
|
||||
/** parser, print parse tree */
|
||||
public static final int IS_PRINT_PARSE = 1 << 18;
|
||||
public static final int IS_PRINT_PARSE = 1 << 17;
|
||||
/** parser, print lower parse tree */
|
||||
public static final int IS_PRINT_LOWER_PARSE = 1 << 19;
|
||||
public static final int IS_PRINT_LOWER_PARSE = 1 << 18;
|
||||
/** parser, print AST */
|
||||
public static final int IS_PRINT_AST = 1 << 20;
|
||||
public static final int IS_PRINT_AST = 1 << 19;
|
||||
/** parser, print lower AST */
|
||||
public static final int IS_PRINT_LOWER_AST = 1 << 21;
|
||||
public static final int IS_PRINT_LOWER_AST = 1 << 20;
|
||||
/** parser, print symbols */
|
||||
public static final int IS_PRINT_SYMBOLS = 1 << 22;
|
||||
public static final int IS_PRINT_SYMBOLS = 1 << 21;
|
||||
|
||||
// callsite tracing, profiling within this function
|
||||
/** profile callsites in this function? */
|
||||
public static final int IS_PROFILE = 1 << 23;
|
||||
public static final int IS_PROFILE = 1 << 22;
|
||||
|
||||
/** trace callsite enterexit in this function? */
|
||||
public static final int IS_TRACE_ENTEREXIT = 1 << 24;
|
||||
public static final int IS_TRACE_ENTEREXIT = 1 << 23;
|
||||
|
||||
/** trace callsite misses in this function? */
|
||||
public static final int IS_TRACE_MISSES = 1 << 25;
|
||||
public static final int IS_TRACE_MISSES = 1 << 24;
|
||||
|
||||
/** trace callsite values in this function? */
|
||||
public static final int IS_TRACE_VALUES = 1 << 26;
|
||||
public static final int IS_TRACE_VALUES = 1 << 25;
|
||||
|
||||
/**
|
||||
* Whether this function needs the callee {@link ScriptFunction} instance passed to its code as a
|
||||
@ -270,7 +263,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* Rather, it is always calculated (see {@link #needsCallee()}). {@link RecompilableScriptFunctionData}
|
||||
* will, however, cache the value of this flag.
|
||||
*/
|
||||
public static final int NEEDS_CALLEE = 1 << 27;
|
||||
public static final int NEEDS_CALLEE = 1 << 26;
|
||||
|
||||
/** extension callsite flags mask */
|
||||
public static final int EXTENSION_CALLSITE_FLAGS = IS_PRINT_PARSE |
|
||||
@ -287,8 +280,8 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
/** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */
|
||||
private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL;
|
||||
|
||||
/** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep eval. */
|
||||
private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL;
|
||||
/** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep eval, or it's the program. */
|
||||
public static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL | IS_PROGRAM;
|
||||
|
||||
/** What is the return type of this function? */
|
||||
private Type returnType = Type.UNKNOWN;
|
||||
@ -360,7 +353,8 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
final Block body,
|
||||
final List<IdentNode> parameters,
|
||||
final int thisProperties,
|
||||
final Class<?> rootClass) {
|
||||
final Class<?> rootClass,
|
||||
final Source source, Namespace namespace) {
|
||||
super(functionNode);
|
||||
|
||||
this.endParserState = endParserState;
|
||||
@ -375,11 +369,11 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this.parameters = parameters;
|
||||
this.thisProperties = thisProperties;
|
||||
this.rootClass = rootClass;
|
||||
this.source = source;
|
||||
this.namespace = namespace;
|
||||
|
||||
// the fields below never change - they are final and assigned in constructor
|
||||
this.source = functionNode.source;
|
||||
this.ident = functionNode.ident;
|
||||
this.namespace = functionNode.namespace;
|
||||
this.kind = functionNode.kind;
|
||||
this.firstToken = functionNode.firstToken;
|
||||
}
|
||||
@ -444,6 +438,39 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the source and namespace for this function. It can only set a non-null source and namespace for a function
|
||||
* that currently has both a null source and a null namespace. This is used to re-set the source and namespace for
|
||||
* a deserialized function node.
|
||||
* @param source the source for the function.
|
||||
* @param namespace the namespace for the function
|
||||
* @return a new function node with the set source and namespace
|
||||
* @throws IllegalArgumentException if the specified source or namespace is null
|
||||
* @throws IllegalStateException if the function already has either a source or namespace set.
|
||||
*/
|
||||
public FunctionNode initializeDeserialized(final Source source, final Namespace namespace) {
|
||||
if (source == null || namespace == null) {
|
||||
throw new IllegalArgumentException();
|
||||
} else if (this.source == source && this.namespace == namespace) {
|
||||
return this;
|
||||
} else if (this.source != null || this.namespace != null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return new FunctionNode(
|
||||
this,
|
||||
lastToken,
|
||||
endParserState,
|
||||
flags,
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
compilationState,
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass, source, namespace);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique ID for this function within the script file.
|
||||
* @return the id
|
||||
@ -545,6 +572,28 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState);
|
||||
newState.add(state);
|
||||
return setCompilationState(lc, newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a compilation state from an original function to this function. Used when creating synthetic
|
||||
* function nodes by the splitter.
|
||||
*
|
||||
* @param lc lexical context
|
||||
* @param original the original function node to copy compilation state from
|
||||
* @return function node or a new one if state was changed
|
||||
*/
|
||||
public FunctionNode copyCompilationState(final LexicalContext lc, final FunctionNode original) {
|
||||
final EnumSet<CompilationState> origState = original.compilationState;
|
||||
if (!AssertsEnabled.assertsEnabled() || this.compilationState.containsAll(origState)) {
|
||||
return this;
|
||||
}
|
||||
final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState);
|
||||
newState.addAll(origState);
|
||||
return setCompilationState(lc, newState);
|
||||
}
|
||||
|
||||
private FunctionNode setCompilationState(final LexicalContext lc, final EnumSet<CompilationState> compilationState) {
|
||||
return Node.replaceInLexicalContext(
|
||||
lc,
|
||||
this,
|
||||
@ -556,13 +605,14 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
name,
|
||||
returnType,
|
||||
compileUnit,
|
||||
newState,
|
||||
compilationState,
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
rootClass, source, namespace));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a unique name in the namespace of this FunctionNode
|
||||
* @param base prefix for name
|
||||
@ -632,7 +682,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
rootClass, source, namespace));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -707,17 +757,10 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* @return true if the function's generated Java method needs a {@code callee} parameter.
|
||||
*/
|
||||
public boolean needsCallee() {
|
||||
// NOTE: we only need isSplit() here to ensure that :scope can never drop below slot 2 for splitting array units.
|
||||
return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict()) || hasOptimisticApplyToCall();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this function uses the return symbol
|
||||
* @return true if uses the return symbol
|
||||
*/
|
||||
public boolean usesReturnSymbol() {
|
||||
return isProgram() || isSplit() || getFlag(USES_RETURN_SYMBOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return {@code true} if this function makes use of the {@code this} object.
|
||||
*
|
||||
@ -780,7 +823,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
rootClass, source, namespace));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -848,7 +891,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* @return true if the function needs parent scope.
|
||||
*/
|
||||
public boolean needsParentScope() {
|
||||
return getFlag(NEEDS_PARENT_SCOPE) || isProgram();
|
||||
return getFlag(NEEDS_PARENT_SCOPE);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -876,7 +919,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
rootClass, source, namespace));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -953,7 +996,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
rootClass, source, namespace));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -967,9 +1010,9 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this function is a sub-function generated by splitting a larger one
|
||||
* Checks if this function is split into several smaller fragments.
|
||||
*
|
||||
* @return true if this function is split from a larger one
|
||||
* @return true if this function is split into several smaller fragments.
|
||||
*/
|
||||
public boolean isSplit() {
|
||||
return getFlag(IS_SPLIT);
|
||||
@ -1019,7 +1062,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
rootClass, source, namespace));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1098,7 +1141,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass
|
||||
rootClass, source, namespace
|
||||
));
|
||||
}
|
||||
|
||||
@ -1146,7 +1189,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
rootClass, source, namespace));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1202,6 +1245,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
body,
|
||||
parameters,
|
||||
thisProperties,
|
||||
rootClass));
|
||||
rootClass, source, namespace));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.ir;
|
||||
|
||||
import java.util.function.Function;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.runtime.Scope;
|
||||
|
||||
/**
|
||||
* Synthetic AST node that represents loading of the scope object and invocation of the {@link Scope#getSplitState()}
|
||||
* method on it. It has no JavaScript source representation and only occurs in synthetic functions created by
|
||||
* the split-into-functions transformation.
|
||||
*/
|
||||
public final class GetSplitState extends Expression {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** The sole instance of this AST node. */
|
||||
public final static GetSplitState INSTANCE = new GetSplitState();
|
||||
|
||||
private GetSplitState() {
|
||||
super(NO_TOKEN, NO_FINISH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType(final Function<Symbol, Type> localVariableTypes) {
|
||||
return Type.INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
return visitor.enterGetSplitState(this) ? visitor.leaveGetSplitState(this) : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb, final boolean printType) {
|
||||
if (printType) {
|
||||
sb.append("{I}");
|
||||
}
|
||||
sb.append(CompilerConstants.SCOPE.symbolName()).append('.').append(Scope.GET_SPLIT_STATE.name()).append("()");
|
||||
}
|
||||
|
||||
private Object readResolve() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
@ -439,6 +439,14 @@ public class LexicalContext {
|
||||
return getParentBlock() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the topmost lexical context element body of a SplitNode?
|
||||
* @return true if it's the body of a split node.
|
||||
*/
|
||||
public boolean isSplitBody() {
|
||||
return sp >= 2 && stack[sp - 1] instanceof Block && stack[sp - 2] instanceof SplitNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent function for a function in the lexical context
|
||||
* @param functionNode function for which to get parent
|
||||
@ -472,9 +480,6 @@ public class LexicalContext {
|
||||
final LexicalContextNode node = iter.next();
|
||||
if (node == until) {
|
||||
break;
|
||||
} else if (node instanceof SplitNode) {
|
||||
// Don't bother popping scopes if we're going to do a return from a split method anyway.
|
||||
return 0;
|
||||
}
|
||||
assert !(node instanceof FunctionNode); // Can't go outside current function
|
||||
if (node instanceof WithNode || node instanceof Block && ((Block)node).needsScope()) {
|
||||
|
@ -672,13 +672,13 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
private static final class ArrayLiteralInitializer {
|
||||
|
||||
static ArrayLiteralNode initialize(final ArrayLiteralNode node) {
|
||||
final Type elementType = computeElementType(node.value, node.elementType);
|
||||
final Type elementType = computeElementType(node.value);
|
||||
final int[] postsets = computePostsets(node.value);
|
||||
final Object presets = computePresets(node.value, elementType, postsets);
|
||||
return new ArrayLiteralNode(node, node.value, elementType, postsets, presets, node.units);
|
||||
}
|
||||
|
||||
private static Type computeElementType(final Expression[] value, final Type elementType) {
|
||||
private static Type computeElementType(final Expression[] value) {
|
||||
Type widestElementType = Type.INT;
|
||||
|
||||
for (final Expression elem : value) {
|
||||
|
@ -38,6 +38,15 @@ import jdk.nashorn.internal.parser.TokenType;
|
||||
public abstract class Node implements Cloneable, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Constant used for synthetic AST nodes that have no line number. */
|
||||
public static final int NO_LINE_NUMBER = -1;
|
||||
|
||||
/** Constant used for synthetic AST nodes that have no token. */
|
||||
public static final long NO_TOKEN = 0L;
|
||||
|
||||
/** Constant used for synthetic AST nodes that have no finish. */
|
||||
public static final int NO_FINISH = 0;
|
||||
|
||||
/** Start of source range. */
|
||||
protected final int start;
|
||||
|
||||
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* 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.ir;
|
||||
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.runtime.Scope;
|
||||
|
||||
/**
|
||||
* Synthetic AST node that represents loading of the scope object and invocation of the {@link Scope#setSplitState(int)}
|
||||
* method on it. It has no JavaScript source representation and only occurs in synthetic functions created by
|
||||
* the split-into-functions transformation.
|
||||
*/
|
||||
public final class SetSplitState extends Statement {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int state;
|
||||
|
||||
/**
|
||||
* Creates a new split state setter
|
||||
* @param state the state to set
|
||||
* @param lineNumber the line number where it is inserted
|
||||
*/
|
||||
public SetSplitState(final int state, final int lineNumber) {
|
||||
super(lineNumber, NO_TOKEN, NO_FINISH);
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state this setter sets.
|
||||
* @return the state this setter sets.
|
||||
*/
|
||||
public int getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
return visitor.enterSetSplitState(this) ? visitor.leaveSetSplitState(this) : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb, final boolean printType) {
|
||||
sb.append(CompilerConstants.SCOPE.symbolName()).append('.').append(Scope.SET_SPLIT_STATE.name())
|
||||
.append('(').append(state).append(");");
|
||||
}
|
||||
}
|
@ -25,13 +25,10 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.io.IOException;
|
||||
import java.io.NotSerializableException;
|
||||
import java.io.ObjectOutputStream;
|
||||
import jdk.nashorn.internal.codegen.CompileUnit;
|
||||
import jdk.nashorn.internal.codegen.Label;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
@ -39,7 +36,7 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
* Node indicating code is split across classes.
|
||||
*/
|
||||
@Immutable
|
||||
public class SplitNode extends LexicalContextStatement implements Labels, CompileUnitHolder {
|
||||
public class SplitNode extends LexicalContextStatement implements CompileUnitHolder {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** Split node method name. */
|
||||
@ -51,8 +48,6 @@ public class SplitNode extends LexicalContextStatement implements Labels, Compil
|
||||
/** Body of split code. */
|
||||
private final Block body;
|
||||
|
||||
private Map<Label, JoinPredecessor> jumps;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -67,19 +62,18 @@ public class SplitNode extends LexicalContextStatement implements Labels, Compil
|
||||
this.compileUnit = compileUnit;
|
||||
}
|
||||
|
||||
private SplitNode(final SplitNode splitNode, final Block body, final CompileUnit compileUnit, final Map<Label, JoinPredecessor> jumps) {
|
||||
private SplitNode(final SplitNode splitNode, final Block body, final CompileUnit compileUnit) {
|
||||
super(splitNode);
|
||||
this.name = splitNode.name;
|
||||
this.body = body;
|
||||
this.compileUnit = compileUnit;
|
||||
this.jumps = jumps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the body for this split node - i.e. the actual code it encloses
|
||||
* @return body for split node
|
||||
*/
|
||||
public Node getBody() {
|
||||
public Block getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
@ -87,7 +81,7 @@ public class SplitNode extends LexicalContextStatement implements Labels, Compil
|
||||
if (this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body, compileUnit, jumps));
|
||||
return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body, compileUnit));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -133,33 +127,12 @@ public class SplitNode extends LexicalContextStatement implements Labels, Compil
|
||||
if (this.compileUnit == compileUnit) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body, compileUnit, jumps));
|
||||
return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body, compileUnit));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a jump that crosses this split node's boundary (it originates within the split node, and goes to a target
|
||||
* outside of it).
|
||||
* @param jumpOrigin the join predecessor that's the origin of the jump
|
||||
* @param targetLabel the label that's the target of the jump.
|
||||
*/
|
||||
public void addJump(final JoinPredecessor jumpOrigin, final Label targetLabel) {
|
||||
if (jumps == null) {
|
||||
jumps = new HashMap<>();
|
||||
}
|
||||
jumps.put(targetLabel, jumpOrigin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the jump origin within this split node for a target.
|
||||
* @param targetLabel the target for which a jump origin is sought.
|
||||
* @return the jump origin, or null.
|
||||
*/
|
||||
public JoinPredecessor getJumpOrigin(final Label targetLabel) {
|
||||
return jumps == null ? null : jumps.get(targetLabel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Label> getLabels() {
|
||||
return Collections.unmodifiableList(new ArrayList<>(jumps.keySet()));
|
||||
private void writeObject(final ObjectOutputStream out) throws IOException {
|
||||
// We are only serializing the AST after we run SplitIntoFunctions; no SplitNodes can remain for the
|
||||
// serialization.
|
||||
throw new NotSerializableException(getClass().getName());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.ir;
|
||||
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
/**
|
||||
* Synthetic AST node that represents return from a split fragment of a split function for control flow reasons (break
|
||||
* or continue into a target outside the current fragment). It has no JavaScript source representation and only occurs
|
||||
* in synthetic functions created by the split-into-functions transformation. It is different from a return node in
|
||||
* that the return value is irrelevant, and doesn't affect the function's return type calculation.
|
||||
*/
|
||||
public final class SplitReturn extends Statement {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** The sole instance of this AST node. */
|
||||
public static final SplitReturn INSTANCE = new SplitReturn();
|
||||
|
||||
private SplitReturn() {
|
||||
super(NO_LINE_NUMBER, NO_TOKEN, NO_FINISH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminal() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
return visitor.enterSplitReturn(this) ? visitor.leaveSplitReturn(this) : this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void toString(StringBuilder sb, boolean printType) {
|
||||
sb.append(":splitreturn;");
|
||||
}
|
||||
|
||||
private Object readResolve() {
|
||||
return INSTANCE;
|
||||
}
|
||||
}
|
@ -97,7 +97,7 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
private int firstSlot = -1;
|
||||
|
||||
/** Field number in scope or property; array index in varargs when not using arguments object. */
|
||||
private int fieldIndex;
|
||||
private int fieldIndex = -1;
|
||||
|
||||
/** Number of times this symbol is used in code */
|
||||
private int useCount;
|
||||
@ -135,28 +135,15 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*
|
||||
* @param name name of symbol
|
||||
* @param flags symbol flags
|
||||
* @param slot bytecode slot for this symbol
|
||||
*/
|
||||
protected Symbol(final String name, final int flags, final int slot) {
|
||||
public Symbol(final String name, final int flags) {
|
||||
this.name = name;
|
||||
this.flags = flags;
|
||||
this.firstSlot = slot;
|
||||
this.fieldIndex = -1;
|
||||
if(shouldTrace()) {
|
||||
trace("CREATE SYMBOL " + name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param name name of symbol
|
||||
* @param flags symbol flags
|
||||
*/
|
||||
public Symbol(final String name, final int flags) {
|
||||
this(name, flags, -1);
|
||||
}
|
||||
|
||||
private static String align(final String string, final int max) {
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(string.substring(0, Math.min(string.length(), max)));
|
||||
|
@ -38,6 +38,7 @@ import jdk.nashorn.internal.ir.EmptyNode;
|
||||
import jdk.nashorn.internal.ir.ExpressionStatement;
|
||||
import jdk.nashorn.internal.ir.ForNode;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.GetSplitState;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.IfNode;
|
||||
import jdk.nashorn.internal.ir.IndexNode;
|
||||
@ -50,7 +51,9 @@ import jdk.nashorn.internal.ir.ObjectNode;
|
||||
import jdk.nashorn.internal.ir.PropertyNode;
|
||||
import jdk.nashorn.internal.ir.ReturnNode;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode;
|
||||
import jdk.nashorn.internal.ir.SetSplitState;
|
||||
import jdk.nashorn.internal.ir.SplitNode;
|
||||
import jdk.nashorn.internal.ir.SplitReturn;
|
||||
import jdk.nashorn.internal.ir.SwitchNode;
|
||||
import jdk.nashorn.internal.ir.TernaryNode;
|
||||
import jdk.nashorn.internal.ir.ThrowNode;
|
||||
@ -389,6 +392,26 @@ public abstract class NodeVisitor<T extends LexicalContext> {
|
||||
return leaveDefault(functionNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for entering a {@link GetSplitState}.
|
||||
*
|
||||
* @param getSplitState the get split state expression
|
||||
* @return true if traversal should continue and node children be traversed, false otherwise
|
||||
*/
|
||||
public boolean enterGetSplitState(final GetSplitState getSplitState) {
|
||||
return enterDefault(getSplitState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for leaving a {@link GetSplitState}.
|
||||
*
|
||||
* @param getSplitState the get split state expression
|
||||
* @return processed node, which will replace the original one, or the original node
|
||||
*/
|
||||
public Node leaveGetSplitState(final GetSplitState getSplitState) {
|
||||
return leaveDefault(getSplitState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for entering an IdentNode
|
||||
*
|
||||
@ -569,6 +592,26 @@ public abstract class NodeVisitor<T extends LexicalContext> {
|
||||
return leaveDefault(runtimeNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for entering a {@link SetSplitState}.
|
||||
*
|
||||
* @param setSplitState the set split state statement
|
||||
* @return true if traversal should continue and node children be traversed, false otherwise
|
||||
*/
|
||||
public boolean enterSetSplitState(final SetSplitState setSplitState) {
|
||||
return enterDefault(setSplitState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for leaving a {@link SetSplitState}.
|
||||
*
|
||||
* @param setSplitState the set split state expression
|
||||
* @return processed node, which will replace the original one, or the original node
|
||||
*/
|
||||
public Node leaveSetSplitState(final SetSplitState setSplitState) {
|
||||
return leaveDefault(setSplitState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for entering a SplitNode
|
||||
*
|
||||
@ -589,6 +632,26 @@ public abstract class NodeVisitor<T extends LexicalContext> {
|
||||
return leaveDefault(splitNode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for entering a SplitReturn
|
||||
*
|
||||
* @param splitReturn the node
|
||||
* @return true if traversal should continue and node children be traversed, false otherwise
|
||||
*/
|
||||
public boolean enterSplitReturn(final SplitReturn splitReturn) {
|
||||
return enterDefault(splitReturn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for leaving a SplitReturn
|
||||
*
|
||||
* @param splitReturn the node
|
||||
* @return processed node, which will replace the original one, or the original node
|
||||
*/
|
||||
public Node leaveSplitReturn(final SplitReturn splitReturn) {
|
||||
return leaveDefault(splitReturn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for entering a SwitchNode
|
||||
*
|
||||
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* 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.runtime;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
|
||||
/**
|
||||
* This static utility class performs deserialization of FunctionNode ASTs from a byte array.
|
||||
* The format is a standard Java serialization stream, deflated.
|
||||
*/
|
||||
final class AstDeserializer {
|
||||
static FunctionNode deserialize(final byte[] serializedAst) {
|
||||
try {
|
||||
return (FunctionNode)new ObjectInputStream(new InflaterInputStream(new ByteArrayInputStream(
|
||||
serializedAst))).readObject();
|
||||
} catch (final ClassNotFoundException | IOException e) {
|
||||
// This is internal, can't happen
|
||||
throw new AssertionError("Unexpected exception deserializing function", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -27,6 +27,7 @@ package jdk.nashorn.internal.runtime;
|
||||
import static jdk.nashorn.internal.lookup.Lookup.MH;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
|
||||
|
||||
import java.lang.invoke.CallSite;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
@ -786,6 +787,7 @@ final class CompiledFunction {
|
||||
// isn't available, we'll use the old one bound into the call site.
|
||||
final OptimismInfo effectiveOptInfo = currentOptInfo != null ? currentOptInfo : oldOptInfo;
|
||||
FunctionNode fn = effectiveOptInfo.reparse();
|
||||
final boolean serialized = effectiveOptInfo.isSerialized();
|
||||
final Compiler compiler = effectiveOptInfo.getCompiler(fn, callSiteType, re); //set to non rest-of
|
||||
|
||||
if (!shouldRecompile) {
|
||||
@ -793,17 +795,17 @@ final class CompiledFunction {
|
||||
// recompiled a deoptimized version for an inner invocation.
|
||||
// We still need to do the rest of from the beginning
|
||||
logRecompile("Rest-of compilation [STANDALONE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
|
||||
return restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
|
||||
return restOfHandle(effectiveOptInfo, compiler.compile(fn, serialized ? CompilationPhases.COMPILE_SERIALIZED_RESTOF : CompilationPhases.COMPILE_ALL_RESTOF), currentOptInfo != null);
|
||||
}
|
||||
|
||||
logRecompile("Deoptimizing recompilation (up to bytecode) ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
|
||||
fn = compiler.compile(fn, CompilationPhases.COMPILE_UPTO_BYTECODE);
|
||||
fn = compiler.compile(fn, serialized ? CompilationPhases.RECOMPILE_SERIALIZED_UPTO_BYTECODE : CompilationPhases.COMPILE_UPTO_BYTECODE);
|
||||
log.info("Reusable IR generated");
|
||||
|
||||
// compile the rest of the function, and install it
|
||||
log.info("Generating and installing bytecode from reusable IR...");
|
||||
logRecompile("Rest-of compilation [CODE PIPELINE REUSE] ", fn, callSiteType, effectiveOptInfo.invalidatedProgramPoints);
|
||||
final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.COMPILE_FROM_BYTECODE);
|
||||
final FunctionNode normalFn = compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL);
|
||||
|
||||
if (effectiveOptInfo.data.usePersistentCodeCache()) {
|
||||
final RecompilableScriptFunctionData data = effectiveOptInfo.data;
|
||||
@ -829,7 +831,7 @@ final class CompiledFunction {
|
||||
constructor = null; // Will be regenerated when needed
|
||||
|
||||
log.info("Done: ", invoker);
|
||||
final MethodHandle restOf = restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.COMPILE_FROM_BYTECODE_RESTOF), canBeDeoptimized);
|
||||
final MethodHandle restOf = restOfHandle(effectiveOptInfo, compiler.compile(fn, CompilationPhases.GENERATE_BYTECODE_AND_INSTALL_RESTOF), canBeDeoptimized);
|
||||
|
||||
// Note that we only adjust the switch point after we set the invoker/constructor. This is important.
|
||||
if (canBeDeoptimized) {
|
||||
@ -921,6 +923,10 @@ final class CompiledFunction {
|
||||
FunctionNode reparse() {
|
||||
return data.reparse();
|
||||
}
|
||||
|
||||
boolean isSerialized() {
|
||||
return data.isSerialized();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -43,6 +43,7 @@ import jdk.nashorn.internal.codegen.Compiler;
|
||||
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.codegen.FunctionSignature;
|
||||
import jdk.nashorn.internal.codegen.Namespace;
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator.AllocatorDescriptor;
|
||||
import jdk.nashorn.internal.codegen.OptimisticTypesPersistence;
|
||||
import jdk.nashorn.internal.codegen.TypeMap;
|
||||
@ -79,6 +80,9 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
/** Source from which FunctionNode was parsed. */
|
||||
private transient Source source;
|
||||
|
||||
/** Serialized, compressed form of the AST. Used by split functions as they can't be reparsed from source. */
|
||||
private final byte[] serializedAst;
|
||||
|
||||
/** Token of this function within the source. */
|
||||
private final long token;
|
||||
|
||||
@ -127,6 +131,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
* @param nestedFunctions nested function map
|
||||
* @param externalScopeDepths external scope depths
|
||||
* @param internalSymbols internal symbols to method, defined in its scope
|
||||
* @param serializedAst a serialized AST representation. Normally only used for split functions.
|
||||
*/
|
||||
public RecompilableScriptFunctionData(
|
||||
final FunctionNode functionNode,
|
||||
@ -134,7 +139,8 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
final AllocatorDescriptor allocationDescriptor,
|
||||
final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
|
||||
final Map<String, Integer> externalScopeDepths,
|
||||
final Set<String> internalSymbols) {
|
||||
final Set<String> internalSymbols,
|
||||
final byte[] serializedAst) {
|
||||
|
||||
super(functionName(functionNode),
|
||||
Math.min(functionNode.getParameters().size(), MAX_ARITY),
|
||||
@ -158,6 +164,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
nfn.setParent(this);
|
||||
}
|
||||
|
||||
this.serializedAst = serializedAst;
|
||||
createLogger();
|
||||
}
|
||||
|
||||
@ -212,10 +219,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
*/
|
||||
public int getExternalSymbolDepth(final String symbolName) {
|
||||
final Integer depth = externalScopeDepths.get(symbolName);
|
||||
if (depth == null) {
|
||||
return -1;
|
||||
}
|
||||
return depth;
|
||||
return depth == null ? -1 : depth;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -354,8 +358,15 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
return allocationStrategy.allocate(map);
|
||||
}
|
||||
|
||||
boolean isSerialized() {
|
||||
return serializedAst != null;
|
||||
}
|
||||
|
||||
FunctionNode reparse() {
|
||||
// NOTE: If we aren't recompiling the top-level program, we decrease functionNodeId 'cause we'll have a synthetic program node
|
||||
if (isSerialized()) {
|
||||
return deserialize();
|
||||
}
|
||||
|
||||
final int descPosition = Token.descPosition(token);
|
||||
final Context context = Context.getContextTrusted();
|
||||
final Parser parser = new Parser(
|
||||
@ -363,8 +374,10 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
source,
|
||||
new Context.ThrowErrorManager(),
|
||||
isStrict(),
|
||||
// source starts at line 0, so even though lineNumber is the correct declaration line, back off
|
||||
// one to make it exclusive
|
||||
lineNumber - 1,
|
||||
context.getLogger(Parser.class)); // source starts at line 0, so even though lineNumber is the correct declaration line, back off one to make it exclusive
|
||||
context.getLogger(Parser.class));
|
||||
|
||||
if (getFunctionFlag(FunctionNode.IS_ANONYMOUS)) {
|
||||
parser.setFunctionName(functionName);
|
||||
@ -378,6 +391,17 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
return (isProgram() ? program : extractFunctionFromScript(program)).setName(null, functionName);
|
||||
}
|
||||
|
||||
private FunctionNode deserialize() {
|
||||
final ScriptEnvironment env = installer.getOwner();
|
||||
final Timing timing = env._timing;
|
||||
final long t1 = System.nanoTime();
|
||||
try {
|
||||
return AstDeserializer.deserialize(serializedAst).initializeDeserialized(source, new Namespace(env.getNamespace()));
|
||||
} finally {
|
||||
timing.accumulateTime("'Deserialize'", System.nanoTime() - t1);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean getFunctionFlag(final int flag) {
|
||||
return (functionFlags & flag) != 0;
|
||||
}
|
||||
@ -486,7 +510,8 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
|
||||
final FunctionNode fn = reparse();
|
||||
final Compiler compiler = getCompiler(fn, actualCallSiteType, runtimeScope);
|
||||
final FunctionNode compiledFn = compiler.compile(fn, CompilationPhases.COMPILE_ALL);
|
||||
final FunctionNode compiledFn = compiler.compile(fn,
|
||||
isSerialized() ? CompilationPhases.COMPILE_ALL_SERIALIZED : CompilationPhases.COMPILE_ALL);
|
||||
|
||||
if (persist && !compiledFn.getFlag(FunctionNode.HAS_APPLY_TO_CALL_SPECIALIZATION)) {
|
||||
compiler.persistClassInfo(cacheKey, compiledFn);
|
||||
@ -606,7 +631,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
|
||||
MethodHandle lookupCodeMethod(final Class<?> codeClass, final MethodType targetType) {
|
||||
if (log.isEnabled()) {
|
||||
log.info("Looking up ", DebugLogger.quote(name), " type=", targetType);
|
||||
log.info("Looking up ", DebugLogger.quote(functionName), " type=", targetType);
|
||||
}
|
||||
return MH.findStatic(LOOKUP, codeClass, functionName, targetType);
|
||||
}
|
||||
@ -817,6 +842,26 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData imp
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the {@link #getFunctionFlags()} flags to a function node. During on-demand compilation, we might need
|
||||
* to restore flags to a function node that was otherwise not subjected to a full compile pipeline (e.g. its parse
|
||||
* was skipped, or it's a nested function of a deserialized function.
|
||||
* @param lc current lexical context
|
||||
* @param fn the function node to restore flags onto
|
||||
* @return the transformed function node
|
||||
*/
|
||||
public FunctionNode restoreFlags(final LexicalContext lc, final FunctionNode fn) {
|
||||
assert fn.getId() == functionNodeId;
|
||||
FunctionNode newFn = fn.setFlags(lc, functionFlags);
|
||||
// This compensates for missing markEval() in case the function contains an inner function
|
||||
// that contains eval(), that now we didn't discover since we skipped the inner function.
|
||||
if (newFn.hasNestedEval()) {
|
||||
assert newFn.hasScopeBlock();
|
||||
newFn = newFn.setBody(lc, newFn.getBody().setNeedsScope(null));
|
||||
}
|
||||
return newFn;
|
||||
}
|
||||
|
||||
private void readObject(final java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
|
||||
in.defaultReadObject();
|
||||
createLogger();
|
||||
|
Loading…
Reference in New Issue
Block a user