diff --git a/nashorn/project.properties b/nashorn/project.properties
--- a/nashorn/project.properties
+++ b/nashorn/project.properties
@@ -139,8 +139,8 @@
 
 #  -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMethods
 # add '-Dtest.js.outofprocess' to run each test in a new sub-process
-run.test.jvmargs=-server -XX:-TieredCompilation -esa -ea -Dnashorn.debug=true -Dfile.encoding=UTF-8
-run.test.jvmargs.v8=-Xms2G -Xmx2G ${run.test.jvmargs}
+run.test.jvmargs=-server -XX:-TieredCompilation -esa -ea -Dnashorn.debug=true -Dfile.encoding=UTF-8 -Dnashorn.option.optimize=true
+run.test.jvmargs.v8=-Xms2G -Xmx2G ${run.test.jvmargs} -Dnashorn.option.optimize=true
 
 run.test.jvmsecurityargs=-Xverify:all -Djava.security.manager -Djava.security.policy=${basedir}/build/nashorn.policy
 #run.test.jvmsecurityargs=
diff --git a/nashorn/src/com/oracle/nashorn/codegen/opt/Optimizer.java b/nashorn/src/com/oracle/nashorn/codegen/opt/Optimizer.java
--- a/nashorn/src/com/oracle/nashorn/codegen/opt/Optimizer.java
+++ b/nashorn/src/com/oracle/nashorn/codegen/opt/Optimizer.java
@@ -70,7 +70,7 @@
         this.compiler     = compiler;
         this.functionNode = functionNode;
         transforms   = new ArrayList<>();
-        //registerTransform(new Ranges(functionNode));
+        registerTransform(new RangeAnalyzer(functionNode));
         assert "runScript".equals(functionNode.getName()) : "root node must be given to optimzer!";
     }
 
@@ -192,7 +192,7 @@
             }
         }
 
-        transforms.clear();
+        //transforms.clear();
     }
 
 }
