8135190: Method code too large in Babel browser.js script

Reviewed-by: attila, sundar
This commit is contained in:
Hannes Wallnöfer 2015-09-19 16:04:28 +02:00
parent 2a12715485
commit 008b5c0ad9
15 changed files with 518 additions and 242 deletions

View File

@ -70,11 +70,10 @@ import jdk.nashorn.internal.ir.IndexNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LexicalContextNode;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.Splittable;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.Symbol;
@ -984,7 +983,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 || isSplitArray(node)) {
if (node instanceof FunctionNode || isSplitLiteral(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;
@ -1010,12 +1009,8 @@ final class AssignSymbols extends NodeVisitor<LexicalContext> implements Loggabl
throw new AssertionError();
}
private static boolean isSplitArray(final LexicalContextNode expr) {
if(!(expr instanceof ArrayLiteralNode)) {
return false;
}
final List<ArrayUnit> units = ((ArrayLiteralNode)expr).getUnits();
return !(units == null || units.isEmpty());
private static boolean isSplitLiteral(final LexicalContextNode expr) {
return expr instanceof Splittable && ((Splittable) expr).getSplitRanges() != null;
}
private void throwUnprotectedSwitchError(final VarNode varNode) {

View File

@ -105,7 +105,6 @@ import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LexicalContextNode;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
import jdk.nashorn.internal.ir.LiteralNode.PrimitiveLiteralNode;
import jdk.nashorn.internal.ir.LocalVariableConversion;
import jdk.nashorn.internal.ir.LoopNode;
@ -118,6 +117,7 @@ import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.SetSplitState;
import jdk.nashorn.internal.ir.SplitReturn;
import jdk.nashorn.internal.ir.Splittable;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.Symbol;
@ -242,7 +242,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
private final DebugLogger log;
/** From what size should we use spill instead of fields for JavaScript objects? */
private static final int OBJECT_SPILL_THRESHOLD = Options.getIntProperty("nashorn.spill.threshold", 256);
static final int OBJECT_SPILL_THRESHOLD = Options.getIntProperty("nashorn.spill.threshold", 256);
private final Set<String> emittedMethods = new HashSet<>();
@ -2234,73 +2234,33 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
*
* @param arrayLiteralNode the array of contents
* @param arrayType the type of the array, e.g. ARRAY_NUMBER or ARRAY_OBJECT
*
* @return the method generator that was used
*/
private MethodEmitter loadArray(final ArrayLiteralNode arrayLiteralNode, final ArrayType arrayType) {
private void loadArray(final ArrayLiteralNode arrayLiteralNode, final ArrayType arrayType) {
assert arrayType == Type.INT_ARRAY || arrayType == Type.LONG_ARRAY || arrayType == Type.NUMBER_ARRAY || arrayType == Type.OBJECT_ARRAY;
final Expression[] nodes = arrayLiteralNode.getValue();
final Object presets = arrayLiteralNode.getPresets();
final int[] postsets = arrayLiteralNode.getPostsets();
final Class<?> type = arrayType.getTypeClass();
final List<ArrayUnit> units = arrayLiteralNode.getUnits();
final Expression[] nodes = arrayLiteralNode.getValue();
final Object presets = arrayLiteralNode.getPresets();
final int[] postsets = arrayLiteralNode.getPostsets();
final List<Splittable.SplitRange> ranges = arrayLiteralNode.getSplitRanges();
loadConstant(presets);
final Type elementType = arrayType.getElementType();
if (units != null) {
final MethodEmitter savedMethod = method;
final FunctionNode currentFunction = lc.getCurrentFunction();
if (ranges != null) {
for (final ArrayUnit arrayUnit : units) {
unit = lc.pushCompileUnit(arrayUnit.getCompileUnit());
final String className = unit.getUnitClassName();
assert unit != null;
final String name = currentFunction.uniqueName(SPLIT_PREFIX.symbolName());
final String signature = methodDescriptor(type, ScriptFunction.class, Object.class, ScriptObject.class, type);
pushMethodEmitter(unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature));
method.setFunctionNode(currentFunction);
method.begin();
defineCommonSplitMethodParameters();
defineSplitMethodParameter(CompilerConstants.SPLIT_ARRAY_ARG.slot(), arrayType);
// 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();
for (int i = arrayUnit.getLo(); i < arrayUnit.getHi(); i++) {
method.load(arrayType, arraySlot);
storeElement(nodes, elementType, postsets[i]);
loadSplitLiteral(new SplitLiteralCreator() {
@Override
public void populateRange(final MethodEmitter method, final Type type, final int slot, final int start, final int end) {
for (int i = start; i < end; i++) {
method.load(type, slot);
storeElement(nodes, elementType, postsets[i]);
}
method.load(type, slot);
}
}, ranges, arrayType);
method.load(arrayType, arraySlot);
method._return();
lc.exitSplitNode();
method.end();
lc.releaseSlots();
popMethodEmitter();
assert method == savedMethod;
method.loadCompilerConstant(CALLEE);
method.swap();
method.loadCompilerConstant(THIS);
method.swap();
method.loadCompilerConstant(SCOPE);
method.swap();
method.invokestatic(className, name, signature);
unit = lc.popCompileUnit(unit);
}
return method;
return;
}
if(postsets.length > 0) {
@ -2312,7 +2272,6 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
}
method.load(arrayType, arraySlot);
}
return method;
}
private void storeElement(final Expression[] nodes, final Type elementType, final int index) {
@ -2537,6 +2496,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
final List<MapTuple<Expression>> tuples = new ArrayList<>();
final List<PropertyNode> gettersSetters = new ArrayList<>();
final int ccp = getCurrentContinuationEntryPoint();
final List<Splittable.SplitRange> ranges = objectNode.getSplitRanges();
Expression protoNode = null;
boolean restOfProperty = false;
@ -2583,7 +2543,13 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
loadExpressionAsType(node, type);
}};
}
oc.makeObject(method);
if (ranges != null) {
oc.createObject(method);
loadSplitLiteral(oc, ranges, Type.typeFor(oc.getAllocatorClass()));
} else {
oc.makeObject(method);
}
//if this is a rest of method and our continuation point was found as one of the values
//in the properties above, we need to reset the map to oc.getMap() in the continuation
@ -2899,6 +2865,54 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
method.onLocalStore(type, slot);
}
private void loadSplitLiteral(final SplitLiteralCreator creator, final List<Splittable.SplitRange> ranges, final Type literalType) {
assert ranges != null;
// final Type literalType = Type.typeFor(literalClass);
final MethodEmitter savedMethod = method;
final FunctionNode currentFunction = lc.getCurrentFunction();
for (final Splittable.SplitRange splitRange : ranges) {
unit = lc.pushCompileUnit(splitRange.getCompileUnit());
assert unit != null;
final String className = unit.getUnitClassName();
final String name = currentFunction.uniqueName(SPLIT_PREFIX.symbolName());
final Class<?> clazz = literalType.getTypeClass();
final String signature = methodDescriptor(clazz, ScriptFunction.class, Object.class, ScriptObject.class, clazz);
pushMethodEmitter(unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature));
method.setFunctionNode(currentFunction);
method.begin();
defineCommonSplitMethodParameters();
defineSplitMethodParameter(CompilerConstants.SPLIT_ARRAY_ARG.slot(), literalType);
// 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 literalSlot = fixScopeSlot(currentFunction, 3);
lc.enterSplitNode();
creator.populateRange(method, literalType, literalSlot, splitRange.getLow(), splitRange.getHigh());
method._return();
lc.exitSplitNode();
method.end();
lc.releaseSlots();
popMethodEmitter();
assert method == savedMethod;
method.loadCompilerConstant(CALLEE).swap();
method.loadCompilerConstant(THIS).swap();
method.loadCompilerConstant(SCOPE).swap();
method.invokestatic(className, name, signature);
unit = lc.popCompileUnit(unit);
}
}
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);
@ -5461,4 +5475,21 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
method.uncheckedGoto(targetCatchLabel);
}
}
/**
* Interface implemented by object creators that support splitting over multiple methods.
*/
interface SplitLiteralCreator {
/**
* Generate code to populate a range of the literal object. A reference to the object
* should be left on the stack when the method terminates.
*
* @param method the method emitter
* @param type the type of the literal object
* @param slot the local slot containing the literal object
* @param start the start index (inclusive)
* @param end the end index (exclusive)
*/
void populateRange(MethodEmitter method, Type type, int slot, int start, int end);
}
}

