Skip to content

Commit

Permalink
Reduce stack frame size during Call and Construct (#1267)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Aug 25, 2022
1 parent 82077b2 commit e1e8649
Show file tree
Hide file tree
Showing 8 changed files with 75 additions and 32 deletions.
11 changes: 1 addition & 10 deletions Jint.Tests/Runtime/Debugger/EvaluateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,16 +105,7 @@ function test(x)
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);
Expand Down
19 changes: 8 additions & 11 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ internal ExecutionContext EnterExecutionContext(
return context;
}

internal ExecutionContext EnterExecutionContext(ExecutionContext context)
internal ExecutionContext EnterExecutionContext(in ExecutionContext context)
{
_executionContexts.Push(context);
return context;
Expand Down Expand Up @@ -1291,6 +1291,7 @@ JsValue Callback()
return ExecuteWithConstraints(Options.Strict, Callback);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal JsValue Call(ICallable callable, JsValue thisObject, JsValue[] arguments, JintExpression? expression)
{
if (callable is FunctionInstance functionInstance)
Expand Down Expand Up @@ -1354,14 +1355,12 @@ internal JsValue Call(
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;
Expand All @@ -1387,14 +1386,12 @@ private ObjectInstance Construct(
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;
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/JsBigInt.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public bool Equals(JsBigInt? other)
return false;
}

return ReferenceEquals(this, other) || _value.Equals(other._value);
return ReferenceEquals(this, other) || _value == other._value;
}

public override int GetHashCode()
Expand Down
25 changes: 21 additions & 4 deletions Jint/Runtime/CallStack/CallStackElement.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,7 +19,7 @@ public CallStackElement(

public readonly FunctionInstance Function;
public readonly JintExpression? Expression;
public readonly ExecutionContext CallingExecutionContext;
public readonly CallStackExecutionContext CallingExecutionContext;

public Location Location
{
Expand Down Expand Up @@ -52,5 +51,23 @@ public override string ToString()

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);
}
}
}
}
36 changes: 35 additions & 1 deletion Jint/Runtime/CallStack/JintCallStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,42 @@
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();
Expand All @@ -23,8 +55,9 @@ public JintCallStack(bool trackRecursionDepth)
}
}

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)
{
Expand Down Expand Up @@ -172,5 +205,6 @@ private static string GetPropertyKey(Node expression)

return "?";
}

}
}
8 changes: 6 additions & 2 deletions Jint/Runtime/Debugger/CallFrame.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/Debugger/DebugCallStack.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed class DebugCallStack : IReadOnlyList<CallFrame>
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));
Expand Down
4 changes: 2 additions & 2 deletions Jint/Runtime/ExceptionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ public static void ThrowJavaScriptException(ErrorConstructor errorConstructor, s
}

[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);
}

Expand Down

0 comments on commit e1e8649

Please sign in to comment.