view src/jdk/nashorn/internal/codegen/FinalizeTypes.java @ 143:4be452026847

8010652: Eliminate non-child references in Block/FunctionNode, and make few node types immutable Reviewed-by: jlaskey, lagergren
author attila
date Sat, 23 Mar 2013 00:58:39 +0100
parents e15806b9d716
children
line wrap: on
line source

/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.codegen;

import java.util.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.CallNode.EvalArgs;
import jdk.nashorn.internal.ir.CaseNode;
import jdk.nashorn.internal.ir.CatchNode;
import jdk.nashorn.internal.ir.DoWhileNode;
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.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 {

    private static final DebugLogger LOG = new DebugLogger("finalize");

    private final LexicalContext lexicalContext = new LexicalContext();

    FinalizeTypes() {
    }

    @Override
    public Node leaveCallNode(final CallNode callNode) {
        final EvalArgs evalArgs = callNode.getEvalArgs();
        if (evalArgs != null) {
            evalArgs.setCode(evalArgs.getCode().accept(this));
        }

        // 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();
        ((CallNode)unaryNode.rhs()).setIsNew();
        return unaryNode;
    }

    @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);
    }

    @Override
    public Node leaveBIT_AND(BinaryNode binaryNode) {
        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger() : "int coercion expected: " + binaryNode.getSymbol();
        return leaveBinary(binaryNode, Type.INT, Type.INT);
    }

    @Override
    public Node leaveBIT_OR(BinaryNode binaryNode) {
        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger() : "int coercion expected: " + binaryNode.getSymbol();
        return leaveBinary(binaryNode, Type.INT, Type.INT);
    }

    @Override
    public Node leaveBIT_XOR(BinaryNode binaryNode) {
        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger() : "int coercion expected: " + binaryNode.getSymbol();
        return leaveBinary(binaryNode, Type.INT, Type.INT);
    }

    @Override
    public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
        assert binaryNode.getSymbol() != null;
        final BinaryNode newBinaryNode = (BinaryNode)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
        propagateType(newBinaryNode, newBinaryNode.lhs().getType());
        return newBinaryNode;
    }

    @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
        propagateType(newBinaryNode, newBinaryNode.rhs().getType());
        return newBinaryNode;
    }

    @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 Node enterBlock(final Block block) {
        lexicalContext.push(block);
        updateSymbols(block);
        return block;
    }

    @Override
    public Node leaveBlock(Block block) {
        lexicalContext.pop(block);
        return super.leaveBlock(block);
    }

    @Override
    public Node leaveCatchNode(final CatchNode catchNode) {
        final Node exceptionCondition = catchNode.getExceptionCondition();
        if (exceptionCondition != null) {
            catchNode.setExceptionCondition(convert(exceptionCondition, Type.BOOLEAN));
        }
        return catchNode;
    }

    @Override
    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
        return enterWhileNode(doWhileNode);
    }

    @Override
    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
        return leaveWhileNode(doWhileNode);
    }

    @Override
    public Node leaveExecuteNode(final ExecuteNode executeNode) {
        executeNode.setExpression(discard(executeNode.getExpression()));
        return executeNode;
    }

    @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()) {
            forNode.setModify(convert(forNode.getModify(), Type.OBJECT)); // NASHORN-400
            return forNode;
        }

        if (init != null) {
            forNode.setInit(discard(init));
        }

        if (test != null) {
            forNode.setTest(convert(test, Type.BOOLEAN));
        } else {
            assert forNode.hasGoto() : "forNode " + forNode + " needs goto and is missing it in " + getCurrentFunctionNode();
        }

        if (modify != null) {
            forNode.setModify(discard(modify));
        }

        return forNode;
    }

    @Override
    public Node enterFunctionNode(final FunctionNode functionNode) {
        if (functionNode.isLazy()) {
            return null;
        }

        lexicalContext.push(functionNode);
        // 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.getCalleeNode().getSymbol().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.needsScope() || functionNode.needsParentScope())) {
            functionNode.getScopeNode().getSymbol().setNeedsSlot(false);
        }

        updateSymbols(functionNode);
        functionNode.setState(CompilationState.FINALIZED);

        return functionNode;
    }

    @Override
    public Node leaveFunctionNode(FunctionNode functionNode) {
        lexicalContext.pop(functionNode);
        return super.leaveFunctionNode(functionNode);
    }

    @Override
    public Node leaveIfNode(final IfNode ifNode) {
        ifNode.setTest(convert(ifNode.getTest(), Type.BOOLEAN));
        return ifNode;
    }

    @SuppressWarnings("rawtypes")
    @Override
    public Node 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 null;
    }

    @Override
    public Node leaveReturnNode(final ReturnNode returnNode) {
        final Node expr = returnNode.getExpression();
        if (expr != null) {
            returnNode.setExpression(convert(expr, getCurrentFunctionNode().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 Node           expression  = switchNode.getExpression();
        final List<CaseNode> cases       = switchNode.getCases();
        final boolean        allInteger  = switchNode.getTag().getSymbolType().isInteger();

        if (!allInteger) {
            switchNode.setExpression(convert(expression, Type.OBJECT));
            for (final CaseNode caseNode : cases) {
                final Node test = caseNode.getTest();
                if (test != null) {
                    caseNode.setTest(convert(test, Type.OBJECT));
                }
            }
        }

        return switchNode;
    }

    @Override
    public Node leaveTernaryNode(final TernaryNode ternaryNode) {
        return ternaryNode.setLHS(convert(ternaryNode.lhs(), Type.BOOLEAN));
    }

    @Override
    public Node leaveThrowNode(final ThrowNode throwNode) {
        throwNode.setExpression(convert(throwNode.getExpression(), Type.OBJECT));
        return throwNode;
    }

    @Override
    public Node leaveVarNode(final VarNode varNode) {
        final Node rhs = varNode.getInit();
        if (rhs != 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";
            return specVarNode.setInit(convert(rhs, destType));
        }
        return varNode;
    }

    @Override
    public Node leaveWhileNode(final WhileNode whileNode) {
        final Node test = whileNode.getTest();
        if (test != null) {
            whileNode.setTest(convert(test, Type.BOOLEAN));
        }
        return whileNode;
    }

    @Override
    public Node leaveWithNode(final WithNode withNode) {
        withNode.setExpression(convert(withNode.getExpression(), Type.OBJECT));
        return withNode;
    }

    private static void updateSymbolsLog(final FunctionNode functionNode, final Symbol symbol, final boolean loseSlot) {
        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 = lexicalContext.getFunction(block);
        assert !(block instanceof FunctionNode) || functionNode == block;

        final List<Symbol> symbols        = block.getFrame().getSymbols();
        final boolean      allVarsInScope = functionNode.allVarsInScope();
        final boolean      isVarArg       = functionNode.isVarArg();

        for (final Symbol symbol : symbols) {
            if (symbol.isInternal() || symbol.isThis()) {
                continue;
            }

            if (symbol.isVar()) {
                if (allVarsInScope || symbol.isScope()) {
                    updateSymbolsLog(functionNode, symbol, true);
                    symbol.setIsScope();
                    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.setIsScope();
                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()) {
                final RuntimeNode runtimeNode = new RuntimeNode(binaryNode, request);
                if (finalized) {
                    runtimeNode.setIsFinal();
                }
                return runtimeNode;
            }
            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) {
        return binaryNode.setLHS(convert(binaryNode.lhs(), lhsType)).setRHS(convert(binaryNode.rhs(), rhsType));
    }

    /**
     * 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() {
            private void setCanBePrimitive(final Symbol symbol) {
                LOG.info("*** can be primitive symbol " + symbol + " " + Debug.id(symbol));
                symbol.setCanBePrimitive(to);
            }

            @Override
            public Node enterIdentNode(final IdentNode identNode) {
                if (!exclude.contains(identNode)) {
                    setCanBePrimitive(identNode.getSymbol());
                }
                return null;
            }

            @Override
            public Node enterAccessNode(final AccessNode accessNode) {
                setCanBePrimitive(accessNode.getProperty().getSymbol());
                return null;
            }

            @Override
            public Node 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 indexNode;
            }
        });
    }

    private static class SpecializedNode {
        final Node node;
        final Type type;

        SpecializedNode(Node node, Type type) {
            this.node = node;
            this.type = type;
        }
    }

    private static <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));
        propagateType(newNode, to);

        return new SpecializedNode(newNode, 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")
    private static <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(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 + " has no symbol!";
        assert node.tokenType() != TokenType.CONVERT : "assert convert in convert " + node + " in " + getCurrentFunctionNode();

        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 && !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(node.getSource(), Token.recast(node.getToken(), TokenType.CONVERT), node);
        }

        LOG.info("CONVERT('" + node + "', " + to + ") => '" + resultNode + "'");

        //This is the only place in this file that can create new temporaries
        //FinalizeTypes may not introduce ANY node that is not a conversion.
        getCurrentFunctionNode().newTemporary(getCurrentBlock().getFrame(), to, resultNode);
        resultNode.copyTerminalFlags(node);

        return resultNode;
    }

    private static Node discard(final Node node) {
        node.setDiscard(true);

        if (node.getSymbol() != null) {
            final Node discard = new UnaryNode(node.getSource(), Token.recast(node.getToken(), TokenType.DISCARD), node);
            //discard never has a symbol in the discard node - then it would be a nop
            discard.copyTerminalFlags(node);
            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 static void propagateType(final Node node, final Type to) {
        final Symbol symbol = node.getSymbol();
        if (symbol.isTemp()) {
            symbol.setTypeOverride(to);
            LOG.info("Type override for temporary in '" + node + "' => " + to);
        }
    }

    /**
     * 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
     */
    static 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(source, token, finish, JSType.toString(value));
            } else if (type.isBoolean()) {
                literalNode = LiteralNode.newInstance(source, token, finish, JSType.toBoolean(value));
            } else if (type.isInteger()) {
                literalNode = LiteralNode.newInstance(source, token, finish, JSType.toInt32(value));
            } else if (type.isLong()) {
                literalNode = LiteralNode.newInstance(source, token, finish, JSType.toLong(value));
            } else if (type.isNumber() || parent.getType().isNumeric() && !parent.getType().isNumber()) {
                literalNode = LiteralNode.newInstance(source, token, finish, JSType.toNumber(value));
            }

            if (literalNode != null) {
                //inherit literal symbol for attr.
                literalNode.setSymbol(parent.getSymbol());
            }

            return literalNode;
        }
    }
}