diff --git a/nashorn/src/com/oracle/nashorn/codegen/opt/Range.java b/nashorn/src/com/oracle/nashorn/codegen/opt/Range.java
new file mode 100644
--- /dev/null
+++ b/nashorn/src/com/oracle/nashorn/codegen/opt/Range.java
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2010-2012, 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 com.oracle.nashorn.codegen.opt;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.oracle.nashorn.codegen.Type;
+
+/**
+ * Represents the value range of a symbol.
+ */
+public final class Range {
+    private final long min;
+    private final long max;
+    private final Type type;
+
+    private Range(Type type) {
+        this.type = type;
+        if (type.isInteger()) {
+            this.min = Integer.MIN_VALUE;
+            this.max = Integer.MAX_VALUE;
+        } else if (type.isLong()) {
+            this.min = Long.MIN_VALUE;
+            this.max = Long.MAX_VALUE;
+        } else if (type.isBoolean()) {
+            this.min = 0;
+            this.max = 1;
+        } else {
+            this.min = 0;
+            this.max = -1;
+        }
+    }
+
+    private Range(long min, long max) {
+        assert min <= max;
+        this.min = min;
+        this.max = max;
+        this.type = typeFromRange(min, max);
+    }
+
+    private static Type typeFromRange(long from, long to) {
+        if (from >= Integer.MIN_VALUE && to <= Integer.MAX_VALUE) {
+            return Type.INT;
+        } else {
+            return Type.LONG;
+        }
+    }
+
+    public Type getType() {
+        return type;
+    }
+
+    public long getMin() {
+        return min;
+    }
+
+    public long getMax() {
+        return max;
+    }
+
+    public boolean isIntegerType() {
+        return type.isInteger() || type.isLong();
+    }
+
+    public boolean isIntegerConst() {
+        return isIntegerType() && getMin() == getMax();
+    }
+
+    public boolean isUnknown() {
+        return type.isUnknown();
+    }
+
+    public static Range createUnknownRange() {
+        return new Range(Type.UNKNOWN);
+    }
+    public static Range createRange(int value) {
+        return new Range(value, value);
+    }
+    public static Range createRange(long value) {
+        return new Range(value, value);
+    }
+    public static Range createRange(double value) {
+        if (isRepresentableAsLong(value)) {
+            return new Range((long) value, (long) value);
+        }
+        return new Range(Type.NUMBER);
+    }
+    public static Range createRange(Object value) {
+        if (value instanceof Integer) {
+            return createRange((int) value);
+        } else if (value instanceof Long) {
+            return createRange((long) value);
+        } else if (value instanceof Double) {
+            return createRange((double) value);
+        }
+
+        return new Range(Type.OBJECT);
+    }
+
+    public static Range createGenericRange() {
+        return createRange(Type.OBJECT);
+    }
+
+    public static Range createTypeRange(Type type) {
+        return new Range(type);
+    }
+
+    public static Range join(Range a, Range b) {
+        if (a.equals(b)) {
+            return a;
+        }
+
+        Optimizer.info("range::join " + a + ", " + b);
+        Type joinedType = a.getType();
+        if (a.getType() != b.getType()) {
+            if (a.isUnknown()) {
+                return b;
+            }
+            if (b.isUnknown()) {
+                return a;
+            }
+
+            joinedType = Type.widest(a.getType(), b.getType());
+        }
+
+        if (joinedType.isInteger() || joinedType.isLong()) {
+            return new Range(Math.min(a.getMin(), b.getMin()), Math.max(a.getMax(), b.getMax()));
+        }
+        return new Range(joinedType);
+    }
+
+    private static boolean checkAdd(long a, long b) {
+        final long result = a + b;
+        return ((a ^ result) & (b ^ result)) >= 0;
+    }
+
+    private static boolean checkSub(long a, long b) {
+        final long result = a - b;
+        return ((a ^ result) & (b ^ result)) >= 0;
+    }
+
+    private static boolean checkMul(long a, long b) {
+        // TODO correct overflow check
+        if (Integer.MIN_VALUE >= a && a <= Integer.MAX_VALUE
+         && Integer.MIN_VALUE >= b && b <= Integer.MAX_VALUE) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public static Range add(Range a, Range b) {
+        Optimizer.info("range::add " + a + " + " + b);
+        if (a.isIntegerType() && b.isIntegerType()) {
+            if (checkAdd(a.getMin(), b.getMin()) && checkAdd(a.getMax(), b.getMax())) {
+                return new Range(a.getMin() + b.getMin(), a.getMax() + b.getMax());
+            }
+        }
+        if (a.getType().isNumeric() && b.getType().isNumeric()) {
+            return new Range(Type.NUMBER);
+        }
+        return createGenericRange();
+    }
+
+    public static Range sub(Range a, Range b) {
+        Optimizer.info("range::sub " + a + " - " + b);
+        if (a.isIntegerType() && b.isIntegerType()) {
+            if (checkSub(a.getMin(), b.getMax()) && checkSub(a.getMax(), b.getMin())) {
+                return new Range(a.getMin() - b.getMax(), a.getMax() - b.getMin());
+            }
+        }
+        if (a.getType().isNumeric() && b.getType().isNumeric()) {
+            return new Range(Type.NUMBER);
+        }
+        return createGenericRange();
+    }
+
+
+    public static Range mul(Range a, Range b) {
+        Optimizer.info("range::mul " + a + " * " + b);
+        if (a.isIntegerType() && b.isIntegerType()) {
+            if (checkMul(a.getMin(), b.getMin()) && checkMul(a.getMax(), b.getMax()) &&
+                checkMul(a.getMin(), b.getMax()) && checkMul(a.getMax(), b.getMin())) {
+                List<Long> results = Arrays.asList(a.getMin() * b.getMin(), a.getMin() * b.getMax(),
+                                                   a.getMax() * b.getMin(), a.getMax() * b.getMax());
+                return new Range(Collections.min(results), Collections.max(results));
+            }
+        }
+        if (a.getType().isNumeric() && b.getType().isNumeric()) {
+            return new Range(Type.NUMBER);
+        }
+        return createGenericRange();
+    }
+
+    public static Range neg(Range a) {
+        Optimizer.info("range::neg " + a);
+        if (a.isIntegerType()) {
+            if (a.getMin() != Long.MIN_VALUE && a.getMax() != Long.MIN_VALUE) {
+                return new Range(-a.getMax(), -a.getMin());
+            }
+        }
+        if (a.getType().isNumeric()) {
+            return new Range(Type.NUMBER);
+        }
+        return createGenericRange();
+    }
+
+    /** any operation that has no equivalent in Range. */
+    public static Range any(Range a, Range b) {
+        Optimizer.info("range::any " + a + ", " + b);
+        return createGenericRange();
+    }
+
+    public static Range and(Range a, Range b) {
+        Optimizer.info("range::and " + a + " & " + b);
+        if (a.isIntegerType() && b.isIntegerType()) {
+            int resultMask = (int) (a.getBitMask() & b.getBitMask());
+            if (resultMask > 0) {
+                return new Range(0, resultMask);
+            }
+        } else if (a.isUnknown() && b.isIntegerType()) {
+            return b;
+        } else if (a.isIntegerType() && b.isUnknown()) {
+            return a;
+        }
+
+        return createTypeRange(Type.INT);
+    }
+    public static Range or(Range a, Range b) {
+        Optimizer.info("range::or " + a + " | " + b);
+        if (a.isIntegerType() && b.isIntegerType()) {
+            int resultMask = (int) (a.getBitMask() | b.getBitMask());
+            if (resultMask > 0) {
+                return new Range(0, resultMask);
+            }
+        }
+
+        return createTypeRange(Type.INT);
+    }
+    public static Range xor(Range a, Range b) {
+        Optimizer.info("range::xor " + a + " ^ " + b);
+        if (a.isIntegerConst() && b.isIntegerConst()) {
+            return createRange(a.getMin() ^ b.getMin());
+        }
+        if (a.isIntegerType() && b.isIntegerType()) {
+            int resultMask = (int) (a.getBitMask() | b.getBitMask());
+            if (resultMask > 0) {
+                return new Range(0, new Range(0, resultMask).getBitMask());
+            }
+        }
+        return createTypeRange(Type.INT);
+    }
+
+    public static Range shl(Range a, Range b) {
+        Optimizer.info("range::shl " + a + " << " + b);
+        if (b.isIntegerType() && b.isIntegerConst()) {
+            int shift = (int)b.getMin() & 0x1f;
+            Range left = a.isIntegerType() ? a : createTypeRange(Type.INT);
+            int min = (int)left.getMin() << shift;
+            int max = (int)left.getMax() << shift;
+            if (min >> shift == left.getMin() && max >> shift == left.getMax()) {
+                return new Range(min, max);
+            }
+        }
+
+        return createTypeRange(Type.INT);
+    }
+    public static Range shr(Range a, Range b) {
+        Optimizer.info("range::shr " + a + " >>> " + b);
+        if (b.isIntegerType() && b.isIntegerConst()) {
+            long shift = b.getMin() & 0x1f;
+            Range left = a.isIntegerType() ? a : createTypeRange(Type.INT);
+            if (left.getMin() >= 0) {
+                long min = left.getMin() >>> shift;
+                long max = left.getMax() >>> shift;
+                return new Range(min, max);
+            } else if (shift >= 1) {
+                return new Range(0, 0xffffffff >>> shift);
+            }
+        }
+
+        return createTypeRange(Type.INT);
+    }
+    public static Range sar(Range a, Range b) {
+        Optimizer.info("range::sar " + a + " >> " + b);
+        if (b.isIntegerType() && b.isIntegerConst()) {
+            long shift = b.getMin() & 0x1f;
+            Range left = a.isIntegerType() ? a : createTypeRange(Type.INT);
+            long min = left.getMin() >> shift;
+            long max = left.getMax() >> shift;
+            return new Range(min, max);
+        }
+
+        return createTypeRange(Type.INT);
+    }
+
+    public static Range mod(Range a, Range b) {
+        Optimizer.info("range::mod " + a + " / " + b);
+        if (a.isIntegerType() && b.isIntegerType()) {
+            if (b.getMin() > 0 || b.getMax() < 0) { // divisor range must not include 0
+                long absmax = Math.max(Math.abs(b.getMin()), Math.abs(b.getMax())) - 1;
+                return new Range(b.getMin() > 0 ? 0 : -absmax, b.getMax() < 0 ? 0 : +absmax);
+            }
+        }
+        return createTypeRange(Type.NUMBER);
+    }
+
+    public static Range div(Range a, Range b) {
+        // TODO
+        return createTypeRange(Type.NUMBER);
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("RANGE:").append(type).append("");
+        if (isIntegerType()) {
+            sb.append("[").append(min).append(":").append(max).append("]");
+        }
+        return sb.toString();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj instanceof Range) {
+            Range other = (Range) obj;
+            return this.type == other.type && this.min == other.min && this.max == other.max;
+        }
+        return false;
+    }
+
+    public long getBitMask() {
+        if (min == max) {
+            return min;
+        }
+        if (min < 0) {
+            return ~0L;
+        }
+        long mask = 1;
+        while (mask < max) {
+            mask = (mask << 1) | 1;
+        }
+        return mask;
+    }
+
+    public static boolean isRepresentableAsInt(double number) {
+        return (int)number == number && !isNegativeZero(number);
+    }
+    public static boolean isRepresentableAsLong(double number) {
+        return (long)number == number && !isNegativeZero(number);
+    }
+    private static boolean isNegativeZero(double number) {
+        return Double.doubleToLongBits(number) == Double.doubleToLongBits(-0.0);
+    }
+}
diff --git a/nashorn/src/com/oracle/nashorn/codegen/opt/RangeAnalyzer.java b/nashorn/src/com/oracle/nashorn/codegen/opt/RangeAnalyzer.java
new file mode 100644
--- /dev/null
+++ b/nashorn/src/com/oracle/nashorn/codegen/opt/RangeAnalyzer.java
@@ -0,0 +1,704 @@
+/*
+ * Copyright (c) 2010-2012, 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 com.oracle.nashorn.codegen.opt;
+
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.Deque;
+import java.util.EnumSet;
+import java.util.HashSet;
+import java.util.Set;
+
+import com.oracle.nashorn.codegen.Type;
+import com.oracle.nashorn.codegen.opt.Optimizer.OptimizerException;
+import com.oracle.nashorn.ir.BinaryNode;
+import com.oracle.nashorn.ir.DoWhileNode;
+import com.oracle.nashorn.ir.ForNode;
+import com.oracle.nashorn.ir.FunctionNode;
+import com.oracle.nashorn.ir.IdentNode;
+import com.oracle.nashorn.ir.LiteralNode;
+import com.oracle.nashorn.ir.LiteralNode.ArrayLiteralNode;
+import com.oracle.nashorn.ir.Node;
+import com.oracle.nashorn.ir.RuntimeNode;
+import com.oracle.nashorn.ir.Symbol;
+import com.oracle.nashorn.ir.UnaryNode;
+import com.oracle.nashorn.ir.VarNode;
+import com.oracle.nashorn.ir.WhileNode;
+import com.oracle.nashorn.ir.visitor.NodeVisitor;
+import com.oracle.nashorn.parser.Token;
+import com.oracle.nashorn.parser.TokenType;
+
+/**
+ * Range analysis and narrowing of type where it can be proven
+ * that there is no spillover, e.g.
+ *
+ *  function func(c) {
+ *    var v = c & 0xfff;
+ *    var w = c & 0xeee;
+ *    var x = v * w;
+ *    return x;
+ *  }
+ *
+ *  Proves that the multiplication never exceeds 24 bits and can thus be an int
+ *
+ *  TODO: this analysis is currently not enabled
+ */
+
+public class RangeAnalyzer implements Transform<FunctionNode> {
+
+    //private final FunctionNode root;
+
+    public RangeAnalyzer(final FunctionNode root) {
+        //this.root = root;
+    }
+
+    @Override
+    public boolean apply(final FunctionNode node) throws OptimizerException {
+        Optimizer.info("FUNCTION " + node.getName());
+        return !narrowSymbols(node).isEmpty();
+    }
+
+    @Override
+    public boolean appliesTo(final FunctionNode functionNode) {
+        return functionNode.isLowered() && !functionNode.needsScope();
+    }
+
+    @SuppressWarnings("unused")
+    private static int msb(final int c) {
+        //highest bit set
+        for(int msb = 31; msb >= 0; msb--) {
+            if ((1 << msb & c) != 0) {
+                return msb + 1;
+            }
+        }
+        if (c == 0) {
+            return 0;
+        }
+        return 32;
+    }
+
+    private static boolean isComparison(final TokenType tokenType) {
+        switch (tokenType) {
+        case EQ:
+        case EQ_STRICT:
+        case NE:
+        case NE_STRICT:
+        case LE:
+        case LT:
+        case GE:
+        case GT:
+            return true;
+        default:
+            return false;
+        }
+    }
+
+    private Collection<Symbol> narrowSymbols(final FunctionNode functionNode) {
+        final Set<Symbol> modified = new HashSet<>();
+        final Set<Symbol> narrowable = new HashSet<>();
+
+        functionNode.accept(new NodeVisitor() {
+
+            private Deque<Node> loopStack = new ArrayDeque<>();
+            private Deque<Symbol> loopCounterStack = new ArrayDeque<>();
+            private boolean isLoopPrePass;
+
+            @SuppressWarnings("rawtypes")
+            private Symbol findLoopCounter(Node node) {
+                if (node instanceof WhileNode) {
+                    Node test = ((WhileNode) node).getTest();
+                    if (test instanceof UnaryNode && test.isTokenType(TokenType.CONVERT)) {
+                        test = ((UnaryNode) test).rhs();
+                    }
+                    if (test instanceof BinaryNode && isComparison(test.tokenType())) {
+                        BinaryNode binaryNode = (BinaryNode) test;
+                        Node lhs = binaryNode.lhs();
+                        if (lhs instanceof UnaryNode && lhs.tokenType() == TokenType.CONVERT) {
+                            lhs = ((UnaryNode)lhs).rhs();
+                        }
+                        Node rhs = binaryNode.rhs();
+                        if (lhs instanceof IdentNode && !lhs.getSymbol().isScope() &&
+                                rhs instanceof LiteralNode && ((LiteralNode) rhs).getType().isInteger()) {
+                            Symbol symbol = lhs.getSymbol();
+                            int margin = ((LiteralNode) rhs).getInt32();
+                            TokenType op = test.tokenType();
+                            if (op == TokenType.LT || op == TokenType.LE) {
+                                symbol.setRange(Range.join(symbol.getRange(), Range.createRange(op == TokenType.LT ? margin - 1 : margin)));
+                                return symbol;
+                            }
+//                            else if (op == TokenType.GT || op == TokenType.GE) {
+//                                setRange(lhs, Range.createRange(op == TokenType.GT ? margin + 1 : margin));
+//                                return symbol;
+//                            }
+                        }
+                    }
+                }
+                return null;
+            }
+
+            private boolean setRange(final Node dest, final Range range) {
+                if (range.isUnknown()) {
+                    return false;
+                }
+
+                final Symbol symbol = dest.getSymbol();
+
+                final Range symRange = Range.join(symbol.getRange(), range);
+                if (!symRange.equals(symbol.getRange())) {
+                    if (isLoopPrePass()) {
+                        // in the loop pre-pass, symbols assigned in the loop are invalidated; no ranges are set
+                        if (inLoop() && dest instanceof IdentNode) { //&& !dest.isTokenType(TokenType.CONVERT) //!(dest instanceof LiteralNode)) {
+//                            if (symbol.getRange().isUnknown()) {
+//                                return false;
+//                            }
+                            Optimizer.info("XXX symbol assigned in loop: " + symbol + ". invalidate range (was " + symbol.getRange() + ")");
+                            symbol.setRange(Range.createGenericRange());
+                            return true;
+                        }
+                        return false;
+                    }
+
+                    Optimizer.info("Modify range for " + symbol + " from " + symbol.getRange() + " to " + symRange + " (in node = " + dest + ")" );
+                    symbol.setRange(symRange);
+
+                    if (symbol.getSymbolType().isNumber() && symRange.getType().isInteger()) {
+                        narrowable.add(symbol);
+                    } else {
+                        narrowable.remove(symbol);
+                    }
+                    return true;
+                }
+
+                return false;
+            }
+
+            private boolean isLoopPrePass() {
+                return isLoopPrePass;
+            }
+
+            private Node visitLoop(final Node node) {
+                if (loopStack.peek() == node) {
+                    return node;
+                }
+
+                int loopLevel = loopStack.size() + 1;
+
+                pushLoop(node);
+                Optimizer.info(loopLevel + " LOOP BEGIN");
+                try {
+                    if (!isLoopPrePass) {
+                        try {
+                            Optimizer.info(loopLevel + " LOOP PREPASS BEGIN");
+                            isLoopPrePass = true;
+                            node.accept(this);
+                        } finally {
+                            isLoopPrePass = false;
+                            Optimizer.info(loopLevel + " LOOP PREPASS END");
+                        }
+                    }
+
+                    node.accept(this);
+                } finally {
+                    Optimizer.info(loopLevel + " LOOP END");
+                    popLoop();
+                }
+                return null;
+            }
+
+            @Override
+            public Node enter(final DoWhileNode node) {
+                return visitLoop(node);
+            }
+
+            @Override
+            public Node enter(final WhileNode node) {
+                return visitLoop(node);
+            }
+
+            @Override
+            public Node enter(final ForNode node) {
+                return visitLoop(node);
+            }
+
+            private void pushLoop(Node node) {
+                loopStack.push(node);
+                Symbol counter = findLoopCounter(node);
+                loopCounterStack.push(counter == null ? new Symbol("null", 0, Type.UNKNOWN) : counter);
+            }
+            private void popLoop() {
+                loopStack.pop();
+                loopCounterStack.pop();
+            }
+            private boolean inLoop() {
+                return !loopStack.isEmpty();
+            }
+
+            @Override
+            public Node leave(final BinaryNode node) {
+                boolean changed = false;
+                Range range;
+                switch (node.tokenType()) {
+                case ADD:
+                    range = Range.add(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+                case SUB:
+                    range = Range.sub(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+                case MUL:
+                    range = Range.mul(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+
+                case DIV:
+                    range = Range.div(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+                case MOD:
+                    range = Range.mod(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+
+                case BIT_AND:
+                    range = Range.and(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+                case BIT_OR:
+                    range = Range.or(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+                case BIT_XOR:
+                    range = Range.xor(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+                case SHR:
+                    range = Range.shr(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+                case SAR:
+                    range = Range.sar(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+                case SHL:
+                    range = Range.shl(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node, range);
+                    break;
+
+                case EQ:
+                case EQ_STRICT:
+                case NE:
+                case NE_STRICT:
+                case GE:
+                case GT:
+                case LE:
+                case LT:
+                    range = Range.createTypeRange(Type.BOOLEAN);
+                    changed |= setRange(node, range);
+                    break;
+
+                case ASSIGN:
+                    Optimizer.info("Looking at assign " + node);
+                    range = node.rhs().getSymbol().getRange();
+                    range = range.isUnknown() ? Range.createGenericRange() : range;
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_ADD:
+                    range = Range.add(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_SUB:
+                    range = Range.sub(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_MUL:
+                    range = Range.mul(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_DIV:
+                    range = Range.createTypeRange(Type.NUMBER);
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_MOD:
+                    range = Range.createTypeRange(Type.NUMBER);
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_BIT_AND:
+                    range = Range.and(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_BIT_OR:
+                    range = Range.or(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_BIT_XOR:
+                    range = Range.createTypeRange(Type.INT);
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_SAR:
+                    range = Range.sar(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_SHL:
+                    range = Range.shl(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+                case ASSIGN_SHR:
+                    range = Range.shr(node.lhs().getSymbol().getRange(), node.rhs().getSymbol().getRange());
+                    changed |= setRange(node.lhs(), range);
+                    changed |= setRange(node, range);
+                    break;
+
+                default:
+                    range = Range.createGenericRange();
+                    changed |= setRange(node, range);
+                    break;
+                }
+
+                if (changed) {
+                    modified.add(node.getSymbol());
+                }
+
+                return node;
+            }
+
+            @Override
+            public Node leave(final UnaryNode node) {
+                boolean changed = false;
+                Range range;
+                switch (node.tokenType()) {
+                case CONVERT:
+                    range = node.rhs().getSymbol().getRange();
+                    break;
+                case DECPREFIX:
+                    range = node.rhs().getSymbol().getRange();
+                    range = Range.sub(range, Range.createRange(1));
+                    setRange(node.rhs(), range);
+                    break;
+                case DECPOSTFIX:
+                    range = node.rhs().getSymbol().getRange();
+                    setRange(node.rhs(), Range.sub(range, Range.createRange(1)));
+                    break;
+                case INCPREFIX:
+                    range = node.rhs().getSymbol().getRange();
+                    if (loopCounterStack.peek() == node.rhs().getSymbol()) {
+                        if (isLoopPrePass) {
+                            node.rhs().getSymbol().setRange(Range.join(node.rhs().getSymbol().getRange(), Range.add(node.rhs().getSymbol().getRange(), Range.createRange(1))));
+                        }
+                        break;
+                    }
+
+                    range = Range.add(range, Range.createRange(1));
+                    setRange(node.rhs(), range);
+                    break;
+                case INCPOSTFIX:
+                    range = node.rhs().getSymbol().getRange();
+                    if (loopCounterStack.peek() == node.rhs().getSymbol()) {
+                        if (isLoopPrePass) {
+                            node.rhs().getSymbol().setRange(Range.join(node.rhs().getSymbol().getRange(), Range.add(node.rhs().getSymbol().getRange(), Range.createRange(1))));
+                        }
+                        break;
+                    }
+
+                    setRange(node.rhs(), Range.add(range, Range.createRange(1)));
+                    break;
+                case ADD:
+                    range = node.rhs().getSymbol().getRange();
+                    range = range.getType().isNumeric() ? range : Range.createTypeRange(Type.NUMBER);
+                    break;
+                case BIT_NOT:
+                    range = Range.createTypeRange(Type.INT);
+                    break;
+                case NOT:
+                    range = Range.createTypeRange(Type.BOOLEAN);
+                    break;
+                case SUB:
+                    range = Range.neg(node.rhs().getSymbol().getRange());
+                    break;
+                case DELETE:
+                case DISCARD:
+                case NEW:
+                case TYPEOF:
+                case VOID:
+                    return node;
+                default:
+                    //TODO
+                    range = Range.createGenericRange();
+                    break;
+                }
+
+                changed |= setRange(node, range);
+                if (changed) {
+                    modified.add(node.getSymbol());
+                }
+
+                return node;
+            }
+
+            @Override
+            public Node leave(@SuppressWarnings("rawtypes") LiteralNode node) {
+                final boolean changed;
+                if (node.getType().isInteger()) {
+                    changed = setRange(node, Range.createRange(node.getInt32()));
+                } else if (node.getType().isNumber()) {
+                    changed = setRange(node, Range.createRange(node.getNumber()));
+                } else if (node.getType().isLong()) {
+                    changed = setRange(node, Range.createRange(node.getLong()));
+                } else if (node.getType().isBoolean()) {
+                    changed = setRange(node, Range.createTypeRange(Type.BOOLEAN));
+                } else {
+                    changed = setRange(node, Range.createGenericRange());
+                }
+                if (changed) {
+                    Optimizer.info("LITERAL " + node);
+                    modified.add(node.getSymbol());
+                }
+
+                return  node;
+            }
+
+            @Override
+            public Node leave(final VarNode node) {
+                if (node.isAssignment()) {
+                    Optimizer.info("Looking at assign " + node);
+                    Range range = node.getInit().getSymbol().getRange();
+                    range = range.isUnknown() ? Range.createGenericRange() : range;
+                    Optimizer.info("Range for init =  " + range + " " + node.getInit().getSymbol());
+
+                    boolean changed = false;
+                    changed |= setRange(node.getName(), range);
+                    changed |= setRange(node, range);
+
+                    if (changed) {
+                        modified.add(node.getSymbol());
+                    }
+                }
+
+                return node;
+            }
+
+            @SuppressWarnings("rawtypes")
+            @Override
+            public Node enter(LiteralNode node) {
+                if (node instanceof ArrayLiteralNode) {
+                    return null;
+                }
+                return node;
+            }
+
+            @Override
+            public Node enter(RuntimeNode node) {
+                return node.getRequest().canSpecialize() ? null : node;
+            }
+
+            @Override
+            public Node enter(FunctionNode node) {
+                return node == functionNode ? node : null;
+            }
+        });
+
+        if (!narrowable.isEmpty()) {
+            Optimizer.info("Narrowables: "  + narrowable);
+
+            for (Symbol symbol : narrowable) {
+                if (symbol.getSymbolType().isNumber() && symbol.getRange().getType().isInteger()) {
+                    Optimizer.info("Overriding type of " + symbol + " with INT (was " + symbol.getSymbolType() + ") " + symbol.getRange());
+                    symbol.setTypeOverride(Type.INT);
+                }
+            }
+
+            // fix types if necessary
+            functionNode.accept(new NodeVisitor() {
+
+                /*
+                @Override
+                public Node visit(RuntimeNode node) {
+                    if (onset) {
+                        return node;
+                    }
+
+                    EnumSet<TokenType> comparisons = EnumSet.of(TokenType.LT, TokenType.LE, TokenType.GT, TokenType.GE, TokenType.EQ, TokenType.EQ_STRICT, TokenType.NE, TokenType.NE_STRICT);
+                    if (comparisons.contains(node.tokenType())) {
+                        Node lhs = node.getArgs().get(0);
+                        Node rhs = node.getArgs().get(1);
+                        Optimizer.info(" rcomparison: " + lhs.getSymbol().getType() + ", " + rhs.getSymbol().getType());
+                        if (Type.isNumber(lhs.getSymbol().getType()) != Type.isNumber(rhs.getSymbol().getType())) {
+                            Type widest = lhs.getSymbol().getType().widest(rhs.getSymbol().getType());
+                            Optimizer.info(" type mismatch: " + lhs.getSymbol().getType() + " != " + rhs.getSymbol().getType() + " widening to " + widest + " @ " + node);
+                            if (!Type.areEquivalent(lhs.getSymbol().getType(), widest)) {
+                                if (lhs.isTokenType(TokenType.CONVERT)) {
+                                    lhs.getSymbol().setTypeOverride(widest);
+                                    modified.add(lhs.getSymbol());
+                                } else if (lhs instanceof LiteralNode) {
+                                    node.getArgs().set(0, convertLiteral((LiteralNode<?>) lhs));
+                                } else {
+                                    Optimizer.info("NO CONVERT: " + lhs);
+                                    UnaryNode convert = new UnaryNode(lhs.getSource(), Token.recast(lhs.getToken(), TokenType.CONVERT), lhs);
+                                    functionNode.pushFrame();
+                                    functionNode.newTemporary(widest, convert);
+                                    functionNode.popFrame();
+                                    node.getArgs().set(0, convert);
+                                    Optimizer.info("CONVERT INSERTED: " + convert);
+                                }
+                            }
+                            if (!Type.areEquivalent(rhs.getSymbol().getType(), widest)) {
+                                if (rhs.isTokenType(TokenType.CONVERT)) {
+                                    rhs.getSymbol().setTypeOverride(widest);
+                                    modified.add(rhs.getSymbol());
+                                } else if (rhs instanceof LiteralNode) {
+                                    node.getArgs().set(1, convertLiteral((LiteralNode<?>) rhs));
+                                } else {
+                                    Optimizer.info("NO CONVERT: " + rhs);
+                                    UnaryNode convert = new UnaryNode(rhs.getSource(), Token.recast(rhs.getToken(), TokenType.CONVERT), rhs);
+                                    functionNode.pushFrame();
+                                    functionNode.newTemporary(widest, convert);
+                                    functionNode.popFrame();
+                                    node.getArgs().set(1, convert);
+                                    Optimizer.info("CONVERT INSERTED: " + convert);
+                                }
+                            }
+                        }
+                    }
+
+                    return node;
+                }
+                */
+
+                @SuppressWarnings("rawtypes")
+                @Override
+                public Node leave(LiteralNode node) {
+                    return convertLiteral(node, node.getSymbol().getSymbolType());
+                }
+
+                private LiteralNode<?> convertLiteral(LiteralNode<?> node, Type targetType) {
+                    if (node.getType() != targetType) {
+                        Optimizer.info("CONVERTLITERAL nodetype=" + node.getType() + " targettype=" + targetType);
+                        if ((node.getType().isNumber()) && (targetType.isInteger())) {
+                            LiteralNode<Number> lit = LiteralNode.newInstance(node.getSource(), node.getToken(), node.getFinish(), (Number)((Number)node.getValue()).intValue());
+                            functionNode.pushFrame();
+                            functionNode.newTemporary(Type.INT, lit);
+                            functionNode.popFrame();
+                            return lit;
+                        } else if (node.getType().isInteger() && targetType.isNumber()) {
+                            LiteralNode<Number> lit = LiteralNode.newInstance(node.getSource(), node.getToken(), node.getFinish(), (Number)((Number)node.getValue()).doubleValue());
+                            functionNode.pushFrame();
+                            functionNode.newTemporary(Type.NUMBER, lit);
+                            functionNode.popFrame();
+                            return lit;
+                        }
+                    }
+                    return node;
+                }
+
+                @Override
+                public Node leave(final BinaryNode node) {
+                    Node lhs = node.lhs();
+                    Node rhs = node.rhs();
+                    if (lhs.getSymbol() == null) {
+                        return node;
+                    }
+                    if ((lhs.getSymbol().getSymbolType().isInteger() != rhs.getSymbol().getSymbolType().isInteger()) &&
+                            !Type.areEquivalent(lhs.getSymbol().getSymbolType(), rhs.getSymbol().getSymbolType())) {
+
+                        if (lhs.isTokenType(TokenType.CONVERT)
+                                && ((UnaryNode)lhs).rhs().getSymbol().getSymbolType() == Type.INT
+                                && lhs.getSymbol().getSymbolType() == Type.NUMBER) {
+                            Optimizer.info("drop lhs convert: " + lhs + " node " + node);
+                            node.setLHS(lhs = ((UnaryNode) lhs).rhs());
+                        }
+                        if (rhs.isTokenType(TokenType.CONVERT)
+                                && ((UnaryNode)rhs).rhs().getSymbol().getSymbolType() == Type.INT
+                                && rhs.getSymbol().getSymbolType() == Type.NUMBER) {
+                            Optimizer.info("drop rhs convert: " + rhs + " node " + node);
+                            node.setRHS(rhs = ((UnaryNode) rhs).rhs());
+                        }
+
+                        Type widest = lhs.getSymbol().getSymbolType().widest(rhs.getSymbol().getSymbolType());
+
+                        if (widest.isNumber()) {
+                            Optimizer.info(" type mismatch: lhs=" + lhs.getSymbol().getSymbolType() + " rhs=" + rhs.getSymbol().getSymbolType() + " nodesym=" + node.getSymbol().getSymbolType() + " widening to " + widest + " @ " + node);
+
+                            if (!Type.areEquivalent(lhs.getSymbol().getSymbolType(), widest)) {
+                                if (lhs.isTokenType(TokenType.CONVERT)) {
+                                    lhs.getSymbol().setTypeOverride(widest);
+                                    modified.add(lhs.getSymbol());
+                                } else if (lhs instanceof LiteralNode) {
+                                    node.setLHS(lhs = convertLiteral((LiteralNode<?>)lhs, widest));
+                                } else {
+                                    Optimizer.info("MISSING CONVERT" + lhs);
+                                    UnaryNode convert = new UnaryNode(lhs.getSource(), Token.recast(lhs.getToken(), TokenType.CONVERT), lhs);
+                                    functionNode.pushFrame();
+                                    functionNode.newTemporary(widest, convert);
+                                    functionNode.popFrame();
+                                    node.setLHS(lhs = convert);
+                                    Optimizer.info("CONVERT INSERTED: " + convert);
+                                }
+                            }
+                            if (!Type.areEquivalent(rhs.getSymbol().getSymbolType(), widest)) {
+                                if (rhs.isTokenType(TokenType.CONVERT)) {
+                                    rhs.getSymbol().setTypeOverride(widest);
+                                    modified.add(rhs.getSymbol());
+                                } else if (rhs instanceof LiteralNode) {
+                                    node.setRHS(rhs = convertLiteral((LiteralNode<?>)rhs, widest));
+                                } else {
+                                    Optimizer.info("MISSING CONVERT: " + rhs);
+                                    UnaryNode convert = new UnaryNode(rhs.getSource(), Token.recast(rhs.getToken(), TokenType.CONVERT), rhs);
+                                    functionNode.pushFrame();
+                                    functionNode.newTemporary(widest, convert);
+                                    functionNode.popFrame();
+                                    node.setRHS(rhs = convert);
+                                    Optimizer.info("CONVERT INSERTED: " + convert);
+                                }
+                            }
+                        }
+                    }
+
+                    // fix result type
+                    if (node.getSymbol().getSymbolType() != lhs.getSymbol().getSymbolType()) {
+                        if (EnumSet.of(TokenType.ADD, TokenType.SUB, TokenType.MUL).contains(node.tokenType())) {
+                            Optimizer.info("FIX RESULT TYPE " + node.getSymbol().getSymbolType() + " => " + lhs.getSymbol().getSymbolType() + " for node: " + node);
+                            node.getSymbol().setTypeOverride(lhs.getSymbol().getSymbolType());
+                        }
+                    }
+
+                    return node;
+                }
+            });
+        }
+
+        return modified;
+    }
+}
diff --git a/nashorn/src/com/oracle/nashorn/ir/Symbol.java b/nashorn/src/com/oracle/nashorn/ir/Symbol.java
--- a/nashorn/src/com/oracle/nashorn/ir/Symbol.java
+++ b/nashorn/src/com/oracle/nashorn/ir/Symbol.java
@@ -29,6 +29,7 @@
 
 import com.oracle.nashorn.codegen.Type;
 import com.oracle.nashorn.runtime.Context;
+import com.oracle.nashorn.codegen.opt.Range;
 import com.oracle.nashorn.runtime.options.Options;
 
 /**
@@ -68,6 +69,9 @@
     /** Local variable slot. -1 indicates external property. */
     private int slot;
 
+    /** The symbol's value range, if known. */
+    private Range range;
+
     /**
      * Constructors.
      */
@@ -78,6 +82,7 @@
         this.block = block;
         this.type  = type;
         this.slot  = slot;
+        this.range   = Range.createUnknownRange();
     }
 
     public Symbol(final String name, final int flags, final Node node, final Block block) {
@@ -155,6 +160,9 @@
         }
 
         stream.print(printName + ": " + printType + ", " + printSlot + ", " + printFlags);
+        if (!range.isUnknown()) {
+            stream.print("range = " + range);
+        }
         stream.println();
     }
 
@@ -389,4 +397,12 @@
     public boolean isGlobal() {
         return block instanceof FunctionNode ? ((FunctionNode) block).isScript() : false;
     }
+
+    public Range getRange() {
+        return range;
+    }
+
+    public void setRange(Range range) {
+        this.range = range;
+    }
 }
