Browse Source

Reduce stack frame size during Call and Construct (#1267)

Marko Lahma 3 years ago
parent
commit
e1e864955d

+ 1 - 10
Jint.Tests/Runtime/Debugger/EvaluateTests.cs

@@ -105,16 +105,7 @@ namespace Jint.Tests.Runtime.Debugger
                 var frameAfter = engine.CallStack.Stack[0];
                 // Stack frames and some of their properties are structs - can't check reference equality
                 // Besides, even if we could, it would be no guarantee. Neither is the following, but it'll do for now.
-                Assert.Equal(frameBefore.CallingExecutionContext.Function,
-                    frameAfter.CallingExecutionContext.Function);
-                Assert.Equal(frameBefore.CallingExecutionContext.LexicalEnvironment,
-                    frameAfter.CallingExecutionContext.LexicalEnvironment);
-                Assert.Equal(frameBefore.CallingExecutionContext.PrivateEnvironment,
-                    frameAfter.CallingExecutionContext.PrivateEnvironment);
-                Assert.Equal(frameBefore.CallingExecutionContext.VariableEnvironment,
-                    frameAfter.CallingExecutionContext.VariableEnvironment);
-                Assert.Equal(frameBefore.CallingExecutionContext.Realm, frameAfter.CallingExecutionContext.Realm);
-
+                Assert.Equal(frameBefore.CallingExecutionContext.LexicalEnvironment, frameAfter.CallingExecutionContext.LexicalEnvironment);
                 Assert.Equal(frameBefore.Arguments, frameAfter.Arguments);
                 Assert.Equal(frameBefore.Expression, frameAfter.Expression);
                 Assert.Equal(frameBefore.Location, frameAfter.Location);

+ 8 - 11
Jint/Engine.cs

@@ -171,7 +171,7 @@ namespace Jint
             return context;
         }
 
-        internal ExecutionContext EnterExecutionContext(ExecutionContext context)
+        internal ExecutionContext EnterExecutionContext(in ExecutionContext context)
         {
             _executionContexts.Push(context);
             return context;
@@ -1291,6 +1291,7 @@ namespace Jint
             return ExecuteWithConstraints(Options.Strict, Callback);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal JsValue Call(ICallable callable, JsValue thisObject, JsValue[] arguments, JintExpression? expression)
         {
             if (callable is FunctionInstance functionInstance)
@@ -1354,14 +1355,12 @@ namespace Jint
             JsValue[] arguments,
             JintExpression? expression)
         {
-            var callStackElement = new CallStackElement(functionInstance, expression, ExecutionContext);
-            var recursionDepth = CallStack.Push(callStackElement);
+            var recursionDepth = CallStack.Push(functionInstance, expression, ExecutionContext);
 
             if (recursionDepth > Options.Constraints.MaxRecursionDepth)
             {
-                // pop the current element as it was never reached
-                CallStack.Pop();
-                ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack, callStackElement.ToString());
+                // automatically pops the current element as it was never reached
+                ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack);
             }
 
             JsValue result;
@@ -1387,14 +1386,12 @@ namespace Jint
             JsValue newTarget,
             JintExpression? expression)
         {
-            var callStackElement = new CallStackElement(functionInstance, expression, ExecutionContext);
-            var recursionDepth = CallStack.Push(callStackElement);
+            var recursionDepth = CallStack.Push(functionInstance, expression, ExecutionContext);
 
             if (recursionDepth > Options.Constraints.MaxRecursionDepth)
             {
-                // pop the current element as it was never reached
-                CallStack.Pop();
-                ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack, callStackElement.ToString());
+                // automatically pops the current element as it was never reached
+                ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack);
             }
 
             ObjectInstance result;

+ 1 - 1
Jint/Native/JsBigInt.cs

@@ -113,7 +113,7 @@ public sealed class JsBigInt : JsValue, IEquatable<JsBigInt>
             return false;
         }
 
-        return ReferenceEquals(this, other) || _value.Equals(other._value);
+        return ReferenceEquals(this, other) || _value == other._value;
     }
 
     public override int GetHashCode()

+ 21 - 4
Jint/Runtime/CallStack/CallStackElement.cs

@@ -1,17 +1,16 @@
 using Esprima;
 using Esprima.Ast;
 using Jint.Native.Function;
