Browse Source

Improve recursion performance v1 (#1269)

Marko Lahma 3 years ago
parent
commit
3020dff7ac

+ 43 - 0
Jint.Tests/Runtime/EngineLimitTests.cs

@@ -0,0 +1,43 @@
+using System.Text;
+
+namespace Jint.Tests.Runtime;
+
+public class EngineLimitTests
+{
+    [Fact]
+    public void ShouldAllowReasonableCallStackDepth()
+    {
+#if RELEASE
+        const int FunctionNestingCount = 350;
+#else
+        const int FunctionNestingCount = 170;
+#endif
+
+        // generate call tree
+        var sb = new StringBuilder();
+        sb.AppendLine("var x = 10;");
+        sb.AppendLine();
+        for (var i = 1; i <= FunctionNestingCount; ++i)
+        {
+            sb.Append("function func").Append(i).Append("(func").Append(i).AppendLine("Param) {");
+            sb.Append("    ");
+            if (i != FunctionNestingCount)
+            {
+                // just to create a bit more nesting add some constructs
+                sb.Append("return x++ > 1 ? func").Append(i + 1).Append("(func").Append(i).AppendLine("Param): undefined;");
+            }
+            else
+            {
+                // use known CLR function to add breakpoint
+                sb.Append("return Math.max(0, func").Append(i).AppendLine("Param);");
+            }
+
+            sb.AppendLine("}");
+            sb.AppendLine();
+        }
+
+        var engine = new Engine();
+        engine.Execute(sb.ToString());
+        Assert.Equal(123, engine.Evaluate("func1(123);").AsNumber());
+    }
+}

+ 4 - 0
Jint/Engine.cs

@@ -1371,6 +1371,8 @@ namespace Jint
             JsValue[] arguments,
             JsValue[] arguments,
             JintExpression? expression)
             JintExpression? expression)
         {
         {
+            // ensure logic is in sync between Call, Construct and JintCallExpression!
+
             var recursionDepth = CallStack.Push(functionInstance, expression, ExecutionContext);
             var recursionDepth = CallStack.Push(functionInstance, expression, ExecutionContext);
 
 
             if (recursionDepth > Options.Constraints.MaxRecursionDepth)
             if (recursionDepth > Options.Constraints.MaxRecursionDepth)
@@ -1402,6 +1404,8 @@ namespace Jint
             JsValue newTarget,
             JsValue newTarget,
             JintExpression? expression)
             JintExpression? expression)
         {
         {
+            // ensure logic is in sync between Call, Construct and JintCallExpression!
+
             var recursionDepth = CallStack.Push(functionInstance, expression, ExecutionContext);
             var recursionDepth = CallStack.Push(functionInstance, expression, ExecutionContext);
 
 
             if (recursionDepth > Options.Constraints.MaxRecursionDepth)
             if (recursionDepth > Options.Constraints.MaxRecursionDepth)

+ 119 - 78
Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs

@@ -1,3 +1,5 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native;
 using Jint.Native.Function;
 using Jint.Native.Function;
@@ -69,50 +71,18 @@ namespace Jint.Runtime.Interpreter.Expressions
 
 
         protected override ExpressionResult EvaluateInternal(EvaluationContext context)
         protected override ExpressionResult EvaluateInternal(EvaluationContext context)
         {
         {
-            return NormalCompletion(_calleeExpression is JintSuperExpression
-                ? SuperCall(context)
-                : Call(context)
-            );
-        }
-
-        private JsValue SuperCall(EvaluationContext context)
-        {
-            var engine = context.Engine;
-            var thisEnvironment = (FunctionEnvironmentRecord) engine.ExecutionContext.GetThisEnvironment();
-            var newTarget = engine.GetNewTarget(thisEnvironment);
-            var func = GetSuperConstructor(thisEnvironment);
-            if (func is null || !func.IsConstructor)
+            if (_calleeExpression._expression.Type == Nodes.Super)
             {
             {
-                ExceptionHelper.ThrowTypeError(engine.Realm, "Not a constructor");
+                return NormalCompletion(SuperCall(context));
             }
             }
 
 
-            var argList = ArgumentListEvaluation(context);
-            var result = ((IConstructor) func).Construct(argList, newTarget ?? JsValue.Undefined);
-            var thisER = (FunctionEnvironmentRecord) engine.ExecutionContext.GetThisEnvironment();
-            return thisER.BindThisValue(result);
-        }
-
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-getsuperconstructor
-        /// </summary>
-        private static ObjectInstance? GetSuperConstructor(FunctionEnvironmentRecord thisEnvironment)
-        {
-            var envRec = thisEnvironment;
-            var activeFunction = envRec._functionObject;
-            var superConstructor = activeFunction.GetPrototypeOf();
-            return superConstructor;
-        }
+            // https://tc39.es/ecma262/#sec-function-calls
 
 
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-function-calls
-        /// </summary>
-        private JsValue Call(EvaluationContext context)
-        {
             var reference = _calleeExpression.Evaluate(context).Value;
             var reference = _calleeExpression.Evaluate(context).Value;
 
 
             if (ReferenceEquals(reference, Undefined.Instance))
             if (ReferenceEquals(reference, Undefined.Instance))
             {
             {
-                return Undefined.Instance;
+                return NormalCompletion(Undefined.Instance);
             }
             }
 
 
             var engine = context.Engine;
             var engine = context.Engine;
@@ -120,48 +90,29 @@ namespace Jint.Runtime.Interpreter.Expressions
 
 
             if (func.IsNullOrUndefined() && _expression.IsOptional())
             if (func.IsNullOrUndefined() && _expression.IsOptional())
             {
             {
-                return Undefined.Instance;
+                return NormalCompletion(Undefined.Instance);
             }
             }
 
 
-            if (reference is Reference referenceRecord
+            var referenceRecord = reference as Reference;
+            if (referenceRecord != null
                 && !referenceRecord.IsPropertyReference()
                 && !referenceRecord.IsPropertyReference()
                 && referenceRecord.GetReferencedName() == CommonProperties.Eval
                 && referenceRecord.GetReferencedName() == CommonProperties.Eval
                 && ReferenceEquals(func, engine.Realm.Intrinsics.Eval))
                 && ReferenceEquals(func, engine.Realm.Intrinsics.Eval))
             {
             {
-                var argList = ArgumentListEvaluation(context);
-                if (argList.Length == 0)
-                {
-                    return Undefined.Instance;
-                }
-
-                var evalFunctionInstance = (EvalFunctionInstance) func;
-                var evalArg = argList[0];
-                var strictCaller = StrictModeScope.IsStrictModeCode;
-                var evalRealm = evalFunctionInstance._realm;
-                var direct = !_expression.IsOptional();
-                var value = evalFunctionInstance.PerformEval(evalArg, evalRealm, strictCaller, direct);
-                engine._referencePool.Return(referenceRecord);
-                return value;
+                return HandleEval(context, func, engine, referenceRecord);
             }
             }
 
 
             var thisCall = (CallExpression) _expression;
             var thisCall = (CallExpression) _expression;
             var tailCall = IsInTailPosition(thisCall);
             var tailCall = IsInTailPosition(thisCall);
-            return EvaluateCall(context, func, reference, thisCall.Arguments, tailCall);
-        }
 
 
-        /// <summary>
-        /// https://tc39.es/ecma262/#sec-evaluatecall
-        /// </summary>
-        private JsValue EvaluateCall(EvaluationContext context, JsValue func, object reference, in NodeList<Expression> arguments, bool tailPosition)
-        {
-            JsValue thisValue;
-            var referenceRecord = reference as Reference;
-            var engine = context.Engine;
+            // https://tc39.es/ecma262/#sec-evaluatecall
+
+            JsValue thisObject;
             if (referenceRecord is not null)
             if (referenceRecord is not null)
             {
             {
                 if (referenceRecord.IsPropertyReference())
                 if (referenceRecord.IsPropertyReference())
                 {
                 {
-                    thisValue = referenceRecord.GetThisValue();
+                    thisObject = referenceRecord.GetThisValue();
                 }
                 }
                 else
                 else
                 {
                 {
@@ -171,52 +122,142 @@ namespace Jint.Runtime.Interpreter.Expressions
                     if (baseValue.IsNullOrUndefined()
                     if (baseValue.IsNullOrUndefined()
                         && engine._referenceResolver.TryUnresolvableReference(engine, referenceRecord, out var value))
                         && engine._referenceResolver.TryUnresolvableReference(engine, referenceRecord, out var value))
                     {
                     {
-                        thisValue = value;
+                        thisObject = value;
                     }
                     }
                     else
                     else
                     {
                     {
                         var refEnv = (EnvironmentRecord) baseValue;
                         var refEnv = (EnvironmentRecord) baseValue;
-                        thisValue = refEnv.WithBaseObject();
+                        thisObject = refEnv.WithBaseObject();
                     }
                     }
                 }
                 }
             }
             }
             else
             else
             {
             {
-                thisValue = Undefined.Instance;
+                thisObject = Undefined.Instance;
             }
             }
 
 
-            var argList = ArgumentListEvaluation(context);
+            var arguments = ArgumentListEvaluation(context);
 
 
             if (!func.IsObject() && !engine._referenceResolver.TryGetCallable(engine, reference, out func))
             if (!func.IsObject() && !engine._referenceResolver.TryGetCallable(engine, reference, out func))
             {
             {
-                var message = referenceRecord == null
-                    ? reference + " is not a function"
-                    : $"Property '{referenceRecord.GetReferencedName()}' of object is not a function";
-                ExceptionHelper.ThrowTypeError(engine.Realm, message);
+                ThrowMemberisNotFunction(referenceRecord, reference, engine);
             }
             }
 
 
             var callable = func as ICallable;
             var callable = func as ICallable;
             if (callable is null)
             if (callable is null)
             {
             {
-                var message = $"{referenceRecord?.GetReferencedName() ?? reference} is not a function";
-                ExceptionHelper.ThrowTypeError(engine.Realm, message);
+                ThrowReferenceNotFunction(referenceRecord, reference, engine);
             }
             }
 
 
-            if (tailPosition)
+            if (tailCall)
             {
             {
                 // TODO tail call
                 // TODO tail call
                 // PrepareForTailCall();
                 // PrepareForTailCall();
             }
             }
 
 
-            var result = engine.Call(callable, thisValue, argList, _calleeExpression);
+            // ensure logic is in sync between Call, Construct and JintCallExpression!
 
 
-            if (!_cached && argList.Length > 0)
+            JsValue result;
+            if (callable is FunctionInstance functionInstance)
             {
             {
-                engine._jsValueArrayPool.ReturnArray(argList);
+                var callStack = engine.CallStack;
+                var recursionDepth = callStack.Push(functionInstance, _calleeExpression, engine.ExecutionContext);
+
+                if (recursionDepth > engine.Options.Constraints.MaxRecursionDepth)
+                {
+                    // automatically pops the current element as it was never reached
+                    ExceptionHelper.ThrowRecursionDepthOverflowException(callStack);
+                }
+
+                try
+                {
+                    result = functionInstance.Call(thisObject, arguments);
+                }
+                finally
+                {
+                    // if call stack was reset due to recursive call to engine or similar, we might not have it anymore
+                    if (callStack.Count > 0)
+                    {
+                        callStack.Pop();
+                    }
+                }
+            }
+            else
+            {
+                result = callable.Call(thisObject, arguments);
+            }
+
+            if (!_cached && arguments.Length > 0)
+            {
+                engine._jsValueArrayPool.ReturnArray(arguments);
             }
             }
 
 
             engine._referencePool.Return(referenceRecord);
             engine._referencePool.Return(referenceRecord);
-            return result;
+            return NormalCompletion(result);
+        }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static void ThrowReferenceNotFunction(Reference? referenceRecord1, object reference, Engine engine)
+        {
+            var message = $"{referenceRecord1?.GetReferencedName() ?? reference} is not a function";
+            ExceptionHelper.ThrowTypeError(engine.Realm, message);
+        }
+
+        [DoesNotReturn]
+        [MethodImpl(MethodImplOptions.NoInlining)]
+        private static void ThrowMemberisNotFunction(Reference? referenceRecord1, object reference, Engine engine)
+        {
+            var message = referenceRecord1 == null
+                ? reference + " is not a function"
+                : $"Property '{referenceRecord1.GetReferencedName()}' of object is not a function";
+            ExceptionHelper.ThrowTypeError(engine.Realm, message);
+        }
+
+        private ExpressionResult HandleEval(EvaluationContext context, JsValue func, Engine engine, Reference referenceRecord)
+        {
+            var argList = ArgumentListEvaluation(context);
+            if (argList.Length == 0)
+            {
+                return NormalCompletion(Undefined.Instance);
+            }
+
+            var evalFunctionInstance = (EvalFunctionInstance) func;
+            var evalArg = argList[0];
+            var strictCaller = StrictModeScope.IsStrictModeCode;
+            var evalRealm = evalFunctionInstance._realm;
+            var direct = !_expression.IsOptional();
+            var value = evalFunctionInstance.PerformEval(evalArg, evalRealm, strictCaller, direct);
+            engine._referencePool.Return(referenceRecord);
+            return NormalCompletion(value);
+        }
+
+        private JsValue SuperCall(EvaluationContext context)
+        {
+            var engine = context.Engine;
+            var thisEnvironment = (FunctionEnvironmentRecord) engine.ExecutionContext.GetThisEnvironment();
+            var newTarget = engine.GetNewTarget(thisEnvironment);
+            var func = GetSuperConstructor(thisEnvironment);
+            if (func is null || !func.IsConstructor)
+            {
+                ExceptionHelper.ThrowTypeError(engine.Realm, "Not a constructor");
+            }
+
+            var argList = ArgumentListEvaluation(context);
+            var result = ((IConstructor) func).Construct(argList, newTarget ?? JsValue.Undefined);
+            var thisER = (FunctionEnvironmentRecord) engine.ExecutionContext.GetThisEnvironment();
+            return thisER.BindThisValue(result);
+        }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-getsuperconstructor
+        /// </summary>
+        private static ObjectInstance? GetSuperConstructor(FunctionEnvironmentRecord thisEnvironment)
+        {
+            var envRec = thisEnvironment;
+            var activeFunction = envRec._functionObject;
+            var superConstructor = activeFunction.GetPrototypeOf();
+            return superConstructor;
         }
         }
 
 
         /// <summary>
         /// <summary>

+ 2 - 0
Jint/Runtime/Interpreter/JintFunctionDefinition.cs

@@ -1,3 +1,4 @@
+using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Esprima.Ast;
 using Jint.Native;
 using Jint.Native;
 using Jint.Native.Function;
 using Jint.Native.Function;
@@ -36,6 +37,7 @@ namespace Jint.Runtime.Interpreter
         /// <summary>
         /// <summary>
         /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody
         /// https://tc39.es/ecma262/#sec-runtime-semantics-evaluatebody
         /// </summary>
         /// </summary>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal Completion EvaluateBody(EvaluationContext context, FunctionInstance functionObject, JsValue[] argumentsList)
         internal Completion EvaluateBody(EvaluationContext context, FunctionInstance functionObject, JsValue[] argumentsList)
         {
         {
             Completion result;
             Completion result;

+ 15 - 1
Jint/Runtime/Interpreter/Statements/JintBlockStatement.cs

@@ -20,8 +20,17 @@ namespace Jint.Runtime.Interpreter.Statements
 
 
         internal override bool SupportsResume => true;
         internal override bool SupportsResume => true;
 
 
-        protected override Completion ExecuteInternal(EvaluationContext context)
+        /// <summary>
+        /// Optimized for direct access without virtual dispatch.
+        /// </summary>
+        public Completion ExecuteBlock(EvaluationContext context)
         {
         {
+            if (_statementList is null)
+            {
+                _statementList = new JintStatementList(_statement, _statement.Body);
+                _lexicalDeclarations = HoistingScope.GetLexicalDeclarations(_statement);
+            }
+
             EnvironmentRecord? oldEnv = null;
             EnvironmentRecord? oldEnv = null;
             var engine = context.Engine;
             var engine = context.Engine;
             if (_lexicalDeclarations != null)
             if (_lexicalDeclarations != null)
@@ -41,5 +50,10 @@ namespace Jint.Runtime.Interpreter.Statements
 
 
             return blockValue;
             return blockValue;
         }
         }
+
+        protected override Completion ExecuteInternal(EvaluationContext context)
+        {
+            return ExecuteBlock(context);
+        }
     }
     }
 }
 }

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

