8038945: Simplify strict undefined checks

Reviewed-by: jlaskey, hannesw
This commit is contained in:
Marcus Lagergren 2014-04-01 16:12:38 +02:00
parent 1584dc799c
commit fd8e5653a0
11 changed files with 373 additions and 32 deletions

View File

@ -86,6 +86,7 @@ package jdk.internal.dynalink.support;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
@ -103,7 +104,7 @@ import jdk.internal.dynalink.CallSiteDescriptor;
* @author Attila Szegedi
*/
public class CallSiteDescriptorFactory {
private static final WeakHashMap<CallSiteDescriptor, WeakReference<CallSiteDescriptor>> publicDescs =
private static final WeakHashMap<CallSiteDescriptor, Reference<CallSiteDescriptor>> publicDescs =
new WeakHashMap<>();
@ -134,18 +135,27 @@ public class CallSiteDescriptorFactory {
static CallSiteDescriptor getCanonicalPublicDescriptor(final CallSiteDescriptor desc) {
synchronized(publicDescs) {
final WeakReference<CallSiteDescriptor> ref = publicDescs.get(desc);
final Reference<CallSiteDescriptor> ref = publicDescs.get(desc);
if(ref != null) {
final CallSiteDescriptor canonical = ref.get();
if(canonical != null) {
return canonical;
}
}
publicDescs.put(desc, new WeakReference<>(desc));
publicDescs.put(desc, createReference(desc));
}
return desc;
}
/**
* Override this to use a different kind of references for the cache
* @param desc desc
* @return reference
*/
protected static Reference<CallSiteDescriptor> createReference(final CallSiteDescriptor desc) {
return new WeakReference<>(desc);
}
private static CallSiteDescriptor createPublicCallSiteDescriptor(String[] tokenizedName, MethodType methodType) {
final int l = tokenizedName.length;
if(l > 0 && tokenizedName[0] == "dyn") {

View File

@ -1413,7 +1413,7 @@ final class Attr extends NodeOperatorVisitor<OptimisticLexicalContext> {
return leaveBinaryArithmetic(binaryNode);
}
private Node leaveCmp(final BinaryNode binaryNode) {
private BinaryNode leaveCmp(final BinaryNode binaryNode) {
//infect untyped comp with opportunistic type from other
final Expression lhs = binaryNode.lhs();
final Expression rhs = binaryNode.rhs();
@ -1423,7 +1423,7 @@ final class Attr extends NodeOperatorVisitor<OptimisticLexicalContext> {
final Type widest = Type.widest(lhs.getType(), rhs.getType());
ensureSymbol(lhs, widest);
ensureSymbol(rhs, widest);
return end(ensureSymbol(binaryNode, Type.BOOLEAN));
return (BinaryNode)end(ensureSymbol(binaryNode, Type.BOOLEAN));
}
private boolean enterBinaryArithmetic(final BinaryNode binaryNode) {

View File

@ -1991,6 +1991,56 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
return node instanceof LiteralNode<?> && ((LiteralNode<?>) node).isNull();
}
private boolean undefinedCheck(final RuntimeNode runtimeNode, final List<Expression> args) {
final Request request = runtimeNode.getRequest();
if (!Request.isUndefinedCheck(request)) {
return false;
}
final Expression lhs = args.get(0);
final Expression rhs = args.get(1);
if (!rhs.getSymbol().isScope()) {
return false; //disallow undefined as local var or parameter
}
//make sure that undefined has not been overridden or scoped as a local var
//between us and global
final CompilationEnvironment env = compiler.getCompilationEnvironment();
RecompilableScriptFunctionData data = env.getScriptFunctionData(lc.getCurrentFunction().getId());
final RecompilableScriptFunctionData program = compiler.getCompilationEnvironment().getProgram();
assert data != null;
while (data != program) {
if (data.hasInternalSymbol("undefined")) {
return false;
}
data = data.getParent();
}
load(lhs);
if (lhs.getType().isPrimitive()) {
method.pop(); //throw away lhs, but it still needs to be evaluated for side effects, even if not in scope, as it can be optimistic
method.load(request == Request.IS_NOT_UNDEFINED);
method.store(runtimeNode.getSymbol());
} else {
final Label isUndefined = new Label("ud_check_true");
final Label notUndefined = new Label("ud_check_false");
final Label end = new Label("end");
method.loadUndefined(Type.OBJECT);
method.if_acmpeq(isUndefined);
method.label(notUndefined);
method.load(request == Request.IS_NOT_UNDEFINED);
method._goto(end);
method.label(isUndefined);
method.load(request == Request.IS_UNDEFINED);
method.label(end);
method.store(runtimeNode.getSymbol());
}
return true;
}
private boolean nullCheck(final RuntimeNode runtimeNode, final List<Expression> args) {
final Request request = runtimeNode.getRequest();
@ -2078,7 +2128,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
return false;
}
assert args.size() == 2;
assert args.size() == 2 : node;
final Type returnType = node.getType();
new OptimisticOperation() {
@ -2135,7 +2185,7 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
*
* TODO - remove this - Access Specializer will always know after Attr/Lower
*/
final List<Expression> args = runtimeNode.getArgs();
final List<Expression> args = new ArrayList<>(runtimeNode.getArgs());
if (runtimeNode.isPrimitive() && !runtimeNode.isFinal() && isReducible(runtimeNode.getRequest())) {
final Expression lhs = args.get(0);
@ -2186,14 +2236,25 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
return false;
}
if (!runtimeNode.isFinal() && specializationCheck(runtimeNode.getRequest(), runtimeNode, args)) {
if (undefinedCheck(runtimeNode, args)) {
return false;
}
final RuntimeNode newRuntimeNode;
if (Request.isUndefinedCheck(runtimeNode.getRequest())) {
newRuntimeNode = runtimeNode.setRequest(runtimeNode.getRequest() == Request.IS_UNDEFINED ? Request.EQ_STRICT : Request.NE_STRICT);
} else {
newRuntimeNode = runtimeNode;
}
if (!newRuntimeNode.isFinal() && specializationCheck(newRuntimeNode.getRequest(), newRuntimeNode, args)) {
return false;
}
new OptimisticOperation() {
@Override
void loadStack() {
for (final Expression arg : args) {
for (final Expression arg : newRuntimeNode.getArgs()) {
load(arg, Type.OBJECT);
}
}
@ -2201,17 +2262,17 @@ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContex
void consumeStack() {
method.invokestatic(
CompilerConstants.className(ScriptRuntime.class),
runtimeNode.getRequest().toString(),
newRuntimeNode.getRequest().toString(),
new FunctionSignature(
false,
false,
runtimeNode.getType(),
args.size()).toString());
newRuntimeNode.getType(),
newRuntimeNode.getArgs().size()).toString());
}
}.emit(runtimeNode);
}.emit(newRuntimeNode);
method.convert(runtimeNode.getType());
method.store(runtimeNode.getSymbol());
method.convert(newRuntimeNode.getType());
method.store(newRuntimeNode.getSymbol());
return false;
}

View File

@ -477,6 +477,20 @@ public final class CompilationEnvironment {
return optimistic;
}
RecompilableScriptFunctionData getProgram() {
if (compiledFunction == null) {
return null;
}
RecompilableScriptFunctionData program = compiledFunction;
while (true) {
final RecompilableScriptFunctionData parent = program.getParent();
if (parent == null) {
return program;
}
program = parent;
}
}
RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId);
}

View File

@ -30,6 +30,7 @@ import static jdk.nashorn.internal.lookup.Lookup.MH;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.Iterator;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.runtime.ScriptFunction;
import jdk.nashorn.internal.runtime.ScriptObject;

View File

@ -28,7 +28,6 @@ package jdk.nashorn.internal.codegen;
import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.Expression;
@ -38,6 +37,8 @@ import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.UnaryNode;
import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
@ -83,6 +84,35 @@ final class FinalizeTypes extends NodeOperatorVisitor<LexicalContext> {
setModify(lc, modify == null ? null : discard(modify));
}
private static Node createIsUndefined(final Expression parent, final Expression lhs, final Expression rhs, final Request request) {
if ("undefined".equals(rhs.getSymbol().getName())) {
return new RuntimeNode(parent, request, lhs, rhs);
}
return parent;
}
@Override
public Node leaveEQ_STRICT(final BinaryNode binaryNode) {
return createIsUndefined(binaryNode, binaryNode.lhs(), binaryNode.rhs(), Request.IS_UNDEFINED);
}
@Override
public Node leaveNE_STRICT(final BinaryNode binaryNode) {
return createIsUndefined(binaryNode, binaryNode.lhs(), binaryNode.rhs(), Request.IS_NOT_UNDEFINED);
}
@Override
public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
switch (runtimeNode.getRequest()) {
case EQ_STRICT:
return createIsUndefined(runtimeNode, runtimeNode.getArgs().get(0), runtimeNode.getArgs().get(1), Request.IS_UNDEFINED);
case NE_STRICT:
return createIsUndefined(runtimeNode, runtimeNode.getArgs().get(0), runtimeNode.getArgs().get(1), Request.IS_NOT_UNDEFINED);
default:
return runtimeNode;
}
}
@Override
public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
assert binaryNode.getSymbol() != null;
@ -194,7 +224,7 @@ final class FinalizeTypes extends NodeOperatorVisitor<LexicalContext> {
private static Expression discard(final Expression expr) {
if (expr.getSymbol() != null) {
UnaryNode discard = new UnaryNode(Token.recast(expr.getToken(), TokenType.DISCARD), expr);
final UnaryNode discard = new UnaryNode(Token.recast(expr.getToken(), TokenType.DISCARD), expr);
//discard never has a symbol in the discard node - then it would be a nop
assert !expr.isTerminal();
return discard;

View File

@ -33,6 +33,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.Expression;
import jdk.nashorn.internal.ir.FunctionNode;
@ -41,6 +42,7 @@ import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.DebugLogger;
import jdk.nashorn.internal.runtime.PropertyMap;
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
@ -57,6 +59,9 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> {
private final CompilationEnvironment env;
private final Map<Integer, Map<Integer, RecompilableScriptFunctionData>> fnIdToNestedFunctions = new HashMap<>();
private final Map<Integer, Map<String, Integer>> externalSymbolDepths = new HashMap<>();
private final Map<Integer, Set<String>> internalSymbols = new HashMap<>();
private final static DebugLogger LOG = new DebugLogger("scopedepths");
FindScopeDepths(final Compiler compiler) {
super(new LexicalContext());
@ -123,7 +128,7 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> {
return null;
}
private static Block findGlobalBlock(final LexicalContext lc, final FunctionNode fn, final Block block) {
private static Block findGlobalBlock(final LexicalContext lc, final Block block) {
final Iterator<Block> iter = lc.getBlocks(block);
Block globalBlock = null;
while (iter.hasNext()) {
@ -174,7 +179,8 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> {
allocatorMap,
nestedFunctions,
compiler.getSourceURL(),
externalSymbolDepths.get(fnId)
externalSymbolDepths.get(fnId),
internalSymbols.get(fnId)
);
if (lc.getOutermostFunction() != newFunctionNode) {
@ -223,15 +229,15 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> {
final Map<String, Integer> internals = new HashMap<>();
final Block globalBlock = findGlobalBlock(lc, block);
final Block bodyBlock = findBodyBlock(lc, fn, block);
assert globalBlock != null;
assert bodyBlock != null;
for (final Symbol symbol : symbols) {
Iterator<Block> iter;
final Block globalBlock = findGlobalBlock(lc, fn, block);
final Block bodyBlock = findBodyBlock(lc, fn, block);
assert globalBlock != null;
assert bodyBlock != null;
final int internalDepth = findInternalDepth(lc, fn, block, symbol);
final boolean internal = internalDepth >= 0;
if (internal) {
@ -258,9 +264,21 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> {
}
}
addInternalSymbols(fn, internals.keySet());
if (LOG.isEnabled()) {
LOG.info(fn.getName() + " internals=" + internals + " externals=" + externalSymbolDepths.get(fn.getId()));
}
return true;
}
private void addInternalSymbols(final FunctionNode functionNode, final Set<String> symbols) {
final int fnId = functionNode.getId();
assert internalSymbols.get(fnId) == null || internalSymbols.get(fnId).equals(symbols); //e.g. cloned finally block
internalSymbols.put(fnId, symbols);
}
private void addExternalSymbol(final FunctionNode functionNode, final Symbol symbol, final int depthAtStart) {
final int fnId = functionNode.getId();
Map<String, Integer> depths = externalSymbolDepths.get(fnId);
@ -268,7 +286,6 @@ final class FindScopeDepths extends NodeVisitor<LexicalContext> {
depths = new HashMap<>();
externalSymbolDepths.put(fnId, depths);
}
//System.err.println("PUT " + functionNode.getName() + " " + symbol + " " +depthAtStart);
depths.put(symbol.getName(), depthAtStart);
}
}

View File

@ -29,11 +29,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.annotations.Immutable;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.parser.TokenType;
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
/**
@ -80,8 +80,10 @@ public class RuntimeNode extends Expression implements Optimistic {
NE_STRICT(TokenType.NE_STRICT, Type.BOOLEAN, 2, true),
/** != operator with at least one object */
NE(TokenType.NE, Type.BOOLEAN, 2, true),
/** Verify type */
TYPE_GUARD(TokenType.VOID, Type.INT, 2);
/** is undefined */
IS_UNDEFINED(TokenType.EQ_STRICT, Type.BOOLEAN, 2),
/** is not undefined */
IS_NOT_UNDEFINED(TokenType.NE_STRICT, Type.BOOLEAN, 2);
/** token type */
private final TokenType tokenType;
@ -192,6 +194,17 @@ public class RuntimeNode extends Expression implements Optimistic {
}
}
/**
* Is this an undefined check?
*
* @param request request
*
* @return true if undefined check
*/
public static boolean isUndefinedCheck(final Request request) {
return request == IS_UNDEFINED || request == IS_NOT_UNDEFINED;
}
/**
* Is this an EQ or EQ_STRICT?
*
@ -300,6 +313,8 @@ public class RuntimeNode extends Expression implements Optimistic {
case LT:
case GE:
case GT:
case IS_UNDEFINED:
case IS_NOT_UNDEFINED:
return true;
default:
return false;
@ -403,6 +418,19 @@ public class RuntimeNode extends Expression implements Optimistic {
this(parent, request, parent.lhs(), parent.rhs());
}
/**
* Reset the request for this runtime node
* @param request request
* @return new runtime node or same if same request
*/
public RuntimeNode setRequest(final Request request) {
if (this.request == request) {
return this;
}
return new RuntimeNode(this, request, isFinal, args, programPoint);
}
/**
* Is this node final - i.e. it can never be replaced with other nodes again
* @return true if final
@ -473,7 +501,12 @@ public class RuntimeNode extends Expression implements Optimistic {
return Collections.unmodifiableList(args);
}
private RuntimeNode setArgs(final List<Expression> args) {
/**
* Set the arguments of this runtime node
* @param args new arguments
* @return new runtime node, or identical if no change
*/
public RuntimeNode setArgs(final List<Expression> args) {
if (this.args == args) {
return this;
}

View File

@ -123,6 +123,8 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
private final Map<String, Integer> externalScopeDepths;
private final Set<String> internalSymbols;
private static final int GET_SET_PREFIX_LENGTH = "*et ".length();
/**
@ -135,6 +137,7 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
* @param nestedFunctions nested function map
* @param sourceURL source URL
* @param externalScopeDepths external scope depths
* @param internalSymbols internal symbols to method, defined in its scope
*/
public RecompilableScriptFunctionData(
final FunctionNode functionNode,
@ -143,7 +146,8 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
final PropertyMap allocatorMap,
final Map<Integer, RecompilableScriptFunctionData> nestedFunctions,
final String sourceURL,
final Map<String, Integer> externalScopeDepths) {
final Map<String, Integer> externalScopeDepths,
final Set<String> internalSymbols) {
super(functionName(functionNode),
Math.min(functionNode.getParameters().size(), MAX_ARITY),
@ -161,8 +165,9 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
this.sourceURL = sourceURL;
this.allocatorClassName = allocatorClassName;
this.allocatorMap = allocatorMap;
this.nestedFunctions = nestedFunctions;//deepTraverse(nestedFunctions);
this.nestedFunctions = nestedFunctions;
this.externalScopeDepths = externalScopeDepths;
this.internalSymbols = internalSymbols;
for (final RecompilableScriptFunctionData nfn : nestedFunctions.values()) {
assert nfn.getParent() == null;
@ -170,6 +175,19 @@ public final class RecompilableScriptFunctionData extends ScriptFunctionData {
}
}
/**
* Check if a symbol is internally defined in a function. For example
* if "undefined" is internally defined in the outermost program function,
* it has not been reassigned or overridden and can be optimized
*
* @param symbolName symbol name
* @return true if symbol is internal to this ScriptFunction
*/
public boolean hasInternalSymbol(final String symbolName) {
return internalSymbols.contains(symbolName);
}
/**
* Return the external symbol table
* @param symbolName symbol name

View File

@ -0,0 +1,137 @@
/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* JDK-8038945.js : test various undefined strict intrinsics and that they
* aren't erroneously applied when undefined is in any scope but global
*
* @test
* @run
*/
//:program internals={print=0, f1=0, f2=0, f3=0, f4=0, undefined=0, f5=0} externals=null
//f1 internals={} externals={undefined=0}
function f1(x) {
return x === undefined;
}
//f2 internals={} externals=null
function f2(x, undefined) {
return x === undefined;
}
//f3 internals={x=0} externals=null
function f3(x) {
//f3$f3_2 internals={} externals={x=0}
function f3_2(undefined) {
return x === undefined;
}
return f3_2(17);
}
//f4 internals={x=0} externals=null
function f4(x) {
//f4$f4_2 internals={} externals={x=0}
function f4_2() {
var undefined = 17;
return x === undefined;
}
return f4_2();
}
//f5 internals={x=0, undefined=0} externals=null
function f5(x) {
var undefined = 17;
//f5$f5_2 internals={} externals={x=0, undefined=0}
function f5_2() {
return x === undefined;
}
return f5_2();
}
print(" 1: " + f1(17) + " === false");
print(" 2: " + f2(17) + " === false");
print(" 3: " + f3(17) + " === true");
print(" 4: " + f4(17) + " === true");
print(" 5: " + f5(17) + " === true");
//recompile
print(" 6: " + f1("17") + " === false");
print(" 7: " + f2("17") + " === false");
print(" 8: " + f3("17") + " === false");
print(" 9: " + f4("17") + " === false");
print("10: " + f5("17") + " === false");
//g1 internals={} externals={undefined=0}
function g1(x) {
return x !== undefined;
}
//g2 internals={} externals=null
function g2(x, undefined) {
return x !== undefined;
}
//g3 internals={x=0} externals=null
function g3(x) {
//g3$g3_2 internals={} externals={x=0}
function g3_2(undefined) {
return x !== undefined;
}
return g3_2(17);
}
//g4 internals={x=0} externals=null
function g4(x) {
//f4$f4_2 internals={} externals={x=0}
function g4_2() {
var undefined = 17;
return x !== undefined;
}
return g4_2();
}
//g5 internals={x=0, undefined=0} externals=null
function g5(x) {
var undefined = 17;
//g5$g5_2 internals={} externals={x=0, undefined=0}
function g5_2() {
return x !== undefined;
}
return g5_2();
}
print("11: " + g1(17) + " === true");
print("12: " + g2(17) + " === true");
print("13: " + g3(17) + " === false");
print("14: " + g4(17) + " === false");
print("15: " + g5(17) + " === false");
//recompile
print("16: " + g1("17") + " === true");
print("17: " + g2("17") + " === true");
print("18: " + g3("17") + " === true");
print("19: " + g4("17") + " === true");
print("20: " + g5("17") + " === true");

View File

@ -0,0 +1,20 @@
1: false === false
2: false === false
3: true === true
4: true === true
5: true === true
6: false === false
7: false === false
8: false === false
9: false === false
10: false === false
11: true === true
12: true === true
13: false === false
14: false === false
15: false === false
16: true === true
17: true === true
18: true === true
19: true === true
20: true === true