Browse Source

Separate InternalTypes to allow integer operation optimizations (#672)

Marko Lahma 5 years ago
parent
commit
34643444a6
30 changed files with 895 additions and 407 deletions
  1. 2 9
      Jint.Tests.CommonScripts/SunSpiderTests.cs
  2. 4 4
      Jint/Engine.cs
  3. 0 2
      Jint/Extensions/JavascriptExtensions.cs
  4. 1 1
      Jint/Jint.csproj
  5. 19 8
      Jint/JsValueExtensions.cs
  6. 30 0
      Jint/Key.cs
  7. 12 5
      Jint/Native/Argument/ArgumentsInstance.cs
  8. 1 1
      Jint/Native/Array/ArrayConstructor.cs
  9. 4 4
      Jint/Native/Array/ArrayInstance.cs
  10. 1 1
      Jint/Native/Array/ArrayPrototype.cs
  11. 1 1
      Jint/Native/Boolean/BooleanPrototype.cs
  12. 2 7
      Jint/Native/Date/DatePrototype.cs
  13. 8 3
      Jint/Native/Function/ScriptFunctionInstance.cs
  14. 26 9
      Jint/Native/JsNumber.cs
  15. 41 20
      Jint/Native/JsValue.cs
  16. 11 4
      Jint/Native/String/StringPrototype.cs
  17. 11 1
      Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs
  18. 12 3
      Jint/Runtime/Environments/LexicalEnvironment.cs
  19. 31 16
      Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs
  20. 380 169
      Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs
  21. 2 2
      Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs
  22. 33 0
      Jint/Runtime/Interpreter/Expressions/JintConstantExpression.cs
  23. 105 26
      Jint/Runtime/Interpreter/Expressions/JintExpression.cs
  24. 19 9
      Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs
  25. 2 2
      Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs
  26. 46 4
      Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs
  27. 15 6
      Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs
  28. 2 2
      Jint/Runtime/Interpreter/Statements/JintForInStatement.cs
  29. 4 4
      Jint/Runtime/References/Reference.cs
  30. 70 84
      Jint/Runtime/TypeConverter.cs

+ 2 - 9
Jint.Tests.CommonScripts/SunSpiderTests.cs

@@ -56,15 +56,8 @@ namespace Jint.Tests.CommonScripts
         [InlineData("string-validate-input", "string-validate-input.js")]
         public void RunScript(string name, string url)
         {
-            try
-            {
-                var content = GetEmbeddedFile(url);
-                RunTest(content);
-            }
-            catch
-            {
-                System.Diagnostics.Debug.WriteLine("Can't run {0}", name);
-            }
+            var content = GetEmbeddedFile(url);
+            RunTest(content);
         }
 
         private string GetEmbeddedFile(string filename)

+ 4 - 4
Jint/Engine.cs

@@ -496,7 +496,7 @@ namespace Jint
 
         internal JsValue GetValue(Reference reference, bool returnReferenceToPool)
         {
-            if (reference._baseValue._type == Types.Undefined)
+            if (reference._baseValue._type == InternalTypes.Undefined)
             {
                 if (_referenceResolver != null &&
                     _referenceResolver.TryUnresolvableReference(this, reference, out JsValue val))
@@ -523,7 +523,7 @@ namespace Jint
                     _referencePool.Return(reference);
                 }
 
-                if (reference._baseValue._type == Types.Object)
+                if (reference._baseValue._type == InternalTypes.Object)
                 {
                     var o = TypeConverter.ToObject(this, baseValue);
                     var v = o.Get(referencedName);
@@ -575,7 +575,7 @@ namespace Jint
         public void PutValue(Reference reference, JsValue value)
         {
             ref readonly var referencedName = ref reference.GetReferencedName();
-            if (reference._baseValue._type == Types.Undefined)
+            if (reference._baseValue._type == InternalTypes.Undefined)
             {
                 if (reference._strict)
                 {
@@ -587,7 +587,7 @@ namespace Jint
             else if (reference.IsPropertyReference())
             {
                 var baseValue = reference._baseValue;
-                if (reference._baseValue._type == Types.Object || reference._baseValue._type == Types.None)
+                if (reference._baseValue._type == InternalTypes.Object || reference._baseValue._type == InternalTypes.None)
                 {
                     ((ObjectInstance) baseValue).Put(referencedName, value, reference._strict);
                 }

+ 0 - 2
Jint/Extensions/JavascriptExtensions.cs

@@ -1,5 +1,3 @@
-using System.Text;
-
 namespace Jint.Extensions
 {
     internal static class JavascriptExtensions

+ 1 - 1
Jint/Jint.csproj

@@ -7,6 +7,6 @@
     <LangVersion>latest</LangVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Esprima" Version="1.0.1246" />
+    <PackageReference Include="Esprima" Version="1.0.1251" />
   </ItemGroup>
 </Project>

+ 19 - 8
Jint/JsValueExtensions.cs

@@ -9,9 +9,9 @@ namespace Jint
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static bool AsBoolean(this JsValue value)
         {
-            if (value._type != Types.Boolean)
+            if (value._type != InternalTypes.Boolean)
             {
-                ExceptionHelper.ThrowArgumentException($"Expected boolean but got {value._type}");
+                ThrowWrongTypeException(value, "boolean");
             }
 
             return ((JsBoolean) value)._value;
@@ -20,20 +20,26 @@ namespace Jint
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static double AsNumber(this JsValue value)
         {
-            if (value._type != Types.Number)
+            if (!value.IsNumber())
             {
-                ExceptionHelper.ThrowArgumentException($"Expected number but got {value._type}");
+                ThrowWrongTypeException(value, "number");
             }
 
             return ((JsNumber) value)._value;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal static int AsInteger(this JsValue value)
+        {
+            return (int) ((JsNumber) value)._value;
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static string AsString(this JsValue value)
         {
-            if (value._type != Types.String)
+            if (value._type != InternalTypes.String)
             {
-                ExceptionHelper.ThrowArgumentException($"Expected string but got {value._type}");
+                ThrowWrongTypeException(value, "string");
             }
 
             return AsStringWithoutTypeCheck(value);
@@ -48,12 +54,17 @@ namespace Jint
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static string AsSymbol(this JsValue value)
         {
-            if (value._type != Types.Symbol)
+            if (value._type != InternalTypes.Symbol)
             {
-                ExceptionHelper.ThrowArgumentException($"Expected symbol but got {value._type}");
+                ThrowWrongTypeException(value, "symbol");
             }
 
             return ((JsSymbol) value)._value;
         }
+
+        private static void ThrowWrongTypeException(JsValue value, string expectedType)
+        {
+            ExceptionHelper.ThrowArgumentException($"Expected {expectedType} but got {value._type}");
+        }
     }
 }

+ 30 - 0
Jint/Key.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Diagnostics;
+using System.Runtime.CompilerServices;
 using Jint.Runtime;
 
 namespace Jint
@@ -11,6 +12,17 @@ namespace Jint
     [DebuggerDisplay("{" + nameof(Name) + "}")]
     public readonly struct Key : IEquatable<Key>
     {
+        // lookup for indexer keys
+        internal static readonly Key[] indexKeys = new Key[TypeConverter.intToString.Length];
+
+        static Key()
+        {
+            for (uint i = 0; i < indexKeys.Length; ++i)
+            {
+                indexKeys[i] = new Key(TypeConverter.ToString(i));
+            }
+        }
+
         public Key(string name)
         {
             if (name == null)
@@ -29,6 +41,24 @@ namespace Jint
             return new Key(name);
         }
 
+        public static implicit operator Key(int value)
+        {
+            var keys = indexKeys;
+            return (uint) value < keys.Length ? keys[value] : BuildKey(value);
+        }
+
+        public static implicit operator Key(uint value)
+        {
+            var keys = indexKeys;
+            return value < keys.Length ? keys[value] : BuildKey(value);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static Key BuildKey(long value)
+        {
+            return new Key(value.ToString());
+        }
+
         public static implicit operator string(Key key) => key.Name;
 
         public static bool operator ==(in Key a, Key b)

+ 12 - 5
Jint/Native/Argument/ArgumentsInstance.cs

@@ -55,13 +55,20 @@ namespace Jint.Native.Argument
             ObjectInstance map = null;
             if (args.Length > 0)
             {
-                var mappedNamed = _mappedNamed.Value;
-                mappedNamed.Clear();
+                HashSet<string> mappedNamed = null;
+                if (!_strict)
+                {
+                    mappedNamed = _mappedNamed.Value;
+                    mappedNamed.Clear();
+                }
                 for (var i = 0; i < (uint) args.Length; i++)
                 {
-                    var indxStr = (Key) TypeConverter.ToString(i);
+                    var indexKey = i < Key.indexKeys.Length
+                        ? Key.indexKeys[i]
+                        : (Key) TypeConverter.ToString(i);
+
                     var val = args[i];
-                    SetOwnProperty(indxStr, new PropertyDescriptor(val, PropertyFlag.ConfigurableEnumerableWritable));
+                    SetOwnProperty(indexKey, new PropertyDescriptor(val, PropertyFlag.ConfigurableEnumerableWritable));
                     if (i < _names.Length)
                     {
                         var name = _names[i];
@@ -69,7 +76,7 @@ namespace Jint.Native.Argument
                         {
                             map = map ?? Engine.Object.Construct(Arguments.Empty);
                             mappedNamed.Add(name);
-                            map.SetOwnProperty(indxStr, new ClrAccessDescriptor(_env, Engine, name));
+                            map.SetOwnProperty(indexKey, new ClrAccessDescriptor(_env, Engine, name));
                         }
                     }
                 }

+ 1 - 1
Jint/Native/Array/ArrayConstructor.cs

@@ -220,7 +220,7 @@ namespace Jint.Native.Array
         {
             // check if we can figure out good size
             var capacity = arguments.Length > 0 ? (uint) arguments.Length : 0;
-            if (arguments.Length == 1 && arguments[0].Type == Types.Number)
+            if (arguments.Length == 1 && arguments[0].IsNumber())
             {
                 var number = ((JsNumber) arguments[0])._value;
                 if (number > 0)

+ 4 - 4
Jint/Native/Array/ArrayInstance.cs

@@ -384,7 +384,7 @@ namespace Jint.Native.Array
             var prop = GetOwnProperty(index);
             if (prop == PropertyDescriptor.Undefined)
             {
-                prop = Prototype?.GetProperty(TypeConverter.ToString(index)) ?? PropertyDescriptor.Undefined;
+                prop = Prototype?.GetProperty(index) ?? PropertyDescriptor.Undefined;
             }
 
             return UnwrapJsValue(prop);
@@ -398,7 +398,7 @@ namespace Jint.Native.Array
             {
                 return prop;
             }
-            return Prototype?.GetProperty(TypeConverter.ToString(index)) ?? PropertyDescriptor.Undefined;
+            return Prototype?.GetProperty(index) ?? PropertyDescriptor.Undefined;
         }
 
         protected internal override void SetOwnProperty(in Key propertyName, PropertyDescriptor desc)
@@ -561,7 +561,7 @@ namespace Jint.Native.Array
             value = Undefined;
 
             TryGetDescriptor(index, out var desc);
-            desc = desc ?? GetProperty(TypeConverter.ToString(index)) ?? PropertyDescriptor.Undefined;
+            desc = desc ?? GetProperty(index) ?? PropertyDescriptor.Undefined;
             return desc.TryGetValue(this, out value);
         }
 
@@ -734,7 +734,7 @@ namespace Jint.Native.Array
             }
             else
             {
-                DefineOwnProperty(TypeConverter.ToString((uint) n), desc, true);
+                DefineOwnProperty((uint) n, desc, true);
             }
         }
 

+ 1 - 1
Jint/Native/Array/ArrayPrototype.cs

@@ -1464,7 +1464,7 @@ namespace Jint.Native.Array
                         var prop = _array._dense[i] ?? PropertyDescriptor.Undefined;
                         if (prop == PropertyDescriptor.Undefined)
                         {
-                            prop = _array.Prototype?.GetProperty(TypeConverter.ToString(i)) ?? PropertyDescriptor.Undefined;
+                            prop = _array.Prototype?.GetProperty(i) ?? PropertyDescriptor.Undefined;
                         }
 
                         jsValues[i] = _array.UnwrapJsValue(prop);

+ 1 - 1
Jint/Native/Boolean/BooleanPrototype.cs

@@ -41,7 +41,7 @@ namespace Jint.Native.Boolean
 
         private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)
         {
-            if (thisObj._type == Types.Boolean)
+            if (thisObj._type == InternalTypes.Boolean)
             {
                 return thisObj;
             }

+ 2 - 7
Jint/Native/Date/DatePrototype.cs

@@ -678,13 +678,8 @@ namespace Jint.Native.Date
         /// <summary>
         /// The year of a time value.
         /// </summary>
-        public static double YearFromTime(double t)
+        public static int YearFromTime(double t)
         {
-            if (!AreFinite(t))
-            {
-                return double.NaN;
-            }
-
             var sign = (t < 0) ? -1 : 1;
             var year = (sign < 0) ? 1969 : 1970;
             for (var timeToTimeZero = (long) t; ;)
@@ -818,7 +813,7 @@ namespace Jint.Native.Date
             return Day(t) - DayFromYear(YearFromTime(t));
         }
 
-        public static double DateFromTime(double t)
+        public static int DateFromTime(double t)
         {
             var monthFromTime = MonthFromTime(t);
             var dayWithinYear = DayWithinYear(t);

+ 8 - 3
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -11,6 +11,8 @@ namespace Jint.Native.Function
     public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor
     {
         internal readonly JintFunctionDefinition _function;
+        private LexicalEnvironment _localEnv;
+
 
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2
@@ -73,11 +75,11 @@ namespace Jint.Native.Function
                 {
                     thisBinding = thisArg;
                 }
-                else if (thisArg._type == Types.Undefined || thisArg._type == Types.Null)
+                else if (thisArg._type == InternalTypes.Undefined || thisArg._type == InternalTypes.Null)
                 {
                     thisBinding = _engine.Global;
                 }
-                else if (thisArg._type != Types.Object)
+                else if (thisArg._type != InternalTypes.Object)
                 {
                     thisBinding = TypeConverter.ToObject(_engine, thisArg);
                 }
@@ -86,7 +88,9 @@ namespace Jint.Native.Function
                     thisBinding = thisArg;
                 }
 
-                var localEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _scope);
+                var localEnv = _localEnv ?? LexicalEnvironment.NewDeclarativeEnvironment(_engine, _scope);
+                localEnv.Reset(_scope);
+                _localEnv = null;
 
                 _engine.EnterExecutionContext(localEnv, localEnv, thisBinding);
 
@@ -121,6 +125,7 @@ namespace Jint.Native.Function
                 finally
                 {
                     _engine.LeaveExecutionContext();
+                    _localEnv = localEnv;
                 }
 
                 return Undefined;

+ 26 - 9
Jint/Native/JsNumber.cs

@@ -15,12 +15,11 @@ namespace Jint.Native
         // how many decimals to check when determining if double is actually an int
         internal const double DoubleIsIntegerTolerance = double.Epsilon * 100;
 
-        private static readonly long NegativeZeroBits = BitConverter.DoubleToInt64Bits(-0.0);
+        internal static readonly long NegativeZeroBits = BitConverter.DoubleToInt64Bits(-0.0);
 
         // we can cache most common values, doubles are used in indexing too at times so we also cache
         // integer values converted to doubles
         private const int NumbersMax = 1024 * 10;
-        private static readonly JsNumber[] _doubleToJsValue = new JsNumber[NumbersMax];
         private static readonly JsNumber[] _intToJsValue = new JsNumber[NumbersMax];
 
         internal static readonly JsNumber DoubleNaN = new JsNumber(double.NaN);
@@ -38,7 +37,6 @@ namespace Jint.Native
             for (int i = 0; i < NumbersMax; i++)
             {
                 _intToJsValue[i] = new JsNumber(i);
-                _doubleToJsValue[i] = new JsNumber((double) i);
             }
         }
 
@@ -47,12 +45,22 @@ namespace Jint.Native
             _value = value;
         }
 
-        public JsNumber(int value) : base(Types.Number)
+        public JsNumber(int value) : base(InternalTypes.Integer)
         {
             _value = value;
         }
 
-        public JsNumber(uint value) : base(Types.Number)
+        public JsNumber(uint value) : base(value < int.MaxValue ? InternalTypes.Integer : InternalTypes.Number)
+        {
+            _value = value;
+        }
+
+        public JsNumber(ulong value) : base(value < int.MaxValue ? InternalTypes.Integer : InternalTypes.Number)
+        {
+            _value = value;
+        }
+
+        public JsNumber(long value) : base(value < int.MaxValue && value > int.MinValue ? InternalTypes.Integer : InternalTypes.Number)
         {
             _value = value;
         }
@@ -65,10 +73,9 @@ namespace Jint.Native
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static JsNumber Create(double value)
         {
-            // we can cache positive double zero, but not negative, -0 == 0 in C# but in JS it's a different story
-            var temp = _doubleToJsValue;
-            if ((value == 0 && BitConverter.DoubleToInt64Bits(value) != NegativeZeroBits || value >= 1)
-                && value < temp.Length
+            // we expect zero to be on the fast path of integer mostly
+            var temp = _intToJsValue;
+            if (value >= 1 && value < temp.Length
                 && System.Math.Abs(value % 1) <= DoubleIsIntegerTolerance)
             {
                 return temp[(uint) value];
@@ -145,6 +152,16 @@ namespace Jint.Native
             return new JsNumber(value);
         }
 
+        internal static JsNumber Create(long value)
+        {
+            if ((ulong) value < (ulong) _intToJsValue.Length)
+            {
+                return _intToJsValue[value];
+            }
+
+            return new JsNumber(value);
+        }
+
         public override string ToString()
         {
             return _value.ToString(CultureInfo.InvariantCulture);

+ 41 - 20
Jint/Native/JsValue.cs

@@ -21,9 +21,14 @@ namespace Jint.Native
     {
         public static readonly JsValue Undefined = new JsUndefined();
         public static readonly JsValue Null = new JsNull();
-        internal readonly Types _type;
+        internal readonly InternalTypes _type;
 
         protected JsValue(Types type)
+        {
+            _type = (InternalTypes) type;
+        }
+
+        internal JsValue(InternalTypes type)
         {
             _type = type;
         }
@@ -32,21 +37,21 @@ namespace Jint.Native
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsPrimitive()
         {
-            return _type != Types.Object && _type != Types.None;
+            return _type != InternalTypes.Object && _type != InternalTypes.None;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsUndefined()
         {
-            return _type == Types.Undefined;
+            return _type == InternalTypes.Undefined;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal bool IsNullOrUndefined()
         {
-            return _type < Types.Boolean;
+            return _type < InternalTypes.Boolean;
         }
 
         [Pure]
@@ -74,49 +79,56 @@ namespace Jint.Native
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsObject()
         {
-            return _type == Types.Object;
+            return _type == InternalTypes.Object;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsString()
         {
-            return _type == Types.String;
+            return _type == InternalTypes.String;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsNumber()
         {
-            return _type == Types.Number;
+            return _type == InternalTypes.Number || _type == InternalTypes.Integer;
+        }
+
+        [Pure]
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        internal bool IsInteger()
+        {
+            return _type == InternalTypes.Integer;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsBoolean()
         {
-            return _type == Types.Boolean;
+            return _type == InternalTypes.Boolean;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsNull()
         {
-            return _type == Types.Null;
+            return _type == InternalTypes.Null;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsCompletion()
         {
-            return _type == Types.Completion;
+            return _type == InternalTypes.Completion;
         }
 
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsSymbol()
         {
-            return _type == Types.Symbol;
+            return _type == InternalTypes.Symbol;
         }
 
         [Pure]
@@ -163,7 +175,7 @@ namespace Jint.Native
 
             return iterator;
         }
-        
+
         [Pure]
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal bool TryGetIterator(Engine engine, out IIterator iterator)
@@ -216,7 +228,7 @@ namespace Jint.Native
         [Pure]
         public Completion AsCompletion()
         {
-            if (_type != Types.Completion)
+            if (_type != InternalTypes.Completion)
             {
                 ExceptionHelper.ThrowArgumentException("The value is not a completion record");
             }
@@ -264,11 +276,10 @@ namespace Jint.Native
             return null;
         }
 
-        // ReSharper disable once ConvertToAutoPropertyWhenPossible // PERF
         public Types Type
         {
             [MethodImpl(MethodImplOptions.AggressiveInlining)]
-            get { return _type; }
+            get => _type == InternalTypes.Integer ? Types.Number : (Types) _type;
         }
 
         /// <summary>
@@ -463,22 +474,32 @@ namespace Jint.Native
             return !a.Equals(b);
         }
 
-        static public implicit operator JsValue(char value)
+        public static implicit operator JsValue(char value)
         {
             return JsString.Create(value);
         }
 
-        static public implicit operator JsValue(int value)
+        public static implicit operator JsValue(int value)
         {
             return JsNumber.Create(value);
         }
 
-        static public implicit operator JsValue(uint value)
+        public static implicit operator JsValue(uint value)
         {
             return JsNumber.Create(value);
         }
 
-        static public implicit operator JsValue(double value)
+        public static implicit operator JsValue(double value)
+        {
+            return JsNumber.Create(value);
+        }
+
+        public static implicit operator JsValue(long value)
+        {
+            return JsNumber.Create(value);
+        }
+
+        public static implicit operator JsValue(ulong value)
         {
             return JsNumber.Create(value);
         }
@@ -494,7 +515,7 @@ namespace Jint.Native
             {
                 return Null;
             }
-                
+
             return JsString.Create(value);
         }
 

+ 11 - 4
Jint/Native/String/StringPrototype.cs

@@ -217,6 +217,14 @@ namespace Jint.Native.String
         }
 
         private static int ToIntegerSupportInfinity(JsValue numberVal)
+        {
+            return numberVal._type == InternalTypes.Integer
+                ? numberVal.AsInteger()
+                : ToIntegerSupportInfinityUnlikely(numberVal);
+        }
+
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static int ToIntegerSupportInfinityUnlikely(JsValue numberVal)
         {
             var doubleVal = TypeConverter.ToInteger(numberVal);
             int intVal;
@@ -840,7 +848,7 @@ namespace Jint.Native.String
             {
                 return JsNumber.DoubleNaN;
             }
-            return (double) s[position];
+            return (long) s[position];
         }
 
         private JsValue CodePointAt(JsValue thisObj, JsValue[] arguments)
@@ -855,10 +863,10 @@ namespace Jint.Native.String
                 return Undefined;
             }
 
-            var first = (double) s[position];
+            var first = (long) s[position];
             if (first >= 0xD800 && first <= 0xDBFF && s.Length > position + 1)
             {
-                double second = s[position + 1];
+                long second = s[position + 1];
                 if (second >= 0xDC00 && second <= 0xDFFF)
                 {
                     return (first - 0xD800) * 0x400 + second - 0xDC00 + 0x10000;
@@ -878,7 +886,6 @@ namespace Jint.Native.String
                 return "";
             }
             return TypeConverter.ToString(s[(int) position]);
-
         }
 
         private JsValue ValueOf(JsValue thisObj, JsValue[] arguments)

+ 11 - 1
Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs

@@ -31,6 +31,16 @@ namespace Jint.Runtime.Environments
         {
         }
 
+        internal void Reset()
+        {
+            _dictionary?.Clear();
+            _set = false;
+            _key = default;
+            _value = default;
+            _argumentsBinding = default;
+            _argumentsBindingWasAccessed = false;
+        }
+
         private void SetItem(in Key key, in Binding value)
         {
             if (_set && _key != key)
@@ -185,7 +195,7 @@ namespace Jint.Runtime.Environments
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private JsValue UnwrapBindingValue(bool strict, in Binding binding)
         {
-            if (!binding.Mutable && binding.Value._type == Types.Undefined)
+            if (!binding.Mutable && binding.Value._type == InternalTypes.Undefined)
             {
                 if (strict)
                 {

+ 12 - 3
Jint/Runtime/Environments/LexicalEnvironment.cs

@@ -13,8 +13,8 @@ namespace Jint.Runtime.Environments
     public sealed class LexicalEnvironment
     {
         private readonly Engine _engine;
-        internal readonly EnvironmentRecord _record;
-        internal readonly LexicalEnvironment _outer;
+        internal EnvironmentRecord _record;
+        internal LexicalEnvironment _outer;
 
         public LexicalEnvironment(Engine engine, EnvironmentRecord record, LexicalEnvironment outer)
         {
@@ -69,7 +69,16 @@ namespace Jint.Runtime.Environments
 
         public static LexicalEnvironment NewDeclarativeEnvironment(Engine engine, LexicalEnvironment outer = null)
         {
-            return new LexicalEnvironment(engine, new DeclarativeEnvironmentRecord(engine), outer);
+            var environment = new LexicalEnvironment(engine, null, null);
+            environment.Reset(outer);
+            return environment;
+        }
+
+        internal void Reset(LexicalEnvironment outer)
+        {
+            _record = _record ?? new DeclarativeEnvironmentRecord(_engine);
+            ((DeclarativeEnvironmentRecord) _record).Reset();
+            _outer = outer;
         }
 
         public static LexicalEnvironment NewObjectEnvironment(Engine engine, ObjectInstance objectInstance, LexicalEnvironment outer, bool provideThis)

+ 31 - 16
Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs

@@ -10,11 +10,13 @@ namespace Jint.Runtime.Interpreter.Expressions
     {
         private readonly JintExpression _left;
         private readonly JintExpression _right;
+        private readonly AssignmentOperator _operator;
 
         private JintAssignmentExpression(Engine engine, AssignmentExpression expression) : base(engine, expression)
         {
             _left = Build(engine, (Expression) expression.Left);
             _right = Build(engine, expression.Right);
+            _operator = expression.Operator;
         }
 
         internal static JintExpression Build(Engine engine, AssignmentExpression expression)
@@ -38,38 +40,51 @@ namespace Jint.Runtime.Interpreter.Expressions
         protected override object EvaluateInternal()
         {
             var lref = _left.Evaluate() as Reference ?? ExceptionHelper.ThrowReferenceError<Reference>(_engine);
-            JsValue rval = _right.GetValue();
 
-            JsValue lval = _engine.GetValue(lref, false);
+            var rval = _right.GetValue();
+            var lval = _engine.GetValue(lref, false);
 
-            var expression = (AssignmentExpression) _expression;
-            switch (expression.Operator)
+            switch (_operator)
             {
                 case AssignmentOperator.PlusAssign:
-                    var lprim = TypeConverter.ToPrimitive(lval);
-                    var rprim = TypeConverter.ToPrimitive(rval);
-                    if (lprim.IsString() || rprim.IsString())
+                    if (AreIntegerOperands(lval, rval))
                     {
-                        if (!(lprim is JsString jsString))
-                        {
-                            jsString = new JsString.ConcatenatedString(TypeConverter.ToString(lprim));
-                        }
-
-                        lval = jsString.Append(rprim);
+                        lval = (long) lval.AsInteger() + rval.AsInteger();
                     }
                     else
                     {
-                        lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
+                        var lprim = TypeConverter.ToPrimitive(lval);
+                        var rprim = TypeConverter.ToPrimitive(rval);
+
+                        if (lprim.IsString() || rprim.IsString())
+                        {
+                            if (!(lprim is JsString jsString))
+                            {
+                                jsString = new JsString.ConcatenatedString(TypeConverter.ToString(lprim));
+                            }
+
+                            lval = jsString.Append(rprim);
+                        }
+                        else
+                        {
+                            lval = TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim);
+                        }
                     }
 
                     break;
 
                 case AssignmentOperator.MinusAssign:
-                    lval = TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval);
+                    lval = AreIntegerOperands(lval, rval)
+                        ? JsNumber.Create(lval.AsInteger() - rval.AsInteger())
+                        : JsNumber.Create(TypeConverter.ToNumber(lval) - TypeConverter.ToNumber(rval));
                     break;
 
                 case AssignmentOperator.TimesAssign:
-                    if (lval.IsUndefined() || rval.IsUndefined())
+                    if (AreIntegerOperands(lval, rval))
+                    {
+                        lval = (long) lval.AsInteger() * rval.AsInteger();
+                    }
+                    else if (lval.IsUndefined() || rval.IsUndefined())
                     {
                         lval = Undefined.Instance;
                     }

+ 380 - 169
Jint/Runtime/Interpreter/Expressions/JintBinaryExpression.cs

@@ -22,7 +22,7 @@ namespace Jint.Runtime.Interpreter.Expressions
 
         internal static JintExpression Build(Engine engine, BinaryExpression expression)
         {
-            JintExpression result;
+            JintBinaryExpression result;
             switch (expression.Operator)
             {
                 case BinaryOperator.StrictlyEqual:
@@ -37,214 +37,268 @@ namespace Jint.Runtime.Interpreter.Expressions
                 case BinaryOperator.Greater:
                     result = new GreaterBinaryExpression(engine, expression);
                     break;
+                case BinaryOperator.Plus:
+                    result = new PlusBinaryExpression(engine, expression);
+                    break;
+                case BinaryOperator.Minus:
+                    result = new MinusBinaryExpression(engine, expression);
+                    break;
+                case BinaryOperator.Times:
+                    result = new TimesBinaryExpression(engine, expression);
+                    break;
+                case BinaryOperator.Divide:
+                    result = new DivideBinaryExpression(engine, expression);
+                    break;
+                case BinaryOperator.Equal:
+                    result = new EqualBinaryExpression(engine, expression);
+                    break;
+                case BinaryOperator.NotEqual:
+                    result = new EqualBinaryExpression(engine, expression, invert: true);
+                    break;
+                case BinaryOperator.GreaterOrEqual:
+                    result = new CompareBinaryExpression(engine, expression, leftFirst: true);
+                    break;
+                case BinaryOperator.LessOrEqual:
+                    result = new CompareBinaryExpression(engine, expression, leftFirst: false);
+                    break;
+                case BinaryOperator.BitwiseAnd:
+                case BinaryOperator.BitwiseOr:
+                case BinaryOperator.BitwiseXOr:
+                case BinaryOperator.LeftShift:
+                case BinaryOperator.RightShift:
+                case BinaryOperator.UnsignedRightShift:
+                    result = new BitwiseBinaryExpression(engine, expression);
+                    break;                
+                case BinaryOperator.InstanceOf:
+                    result = new InstanceOfBinaryExpression(engine, expression);
+                    break;                
+                case BinaryOperator.Exponentiation:
+                    result = new ExponentiationBinaryExpression(engine, expression);
+                    break;                
+                case BinaryOperator.Modulo:
+                    result = new ModuloBinaryExpression(engine, expression);
+                    break;                
+                case BinaryOperator.In:
+                    result = new InBinaryExpression(engine, expression);
+                    break;                
                 default:
-                    result = new JintGenericBinaryExpression(engine, expression);
+                    result = ExceptionHelper.ThrowArgumentOutOfRangeException<JintBinaryExpression>(nameof(_operatorType), "cannot handle operator");
                     break;
             }
 
-            if (expression.Left.Type == Nodes.Literal
-                && expression.Right.Type == Nodes.Literal
-                && expression.Operator != BinaryOperator.InstanceOf
-                && expression.Operator != BinaryOperator.In)
+            if (expression.Operator != BinaryOperator.InstanceOf
+                && expression.Operator != BinaryOperator.In
+                && expression.Left is Literal leftLiteral
+                && expression.Right is Literal rightLiteral)
             {
-                // calculate eagerly
-                // TODO result = new JintConstantExpression(engine, (JsValue) result.Evaluate());
+                var lval = JintLiteralExpression.ConvertToJsValue(leftLiteral);
+                var rval = JintLiteralExpression.ConvertToJsValue(rightLiteral);
+
+                if (!(lval is null) && !(rval is null))
+                {
+                    // we have fixed result
+                    return new JintConstantExpression(engine, expression, result.GetValue());
+                }
             }
 
             return result;
         }
 
+        public override JsValue GetValue()
+        {
+            // need to notify correct node when taking shortcut
+            _engine._lastSyntaxNode = _expression;
+
+            // we always create a JsValue
+            return (JsValue) EvaluateInternal();
+        }
+
         public static bool StrictlyEqual(JsValue x, JsValue y)
         {
-            var typeX = x.Type;
-            var typeY = y.Type;
+            var typeX = x._type;
+            var typeY = y._type;
 
             if (typeX != typeY)
             {
-                return false;
+                if (typeX == InternalTypes.Integer)
+                {
+                    typeX = InternalTypes.Number;
+                }
+
+                if (typeY == InternalTypes.Integer)
+                {
+                    typeY = InternalTypes.Number;
+                }
+
+                if (typeX != typeY)
+                {
+                    return false;
+                }
             }
 
             switch (typeX)
             {
-                case Types.Undefined:
-                case Types.Null:
+                case InternalTypes.Undefined:
+                case InternalTypes.Null:
                     return true;
-                case Types.Number:
+                case InternalTypes.Integer:
+                    return x.AsInteger() == y.AsInteger();
+                case InternalTypes.Number:
                     var nx = ((JsNumber) x)._value;
                     var ny = ((JsNumber) y)._value;
                     return !double.IsNaN(nx) && !double.IsNaN(ny) && nx == ny;
-                case Types.String:
+                case InternalTypes.String:
                     return x.AsStringWithoutTypeCheck() == y.AsStringWithoutTypeCheck();
-                case Types.Boolean:
+                case InternalTypes.Boolean:
                     return ((JsBoolean) x)._value == ((JsBoolean) y)._value;
-                case Types.Object when x.AsObject() is IObjectWrapper xw:
+                case InternalTypes.Object when x.AsObject() is IObjectWrapper xw:
                     var yw = y.AsObject() as IObjectWrapper;
                     if (yw == null)
                         return false;
                     return Equals(xw.Target, yw.Target);
-                case Types.None:
+                case InternalTypes.None:
                     return true;
                 default:
                     return x == y;
             }
         }
 
-        private sealed class JintGenericBinaryExpression : JintBinaryExpression
+        private sealed class StrictlyEqualBinaryExpression : JintBinaryExpression
         {
-            private readonly Func<JsValue, JsValue, JsValue> _operator;
+            public StrictlyEqualBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
 
-            public JintGenericBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            protected override object EvaluateInternal()
             {
-                switch (_operatorType)
-                {
-                    case BinaryOperator.Plus:
-                        _operator = (left, right) =>
-                        {
-                            var lprim = TypeConverter.ToPrimitive(left);
-                            var rprim = TypeConverter.ToPrimitive(right);
-                            return lprim.IsString() || rprim.IsString()
-                                ? (JsValue) JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim))
-                                : JsNumber.Create(TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim));
-                        };
-                        break;
-
-                    case BinaryOperator.Minus:
-                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToNumber(left) - TypeConverter.ToNumber(right));
-                        break;
-
-                    case BinaryOperator.Times:
-                        _operator = (left, right) => left.IsUndefined() || right.IsUndefined()
-                            ? Undefined.Instance
-                            : JsNumber.Create(TypeConverter.ToNumber(left) * TypeConverter.ToNumber(right));
-                        break;
-
-                    case BinaryOperator.Divide:
-                        _operator = Divide;
-                        break;
-
-                    case BinaryOperator.Modulo:
-                        _operator = (left, right) => left.IsUndefined() || right.IsUndefined()
-                            ? Undefined.Instance
-                            : TypeConverter.ToNumber(left) % TypeConverter.ToNumber(right);
-
-                        break;
-
-                    case BinaryOperator.Equal:
-                        _operator = (left, right) => Equal(left, right)
-                            ? JsBoolean.True
-                            : JsBoolean.False;
-                        break;
-
-                    case BinaryOperator.NotEqual:
-                        _operator = (left, right) => Equal(left, right) ? JsBoolean.False : JsBoolean.True;
-                        break;
-
-                    case BinaryOperator.GreaterOrEqual:
-                        _operator = (left, right) =>
-                        {
-                            var value = Compare(left, right);
-                            if (value.IsUndefined() || ((JsBoolean) value)._value)
-                            {
-                                value = JsBoolean.False;
-                            }
-                            else
-                            {
-                                value = JsBoolean.True;
-                            }
-
-                            return value;
-                        };
-
-                        break;
-
-                    case BinaryOperator.LessOrEqual:
-                        _operator = (left, right) =>
-                        {
-                            var value = Compare(right, left, false);
-                            if (value.IsUndefined() || ((JsBoolean) value)._value)
-                            {
-                                value = JsBoolean.False;
-                            }
-                            else
-                            {
-                                value = JsBoolean.True;
-                            }
-
-                            return value;
-                        };
-
-                        break;
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                return StrictlyEqual(left, right)
+                    ? JsBoolean.True
+                    : JsBoolean.False;
+            }
+        }
 
-                    case BinaryOperator.BitwiseAnd:
-                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) & TypeConverter.ToInt32(right));
-                        break;
+        private sealed class StrictlyNotEqualBinaryExpression : JintBinaryExpression
+        {
+            public StrictlyNotEqualBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
 
-                    case BinaryOperator.BitwiseOr:
-                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) | TypeConverter.ToInt32(right));
-                        break;
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                return StrictlyEqual(left, right)
+                    ? JsBoolean.False
+                    : JsBoolean.True;
+            }
+        }
 
-                    case BinaryOperator.BitwiseXOr:
-                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) ^ TypeConverter.ToInt32(right));
-                        break;
+        private sealed class LessBinaryExpression : JintBinaryExpression
+        {
+            public LessBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
 
-                    case BinaryOperator.LeftShift:
-                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) << (int) (TypeConverter.ToUint32(right) & 0x1F));
-                        break;
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                var value = Compare(left, right);
 
-                    case BinaryOperator.RightShift:
-                        _operator = (left, right) => JsNumber.Create(TypeConverter.ToInt32(left) >> (int) (TypeConverter.ToUint32(right) & 0x1F));
-                        break;
+                return value._type == InternalTypes.Undefined
+                    ? JsBoolean.False
+                    : value;
+            }
+        }
 
-                    case BinaryOperator.UnsignedRightShift:
-                        _operator = (left, right) => JsNumber.Create((uint) TypeConverter.ToInt32(left) >> (int) (TypeConverter.ToUint32(right) & 0x1F));
-                        break;
-
-                    case BinaryOperator.Exponentiation:
-                        _operator = (left, right) => JsNumber.Create(Math.Pow(TypeConverter.ToNumber(left), TypeConverter.ToNumber(right)));
-                        break;
-
-                    case BinaryOperator.InstanceOf:
-                        _operator = (left, right) =>
-                        {
-                            if (!(right is FunctionInstance f))
-                            {
-                                return ExceptionHelper.ThrowTypeError<JsValue>(_engine, "instanceof can only be used with a function object");
-                            }
-
-                            return f.HasInstance(left) ? JsBoolean.True : JsBoolean.False;
-                        };
-                        break;
-
-                    case BinaryOperator.In:
-                        _operator = (left, right) =>
-                        {
-                            if (!(right is ObjectInstance oi))
-                            {
-                                return ExceptionHelper.ThrowTypeError<JsValue>(_engine, "in can only be used with an object");
-                            }
-
-                            return oi.HasProperty(TypeConverter.ToString(left)) ? JsBoolean.True : JsBoolean.False;
-                        };
-
-                        break;
+        private sealed class GreaterBinaryExpression : JintBinaryExpression
+        {
+            public GreaterBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
 
-                    default:
-                        _operator = ExceptionHelper.ThrowNotImplementedException<Func<JsValue, JsValue, JsValue>>();
-                        break;
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                var value = Compare(right, left, false);
+
+                return value._type == InternalTypes.Undefined
+                    ? JsBoolean.False
+                    : value;
+            }
+        }
+        
+        private sealed class PlusBinaryExpression : JintBinaryExpression
+        {
+            public PlusBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                
+                if (AreIntegerOperands(left, right))
+                {
+                    return JsNumber.Create(left.AsInteger() + right.AsInteger());
                 }
+
+                var lprim = TypeConverter.ToPrimitive(left);
+                var rprim = TypeConverter.ToPrimitive(right);
+                return lprim.IsString() || rprim.IsString()
+                    ? (JsValue) JsString.Create(TypeConverter.ToString(lprim) + TypeConverter.ToString(rprim))
+                    : JsNumber.Create(TypeConverter.ToNumber(lprim) + TypeConverter.ToNumber(rprim));
+            }
+        }
+        private sealed class MinusBinaryExpression : JintBinaryExpression
+        {
+            public MinusBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
             }
 
             protected override object EvaluateInternal()
             {
                 var left = _left.GetValue();
                 var right = _right.GetValue();
-                return _operator(left, right);
+                
+                return AreIntegerOperands(left, right)
+                    ? JsNumber.Create(left.AsInteger() - right.AsInteger())
+                    : JsNumber.Create(TypeConverter.ToNumber(left) - TypeConverter.ToNumber(right));
             }
         }
 
-        private sealed class StrictlyEqualBinaryExpression : JintBinaryExpression
+        private sealed class TimesBinaryExpression : JintBinaryExpression
         {
-            public StrictlyEqualBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            public TimesBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+                
+                if (AreIntegerOperands(left, right))
+                {
+                    return JsNumber.Create((long) left.AsInteger() * right.AsInteger());
+                }
+
+                if (left.IsUndefined() || right.IsUndefined())
+                {
+                    return Undefined.Instance;
+                }
+
+                return JsNumber.Create(TypeConverter.ToNumber(left) * TypeConverter.ToNumber(right));
+            }
+        }
+
+        private sealed class DivideBinaryExpression : JintBinaryExpression
+        {
+            public DivideBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
             {
             }
 
@@ -252,27 +306,58 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var left = _left.GetValue();
                 var right = _right.GetValue();
-                return StrictlyEqual(left, right) ? JsBoolean.True : JsBoolean.False;
+
+                return Divide(left, right);
             }
         }
 
-        private sealed class StrictlyNotEqualBinaryExpression : JintBinaryExpression
+        private sealed class EqualBinaryExpression : JintBinaryExpression
         {
-            public StrictlyNotEqualBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            private readonly bool _invert;
+
+            public EqualBinaryExpression(Engine engine, BinaryExpression expression, bool invert = false) : base(engine, expression)
             {
+                _invert = invert;
             }
 
             protected override object EvaluateInternal()
             {
                 var left = _left.GetValue();
                 var right = _right.GetValue();
-                return StrictlyEqual(left, right) ? JsBoolean.False : JsBoolean.True;
+
+                return Equal(left, right) == !_invert
+                    ? JsBoolean.True
+                    : JsBoolean.False;
             }
         }
 
-        private sealed class LessBinaryExpression : JintBinaryExpression
+        private sealed class CompareBinaryExpression : JintBinaryExpression
         {
-            public LessBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            private readonly bool _leftFirst;
+
+            public CompareBinaryExpression(Engine engine, BinaryExpression expression, bool leftFirst) : base(engine, expression)
+            {
+                _leftFirst = leftFirst;
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var leftValue = _left.GetValue();
+                var rightValue = _right.GetValue();
+                
+                var left = _leftFirst ? leftValue : rightValue;
+                var right = _leftFirst ? rightValue : leftValue;
+
+                var value = Compare(left, right, _leftFirst);
+                return value.IsUndefined() || ((JsBoolean) value)._value
+                    ? JsBoolean.False
+                    : JsBoolean.True;
+            }
+        }
+
+        private sealed class InstanceOfBinaryExpression : JintBinaryExpression
+        {
+            public InstanceOfBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
             {
             }
 
@@ -280,19 +365,19 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var left = _left.GetValue();
                 var right = _right.GetValue();
-                var value = Compare(left, right);
-                if (value._type == Types.Undefined)
+
+                if (!(right is FunctionInstance f))
                 {
-                    value = JsBoolean.False;
+                    return ExceptionHelper.ThrowTypeError<JsValue>(_engine, "instanceof can only be used with a function object");
                 }
 
-                return value;
+                return f.HasInstance(left) ? JsBoolean.True : JsBoolean.False;
             }
         }
 
-        private sealed class GreaterBinaryExpression : JintBinaryExpression
+        private sealed class ExponentiationBinaryExpression : JintBinaryExpression
         {
-            public GreaterBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            public ExponentiationBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
             {
             }
 
@@ -300,13 +385,139 @@ namespace Jint.Runtime.Interpreter.Expressions
             {
                 var left = _left.GetValue();
                 var right = _right.GetValue();
-                var value = Compare(right, left, false);
-                if (value._type == Types.Undefined)
+
+                return JsNumber.Create(Math.Pow(TypeConverter.ToNumber(left), TypeConverter.ToNumber(right)));
+            }
+        }
+        private sealed class InBinaryExpression : JintBinaryExpression
+        {
+            public InBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+
+                if (!(right is ObjectInstance oi))
+                {
+                    return ExceptionHelper.ThrowTypeError<JsValue>(_engine, "in can only be used with an object");
+                }
+
+                return oi.HasProperty(TypeConverter.ToString(left)) ? JsBoolean.True : JsBoolean.False;
+            }
+        }
+
+        private sealed class ModuloBinaryExpression : JintBinaryExpression
+        {
+            public ModuloBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+
+                if (AreIntegerOperands(left, right))
+                {
+                    var leftInteger = left.AsInteger();
+                    var rightInteger = right.AsInteger();
+                    if (leftInteger > 0 && rightInteger != 0)
+                    {
+                        return JsNumber.Create(leftInteger % rightInteger);
+                    }
+                }
+
+                if (left.IsUndefined() || right.IsUndefined())
+                {
+                    return Undefined.Instance;
+                }
+
+                return JsNumber.Create(TypeConverter.ToNumber(left) % TypeConverter.ToNumber(right));            }
+        }
+
+        private sealed class BitwiseBinaryExpression : JintBinaryExpression
+        {
+            private readonly BinaryOperator _operator;
+
+            public BitwiseBinaryExpression(Engine engine, BinaryExpression expression) : base(engine, expression)
+            {
+                _operator = expression.Operator;
+            }
+
+            protected override object EvaluateInternal()
+            {
+                var left = _left.GetValue();
+                var right = _right.GetValue();
+
+                if (AreIntegerOperands(left, right))
                 {
-                    value = JsBoolean.False;
+                    int leftValue = left.AsInteger();
+                    int rightValue = right.AsInteger();
+                    
+                    switch (_operator)
+                    {
+                        case BinaryOperator.BitwiseAnd:
+                            return JsNumber.Create(leftValue & rightValue);
+
+                        case BinaryOperator.BitwiseOr:
+                            return
+                                JsNumber.Create(leftValue | rightValue);
+
+                        case BinaryOperator.BitwiseXOr:
+                            return
+                                JsNumber.Create(leftValue ^ rightValue);
+
+                        case BinaryOperator.LeftShift:
+                            return JsNumber.Create(leftValue << (int) ((uint) rightValue & 0x1F));
+
+                        case BinaryOperator.RightShift:
+                            return JsNumber.Create(leftValue >> (int) ((uint) rightValue & 0x1F));
+
+                        case BinaryOperator.UnsignedRightShift:
+                            return JsNumber.Create((uint) leftValue >> (int) ((uint) rightValue & 0x1F));
+                        default:
+                            return ExceptionHelper.ThrowArgumentOutOfRangeException<object>(nameof(_operator),
+                                "unknown shift operator");
+                    }
+  
                 }
+                
+                return EvaluateNonInteger(left, right);
+            }
+
+            private object EvaluateNonInteger(JsValue left, JsValue right)
+            {
+                switch (_operator)
+                {
+                    case BinaryOperator.BitwiseAnd:
+                        return JsNumber.Create(TypeConverter.ToInt32(left) & TypeConverter.ToInt32(right));
+
+                    case BinaryOperator.BitwiseOr:
+                        return
+                            JsNumber.Create(TypeConverter.ToInt32(left) | TypeConverter.ToInt32(right));
+
+                    case BinaryOperator.BitwiseXOr:
+                        return
+                            JsNumber.Create(TypeConverter.ToInt32(left) ^ TypeConverter.ToInt32(right));
+
+                    case BinaryOperator.LeftShift:
+                        return JsNumber.Create(TypeConverter.ToInt32(left) <<
+                                               (int) (TypeConverter.ToUint32(right) & 0x1F));
+
+                    case BinaryOperator.RightShift:
+                        return JsNumber.Create(TypeConverter.ToInt32(left) >>
+                                               (int) (TypeConverter.ToUint32(right) & 0x1F));
 
-                return value;
+                    case BinaryOperator.UnsignedRightShift:
+                        return JsNumber.Create((uint) TypeConverter.ToInt32(left) >>
+                                               (int) (TypeConverter.ToUint32(right) & 0x1F));
+                    default:
+                        return ExceptionHelper.ThrowArgumentOutOfRangeException<object>(nameof(_operator),
+                            "unknown shift operator");
+                }
             }
         }
     }