@@ -9,7 +9,7 @@ namespace Jint.Runtime.Interpreter.Statements
     /// </summary>
     /// </summary>
     internal sealed class JintDoWhileStatement : JintStatement<DoWhileStatement>
     internal sealed class JintDoWhileStatement : JintStatement<DoWhileStatement>
     {
     {
-        private JintStatement _body = null!;
+        private ProbablyBlockStatement _body;
         private string? _labelSetName;
         private string? _labelSetName;
         private JintExpression _test = null!;
         private JintExpression _test = null!;
 
 
@@ -19,7 +19,7 @@ namespace Jint.Runtime.Interpreter.Statements
 
 
         protected override void Initialize(EvaluationContext context)
         protected override void Initialize(EvaluationContext context)
         {
         {
-            _body = Build(_statement.Body);
+            _body = new ProbablyBlockStatement(_statement.Body);
             _test = JintExpression.Build(context.Engine, _statement.Test);
             _test = JintExpression.Build(context.Engine, _statement.Test);
             _labelSetName = _statement.LabelSet?.Name;
             _labelSetName = _statement.LabelSet?.Name;
         }
         }

+ 3 - 3
Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs

@@ -20,7 +20,7 @@ namespace Jint.Runtime.Interpreter.Statements
         private readonly Expression _rightExpression;
         private readonly Expression _rightExpression;
         private readonly IterationKind _iterationKind;
         private readonly IterationKind _iterationKind;
 
 
-        private JintStatement _body = null!;
+        private ProbablyBlockStatement _body;
         private JintExpression? _expr;
         private JintExpression? _expr;
         private BindingPattern? _assignmentPattern;
         private BindingPattern? _assignmentPattern;
         private JintExpression _right = null!;
         private JintExpression _right = null!;
@@ -87,7 +87,7 @@ namespace Jint.Runtime.Interpreter.Statements
                 _expr = new JintIdentifierExpression((Identifier) _leftNode);
                 _expr = new JintIdentifierExpression((Identifier) _leftNode);
             }
             }
 
 