-using Jint.Runtime.Environments;
 using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Runtime.CallStack
 {
-    internal readonly struct CallStackElement
+    internal readonly struct CallStackElement : IEquatable<CallStackElement>
     {
         public CallStackElement(
             FunctionInstance function,
             JintExpression? expression,
-            ExecutionContext callingExecutionContext)
+            in CallStackExecutionContext callingExecutionContext)
         {
             Function = function;
             Expression = expression;
@@ -20,7 +19,7 @@ namespace Jint.Runtime.CallStack
 
         public readonly FunctionInstance Function;
         public readonly JintExpression? Expression;
-        public readonly ExecutionContext CallingExecutionContext;
+        public readonly CallStackExecutionContext CallingExecutionContext;
 
         public Location Location
         {
@@ -52,5 +51,23 @@ namespace Jint.Runtime.CallStack
 
             return name ?? "(anonymous)";
         }
+
+        public bool Equals(CallStackElement other)
+        {
+            return Function.Equals(other.Function) && Equals(Expression, other.Expression);
+        }
+
+        public override bool Equals(object? obj)
+        {
+            return obj is CallStackElement other && Equals(other);
+        }
+
+        public override int GetHashCode()
+        {
+            unchecked
+            {
+                return (Function.GetHashCode() * 397) ^ (Expression != null ? Expression.GetHashCode() : 0);
+            }
+        }
     }
 }

+ 35 - 1
Jint/Runtime/CallStack/JintCallStack.cs

@@ -3,10 +3,42 @@ using System.Text;
 using Esprima;
 using Esprima.Ast;
 using Jint.Collections;
+using Jint.Native.Function;
 using Jint.Pooling;
+using Jint.Runtime.Environments;
+using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Runtime.CallStack
 {
+    // smaller version with only required info
+    internal readonly record struct CallStackExecutionContext
+    {
+        public CallStackExecutionContext(in ExecutionContext context)
+        {
+            LexicalEnvironment = context.LexicalEnvironment;
+        }
+
+        internal readonly EnvironmentRecord LexicalEnvironment;
+
+        internal EnvironmentRecord GetThisEnvironment()
+        {
+            var lex = LexicalEnvironment;
+            while (true)
+            {
+                if (lex != null)
+                {
+                    if (lex.HasThisBinding())
+                    {
+                        return lex;
+
+                    }
+
+                    lex = lex._outerEnv;
+                }
+            }
+        }
+    }
+
     internal sealed class JintCallStack
     {
         private readonly RefStack<CallStackElement> _stack = new();
@@ -23,8 +55,9 @@ namespace Jint.Runtime.CallStack
             }
         }
 
-        public int Push(in CallStackElement item)
+        public int Push(FunctionInstance functionInstance, JintExpression? expression, in ExecutionContext executionContext)
         {
+            var item = new CallStackElement(functionInstance, expression, new CallStackExecutionContext(executionContext));
             _stack.Push(item);
             if (_statistics is not null)
             {
@@ -172,5 +205,6 @@ namespace Jint.Runtime.CallStack
 
             return "?";
         }
+
     }
 }

+ 6 - 2
Jint/Runtime/Debugger/CallFrame.cs

@@ -8,11 +8,15 @@ namespace Jint.Runtime.Debugger
 {
     public sealed class CallFrame
     {
-        private readonly ExecutionContext _context;
+        private readonly CallStackExecutionContext _context;
         private readonly CallStackElement? _element;
         private readonly Lazy<DebugScopes> _scopeChain;
 
-        internal CallFrame(CallStackElement? element, ExecutionContext context, Location location, JsValue? returnValue)
+        internal CallFrame(
+            CallStackElement? element,
+            in CallStackExecutionContext context,
+            Location location,
+            JsValue? returnValue)
         {
             _element = element;
             _context = context;

+ 1 - 1
Jint/Runtime/Debugger/DebugCallStack.cs

@@ -12,7 +12,7 @@ namespace Jint.Runtime.Debugger
         internal DebugCallStack(Engine engine, Location location, JintCallStack callStack, JsValue? returnValue)
         {
             _stack = new List<CallFrame>(callStack.Count + 1);
-            var executionContext = engine.ExecutionContext;
+            var executionContext = new CallStackExecutionContext(engine.ExecutionContext);
             foreach (var element in callStack.Stack)
             {
                 _stack.Add(new CallFrame(element, executionContext, location, returnValue));

+ 2 - 2
Jint/Runtime/ExceptionHelper.cs

@@ -152,9 +152,9 @@ namespace Jint.Runtime
         }
 
         [DoesNotReturn]
-        public static void ThrowRecursionDepthOverflowException(JintCallStack currentStack,
-            string currentExpressionReference)
+        public static void ThrowRecursionDepthOverflowException(JintCallStack currentStack)
         {
+            var currentExpressionReference = currentStack.Pop().ToString();
             throw new RecursionDepthOverflowException(currentStack, currentExpressionReference);
         }