+ 2 - 2
Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs

@@ -123,12 +123,12 @@ namespace Jint.Runtime.Interpreter.Expressions
                 }
             }
 
-            if (func._type == Types.Undefined)
+            if (func._type == InternalTypes.Undefined)
             {
                 ExceptionHelper.ThrowTypeError(_engine, r == null ? "" : $"Object has no method '{r.GetReferencedName()}'");
             }
 
-            if (func._type != Types.Object)
+            if (func._type != InternalTypes.Object)
             {
                 if (_engine._referenceResolver == null || !_engine._referenceResolver.TryGetCallable(_engine, callee, out func))
                 {

+ 33 - 0
Jint/Runtime/Interpreter/Expressions/JintConstantExpression.cs

@@ -0,0 +1,33 @@
+using Esprima.Ast;
+using Jint.Native;
+
+namespace Jint.Runtime.Interpreter.Expressions
+{
+    /// <summary>
+    /// Constant JsValue returning expression.
+    /// </summary>
+    internal sealed class JintConstantExpression : JintExpression
+    {
+        private readonly JsValue _value;
+
+        public JintConstantExpression(Engine engine, INode expression, JsValue value) : base(engine, expression)
+        {
+            _value = value;
+        }
+
+        /// <summary>
+        /// Resolves the underlying value for this expression.
+        /// By default uses the Engine for resolving.
+        /// </summary>
+        /// <seealso cref="JintLiteralExpression"/>
+        public override JsValue GetValue()
+        {
+            // need to notify correct node when taking shortcut
+            _engine._lastSyntaxNode = _expression;
+
+            return _value;
+        }
+
+        protected override object EvaluateInternal() => _value;
+    }
+}

+ 105 - 26
Jint/Runtime/Interpreter/Expressions/JintExpression.cs

@@ -79,10 +79,10 @@ namespace Jint.Runtime.Interpreter.Expressions
                     return new JintFunctionExpression(engine, (IFunction) expression);
 
                 case Nodes.Identifier:
-                    return new JintIdentifierExpression(engine, (Esprima.Ast.Identifier) expression);
+                    return new JintIdentifierExpression(engine, (Identifier) expression);
 
                 case Nodes.Literal:
-                    return new JintLiteralExpression(engine, (Literal) expression);
+                    return JintLiteralExpression.Build(engine, (Literal) expression);
 
                 case Nodes.LogicalExpression:
                     var binaryExpression = (BinaryExpression) expression;
@@ -115,7 +115,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                     return new JintUpdateExpression(engine, (UpdateExpression) expression);
 
                 case Nodes.UnaryExpression:
-                    return new JintUnaryExpression(engine, (UnaryExpression) expression);
+                    return JintUnaryExpression.Build(engine, (UnaryExpression) expression);
 
                 case Nodes.SpreadElement:
                     return new JintSpreadExpression(engine, (SpreadElement) expression);
@@ -132,7 +132,38 @@ namespace Jint.Runtime.Interpreter.Expressions
             }
         }
 