-            _body = Build(_forBody);
+            _body = new ProbablyBlockStatement(_forBody);
             _right = JintExpression.Build(engine, _rightExpression);
             _right = JintExpression.Build(engine, _rightExpression);
         }
         }
 
 
@@ -147,7 +147,7 @@ namespace Jint.Runtime.Interpreter.Statements
         private Completion BodyEvaluation(
         private Completion BodyEvaluation(
             EvaluationContext context,
             EvaluationContext context,
             JintExpression? lhs,
             JintExpression? lhs,
-            JintStatement stmt,
+            in ProbablyBlockStatement stmt,
             IteratorInstance iteratorRecord,
             IteratorInstance iteratorRecord,
             IterationKind iterationKind,
             IterationKind iterationKind,
             LhsKind lhsKind,
             LhsKind lhsKind,

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

@@ -16,7 +16,7 @@ namespace Jint.Runtime.Interpreter.Statements
         private JintExpression? _test;
         private JintExpression? _test;
         private JintExpression? _increment;
         private JintExpression? _increment;
 
 
-        private JintStatement _body = null!;
+        private ProbablyBlockStatement _body;
         private List<string>? _boundNames;
         private List<string>? _boundNames;
 
 
         private bool _shouldCreatePerIterationEnvironment;
         private bool _shouldCreatePerIterationEnvironment;
@@ -28,7 +28,7 @@ namespace Jint.Runtime.Interpreter.Statements
         protected override void Initialize(EvaluationContext context)
         protected override void Initialize(EvaluationContext context)
         {
         {
             var engine = context.Engine;
             var engine = context.Engine;
-            _body = Build(_statement.Body);
+            _body = new ProbablyBlockStatement(_statement.Body);
 
 
             if (_statement.Init != null)
             if (_statement.Init != null)
             {
             {

+ 5 - 5
Jint/Runtime/Interpreter/Statements/JintIfStatement.cs

@@ -5,9 +5,9 @@ namespace Jint.Runtime.Interpreter.Statements
 {
 {
     internal sealed class JintIfStatement : JintStatement<IfStatement>
     internal sealed class JintIfStatement : JintStatement<IfStatement>
     {
     {
-        private JintStatement _statementConsequent = null!;
+        private ProbablyBlockStatement _statementConsequent;
         private JintExpression _test = null!;
         private JintExpression _test = null!;
-        private JintStatement? _alternate;
+        private ProbablyBlockStatement? _alternate;
 
 
         public JintIfStatement(IfStatement statement) : base(statement)
         public JintIfStatement(IfStatement statement) : base(statement)
         {
         {
@@ -15,9 +15,9 @@ namespace Jint.Runtime.Interpreter.Statements
 
 
         protected override void Initialize(EvaluationContext context)
         protected override void Initialize(EvaluationContext context)
         {
         {
-            _statementConsequent = Build(_statement.Consequent);
+            _statementConsequent = new ProbablyBlockStatement(_statement.Consequent);
             _test = JintExpression.Build(context.Engine, _statement.Test);
             _test = JintExpression.Build(context.Engine, _statement.Test);
-            _alternate = _statement.Alternate != null ? Build(_statement.Alternate) : null;
+            _alternate = _statement.Alternate != null ? new ProbablyBlockStatement(_statement.Alternate) : null;
         }
         }
 
 
         protected override Completion ExecuteInternal(EvaluationContext context)
         protected override Completion ExecuteInternal(EvaluationContext context)
@@ -29,7 +29,7 @@ namespace Jint.Runtime.Interpreter.Statements
             }
             }
             else if (_alternate != null)
             else if (_alternate != null)
             {
             {
-                result = _alternate.Execute(context);
+                result = _alternate.Value.Execute(context);
             }
             }
             else
             else
             {
             {

+ 40 - 33
Jint/Runtime/Interpreter/Statements/JintTryStatement.cs

@@ -10,7 +10,7 @@ namespace Jint.Runtime.Interpreter.Statements
     internal sealed class JintTryStatement : JintStatement<TryStatement>
     internal sealed class JintTryStatement : JintStatement<TryStatement>
     {
     {
         private JintBlockStatement _block = null!;
         private JintBlockStatement _block = null!;
-        private JintStatement? _catch;
+        private JintBlockStatement? _catch;
         private JintBlockStatement? _finalizer;
         private JintBlockStatement? _finalizer;
 
 
         public JintTryStatement(TryStatement statement) : base(statement)
         public JintTryStatement(TryStatement statement) : base(statement)
@@ -37,38 +37,7 @@ namespace Jint.Runtime.Interpreter.Statements
 
 
             if (b.Type == CompletionType.Throw)
             if (b.Type == CompletionType.Throw)
             {
             {
-                // execute catch
-                if (_statement.Handler is not null)
-                {
-                    // initialize lazily
-                    if (_catch is null)
-                    {
-                        _catch = Build(_statement.Handler.Body);
-                    }
-
-                    // https://tc39.es/ecma262/#sec-runtime-semantics-catchclauseevaluation
-
-                    var thrownValue = b.Value;
-                    var oldEnv = engine.ExecutionContext.LexicalEnvironment;
-                    var catchEnv = JintEnvironment.NewDeclarativeEnvironment(engine, oldEnv, catchEnvironment: true);
-
-                    var boundNames = new List<string>();
-                    _statement.Handler.Param.GetBoundNames(boundNames);
-
-                    foreach (var argName in boundNames)
-                    {
-                        catchEnv.CreateMutableBinding(argName, false);
-                    }
-
-                    engine.UpdateLexicalEnvironment(catchEnv);
-
-                    var catchParam = _statement.Handler?.Param;
-                    catchParam.BindingInitialization(context, thrownValue, catchEnv);
-
-                    b = _catch.Execute(context);
-
-                    engine.UpdateLexicalEnvironment(oldEnv);
-                }
+                b = ExecuteCatch(context, b, engine);
             }
             }
 
 
             if (_finalizer != null)
             if (_finalizer != null)
@@ -84,5 +53,43 @@ namespace Jint.Runtime.Interpreter.Statements
 
 
             return b.UpdateEmpty(Undefined.Instance);
             return b.UpdateEmpty(Undefined.Instance);
         }
         }
+
+        private Completion ExecuteCatch(EvaluationContext context, Completion b, Engine engine)
+        {
+            // execute catch
+            if (_statement.Handler is not null)
+            {
+                // initialize lazily
+                if (_catch is null)
+                {
+                    _catch = new JintBlockStatement(_statement.Handler.Body);
+                }
+
+                // https://tc39.es/ecma262/#sec-runtime-semantics-catchclauseevaluation
+
+                var thrownValue = b.Value;
+                var oldEnv = engine.ExecutionContext.LexicalEnvironment;
+                var catchEnv = JintEnvironment.NewDeclarativeEnvironment(engine, oldEnv, catchEnvironment: true);
+
+                var boundNames = new List<string>();
+                _statement.Handler.Param.GetBoundNames(boundNames);
+
+                foreach (var argName in boundNames)
+                {
+                    catchEnv.CreateMutableBinding(argName, false);
+                }
+
+                engine.UpdateLexicalEnvironment(catchEnv);
+
+                var catchParam = _statement.Handler?.Param;
+                catchParam.BindingInitialization(context, thrownValue, catchEnv);
+
+                b = _catch.Execute(context);
+
+                engine.UpdateLexicalEnvironment(oldEnv);
+            }
+
+            return b;
+        }
     }
     }
 }
 }

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

@@ -10,7 +10,7 @@ namespace Jint.Runtime.Interpreter.Statements
     internal sealed class JintWhileStatement : JintStatement<WhileStatement>
     internal sealed class JintWhileStatement : JintStatement<WhileStatement>
     {
     {
         private string? _labelSetName;
         private string? _labelSetName;
-        private JintStatement _body = null!;
+        private ProbablyBlockStatement _body;
         private JintExpression _test = null!;
         private JintExpression _test = null!;
 
 
         public JintWhileStatement(WhileStatement statement) : base(statement)
         public JintWhileStatement(WhileStatement statement) : base(statement)
@@ -20,7 +20,7 @@ namespace Jint.Runtime.Interpreter.Statements
         protected override void Initialize(EvaluationContext context)
         protected override void Initialize(EvaluationContext context)
         {
         {
             _labelSetName = _statement.LabelSet?.Name;
             _labelSetName = _statement.LabelSet?.Name;
-            _body = Build(_statement.Body);
+            _body = new ProbablyBlockStatement(_statement.Body);
             _test = JintExpression.Build(context.Engine, _statement.Test);
             _test = JintExpression.Build(context.Engine, _statement.Test);
         }
         }
 
 

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

@@ -9,7 +9,7 @@ namespace Jint.Runtime.Interpreter.Statements
     /// </summary>
     /// </summary>
     internal sealed class JintWithStatement : JintStatement<WithStatement>
     internal sealed class JintWithStatement : JintStatement<WithStatement>
     {
     {
-        private JintStatement _body = null!;
+        private ProbablyBlockStatement _body;
         private JintExpression _object = null!;
         private JintExpression _object = null!;
 
 
         public JintWithStatement(WithStatement statement) : base(statement)
         public JintWithStatement(WithStatement statement) : base(statement)
@@ -18,7 +18,7 @@ namespace Jint.Runtime.Interpreter.Statements
 
 
         protected override void Initialize(EvaluationContext context)
         protected override void Initialize(EvaluationContext context)
         {
         {
-            _body = Build(_statement.Body);
+            _body = new ProbablyBlockStatement(_statement.Body);
             _object = JintExpression.Build(context.Engine, _statement.Object);
             _object = JintExpression.Build(context.Engine, _statement.Object);
         }
         }
 
 

+ 36 - 0
Jint/Runtime/Interpreter/Statements/ProbablyBlockStatement.cs

@@ -0,0 +1,36 @@
+using System.Runtime.CompilerServices;
+using Esprima.Ast;
+
+namespace Jint.Runtime.Interpreter.Statements;
+
+/// <summary>
+/// Helper to remove virtual dispatch from block statements when it's most common target.
+/// This is especially true for things like for statements body
+/// </summary>
+internal readonly struct ProbablyBlockStatement
+{
+    private readonly JintStatement  _target;
+
+    public ProbablyBlockStatement(Statement statement)
+    {
+        if (statement is BlockStatement blockStatement)
+        {
+            _target = new JintBlockStatement(blockStatement);
+        }
+        else
+        {
+            _target = JintStatement.Build(statement);
+        }
+    }
+
+    [MethodImpl(MethodImplOptions.AggressiveInlining)]
+    public Completion Execute(EvaluationContext context)
+    {
+        if (_target is JintBlockStatement blockStatement)
+        {
+            return blockStatement.ExecuteBlock(context);
+        }
+
+        return _target.Execute(context);
+    }
+}