diff --git a/.gitignore b/.gitignore index 846225b9bc..cf4944c2ae 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,4 @@ project.lock.json .build .idea -BenchmarkDotNet.Artifacts +BenchmarkDotNet.Artifacts* diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 20ee920fbc..eb82a0e559 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Esprima; using Esprima.Ast; using Jint.Native; @@ -408,7 +407,7 @@ public Completion ExecuteStatement(Statement statement) return _statements.ExecuteForInStatement(statement.As()); case Nodes.FunctionDeclaration: - return new Completion(Completion.Normal, null, null); + return Completion.Empty; case Nodes.IfStatement: return _statements.ExecuteIfStatement(statement.As()); @@ -728,7 +727,12 @@ public JsValue Invoke(JsValue value, object thisObj, object[] arguments) throw new ArgumentException("Can only invoke functions"); } - return callable.Call(JsValue.FromObject(this, thisObj), arguments.Select(x => JsValue.FromObject(this, x)).ToArray()); + var items = new JsValue[arguments.Length]; + for (int i = 0; i < arguments.Length; ++i) + { + items[i] = JsValue.FromObject(this, arguments[i]); + } + return callable.Call(JsValue.FromObject(this, thisObj), items); } /// @@ -766,7 +770,12 @@ public JsValue GetValue(JsValue scope, string propertyName) } // http://www.ecma-international.org/ecma-262/5.1/#sec-10.5 - public void DeclarationBindingInstantiation(DeclarationBindingType declarationBindingType, IEnumerable functionDeclarations, IEnumerable variableDeclarations, FunctionInstance functionInstance, JsValue[] arguments) + public void DeclarationBindingInstantiation( + DeclarationBindingType declarationBindingType, + IList functionDeclarations, + IList variableDeclarations, + FunctionInstance functionInstance, + JsValue[] arguments) { var env = ExecutionContext.VariableEnvironment.Record; bool configurableBindings = declarationBindingType == DeclarationBindingType.EvalCode; @@ -776,8 +785,9 @@ public void DeclarationBindingInstantiation(DeclarationBindingType declarationBi { var argCount = arguments.Length; var n = 0; - foreach (var argName in functionInstance.FormalParameters) + for (var i = 0; i < functionInstance.FormalParameters.Length; i++) { + var argName = functionInstance.FormalParameters[i]; n++; var v = n > argCount ? Undefined.Instance : arguments[n - 1]; var argAlreadyDeclared = env.HasBinding(argName); @@ -790,8 +800,9 @@ public void DeclarationBindingInstantiation(DeclarationBindingType declarationBi } } - foreach (var f in functionDeclarations) + for (var i = 0; i < functionDeclarations.Count; i++) { + var f = functionDeclarations[i]; var fn = f.Id.Name; var fo = Function.CreateFunctionObject(f); var funcAlreadyDeclared = env.HasBinding(fn); @@ -808,12 +819,12 @@ public void DeclarationBindingInstantiation(DeclarationBindingType declarationBi if (existingProp.Configurable.Value) { go.DefineOwnProperty(fn, - new PropertyDescriptor( - value: Undefined.Instance, - writable: true, - enumerable: true, - configurable: configurableBindings - ), true); + new PropertyDescriptor( + value: Undefined.Instance, + writable: true, + enumerable: true, + configurable: configurableBindings + ), true); } else { @@ -854,14 +865,19 @@ public void DeclarationBindingInstantiation(DeclarationBindingType declarationBi } // process all variable declarations in the current parser scope - foreach (var d in variableDeclarations.SelectMany(x => x.Declarations)) + for (var i = 0; i < variableDeclarations.Count; i++) { - var dn = d.Id.As().Name; - var varAlreadyDeclared = env.HasBinding(dn); - if (!varAlreadyDeclared) + var variableDeclaration = variableDeclarations[i]; + var declarations = (List) variableDeclaration.Declarations; + foreach (var d in declarations) { - env.CreateMutableBinding(dn, configurableBindings); - env.SetMutableBinding(dn, Undefined.Instance, strict); + var dn = d.Id.As().Name; + var varAlreadyDeclared = env.HasBinding(dn); + if (!varAlreadyDeclared) + { + env.CreateMutableBinding(dn, configurableBindings); + env.SetMutableBinding(dn, Undefined.Instance, strict); + } } } } diff --git a/Jint/Native/Function/FunctionPrototype.cs b/Jint/Native/Function/FunctionPrototype.cs index 5f8b33c643..b807d31402 100644 --- a/Jint/Native/Function/FunctionPrototype.cs +++ b/Jint/Native/Function/FunctionPrototype.cs @@ -1,6 +1,4 @@ -using System.Collections.Generic; -using System.Linq; -using Jint.Native.Object; +using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; @@ -49,7 +47,7 @@ private JsValue Bind(JsValue thisObj, JsValue[] arguments) var f = new BindFunctionInstance(Engine) {Extensible = true}; f.TargetFunction = thisObj; f.BoundThis = thisArg; - f.BoundArgs = arguments.Skip(1).ToArray(); + f.BoundArgs = arguments.Skip(1); f.Prototype = Engine.Function.PrototypeObject; var o = target as FunctionInstance; @@ -126,7 +124,7 @@ public JsValue CallImpl(JsValue thisObject, JsValue[] arguments) throw new JavaScriptException(Engine.TypeError); } - return func.Call(arguments.At(0), arguments.Length == 0 ? arguments : arguments.Skip(1).ToArray()); + return func.Call(arguments.At(0), arguments.Length == 0 ? arguments : arguments.Skip(1)); } public override JsValue Call(JsValue thisObject, JsValue[] arguments) diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index 52b3d5a065..196d4e6852 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Collections.Generic; +using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native.Object; using Jint.Runtime; @@ -22,7 +23,7 @@ public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor /// /// public ScriptFunctionInstance(Engine engine, IFunction functionDeclaration, LexicalEnvironment scope, bool strict) - : base(engine, functionDeclaration.Params.Select(x => x.As().Name).ToArray(), scope, strict) + : base(engine, GetParameterNames(functionDeclaration), scope, strict) { _functionDeclaration = functionDeclaration; @@ -48,6 +49,20 @@ public ScriptFunctionInstance(Engine engine, IFunction functionDeclaration, Lexi } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static string[] GetParameterNames(IFunction functionDeclaration) + { + var list = (List) functionDeclaration.Params; + var count = list.Count; + var names = new string[count]; + for (var i = 0; i < count; ++i) + { + names[i] = ((Identifier) list[i]).Name; + } + + return names; + } + /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.1 /// diff --git a/Jint/Native/Number/NumberPrototype.cs b/Jint/Native/Number/NumberPrototype.cs index c9192a66e3..f26626f176 100644 --- a/Jint/Native/Number/NumberPrototype.cs +++ b/Jint/Native/Number/NumberPrototype.cs @@ -12,6 +12,8 @@ namespace Jint.Native.Number /// public sealed class NumberPrototype : NumberInstance { + private static readonly char[] _numberSeparators = {'.', 'e'}; + private NumberPrototype(Engine engine) : base(engine) { @@ -154,7 +156,7 @@ private JsValue ToPrecision(JsValue thisObj, JsValue[] arguments) // Get the number of decimals string str = x.ToString("e23", CultureInfo.InvariantCulture); - int decimals = str.IndexOfAny(new [] { '.', 'e' }); + int decimals = str.IndexOfAny(_numberSeparators); decimals = decimals == -1 ? str.Length : decimals; p -= decimals; diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 48d10de5e9..b664f4921e 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using Jint.Runtime; using Jint.Runtime.Descriptors; -using System; -using System.Collections.Specialized; namespace Jint.Native.Object { diff --git a/Jint/Native/String/StringPrototype.cs b/Jint/Native/String/StringPrototype.cs index 84bb120147..334fa17fae 100644 --- a/Jint/Native/String/StringPrototype.cs +++ b/Jint/Native/String/StringPrototype.cs @@ -320,19 +320,20 @@ private JsValue Split(JsValue thisObj, JsValue[] arguments) } else { - var segments = new List(); + List segments; var sep = TypeConverter.ToString(separator); if (sep == string.Empty || (rx != null && rx.Source == regExpForMatchingAllCharactere)) // for s.split(new RegExp) { + segments = new List(s.Length); foreach (var c in s) { - segments.Add(c.ToString()); + segments.Add(TypeConverter.ToString(c)); } } else { - segments = s.Split(new[] {sep}, StringSplitOptions.None).ToList(); + segments = new List(s.Split(new[] {sep}, StringSplitOptions.None)); } for (int i = 0; i < segments.Count && i < limit; i++) @@ -731,7 +732,7 @@ private JsValue CharAt(JsValue thisObj, JsValue[] arguments) { return ""; } - return s[(int) position].ToString(); + return TypeConverter.ToString(s[(int) position]); } diff --git a/Jint/Runtime/Arguments.cs b/Jint/Runtime/Arguments.cs index 07fd5b9005..a15458688c 100644 --- a/Jint/Runtime/Arguments.cs +++ b/Jint/Runtime/Arguments.cs @@ -1,4 +1,6 @@ -using Jint.Native; +using System; +using System.Runtime.CompilerServices; +using Jint.Native; namespace Jint.Runtime { @@ -18,14 +20,30 @@ public static JsValue[] From(params JsValue[] o) /// The index of the parameter to return /// The value to return is the parameter is not provided /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsValue At(this JsValue[] args, int index, JsValue undefinedValue) { return args.Length > index ? args[index] : undefinedValue; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static JsValue At(this JsValue[] args, int index) { return At(args, index, Undefined.Instance); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static JsValue[] Skip(this JsValue[] args, int count) + { + var newLength = args.Length - count; + if (newLength <= 0) + { + return Array.Empty(); + } + + var array = new JsValue[newLength]; + Array.Copy(args, count, array, 0, newLength); + return array; + } } -} +} \ No newline at end of file diff --git a/Jint/Runtime/Completion.cs b/Jint/Runtime/Completion.cs index 1b2b2d8b80..782cd51b34 100644 --- a/Jint/Runtime/Completion.cs +++ b/Jint/Runtime/Completion.cs @@ -8,11 +8,14 @@ namespace Jint.Runtime /// public class Completion { - public static string Normal = "normal"; - public static string Break = "break"; - public static string Continue = "continue"; - public static string Return = "return"; - public static string Throw = "throw"; + public const string Normal = "normal"; + public const string Break = "break"; + public const string Continue = "continue"; + public const string Return = "return"; + public const string Throw = "throw"; + + public static readonly Completion Empty = new Completion(Normal, null, null); + public static readonly Completion EmptyUndefined = new Completion(Normal, Undefined.Instance, null); public Completion(string type, JsValue value, string identifier) { @@ -21,9 +24,9 @@ public Completion(string type, JsValue value, string identifier) Identifier = identifier; } - public string Type { get; private set; } - public JsValue Value { get; private set; } - public string Identifier { get; private set; } + public string Type { get; } + public JsValue Value { get; } + public string Identifier { get; } public JsValue GetValueOrDefault() { diff --git a/Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs b/Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs index c96c7c2852..496cd781f3 100644 --- a/Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs @@ -11,7 +11,7 @@ namespace Jint.Runtime.Environments public sealed class DeclarativeEnvironmentRecord : EnvironmentRecord { private readonly Engine _engine; - private readonly IDictionary _bindings = new Dictionary(); + private readonly Dictionary _bindings = new Dictionary(); public DeclarativeEnvironmentRecord(Engine engine) : base(engine) { @@ -27,7 +27,7 @@ public override void CreateMutableBinding(string name, bool canBeDeleted = false { _bindings.Add(name, new Binding { - Value = Undefined.Instance, + Value = Undefined.Instance, CanBeDeleted = canBeDeleted, Mutable = true }); @@ -80,7 +80,7 @@ public override bool DeleteBinding(string name) } _bindings.Remove(name); - + return true; } diff --git a/Jint/Runtime/ExpressionIntepreter.cs b/Jint/Runtime/ExpressionIntepreter.cs index 25795123a1..4fe76b0092 100644 --- a/Jint/Runtime/ExpressionIntepreter.cs +++ b/Jint/Runtime/ExpressionIntepreter.cs @@ -1,5 +1,6 @@ using System; -using System.Linq; +using System.Collections.Generic; +using System.Runtime.CompilerServices; using Esprima; using Esprima.Ast; using Jint.Native; @@ -810,12 +811,12 @@ public JsValue EvaluateCallExpression(CallExpression callExpression) } else { - arguments = callExpression.Arguments.Select(x => EvaluateExpression(x.As())).Select(_engine.GetValue).ToArray(); + arguments = BuildArguments(callExpression.Arguments, out bool allLiteral); if (callExpression.CanBeCached) { // The arguments array can be cached if they are all literals - if (callExpression.Arguments.All(x => x is Literal)) + if (allLiteral) { callExpression.CachedArguments = arguments; callExpression.Cached = true; @@ -927,7 +928,7 @@ public JsValue EvaluateUpdateExpression(UpdateExpression updateExpression) if (r != null && r.IsStrict() && (r.GetBase().TryCast() != null) - && (Array.IndexOf(new[] { "eval", "arguments" }, r.GetReferencedName()) != -1)) + && ("eval" == r.GetReferencedName() || "arguments" == r.GetReferencedName())) { throw new JavaScriptException(_engine.SyntaxError); } @@ -943,7 +944,7 @@ public JsValue EvaluateUpdateExpression(UpdateExpression updateExpression) if (r != null && r.IsStrict() && (r.GetBase().TryCast() != null) - && (Array.IndexOf(new[] { "eval", "arguments" }, r.GetReferencedName()) != -1)) + && ("eval" == r.GetReferencedName() || "arguments" == r.GetReferencedName())) { throw new JavaScriptException(_engine.SyntaxError); } @@ -966,7 +967,7 @@ public JsValue EvaluateThisExpression(ThisExpression thisExpression) public JsValue EvaluateNewExpression(NewExpression newExpression) { - var arguments = newExpression.Arguments.Select(x => EvaluateExpression(x.As())).Select(_engine.GetValue).ToArray(); + var arguments = BuildArguments(newExpression.Arguments, out bool _); // todo: optimize by defining a common abstract class or interface var callee = _engine.GetValue(EvaluateExpression(newExpression.Callee)).TryCast(); @@ -984,17 +985,18 @@ public JsValue EvaluateNewExpression(NewExpression newExpression) public JsValue EvaluateArrayExpression(ArrayExpression arrayExpression) { - var a = _engine.Array.Construct(new JsValue[] { arrayExpression.Elements.Count() }); - var n = 0; - foreach (var expr in arrayExpression.Elements) + var elements = (List) arrayExpression.Elements; + var count = elements.Count; + var a = _engine.Array.Construct(new JsValue[] { count }); + for (var n = 0; n < count; n++) { + var expr = elements[n]; if (expr != null) { var value = _engine.GetValue(EvaluateExpression(expr.As())); a.DefineOwnProperty(TypeConverter.ToString(n), new PropertyDescriptor(value, true, true, true), false); } - n++; } return a; @@ -1085,5 +1087,21 @@ public JsValue EvaluateUnaryExpression(UnaryExpression unaryExpression) throw new ArgumentException(); } } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private JsValue[] BuildArguments(List expressionArguments, out bool allLiteral) + { + allLiteral = true; + var count = expressionArguments.Count; + var arguments = new JsValue[count]; + for (var i = 0; i < count; i++) + { + var argument = (Expression) expressionArguments[i]; + arguments[i] = _engine.GetValue(EvaluateExpression(argument)); + allLiteral &= argument is Literal; + } + + return arguments; + } } } diff --git a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs index 4e845a99d3..cb319982fb 100644 --- a/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs +++ b/Jint/Runtime/Interop/MethodInfoFunctionInstance.cs @@ -1,11 +1,8 @@ using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Linq.Expressions; using System.Reflection; using Jint.Native; -using Jint.Native.Array; using Jint.Native.Function; namespace Jint.Runtime.Interop @@ -29,10 +26,9 @@ public override JsValue Call(JsValue thisObject, JsValue[] arguments) public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] jsArguments) { var arguments = ProcessParamsArrays(jsArguments, methodInfos); - var methods = TypeConverter.FindBestMatch(Engine, methodInfos, arguments).ToList(); var converter = Engine.ClrTypeConverter; - foreach (var method in methods) + foreach (var method in TypeConverter.FindBestMatch(Engine, methodInfos, arguments)) { var parameters = new object[arguments.Length]; var argumentsMatch = true; @@ -86,7 +82,7 @@ public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] js // todo: cache method info try { - return JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters.ToArray())); + return JsValue.FromObject(Engine, method.Invoke(thisObject.ToObject(), parameters)); } catch (TargetInvocationException exception) { @@ -108,29 +104,45 @@ public JsValue Invoke(MethodInfo[] methodInfos, JsValue thisObject, JsValue[] js /// /// Reduces a flat list of parameters to a params array /// - private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, IEnumerable methodInfos) + private JsValue[] ProcessParamsArrays(JsValue[] jsArguments, MethodInfo[] methodInfos) { - foreach (var methodInfo in methodInfos) + for (var i = 0; i < methodInfos.Length; i++) { + var methodInfo = methodInfos[i]; var parameters = methodInfo.GetParameters(); - if (!parameters.Any(p => p.HasAttribute())) + + bool hasParamArrayAttribute = false; + for (int j = 0; j < parameters.Length; ++j) + { + if (parameters[j].HasAttribute()) + { + hasParamArrayAttribute = true; + break; + } + } + if (!hasParamArrayAttribute) continue; var nonParamsArgumentsCount = parameters.Length - 1; if (jsArguments.Length < nonParamsArgumentsCount) continue; - var newArgumentsCollection = jsArguments.Take(nonParamsArgumentsCount).ToList(); - var argsToTransform = jsArguments.Skip(nonParamsArgumentsCount).ToList(); + var argsToTransform = jsArguments.Skip(nonParamsArgumentsCount); - if (argsToTransform.Count == 1 && argsToTransform.FirstOrDefault().IsArray()) + if (argsToTransform.Length == 1 && argsToTransform[0].IsArray()) continue; var jsArray = Engine.Array.Construct(Arguments.Empty); - Engine.Array.PrototypeObject.Push(jsArray, argsToTransform.ToArray()); + Engine.Array.PrototypeObject.Push(jsArray, argsToTransform); + + var newArgumentsCollection = new JsValue[nonParamsArgumentsCount + 1]; + for (var j = 0; j < nonParamsArgumentsCount; ++j) + { + newArgumentsCollection[j] = jsArguments[j]; + } - newArgumentsCollection.Add(jsArray.JsValue); - return newArgumentsCollection.ToArray(); + newArgumentsCollection[nonParamsArgumentsCount] = new JsValue(jsArray); + return newArgumentsCollection; } return jsArguments; diff --git a/Jint/Runtime/StatementInterpreter.cs b/Jint/Runtime/StatementInterpreter.cs index d8f7ac0967..48c5c0e2f4 100644 --- a/Jint/Runtime/StatementInterpreter.cs +++ b/Jint/Runtime/StatementInterpreter.cs @@ -25,7 +25,7 @@ private Completion ExecuteStatement(Statement statement) public Completion ExecuteEmptyStatement(EmptyStatement emptyStatement) { - return new Completion(Completion.Normal, null, null); + return Completion.Empty; } public Completion ExecuteExpressionStatement(ExpressionStatement expressionStatement) @@ -49,7 +49,7 @@ public Completion ExecuteIfStatement(IfStatement ifStatement) } else { - return new Completion(Completion.Normal, null, null); + return Completion.Empty; } return result; @@ -217,7 +217,7 @@ public Completion ExecuteForInStatement(ForInStatement forInStatement) var experValue = _engine.GetValue(exprRef); if (experValue == Undefined.Instance || experValue == Null.Instance) { - return new Completion(Completion.Normal, null, null); + return Completion.Empty; } @@ -415,13 +415,15 @@ public Completion ExecuteSwitchBlock(IEnumerable switchBlock, JsValu public Completion ExecuteStatementList(IEnumerable statementList) { - var c = new Completion(Completion.Normal, null, null); + var c = Completion.Empty; Completion sl = c; Statement s = null; + var list = (List) statementList; + try { - foreach (var statement in statementList) + foreach (var statement in list) { s = statement.As(); c = ExecuteStatement(s); @@ -528,7 +530,7 @@ public Completion ExecuteVariableDeclaration(VariableDeclaration statement) } } - return new Completion(Completion.Normal, Undefined.Instance, null); + return Completion.EmptyUndefined; } public Completion ExecuteBlockStatement(BlockStatement blockStatement) @@ -548,7 +550,7 @@ public Completion ExecuteDebuggerStatement(DebuggerStatement debuggerStatement) System.Diagnostics.Debugger.Break(); } - return new Completion(Completion.Normal, null, null); + return Completion.Empty; } } } diff --git a/Jint/Runtime/TypeConverter.cs b/Jint/Runtime/TypeConverter.cs index 6b5a75469a..a0211825ec 100644 --- a/Jint/Runtime/TypeConverter.cs +++ b/Jint/Runtime/TypeConverter.cs @@ -440,7 +440,7 @@ public static void CheckObjectCoercible(Engine engine, JsValue o) public static IEnumerable FindBestMatch(Engine engine, MethodBase[] methods, JsValue[] arguments) { methods = methods - .Where(m => m.GetParameters().Count() == arguments.Length) + .Where(m => m.GetParameters().Length == arguments.Length) .ToArray(); if (methods.Length == 1 && !methods[0].GetParameters().Any())