-        protected JsValue Divide(JsValue lval, JsValue rval)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        protected static JsValue Divide(JsValue lval, JsValue rval)
+        {
+            return AreIntegerOperands(lval, rval)
+                ? DivideInteger(lval, rval)
+                : DivideComplex(lval, rval);
+        }
+
+        private static JsValue DivideInteger(JsValue lval, JsValue rval)
+        {
+            var lN = lval.AsInteger();
+            var rN = rval.AsInteger();
+
+            if (lN == 0 && rN == 0)
+            {
+                return JsNumber.DoubleNaN;
+            }
+
+            if (rN == 0)
+            {
+                return lN > 0 ? double.PositiveInfinity : double.NegativeInfinity;
+            }
+
+            if (lN % rN == 0)
+            {
+                return lN / rN;
+            }
+
+            return (double) lN / rN;
+        }
+
+        private static JsValue DivideComplex(JsValue lval, JsValue rval)
         {
             if (lval.IsUndefined() || rval.IsUndefined())
             {
@@ -180,52 +211,57 @@ namespace Jint.Runtime.Interpreter.Expressions
 
                 return lN / rN;
             }
-        }
 
+        }
 
         protected static bool Equal(JsValue x, JsValue y)
         {
-            if (x._type == y._type)
+            if (x._type == y._type || x.IsNumber() && y.IsNumber())
             {
                 return JintBinaryExpression.StrictlyEqual(x, y);
             }
 
-            if (x._type == Types.Null && y._type == Types.Undefined)
+            return EqualUnlikely(x, y);
+        }
+
+        private static bool EqualUnlikely(JsValue x, JsValue y)
+        {
+            if (x._type == InternalTypes.Null && y._type == InternalTypes.Undefined)
             {
                 return true;
             }
 
-            if (x._type == Types.Undefined && y._type == Types.Null)
+            if (x._type == InternalTypes.Undefined && y._type == InternalTypes.Null)
             {
                 return true;
             }
 
-            if (x._type == Types.Number && y._type == Types.String)
+            if (x.IsNumber() && y._type == InternalTypes.String)
             {
                 return Equal(x, TypeConverter.ToNumber(y));
             }
 
-            if (x._type == Types.String && y._type == Types.Number)
+            if (x._type == InternalTypes.String && y.IsNumber())
             {
                 return Equal(TypeConverter.ToNumber(x), y);
             }
 
-            if (x._type == Types.Boolean)
+            if (x._type == InternalTypes.Boolean)
             {
                 return Equal(TypeConverter.ToNumber(x), y);
             }
 
-            if (y._type == Types.Boolean)
+            if (y._type == InternalTypes.Boolean)
             {
                 return Equal(x, TypeConverter.ToNumber(y));
             }
 
-            if (y._type == Types.Object && (x._type == Types.String || x._type == Types.Number))
+            if (y._type == InternalTypes.Object && (x._type == InternalTypes.String || x.IsNumber()))
             {
                 return Equal(x, TypeConverter.ToPrimitive(y));
             }
 
-            if (x._type == Types.Object && (y._type == Types.String || y._type == Types.Number))
+            if (x._type == InternalTypes.Object && (y._type == InternalTypes.String || y.IsNumber()))
             {
                 return Equal(TypeConverter.ToPrimitive(x), y);
             }
@@ -235,19 +271,35 @@ namespace Jint.Runtime.Interpreter.Expressions
 
         protected internal static bool SameValue(JsValue x, JsValue y)
         {
-            var typea = TypeConverter.GetPrimitiveType(x);
-            var typeb = TypeConverter.GetPrimitiveType(y);
+            var typea = TypeConverter.GetInternalPrimitiveType(x);
+            var typeb = TypeConverter.GetInternalPrimitiveType(y);
 
             if (typea != typeb)
             {
-                return false;
+                if (typea == InternalTypes.Integer)
+                {
+                    typea = InternalTypes.Number;
+                }
+                if (typeb == InternalTypes.Integer)
+                {
+                    typeb = InternalTypes.Number;
+                }
+
+                if (typea != typeb)
+                {
+                    return false;
+                }
             }
 
             switch (typea)
             {
-                case Types.None:
+                case InternalTypes.None:
                     return true;
-                case Types.Number:
+
+                case InternalTypes.Integer:
+                    return x.AsInteger() == y.AsInteger();
+
+                case InternalTypes.Number:
                     var nx = TypeConverter.ToNumber(x);
                     var ny = TypeConverter.ToNumber(y);
 
@@ -268,16 +320,39 @@ namespace Jint.Runtime.Interpreter.Expressions
                     }
 
                     return false;
-                case Types.String:
+                case InternalTypes.String:
                     return TypeConverter.ToString(x) == TypeConverter.ToString(y);
-                case Types.Boolean:
+                case InternalTypes.Boolean:
                     return TypeConverter.ToBoolean(x) == TypeConverter.ToBoolean(y);
                 default:
                     return x == y;
             }
         }
 
