blob: f9d643228e89ef4dae7eb899b02df9f70c6b9fe9 [file] [log] [blame]
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.internal.codegen;
import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.AccessNode;
import jdk.nashorn.internal.ir.Assignment;
import jdk.nashorn.internal.ir.BinaryNode;
import jdk.nashorn.internal.ir.Block;
import jdk.nashorn.internal.ir.CallNode;
import jdk.nashorn.internal.ir.CaseNode;
import jdk.nashorn.internal.ir.CatchNode;
import jdk.nashorn.internal.ir.ExecuteNode;
import jdk.nashorn.internal.ir.ForNode;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.IdentNode;
import jdk.nashorn.internal.ir.IfNode;
import jdk.nashorn.internal.ir.IndexNode;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.ReturnNode;
import jdk.nashorn.internal.ir.RuntimeNode;
import jdk.nashorn.internal.ir.RuntimeNode.Request;
import jdk.nashorn.internal.ir.SwitchNode;
import jdk.nashorn.internal.ir.Symbol;
import jdk.nashorn.internal.ir.TemporarySymbols;
import jdk.nashorn.internal.ir.TernaryNode;
import jdk.nashorn.internal.ir.ThrowNode;
import jdk.nashorn.internal.ir.TypeOverride;
import jdk.nashorn.internal.ir.UnaryNode;
import jdk.nashorn.internal.ir.VarNode;
import jdk.nashorn.internal.ir.WhileNode;
import jdk.nashorn.internal.ir.WithNode;
import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.parser.Token;
import jdk.nashorn.internal.parser.TokenType;
import jdk.nashorn.internal.runtime.Debug;
import jdk.nashorn.internal.runtime.DebugLogger;
import jdk.nashorn.internal.runtime.JSType;
/**
* Lower to more primitive operations. After lowering, an AST has symbols and
* types. Lowering may also add specialized versions of methods to the script if
* the optimizer is turned on.
*
* Any expression that requires temporary storage as part of computation will
* also be detected here and give a temporary symbol
*
* For any op that we process in FinalizeTypes it is an absolute guarantee
* that scope and slot information is correct. This enables e.g. AccessSpecialization
* and frame optimizations
*/
final class FinalizeTypes extends NodeOperatorVisitor<LexicalContext> {
private static final DebugLogger LOG = new DebugLogger("finalize");
private final TemporarySymbols temporarySymbols;
FinalizeTypes(final TemporarySymbols temporarySymbols) {
super(new LexicalContext());
this.temporarySymbols = temporarySymbols;
}
@Override
public Node leaveCallNode(final CallNode callNode) {
// AccessSpecializer - call return type may change the access for this location
final Node function = callNode.getFunction();
if (function instanceof FunctionNode) {
return setTypeOverride(callNode, ((FunctionNode)function).getReturnType());
}
return callNode;
}
private Node leaveUnary(final UnaryNode unaryNode) {
return unaryNode.setRHS(convert(unaryNode.rhs(), unaryNode.getType()));
}
@Override
public Node leaveADD(final UnaryNode unaryNode) {
return leaveUnary(unaryNode);
}
@Override
public Node leaveBIT_NOT(final UnaryNode unaryNode) {
return leaveUnary(unaryNode);
}
@Override
public Node leaveCONVERT(final UnaryNode unaryNode) {
assert unaryNode.rhs().tokenType() != TokenType.CONVERT : "convert(convert encountered. check its origin and remove it";
return unaryNode;
}
@Override
public Node leaveDECINC(final UnaryNode unaryNode) {
return specialize(unaryNode).node;
}
@Override
public Node leaveNEW(final UnaryNode unaryNode) {
assert unaryNode.getSymbol() != null && unaryNode.getSymbol().getSymbolType().isObject();
return unaryNode.setRHS(((CallNode)unaryNode.rhs()).setIsNew());
}
@Override
public Node leaveSUB(final UnaryNode unaryNode) {
return leaveUnary(unaryNode);
}
/**
* Add is a special binary, as it works not only on arithmetic, but for
* strings etc as well.
*/
@Override
public Node leaveADD(final BinaryNode binaryNode) {
final Node lhs = binaryNode.lhs();
final Node rhs = binaryNode.rhs();
final Type type = binaryNode.getType();
if (type.isObject()) {
if (!isAddString(binaryNode)) {
return new RuntimeNode(binaryNode, Request.ADD);
}
}
return binaryNode.setLHS(convert(lhs, type)).setRHS(convert(rhs, type));
}
@Override
public Node leaveAND(final BinaryNode binaryNode) {
return binaryNode;
}
@Override
public Node leaveASSIGN(final BinaryNode binaryNode) {
final SpecializedNode specialized = specialize(binaryNode);
final BinaryNode specBinaryNode = (BinaryNode)specialized.node;
Type destType = specialized.type;
if (destType == null) {
destType = specBinaryNode.getType();
}
return specBinaryNode.setRHS(convert(specBinaryNode.rhs(), destType));
}
@Override
public Node leaveASSIGN_ADD(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_BIT_AND(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_BIT_OR(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_BIT_XOR(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_DIV(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_MOD(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_MUL(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_SAR(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_SHL(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_SHR(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
@Override
public Node leaveASSIGN_SUB(final BinaryNode binaryNode) {
return leaveASSIGN(binaryNode);
}
private boolean symbolIsInteger(Node node) {
final Symbol symbol = node.getSymbol();
assert symbol != null && symbol.getSymbolType().isInteger() : "int coercion expected: " + Debug.id(symbol) + " " + symbol + " " + lc.getCurrentFunction().getSource();
return true;
}
@Override
public Node leaveBIT_AND(final BinaryNode binaryNode) {
assert symbolIsInteger(binaryNode);
return leaveBinary(binaryNode, Type.INT, Type.INT);
}
@Override
public Node leaveBIT_OR(final BinaryNode binaryNode) {
assert symbolIsInteger(binaryNode);
return leaveBinary(binaryNode, Type.INT, Type.INT);
}
@Override
public Node leaveBIT_XOR(final BinaryNode binaryNode) {
assert symbolIsInteger(binaryNode);
return leaveBinary(binaryNode, Type.INT, Type.INT);
}
@Override
public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
assert binaryNode.getSymbol() != null;
final BinaryNode newBinaryNode = binaryNode.setRHS(discard(binaryNode.rhs()));
// AccessSpecializer - the type of lhs, which is the remaining value of this node may have changed
// in that case, update the node type as well
return propagateType(newBinaryNode, newBinaryNode.lhs().getType());
}
@Override
public Node leaveCOMMARIGHT(final BinaryNode binaryNode) {
assert binaryNode.getSymbol() != null;
final BinaryNode newBinaryNode = binaryNode.setLHS(discard(binaryNode.lhs()));
// AccessSpecializer - the type of rhs, which is the remaining value of this node may have changed
// in that case, update the node type as well
return propagateType(newBinaryNode, newBinaryNode.rhs().getType());
}
@Override
public Node leaveDIV(final BinaryNode binaryNode) {
return leaveBinaryArith(binaryNode);
}
@Override
public Node leaveEQ(final BinaryNode binaryNode) {
return leaveCmp(binaryNode, Request.EQ);
}
@Override
public Node leaveEQ_STRICT(final BinaryNode binaryNode) {
return leaveCmp(binaryNode, Request.EQ_STRICT);
}
@Override
public Node leaveGE(final BinaryNode binaryNode) {
return leaveCmp(binaryNode, Request.GE);
}
@Override
public Node leaveGT(final BinaryNode binaryNode) {
return leaveCmp(binaryNode, Request.GT);
}
@Override
public Node leaveLE(final BinaryNode binaryNode) {
return leaveCmp(binaryNode, Request.LE);
}
@Override
public Node leaveLT(final BinaryNode binaryNode) {
return leaveCmp(binaryNode, Request.LT);
}
@Override
public Node leaveMOD(final BinaryNode binaryNode) {
return leaveBinaryArith(binaryNode);
}
@Override
public Node leaveMUL(final BinaryNode binaryNode) {
return leaveBinaryArith(binaryNode);
}
@Override
public Node leaveNE(final BinaryNode binaryNode) {
return leaveCmp(binaryNode, Request.NE);
}
@Override
public Node leaveNE_STRICT(final BinaryNode binaryNode) {
return leaveCmp(binaryNode, Request.NE_STRICT);
}
@Override
public Node leaveOR(final BinaryNode binaryNode) {
return binaryNode;
}
@Override
public Node leaveSAR(final BinaryNode binaryNode) {
return leaveBinary(binaryNode, Type.INT, Type.INT);
}
@Override
public Node leaveSHL(final BinaryNode binaryNode) {
return leaveBinary(binaryNode, Type.INT, Type.INT);
}
@Override
public Node leaveSHR(final BinaryNode binaryNode) {
assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isLong() : "long coercion expected: " + binaryNode.getSymbol();
return leaveBinary(binaryNode, Type.INT, Type.INT);
}
@Override
public Node leaveSUB(final BinaryNode binaryNode) {
return leaveBinaryArith(binaryNode);
}
@Override
public boolean enterBlock(final Block block) {
updateSymbols(block);
return true;
}
@Override
public Node leaveCatchNode(final CatchNode catchNode) {
final Node exceptionCondition = catchNode.getExceptionCondition();
if (exceptionCondition != null) {
return catchNode.setExceptionCondition(convert(exceptionCondition, Type.BOOLEAN));
}
return catchNode;
}
@Override
public Node leaveExecuteNode(final ExecuteNode executeNode) {
temporarySymbols.reuse();
return executeNode.setExpression(discard(executeNode.getExpression()));
}
@Override
public Node leaveForNode(final ForNode forNode) {
final Node init = forNode.getInit();
final Node test = forNode.getTest();
final Node modify = forNode.getModify();
if (forNode.isForIn()) {
return forNode.setModify(lc, convert(forNode.getModify(), Type.OBJECT)); // NASHORN-400
}
assert test != null || forNode.hasGoto() : "forNode " + forNode + " needs goto and is missing it in " + lc.getCurrentFunction();
return forNode.
setInit(lc, init == null ? null : discard(init)).
setTest(lc, test == null ? null : convert(test, Type.BOOLEAN)).
setModify(lc, modify == null ? null : discard(modify));
}
@Override
public boolean enterFunctionNode(final FunctionNode functionNode) {
if (functionNode.isLazy()) {
return false;
}
// If the function doesn't need a callee, we ensure its __callee__ symbol doesn't get a slot. We can't do
// this earlier, as access to scoped variables, self symbol, etc. in previous phases can all trigger the
// need for the callee.
if (!functionNode.needsCallee()) {
functionNode.compilerConstant(CALLEE).setNeedsSlot(false);
}
// Similar reasoning applies to __scope__ symbol: if the function doesn't need either parent scope or its
// own scope, we ensure it doesn't get a slot, but we can't determine whether it needs a scope earlier than
// this phase.
if (!(functionNode.getBody().needsScope() || functionNode.needsParentScope())) {
functionNode.compilerConstant(SCOPE).setNeedsSlot(false);
}
return true;
}
@Override
public Node leaveFunctionNode(final FunctionNode functionNode) {
return functionNode.setState(lc, CompilationState.FINALIZED);
}
@Override
public Node leaveIfNode(final IfNode ifNode) {
return ifNode.setTest(convert(ifNode.getTest(), Type.BOOLEAN));
}
@SuppressWarnings("rawtypes")
@Override
public boolean enterLiteralNode(final LiteralNode literalNode) {
if (literalNode instanceof ArrayLiteralNode) {
final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode)literalNode;
final Node[] array = arrayLiteralNode.getValue();
final Type elementType = arrayLiteralNode.getElementType();
for (int i = 0; i < array.length; i++) {
final Node element = array[i];
if (element != null) {
array[i] = convert(element.accept(this), elementType);
}
}
}
return false;
}
@Override
public Node leaveReturnNode(final ReturnNode returnNode) {
final Node expr = returnNode.getExpression();
if (expr != null) {
return returnNode.setExpression(convert(expr, lc.getCurrentFunction().getReturnType()));
}
return returnNode;
}
@Override
public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
final List<Node> args = runtimeNode.getArgs();
for (final Node arg : args) {
assert !arg.getType().isUnknown();
}
return runtimeNode;
}
@Override
public Node leaveSwitchNode(final SwitchNode switchNode) {
final boolean allInteger = switchNode.getTag().getSymbolType().isInteger();
if (allInteger) {
return switchNode;
}
final Node expression = switchNode.getExpression();
final List<CaseNode> cases = switchNode.getCases();
final List<CaseNode> newCases = new ArrayList<>();
for (final CaseNode caseNode : cases) {
final Node test = caseNode.getTest();
newCases.add(test != null ? caseNode.setTest(convert(test, Type.OBJECT)) : caseNode);
}
return switchNode.
setExpression(lc, convert(expression, Type.OBJECT)).
setCases(lc, newCases);
}
@Override
public Node leaveTernaryNode(final TernaryNode ternaryNode) {
return ternaryNode.setLHS(convert(ternaryNode.lhs(), Type.BOOLEAN));
}
@Override
public Node leaveThrowNode(final ThrowNode throwNode) {
return throwNode.setExpression(convert(throwNode.getExpression(), Type.OBJECT));
}
@Override
public Node leaveVarNode(final VarNode varNode) {
final Node init = varNode.getInit();
if (init != null) {
final SpecializedNode specialized = specialize(varNode);
final VarNode specVarNode = (VarNode)specialized.node;
Type destType = specialized.type;
if (destType == null) {
destType = specVarNode.getType();
}
assert specVarNode.hasType() : specVarNode + " doesn't have a type";
final Node convertedInit = convert(init, destType);
temporarySymbols.reuse();
return specVarNode.setInit(convertedInit);
}
temporarySymbols.reuse();
return varNode;
}
@Override
public Node leaveWhileNode(final WhileNode whileNode) {
final Node test = whileNode.getTest();
if (test != null) {
return whileNode.setTest(lc, convert(test, Type.BOOLEAN));
}
return whileNode;
}
@Override
public Node leaveWithNode(final WithNode withNode) {
return withNode.setExpression(lc, convert(withNode.getExpression(), Type.OBJECT));
}
private static void updateSymbolsLog(final FunctionNode functionNode, final Symbol symbol, final boolean loseSlot) {
if (LOG.isEnabled()) {
if (!symbol.isScope()) {
LOG.finest("updateSymbols: ", symbol, " => scope, because all vars in ", functionNode.getName(), " are in scope");
}
if (loseSlot && symbol.hasSlot()) {
LOG.finest("updateSymbols: ", symbol, " => no slot, because all vars in ", functionNode.getName(), " are in scope");
}
}
}
/**
* Called after a block or function node (subclass of block) is finished. Guarantees
* that scope and slot information is correct for every symbol
* @param block block for which to to finalize type info.
*/
private void updateSymbols(final Block block) {
if (!block.needsScope()) {
return; // nothing to do
}
final FunctionNode functionNode = lc.getFunction(block);
final boolean allVarsInScope = functionNode.allVarsInScope();
final boolean isVarArg = functionNode.isVarArg();
for (final Symbol symbol : block.getSymbols()) {
if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) {
continue;
}
if (symbol.isVar()) {
if (allVarsInScope || symbol.isScope()) {
updateSymbolsLog(functionNode, symbol, true);
Symbol.setSymbolIsScope(lc, symbol);
symbol.setNeedsSlot(false);
} else {
assert symbol.hasSlot() : symbol + " should have a slot only, no scope";
}
} else if (symbol.isParam() && (allVarsInScope || isVarArg || symbol.isScope())) {
updateSymbolsLog(functionNode, symbol, isVarArg);
Symbol.setSymbolIsScope(lc, symbol);
symbol.setNeedsSlot(!isVarArg);
}
}
}
/**
* Exit a comparison node and do the appropriate replacements. We need to introduce runtime
* nodes late for comparisons as types aren't known until the last minute
*
* Both compares and adds may turn into runtimes node at this level as when we first bump
* into the op in Attr, we may type it according to what we know there, which may be wrong later
*
* e.g. i (int) < 5 -> normal compare
* i = object
* then the post pass that would add the conversion to the 5 needs to
*
* @param binaryNode binary node to leave
* @param request runtime request
* @return lowered cmp node
*/
@SuppressWarnings("fallthrough")
private Node leaveCmp(final BinaryNode binaryNode, final RuntimeNode.Request request) {
final Node lhs = binaryNode.lhs();
final Node rhs = binaryNode.rhs();
Type widest = Type.widest(lhs.getType(), rhs.getType());
boolean newRuntimeNode = false, finalized = false;
switch (request) {
case EQ_STRICT:
case NE_STRICT:
if (lhs.getType().isBoolean() != rhs.getType().isBoolean()) {
newRuntimeNode = true;
widest = Type.OBJECT;
finalized = true;
}
//fallthru
default:
if (newRuntimeNode || widest.isObject()) {
return new RuntimeNode(binaryNode, request).setIsFinal(finalized);
}
break;
}
return binaryNode.setLHS(convert(lhs, widest)).setRHS(convert(rhs, widest));
}
/**
* Compute the binary arithmetic type given the lhs and an rhs of a binary expression
* @param lhsType the lhs type
* @param rhsType the rhs type
* @return the correct binary type
*/
private static Type binaryArithType(final Type lhsType, final Type rhsType) {
if (!Compiler.shouldUseIntegerArithmetic()) {
return Type.NUMBER;
}
return Type.widest(lhsType, rhsType, Type.NUMBER);
}
private Node leaveBinaryArith(final BinaryNode binaryNode) {
final Type type = binaryArithType(binaryNode.lhs().getType(), binaryNode.rhs().getType());
return leaveBinary(binaryNode, type, type);
}
private Node leaveBinary(final BinaryNode binaryNode, final Type lhsType, final Type rhsType) {
Node b = binaryNode.setLHS(convert(binaryNode.lhs(), lhsType)).setRHS(convert(binaryNode.rhs(), rhsType));
return b;
}
/**
* A symbol (and {@link Property}) can be tagged as "may be primitive". This is
* used a hint for dual fields that it is even worth it to try representing this
* field as something other than java.lang.Object.
*
* @param node node in which to tag symbols as primitive
* @param to which primitive type to use for tagging
*/
private static void setCanBePrimitive(final Node node, final Type to) {
final HashSet<Node> exclude = new HashSet<>();
node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
private void setCanBePrimitive(final Symbol symbol) {
LOG.info("*** can be primitive symbol ", symbol, " ", Debug.id(symbol));
symbol.setCanBePrimitive(to);
}
@Override
public boolean enterIdentNode(final IdentNode identNode) {
if (!exclude.contains(identNode)) {
setCanBePrimitive(identNode.getSymbol());
}
return false;
}
@Override
public boolean enterAccessNode(final AccessNode accessNode) {
setCanBePrimitive(accessNode.getProperty().getSymbol());
return false;
}
@Override
public boolean enterIndexNode(final IndexNode indexNode) {
exclude.add(indexNode.getBase()); //prevent array base node to be flagged as primitive, but k in a[k++] is fine
return true;
}
});
}
private static class SpecializedNode {
final Node node;
final Type type;
SpecializedNode(Node node, Type type) {
this.node = node;
this.type = type;
}
}
<T extends Node> SpecializedNode specialize(final Assignment<T> assignment) {
final Node node = ((Node)assignment);
final T lhs = assignment.getAssignmentDest();
final Node rhs = assignment.getAssignmentSource();
if (!canHaveCallSiteType(lhs)) {
return new SpecializedNode(node, null);
}
final Type to;
if (node.isSelfModifying()) {
to = node.getWidestOperationType();
} else {
to = rhs.getType();
}
if (!isSupportedCallSiteType(to)) {
//meaningless to specialize to boolean or object
return new SpecializedNode(node, null);
}
final Node newNode = assignment.setAssignmentDest(setTypeOverride(lhs, to));
final Node typePropagatedNode = propagateType(newNode, to);
return new SpecializedNode(typePropagatedNode, to);
}
/**
* Is this a node that can have its type overridden. This is true for
* AccessNodes, IndexNodes and IdentNodes
*
* @param node the node to check
* @return true if node can have a callsite type
*/
private static boolean canHaveCallSiteType(final Node node) {
return node instanceof TypeOverride && ((TypeOverride<?>)node).canHaveCallSiteType();
}
/**
* Is the specialization type supported. Currently we treat booleans as objects
* and have no special boolean type accessor, thus booleans are ignored.
* TODO - support booleans? NASHORN-590
*
* @param castTo the type to check
* @return true if call site type is supported
*/
private static boolean isSupportedCallSiteType(final Type castTo) {
return castTo.isNumeric(); // don't specializable for boolean
}
/**
* Override the type of a node for e.g. access specialization of scope
* objects. Normally a variable can only get a wider type and narrower type
* sets are ignored. Not that a variable can still be on object type as
* per the type analysis, but a specific access may be narrower, e.g. if it
* is used in an arithmetic op. This overrides a type, regardless of
* type environment and is used primarily by the access specializer
*
* @param node node for which to change type
* @param to new type
*/
@SuppressWarnings("unchecked")
<T extends Node> T setTypeOverride(final T node, final Type to) {
final Type from = node.getType();
if (!node.getType().equals(to)) {
LOG.info("Changing call override type for '", node, "' from ", node.getType(), " to ", to);
if (!to.isObject() && from.isObject()) {
setCanBePrimitive(node, to);
}
}
LOG.info("Type override for lhs in '", node, "' => ", to);
return ((TypeOverride<T>)node).setType(temporarySymbols, lc, to);
}
/**
* Add an explicit conversion. This is needed when attribution has created types
* that do not mesh into an op type, e.g. a = b, where b is object and a is double
* at the end of Attr, needs explicit conversion logic.
*
* An explicit conversion can be one of the following:
* + Convert a literal - just replace it with another literal
* + Convert a scope object - just replace the type of the access, e.g. get()D->get()I
* + Explicit convert placement, e.g. a = (double)b - all other cases
*
* No other part of the world after {@link Attr} may introduce new symbols. This
* is the only place.
*
* @param node node to convert
* @param to destination type
* @return conversion node
*/
private Node convert(final Node node, final Type to) {
assert !to.isUnknown() : "unknown type for " + node + " class=" + node.getClass();
assert node != null : "node is null";
assert node.getSymbol() != null : "node " + node + " " + node.getClass() + " has no symbol! " + lc.getCurrentFunction();
assert node.tokenType() != TokenType.CONVERT : "assert convert in convert " + node + " in " + lc.getCurrentFunction();
final Type from = node.getType();
if (Type.areEquivalent(from, to)) {
return node;
}
if (from.isObject() && to.isObject()) {
return node;
}
Node resultNode = node;
if (node instanceof LiteralNode && !(node instanceof ArrayLiteralNode) && !to.isObject()) {
final LiteralNode<?> newNode = new LiteralNodeConstantEvaluator((LiteralNode<?>)node, to).eval();
if (newNode != null) {
resultNode = newNode;
}
} else {
if (canHaveCallSiteType(node) && isSupportedCallSiteType(to)) {
assert node instanceof TypeOverride;
return setTypeOverride(node, to);
}
resultNode = new UnaryNode(Token.recast(node.getToken(), TokenType.CONVERT), node);
}
LOG.info("CONVERT('", node, "', ", to, ") => '", resultNode, "'");
assert !node.isTerminal();
//This is the only place in this file that can create new temporaries
//FinalizeTypes may not introduce ANY node that is not a conversion.
return temporarySymbols.ensureSymbol(lc, to, resultNode);
}
private static Node discard(final Node node) {
if (node.getSymbol() != null) {
final Node discard = new UnaryNode(Token.recast(node.getToken(), TokenType.DISCARD), node);
//discard never has a symbol in the discard node - then it would be a nop
assert !node.isTerminal();
return discard;
}
// node has no result (symbol) so we can keep it the way it is
return node;
}
/**
* Whenever an expression like an addition or an assignment changes type, it
* may be that case that {@link Attr} created a symbol for an intermediate
* result of the expression, say for an addition. This also has to be updated
* if the expression type changes.
*
* Assignments use their lhs as node symbol, and in this case we can't modify
* it. Then {@link CodeGenerator#Store} needs to do an explicit conversion.
* This is happens very rarely.
*
* @param node
* @param to
*/
private Node propagateType(final Node node, final Type to) {
Symbol symbol = node.getSymbol();
if (symbol.isTemp() && symbol.getSymbolType() != to) {
symbol = symbol.setTypeOverrideShared(to, temporarySymbols);
LOG.info("Type override for temporary in '", node, "' => ", to);
}
return node.setSymbol(lc, symbol);
}
/**
* Determine if the outcome of + operator is a string.
*
* @param node Node to test.
* @return true if a string result.
*/
private boolean isAddString(final Node node) {
if (node instanceof BinaryNode && node.isTokenType(TokenType.ADD)) {
final BinaryNode binaryNode = (BinaryNode)node;
final Node lhs = binaryNode.lhs();
final Node rhs = binaryNode.rhs();
return isAddString(lhs) || isAddString(rhs);
}
return node instanceof LiteralNode<?> && ((LiteralNode<?>)node).isString();
}
/**
* Whenever an explicit conversion is needed and the convertee is a literal, we can
* just change the literal
*/
class LiteralNodeConstantEvaluator extends FoldConstants.ConstantEvaluator<LiteralNode<?>> {
private final Type type;
LiteralNodeConstantEvaluator(final LiteralNode<?> parent, final Type type) {
super(parent);
this.type = type;
}
@Override
protected LiteralNode<?> eval() {
final Object value = ((LiteralNode<?>)parent).getValue();
LiteralNode<?> literalNode = null;
if (type.isString()) {
literalNode = LiteralNode.newInstance(token, finish, JSType.toString(value));
} else if (type.isBoolean()) {
literalNode = LiteralNode.newInstance(token, finish, JSType.toBoolean(value));
} else if (type.isInteger()) {
literalNode = LiteralNode.newInstance(token, finish, JSType.toInt32(value));
} else if (type.isLong()) {
literalNode = LiteralNode.newInstance(token, finish, JSType.toLong(value));
} else if (type.isNumber() || parent.getType().isNumeric() && !parent.getType().isNumber()) {
literalNode = LiteralNode.newInstance(token, finish, JSType.toNumber(value));
}
if (literalNode != null) {
//inherit literal symbol for attr.
literalNode = (LiteralNode<?>)literalNode.setSymbol(lc, parent.getSymbol());
}
return literalNode;
}
}
}