View File

@ -34,7 +34,6 @@ import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getPaddedFieldCo
import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex;
import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex;
import java.util.Iterator;
import java.util.List;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.Symbol;
@ -91,27 +90,20 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator<T> {
findClass();
}
/**
* Construct an object.
*
* @param method the method emitter
*/
@Override
protected void makeObject(final MethodEmitter method) {
public void createObject(final MethodEmitter method) {
makeMap();
final String className = getClassName();
try {
// NOTE: we must load the actual structure class here, because the API operates with Nashorn Type objects,
// and Type objects need a loaded class, for better or worse. We also have to be specific and use the type
// of the actual structure class, we can't generalize it to e.g. Type.typeFor(ScriptObject.class) as the
// exact type information is needed for generating continuations in rest-of methods. If we didn't do this,
// object initializers like { x: arr[i] } would fail during deoptimizing compilation on arr[i], as the
// values restored from the RewriteException would be cast to "ScriptObject" instead of to e.g. "JO4", and
// subsequently the "PUTFIELD J04.L0" instruction in the continuation code would fail bytecode verification.
method._new(Context.forStructureClass(className.replace('/', '.'))).dup();
} catch (final ClassNotFoundException e) {
throw new AssertionError(e);
}
// NOTE: we must load the actual structure class here, because the API operates with Nashorn Type objects,
// and Type objects need a loaded class, for better or worse. We also have to be specific and use the type
// of the actual structure class, we can't generalize it to e.g. Type.typeFor(ScriptObject.class) as the
// exact type information is needed for generating continuations in rest-of methods. If we didn't do this,
// object initializers like { x: arr[i] } would fail during deoptimizing compilation on arr[i], as the
// values restored from the RewriteException would be cast to "ScriptObject" instead of to e.g. "JO4", and
// subsequently the "PUTFIELD J04.L0" instruction in the continuation code would fail bytecode verification.
assert fieldObjectClass != null;
method._new(fieldObjectClass).dup();
loadMap(method); //load the map
if (isScope()) {
@ -126,14 +118,14 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator<T> {
} else {
method.invoke(constructorNoLookup(className, PropertyMap.class));
}
}
helpOptimisticRecognizeDuplicateIdentity(method);
@Override
public void populateRange(final MethodEmitter method, final Type objectType, final int objectSlot, final int start, final int end) {
method.load(objectType, objectSlot);
// Set values.
final Iterator<MapTuple<T>> iter = tuples.iterator();
while (iter.hasNext()) {
final MapTuple<T> tuple = iter.next();
for (int i = start; i < end; i++) {
final MapTuple<T> tuple = tuples.get(i);
//we only load when we have both symbols and values (which can be == the symbol)
//if we didn't load, we need an array property
if (tuple.symbol != null && tuple.value != null) {
@ -212,6 +204,11 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator<T> {
}
}
@Override
protected Class<? extends ScriptObject> getAllocatorClass() {
return fieldObjectClass;
}
/**
* Get the class name for the object class,
* e.g. {@code com.nashorn.oracle.scripts.JO2P0}

View File

@ -257,8 +257,7 @@ public class MethodEmitter {
*/
private Type popType(final Type expected) {
final Type type = popType();
assert type.isObject() && expected.isObject() ||
type.isEquivalentTo(expected) : type + " is not compatible with " + expected;
assert type.isEquivalentTo(expected) : type + " is not compatible with " + expected;
return type;
}

View File

@ -36,7 +36,7 @@ import jdk.nashorn.internal.runtime.ScriptObject;
* Base class for object creation code generation.
* @param <T> value type
*/
public abstract class ObjectCreator<T> {
public abstract class ObjectCreator<T> implements CodeGenerator.SplitLiteralCreator {
/** List of keys & symbols to initiate in this ObjectCreator */
final List<MapTuple<T>> tuples;
@ -69,7 +69,23 @@ public abstract class ObjectCreator<T> {
* Generate code for making the object.
* @param method Script method.
*/
protected abstract void makeObject(final MethodEmitter method);
public void makeObject(final MethodEmitter method) {
createObject(method);
// We need to store the object in a temporary slot as populateRange expects to load the
// object from a slot (as it is also invoked within split methods). Note that this also
// helps optimistic continuations to handle the stack in case an optimistic assumption
// fails during initialization (see JDK-8079269).
final int objectSlot = method.getUsedSlotsWithLiveTemporaries();
final Type objectType = method.peekType();
method.storeTemp(objectType, objectSlot);
populateRange(method, objectType, objectSlot, 0, tuples.size());
}
/**
* Generate code for creating and initializing the object.
* @param method the method emitter
*/
protected abstract void createObject(final MethodEmitter method);
/**
* Construct the property map appropriate for the object.
@ -124,6 +140,12 @@ public abstract class ObjectCreator<T> {
return hasArguments;
}
/**
* Get the class of objects created by this ObjectCreator
* @return class of created object
*/
abstract protected Class<? extends ScriptObject> getAllocatorClass();
/**
* Technique for loading an initial value. Defined by anonymous subclasses in code gen.
*
@ -145,29 +167,4 @@ public abstract class ObjectCreator<T> {
MethodEmitter loadTuple(final MethodEmitter method, final MapTuple<T> tuple) {
return loadTuple(method, tuple, true);
}
/**
* If using optimistic typing, let the code generator realize that the newly created object on the stack
* when DUP-ed will be the same value. Basically: {NEW, DUP, INVOKESPECIAL init, DUP} will leave a stack
* load specification {unknown, unknown} on stack (that is "there's two values on the stack, but neither
* comes from a known local load"). If there's an optimistic operation in the literal initializer,
* OptimisticOperation.storeStack will allocate two temporary locals for it and store them as
* {ASTORE 4, ASTORE 3}. If we instead do {NEW, DUP, INVOKESPECIAL init, ASTORE 3, ALOAD 3, DUP} we end up
* with stack load specification {ALOAD 3, ALOAD 3} (as DUP can track that the value it duplicated came
* from a local load), so if/when a continuation needs to be recreated from it, it'll be
* able to emit ALOAD 3, ALOAD 3 to recreate the stack. If we didn't do this, deoptimization within an
* object literal initialization could in rare cases cause an incompatible change in the shape of the
* local variable table for the temporaries, e.g. in the following snippet where a variable is reassigned
* to a wider type in an object initializer:
* <code>var m = 1; var obj = {p0: m, p1: m = "foo", p2: m}</code>
* @param method the current method emitter.
*/
void helpOptimisticRecognizeDuplicateIdentity(final MethodEmitter method) {
if (codegen.useOptimisticTypes()) {
final Type objectType = method.peekType();
final int tempSlot = method.defineTemporaryLocalVariable(objectType.getSlots());
method.storeHidden(objectType, tempSlot);
method.load(objectType, tempSlot);
}
}
}

View File

@ -27,13 +27,15 @@ 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.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.ObjectNode;
import jdk.nashorn.internal.ir.Splittable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
/**
@ -70,15 +72,28 @@ abstract class ReplaceCompileUnits extends NodeVisitor<LexicalContext> {
public Node leaveLiteralNode(final LiteralNode<?> node) {
if (node instanceof ArrayLiteralNode) {
final ArrayLiteralNode aln = (ArrayLiteralNode)node;
if (aln.getUnits() == null) {
if (aln.getSplitRanges() == 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()));
final List<Splittable.SplitRange> newArrayUnits = new ArrayList<>();
for (final Splittable.SplitRange au : aln.getSplitRanges()) {
newArrayUnits.add(new Splittable.SplitRange(getExistingReplacement(au), au.getLow(), au.getHigh()));
}
return aln.setUnits(lc, newArrayUnits);
return aln.setSplitRanges(lc, newArrayUnits);
}
return node;
}
@Override
public Node leaveObjectNode(final ObjectNode objectNode) {
final List<Splittable.SplitRange> ranges = objectNode.getSplitRanges();
if (ranges != null) {
final List<Splittable.SplitRange> newRanges = new ArrayList<>();
for (final Splittable.SplitRange range : ranges) {
newRanges.add(new Splittable.SplitRange(getExistingReplacement(range), range.getLow(), range.getHigh()));
}
return objectNode.setSplitRanges(lc, newRanges);
}
return super.leaveObjectNode(objectNode);
}
}

View File

@ -61,7 +61,7 @@ public final class SpillObjectCreator extends ObjectCreator<Expression> {
}
@Override
protected void makeObject(final MethodEmitter method) {
public void createObject(final MethodEmitter method) {
assert !isScope() : "spill scope objects are not currently supported";
final int length = tuples.size();
@ -69,9 +69,7 @@ public final class SpillObjectCreator extends ObjectCreator<Expression> {
final int spillLength = ScriptObject.spillAllocationLength(length);
final long[] jpresetValues = dualFields ? new long[spillLength] : null;
final Object[] opresetValues = new Object[spillLength];
final Set<Integer> postsetValues = new LinkedHashSet<>();
final int callSiteFlags = codegen.getCallSiteFlags();
final Class<?> objectClass = dualFields ? JD.class : JO.class;
final Class<?> objectClass = getAllocatorClass();
ArrayData arrayData = ArrayData.allocate(ScriptRuntime.EMPTY_ARRAY);
// Compute constant property values
@ -85,9 +83,7 @@ public final class SpillObjectCreator extends ObjectCreator<Expression> {
if (value != null) {
final Object constantValue = LiteralNode.objectAsConstant(value);
if (constantValue == LiteralNode.POSTSET_MARKER) {
postsetValues.add(pos);
} else {
if (constantValue != LiteralNode.POSTSET_MARKER) {
final Property property = propertyMap.findProperty(key);
if (property != null) {
// normal property key
@ -146,25 +142,34 @@ public final class SpillObjectCreator extends ObjectCreator<Expression> {
// instantiate the script object with spill objects
method.invoke(constructorNoLookup(objectClass, PropertyMap.class, long[].class, Object[].class));
helpOptimisticRecognizeDuplicateIdentity(method);
// Set prefix array data if any
if (arrayData.length() > 0) {
method.dup();
codegen.loadConstant(arrayData);
method.invoke(virtualCallNoLookup(ScriptObject.class, "setArray", void.class, ArrayData.class));
}
}
@Override
public void populateRange(final MethodEmitter method, final Type objectType, final int objectSlot, final int start, final int end) {
final int callSiteFlags = codegen.getCallSiteFlags();
method.load(objectType, objectSlot);
// set postfix values
for (final int i : postsetValues) {
for (int i = start; i < end; i++) {
final MapTuple<Expression> tuple = tuples.get(i);
if (LiteralNode.isConstant(tuple.value)) {
continue;
}
final Property property = propertyMap.findProperty(tuple.key);
if (property == null) {
final int index = ArrayIndex.getArrayIndex(tuple.key);
assert ArrayIndex.isValidArrayIndex(index);
method.dup();
method.load(ArrayIndex.toLongIndex(index));
//method.println("putting " + tuple + " into arraydata");
loadTuple(method, tuple);
method.dynamicSetIndex(callSiteFlags);
} else {
@ -178,8 +183,7 @@ public final class SpillObjectCreator extends ObjectCreator<Expression> {
@Override
protected PropertyMap makeMap() {
assert propertyMap == null : "property map already initialized";
final boolean dualFields = codegen.useDualFields();
final Class<? extends ScriptObject> clazz = dualFields ? JD.class : JO.class;
final Class<? extends ScriptObject> clazz = getAllocatorClass();
propertyMap = new MapCreator<>(clazz, tuples).makeSpillMap(false, codegen.useDualFields());
return propertyMap;
}
@ -188,4 +192,9 @@ public final class SpillObjectCreator extends ObjectCreator<Expression> {
protected void loadValue(final Expression expr, final Type type) {
codegen.loadExpressionAsType(expr, type);
}
@Override
protected Class<? extends ScriptObject> getAllocatorClass() {
return codegen.useDualFields() ? JD.class : JO.class;
}
}

View File

@ -36,9 +36,11 @@ import jdk.nashorn.internal.ir.FunctionNode;
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.ObjectNode;
import jdk.nashorn.internal.ir.PropertyNode;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.Splittable;
import jdk.nashorn.internal.ir.Statement;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.Context;
@ -295,7 +297,7 @@ final class Splitter extends NodeVisitor<LexicalContext> implements Loggable {
final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode) literal;
final Node[] value = arrayLiteralNode.getValue();
final int[] postsets = arrayLiteralNode.getPostsets();
final List<ArrayUnit> units = new ArrayList<>();
final List<Splittable.SplitRange> ranges = new ArrayList<>();
long totalWeight = 0;
int lo = 0;
@ -309,7 +311,7 @@ final class Splitter extends NodeVisitor<LexicalContext> implements Loggable {
if (totalWeight >= SPLIT_THRESHOLD) {
final CompileUnit unit = compiler.findUnit(totalWeight - weight);
units.add(new ArrayUnit(unit, lo, i));
ranges.add(new Splittable.SplitRange(unit, lo, i));
lo = i;
totalWeight = weight;
}
@ -317,15 +319,58 @@ final class Splitter extends NodeVisitor<LexicalContext> implements Loggable {
if (lo != postsets.length) {
final CompileUnit unit = compiler.findUnit(totalWeight);
units.add(new ArrayUnit(unit, lo, postsets.length));
ranges.add(new Splittable.SplitRange(unit, lo, postsets.length));
}
return arrayLiteralNode.setUnits(lc, units);
return arrayLiteralNode.setSplitRanges(lc, ranges);
}
return literal;
}
@Override
public Node leaveObjectNode(final ObjectNode objectNode) {
long weight = WeighNodes.weigh(objectNode);
if (weight < SPLIT_THRESHOLD) {
return objectNode;
}
final FunctionNode functionNode = lc.getCurrentFunction();
lc.setFlag(functionNode, FunctionNode.IS_SPLIT);
final List<Splittable.SplitRange> ranges = new ArrayList<>();
final List<PropertyNode> properties = objectNode.getElements();
final boolean isSpillObject = properties.size() > CodeGenerator.OBJECT_SPILL_THRESHOLD;
long totalWeight = 0;
int lo = 0;
for (int i = 0; i < properties.size(); i++) {
final PropertyNode property = properties.get(i);
final boolean isConstant = LiteralNode.isConstant(property.getValue());
if (!isConstant || !isSpillObject) {
weight = isConstant ? 0 : WeighNodes.weigh(property.getValue());
totalWeight += WeighNodes.AASTORE_WEIGHT + weight;
if (totalWeight >= SPLIT_THRESHOLD) {
final CompileUnit unit = compiler.findUnit(totalWeight - weight);
ranges.add(new Splittable.SplitRange(unit, lo, i));
lo = i;
totalWeight = weight;
}
}
}
if (lo != properties.size()) {
final CompileUnit unit = compiler.findUnit(totalWeight);
ranges.add(new Splittable.SplitRange(unit, lo, properties.size()));
}
return objectNode.setSplitRanges(lc, ranges);
}
@Override
public boolean enterFunctionNode(final FunctionNode node) {
//only go into the function node for this splitter. any subfunctions are rejected

View File

@ -44,12 +44,13 @@ import jdk.nashorn.internal.ir.JumpToInlinedFinally;
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.ObjectNode;
import jdk.nashorn.internal.ir.PropertyNode;
import jdk.nashorn.internal.ir.ReturnNode;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.Splittable;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.ThrowNode;
import jdk.nashorn.internal.ir.TryNode;
@ -88,6 +89,8 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
static final long THROW_WEIGHT = 2;
static final long VAR_WEIGHT = 40;
static final long WITH_WEIGHT = 8;
static final long OBJECT_WEIGHT = 16;
static final long SETPROP_WEIGHT = 5;
/** Accumulated weight. */
private long weight;
@ -213,7 +216,7 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode)literalNode;
final Node[] value = arrayLiteralNode.getValue();
final int[] postsets = arrayLiteralNode.getPostsets();
final List<ArrayUnit> units = arrayLiteralNode.getUnits();
final List<Splittable.SplitRange> units = arrayLiteralNode.getSplitRanges();
if (units == null) {
for (final int postset : postsets) {
@ -232,6 +235,27 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
return true;
}
@Override
public boolean enterObjectNode(final ObjectNode objectNode) {
weight += OBJECT_WEIGHT;
final List<PropertyNode> properties = objectNode.getElements();
final boolean isSpillObject = properties.size() > CodeGenerator.OBJECT_SPILL_THRESHOLD;
for (final PropertyNode property : properties) {
if (!LiteralNode.isConstant(property.getValue())) {
weight += SETPROP_WEIGHT;
property.getValue().accept(this);
} else if (!isSpillObject) {
// constants in spill object are set via preset spill array,
// but fields objects need to set constants.
weight += SETPROP_WEIGHT;
}
}
return false;
}
@Override
public Node leavePropertyNode(final PropertyNode propertyNode) {
weight += LITERAL_WEIGHT;

View File

@ -65,6 +65,7 @@ import java.util.concurrent.ConcurrentMap;
import jdk.internal.org.objectweb.asm.Handle;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.Undefined;
import jdk.nashorn.internal.runtime.linker.Bootstrap;
@ -256,6 +257,9 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl
case jdk.internal.org.objectweb.asm.Type.DOUBLE:
return NUMBER;
case jdk.internal.org.objectweb.asm.Type.OBJECT:
if (Context.isStructureClass(itype.getClassName())) {
return SCRIPT_OBJECT;
}
try {
return Type.typeFor(Class.forName(itype.getClassName()));
} catch(final ClassNotFoundException e) {
@ -949,7 +953,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl
/**
* This is the singleton for integer arrays
*/
public static final ArrayType INT_ARRAY = new ArrayType(int[].class) {
public static final ArrayType INT_ARRAY = putInCache(new ArrayType(int[].class) {
private static final long serialVersionUID = 1L;
@Override
@ -973,12 +977,12 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl
public Type getElementType() {
return INT;
}
};
});
/**
* This is the singleton for long arrays
*/
public static final ArrayType LONG_ARRAY = new ArrayType(long[].class) {
public static final ArrayType LONG_ARRAY = putInCache(new ArrayType(long[].class) {
private static final long serialVersionUID = 1L;
@Override
@ -1002,12 +1006,12 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl
public Type getElementType() {
return LONG;
}
};
});
/**
* This is the singleton for numeric arrays
*/
public static final ArrayType NUMBER_ARRAY = new ArrayType(double[].class) {
public static final ArrayType NUMBER_ARRAY = putInCache(new ArrayType(double[].class) {
private static final long serialVersionUID = 1L;
@Override
@ -1031,13 +1035,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps, Serializabl
public Type getElementType() {
return NUMBER;
}
};
/** Singleton for method handle arrays used for properties etc. */
public static final ArrayType METHODHANDLE_ARRAY = putInCache(new ArrayType(MethodHandle[].class));
/** This is the singleton for string arrays */
public static final ArrayType STRING_ARRAY = putInCache(new ArrayType(String[].class));
});
/** This is the singleton for object arrays */
public static final ArrayType OBJECT_ARRAY = putInCache(new ArrayType(Object[].class));

View File

@ -25,11 +25,9 @@
package jdk.nashorn.internal.ir;
import java.io.Serializable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import jdk.nashorn.internal.codegen.CompileUnit;
import jdk.nashorn.internal.codegen.types.ArrayType;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
@ -583,6 +581,15 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
return POSTSET_MARKER;
}
/**
* Test whether {@code object} represents a constant value.
* @param object a node or value object
* @return true if object is a constant value
*/
public static boolean isConstant(final Object object) {
return objectAsConstant(object) != POSTSET_MARKER;
}
private static final class NullLiteralNode extends PrimitiveLiteralNode<Object> {
private static final long serialVersionUID = 1L;
@ -614,7 +621,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
* Array literal node class.
*/
@Immutable
public static final class ArrayLiteralNode extends LiteralNode<Expression[]> implements LexicalContextNode {
public static final class ArrayLiteralNode extends LiteralNode<Expression[]> implements LexicalContextNode, Splittable {
private static final long serialVersionUID = 1L;
/** Array element type. */
@ -626,8 +633,8 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
/** Indices of array elements requiring computed post sets. */
private final int[] postsets;
/** Sub units with indexes ranges, in which to split up code generation, for large literals */
private final List<ArrayUnit> units;
/** Ranges for splitting up large literals in code generation */
private final List<Splittable.SplitRange> splitRanges;
@Override
public boolean isArray() {
@ -635,64 +642,13 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
}
/**
* An ArrayUnit is a range in an ArrayLiteral. ArrayLiterals can
* be split if they are too large, for bytecode generation reasons
*/
public static final class ArrayUnit implements CompileUnitHolder, Serializable {
private static final long serialVersionUID = 1L;
/** Compile unit associated with the postsets range. */
private final CompileUnit compileUnit;
/** postsets range associated with the unit (hi not inclusive). */
private final int lo, hi;
/**
* Constructor
* @param compileUnit compile unit
* @param lo lowest array index in unit
* @param hi highest array index in unit + 1
*/
public ArrayUnit(final CompileUnit compileUnit, final int lo, final int hi) {
this.compileUnit = compileUnit;
this.lo = lo;
this.hi = hi;
}
/**
* Get the high index position of the ArrayUnit (non inclusive)
* @return high index position
*/
public int getHi() {
return hi;
}
/**
* Get the low index position of the ArrayUnit (inclusive)
* @return low index position
*/
public int getLo() {
return lo;
}
/**
* The array compile unit
* @return array compile unit
*/
@Override
public CompileUnit getCompileUnit() {
return compileUnit;
}
}
private static final class ArrayLiteralInitializer {
static ArrayLiteralNode initialize(final ArrayLiteralNode node) {
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);
return new ArrayLiteralNode(node, node.value, elementType, postsets, presets, node.splitRanges);
}
private static Type computeElementType(final Expression[] value) {
@ -725,7 +681,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
for (int i = 0; i < value.length; i++) {
final Expression element = value[i];
if (element == null || objectAsConstant(element) == POSTSET_MARKER) {
if (element == null || !isConstant(element)) {
computed[nComputed++] = i;
}
}
@ -842,19 +798,19 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
this.elementType = Type.UNKNOWN;
this.presets = null;
this.postsets = null;
this.units = null;
this.splitRanges = null;
}
/**
* Copy constructor
* @param node source array literal node
*/
private ArrayLiteralNode(final ArrayLiteralNode node, final Expression[] value, final Type elementType, final int[] postsets, final Object presets, final List<ArrayUnit> units) {
private ArrayLiteralNode(final ArrayLiteralNode node, final Expression[] value, final Type elementType, final int[] postsets, final Object presets, final List<Splittable.SplitRange> splitRanges) {
super(node, value);
this.elementType = elementType;
this.postsets = postsets;
this.presets = presets;
this.units = units;
this.splitRanges = splitRanges;
}
/**
@ -946,26 +902,27 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
}
/**
* Get the array units that make up this ArrayLiteral
* @see ArrayUnit
* @return list of array units
* Get the split ranges for this ArrayLiteral, or null if this array does not have to be split.
* @see Splittable.SplitRange
* @return list of split ranges
*/
public List<ArrayUnit> getUnits() {
return units == null ? null : Collections.unmodifiableList(units);
@Override
public List<Splittable.SplitRange> getSplitRanges() {
return splitRanges == null ? null : Collections.unmodifiableList(splitRanges);
}
/**
* Set the ArrayUnits that make up this ArrayLiteral
* Set the SplitRanges that make up this ArrayLiteral
* @param lc lexical context
* @see ArrayUnit
* @param units list of array units
* @return new or changed arrayliteralnode
* @see Splittable.SplitRange
* @param splitRanges list of split ranges
* @return new or changed node
*/
public ArrayLiteralNode setUnits(final LexicalContext lc, final List<ArrayUnit> units) {
if (this.units == units) {
public ArrayLiteralNode setSplitRanges(final LexicalContext lc, final List<Splittable.SplitRange> splitRanges) {
if (this.splitRanges == splitRanges) {
return this;
}
return Node.replaceInLexicalContext(lc, this, new ArrayLiteralNode(this, value, elementType, postsets, presets, units));
return Node.replaceInLexicalContext(lc, this, new ArrayLiteralNode(this, value, elementType, postsets, presets, splitRanges));
}
@Override
@ -987,7 +944,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
if (this.value == value) {
return this;
}
return Node.replaceInLexicalContext(lc, this, new ArrayLiteralNode(this, value, elementType, postsets, presets, units));
return Node.replaceInLexicalContext(lc, this, new ArrayLiteralNode(this, value, elementType, postsets, presets, splitRanges));
}
private ArrayLiteralNode setValue(final LexicalContext lc, final List<Expression> value) {

View File

@ -27,6 +27,7 @@ package jdk.nashorn.internal.ir;
import java.util.Collections;
import java.util.List;
import java.util.RandomAccess;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@ -35,12 +36,15 @@ import jdk.nashorn.internal.ir.visitor.NodeVisitor;
* IR representation of an object literal.
*/
@Immutable
public final class ObjectNode extends Expression {
public final class ObjectNode extends Expression implements LexicalContextNode, Splittable {
private static final long serialVersionUID = 1L;
/** Literal elements. */
private final List<PropertyNode> elements;
/** Ranges for splitting large literals over multiple compile units in codegen. */
private final List<Splittable.SplitRange> splitRanges;
/**
* Constructor
*
@ -51,19 +55,27 @@ public final class ObjectNode extends Expression {
public ObjectNode(final long token, final int finish, final List<PropertyNode> elements) {
super(token, finish);
this.elements = elements;
this.splitRanges = null;
assert elements instanceof RandomAccess : "Splitting requires random access lists";
}
private ObjectNode(final ObjectNode objectNode, final List<PropertyNode> elements) {
private ObjectNode(final ObjectNode objectNode, final List<PropertyNode> elements,
final List<Splittable.SplitRange> splitRanges ) {
super(objectNode);
this.elements = elements;
this.splitRanges = splitRanges;
}
@Override
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
if (visitor.enterObjectNode(this)) {
return visitor.leaveObjectNode(setElements(Node.accept(visitor, elements)));
}
return Acceptor.accept(this, visitor);
}
@Override
public Node accept(final LexicalContext lc, final NodeVisitor<? extends LexicalContext> visitor) {
if (visitor.enterObjectNode(this)) {
return visitor.leaveObjectNode(setElements(lc, Node.accept(visitor, elements)));
}
return this;
}
@ -102,10 +114,35 @@ public final class ObjectNode extends Expression {
return Collections.unmodifiableList(elements);
}
private ObjectNode setElements(final List<PropertyNode> elements) {
private ObjectNode setElements(final LexicalContext lc, final List<PropertyNode> elements) {
if (this.elements == elements) {
return this;
}
return new ObjectNode(this, elements);
return Node.replaceInLexicalContext(lc, this, new ObjectNode(this, elements, this.splitRanges));
}
/**
* Set the split ranges for this ObjectNode
* @see Splittable.SplitRange
* @param lc the lexical context
* @param splitRanges list of split ranges
* @return new or changed object node
*/
public ObjectNode setSplitRanges(final LexicalContext lc, final List<Splittable.SplitRange> splitRanges) {
if (this.splitRanges == splitRanges) {
return this;
}
return Node.replaceInLexicalContext(lc, this, new ObjectNode(this, elements, splitRanges));
}
/**
* Get the split ranges for this ObjectNode, or null if the object is not split.
* @see Splittable.SplitRange
* @return list of split ranges
*/
@Override
public List<Splittable.SplitRange> getSplitRanges() {
return splitRanges == null ? null : Collections.unmodifiableList(splitRanges);
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.ir;
import java.io.Serializable;
import java.util.List;
import jdk.nashorn.internal.codegen.CompileUnit;
/**
* An interface for splittable expressions.
*/
public interface Splittable {
/**
* Get a list of split ranges for this splittable expression, or null
* if the expression should not be split.
*
* @return a list of split ranges
*/
List<SplitRange> getSplitRanges();
/**
* A SplitRange is a range in a splittable expression. It defines the
* boundaries of the split range and provides a compile unit for code generation.
*/
final class SplitRange implements CompileUnitHolder, Serializable {
private static final long serialVersionUID = 1L;
/** Compile unit associated with the postsets range. */
private final CompileUnit compileUnit;
/** postsets range associated with the unit (hi not inclusive). */
private final int low, high;
/**
* Constructor
* @param compileUnit compile unit
* @param low lowest array index in unit
* @param high highest array index in unit + 1
*/
public SplitRange(final CompileUnit compileUnit, final int low, final int high) {
this.compileUnit = compileUnit;
this.low = low;
this.high = high;
}
/**
* Get the high index position of the ArrayUnit (exclusive)
* @return high index position
*/
public int getHigh() {
return high;
}
/**
* Get the low index position of the ArrayUnit (inclusive)
* @return low index position
*/
public int getLow() {
return low;
}
/**
* The array compile unit
* @return array compile unit
*/
@Override
public CompileUnit getCompileUnit() {
return compileUnit;
}
}
}

View File

@ -1033,6 +1033,16 @@ public final class Context {
return (Class<? extends ScriptObject>)Class.forName(fullName, true, sharedLoader);
}
/**
* Is {@code className} the name of a structure class?
*
* @param className a class name
* @return true if className is a structure class name
*/
public static boolean isStructureClass(final String className) {
return StructureLoader.isStructureClass(className);
}
/**
* Checks that the given Class can be accessed from no permissions context.
*

View File

@ -0,0 +1,67 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* JDK-8135190: Method code too large in Babel browser.js script
*
* @test
* @run
*/
// Make sure huge object literals are parsed correctly and don't throw
// (using buildObject -> JSON.stringify -> eval -> testObject)
function buildObject(n, d) {
if (n < 2) {
return {name: "property", type: "identifier"};
}
var obj = {};
for (var i = 0; i < n; i++) {
obj["expr" + i] = buildObject(Math.floor(n / d), d);
}
return obj;
}
function testObject(obj, n, d) {
var keys = Object.keys(obj);
if (n < 2) {
Assert.assertTrue(keys.length === 2);
Assert.assertTrue(keys[0] === "name");
Assert.assertTrue(keys[1] === "type");
} else {
Assert.assertTrue(keys.length === n);
for (var i = 0; i < n; i++) {
Assert.assertTrue(keys[i] === "expr" + i);
}
}
if (n >= 2) {
for (var k in keys) {
testObject(obj[keys[k]], Math.floor(n / d), d)
}
}
}
var fieldObject = (eval("(" + JSON.stringify(buildObject(25, 2)) + ")"));
testObject(fieldObject, 25, 2);
var spillObject = (eval("(" + JSON.stringify(buildObject(1000, 100)) + ")"));
testObject(spillObject, 1000, 100);