-        protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true)
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        protected static JsValue Compare(JsValue x, JsValue y, bool leftFirst = true) =>
+            x._type == y._type && x._type == InternalTypes.Integer
+                ? CompareInteger(x, y, leftFirst)
+                : CompareComplex(x, y, leftFirst);
+
+        private static JsValue CompareInteger(JsValue x, JsValue y, bool leftFirst)
+        {
+            int nx, ny;
+            if (leftFirst)
+            {
+                nx = x.AsInteger();
+                ny = y.AsInteger();
+            }
+            else
+            {
+                ny = y.AsInteger();
+                nx = x.AsInteger();
+            }
+
+            return nx < ny;
+        }
+
+        private static  JsValue CompareComplex(JsValue x, JsValue y, bool leftFirst)
         {
             JsValue px, py;
             if (leftFirst)
@@ -331,10 +406,8 @@ namespace Jint.Runtime.Interpreter.Expressions
 
                 return nx < ny;
             }
-            else
-            {
-                return string.CompareOrdinal(TypeConverter.ToString(x), TypeConverter.ToString(y)) < 0;
-            }
+
+            return string.CompareOrdinal(TypeConverter.ToString(x), TypeConverter.ToString(y)) < 0;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -432,5 +505,11 @@ namespace Jint.Runtime.Interpreter.Expressions
                 out record,
                 out value);
         }
+        
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        protected static bool AreIntegerOperands(JsValue left, JsValue right)
+        {
+            return left._type == right._type && left._type == InternalTypes.Integer;
+        }
     }
 }

+ 19 - 9
Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs

@@ -1,3 +1,4 @@
+using System;
 using Esprima;
 using Esprima.Ast;
 using Jint.Native;
@@ -6,11 +7,19 @@ namespace Jint.Runtime.Interpreter.Expressions
 {
     internal class JintLiteralExpression : JintExpression
     {
-        internal readonly JsValue _cachedValue;
+        private JintLiteralExpression(Engine engine, Literal expression) : base(engine, expression)
+        {
+        }
 
-        public JintLiteralExpression(Engine engine, Literal expression) : base(engine, expression)
+        internal static JintExpression Build(Engine engine, Literal expression)
         {
-            _cachedValue = ConvertToJsValue(expression);
+            var constantValue = ConvertToJsValue(expression);
+            if (!(constantValue is null))
+            {
+                return new JintConstantExpression(engine, expression, constantValue);
+            }
+            
+            return new JintLiteralExpression(engine, expression);
         }
 
         internal static JsValue ConvertToJsValue(Literal literal)
@@ -27,7 +36,10 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             if (literal.TokenType == TokenType.NumericLiteral)
             {
-                return JsNumber.Create(literal.NumericValue);
+                return int.TryParse(literal.Raw, out var intValue)
+                       && (intValue != 0 || BitConverter.DoubleToInt64Bits(literal.NumericValue) != JsNumber.NegativeZeroBits)
+                    ? JsNumber.Create(intValue)
+                    : JsNumber.Create(literal.NumericValue);
             }
 
             if (literal.TokenType == TokenType.StringLiteral)
@@ -42,13 +54,11 @@ namespace Jint.Runtime.Interpreter.Expressions
         {
             // need to notify correct node when taking shortcut
             _engine._lastSyntaxNode = _expression;
-            return _cachedValue ?? ResolveValue();
+            
+            return ResolveValue();
         }
 
-        protected override object EvaluateInternal()
-        {
-            return _cachedValue ?? ResolveValue();
-        }
+        protected override object EvaluateInternal() => ResolveValue();
 
         private JsValue ResolveValue()
         {

+ 2 - 2
Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs

@@ -24,7 +24,7 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             if (!expression.Computed)
             {
-                _determinedPropertyName = ((Esprima.Ast.Identifier) expression.Property).Name;
+                _determinedPropertyName = ((Identifier) expression.Property).Name;
             }
             else
             {
@@ -70,7 +70,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 }
             }
 
-            var propertyName = !string.IsNullOrEmpty(_determinedPropertyName.Name)
+            var propertyName = _determinedPropertyName.Name.Length > 0
                 ? _determinedPropertyName
                 : (Key) TypeConverter.ToPropertyKey(_propertyExpression.GetValue());
 

+ 46 - 4
Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs

@@ -10,22 +10,48 @@ namespace Jint.Runtime.Interpreter.Expressions
         private readonly JintExpression _argument;
         private readonly UnaryOperator _operator;
 
-        public JintUnaryExpression(Engine engine, UnaryExpression expression) : base(engine, expression)
+        private JintUnaryExpression(Engine engine, UnaryExpression expression) : base(engine, expression)
         {
             _argument = Build(engine, expression.Argument);
             _operator = expression.Operator;
         }
 
+        internal static JintExpression Build(Engine engine, UnaryExpression expression)
+        {
+            if (expression.Operator == UnaryOperator.Minus
+                && expression.Argument is Literal literal)
+            {
+                var value = JintLiteralExpression.ConvertToJsValue(literal);
+                if (!(value is null))
+                {
+                    // valid for caching
+                    return new JintConstantExpression(engine, expression, EvaluateMinus(value));
+                }
+            }
+
+            return new JintUnaryExpression(engine, expression);
+        }
+
+        public override JsValue GetValue()
+        {
+            // need to notify correct node when taking shortcut
+            _engine._lastSyntaxNode = _expression;
+
+            return (JsValue) EvaluateInternal();
+        }
+
         protected override object EvaluateInternal()
         {
             switch (_operator)
             {
                 case UnaryOperator.Plus:
-                    return JsNumber.Create(TypeConverter.ToNumber(_argument.GetValue()));
+                    var plusValue = _argument.GetValue();
+                    return plusValue.IsInteger() && plusValue.AsInteger() != 0
+                        ? plusValue
+                        : JsNumber.Create(TypeConverter.ToNumber(plusValue));
 
                 case UnaryOperator.Minus:
-                    var n = TypeConverter.ToNumber(_argument.GetValue());
-                    return JsNumber.Create(double.IsNaN(n) ? double.NaN : n * -1);
+                    return EvaluateMinus(_argument.GetValue());
 
                 case UnaryOperator.BitwiseNot:
                     return JsNumber.Create(~TypeConverter.ToInt32(_argument.GetValue()));
@@ -116,5 +142,21 @@ namespace Jint.Runtime.Interpreter.Expressions
                     return ExceptionHelper.ThrowArgumentException<object>();
             }
         }
+
+        private static JsNumber EvaluateMinus(JsValue value)
+        {
+            var minusValue = value;
+            if (minusValue.IsInteger())
+            {
+                var asInteger = minusValue.AsInteger();
+                if (asInteger != 0)
+                {
+                    return JsNumber.Create(asInteger * -1);
+                }
+            }
+
+            var n = TypeConverter.ToNumber(minusValue);
+            return JsNumber.Create(double.IsNaN(n) ? double.NaN : n * -1);
+        }
     }
 }

+ 15 - 6
Jint/Runtime/Interpreter/Expressions/JintUpdateExpression.cs

@@ -54,13 +54,18 @@ namespace Jint.Runtime.Interpreter.Expressions
 
             reference.AssertValid(_engine);
 
-            var oldValue = TypeConverter.ToNumber(_engine.GetValue(reference, false));
-            var newValue = oldValue + _change;
+            var value = _engine.GetValue(reference, false);
+            var isInteger = value._type == InternalTypes.Integer;
+            var newValue = isInteger
+                ? JsNumber.Create(value.AsInteger() + _change)
+                : JsNumber.Create(TypeConverter.ToNumber(value) + _change);
 
             _engine.PutValue(reference, newValue);
             _engine._referencePool.Return(reference);
 
-            return JsNumber.Create(_prefix ? newValue : oldValue);
+            return _prefix
+                ? newValue
+                : (isInteger ? value : JsNumber.Create(TypeConverter.ToNumber(value)));
         }
 
         private JsValue UpdateIdentifier()
@@ -77,11 +82,15 @@ namespace Jint.Runtime.Interpreter.Expressions
                     ExceptionHelper.ThrowSyntaxError(_engine);
                 }
 
-                var oldValue = TypeConverter.ToNumber(value);
-                var newValue = oldValue + _change;
+                var isInteger = value._type == InternalTypes.Integer;
+                var newValue = isInteger
+                    ? JsNumber.Create(value.AsInteger() + _change)
+                    : JsNumber.Create(TypeConverter.ToNumber(value) + _change);
 
                 environmentRecord.SetMutableBinding(name, newValue, strict);
-                return JsNumber.Create(_prefix ? newValue : oldValue);
+                return _prefix
+                    ? newValue
+                    : (isInteger ? value : JsNumber.Create(TypeConverter.ToNumber(value)));
             }
 
             return null;

+ 2 - 2
Jint/Runtime/Interpreter/Statements/JintForInStatement.cs

@@ -56,9 +56,9 @@ namespace Jint.Runtime.Interpreter.Statements
                 var keys = _engine.Object.GetOwnPropertyNames(Undefined.Instance, Arguments.From(cursor)).AsArray();
 
                 var length = keys.GetLength();
-                for (var i = 0; i < length; i++)
+                for (uint i = 0; i < length; i++)
                 {
-                    var p = keys.GetOwnProperty(TypeConverter.ToString(i)).Value.AsStringWithoutTypeCheck();
+                    var p = keys.GetOwnProperty(i).Value.AsStringWithoutTypeCheck();
 
                     if (processedKeys.Contains(p))
                     {

+ 4 - 4
Jint/Runtime/References/Reference.cs

@@ -42,21 +42,21 @@ namespace Jint.Runtime.References
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool HasPrimitiveBase()
         {
-            return _baseValue._type != Types.Object && _baseValue._type != Types.None;
+            return _baseValue._type != InternalTypes.Object && _baseValue._type != InternalTypes.None;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsUnresolvableReference()
         {
-            return _baseValue._type == Types.Undefined;
+            return _baseValue._type == InternalTypes.Undefined;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public bool IsPropertyReference()
         {
             // http://www.ecma-international.org/ecma-262/5.1/#sec-8.7
-            return _baseValue._type != Types.Object && _baseValue._type != Types.None
-                   || _baseValue._type == Types.Object && !(_baseValue is EnvironmentRecord);
+            return _baseValue._type != InternalTypes.Object && _baseValue._type != InternalTypes.None
+                   || _baseValue._type == InternalTypes.Object && !(_baseValue is EnvironmentRecord);
         }
 
         internal Reference Reassign(JsValue baseValue, in Key name, bool strict)

+ 70 - 84
Jint/Runtime/TypeConverter.cs

@@ -10,7 +10,6 @@ using Jint.Native.Number.Dtoa;
 using Jint.Native.Object;
 using Jint.Native.String;
 using Jint.Pooling;
-using Jint.Runtime.References;
 
 namespace Jint.Runtime
 {
@@ -24,7 +23,21 @@ namespace Jint.Runtime
         Number = 5,
         Symbol = 9,
         Object = 10,
-        Completion = 20,
+        Completion = 20
+    }
+
+    internal enum InternalTypes
+    {
+        None = 0,
+        Undefined = 1,
+        Null = 2,
+        Boolean = 3,
+        String = 4,
+        Number = 5,
+        Integer = 6,
+        Symbol = 9,
+        Object = 10,
+        Completion = 20
     }
 
     public static class TypeConverter
@@ -32,7 +45,7 @@ namespace Jint.Runtime
         // how many decimals to check when determining if double is actually an int
         private const double DoubleIsIntegerTolerance = double.Epsilon * 100;
 
-        private static readonly string[] intToString = new string[1024];
+        internal static readonly string[] intToString = new string[1024];
         private static readonly string[] charToString = new string[256];
 
         static TypeConverter()
@@ -55,7 +68,7 @@ namespace Jint.Runtime
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static JsValue ToPrimitive(JsValue input, Types preferredType = Types.None)
         {
-            if (input._type > Types.None && input._type < Types.Object)
+            if (input._type > InternalTypes.None && input._type < InternalTypes.Object)
             {
                 return input;
             }
@@ -71,15 +84,17 @@ namespace Jint.Runtime
         {
             switch (o._type)
             {
-                case Types.Boolean:
+                case InternalTypes.Boolean:
                     return ((JsBoolean) o)._value;
-                case Types.Undefined:
-                case Types.Null:
+                case InternalTypes.Undefined:
+                case InternalTypes.Null:
                     return false;
-                case Types.Number:
+                case InternalTypes.Integer:
+                    return (int) ((JsNumber) o)._value != 0;
+                case InternalTypes.Number:
                     var n = ((JsNumber) o)._value;
                     return n != 0 && !double.IsNaN(n);
-                case Types.String:
+                case InternalTypes.String:
                     return !((JsString) o).IsNullOrEmpty();
                 default:
                     return true;
@@ -92,7 +107,7 @@ namespace Jint.Runtime
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static double ToNumber(JsValue o)
         {
-            return o._type == Types.Number
+            return o.IsNumber()
                 ? ((JsNumber) o)._value
                 : ToNumberUnlikely(o);
         }
@@ -101,17 +116,17 @@ namespace Jint.Runtime
         {
             switch (o._type)
             {
-                case Types.Undefined:
+                case InternalTypes.Undefined:
                     return double.NaN;
-                case Types.Null:
+                case InternalTypes.Null:
                     return 0;
-                case Types.Object when o is IPrimitiveInstance p:
+                case InternalTypes.Object when o is IPrimitiveInstance p:
                     return ToNumber(ToPrimitive(p.PrimitiveValue, Types.Number));
-                case Types.Boolean:
+                case InternalTypes.Boolean:
                     return ((JsBoolean) o)._value ? 1 : 0;
-                case Types.String:
+                case InternalTypes.String:
                     return ToNumber(o.AsStringWithoutTypeCheck());
-                case Types.Symbol:
+                case InternalTypes.Symbol:
                     // TODO proper TypeError would require Engine instance and a lot of API changes
                     return ExceptionHelper.ThrowTypeErrorNoEngine<double>("Cannot convert a Symbol value to a number");
                 default:
@@ -231,8 +246,6 @@ namespace Jint.Runtime
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.4
         /// </summary>
-        /// <param name="o"></param>
-        /// <returns></returns>
         public static double ToInteger(JsValue o)
         {
             var number = ToNumber(o);
@@ -270,34 +283,33 @@ namespace Jint.Runtime
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.5
         /// </summary>
-        /// <param name="o"></param>
-        /// <returns></returns>
         public static int ToInt32(JsValue o)
         {
-            return (int) (uint) ToNumber(o);
+            return o._type == InternalTypes.Integer
+                ? o.AsInteger()
+                : (int) (uint) ToNumber(o);
         }
 
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.6
         /// </summary>
-        /// <param name="o"></param>
-        /// <returns></returns>
         public static uint ToUint32(JsValue o)
         {
-            return (uint) ToNumber(o);
+            return o._type == InternalTypes.Integer
+                ? (uint) o.AsInteger()
+                : (uint) ToNumber(o);
         }
 
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-9.7
         /// </summary>
-        /// <param name="o"></param>
-        /// <returns></returns>
         public static ushort ToUint16(JsValue o)
         {
-            return (ushort) (uint) ToNumber(o);
+            return  o._type == InternalTypes.Integer
+                ? (ushort) (uint) o.AsInteger()
+                : (ushort) (uint) ToNumber(o);
         }
 
-
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static string ToString(long i)
         {
@@ -330,7 +342,6 @@ namespace Jint.Runtime
                 : c.ToString();
         }
 
-
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal static string ToString(ulong i)
         {
@@ -340,7 +351,7 @@ namespace Jint.Runtime
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static string ToString(double d)
+        internal static string  ToString(double d)
         {
             if (d > long.MinValue && d < long.MaxValue  && Math.Abs(d % 1) <= DoubleIsIntegerTolerance)
             {
@@ -375,11 +386,16 @@ namespace Jint.Runtime
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public static string ToString(JsValue o)
         {
-            if (o._type == Types.String)
+            if (o._type == InternalTypes.String)
             {
                 return o.AsStringWithoutTypeCheck();
             }
 
+            if (o._type == InternalTypes.Integer)
+            {
+                return ToString((int) ((JsNumber) o)._value);
+            }
+
             return ToStringUnlikely(o);
         }
 
@@ -387,17 +403,17 @@ namespace Jint.Runtime
         {
             switch (o._type)
             {
-                case Types.Boolean:
+                case InternalTypes.Boolean:
                     return ((JsBoolean) o)._value ? "true" : "false";
-                case Types.Number:
+                case InternalTypes.Number:
                     return ToString(((JsNumber) o)._value);
-                case Types.Symbol:
+                case InternalTypes.Symbol:
                     return ExceptionHelper.ThrowTypeErrorNoEngine<string>("Cannot convert a Symbol value to a string");
-                case Types.Undefined:
+                case InternalTypes.Undefined:
                     return Undefined.Text;
-                case Types.Null:
+                case InternalTypes.Null:
                     return Null.Text;
-                case Types.Object when o is IPrimitiveInstance p:
+                case InternalTypes.Object when o is IPrimitiveInstance p:
                     return ToString(ToPrimitive(p.PrimitiveValue, Types.String));
                 default:
                     return ToString(ToPrimitive(o, Types.String));
@@ -409,15 +425,16 @@ namespace Jint.Runtime
         {
             switch (value._type)
             {
-                case Types.Object:
+                case InternalTypes.Object:
                     return (ObjectInstance) value;
-                case Types.Boolean:
+                case InternalTypes.Boolean:
                     return engine.Boolean.Construct(((JsBoolean) value)._value);
-                case Types.Number:
+                case InternalTypes.Number:
+                case InternalTypes.Integer:
                     return engine.Number.Construct(((JsNumber) value)._value);
-                case Types.String:
+                case InternalTypes.String:
                     return engine.String.Construct(value.AsStringWithoutTypeCheck());
-                case Types.Symbol:
+                case InternalTypes.Symbol:
                     return engine.Symbol.Construct(((JsSymbol) value)._value);
                 default:
                     ExceptionHelper.ThrowTypeError(engine);
@@ -427,37 +444,23 @@ namespace Jint.Runtime
 
         public static Types GetPrimitiveType(JsValue value)
         {
-            if (value._type == Types.Object)
-            {
-                if (value is IPrimitiveInstance primitive)
-                {
-                    return primitive.Type;
-                }
-
-                return Types.Object;
-            }
-
-            return value.Type;
+            var type = GetInternalPrimitiveType(value);
+            return type == InternalTypes.Integer ? Types.Number : (Types) type;
         }
 
-        public static void CheckObjectCoercible(
-            Engine engine,
-            JsValue o,
-            MemberExpression expression,
-            object baseReference)
+        internal static InternalTypes GetInternalPrimitiveType(JsValue value)
         {
-            if (o._type > Types.Null)
+            if (value._type != InternalTypes.Object)
             {
-                return;
+                return value._type;
             }
 
-            var referenceResolver = engine.Options.ReferenceResolver;
-            if (referenceResolver != null && referenceResolver.CheckCoercible(o))
+            if (value is IPrimitiveInstance primitive)
             {
-                return;
+                return (InternalTypes) primitive.Type;
             }
 
-            ThrowTypeError(engine, o, expression, baseReference);
+            return InternalTypes.Object;
         }
 
         internal static void CheckObjectCoercible(
@@ -466,27 +469,10 @@ namespace Jint.Runtime
             MemberExpression expression,
             string referenceName)
         {
-            if (o._type > Types.Null)
-            {
-                return;
-            }
-
-            var referenceResolver = engine.Options.ReferenceResolver;
-            if (referenceResolver != null && referenceResolver.CheckCoercible(o))
+            if (o._type < InternalTypes.Boolean && (engine.Options.ReferenceResolver?.CheckCoercible(o)).GetValueOrDefault() != true)
             {
-                return;
+                ThrowTypeError(engine, o, expression, referenceName);
             }
-
-            ThrowTypeError(engine, o, expression, referenceName);
-        }
-
-        private static void ThrowTypeError(
-            Engine engine,
-            JsValue o,
-            MemberExpression expression,
-            object baseReference)
-        {
-            ThrowTypeError(engine, o, expression, (baseReference as Reference)?.GetReferencedName());
         }
 
         private static void ThrowTypeError(
@@ -502,7 +488,7 @@ namespace Jint.Runtime
 
         public static void CheckObjectCoercible(Engine engine, JsValue o)
         {
-            if (o._type == Types.Undefined || o._type == Types.Null)
+            if (o._type < InternalTypes.Boolean)
             {
                 ExceptionHelper.ThrowTypeError(engine);
             }